// TODO : Adapt stuff from D3D12GraphicsDevice.c specially all the shenanigans with dxgi factory and stuff // TODO : Support more kind of format (HDR) // ============================================================================= // Helpers internal R_D3D12_Window* r_d3d12_window_from_handle(R_Handle handle) { R_D3D12_Window* window = (R_D3D12_Window*)handle.u64[0]; if (window == 0) { window = &r_d3d12_nil_window; } return window; } internal R_Handle r_d3d12_handle_from_window(R_D3D12_Window* window) { R_Handle handle = { 0 }; handle.u64[0] = (uint64)window; return handle; } // ============================================================================= // Wait Function internal void r_d3d12_wait() { r_d3d12_state->global_fence_value += 1; ID3D12CommandQueue_Signal(r_d3d12_state->direct_command_queue, r_d3d12_state->fence, r_d3d12_state->global_fence_value); r_d3d12_wait_for_fence_value(r_d3d12_state->global_fence_value); } internal void r_d3d12_wait_for_fence_value(uint64 fence_value) { if (ID3D12Fence_GetCompletedValue(r_d3d12_state->fence) < fence_value) { ID3D12Fence_SetEventOnCompletion(r_d3d12_state->fence, fence_value, r_d3d12_state->fence_event); WaitForSingleObject(r_d3d12_state->fence_event, INFINITE); } } // ============================================================================= // top level layer initialization void r_init() { HRESULT error = 0; Arena* arena = arena_alloc(); r_d3d12_state = push_array(arena, R_D3D12_State, 1); r_d3d12_state->arena = arena; r_d3d12_state->device_rw_mutex = rw_mutex_alloc(); // Create base device D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_12_1; UINT dxgi_flags = 0; #if BUILD_DEBUG ID3D12Debug1* debug_controller = 0; if (SUCCEEDED(D3D12GetDebugInterface(&IID_ID3D12Debug1, (void**)&debug_controller))) { ID3D12Debug1_EnableDebugLayer(debug_controller); ID3D12Debug1_SetEnableGPUBasedValidation(debug_controller, TRUE); ID3D12Debug1_Release(debug_controller); } dxgi_flags |= DXGI_CREATE_FACTORY_DEBUG; #endif error = CreateDXGIFactory2(dxgi_flags, &IID_IDXGIFactory4, (void**)&r_d3d12_state->dxgi_factory); if (FAILED(error)) { char buffer[256] = { 0 }; juliet_snprintf(buffer, sizeof(buffer), "D3D12 DXGI factory creation failure (%lx). Process is terminating", error); sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer)); abort_self(1); } // dxgi setup (device and adapter) IDXGIFactory6* factory6 = 0; error = IDXGIFactory4_QueryInterface(r_d3d12_state->dxgi_factory, &IID_IDXGIFactory6, (void**)&factory6); if (SUCCEEDED(error)) { // Prioritize discrete GPU error = IDXGIFactory6_EnumAdapterByGpuPreference(factory6, 0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, &IID_IDXGIAdapter1, (void**)&r_d3d12_state->dxgi_adapter); IDXGIFactory6_Release(factory6); } else { // Pray device 0 is the discrete gpu error = IDXGIFactory4_EnumAdapters1(r_d3d12_state->dxgi_factory, 0, &r_d3d12_state->dxgi_adapter); } error = D3D12CreateDevice((IUnknown*)r_d3d12_state->dxgi_adapter, feature_level, &IID_ID3D12Device4, (void**)&r_d3d12_state->device); // Try warp as hardware is no available if (FAILED(error)) { IDXGIAdapter1_Release(r_d3d12_state->dxgi_adapter); error = IDXGIFactory4_EnumWarpAdapter(r_d3d12_state->dxgi_factory, &IID_IDXGIAdapter, (void**)&r_d3d12_state->dxgi_adapter); if (SUCCEEDED(error)) { error = D3D12CreateDevice((IUnknown*)r_d3d12_state->dxgi_adapter, feature_level, &IID_ID3D12Device4, (void**)&r_d3d12_state->device); } } if (FAILED(error)) { char buffer[256] = { 0 }; juliet_snprintf(buffer, sizeof(buffer), "D3D12 device factory creation failure (%lx). Process is terminating", error); sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer)); abort_self(1); } // break on error #if BUILD_DEBUG ID3D12InfoQueue* info = 0; error = ID3D12Device4_QueryInterface(r_d3d12_state->device, &IID_ID3D12InfoQueue, (void**)&info); if (SUCCEEDED(error)) { error = ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE); error = ID3D12InfoQueue_SetBreakOnSeverity(info, D3D12_MESSAGE_SEVERITY_ERROR, TRUE); ID3D12InfoQueue_Release(info); } #endif // Create basic graphics queue D3D12_COMMAND_QUEUE_DESC queue_desc = { 0 }; queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; error = ID3D12Device4_CreateCommandQueue(r_d3d12_state->device, &queue_desc, &IID_ID3D12CommandQueue, (void**)&r_d3d12_state->direct_command_queue); if (FAILED(error)) { char buffer[256] = { 0 }; juliet_snprintf(buffer, sizeof(buffer), "D3D12 direct command queue creation failure (%lx). Process is terminating", error); sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer)); abort_self(1); } // Create command allocator and command list for EachIndex(i, 2) { ID3D12Device4_CreateCommandAllocator(r_d3d12_state->device, D3D12_COMMAND_LIST_TYPE_DIRECT, &IID_ID3D12CommandAllocator, (void**)&r_d3d12_state->command_allocators[i]); } ID3D12Device4_CreateCommandList1(r_d3d12_state->device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_LIST_FLAG_NONE, &IID_ID3D12GraphicsCommandList, (void**)&r_d3d12_state->command_list); ID3D12Device4_CreateFence(r_d3d12_state->device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void**)&r_d3d12_state->fence); r_d3d12_state->fence_event = CreateEventW(0, FALSE, FALSE, 0); } void r_shutdown() { MutexScopeW(r_d3d12_state->device_rw_mutex) { r_d3d12_wait(); CloseHandle(r_d3d12_state->fence_event); ID3D12Fence_Release(r_d3d12_state->fence); for EachIndex(i,2) { ID3D12CommandAllocator_Release(r_d3d12_state->command_allocators[i]); } ID3D12CommandList_Release(r_d3d12_state->command_list); ID3D12CommandQueue_Release(r_d3d12_state->direct_command_queue); ID3D12Device4_Release(r_d3d12_state->device); IDXGIAdapter1_Release(r_d3d12_state->dxgi_adapter); IDXGIFactory4_Release(r_d3d12_state->dxgi_factory); } rw_mutex_release(r_d3d12_state->device_rw_mutex); arena_release(r_d3d12_state->arena); } // ============================================================================= // window setup/teardown R_Handle r_window_equip(WM_Window handle) { R_Handle result = { 0 }; MutexScopeW(r_d3d12_state->device_rw_mutex) { R_D3D12_Window* window = r_d3d12_state->first_free_window; if (window != 0) { SLL_stack_pop(r_d3d12_state->first_free_window); MemoryZeroStruct(window); } else { window = push_array(r_d3d12_state->arena, R_D3D12_Window, 1); } HWND hwnd = { 0 }; { W32_WM_Window* w32_layer_window = w32_wm_window_from_handle(handle); hwnd = w32_wm_hwnd_from_window(w32_layer_window); } // Create swapchain DXGI_SWAP_CHAIN_DESC1 swapchain_desc = { 0 }; swapchain_desc.Width = 0; // Use all window width swapchain_desc.Height = 0; // Use all window height swapchain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; swapchain_desc.Stereo = 0; swapchain_desc.SampleDesc.Count = 1; swapchain_desc.SampleDesc.Quality = 0; swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapchain_desc.BufferCount = 2; // Double buffering swapchain_desc.Scaling = DXGI_SCALING_NONE; swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapchain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; swapchain_desc.Flags = 0; DXGI_SWAP_CHAIN_FULLSCREEN_DESC swapchain_fullscreen_desc = { 0 }; swapchain_fullscreen_desc.RefreshRate.Numerator = 0; swapchain_fullscreen_desc.RefreshRate.Denominator = 0; swapchain_fullscreen_desc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapchain_fullscreen_desc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; swapchain_fullscreen_desc.Windowed = TRUE; IDXGISwapChain1* swapchain1 = 0; HRESULT error = IDXGIFactory4_CreateSwapChainForHwnd(r_d3d12_state->dxgi_factory, (IUnknown*)r_d3d12_state->direct_command_queue, hwnd, &swapchain_desc, &swapchain_fullscreen_desc, 0, &swapchain1); if (FAILED(error)) { char buffer[256]; juliet_snprintf(buffer, sizeof(buffer), "DXGI swap chain creation failure (%lx). Process is terminating.", error); sh_message(1, str8_literal("Fatal Error"), str8_cstring(buffer)); abort_self(1); } IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain3, (void**)&window->swapchain); IDXGISwapChain1_Release(swapchain1); IDXGIFactory4_MakeWindowAssociation(r_d3d12_state->dxgi_factory, hwnd, DXGI_MWA_NO_ALT_ENTER); // Create framebuffer and rtv for swapchain D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = { 0 }; rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtv_heap_desc.NumDescriptors = 2; rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; ID3D12Device4_CreateDescriptorHeap(r_d3d12_state->device, &rtv_heap_desc, &IID_ID3D12DescriptorHeap, (void**)&window->rtv_heap); UINT rtv_stride = ID3D12Device4_GetDescriptorHandleIncrementSize(r_d3d12_state->device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV); D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = { 0 }; ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(window->rtv_heap, &rtv_handle); for (UINT i = 0; i < 2; ++i) { window->rtv_handles[i] = rtv_handle; IDXGISwapChain3_GetBuffer(window->swapchain, i, &IID_ID3D12Resource, (void**)&window->framebuffer[i]); ID3D12Device4_CreateRenderTargetView(r_d3d12_state->device, window->framebuffer[i], 0, window->rtv_handles[i]); rtv_handle.ptr += rtv_stride; } // Save resolution in case of resize Rect2f client_rect = wm_client_rect_from_window(handle); Vector2i resolution = { (int32)(client_rect.x1 - client_rect.x0), (int32)(client_rect.y1 - client_rect.y0) }; window->last_resolution = resolution; result = r_d3d12_handle_from_window(window); } return result; } void r_window_unequip(WM_Window handle, R_Handle handle_equip) { MutexScopeW(r_d3d12_state->device_rw_mutex) { r_d3d12_wait(); R_D3D12_Window* window = r_d3d12_window_from_handle(handle_equip); if (window != 0 && window != &r_d3d12_nil_window) { for EachIndex(i, 2) { ID3D12Resource_Release(window->framebuffer[i]); } ID3D12DescriptorHeap_Release(window->rtv_heap); IDXGISwapChain1_Release(window->swapchain); SLL_stack_push(r_d3d12_state->first_free_window, window); } } } // frame markers void r_begin_frame() { MutexScopeW(r_d3d12_state->device_rw_mutex) { uint64 frame_slot = r_d3d12_state->frame_idx % 2; r_d3d12_wait_for_fence_value(r_d3d12_state->frame_fence_value[frame_slot]); ID3D12CommandAllocator_Reset(r_d3d12_state->command_allocators[frame_slot]); r_d3d12_state->frame_has_presented = 0; } } void r_end_frame() { MutexScopeW(r_d3d12_state->device_rw_mutex) { uint64 frame_slot = r_d3d12_state->frame_idx % 2; // Setup the frame fence r_d3d12_state->global_fence_value += 1; ID3D12CommandQueue_Signal(r_d3d12_state->direct_command_queue, r_d3d12_state->fence, r_d3d12_state->global_fence_value); r_d3d12_state->frame_fence_value[frame_slot] = r_d3d12_state->global_fence_value; r_d3d12_state->frame_idx += 1; } } void r_window_begin_frame(WM_Window window, R_Handle window_equip) { MutexScopeW(r_d3d12_state->device_rw_mutex) { R_D3D12_Window* r_window = r_d3d12_window_from_handle(window_equip); // Get resolution Rect2f client_rect = wm_client_rect_from_window(window); Vector2i resolution = { (int32)(client_rect.x1 - client_rect.x0), (int32)(client_rect.y1 - client_rect.y0) }; // change resolution if (resolution.x > 0 && resolution.y > 0 && (r_window->last_resolution.x != resolution.x || r_window->last_resolution.y != resolution.y)) { r_window->last_resolution = resolution; // resize swap chain r_d3d12_wait(); for EachIndex(i,2) { if (r_window->framebuffer[i] != 0) { ID3D12Resource_Release(r_window->framebuffer[i]); r_window->framebuffer[i] = 0; } } IDXGISwapChain3_ResizeBuffers(r_window->swapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0); for EachIndex(i,2) { IDXGISwapChain3_GetBuffer(r_window->swapchain, i, &IID_ID3D12Resource, (void**)&r_window->framebuffer[i]); ID3D12Device4_CreateRenderTargetView(r_d3d12_state->device, r_window->framebuffer[i], 0, r_window->rtv_handles[i]); } } // Open the command list for that window that frame uint64 frame_slot = r_d3d12_state->frame_idx % 2; r_window->framebuffer_idx = IDXGISwapChain3_GetCurrentBackBufferIndex(r_window->swapchain); ID3D12CommandAllocator* allocator = r_d3d12_state->command_allocators[frame_slot]; ID3D12GraphicsCommandList* cmd_list = r_d3d12_state->command_list; ID3D12GraphicsCommandList1_Reset(cmd_list, allocator, 0); // Barrier to transition from Present (end of frame) to render target D3D12_RESOURCE_BARRIER barrier = { 0 }; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Transition.pResource = r_window->framebuffer[r_window->framebuffer_idx]; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; ID3D12GraphicsCommandList_ResourceBarrier(cmd_list, 1, &barrier); // HARDCODED // BLUE float32 background[4] = { 0.1f, 0.2f, 0.8f, 1.0f }; ID3D12GraphicsCommandList_ClearRenderTargetView(cmd_list, r_window->rtv_handles[r_window->framebuffer_idx], background, 0, 0); } } void r_window_end_frame(WM_Window window, R_Handle window_equip) { MutexScopeW(r_d3d12_state->device_rw_mutex) { R_D3D12_Window* r_window = r_d3d12_window_from_handle(window_equip); ID3D12GraphicsCommandList* cmd_list = r_d3d12_state->command_list; // Transition from render target to present D3D12_RESOURCE_BARRIER barrier = { 0 }; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Transition.pResource = r_window->framebuffer[r_window->framebuffer_idx]; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; ID3D12GraphicsCommandList_ResourceBarrier(cmd_list, 1, &barrier); // Close command list and push it onto the command queue ID3D12GraphicsCommandList_Close(cmd_list); ID3D12CommandQueue_ExecuteCommandLists(r_d3d12_state->direct_command_queue, 1, (ID3D12CommandList**)&cmd_list); UINT sync_interval = r_d3d12_state->frame_has_presented ? 0 : 1; IDXGISwapChain3_Present(r_window->swapchain, sync_interval, 0); r_d3d12_state->frame_has_presented = 1; } }