aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Sunet <marc.sunet@amd.com>2025-11-21 09:41:06 -0800
committerMarc Sunet <marc.sunet@amd.com>2025-11-21 09:41:06 -0800
commit0b5491e0a2f1a9a4023e2c4eb171287bede41388 (patch)
treee82e93313f1fcfcc5622bae706aea9335dbc43ef
parentb5697421bbc73ed17ef3a8bc003571d1b6351b5c (diff)
Switch to plain C
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt23
-rw-r--r--app/CMakeLists.txt11
-rw-r--r--app/include/dxwindow.h28
-rw-r--r--app/src/dxwindow.c81
-rw-r--r--contrib/glfw/CMakeLists.txt2
-rw-r--r--dxg/CMakeLists.txt23
-rw-r--r--dxg/dxcommon.h17
-rw-r--r--dxg/dxcommon.ixx53
-rw-r--r--dxg/include/dxg/dxcommon.h55
-rw-r--r--dxg/src/dxg.c1
-rw-r--r--dxwindow/CMakeLists.txt10
-rw-r--r--dxwindow/dxwindow.ixx113
-rw-r--r--hello/CMakeLists.txt8
-rw-r--r--hello/main.c430
-rw-r--r--hello/main.cc433
16 files changed, 638 insertions, 651 deletions
diff --git a/.gitignore b/.gitignore
index c4c4ffc..2795b52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
1*.zip 1*.zip
2compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca8b0ac..82d9df9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,16 +1,18 @@
1cmake_minimum_required(VERSION 3.25) 1cmake_minimum_required(VERSION 3.20)
2 2
3set(CMAKE_CXX_STANDARD 20) 3set(CMAKE_C_STANDARD 17)
4set(CMAKE_CXX_STANDARD_REQUIRED ON) 4set(CMAKE_C_STANDARD_REQUIRED ON)
5set(CMAKE_CXX_EXTENSIONS OFF) 5set(CMAKE_C_EXTENSIONS OFF)
6set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
7set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a")
8set(CMAKE_CXX_SCAN_FOR_MODULES ON)
9 6
10# Multi-threaded statically-linked runtime library (-MT) 7# Multi-threaded statically-linked runtime library (-MT)
11set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") 8# Debug results in a linker warning, I think because the DX12 libs are linked
9# against a Release version of the runtime.
10#set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
11set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
12 12
13project(dx12) 13add_compile_definitions(-D_AMD64_=1)
14
15project(dx12c)
14 16
15# External dependencies. 17# External dependencies.
16add_subdirectory(contrib/DirectX-Headers) 18add_subdirectory(contrib/DirectX-Headers)
@@ -18,7 +20,8 @@ add_subdirectory(contrib/glfw)
18 20
19# Common libraries. 21# Common libraries.
20add_subdirectory(dxg) 22add_subdirectory(dxg)
21add_subdirectory(dxwindow) 23add_subdirectory(app)
22 24
23# Applications. 25# Applications.
26#add_subdirectory(game)
24add_subdirectory(hello) 27add_subdirectory(hello)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
new file mode 100644
index 0000000..7b2bdaf
--- /dev/null
+++ b/app/CMakeLists.txt
@@ -0,0 +1,11 @@
1cmake_minimum_required(VERSION 3.20)
2
3add_library(app
4 include/dxwindow.h
5 src/dxwindow.c)
6
7target_include_directories(app PUBLIC
8 include)
9
10target_link_libraries(app PUBLIC
11 glfw)
diff --git a/app/include/dxwindow.h b/app/include/dxwindow.h
new file mode 100644
index 0000000..7e5a373
--- /dev/null
+++ b/app/include/dxwindow.h
@@ -0,0 +1,28 @@
1#pragma once
2
3#include <WinDef.h> // HWND
4
5#include <stdbool.h>
6
7typedef struct Window Window;
8
9/// Initialise the window subsystem.
10///
11/// This function must be called at the start of your application before any
12/// Windows are created.
13bool window_global_init();
14
15/// Terminate the window subsystem.
16///
17/// This function should be called at the end of your application. Any existing
18/// Windows are destroyed and are invalid beyond this call.
19void window_global_quit();
20
21/// Return the last Window error.
22const char* window_get_error();
23
24Window* window_init(int width, int height, const char* title);
25void window_destroy(Window**);
26HWND window_handle(Window*);
27void window_update(Window*);
28bool window_should_close(const Window*);
diff --git a/app/src/dxwindow.c b/app/src/dxwindow.c
new file mode 100644
index 0000000..3f775e7
--- /dev/null
+++ b/app/src/dxwindow.c
@@ -0,0 +1,81 @@
1#include <dxwindow.h>
2
3// Include Windows.h before GLFW to avoid macro redefinition warnings.
4#define WIN32_LEAN_AND_MEAN
5#include <Windows.h>
6
7#define GLFW_INCLUDE_NONE // Do not include OpenGL headers.
8#include <GLFW/glfw3.h>
9
10#define GLFW_EXPOSE_NATIVE_WIN32
11#include <GLFW/glfw3native.h>
12
13#include <assert.h>
14#include <stdio.h>
15#include <stdlib.h>
16
17typedef struct Window {
18 GLFWwindow* glfw_window;
19} Window;
20
21static char glfw_error[1024] = {};
22
23static void glfw_error_callback(int error, const char* description) {
24 sprintf_s(glfw_error, sizeof(glfw_error),
25 "GLFW error %d: %s", error, description);
26}
27
28bool window_global_init() {
29 glfwSetErrorCallback(glfw_error_callback);
30 return glfwInit() == GLFW_TRUE;
31}
32
33void window_global_quit() {
34 glfwTerminate();
35}
36
37const char* window_get_error(Window* wnd) {
38 assert(wnd);
39 return glfw_error;
40}
41
42Window* window_init(int width, int height, const char* title) {
43 Window* wnd = calloc(1, sizeof(Window));
44 if (!wnd) {
45 return 0;
46 }
47 // GLFW by default creates an OpenGL context with the window.
48 // Use GLFW_NO_API to tell it not to do so.
49 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
50 if ((wnd->glfw_window = glfwCreateWindow(
51 width, height, title, /*monitor=*/NULL, /*share=*/NULL)) == 0) {
52 free(wnd);
53 return 0;
54 }
55 return wnd;
56}
57
58void window_destroy(Window** ppWindow) {
59 assert(ppWindow);
60 Window* wnd = *ppWindow;
61 if (wnd) {
62 glfwDestroyWindow(wnd->glfw_window);
63 free(wnd);
64 *ppWindow = 0;
65 }
66}
67
68HWND window_handle(Window* wnd) {
69 assert(wnd);
70 return glfwGetWin32Window(wnd->glfw_window);
71}
72
73void window_update(Window* wnd) {
74 assert(wnd);
75 glfwPollEvents();
76}
77
78bool window_should_close(const Window* wnd) {
79 assert(wnd);
80 return glfwWindowShouldClose(wnd->glfw_window) == GLFW_TRUE;
81}
diff --git a/contrib/glfw/CMakeLists.txt b/contrib/glfw/CMakeLists.txt
index 5635cfb..3521151 100644
--- a/contrib/glfw/CMakeLists.txt
+++ b/contrib/glfw/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.25) 1cmake_minimum_required(VERSION 3.20)
2 2
3add_library(glfw INTERFACE) 3add_library(glfw INTERFACE)
4 4
diff --git a/dxg/CMakeLists.txt b/dxg/CMakeLists.txt
index 6fa5401..b7607c0 100644
--- a/dxg/CMakeLists.txt
+++ b/dxg/CMakeLists.txt
@@ -1,20 +1,23 @@
1cmake_minimum_required(VERSION 3.25) 1cmake_minimum_required(VERSION 3.20)
2 2
3project(dxg) 3project(dxg)
4 4
5add_library(dxg) 5add_library(dxg
6 include/dxg/dxcommon.h
7 src/dxg.c)
6 8
7target_sources(dxg PUBLIC 9# target_sources(dxg PUBLIC
8 dxcommon.h) 10# FILE_SET cxx_modules TYPE CXX_MODULES FILES
9 11# asset.ixx
10target_sources(dxg PUBLIC 12# dxcommon.ixx
11 FILE_SET cxx_modules TYPE CXX_MODULES FILES 13# dxg.ixx
12 dxcommon.ixx) 14# imm.ixx)
13 15
14target_include_directories(dxg PUBLIC 16target_include_directories(dxg PUBLIC
15 .) 17 include)
16 18
17target_link_libraries(dxg PUBLIC 19target_link_libraries(dxg PUBLIC
18 DirectX-Headers 20 DirectX-Headers
19 D3D12.lib 21 D3D12.lib
20 DXGI.lib) 22 DXGI.lib
23 DXGUID.lib) # For IID_Xyz symbols
diff --git a/dxg/dxcommon.h b/dxg/dxcommon.h
deleted file mode 100644
index addb8c3..0000000
--- a/dxg/dxcommon.h
+++ /dev/null
@@ -1,17 +0,0 @@
1#pragma once
2
3#include <d3d12.h>
4#include <dxgi1_4.h>
5#include <directx/d3dx12.h>
6
7#define THROW(error) throw exception(error, __FILE__, __LINE__)
8
9#define ThrowIfFailed(result) \
10{\
11 if (result != S_OK) \
12 {\
13 THROW(result);\
14 }\
15}
16
17//#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), static_cast<void**>(ppType)
diff --git a/dxg/dxcommon.ixx b/dxg/dxcommon.ixx
deleted file mode 100644
index b06ae95..0000000
--- a/dxg/dxcommon.ixx
+++ /dev/null
@@ -1,53 +0,0 @@
1module;
2
3#include <wrl.h>
4#include <stdexcept>
5
6export module dxcommon;
7
8using Microsoft::WRL::ComPtr;
9
10namespace dx {
11
12export {
13
14class exception : public std::exception
15{
16public:
17 exception() noexcept = default;
18
19 exception(HRESULT result, const char* file, int line) noexcept
20 {
21 sprintf_s(m_error, sizeof(m_error), "%s:%d Failed with HRESULT = %08X",
22 file, line, static_cast<unsigned int>(result));
23 }
24
25 exception(const char* error, const char* file, int line) noexcept
26 {
27 sprintf_s(m_error, sizeof(m_error), "%s:%d %s", file, line, error);
28 }
29
30 [[nodiscard]] const char* what() const noexcept final
31 {
32 return m_error;
33 }
34
35private:
36 static thread_local char m_error[1024];
37};
38
39template <typename T>
40void SafeRelease(ComPtr<T>& ptr)
41{
42 if (ptr)
43 {
44 ptr->Release();
45 ptr = nullptr;
46 }
47}
48
49} // export
50
51thread_local char exception::m_error[1024];
52
53} // dx
diff --git a/dxg/include/dxg/dxcommon.h b/dxg/include/dxg/dxcommon.h
new file mode 100644
index 0000000..bfcdbe8
--- /dev/null
+++ b/dxg/include/dxg/dxcommon.h
@@ -0,0 +1,55 @@
1#pragma once
2
3#include <d3d12.h>
4#include <dxgi1_4.h>
5#include <directx/d3dx12.h>
6
7#include <assert.h>
8#include <stdio.h>
9
10#define TRAP(ERROR) { \
11 fprintf(stderr, "Error in file:[%s] line:%d: %s", __FILE__, __LINE__, ERROR);\
12 assert(false);\
13 __debugbreak();\
14}
15
16#define TRAP_HRESULT(RESULT) { \
17 fprintf(stderr, "HRESULT: %u", RESULT);\
18 TRAP("API call failed")\
19}
20
21#define TrapIfFailed(RESULT) {\
22 if (RESULT != S_OK) {\
23 TRAP_HRESULT(RESULT);\
24 }\
25}
26
27#define SafeRelease(PTR) {\
28 if (PTR) {\
29 PTR->lpVtbl->Release(PTR);\
30 PTR = 0;\
31 }\
32}
33
34#define CD3DX12_CPU_DESCRIPTOR_HANDLE(HANDLE, INDEX, SIZE) \
35 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (INDEX * SIZE)}
36
37#define OFFSET_HANDLE(HANDLE, OFFSET, SIZE) \
38 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (OFFSET * SIZE)}
39
40static inline D3D12_RESOURCE_BARRIER CD3DX12_RESOURCE_BARRIER_Transition(
41 _In_ ID3D12Resource* pResource,
42 D3D12_RESOURCE_STATES stateBefore,
43 D3D12_RESOURCE_STATES stateAfter) {
44 return (D3D12_RESOURCE_BARRIER){
45 .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
46 .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
47 .Transition.pResource = pResource,
48 .Transition.StateBefore = stateBefore,
49 .Transition.StateAfter = stateAfter,
50 .Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES};
51}
52
53typedef enum SampleMask {
54 PointSampling = 0xffffffff
55} SampleMask;
diff --git a/dxg/src/dxg.c b/dxg/src/dxg.c
new file mode 100644
index 0000000..e985d3d
--- /dev/null
+++ b/dxg/src/dxg.c
@@ -0,0 +1 @@
int x = 2;
diff --git a/dxwindow/CMakeLists.txt b/dxwindow/CMakeLists.txt
deleted file mode 100644
index 16c1709..0000000
--- a/dxwindow/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
1cmake_minimum_required(VERSION 3.25)
2
3add_library(dxwindow)
4
5target_sources(dxwindow PUBLIC
6 FILE_SET cxx_modules TYPE CXX_MODULES FILES
7 dxwindow.ixx)
8
9target_link_libraries(dxwindow PUBLIC
10 glfw)
diff --git a/dxwindow/dxwindow.ixx b/dxwindow/dxwindow.ixx
deleted file mode 100644
index 6efcc18..0000000
--- a/dxwindow/dxwindow.ixx
+++ /dev/null
@@ -1,113 +0,0 @@
1module;
2
3// Include Windows.h before GLFW to avoid macro redefinition warnings.
4#define WIN32_LEAN_AND_MEAN
5#include <Windows.h>
6
7#define GLFW_INCLUDE_NONE // Do not include OpenGL headers.
8#include <GLFW/glfw3.h>
9
10#define GLFW_EXPOSE_NATIVE_WIN32
11#include <GLFW/glfw3native.h>
12
13#include <cassert>
14#include <cstdio>
15
16export module dxwindow;
17
18namespace dx {
19
20char glfw_error[1024] = {};
21
22void glfw_error_callback(int error, const char* description)
23{
24 sprintf_s(glfw_error, sizeof(glfw_error),
25 "GLFW error %d: %s", error, description);
26}
27
28export {
29
30class Window
31{
32public:
33 ~Window()
34 {
35 if (m_window != nullptr)
36 {
37 glfwDestroyWindow(m_window);
38 }
39 }
40
41 /// Creates the window.
42 bool Initialise(int width, int height, const char* title)
43 {
44 // GLFW by default creates an OpenGL context with the window.
45 // Use GLFW_NO_API to tell it not to do so.
46 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
47
48 if ((m_window = glfwCreateWindow(
49 width, height, title, /*monitor=*/NULL, /*share=*/NULL)) == nullptr)
50 {
51 return false;
52 }
53
54 return true;
55 }
56
57 /// Returns the native window handle.
58 /// If the window has not been initialized, returns an invalid handle.
59 HWND GetWindowHandle()
60 {
61 if (!m_window)
62 {
63 return NULL;
64 }
65 return glfwGetWin32Window(m_window);
66 }
67
68 /// Updates the window by polling for user input.
69 void Update()
70 {
71 assert(m_window);
72 glfwPollEvents();
73 }
74
75 /// Returns true if the user tried to close the window, false otherwise.
76 bool ShouldClose() const
77 {
78 assert(m_window);
79 return glfwWindowShouldClose(m_window) == GLFW_TRUE;
80 }
81
82private:
83 GLFWwindow* m_window = nullptr;
84};
85
86/// Initialise the window subsystem.
87///
88/// This function must be called at the start of your application before any
89/// Windows are created.
90bool WindowInitialise()
91{
92 glfwSetErrorCallback(glfw_error_callback);
93 return glfwInit() == GLFW_TRUE;
94}
95
96/// Terminate the window subsystem.
97///
98/// This function should be called at the end of your application. Any existing
99/// Windows are destroyed and are invalid beyond this call.
100void WindowTerminate()
101{
102 glfwTerminate();
103}
104
105/// Returns the last Window error.
106const char* GetWindowError()
107{
108 return glfw_error;
109}
110
111} // export
112
113} // namespace dx
diff --git a/hello/CMakeLists.txt b/hello/CMakeLists.txt
index 2804b24..b3c1507 100644
--- a/hello/CMakeLists.txt
+++ b/hello/CMakeLists.txt
@@ -1,10 +1,10 @@
1cmake_minimum_required(VERSION 3.25) 1cmake_minimum_required(VERSION 3.20)
2 2
3project(hello) 3project(hello)
4 4
5add_executable(hello 5add_executable(hello
6 main.cc) 6 main.c)
7 7
8target_link_libraries(hello PRIVATE 8target_link_libraries(hello PRIVATE
9 dxg 9 app
10 dxwindow) 10 dxg)
diff --git a/hello/main.c b/hello/main.c
new file mode 100644
index 0000000..738f5c0
--- /dev/null
+++ b/hello/main.c
@@ -0,0 +1,430 @@
1#include <dxg/dxcommon.h>
2#include <dxwindow.h>
3
4#include <assert.h>
5#include <stdio.h>
6
7#define SWAP_CHAIN_BUFFER_COUNT 2 // Double-buffering.
8
9typedef struct D3DSettings
10{
11 int width;
12 int height;
13} D3DSettings;
14
15typedef struct D3D {
16 Window* pWindow;
17 D3DSettings settings;
18
19 IDXGIFactory4* pDxgiFactory;
20 ID3D12Device* pDevice;
21
22 ID3D12CommandQueue* pCommandQueue;
23 ID3D12CommandAllocator* pCommandAllocator;
24 ID3D12GraphicsCommandList* pCommandList;
25
26 IDXGISwapChain3* pSwapChain;
27
28 ID3D12DescriptorHeap* pRtvHeap;
29 ID3D12DescriptorHeap* pDsvHeap;
30
31 ID3D12Resource* pSwapChainBuffer[SWAP_CHAIN_BUFFER_COUNT];
32 ID3D12Resource* pDepthStencilBuffer;
33
34 ID3D12Fence* pFence;
35 HANDLE fence_event;
36 UINT64 fence_value;
37
38 UINT rtv_descriptor_size;
39 UINT dsv_descriptor_size;
40 UINT cbv_descriptor_size;
41} D3D;
42
43/// Creates the application's swap chain.
44///
45/// This method can be called multiple times to re-create the swap chain.
46static void d3d_create_swap_chain(D3D* d3d) {
47 assert(d3d);
48 assert(d3d->pDxgiFactory);
49 assert(d3d->pCommandQueue);
50
51 SafeRelease(d3d->pSwapChain);
52
53 DXGI_SWAP_CHAIN_DESC1 desc = (DXGI_SWAP_CHAIN_DESC1){
54 .Width = (UINT)(d3d->settings.width),
55 .Height = (UINT)(d3d->settings.height),
56 .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
57 .SampleDesc = (DXGI_SAMPLE_DESC){
58 .Count = 1,
59 .Quality = 0,
60 },
61 .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
62 .BufferCount = SWAP_CHAIN_BUFFER_COUNT,
63 .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
64 };
65 IDXGISwapChain1* pSwapChain = 0;
66 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->CreateSwapChainForHwnd(
67 d3d->pDxgiFactory,
68 (IUnknown*)d3d->pCommandQueue, // Swap chain uses queue to perform flush.
69 window_handle(d3d->pWindow),
70 &desc,
71 /*pFullScreenDesc=*/0, // Running in windowed mode.
72 /*pRestrictToOutput=*/0,
73 &pSwapChain));
74 //TrapIfFailed(pSwapChain.As(&d3d->pSwapChain));
75 d3d->pSwapChain = (IDXGISwapChain3*)pSwapChain;
76}
77
78/// Creates RTVs for all of the swap chain's buffers.
79static void d3d_create_swap_chain_buffer_render_target_views(D3D* d3d) {
80 assert(d3d);
81 assert(d3d->pDevice);
82 assert(d3d->pSwapChain);
83 assert(d3d->pRtvHeap);
84
85 // Create the new buffer views.
86 D3D12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle;
87 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_heap_handle);
88 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
89 {
90 TrapIfFailed(d3d->pSwapChain->lpVtbl->GetBuffer(
91 d3d->pSwapChain, i, &IID_ID3D12Resource, &d3d->pSwapChainBuffer[i]));
92
93 d3d->pDevice->lpVtbl->CreateRenderTargetView(
94 d3d->pDevice, d3d->pSwapChainBuffer[i], /*pDesc=*/0, rtv_heap_handle);
95
96 rtv_heap_handle = OFFSET_HANDLE(rtv_heap_handle, 1, d3d->rtv_descriptor_size);
97 }
98}
99
100static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_depth_stencil_view(D3D* d3d) {
101 assert(d3d);
102 assert(d3d->pDsvHeap);
103 D3D12_CPU_DESCRIPTOR_HANDLE handle;
104 d3d->pDsvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pDsvHeap, &handle);
105 return handle;
106}
107
108/// Creates a depth/stencil buffer and its view.
109static void d3d_create_depth_stencil_buffer_and_view(D3D* d3d) {
110 assert(d3d);
111 assert(d3d->pDevice);
112
113 const D3D12_RESOURCE_DESC depth_stencil_desc = (D3D12_RESOURCE_DESC){
114 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
115 .Alignment = 0,
116 .Width = (UINT64)(d3d->settings.width),
117 .Height = (UINT)(d3d->settings.height),
118 .DepthOrArraySize = 1,
119 .MipLevels = 1,
120 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
121 .SampleDesc = (DXGI_SAMPLE_DESC){
122 .Count = 1,
123 .Quality = 0,
124 },
125 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
126 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
127 };
128 const D3D12_CLEAR_VALUE opt_clear_value = (D3D12_CLEAR_VALUE){
129 .Format = depth_stencil_desc.Format,
130 .DepthStencil = (D3D12_DEPTH_STENCIL_VALUE){
131 .Depth = 1.0f,
132 .Stencil = 0,
133 },
134 };
135 const D3D12_HEAP_PROPERTIES depth_stencil_heap_properties = (D3D12_HEAP_PROPERTIES){
136 .Type = D3D12_HEAP_TYPE_DEFAULT,
137 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
138 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
139 .CreationNodeMask = 1,
140 .VisibleNodeMask = 1,
141 };
142 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommittedResource(
143 d3d->pDevice,
144 &depth_stencil_heap_properties,
145 D3D12_HEAP_FLAG_NONE,
146 &depth_stencil_desc,
147 D3D12_RESOURCE_STATE_COMMON,
148 &opt_clear_value,
149 &IID_ID3D12Resource,
150 &d3d->pDepthStencilBuffer));
151
152 d3d->pDevice->lpVtbl->CreateDepthStencilView(
153 d3d->pDevice,
154 d3d->pDepthStencilBuffer,
155 /*pDesc=*/0,
156 d3d_get_depth_stencil_view(d3d));
157}
158
159/// Creates RTV and DSV descriptor heaps.
160static void d3d_create_descriptor_heaps(D3D* d3d) {
161 assert(d3d);
162 assert(d3d->pDevice);
163
164 // The RTV heap must hold as many descriptors as we have buffers in the
165 // swap chain.
166 const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
167 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
168 .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT,
169 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
170 .NodeMask = 0,
171 };
172 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
173 d3d->pDevice, &rtv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pRtvHeap));
174
175 // For the depth/stencil buffer, we just need one view.
176 const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
177 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
178 .NumDescriptors = 1,
179 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
180 .NodeMask = 0,
181 };
182 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
183 d3d->pDevice, &dsv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pDsvHeap));
184}
185
186static void d3d_init(D3D* d3d, Window* pWindow, const D3DSettings* pSettings) {
187 assert(d3d);
188 assert(pWindow);
189 assert(pSettings);
190
191 d3d->pWindow = pWindow;
192 d3d->settings = *pSettings;
193
194 UINT dxgiFactoryFlags = 0;
195#ifdef DEBUG
196 ID3D12Debug* debug = 0;
197 D3D12GetDebugInterface(&IID_ID3D12Debug, (&debug));
198 debug->EnableDebugLayer();
199 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
200#endif
201 TrapIfFailed(CreateDXGIFactory2(
202 dxgiFactoryFlags, &IID_IDXGIFactory4, &d3d->pDxgiFactory));
203
204 // Prevent Alt+Enter from going into fullscreen.
205 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->MakeWindowAssociation(
206 d3d->pDxgiFactory,
207 window_handle(d3d->pWindow),
208 DXGI_MWA_NO_ALT_ENTER));
209
210 TrapIfFailed(D3D12CreateDevice(
211 /*pAdapter=*/0, // Default adapter.
212 D3D_FEATURE_LEVEL_11_0,
213 &IID_ID3D12Device,
214 &d3d->pDevice));
215
216 d3d->rtv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
217 d3d->pDevice,
218 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
219 d3d->dsv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
220 d3d->pDevice,
221 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
222 d3d->cbv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
223 d3d->pDevice,
224 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
225
226 const D3D12_COMMAND_QUEUE_DESC queue_desc = (D3D12_COMMAND_QUEUE_DESC){
227 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
228 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
229 };
230 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandQueue(
231 d3d->pDevice,
232 &queue_desc,
233 &IID_ID3D12CommandQueue,
234 &d3d->pCommandQueue));
235
236 // The command allocator is the memory backing for the command list.
237 // It is in the allocator's memory where the commands are stored.
238 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandAllocator(
239 d3d->pDevice,
240 queue_desc.Type,
241 &IID_ID3D12CommandAllocator,
242 &d3d->pCommandAllocator));
243
244 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandList(
245 d3d->pDevice,
246 /*nodeMask=*/0,
247 queue_desc.Type,
248 d3d->pCommandAllocator,
249 /*pInitialState=*/0, // Pipeline state.
250 &IID_ID3D12CommandList,
251 &d3d->pCommandList));
252
253 // Command lists are in the "open" state after they are created. It is
254 // easier to assume that they start in the "closed" state at each
255 // iteration of the main loop, however. The Reset() method, which we'll
256 // use later, also expects the command list to be closed.
257 TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList));
258
259 d3d_create_descriptor_heaps(d3d);
260
261 d3d_create_swap_chain(d3d);
262 d3d_create_swap_chain_buffer_render_target_views(d3d);
263 d3d_create_depth_stencil_buffer_and_view(d3d);
264
265 TrapIfFailed(d3d->pDevice->lpVtbl->CreateFence(
266 d3d->pDevice,
267 d3d->fence_value,
268 D3D12_FENCE_FLAG_NONE,
269 &IID_ID3D12Fence,
270 &d3d->pFence));
271
272 if ((d3d->fence_event = CreateEvent(0, FALSE, FALSE, 0)) == 0) {
273 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
274 }
275}
276
277static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_current_back_buffer_view(const D3D* d3d) {
278 assert(d3d);
279 assert(d3d->pRtvHeap);
280 assert(d3d->rtv_descriptor_size > 0);
281 D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle;
282 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_handle);
283 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
284 rtv_handle,
285 d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain),
286 d3d->rtv_descriptor_size);
287}
288
289static ID3D12Resource* d3d_get_current_back_buffer(const D3D* d3d) {
290 assert(d3d);
291 return d3d->pSwapChainBuffer[d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain)];
292}
293
294static void d3d_populate_command_list(D3D* d3d) {
295 assert(d3d);
296
297 /// Note that we skip the following two items:
298 ///
299 /// 1. RSSetViewports()
300 /// 2. OMSetRenderTargets()
301 ///
302 /// This application does not render anything useful, it simply clears
303 /// the back buffer and depth/stencil view. Clearing both resources
304 /// does not require a viewport to be set or the OM (output-merger
305 /// stage) to be configured.
306
307 // A command allocator can only be reset when its associated command
308 // lists are finished executing on the GPU. This requires
309 // synchronisation.
310 TrapIfFailed(d3d->pCommandAllocator->lpVtbl->Reset(d3d->pCommandAllocator));
311
312 // A command list can be reset as soon as it is executed with
313 // ExecuteCommandList(). Reset() does require that the command list is
314 // in a "closed" state, however, which is why we Close() it right away
315 // after creation.
316 TrapIfFailed(d3d->pCommandList->lpVtbl->Reset(
317 d3d->pCommandList,
318 d3d->pCommandAllocator,
319 /*pInitialState=*/0));
320
321 // Indicate that we intend to use the back buffer as a render target.
322 const D3D12_RESOURCE_BARRIER render_barrier = CD3DX12_RESOURCE_BARRIER_Transition(
323 d3d_get_current_back_buffer(d3d),
324 D3D12_RESOURCE_STATE_PRESENT,
325 D3D12_RESOURCE_STATE_RENDER_TARGET);
326 d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &render_barrier);
327
328 // Record commands.
329 const float clear_colour[] = { 0.0f, 0.502f, 0.494f, 0.0f };
330 d3d->pCommandList->lpVtbl->ClearRenderTargetView(
331 d3d->pCommandList,
332 d3d_get_current_back_buffer_view(d3d),
333 clear_colour,
334 0, // Number of rectangles in the following array.
335 0); // No rectangles; clear the entire resource.
336
337 d3d->pCommandList->lpVtbl->ClearDepthStencilView(
338 d3d->pCommandList,
339 d3d_get_depth_stencil_view(d3d),
340 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
341 1.0f, // Depth.
342 0, // Stencil.
343 0, // Number of rectangles in the following array.
344 0); // No rectangles; clear the entire resource view.
345
346 // Indicate that we now intend to use the back buffer to present.
347 const D3D12_RESOURCE_BARRIER present_barrier = CD3DX12_RESOURCE_BARRIER_Transition(
348 d3d_get_current_back_buffer(d3d),
349 D3D12_RESOURCE_STATE_RENDER_TARGET,
350 D3D12_RESOURCE_STATE_PRESENT);
351 d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &present_barrier);
352
353 // A command list must be closed before it can be executed.
354 TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList));
355}
356
357static void d3d_wait_for_previous_frame(D3D* d3d) {
358 assert(d3d);
359
360 // Advance the fence value to mark commands up to this fence point.
361 d3d->fence_value++;
362
363 // The command queue will signal the new fence value when all commands
364 // up to this point have finished execution.
365 TrapIfFailed(d3d->pCommandQueue->lpVtbl->Signal(
366 d3d->pCommandQueue, d3d->pFence, d3d->fence_value));
367
368 // Wait for commands to finish execution.
369 // It is possible that execution has already finished by the time we
370 // get here, so first check the fence's completed value.
371 if (d3d->pFence->lpVtbl->GetCompletedValue(d3d->pFence) < d3d->fence_value) {
372 // Commands are still being executed. Configure a Windows event
373 // and wait for it. The event fires when the commands have finished
374 // execution.
375
376 // Indicate that |fence_event| is to be fired when |fence|
377 // reaches the new fence value.
378 TrapIfFailed(d3d->pFence->lpVtbl->SetEventOnCompletion(
379 d3d->pFence, d3d->fence_value, d3d->fence_event));
380
381 // Will wake up when the fence takes on the new fence value.
382 WaitForSingleObject(d3d->fence_event, INFINITE);
383 }
384}
385
386static void d3d_render(D3D* d3d) {
387 assert(d3d);
388
389 d3d_populate_command_list(d3d);
390
391 ID3D12CommandList* command_lists[] = { (ID3D12CommandList*)d3d->pCommandList };
392 d3d->pCommandQueue->lpVtbl->ExecuteCommandLists(
393 d3d->pCommandQueue, _countof(command_lists), command_lists);
394
395 TrapIfFailed(d3d->pSwapChain->lpVtbl->Present(
396 d3d->pSwapChain, /*SyncInterval=*/1, /*Flags=*/0));
397
398 // It is not efficient to wait for the frame to complete here, but it
399 // is simple and sufficient for this application.
400 d3d_wait_for_previous_frame(d3d);
401}
402
403int main(int argc, const char** argv) {
404 const D3DSettings settings = (D3DSettings){
405 .width = 1920,
406 .height = 1200,
407 };
408
409 if (!window_global_init()) {
410 TRAP("Failed to initialise the window subsystem");
411 }
412
413 Window* window = window_init(settings.width, settings.height, "D3D Application");
414 if (!window) {
415 TRAP(window_get_error());
416 }
417
418 D3D d3d = {0};
419 d3d_init(&d3d, window, &settings);
420
421 while (!window_should_close(window))
422 {
423 window_update(window);
424 d3d_render(&d3d);
425 Sleep(10);
426 }
427
428 window_global_quit();
429 return 0;
430}
diff --git a/hello/main.cc b/hello/main.cc
deleted file mode 100644
index d9040b4..0000000
--- a/hello/main.cc
+++ /dev/null
@@ -1,433 +0,0 @@
1#include <dxcommon.h>
2
3#include <cassert>
4#include <cstdio>
5
6import dxcommon;
7import dxwindow;
8
9using namespace dx;
10using Microsoft::WRL::ComPtr;
11
12struct D3DSettings
13{
14 int width = 0;
15 int height = 0;
16};
17
18class D3D
19{
20public:
21 void Initialise(Window* window, const D3DSettings& settings)
22 {
23 m_window = window;
24 m_settings = settings;
25
26 UINT dxgiFactoryFlags = 0;
27#ifdef DEBUG
28 {
29 ComPtr<ID3D12Debug> debug;
30 D3D12GetDebugInterface(IID_PPV_ARGS(&debug));
31 debug->EnableDebugLayer();
32 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
33 }
34#endif
35 ThrowIfFailed(CreateDXGIFactory2(
36 dxgiFactoryFlags, IID_PPV_ARGS(&m_dxgi_factory)));
37
38 // Prevent Alt+Enter from going into fullscreen.
39 ThrowIfFailed(m_dxgi_factory->MakeWindowAssociation(
40 m_window->GetWindowHandle(),
41 DXGI_MWA_NO_ALT_ENTER));
42
43 ThrowIfFailed(D3D12CreateDevice(
44 /*pAdapter=*/nullptr, // Default adapter.
45 D3D_FEATURE_LEVEL_11_0,
46 IID_PPV_ARGS(&m_device)));
47
48 m_rtv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
49 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
50 m_dsv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
51 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
52 m_cbv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
53 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
54
55 const D3D12_COMMAND_QUEUE_DESC queue_desc
56 {
57 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
58 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
59 };
60 ThrowIfFailed(m_device->CreateCommandQueue(
61 &queue_desc,
62 IID_PPV_ARGS(&m_command_queue)));
63
64 // The command allocator is the memory backing for the command list.
65 // It is in the allocator's memory where the commands are stored.
66 ThrowIfFailed(m_device->CreateCommandAllocator(
67 queue_desc.Type,
68 IID_PPV_ARGS(&m_command_allocator)));
69
70 ThrowIfFailed(m_device->CreateCommandList(
71 /*nodeMask=*/0,
72 queue_desc.Type,
73 m_command_allocator.Get(),
74 /*pInitialState=*/nullptr, // Pipeline state.
75 IID_PPV_ARGS(&m_command_list)));
76
77 // Command lists are in the "open" state after they are created. It is
78 // easier to assume that they start in the "closed" state at each
79 // iteration of the main loop, however. The Reset() method, which we'll
80 // use later, also expects the command list to be closed.
81 ThrowIfFailed(m_command_list->Close());
82
83 CreateDescriptorHeaps();
84
85 CreateSwapChain();
86 CreateSwapChainBufferRenderTargetViews();
87 CreateDepthStencilBufferAndView();
88
89 ThrowIfFailed(m_device->CreateFence(
90 /*InitialValue=*/m_fence_value,
91 D3D12_FENCE_FLAG_NONE,
92 IID_PPV_ARGS(&m_fence)));
93
94 if ((m_fence_event = CreateEvent(
95 /*lpEventAttributes=*/nullptr,
96 /*bManualReset=*/FALSE,
97 /*bInitialState=*/FALSE,
98 /*lpName=*/nullptr)) == 0)
99 {
100 ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
101 }
102 }
103
104 void Render()
105 {
106 PopulateCommandList();
107
108 ID3D12CommandList* command_lists[] { m_command_list.Get() };
109 m_command_queue->ExecuteCommandLists(
110 _countof(command_lists), command_lists);
111
112 ThrowIfFailed(m_swap_chain->Present(/*SyncInterval=*/1, /*Flags=*/0));
113
114 // It is not efficient to wait for the frame to complete here, but it
115 // is simple and sufficient for this application.
116 WaitForPreviousFrame();
117 }
118
119private:
120 void PopulateCommandList()
121 {
122 /// Note that we skip the following two items:
123 ///
124 /// 1. RSSetViewports()
125 /// 2. OMSetRenderTargets()
126 ///
127 /// This application does not render anything useful, it simply clears
128 /// the back buffer and depth/stencil view. Clearing both resources
129 /// does not require a viewport to be set or the OM (output-merger
130 /// stage) to be configured.
131
132 // A command allocator can only be reset when its associated command
133 // lists are finished executing on the GPU. This requires
134 // synchronisation.
135 ThrowIfFailed(m_command_allocator->Reset());
136
137 // A command list can be reset as soon as it is executed with
138 // ExecuteCommandList(). Reset() does require that the command list is
139 // in a "closed" state, however, which is why we Close() it right away
140 // after creation.
141 ThrowIfFailed(m_command_list->Reset(
142 m_command_allocator.Get(),
143 /*pInitialState=*/nullptr));
144
145 // Indicate that we intend to use the back buffer as a render target.
146 const auto render_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
147 GetCurrentBackBuffer(),
148 D3D12_RESOURCE_STATE_PRESENT,
149 D3D12_RESOURCE_STATE_RENDER_TARGET);
150 m_command_list->ResourceBarrier(1, &render_barrier);
151
152 // Record commands.
153 const float clear_colour[] { 0.0f, 0.502f, 0.494f, 0.0f };
154 m_command_list->ClearRenderTargetView(
155 GetCurrentBackBufferView(),
156 clear_colour,
157 0, // Number of rectangles in the following array.
158 nullptr); // No rectangles; clear the entire resource.
159
160 m_command_list->ClearDepthStencilView(
161 GetDepthStencilView(),
162 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
163 1.0f, // Depth.
164 0, // Stencil.
165 0, // Number of rectangles in the following array.
166 nullptr); // No rectangles; clear the entire resource view.
167
168 // Indicate that we now intend to use the back buffer to present.
169 const auto present_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
170 GetCurrentBackBuffer(),
171 D3D12_RESOURCE_STATE_RENDER_TARGET,
172 D3D12_RESOURCE_STATE_PRESENT);
173 m_command_list->ResourceBarrier(1, &present_barrier);
174
175 // A command list must be closed before it can be executed.
176 ThrowIfFailed(m_command_list->Close());
177 }
178
179 void WaitForPreviousFrame()
180 {
181 // Advance the fence value to mark commands up to this fence point.
182 m_fence_value++;
183
184 // The command queue will signal the new fence value when all commands
185 // up to this point have finished execution.
186 ThrowIfFailed(m_command_queue->Signal(m_fence.Get(), m_fence_value));
187
188 // Wait for commands to finish execution.
189 // It is possible that execution has already finished by the time we
190 // get here, so first check the fence's completed value.
191 if (m_fence->GetCompletedValue() < m_fence_value)
192 {
193 // Commands are still being executed. Configure a Windows event
194 // and wait for it. The event fires when the commands have finished
195 // execution.
196
197 // Indicate that |m_fence_event| is to be fired when |m_fence|
198 // reaches the new fence value.
199 ThrowIfFailed(m_fence->SetEventOnCompletion(
200 m_fence_value, m_fence_event));
201
202 // Will wake up when the fence takes on the new fence value.
203 WaitForSingleObject(m_fence_event, INFINITE);
204 }
205 }
206
207 /// Creates RTV and DSV descriptor heaps.
208 void CreateDescriptorHeaps()
209 {
210 assert(m_device);
211
212 // The RTV heap must hold as many descriptors as we have buffers in the
213 // swap chain.
214 const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc
215 {
216 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
217 .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT,
218 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
219 .NodeMask = 0,
220 };
221 ThrowIfFailed(m_device->CreateDescriptorHeap(
222 &rtv_heap_desc, IID_PPV_ARGS(&m_rtv_heap)));
223
224 // For the depth/stencil buffer, we just need one view.
225 const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc
226 {
227 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
228 .NumDescriptors = 1,
229 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
230 .NodeMask = 0,
231 };
232 ThrowIfFailed(m_device->CreateDescriptorHeap(
233 &dsv_heap_desc, IID_PPV_ARGS(&m_dsv_heap)));
234 }
235
236 /// Creates the application's swap chain.
237 ///
238 /// This method can be called multiple times to re-create the swap chain.
239 void CreateSwapChain()
240 {
241 assert(m_dxgi_factory);
242 assert(m_command_queue);
243
244 SafeRelease(m_swap_chain);
245
246 DXGI_SWAP_CHAIN_DESC1 desc
247 {
248 .Width = static_cast<UINT>(m_settings.width),
249 .Height = static_cast<UINT>(m_settings.height),
250 .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
251 .SampleDesc = DXGI_SAMPLE_DESC
252 {
253 .Count = 1,
254 .Quality = 0,
255 },
256 .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
257 .BufferCount = SWAP_CHAIN_BUFFER_COUNT,
258 .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
259 };
260 ComPtr<IDXGISwapChain1> swap_chain;
261 ThrowIfFailed(m_dxgi_factory->CreateSwapChainForHwnd(
262 m_command_queue.Get(), // Swap chain uses queue to perform flush.
263 m_window->GetWindowHandle(),
264 &desc,
265 /*pFullScreenDesc=*/nullptr, // Running in windowed mode.
266 /*pRestrictToOutput=*/nullptr,
267 &swap_chain));
268 ThrowIfFailed(swap_chain.As(&m_swap_chain));
269 }
270
271 /// Creates RTVs for all of the swap chain's buffers.
272 void CreateSwapChainBufferRenderTargetViews()
273 {
274 assert(m_device);
275 assert(m_swap_chain);
276 assert(m_rtv_heap);
277
278 // Create the new buffer views.
279 CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle(
280 m_rtv_heap->GetCPUDescriptorHandleForHeapStart());
281 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
282 {
283 ThrowIfFailed(m_swap_chain->GetBuffer(
284 i, IID_PPV_ARGS(&m_swap_chain_buffer[i])));
285
286 m_device->CreateRenderTargetView(
287 m_swap_chain_buffer[i].Get(), /*pDesc=*/nullptr, rtv_heap_handle);
288
289 rtv_heap_handle.Offset(1, m_rtv_descriptor_size);
290 }
291 }
292
293 /// Creates a depth/stencil buffer and its view.
294 void CreateDepthStencilBufferAndView()
295 {
296 assert(m_device);
297
298 const D3D12_RESOURCE_DESC depth_stencil_desc
299 {
300 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
301 .Alignment = 0,
302 .Width = static_cast<UINT64>(m_settings.width),
303 .Height = static_cast<UINT>(m_settings.height),
304 .DepthOrArraySize = 1,
305 .MipLevels = 1,
306 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
307 .SampleDesc = DXGI_SAMPLE_DESC
308 {
309 .Count = 1,
310 .Quality = 0,
311 },
312 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
313 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
314 };
315 const D3D12_CLEAR_VALUE opt_clear_value
316 {
317 .Format = depth_stencil_desc.Format,
318 .DepthStencil = D3D12_DEPTH_STENCIL_VALUE
319 {
320 .Depth = 1.0f,
321 .Stencil = 0,
322 },
323 };
324 const CD3DX12_HEAP_PROPERTIES depth_stencil_heap_properties(
325 D3D12_HEAP_TYPE_DEFAULT);
326 ThrowIfFailed(m_device->CreateCommittedResource(
327 &depth_stencil_heap_properties,
328 D3D12_HEAP_FLAG_NONE,
329 &depth_stencil_desc,
330 D3D12_RESOURCE_STATE_COMMON,
331 &opt_clear_value,
332 IID_PPV_ARGS(&m_depth_stencil_buffer)));
333
334 m_device->CreateDepthStencilView(
335 m_depth_stencil_buffer.Get(),
336 /*pDesc=*/nullptr,
337 GetDepthStencilView());
338 }
339
340 ID3D12Resource* GetCurrentBackBuffer() const
341 {
342 return m_swap_chain_buffer[m_swap_chain->GetCurrentBackBufferIndex()].Get();
343 }
344
345 D3D12_CPU_DESCRIPTOR_HANDLE GetCurrentBackBufferView() const
346 {
347 assert(m_rtv_heap);
348 assert(m_rtv_descriptor_size > 0);
349 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
350 m_rtv_heap->GetCPUDescriptorHandleForHeapStart(),
351 m_swap_chain->GetCurrentBackBufferIndex(),
352 m_rtv_descriptor_size);
353 }
354
355 D3D12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const
356 {
357 assert(m_dsv_heap);
358 return m_dsv_heap->GetCPUDescriptorHandleForHeapStart();
359 }
360
361private:
362 static constexpr int SWAP_CHAIN_BUFFER_COUNT = 2; // Double-buffering.
363
364 Window* m_window = nullptr;
365 D3DSettings m_settings;
366
367 ComPtr<IDXGIFactory4> m_dxgi_factory;
368 ComPtr<ID3D12Device> m_device;
369
370 ComPtr<ID3D12CommandQueue> m_command_queue;
371 ComPtr<ID3D12CommandAllocator> m_command_allocator;
372 ComPtr<ID3D12GraphicsCommandList> m_command_list;
373
374 ComPtr<IDXGISwapChain3> m_swap_chain;
375
376 ComPtr<ID3D12DescriptorHeap> m_rtv_heap;
377 ComPtr<ID3D12DescriptorHeap> m_dsv_heap;
378
379 ComPtr<ID3D12Resource> m_swap_chain_buffer[SWAP_CHAIN_BUFFER_COUNT];
380 ComPtr<ID3D12Resource> m_depth_stencil_buffer;
381
382 ComPtr<ID3D12Fence> m_fence;
383 HANDLE m_fence_event = 0;
384 UINT64 m_fence_value = 0;
385
386 UINT m_rtv_descriptor_size = 0;
387 UINT m_dsv_descriptor_size = 0;
388 UINT m_cbv_descriptor_size = 0;
389};
390
391int main()
392{
393 try
394 {
395 const D3DSettings settings =
396 {
397 // TODO: use 960x600 or 1920x1200 depending on native resolution.
398 .width = 1920,
399 .height = 1200,
400 };
401
402 if (!WindowInitialise())
403 {
404 THROW("Failed to initialise the window subsystem");
405 }
406 {
407 Window window;
408 if (!window.Initialise(
409 settings.width, settings.height, /*title=*/"D3D Application"))
410 {
411 THROW(GetWindowError());
412 }
413
414 D3D d3d;
415 d3d.Initialise(&window, settings);
416
417 while (!window.ShouldClose())
418 {
419 window.Update();
420 d3d.Render();
421 Sleep(10);
422 }
423 }
424 WindowTerminate();
425
426 return 0;
427 }
428 catch (const std::exception& e)
429 {
430 fprintf(stderr, "Exception caught: %s\n", e.what());
431 return 1;
432 }
433}