aboutsummaryrefslogtreecommitdiff
path: root/dxg
diff options
context:
space:
mode:
Diffstat (limited to 'dxg')
-rw-r--r--dxg/CMakeLists.txt54
-rw-r--r--dxg/include/dxg/dxcommon.h125
-rw-r--r--dxg/include/dxg/dxg.h4
-rw-r--r--dxg/include/dxg/imm.h13
-rw-r--r--dxg/shaders/CMakeLists.txt98
-rw-r--r--dxg/shaders/imm.hlsl23
-rw-r--r--dxg/src/dxcommon.c173
-rw-r--r--dxg/src/imm.c364
8 files changed, 854 insertions, 0 deletions
diff --git a/dxg/CMakeLists.txt b/dxg/CMakeLists.txt
new file mode 100644
index 0000000..1040276
--- /dev/null
+++ b/dxg/CMakeLists.txt
@@ -0,0 +1,54 @@
1cmake_minimum_required(VERSION 3.20)
2
3project(dxg)
4
5# Agility SDK.
6# Give AGILITY_SDK_BIN PARENT_SCOPE so that it is accessible from the
7# install_agility_sdk function below.
8set(AGILITY_SDK_DIR "../contrib/microsoft.direct3d.d3d12.1.618.4/build/native")
9set(AGILITY_SDK_BIN "${AGILITY_SDK_DIR}/bin/x64" PARENT_SCOPE)
10
11add_subdirectory(shaders)
12
13add_library(dxg
14 include/dxg/dxcommon.h
15 include/dxg/dxg.h
16 include/dxg/imm.h
17 src/dxcommon.c
18 src/imm.c)
19
20# exe must export these so that D3D12.dll can find and load D3D12Core.dll and
21# other DLLs from the Agility SDK.
22target_compile_definitions(dxg PRIVATE
23 AGILITY_SDK_VERSION=618 # Must match AGILITY_SDK_DIR above.
24 AGILITY_SDK_INSTALL=u8".\\\\D3D12\\\\") # Binaries will be copied to this subdirectory.
25
26# Use BEFORE so that the D3D headers in the Agility SDK are picked before the
27# ones in the Windows SDK.
28target_include_directories(dxg BEFORE PUBLIC
29 include
30 ${AGILITY_SDK_DIR}/include)
31
32target_link_directories(dxg BEFORE PUBLIC
33 ${AGILITY_SDK_DIR}/bin/x64)
34
35target_link_libraries(dxg PUBLIC
36 DirectX-Headers
37 D3D12.lib
38 DXGI.lib
39 DXGUID.lib) # For IID_Xyz symbols
40
41target_link_libraries(dxg PRIVATE
42 shaders)
43
44# Function to copy Agility SDK binaries to the D3D12\ directory next to the exe.
45# exe targets must call this function.
46function(install_agility_sdk exe_path)
47 cmake_path(APPEND d3d12_path ${exe_path} "D3D12")
48 if(NOT EXISTS ${d3d12_path})
49 message("D3D12 path = ${d3d12_path}")
50 message("AGILITY_SDK_BIN = ${AGILITY_SDK_BIN}")
51 file(MAKE_DIRECTORY ${d3d12_path})
52 file(COPY ${AGILITY_SDK_BIN}/ DESTINATION ${d3d12_path})
53 endif()
54endfunction()
diff --git a/dxg/include/dxg/dxcommon.h b/dxg/include/dxg/dxcommon.h
new file mode 100644
index 0000000..6cc3466
--- /dev/null
+++ b/dxg/include/dxg/dxcommon.h
@@ -0,0 +1,125 @@
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 <stdbool.h>
9#include <stdio.h>
10#include <stdlib.h>
11
12#define COUNTOF(ARR) (sizeof(ARR) / sizeof(ARR[0]))
13
14// MSVC currently has neither aligned_alloc nor alignof.
15//#define ALLOC(TYPE, COUNT) (TYPE*)aligned_alloc(alignof(TYPE), sizeof(TYPE) * (COUNT))
16
17typedef struct alloc_t {
18 void* base; // The result of malloc(). Pass this to free().
19 void* ptr; // Aligned pointer within the allocation.
20} alloc_t;
21
22static inline bool is_pow2_or_0(size_t x) { return (x & (x - 1)) == 0; }
23
24static inline void* align(void* address, size_t alignment) {
25 assert(is_pow2_or_0(alignment));
26 const size_t mask = alignment - 1;
27 return (void*)(((uintptr_t)address + mask) & ~mask);
28}
29
30static inline alloc_t alloc_aligned(size_t size, size_t alignment) {
31 void* base = malloc(size + (alignment - 1));
32 return (alloc_t){.base = base, .ptr = align(base, alignment)};
33}
34
35static inline free_aligned(alloc_t* alloc) {
36 assert(alloc);
37 free(alloc->base);
38 *alloc = (alloc_t){0};
39}
40
41#define ALIGNOF(TYPE) _Alignof(TYPE)
42#define ALLOC(TYPE, COUNT) alloc_aligned(sizeof(TYPE) * (COUNT), ALIGNOF(TYPE))
43#define FREE(ALLOC) free_aligned(&(ALLOC))
44
45#ifndef NDEBUG
46#define WIN32_LEAN_AND_MEAN
47#include <Windows.h> // OutputDebugStringA
48#define DEBUG_PRINT OutputDebugStringA
49#else
50#define DEBUG_PRINT
51#endif
52
53#define TRAP(ERROR) { \
54 fprintf(stderr, "Error in file:[%s] line:%d: %s\n", __FILE__, __LINE__, ERROR);\
55 assert(false);\
56 __debugbreak();\
57}
58
59#define TRAP_HRESULT(RESULT) { \
60 fprintf(stderr, "HRESULT: 0x%x - ", RESULT);\
61 TRAP("API call failed")\
62}
63
64#define TrapIfFailed(RESULT) {\
65 if (RESULT != S_OK) {\
66 TRAP_HRESULT(RESULT);\
67 }\
68}
69
70#define SafeRelease(PTR) {\
71 if (PTR) {\
72 PTR->lpVtbl->Release(PTR);\
73 PTR = 0;\
74 }\
75}
76
77#define MIN(A, B) ((A) < (B) ? (A) : (B))
78
79#define CD3DX12_CPU_DESCRIPTOR_HANDLE(HANDLE, INDEX, SIZE) \
80 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (INDEX * SIZE)}
81
82#define OFFSET_HANDLE(HANDLE, OFFSET, SIZE) \
83 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (OFFSET * SIZE)}
84
85typedef enum SampleMask {
86 PointSampling = 0xffffffff
87} SampleMask;
88
89D3D12_RESOURCE_BARRIER CD3DX12_RESOURCE_BARRIER_Transition(
90 ID3D12Resource* pResource,
91 D3D12_RESOURCE_STATES stateBefore,
92 D3D12_RESOURCE_STATES stateAfter);
93D3D12_RASTERIZER_DESC CD3DX12_RASTERIZER_DESC_DEFAULT();
94D3D12_BLEND_DESC CD3DX12_BLEND_DESC_DEFAULT();
95
96void dxg_wait(ID3D12Fence*, HANDLE fenceEvent, UINT64 fenceValue);
97
98// -----------------------------------------------------------------------------
99// Command Recorder
100// -----------------------------------------------------------------------------
101
102// Currently handling graphics command lists only.
103// Add compute when needed.
104typedef struct CommandRecorder {
105 ID3D12GraphicsCommandList* pCmdList;
106 ID3D12CommandAllocator* pCmdAllocator;
107} CommandRecorder;
108
109HRESULT dxg_cmdrec_init(CommandRecorder*, ID3D12Device*);
110void dxg_cmdrec_destroy(CommandRecorder*);
111HRESULT dxg_cmdrec_reset(CommandRecorder*);
112
113// -----------------------------------------------------------------------------
114// Upload Buffer
115// -----------------------------------------------------------------------------
116
117typedef struct UploadBuffer {
118 ID3D12Resource* pUploadBuffer;
119 size_t size;
120} UploadBuffer;
121
122void dxg_upload_buffer_init(UploadBuffer*, ID3D12Device*, size_t size);
123void dxg_upload_buffer_destroy(UploadBuffer*, ID3D12Device*);
124void dxg_upload_buffer_load(UploadBuffer*, const void* pData, size_t bytes, ID3D12Resource* pDstBuffer);
125bool dxg_upload_buffer_done(UploadBuffer*);
diff --git a/dxg/include/dxg/dxg.h b/dxg/include/dxg/dxg.h
new file mode 100644
index 0000000..0982d9c
--- /dev/null
+++ b/dxg/include/dxg/dxg.h
@@ -0,0 +1,4 @@
1#pragma once
2
3#include <dxg/dxcommon.h>
4#include <dxg/imm.h>
diff --git a/dxg/include/dxg/imm.h b/dxg/include/dxg/imm.h
new file mode 100644
index 0000000..831c1b8
--- /dev/null
+++ b/dxg/include/dxg/imm.h
@@ -0,0 +1,13 @@
1#pragma once
2
3#include <dxg/dxcommon.h>
4
5#include <stddef.h>
6
7typedef struct DxgImm DxgImm;
8
9DxgImm* dxg_imm_init(ID3D12Device* pDevice, ID3D12CommandQueue*, DXGI_FORMAT swapChainRtvFormat, DXGI_SAMPLE_DESC swapChainSampleDesc, size_t bufferSize);
10void dxg_imm_destroy(DxgImm**);
11void dxg_imm_set_graphics_state(DxgImm*, const D3D12_VIEWPORT*, D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView, D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView);
12void dxg_imm_flush(DxgImm*);
13void dxg_imm_draw_triangles(DxgImm*, const float* pVerts, size_t numTris);
diff --git a/dxg/shaders/CMakeLists.txt b/dxg/shaders/CMakeLists.txt
new file mode 100644
index 0000000..c6ec687
--- /dev/null
+++ b/dxg/shaders/CMakeLists.txt
@@ -0,0 +1,98 @@
1set(SHADER_MODEL "6_5")
2set(DXC ${PROJECT_SOURCE_DIR}/../contrib/dxc_2025_07_14/bin/x64/dxc.exe)
3set(SHADERS_PATH ${CMAKE_CURRENT_SOURCE_DIR})
4set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}")
5
6# Compile HLSL to binary DXIL.
7#
8# This defines a custom command so that the shader is compiled at cmake build
9# instead of configure.
10#
11# hlsl_file: "foo.hlsl"; just the file name, not the path.
12# shader_type: "vs", "ps", "cs", etc.
13# entry_point: "main", etc.
14function(compile_shader hlsl_file shader_type entry_point dxil_file)
15 #string(REPLACE ".hlsl" ".dxil" dxil_file ${hlsl_file})
16 set(hlsl_path "${SHADERS_PATH}/${hlsl_file}")
17 set(dxil_path "${BUILD_DIR}/${dxil_file}")
18 set(target_profile "${shader_type}_${SHADER_MODEL}")
19 message("COMPILING ${dxil_path}")
20 message("DXC = ${DXC}")
21 add_custom_command(
22 OUTPUT ${dxil_path}
23 COMMAND ${DXC} -T ${target_profile} -E ${entry_point} ${hlsl_path} -Fo ${dxil_path}
24 WORKING_DIRECTORY ${BUILD_DIR}
25 DEPENDS ${hlsl_path}
26 COMMENT "Generating ${dxil_path}")
27endfunction()
28
29# Inline a binary file into C code.
30# This is a workaround for the lack of C23 #embed in MSVC.
31#
32# identifier: Identifier to use for the global array that will contain the file,
33# the generated file names, and the cmake target.
34# file_path: Path to the binary file to embed.
35#
36# Reference: https://github.com/andoalon/embed-binaries
37function(generate_c file_path identifier out_header_path out_source_path)
38 file(READ "${file_path}" file_contents HEX)
39 string(LENGTH "${file_contents}" file_contents_length)
40 math(EXPR file_bytes "${file_contents_length} / 2")
41
42 set(bytes_per_line 32)
43 string(REPEAT "[0-9a-f]" ${bytes_per_line} column_pattern)
44 string(REGEX REPLACE "(${column_pattern})" "\\1\n" code "${file_contents}")
45 string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," code "${code}")
46
47 set(declaration "const uint8_t ${identifier}[${file_bytes}]")
48
49 string(APPEND header "#pragma once\n\n#include <stdint.h>\n\nextern ${declaration}\;\n")
50 string(APPEND implementation "#include \"${identifier}.h\"\n\n${declaration} = {\n${code}}\;\n")
51
52 file(WRITE "${BUILD_DIR}/${identifier}.h" ${header})
53 file(WRITE "${BUILD_DIR}/${identifier}.c" ${implementation})
54endfunction()
55
56# Create a target that embeds the given DXIL file in a C header/source.
57# Like compile_shader, this just adds the target so that the embedding is done
58# at cmake build and not configure.
59function(create_c_target dxil_file identifier)
60 set(dxil_path "${BUILD_DIR}/${dxil_file}")
61 string(REPLACE ".dxil" ".h" header_path ${dxil_path})
62 string(REPLACE ".dxil" ".c" source_path ${dxil_path})
63 add_custom_command(
64 OUTPUT "${header_path}" "${source_path}"
65 COMMAND ${CMAKE_COMMAND} -D dxil_path=${dxil_path} -D identifier=${identifier} -D out_header_path=${header_path} -D out_source_path=${source_path} -P ${CMAKE_CURRENT_LIST_FILE}
66 WORKING_DIRECTORY ${BUILD_DIR}
67 DEPENDS ${dxil_path}
68 COMMENT "Generating ${source_path}"
69 )
70endfunction()
71
72# Running in script mode.
73# https://stackoverflow.com/questions/51427538/cmake-test-if-i-am-in-scripting-mode
74#
75# When running in script mode, we embed the binary DXIL into a generated C file.
76if(CMAKE_SCRIPT_MODE_FILE AND NOT CMAKE_PARENT_LIST_FILE)
77 foreach(variable "dxil_path" "identifier" "out_header_path" "out_source_path")
78 if (NOT DEFINED ${variable})
79 message(FATAL_ERROR "'${variable}' is not defined")
80 endif()
81 endforeach()
82 generate_c("${dxil_path}" ${identifier} "${out_header_path}" "${out_source_path}")
83else()
84 compile_shader("imm.hlsl" "vs" "vs" "imm_vs.dxil")
85 compile_shader("imm.hlsl" "ps" "ps" "imm_ps.dxil")
86
87 create_c_target("imm_vs.dxil" "imm_vs")
88 create_c_target("imm_ps.dxil" "imm_ps")
89
90 add_library(shaders
91 "${BUILD_DIR}/imm_ps.c"
92 "${BUILD_DIR}/imm_ps.h"
93 "${BUILD_DIR}/imm_vs.c"
94 "${BUILD_DIR}/imm_vs.h")
95
96 target_include_directories(shaders PUBLIC
97 ${BUILD_DIR})
98endif()
diff --git a/dxg/shaders/imm.hlsl b/dxg/shaders/imm.hlsl
new file mode 100644
index 0000000..da6b1f6
--- /dev/null
+++ b/dxg/shaders/imm.hlsl
@@ -0,0 +1,23 @@
1struct VertexIn {
2 float3 position : POSITION;
3};
4
5struct VertexOut {
6 float4 position : SV_POSITION;
7};
8
9struct PixelOut {
10 float4 color : SV_TARGET;
11};
12
13VertexOut vs(VertexIn vin) {
14 VertexOut vout;
15 vout.position = float4(vin.position, 1.0f);
16 return vout;
17}
18
19PixelOut ps(VertexOut vout) {
20 PixelOut pixel;
21 pixel.color = float4(0.9f, 0.2f, 0.9f, 1.0f);
22 return pixel;
23}
diff --git a/dxg/src/dxcommon.c b/dxg/src/dxcommon.c
new file mode 100644
index 0000000..ecc9a88
--- /dev/null
+++ b/dxg/src/dxcommon.c
@@ -0,0 +1,173 @@
1#include <dxg/dxcommon.h>
2
3// Required so that D3D12.dll can find and load D3D12Core.dll and other DLLs
4// from the Agility SDK. The macro comes from CMakeLists.txt.
5__declspec(dllexport) extern const UINT D3D12SDKVersion = AGILITY_SDK_VERSION;
6__declspec(dllexport) extern const char* D3D12SDKPath = AGILITY_SDK_INSTALL;
7D3D12_RESOURCE_BARRIER CD3DX12_RESOURCE_BARRIER_Transition(
8 ID3D12Resource* pResource,
9 D3D12_RESOURCE_STATES stateBefore,
10 D3D12_RESOURCE_STATES stateAfter) {
11 return (D3D12_RESOURCE_BARRIER){
12 .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
13 .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
14 .Transition.pResource = pResource,
15 .Transition.StateBefore = stateBefore,
16 .Transition.StateAfter = stateAfter,
17 .Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES};
18}
19
20D3D12_RASTERIZER_DESC CD3DX12_RASTERIZER_DESC_DEFAULT() {
21 return (D3D12_RASTERIZER_DESC){
22 .FillMode = D3D12_FILL_MODE_SOLID,
23 .CullMode = D3D12_CULL_MODE_BACK,
24 .FrontCounterClockwise = FALSE,
25 .DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
26 .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
27 .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
28 .DepthClipEnable = TRUE,
29 .MultisampleEnable = FALSE,
30 .AntialiasedLineEnable = FALSE,
31 .ForcedSampleCount = 0,
32 .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF};
33}
34
35D3D12_BLEND_DESC CD3DX12_BLEND_DESC_DEFAULT() {
36 const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {
37 FALSE, FALSE,
38 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
39 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
40 D3D12_LOGIC_OP_NOOP,
41 D3D12_COLOR_WRITE_ENABLE_ALL,
42 };
43 D3D12_BLEND_DESC desc = {
44 .AlphaToCoverageEnable = FALSE,
45 .IndependentBlendEnable = FALSE,
46 };
47 for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
48 desc.RenderTarget[i] = defaultRenderTargetBlendDesc;
49 }
50 return desc;
51}
52
53void dxg_wait(ID3D12Fence* pFence, HANDLE fenceEvent, UINT64 fenceValue) {
54 assert(pFence);
55 // Wait for commands to finish execution.
56 // It is possible that execution has already finished by the time we
57 // get here, so first check the fence's completed value.
58 if (pFence->lpVtbl->GetCompletedValue(pFence) < fenceValue) {
59 // GPU Signal still pending. Configure a Windows event and wait for it.
60 // The event fires when the GPU signals.
61 //
62 // Indicate that the fence event is to be fired when the fence reaches
63 // the given fence value.
64 TrapIfFailed(pFence->lpVtbl->SetEventOnCompletion(pFence, fenceValue, fenceEvent));
65 // Will wake up when the fence takes on the given fence value.
66 WaitForSingleObject(fenceEvent, INFINITE);
67 }
68}
69
70// -----------------------------------------------------------------------------
71// Command Recorder
72// -----------------------------------------------------------------------------
73
74HRESULT dxg_cmdrec_init(CommandRecorder* pRec, ID3D12Device* pDevice) {
75 assert(pRec);
76 assert(pDevice);
77
78 HRESULT result = S_OK;
79
80 const D3D12_COMMAND_LIST_TYPE type = D3D12_COMMAND_LIST_TYPE_DIRECT;
81
82 if ((result = pDevice->lpVtbl->CreateCommandAllocator(
83 pDevice, type, &IID_ID3D12CommandAllocator, &pRec->pCmdAllocator)) != S_OK) {
84 return result;
85 }
86
87 if ((result = pDevice->lpVtbl->CreateCommandList(
88 pDevice, 0, type, pRec->pCmdAllocator, NULL, &IID_ID3D12CommandList, &pRec->pCmdList)) != S_OK) {
89 return result;
90 }
91
92 // Command lists start open. Close it for API convenience.
93 if ((result = pRec->pCmdList->lpVtbl->Close(pRec->pCmdList)) != S_OK) {
94 return result;
95 }
96
97 return result;
98}
99
100void dxg_cmdrec_destroy(CommandRecorder* pRec) {
101 assert(pRec);
102 SafeRelease(pRec->pCmdList);
103 SafeRelease(pRec->pCmdAllocator);
104}
105
106HRESULT dxg_cmdrec_reset(CommandRecorder* pRec) {
107 assert(pRec);
108 assert(pRec->pCmdAllocator);
109 assert(pRec->pCmdList);
110 HRESULT result = S_OK;
111 if ((result = pRec->pCmdAllocator->lpVtbl->Reset(pRec->pCmdAllocator)) != S_OK) {
112 return result;
113 }
114 if ((result = pRec->pCmdList->lpVtbl->Reset(pRec->pCmdList, pRec->pCmdAllocator, NULL)) != S_OK) {
115 return result;
116 }
117 return result;
118}
119
120// -----------------------------------------------------------------------------
121// Upload Buffer
122// -----------------------------------------------------------------------------
123
124void dxg_upload_buffer_init(UploadBuffer* pBuf, ID3D12Device* pDevice, size_t size) {
125 assert(pBuf);
126 assert(pDevice);
127
128 pBuf->size = size;
129
130 const D3D12_HEAP_PROPERTIES props = {
131 .Type = D3D12_HEAP_TYPE_UPLOAD,
132 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
133 .MemoryPoolPreference = D3D12_MEMORY_POOL_L0,
134 .CreationNodeMask = 0,
135 .VisibleNodeMask = 0
136 };
137 // Constant buffers need to be aligned to 256 bytes. Other types of buffers
138 // do not have this requirement. To make the upload buffer general, use the
139 // worst-case alignment.
140 const D3D12_RESOURCE_DESC desc = {
141 .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
142 .Alignment = 256,
143 .Width = size,
144 .Height = 0,
145 .DepthOrArraySize = 0,
146 .MipLevels = 0,
147 .Format = DXGI_FORMAT_UNKNOWN,
148 .SampleDesc = (DXGI_SAMPLE_DESC){0},
149 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
150 .Flags = D3D12_RESOURCE_FLAG_NONE
151 };
152 TrapIfFailed(pDevice->lpVtbl->CreateCommittedResource(
153 pDevice,
154 &props,
155 D3D12_HEAP_FLAG_NONE,
156 &desc,
157 D3D12_RESOURCE_STATE_COPY_SOURCE,
158 NULL,
159 &IID_ID3D12Resource,
160 &pBuf->pUploadBuffer));
161}
162
163void dxg_upload_buffer_destroy(UploadBuffer* pBuf, ID3D12Device* pDevice) {
164 assert(pDevice);
165 assert(pBuf);
166 SafeRelease(pBuf->pUploadBuffer);
167}
168
169void dxg_upload_buffer_load(UploadBuffer* pBuf, const void* pData, size_t bytes, ID3D12Resource* pDstBuffer) {
170 assert(pBuf);
171 assert(pData);
172 assert(pDstBuffer);
173}
diff --git a/dxg/src/imm.c b/dxg/src/imm.c
new file mode 100644
index 0000000..4a4be93
--- /dev/null
+++ b/dxg/src/imm.c
@@ -0,0 +1,364 @@
1/* Immediate-mode renderer.
2
3Geometry is given by client code and buffered in an upload-heap buffer stored
4in host memory.
5When the buffer fills up or the client is done, a draw call is issued. The draw
6call reads directly from the buffer in host memory; there is no intermediate
7buffer copy.
8The renderer double-buffers two host-side buffers so that the client can
9continue specifying more data into a second buffer while the contents of the
10first buffer are rendered.
11If the first buffer is still being rendered while the client loops around, then
12the client must wait before issuing further geometry.
13Once the render of the first buffer completes, the process starts again,
14ping-ponging between the two buffers.*/
15#include <dxg/imm.h>
16#include <dxg/dxcommon.h>
17
18#include <imm_vs.h> // generated
19#include <imm_ps.h> // generated
20
21#include <stdint.h>
22#include <stdlib.h>
23
24static ID3D12Resource* create_buffer(ID3D12Device* pDevice, size_t size) {
25 assert(pDevice);
26 const D3D12_HEAP_PROPERTIES props = {
27 .Type = D3D12_HEAP_TYPE_UPLOAD,
28 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
29 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
30 .CreationNodeMask = 0,
31 .VisibleNodeMask = 0
32 };
33 const D3D12_RESOURCE_DESC desc = {
34 .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
35 .Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
36 .Width = size,
37 .Height = 1,
38 .DepthOrArraySize = 1,
39 .MipLevels = 1,
40 .Format = DXGI_FORMAT_UNKNOWN,
41 .SampleDesc = {.Count = 1, .Quality = 0},
42 .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
43 .Flags = D3D12_RESOURCE_FLAG_NONE
44 };
45 ID3D12Resource* pBuffer = NULL;
46 TrapIfFailed(pDevice->lpVtbl->CreateCommittedResource(
47 pDevice,
48 &props,
49 D3D12_HEAP_FLAG_NONE,
50 &desc,
51 D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
52 NULL,
53 &IID_ID3D12Resource,
54 &pBuffer));
55 return pBuffer;
56}
57
58typedef struct GraphicsState {
59 D3D12_VIEWPORT viewport;
60 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView;
61 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView;
62} GraphicsState;
63
64// Set of per-draw resources. The renderer cycles between sets per draw.
65typedef struct ResourceSet {
66 ID3D12Resource* pVertexBuffer;
67 CommandRecorder cmdRec;
68} ResourceSet;
69
70typedef struct DxgImm {
71 ID3D12Device* pDevice;
72 ID3D12CommandQueue* pCmdQueue;
73 ID3D12PipelineState* pPipelineState;
74 ID3D12RootSignature* pRootSignature;
75 GraphicsState graphicsState;
76 ResourceSet resources[2];
77 int cur; // Index to current resource set. New geometry written here.
78 float* pCurrentBufferData; // Mapped region of current buffer.
79 size_t bufferSizeVerts; // Num verts per buffer.
80 ID3D12Fence* pFence;
81 HANDLE fenceEvent;
82 uint64_t fenceValue;
83 size_t vertsWritten; // Verts written to current buffer.
84 bool wait; // Whether the next draw should wait.
85} DxgImm;
86
87static inline size_t vertex_size_bytes() {
88 return 3 * sizeof(float);
89}
90
91static inline size_t verts_byte_count(size_t numVerts) {
92 return numVerts * vertex_size_bytes();
93}
94
95static inline size_t dxg_imm_verts_left(const DxgImm* imm) {
96 assert(imm);
97 assert(imm->bufferSizeVerts >= imm->vertsWritten);
98 return imm->bufferSizeVerts - imm->vertsWritten;
99}
100
101static void dxg_imm_copy_verts(DxgImm* imm, const float* pVerts, size_t count) {
102 assert(imm);
103 assert(pVerts);
104 assert(count <= dxg_imm_verts_left(imm));
105 memcpy(&imm->pCurrentBufferData[imm->vertsWritten], pVerts, verts_byte_count(count));
106 imm->vertsWritten += count;
107}
108
109// Set up the current resource set for drawing.
110static void dxg_imm_set_up_resource_set(DxgImm* imm) {
111 assert(imm);
112 ResourceSet* const pResources = &imm->resources[imm->cur];
113 TrapIfFailed(pResources->pVertexBuffer->lpVtbl->Map(
114 pResources->pVertexBuffer, 0, NULL, &imm->pCurrentBufferData));
115 dxg_cmdrec_reset(&pResources->cmdRec);
116}
117
118// Move on to the next resource set.
119static ID3D12Resource* dxg_imm_next_resource_set(DxgImm* imm) {
120 assert(imm);
121 ResourceSet* const pResources = &imm->resources[imm->cur];
122 // Unmap the current buffer.
123 // TODO: Do we actually need to do this or can we leave it mapped? If the
124 // latter, then we could just map both buffers and let them be.
125 pResources->pVertexBuffer->lpVtbl->Unmap(pResources->pVertexBuffer, 0, NULL);
126 // Move on to the next resource set.
127 imm->cur = (imm->cur + 1) & 1;
128 imm->vertsWritten = 0;
129 // Set up the new resource set.
130 dxg_imm_set_up_resource_set(imm);
131}
132
133// Wait for the current buffer to be available for writing.
134static void dxg_imm_wait(DxgImm* imm) {
135 assert(imm);
136 assert(imm->wait);
137 // We only need to wait upon the first round around both buffers.
138 // First Signal is on fence value 1, 0 is not actually Signaled.
139 if (imm->fenceValue > 2) { // TODO: Do we need this check?
140 // The last buffer (not current) was Signaled with fenceValue - 1.
141 // The current buffer was therefore Signaled two fence values ago, or
142 // fenceValue - 2.
143 dxg_wait(imm->pFence, imm->fenceEvent, imm->fenceValue - 2);
144 }
145 imm->wait = false;
146}
147
148// Draw the current buffer.
149static void dxg_imm_draw(DxgImm* imm) {
150 assert(imm);
151 ResourceSet* const pResourceSet = &imm->resources[imm->cur];
152 ID3D12Resource* const pCurrentBuffer = pResourceSet->pVertexBuffer;
153 ID3D12GraphicsCommandList* const pCmdList = pResourceSet->cmdRec.pCmdList;
154 const D3D12_VIEWPORT* const pViewport = &imm->graphicsState.viewport;
155 const D3D12_RECT scissor = {
156 .bottom = pViewport->Height,
157 .left = 0,
158 .right = pViewport->Width,
159 .top = 0,
160 };
161 const D3D12_VERTEX_BUFFER_VIEW vertexBufferView = {
162 .BufferLocation = pCurrentBuffer->lpVtbl->GetGPUVirtualAddress(pCurrentBuffer),
163 .SizeInBytes = verts_byte_count(imm->vertsWritten),
164 .StrideInBytes = vertex_size_bytes(),
165 };
166 pCmdList->lpVtbl->RSSetViewports(pCmdList, 1, pViewport);
167 pCmdList->lpVtbl->RSSetScissorRects(pCmdList, 1, &scissor);
168 pCmdList->lpVtbl->OMSetRenderTargets(
169 pCmdList, 1, &imm->graphicsState.hBackBufferView, false, &imm->graphicsState.hDepthStencilView);
170 pCmdList->lpVtbl->SetPipelineState(pCmdList, imm->pPipelineState);
171 pCmdList->lpVtbl->SetGraphicsRootSignature(pCmdList, imm->pRootSignature);
172 pCmdList->lpVtbl->IASetPrimitiveTopology(pCmdList, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
173 pCmdList->lpVtbl->IASetVertexBuffers(pCmdList, 0, 1, &vertexBufferView);
174 pCmdList->lpVtbl->DrawInstanced(pCmdList, imm->vertsWritten, 1, 0, 0);
175 pCmdList->lpVtbl->Close(pCmdList);
176 ID3D12CommandList* const cmdLists[] = {(ID3D12CommandList*)pCmdList};
177 ID3D12CommandQueue* const pCmdQueue = imm->pCmdQueue;
178 pCmdQueue->lpVtbl->ExecuteCommandLists(pCmdQueue, 1, cmdLists);
179}
180
181DxgImm* dxg_imm_init(ID3D12Device* pDevice, ID3D12CommandQueue* pCmdQueue, DXGI_FORMAT swapChainRtvFormat, DXGI_SAMPLE_DESC swapChainSampleDesc, size_t bufferSize) {
182 assert(pDevice);
183 assert(pCmdQueue);
184
185 DxgImm* imm = calloc(1, sizeof(DxgImm));
186 if (!imm) {
187 return 0;
188 }
189
190 imm->pDevice = pDevice;
191 imm->pCmdQueue = pCmdQueue;
192 imm->bufferSizeVerts = bufferSize / 3 / sizeof(float);
193 imm->fenceValue = 0;
194
195 // TODO: Move this to the application side.
196 const D3D_SHADER_MODEL model = D3D_SHADER_MODEL_6_5;
197 D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { model };
198 HRESULT result = pDevice->lpVtbl->CheckFeatureSupport(
199 pDevice, D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
200 if (FAILED(result) || (shaderModel.HighestShaderModel < model)) {
201 DEBUG_PRINT("ERROR: Shader Model 6.5 is not supported!\n");
202 TrapIfFailed(result);
203 }
204
205 const D3D12_SHADER_BYTECODE vs_bytecode = {
206 .pShaderBytecode = imm_vs,
207 .BytecodeLength = sizeof(imm_vs)
208 };
209
210 const D3D12_SHADER_BYTECODE ps_bytecode = {
211 .pShaderBytecode = imm_ps,
212 .BytecodeLength = sizeof(imm_ps)
213 };
214
215 // TODO: Find out how many root parameters to use.
216 // Let's do bindless rendering to keep things flexible.
217 const D3D12_ROOT_SIGNATURE_DESC rootsig_desc = {
218 .NumParameters = 0,
219 .pParameters = NULL,
220 .NumStaticSamplers = 0,
221 .pStaticSamplers = NULL,
222 .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
223 };
224
225 ID3DBlob* pRootSignature = NULL;
226 ID3DBlob* pErrors = NULL;
227 result = D3D12SerializeRootSignature(
228 &rootsig_desc,
229 D3D_ROOT_SIGNATURE_VERSION_1,
230 &pRootSignature,
231 &pErrors);
232 if (FAILED(result)) {
233 if (pErrors) {
234 DEBUG_PRINT(pErrors->lpVtbl->GetBufferPointer(pErrors));
235 }
236 TrapIfFailed(result);
237 }
238
239 TrapIfFailed(imm->pDevice->lpVtbl->CreateRootSignature(
240 imm->pDevice,
241 0,
242 pRootSignature->lpVtbl->GetBufferPointer(pRootSignature),
243 pRootSignature->lpVtbl->GetBufferSize(pRootSignature),
244 &IID_ID3D12RootSignature,
245 &imm->pRootSignature));
246
247 const D3D12_INPUT_ELEMENT_DESC input_layout[] = {
248 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
249 };
250 const D3D12_INPUT_LAYOUT_DESC input_layout_desc = {
251 .pInputElementDescs = input_layout,
252 .NumElements = COUNTOF(input_layout)
253 };
254
255 const D3D12_GRAPHICS_PIPELINE_STATE_DESC gpso = {
256 .pRootSignature = imm->pRootSignature,
257 .VS = vs_bytecode,
258 .PS = ps_bytecode,
259 .BlendState = CD3DX12_BLEND_DESC_DEFAULT(),
260 .SampleMask = PointSampling,
261 .RasterizerState = CD3DX12_RASTERIZER_DESC_DEFAULT(),
262 .InputLayout = input_layout_desc,
263 .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
264 .NumRenderTargets = 1,
265 .RTVFormats = {swapChainRtvFormat},
266 .SampleDesc = swapChainSampleDesc
267 };
268 TrapIfFailed(imm->pDevice->lpVtbl->CreateGraphicsPipelineState(
269 imm->pDevice, &gpso, &IID_ID3D12PipelineState, &imm->pPipelineState));
270
271 for (int i = 0; i < 2; ++i) {
272 imm->resources[i].pVertexBuffer = create_buffer(pDevice, bufferSize);
273 if (!imm->resources[i].pVertexBuffer) {
274 dxg_imm_destroy(&imm);
275 }
276 TrapIfFailed(dxg_cmdrec_init(&imm->resources[i].cmdRec, pDevice));
277 }
278 imm->cur = 0;
279 dxg_imm_set_up_resource_set(imm);
280
281 TrapIfFailed(pDevice->lpVtbl->CreateFence(
282 pDevice, imm->fenceValue, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, &imm->pFence));
283
284 if ((imm->fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
285 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
286 }
287
288 return imm;
289}
290
291void dxg_imm_destroy(DxgImm** ppImm) {
292 assert(ppImm);
293 DxgImm* imm = *ppImm;
294 if (imm) {
295 for (int i = 0; i < 2; ++i) {
296 SafeRelease(imm->resources[i].pVertexBuffer);
297 dxg_cmdrec_destroy(&imm->resources[i].cmdRec);
298 }
299 SafeRelease(imm->pRootSignature);
300 SafeRelease(imm->pPipelineState);
301 SafeRelease(imm->pFence);
302 if (imm->fenceEvent != NULL) {
303 CloseHandle(imm->fenceEvent);
304 }
305 free(imm);
306 *ppImm = 0;
307 }
308}
309
310void dxg_imm_set_graphics_state(
311 DxgImm* imm,
312 const D3D12_VIEWPORT* pViewport,
313 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView,
314 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView) {
315 assert(imm);
316 assert(pViewport);
317 assert(hBackBufferView.ptr);
318 assert(hDepthStencilView.ptr);
319 imm->graphicsState = (GraphicsState) {
320 .viewport = *pViewport,
321 .hBackBufferView = hBackBufferView,
322 .hDepthStencilView = hDepthStencilView,
323 };
324}
325
326void dxg_imm_flush(DxgImm* imm) {
327 assert(imm);
328 if (imm->vertsWritten > 0) {
329 dxg_imm_draw(imm);
330 // Signal the fence so that the current buffer can be reused once the
331 // draw has finished.
332 ID3D12CommandQueue* pCmdQueue = imm->pCmdQueue;
333 imm->fenceValue++;
334 pCmdQueue->lpVtbl->Signal(pCmdQueue, imm->pFence, imm->fenceValue);
335 // Next draw should Wait for the next buffer. Wait lazily on the next
336 // draw to avoid a stall here.
337 imm->wait = true;
338 dxg_imm_next_resource_set(imm);
339 }
340}
341
342void dxg_imm_draw_triangles(DxgImm* imm, const float* pVerts, size_t numTris) {
343 assert(imm);
344 assert(pVerts);
345 // TODO: This could be a loop to handle the case where the max buffer
346 // capacity cannot hold numTris. Or maybe we should rely on the caller
347 // to specify a big enough capacity, but that makes the API less
348 // friendly.
349 size_t triCapacity = dxg_imm_verts_left(imm) / 3;
350 if (triCapacity == 0) {
351 dxg_imm_flush(imm);
352 }
353 // If we just flushed the previous buffer, then we have to wait on the next
354 // one. The wait is done here, and not inside the branch above, because the
355 // client code can also flush the buffer.
356 if (imm->wait) {
357 dxg_imm_wait(imm);
358 }
359 // Re-evaluate capacity. It must be >0 now.
360 triCapacity = dxg_imm_verts_left(imm) / 3;
361 assert(triCapacity > 0);
362 const size_t numVerts = MIN(triCapacity, numTris) * 3;
363 dxg_imm_copy_verts(imm, pVerts, numVerts);
364}