1 // ImGui - standalone example application for DirectX 12
2 // If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp.
3 // FIXME: 64-bit only for now! (Because sizeof(ImTextureId) == sizeof(void*))
4 
5 #include "imgui.h"
6 #include "imgui_impl_win32.h"
7 #include "imgui_impl_dx12.h"
8 #include <d3d12.h>
9 #include <dxgi1_4.h>
10 #include <tchar.h>
11 
12 #define DX12_ENABLE_DEBUG_LAYER     0
13 
14 struct FrameContext
15 {
16     ID3D12CommandAllocator* CommandAllocator;
17     UINT64                  FenceValue;
18 };
19 
20 // Data
21 static int const                    NUM_FRAMES_IN_FLIGHT = 3;
22 static FrameContext                 g_frameContext[NUM_FRAMES_IN_FLIGHT] = {};
23 static UINT                         g_frameIndex = 0;
24 
25 static int const                    NUM_BACK_BUFFERS = 3;
26 static ID3D12Device*                g_pd3dDevice = NULL;
27 static ID3D12DescriptorHeap*        g_pd3dRtvDescHeap = NULL;
28 static ID3D12DescriptorHeap*        g_pd3dSrvDescHeap = NULL;
29 static ID3D12CommandQueue*          g_pd3dCommandQueue = NULL;
30 static ID3D12GraphicsCommandList*   g_pd3dCommandList = NULL;
31 static ID3D12Fence*                 g_fence = NULL;
32 static HANDLE                       g_fenceEvent = NULL;
33 static UINT64                       g_fenceLastSignaledValue = 0;
34 static IDXGISwapChain3*             g_pSwapChain = NULL;
35 static HANDLE                       g_hSwapChainWaitableObject = NULL;
36 static ID3D12Resource*              g_mainRenderTargetResource[NUM_BACK_BUFFERS] = {};
37 static D3D12_CPU_DESCRIPTOR_HANDLE  g_mainRenderTargetDescriptor[NUM_BACK_BUFFERS] = {};
38 
CreateRenderTarget()39 void CreateRenderTarget()
40 {
41     ID3D12Resource* pBackBuffer;
42     for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
43     {
44         g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer));
45         g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, g_mainRenderTargetDescriptor[i]);
46         g_mainRenderTargetResource[i] = pBackBuffer;
47     }
48 }
49 
WaitForLastSubmittedFrame()50 void WaitForLastSubmittedFrame()
51 {
52     FrameContext* frameCtxt = &g_frameContext[g_frameIndex % NUM_FRAMES_IN_FLIGHT];
53 
54     UINT64 fenceValue = frameCtxt->FenceValue;
55     if (fenceValue == 0)
56         return; // No fence was signaled
57 
58     frameCtxt->FenceValue = 0;
59     if (g_fence->GetCompletedValue() >= fenceValue)
60         return;
61 
62     g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent);
63     WaitForSingleObject(g_fenceEvent, INFINITE);
64 }
65 
WaitForNextFrameResources()66 FrameContext* WaitForNextFrameResources()
67 {
68     UINT nextFrameIndex = g_frameIndex + 1;
69     g_frameIndex = nextFrameIndex;
70 
71     HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, NULL };
72     DWORD numWaitableObjects = 1;
73 
74     FrameContext* frameCtxt = &g_frameContext[nextFrameIndex % NUM_FRAMES_IN_FLIGHT];
75     UINT64 fenceValue = frameCtxt->FenceValue;
76     if (fenceValue != 0) // means no fence was signaled
77     {
78         frameCtxt->FenceValue = 0;
79         g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent);
80         waitableObjects[1] = g_fenceEvent;
81         numWaitableObjects = 2;
82     }
83 
84     WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE);
85 
86     return frameCtxt;
87 }
88 
ResizeSwapChain(HWND hWnd,int width,int height)89 void ResizeSwapChain(HWND hWnd, int width, int height)
90 {
91     DXGI_SWAP_CHAIN_DESC1 sd;
92     g_pSwapChain->GetDesc1(&sd);
93     sd.Width = width;
94     sd.Height = height;
95 
96     IDXGIFactory4* dxgiFactory = NULL;
97     g_pSwapChain->GetParent(IID_PPV_ARGS(&dxgiFactory));
98 
99     g_pSwapChain->Release();
100     CloseHandle(g_hSwapChainWaitableObject);
101 
102     IDXGISwapChain1* swapChain1 = NULL;
103     dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, NULL, NULL, &swapChain1);
104     swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain));
105     swapChain1->Release();
106     dxgiFactory->Release();
107 
108     g_pSwapChain->SetMaximumFrameLatency(NUM_BACK_BUFFERS);
109 
110     g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject();
111     assert(g_hSwapChainWaitableObject != NULL);
112 }
113 
CleanupRenderTarget()114 void CleanupRenderTarget()
115 {
116     WaitForLastSubmittedFrame();
117 
118     for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
119         if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = NULL; }
120 }
121 
CreateDeviceD3D(HWND hWnd)122 HRESULT CreateDeviceD3D(HWND hWnd)
123 {
124     // Setup swap chain
125     DXGI_SWAP_CHAIN_DESC1 sd;
126     {
127         ZeroMemory(&sd, sizeof(sd));
128         sd.BufferCount = NUM_BACK_BUFFERS;
129         sd.Width = 0;
130         sd.Height = 0;
131         sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
132         sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
133         sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
134         sd.SampleDesc.Count = 1;
135         sd.SampleDesc.Quality = 0;
136         sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
137         sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
138         sd.Scaling = DXGI_SCALING_STRETCH;
139         sd.Stereo = FALSE;
140     }
141 
142     if (DX12_ENABLE_DEBUG_LAYER)
143     {
144         ID3D12Debug* dx12Debug = NULL;
145         if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&dx12Debug))))
146         {
147             dx12Debug->EnableDebugLayer();
148             dx12Debug->Release();
149         }
150     }
151 
152     D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
153     if (D3D12CreateDevice(NULL, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK)
154         return E_FAIL;
155 
156     {
157         D3D12_DESCRIPTOR_HEAP_DESC desc = {};
158         desc.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
159         desc.NumDescriptors = NUM_BACK_BUFFERS;
160         desc.Flags          = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
161         desc.NodeMask       = 1;
162         if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dRtvDescHeap)) != S_OK)
163             return E_FAIL;
164 
165         SIZE_T rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
166         D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart();
167         for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
168         {
169             g_mainRenderTargetDescriptor[i] = rtvHandle;
170             rtvHandle.ptr += rtvDescriptorSize;
171         }
172     }
173 
174     {
175         D3D12_DESCRIPTOR_HEAP_DESC desc = {};
176         desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
177         desc.NumDescriptors = 1;
178         desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
179         if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK)
180             return E_FAIL;
181     }
182 
183     {
184         D3D12_COMMAND_QUEUE_DESC desc = {};
185         desc.Type     = D3D12_COMMAND_LIST_TYPE_DIRECT;
186         desc.Flags    = D3D12_COMMAND_QUEUE_FLAG_NONE;
187         desc.NodeMask = 1;
188         if (g_pd3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK)
189             return E_FAIL;
190     }
191 
192     for (UINT i = 0; i < NUM_FRAMES_IN_FLIGHT; i++)
193         if (g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) != S_OK)
194             return E_FAIL;
195 
196     if (g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator, NULL, IID_PPV_ARGS(&g_pd3dCommandList)) != S_OK ||
197         g_pd3dCommandList->Close() != S_OK)
198         return E_FAIL;
199 
200     if (g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence)) != S_OK)
201         return E_FAIL;
202 
203     g_fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
204     if (g_fenceEvent == NULL)
205         return E_FAIL;
206 
207     {
208         IDXGIFactory4* dxgiFactory = NULL;
209         IDXGISwapChain1* swapChain1 = NULL;
210         if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK ||
211             dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, NULL, NULL, &swapChain1) != S_OK ||
212             swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK)
213             return E_FAIL;
214         swapChain1->Release();
215         dxgiFactory->Release();
216         g_pSwapChain->SetMaximumFrameLatency(NUM_BACK_BUFFERS);
217         g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject();
218     }
219 
220     CreateRenderTarget();
221 
222     return S_OK;
223 }
224 
CleanupDeviceD3D()225 void CleanupDeviceD3D()
226 {
227     CleanupRenderTarget();
228     if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = NULL; }
229     if (g_hSwapChainWaitableObject != NULL) { CloseHandle(g_hSwapChainWaitableObject); }
230     for (UINT i = 0; i < NUM_FRAMES_IN_FLIGHT; i++)
231         if (g_frameContext[i].CommandAllocator) { g_frameContext[i].CommandAllocator->Release(); g_frameContext[i].CommandAllocator = NULL; }
232     if (g_pd3dCommandQueue) { g_pd3dCommandQueue->Release(); g_pd3dCommandQueue = NULL; }
233     if (g_pd3dCommandList) { g_pd3dCommandList->Release(); g_pd3dCommandList = NULL; }
234     if (g_pd3dRtvDescHeap) { g_pd3dRtvDescHeap->Release(); g_pd3dRtvDescHeap = NULL; }
235     if (g_pd3dSrvDescHeap) { g_pd3dSrvDescHeap->Release(); g_pd3dSrvDescHeap = NULL; }
236     if (g_fence) { g_fence->Release(); g_fence = NULL; }
237     if (g_fenceEvent) { CloseHandle(g_fenceEvent); g_fenceEvent = NULL; }
238     if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; }
239 }
240 
241 extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)242 LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
243 {
244     if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
245         return true;
246 
247     switch (msg)
248     {
249     case WM_SIZE:
250         if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
251         {
252             ImGui_ImplDX12_InvalidateDeviceObjects();
253             CleanupRenderTarget();
254             ResizeSwapChain(hWnd, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam));
255             CreateRenderTarget();
256             ImGui_ImplDX12_CreateDeviceObjects();
257         }
258         return 0;
259     case WM_SYSCOMMAND:
260         if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
261             return 0;
262         break;
263     case WM_DESTROY:
264         PostQuitMessage(0);
265         return 0;
266     }
267     return DefWindowProc(hWnd, msg, wParam, lParam);
268 }
269 
main(int,char **)270 int main(int, char**)
271 {
272     // Create application window
273     WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL };
274     RegisterClassEx(&wc);
275     HWND hwnd = CreateWindow(_T("ImGui Example"), _T("Dear ImGui DirectX12 Example"), WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL);
276 
277     // Initialize Direct3D
278     if (CreateDeviceD3D(hwnd) < 0)
279     {
280         CleanupDeviceD3D();
281         UnregisterClass(_T("ImGui Example"), wc.hInstance);
282         return 1;
283     }
284 
285     // Show the window
286     ShowWindow(hwnd, SW_SHOWDEFAULT);
287     UpdateWindow(hwnd);
288 
289     // Setup Dear ImGui binding
290     IMGUI_CHECKVERSION();
291     ImGui::CreateContext();
292     ImGuiIO& io = ImGui::GetIO(); (void)io;
293     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls
294 
295     ImGui_ImplWin32_Init(hwnd);
296     ImGui_ImplDX12_Init(g_pd3dDevice, NUM_FRAMES_IN_FLIGHT,
297         DXGI_FORMAT_R8G8B8A8_UNORM,
298         g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(),
299         g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart());
300 
301     // Setup style
302     ImGui::StyleColorsDark();
303     //ImGui::StyleColorsClassic();
304 
305     // Load Fonts
306     // - 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.
307     // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
308     // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
309     // - 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.
310     // - Read 'misc/fonts/README.txt' for more instructions and details.
311     // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
312     //io.Fonts->AddFontDefault();
313     //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
314     //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
315     //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
316     //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
317     //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
318     //IM_ASSERT(font != NULL);
319 
320     bool show_demo_window = true;
321     bool show_another_window = false;
322     ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
323 
324     // Main loop
325     MSG msg;
326     ZeroMemory(&msg, sizeof(msg));
327     while (msg.message != WM_QUIT)
328     {
329         // Poll and handle messages (inputs, window resize, etc.)
330         // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
331         // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
332         // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
333         // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
334         if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
335         {
336             TranslateMessage(&msg);
337             DispatchMessage(&msg);
338             continue;
339         }
340 
341         // Start the Dear ImGui frame
342         ImGui_ImplDX12_NewFrame();
343         ImGui_ImplWin32_NewFrame();
344         ImGui::NewFrame();
345 
346         // 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!).
347         if (show_demo_window)
348             ImGui::ShowDemoWindow(&show_demo_window);
349 
350         // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
351         {
352             static float f = 0.0f;
353             static int counter = 0;
354 
355             ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.
356 
357             ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
358             ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
359             ImGui::Checkbox("Another Window", &show_another_window);
360 
361             ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
362             ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
363 
364             if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
365                 counter++;
366             ImGui::SameLine();
367             ImGui::Text("counter = %d", counter);
368 
369             ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
370             ImGui::End();
371         }
372 
373         // 3. Show another simple window.
374         if (show_another_window)
375         {
376             ImGui::Begin("Another Window", &show_another_window);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
377             ImGui::Text("Hello from another window!");
378             if (ImGui::Button("Close Me"))
379                 show_another_window = false;
380             ImGui::End();
381         }
382 
383         // Rendering
384         FrameContext* frameCtxt = WaitForNextFrameResources();
385         UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex();
386         frameCtxt->CommandAllocator->Reset();
387 
388         D3D12_RESOURCE_BARRIER barrier = {};
389         barrier.Type                   = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
390         barrier.Flags                  = D3D12_RESOURCE_BARRIER_FLAG_NONE;
391         barrier.Transition.pResource   = g_mainRenderTargetResource[backBufferIdx];
392         barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
393         barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
394         barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_RENDER_TARGET;
395 
396         g_pd3dCommandList->Reset(frameCtxt->CommandAllocator, NULL);
397         g_pd3dCommandList->ResourceBarrier(1, &barrier);
398         g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], (float*)&clear_color, 0, NULL);
399         g_pd3dCommandList->OMSetRenderTargets(1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, NULL);
400         g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap);
401         ImGui::Render();
402         ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList);
403         barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
404         barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_PRESENT;
405         g_pd3dCommandList->ResourceBarrier(1, &barrier);
406         g_pd3dCommandList->Close();
407 
408         g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList);
409 
410         g_pSwapChain->Present(1, 0); // Present with vsync
411         //g_pSwapChain->Present(0, 0); // Present without vsync
412 
413         UINT64 fenceValue = g_fenceLastSignaledValue + 1;
414         g_pd3dCommandQueue->Signal(g_fence, fenceValue);
415         g_fenceLastSignaledValue = fenceValue;
416         frameCtxt->FenceValue = fenceValue;
417     }
418 
419     WaitForLastSubmittedFrame();
420     ImGui_ImplDX12_Shutdown();
421     ImGui_ImplWin32_Shutdown();
422     ImGui::DestroyContext();
423 
424     CleanupDeviceD3D();
425     DestroyWindow(hwnd);
426     UnregisterClass(_T("ImGui Example"), wc.hInstance);
427 
428     return 0;
429 }
430