1 | // Dear ImGui: standalone example application for DirectX 12 |
---|---|
2 | |
3 | // Learn about Dear ImGui: |
4 | // - FAQ https://dearimgui.com/faq |
5 | // - Getting Started https://dearimgui.com/getting-started |
6 | // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). |
7 | // - Introduction, links and more at the top of imgui.cpp |
8 | |
9 | #include "imgui.h" |
10 | #include "imgui_impl_win32.h" |
11 | #include "imgui_impl_dx12.h" |
12 | #include <d3d12.h> |
13 | #include <dxgi1_4.h> |
14 | #include <tchar.h> |
15 | |
16 | #ifdef _DEBUG |
17 | #define DX12_ENABLE_DEBUG_LAYER |
18 | #endif |
19 | |
20 | #ifdef DX12_ENABLE_DEBUG_LAYER |
21 | #include <dxgidebug.h> |
22 | #pragma comment(lib, "dxguid.lib") |
23 | #endif |
24 | |
25 | // Config for example app |
26 | static const int APP_NUM_FRAMES_IN_FLIGHT = 2; |
27 | static const int APP_NUM_BACK_BUFFERS = 2; |
28 | static const int APP_SRV_HEAP_SIZE = 64; |
29 | |
30 | struct FrameContext |
31 | { |
32 | ID3D12CommandAllocator* CommandAllocator; |
33 | UINT64 FenceValue; |
34 | }; |
35 | |
36 | // Simple free list based allocator |
37 | struct ExampleDescriptorHeapAllocator |
38 | { |
39 | ID3D12DescriptorHeap* Heap = nullptr; |
40 | D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; |
41 | D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu; |
42 | D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu; |
43 | UINT HeapHandleIncrement; |
44 | ImVector<int> FreeIndices; |
45 | |
46 | void Create(ID3D12Device* device, ID3D12DescriptorHeap* heap) |
47 | { |
48 | IM_ASSERT(Heap == nullptr && FreeIndices.empty()); |
49 | Heap = heap; |
50 | D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc(); |
51 | HeapType = desc.Type; |
52 | HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart(); |
53 | HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart(); |
54 | HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType); |
55 | FreeIndices.reserve(new_capacity: (int)desc.NumDescriptors); |
56 | for (int n = desc.NumDescriptors; n > 0; n--) |
57 | FreeIndices.push_back(v: n - 1); |
58 | } |
59 | void Destroy() |
60 | { |
61 | Heap = nullptr; |
62 | FreeIndices.clear(); |
63 | } |
64 | void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle) |
65 | { |
66 | IM_ASSERT(FreeIndices.Size > 0); |
67 | int idx = FreeIndices.back(); |
68 | FreeIndices.pop_back(); |
69 | out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement); |
70 | out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement); |
71 | } |
72 | void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle) |
73 | { |
74 | int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) / HeapHandleIncrement); |
75 | int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) / HeapHandleIncrement); |
76 | IM_ASSERT(cpu_idx == gpu_idx); |
77 | FreeIndices.push_back(v: cpu_idx); |
78 | } |
79 | }; |
80 | |
81 | // Data |
82 | static FrameContext g_frameContext[APP_NUM_FRAMES_IN_FLIGHT] = {}; |
83 | static UINT g_frameIndex = 0; |
84 | |
85 | static ID3D12Device* g_pd3dDevice = nullptr; |
86 | static ID3D12DescriptorHeap* g_pd3dRtvDescHeap = nullptr; |
87 | static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = nullptr; |
88 | static ExampleDescriptorHeapAllocator g_pd3dSrvDescHeapAlloc; |
89 | static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr; |
90 | static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr; |
91 | static ID3D12Fence* g_fence = nullptr; |
92 | static HANDLE g_fenceEvent = nullptr; |
93 | static UINT64 g_fenceLastSignaledValue = 0; |
94 | static IDXGISwapChain3* g_pSwapChain = nullptr; |
95 | static bool g_SwapChainOccluded = false; |
96 | static HANDLE g_hSwapChainWaitableObject = nullptr; |
97 | static ID3D12Resource* g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {}; |
98 | static D3D12_CPU_DESCRIPTOR_HANDLE g_mainRenderTargetDescriptor[APP_NUM_BACK_BUFFERS] = {}; |
99 | |
100 | // Forward declarations of helper functions |
101 | bool CreateDeviceD3D(HWND hWnd); |
102 | void CleanupDeviceD3D(); |
103 | void CreateRenderTarget(); |
104 | void CleanupRenderTarget(); |
105 | void WaitForLastSubmittedFrame(); |
106 | FrameContext* WaitForNextFrameResources(); |
107 | LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); |
108 | |
109 | // Main code |
110 | int main(int, char**) |
111 | { |
112 | // Create application window |
113 | //ImGui_ImplWin32_EnableDpiAwareness(); |
114 | WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; |
115 | ::RegisterClassExW(&wc); |
116 | HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); |
117 | |
118 | // Initialize Direct3D |
119 | if (!CreateDeviceD3D(hwnd)) |
120 | { |
121 | CleanupDeviceD3D(); |
122 | ::UnregisterClassW(wc.lpszClassName, wc.hInstance); |
123 | return 1; |
124 | } |
125 | |
126 | // Show the window |
127 | ::ShowWindow(hwnd, SW_SHOWDEFAULT); |
128 | ::UpdateWindow(hwnd); |
129 | |
130 | // Setup Dear ImGui context |
131 | IMGUI_CHECKVERSION(); |
132 | ImGui::CreateContext(); |
133 | ImGuiIO& io = ImGui::GetIO(); (void)io; |
134 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls |
135 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls |
136 | io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking |
137 | io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows |
138 | //io.ConfigViewportsNoAutoMerge = true; |
139 | //io.ConfigViewportsNoTaskBarIcon = true; |
140 | |
141 | // Setup Dear ImGui style |
142 | ImGui::StyleColorsDark(); |
143 | //ImGui::StyleColorsLight(); |
144 | |
145 | // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. |
146 | ImGuiStyle& style = ImGui::GetStyle(); |
147 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) |
148 | { |
149 | style.WindowRounding = 0.0f; |
150 | style.Colors[ImGuiCol_WindowBg].w = 1.0f; |
151 | } |
152 | |
153 | // Setup Platform/Renderer backends |
154 | ImGui_ImplWin32_Init(hwnd); |
155 | |
156 | ImGui_ImplDX12_InitInfo init_info = {}; |
157 | init_info.Device = g_pd3dDevice; |
158 | init_info.CommandQueue = g_pd3dCommandQueue; |
159 | init_info.NumFramesInFlight = APP_NUM_FRAMES_IN_FLIGHT; |
160 | init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM; |
161 | init_info.DSVFormat = DXGI_FORMAT_UNKNOWN; |
162 | // Allocating SRV descriptors (for textures) is up to the application, so we provide callbacks. |
163 | // (current version of the backend will only allocate one descriptor, future versions will need to allocate more) |
164 | init_info.SrvDescriptorHeap = g_pd3dSrvDescHeap; |
165 | init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle) { return g_pd3dSrvDescHeapAlloc.Alloc(out_cpu_desc_handle: out_cpu_handle, out_gpu_desc_handle: out_gpu_handle); }; |
166 | init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle) { return g_pd3dSrvDescHeapAlloc.Free(out_cpu_desc_handle: cpu_handle, out_gpu_desc_handle: gpu_handle); }; |
167 | ImGui_ImplDX12_Init(info: &init_info); |
168 | |
169 | // Before 1.91.6: our signature was using a single descriptor. From 1.92, specifying SrvDescriptorAllocFn/SrvDescriptorFreeFn will be required to benefit from new features. |
170 | //ImGui_ImplDX12_Init(g_pd3dDevice, APP_NUM_FRAMES_IN_FLIGHT, DXGI_FORMAT_R8G8B8A8_UNORM, g_pd3dSrvDescHeap, g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); |
171 | |
172 | // Load Fonts |
173 | // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. |
174 | // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. |
175 | // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). |
176 | // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. |
177 | // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. |
178 | // - Read 'docs/FONTS.md' for more instructions and details. |
179 | // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! |
180 | //io.Fonts->AddFontDefault(); |
181 | //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); |
182 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); |
183 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); |
184 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); |
185 | //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); |
186 | //IM_ASSERT(font != nullptr); |
187 | |
188 | // Our state |
189 | bool show_demo_window = true; |
190 | bool show_another_window = false; |
191 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); |
192 | |
193 | // Main loop |
194 | bool done = false; |
195 | while (!done) |
196 | { |
197 | // Poll and handle messages (inputs, window resize, etc.) |
198 | // See the WndProc() function below for our to dispatch events to the Win32 backend. |
199 | MSG msg; |
200 | while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) |
201 | { |
202 | ::TranslateMessage(&msg); |
203 | ::DispatchMessage(&msg); |
204 | if (msg.message == WM_QUIT) |
205 | done = true; |
206 | } |
207 | if (done) |
208 | break; |
209 | |
210 | // Handle window screen locked |
211 | if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) |
212 | { |
213 | ::Sleep(10); |
214 | continue; |
215 | } |
216 | g_SwapChainOccluded = false; |
217 | |
218 | // Start the Dear ImGui frame |
219 | ImGui_ImplDX12_NewFrame(); |
220 | ImGui_ImplWin32_NewFrame(); |
221 | ImGui::NewFrame(); |
222 | |
223 | // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). |
224 | if (show_demo_window) |
225 | ImGui::ShowDemoWindow(p_open: &show_demo_window); |
226 | |
227 | // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. |
228 | { |
229 | static float f = 0.0f; |
230 | static int counter = 0; |
231 | |
232 | ImGui::Begin(name: "Hello, world!"); // Create a window called "Hello, world!" and append into it. |
233 | |
234 | ImGui::Text(fmt: "This is some useful text."); // Display some text (you can use a format strings too) |
235 | ImGui::Checkbox(label: "Demo Window", v: &show_demo_window); // Edit bools storing our window open/close state |
236 | ImGui::Checkbox(label: "Another Window", v: &show_another_window); |
237 | |
238 | ImGui::SliderFloat(label: "float", v: &f, v_min: 0.0f, v_max: 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f |
239 | ImGui::ColorEdit3(label: "clear color", col: (float*)&clear_color); // Edit 3 floats representing a color |
240 | |
241 | if (ImGui::Button(label: "Button")) // Buttons return true when clicked (most widgets return true when edited/activated) |
242 | counter++; |
243 | ImGui::SameLine(); |
244 | ImGui::Text(fmt: "counter = %d", counter); |
245 | |
246 | ImGui::Text(fmt: "Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); |
247 | ImGui::End(); |
248 | } |
249 | |
250 | // 3. Show another simple window. |
251 | if (show_another_window) |
252 | { |
253 | ImGui::Begin(name: "Another Window", p_open: &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) |
254 | ImGui::Text(fmt: "Hello from another window!"); |
255 | if (ImGui::Button(label: "Close Me")) |
256 | show_another_window = false; |
257 | ImGui::End(); |
258 | } |
259 | |
260 | // Rendering |
261 | ImGui::Render(); |
262 | |
263 | FrameContext* frameCtx = WaitForNextFrameResources(); |
264 | UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); |
265 | frameCtx->CommandAllocator->Reset(); |
266 | |
267 | D3D12_RESOURCE_BARRIER barrier = {}; |
268 | barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; |
269 | barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; |
270 | barrier.Transition.pResource = g_mainRenderTargetResource[backBufferIdx]; |
271 | barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; |
272 | barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; |
273 | barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; |
274 | g_pd3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); |
275 | g_pd3dCommandList->ResourceBarrier(1, &barrier); |
276 | |
277 | // Render Dear ImGui graphics |
278 | const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; |
279 | g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], clear_color_with_alpha, 0, nullptr); |
280 | g_pd3dCommandList->OMSetRenderTargets(1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr); |
281 | g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap); |
282 | ImGui_ImplDX12_RenderDrawData(draw_data: ImGui::GetDrawData(), graphics_command_list: g_pd3dCommandList); |
283 | barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; |
284 | barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; |
285 | g_pd3dCommandList->ResourceBarrier(1, &barrier); |
286 | g_pd3dCommandList->Close(); |
287 | |
288 | g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); |
289 | |
290 | // Update and Render additional Platform Windows |
291 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) |
292 | { |
293 | ImGui::UpdatePlatformWindows(); |
294 | ImGui::RenderPlatformWindowsDefault(); |
295 | } |
296 | |
297 | // Present |
298 | HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync |
299 | //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync |
300 | g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); |
301 | |
302 | UINT64 fenceValue = g_fenceLastSignaledValue + 1; |
303 | g_pd3dCommandQueue->Signal(g_fence, fenceValue); |
304 | g_fenceLastSignaledValue = fenceValue; |
305 | frameCtx->FenceValue = fenceValue; |
306 | } |
307 | |
308 | WaitForLastSubmittedFrame(); |
309 | |
310 | // Cleanup |
311 | ImGui_ImplDX12_Shutdown(); |
312 | ImGui_ImplWin32_Shutdown(); |
313 | ImGui::DestroyContext(); |
314 | |
315 | CleanupDeviceD3D(); |
316 | ::DestroyWindow(hwnd); |
317 | ::UnregisterClassW(wc.lpszClassName, wc.hInstance); |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | // Helper functions |
323 | bool CreateDeviceD3D(HWND hWnd) |
324 | { |
325 | // Setup swap chain |
326 | DXGI_SWAP_CHAIN_DESC1 sd; |
327 | { |
328 | ZeroMemory(&sd, sizeof(sd)); |
329 | sd.BufferCount = APP_NUM_BACK_BUFFERS; |
330 | sd.Width = 0; |
331 | sd.Height = 0; |
332 | sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; |
333 | sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; |
334 | sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; |
335 | sd.SampleDesc.Count = 1; |
336 | sd.SampleDesc.Quality = 0; |
337 | sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; |
338 | sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; |
339 | sd.Scaling = DXGI_SCALING_STRETCH; |
340 | sd.Stereo = FALSE; |
341 | } |
342 | |
343 | // [DEBUG] Enable debug interface |
344 | #ifdef DX12_ENABLE_DEBUG_LAYER |
345 | ID3D12Debug* pdx12Debug = nullptr; |
346 | if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug)))) |
347 | pdx12Debug->EnableDebugLayer(); |
348 | #endif |
349 | |
350 | // Create device |
351 | D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; |
352 | if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK) |
353 | return false; |
354 | |
355 | // [DEBUG] Setup debug interface to break on any warnings/errors |
356 | #ifdef DX12_ENABLE_DEBUG_LAYER |
357 | if (pdx12Debug != nullptr) |
358 | { |
359 | ID3D12InfoQueue* pInfoQueue = nullptr; |
360 | g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&pInfoQueue)); |
361 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); |
362 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); |
363 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); |
364 | pInfoQueue->Release(); |
365 | pdx12Debug->Release(); |
366 | } |
367 | #endif |
368 | |
369 | { |
370 | D3D12_DESCRIPTOR_HEAP_DESC desc = {}; |
371 | desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; |
372 | desc.NumDescriptors = APP_NUM_BACK_BUFFERS; |
373 | desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; |
374 | desc.NodeMask = 1; |
375 | if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dRtvDescHeap)) != S_OK) |
376 | return false; |
377 | |
378 | SIZE_T rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); |
379 | D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart(); |
380 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) |
381 | { |
382 | g_mainRenderTargetDescriptor[i] = rtvHandle; |
383 | rtvHandle.ptr += rtvDescriptorSize; |
384 | } |
385 | } |
386 | |
387 | { |
388 | D3D12_DESCRIPTOR_HEAP_DESC desc = {}; |
389 | desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; |
390 | desc.NumDescriptors = APP_SRV_HEAP_SIZE; |
391 | desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; |
392 | if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK) |
393 | return false; |
394 | g_pd3dSrvDescHeapAlloc.Create(device: g_pd3dDevice, heap: g_pd3dSrvDescHeap); |
395 | } |
396 | |
397 | { |
398 | D3D12_COMMAND_QUEUE_DESC desc = {}; |
399 | desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; |
400 | desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; |
401 | desc.NodeMask = 1; |
402 | if (g_pd3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK) |
403 | return false; |
404 | } |
405 | |
406 | for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++) |
407 | if (g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) != S_OK) |
408 | return false; |
409 | |
410 | if (g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&g_pd3dCommandList)) != S_OK || |
411 | g_pd3dCommandList->Close() != S_OK) |
412 | return false; |
413 | |
414 | if (g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence)) != S_OK) |
415 | return false; |
416 | |
417 | g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); |
418 | if (g_fenceEvent == nullptr) |
419 | return false; |
420 | |
421 | { |
422 | IDXGIFactory4* dxgiFactory = nullptr; |
423 | IDXGISwapChain1* swapChain1 = nullptr; |
424 | if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) |
425 | return false; |
426 | if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) |
427 | return false; |
428 | if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) |
429 | return false; |
430 | swapChain1->Release(); |
431 | dxgiFactory->Release(); |
432 | g_pSwapChain->SetMaximumFrameLatency(APP_NUM_BACK_BUFFERS); |
433 | g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); |
434 | } |
435 | |
436 | CreateRenderTarget(); |
437 | return true; |
438 | } |
439 | |
440 | void CleanupDeviceD3D() |
441 | { |
442 | CleanupRenderTarget(); |
443 | if (g_pSwapChain) { g_pSwapChain->SetFullscreenState(false, nullptr); g_pSwapChain->Release(); g_pSwapChain = nullptr; } |
444 | if (g_hSwapChainWaitableObject != nullptr) { CloseHandle(g_hSwapChainWaitableObject); } |
445 | for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++) |
446 | if (g_frameContext[i].CommandAllocator) { g_frameContext[i].CommandAllocator->Release(); g_frameContext[i].CommandAllocator = nullptr; } |
447 | if (g_pd3dCommandQueue) { g_pd3dCommandQueue->Release(); g_pd3dCommandQueue = nullptr; } |
448 | if (g_pd3dCommandList) { g_pd3dCommandList->Release(); g_pd3dCommandList = nullptr; } |
449 | if (g_pd3dRtvDescHeap) { g_pd3dRtvDescHeap->Release(); g_pd3dRtvDescHeap = nullptr; } |
450 | if (g_pd3dSrvDescHeap) { g_pd3dSrvDescHeap->Release(); g_pd3dSrvDescHeap = nullptr; } |
451 | if (g_fence) { g_fence->Release(); g_fence = nullptr; } |
452 | if (g_fenceEvent) { CloseHandle(g_fenceEvent); g_fenceEvent = nullptr; } |
453 | if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; } |
454 | |
455 | #ifdef DX12_ENABLE_DEBUG_LAYER |
456 | IDXGIDebug1* pDebug = nullptr; |
457 | if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug)))) |
458 | { |
459 | pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY); |
460 | pDebug->Release(); |
461 | } |
462 | #endif |
463 | } |
464 | |
465 | void CreateRenderTarget() |
466 | { |
467 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) |
468 | { |
469 | ID3D12Resource* pBackBuffer = nullptr; |
470 | g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer)); |
471 | g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, g_mainRenderTargetDescriptor[i]); |
472 | g_mainRenderTargetResource[i] = pBackBuffer; |
473 | } |
474 | } |
475 | |
476 | void CleanupRenderTarget() |
477 | { |
478 | WaitForLastSubmittedFrame(); |
479 | |
480 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) |
481 | if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; } |
482 | } |
483 | |
484 | void WaitForLastSubmittedFrame() |
485 | { |
486 | FrameContext* frameCtx = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT]; |
487 | |
488 | UINT64 fenceValue = frameCtx->FenceValue; |
489 | if (fenceValue == 0) |
490 | return; // No fence was signaled |
491 | |
492 | frameCtx->FenceValue = 0; |
493 | if (g_fence->GetCompletedValue() >= fenceValue) |
494 | return; |
495 | |
496 | g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); |
497 | WaitForSingleObject(g_fenceEvent, INFINITE); |
498 | } |
499 | |
500 | FrameContext* WaitForNextFrameResources() |
501 | { |
502 | UINT nextFrameIndex = g_frameIndex + 1; |
503 | g_frameIndex = nextFrameIndex; |
504 | |
505 | HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, nullptr }; |
506 | DWORD numWaitableObjects = 1; |
507 | |
508 | FrameContext* frameCtx = &g_frameContext[nextFrameIndex % APP_NUM_FRAMES_IN_FLIGHT]; |
509 | UINT64 fenceValue = frameCtx->FenceValue; |
510 | if (fenceValue != 0) // means no fence was signaled |
511 | { |
512 | frameCtx->FenceValue = 0; |
513 | g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); |
514 | waitableObjects[1] = g_fenceEvent; |
515 | numWaitableObjects = 2; |
516 | } |
517 | |
518 | WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); |
519 | |
520 | return frameCtx; |
521 | } |
522 | |
523 | // Forward declare message handler from imgui_impl_win32.cpp |
524 | extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); |
525 | |
526 | // Win32 message handler |
527 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. |
528 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. |
529 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. |
530 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. |
531 | LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
532 | { |
533 | if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) |
534 | return true; |
535 | |
536 | switch (msg) |
537 | { |
538 | case WM_SIZE: |
539 | if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) |
540 | { |
541 | WaitForLastSubmittedFrame(); |
542 | CleanupRenderTarget(); |
543 | HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); |
544 | assert(SUCCEEDED(result) && "Failed to resize swapchain."); |
545 | CreateRenderTarget(); |
546 | } |
547 | return 0; |
548 | case WM_SYSCOMMAND: |
549 | if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu |
550 | return 0; |
551 | break; |
552 | case WM_DESTROY: |
553 | ::PostQuitMessage(0); |
554 | return 0; |
555 | } |
556 | return ::DefWindowProcW(hWnd, msg, wParam, lParam); |
557 | } |
558 |
Definitions
- APP_NUM_FRAMES_IN_FLIGHT
- APP_NUM_BACK_BUFFERS
- APP_SRV_HEAP_SIZE
- FrameContext
- ExampleDescriptorHeapAllocator
- Create
- Destroy
- Alloc
- Free
- g_frameContext
- g_frameIndex
- g_pd3dDevice
- g_pd3dRtvDescHeap
- g_pd3dSrvDescHeap
- g_pd3dSrvDescHeapAlloc
- g_pd3dCommandQueue
- g_pd3dCommandList
- g_fence
- g_fenceEvent
- g_fenceLastSignaledValue
- g_pSwapChain
- g_SwapChainOccluded
- g_hSwapChainWaitableObject
- g_mainRenderTargetResource
- g_mainRenderTargetDescriptor
- WINAPI
- main
- CreateDeviceD3D
- CleanupDeviceD3D
- CreateRenderTarget
- CleanupRenderTarget
- WaitForLastSubmittedFrame
- WaitForNextFrameResources
Learn to use CMake with our Intro Training
Find out more