aboutsummaryrefslogtreecommitdiff
path: root/dxg
diff options
context:
space:
mode:
Diffstat (limited to 'dxg')
-rw-r--r--dxg/CMakeLists.txt49
-rw-r--r--dxg/include/dxg/dxcommon.h98
-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/dxg.c1
-rw-r--r--dxg/src/imm.c368
9 files changed, 802 insertions, 25 deletions
diff --git a/dxg/CMakeLists.txt b/dxg/CMakeLists.txt
index b7607c0..1040276 100644
--- a/dxg/CMakeLists.txt
+++ b/dxg/CMakeLists.txt
@@ -2,22 +2,53 @@ cmake_minimum_required(VERSION 3.20)
2 2
3project(dxg) 3project(dxg)
4 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
5add_library(dxg 13add_library(dxg
6 include/dxg/dxcommon.h 14 include/dxg/dxcommon.h
7 src/dxg.c) 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.
8 25
9# target_sources(dxg PUBLIC 26# Use BEFORE so that the D3D headers in the Agility SDK are picked before the
10# FILE_SET cxx_modules TYPE CXX_MODULES FILES 27# ones in the Windows SDK.
11# asset.ixx 28target_include_directories(dxg BEFORE PUBLIC
12# dxcommon.ixx 29 include
13# dxg.ixx 30 ${AGILITY_SDK_DIR}/include)
14# imm.ixx)
15 31
16target_include_directories(dxg PUBLIC 32target_link_directories(dxg BEFORE PUBLIC
17 include) 33 ${AGILITY_SDK_DIR}/bin/x64)
18 34
19target_link_libraries(dxg PUBLIC 35target_link_libraries(dxg PUBLIC
20 DirectX-Headers 36 DirectX-Headers
21 D3D12.lib 37 D3D12.lib
22 DXGI.lib 38 DXGI.lib
23 DXGUID.lib) # For IID_Xyz symbols 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
index bfcdbe8..96cf3a5 100644
--- a/dxg/include/dxg/dxcommon.h
+++ b/dxg/include/dxg/dxcommon.h
@@ -5,16 +5,57 @@
5#include <directx/d3dx12.h> 5#include <directx/d3dx12.h>
6 6
7#include <assert.h> 7#include <assert.h>
8#include <stdbool.h>
8#include <stdio.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 DEBUG_PRINT OutputDebugStringA
47#else
48#define DEBUG_PRINT
49#endif
9 50
10#define TRAP(ERROR) { \ 51#define TRAP(ERROR) { \
11 fprintf(stderr, "Error in file:[%s] line:%d: %s", __FILE__, __LINE__, ERROR);\ 52 fprintf(stderr, "Error in file:[%s] line:%d: %s\n", __FILE__, __LINE__, ERROR);\
12 assert(false);\ 53 assert(false);\
13 __debugbreak();\ 54 __debugbreak();\
14} 55}
15 56
16#define TRAP_HRESULT(RESULT) { \ 57#define TRAP_HRESULT(RESULT) { \
17 fprintf(stderr, "HRESULT: %u", RESULT);\ 58 fprintf(stderr, "HRESULT: 0x%x - ", RESULT);\
18 TRAP("API call failed")\ 59 TRAP("API call failed")\
19} 60}
20 61
@@ -31,25 +72,52 @@
31 }\ 72 }\
32} 73}
33 74
75#define MIN(A, B) ((A) < (B) ? (A) : (B))
76
34#define CD3DX12_CPU_DESCRIPTOR_HANDLE(HANDLE, INDEX, SIZE) \ 77#define CD3DX12_CPU_DESCRIPTOR_HANDLE(HANDLE, INDEX, SIZE) \
35 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (INDEX * SIZE)} 78 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (INDEX * SIZE)}
36 79
37#define OFFSET_HANDLE(HANDLE, OFFSET, SIZE) \ 80#define OFFSET_HANDLE(HANDLE, OFFSET, SIZE) \
38 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (OFFSET * SIZE)} 81 (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (OFFSET * SIZE)}
39 82
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 { 83typedef enum SampleMask {
54 PointSampling = 0xffffffff 84 PointSampling = 0xffffffff
55} SampleMask; 85} SampleMask;
86
87D3D12_RESOURCE_BARRIER CD3DX12_RESOURCE_BARRIER_Transition(
88 ID3D12Resource* pResource,
89 D3D12_RESOURCE_STATES stateBefore,
90 D3D12_RESOURCE_STATES stateAfter);
91D3D12_RASTERIZER_DESC CD3DX12_RASTERIZER_DESC_DEFAULT();
92D3D12_BLEND_DESC CD3DX12_BLEND_DESC_DEFAULT();
93
94void dxg_wait(ID3D12Fence*, HANDLE fenceEvent, UINT64 fenceValue);
95
96// -----------------------------------------------------------------------------
97// Command Recorder
98// -----------------------------------------------------------------------------
99
100// Currently handling graphics command lists only.
101// Add compute when needed.
102typedef struct CommandRecorder {
103 ID3D12GraphicsCommandList* pCmdList;
104 ID3D12CommandAllocator* pCmdAllocator;
105} CommandRecorder;
106
107HRESULT dxg_cmdrec_init(CommandRecorder*, ID3D12Device*);
108void dxg_cmdrec_destroy(CommandRecorder*);
109HRESULT dxg_cmdrec_reset(CommandRecorder*);
110
111// -----------------------------------------------------------------------------
112// Upload Buffer
113// -----------------------------------------------------------------------------
114
115typedef struct UploadBuffer {
116 ID3D12Resource* pUploadBuffer;
117 size_t size;
118} UploadBuffer;
119
120void dxg_upload_buffer_init(UploadBuffer*, ID3D12Device*, size_t size);
121void dxg_upload_buffer_destroy(UploadBuffer*, ID3D12Device*);
122void dxg_upload_buffer_load(UploadBuffer*, const void* pData, size_t bytes, ID3D12Resource* pDstBuffer);
123bool 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..fdee725
--- /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 bufferSizeVerts);
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/dxg.c b/dxg/src/dxg.c
deleted file mode 100644
index e985d3d..0000000
--- a/dxg/src/dxg.c
+++ /dev/null
@@ -1 +0,0 @@
1int x = 2;
diff --git a/dxg/src/imm.c b/dxg/src/imm.c
new file mode 100644
index 0000000..28baa99
--- /dev/null
+++ b/dxg/src/imm.c
@@ -0,0 +1,368 @@
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#define WIN32_LEAN_AND_MEAN
22#include <Windows.h> // OutputDebugStringA
23
24#include <stdint.h>
25#include <stdlib.h>
26
27static ID3D12Resource* create_buffer(ID3D12Device* pDevice, size_t size) {
28 assert(pDevice);
29 const D3D12_HEAP_PROPERTIES props = {
30 .Type = D3D12_HEAP_TYPE_UPLOAD,
31 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
32 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
33 .CreationNodeMask = 0,
34 .VisibleNodeMask = 0
35 };
36 const D3D12_RESOURCE_DESC desc = {
37 .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
38 .Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
39 .Width = size,
40 .Height = 1,
41 .DepthOrArraySize = 1,
42 .MipLevels = 1,
43 .Format = DXGI_FORMAT_UNKNOWN,
44 .SampleDesc = {.Count = 1, .Quality = 0},
45 .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
46 .Flags = D3D12_RESOURCE_FLAG_NONE
47 };
48 ID3D12Resource* pBuffer = NULL;
49 TrapIfFailed(pDevice->lpVtbl->CreateCommittedResource(
50 pDevice,
51 &props,
52 D3D12_HEAP_FLAG_NONE,
53 &desc,
54 D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
55 NULL,
56 &IID_ID3D12Resource,
57 &pBuffer));
58 return pBuffer;
59}
60
61typedef struct GraphicsState {
62 D3D12_VIEWPORT viewport;
63 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView;
64 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView;
65} GraphicsState;
66
67// Set of per-draw resources. The renderer cycles between sets per draw.
68typedef struct ResourceSet {
69 ID3D12Resource* pVertexBuffer;
70 CommandRecorder cmdRec;
71} ResourceSet;
72
73typedef struct DxgImm {
74 ID3D12Device* pDevice;
75 ID3D12CommandQueue* pCmdQueue;
76 ID3D12PipelineState* pPipelineState;
77 ID3D12RootSignature* pRootSignature;
78 GraphicsState graphicsState;
79 ResourceSet resources[2];
80 int cur; // Index to current resource set. New geometry written here.
81 float* pCurrentBufferData; // Mapped region of current buffer.
82 size_t bufferSizeVerts; // Num verts per buffer.
83 ID3D12Fence* pFence;
84 HANDLE fenceEvent;
85 uint64_t fenceValue;
86 size_t vertsWritten; // Verts written to current buffer.
87 bool wait; // Whether the next draw should wait.
88} DxgImm;
89
90static inline size_t vertex_size_bytes() {
91 return 3 * sizeof(float);
92}
93
94static inline size_t verts_byte_count(size_t numVerts) {
95 return numVerts * vertex_size_bytes();
96}
97
98static inline size_t dxg_imm_verts_left(const DxgImm* imm) {
99 assert(imm);
100 assert(imm->bufferSizeVerts >= imm->vertsWritten);
101 return imm->bufferSizeVerts - imm->vertsWritten;
102}
103
104static void dxg_imm_copy_verts(DxgImm* imm, const float* pVerts, size_t count) {
105 assert(imm);
106 assert(pVerts);
107 assert(count <= dxg_imm_verts_left(imm));
108 memcpy(&imm->pCurrentBufferData[imm->vertsWritten], pVerts, verts_byte_count(count));
109 imm->vertsWritten += count;
110}
111
112// Set up the current resource set for drawing.
113static void dxg_imm_set_up_resource_set(DxgImm* imm) {
114 assert(imm);
115 ResourceSet* const pResources = &imm->resources[imm->cur];
116 TrapIfFailed(pResources->pVertexBuffer->lpVtbl->Map(
117 pResources->pVertexBuffer, 0, NULL, &imm->pCurrentBufferData));
118 dxg_cmdrec_reset(&pResources->cmdRec);
119}
120
121// Move on to the next resource set.
122static ID3D12Resource* dxg_imm_next_resource_set(DxgImm* imm) {
123 assert(imm);
124 ResourceSet* const pResources = &imm->resources[imm->cur];
125 // Unmap the current buffer.
126 // TODO: Do we actually need to do this or can we leave it mapped? If the
127 // latter, then we could just map both buffers and let them be.
128 pResources->pVertexBuffer->lpVtbl->Unmap(pResources->pVertexBuffer, 0, NULL);
129 // Move on to the next resource set.
130 imm->cur = (imm->cur + 1) & 1;
131 imm->vertsWritten = 0;
132 // Set up the new resource set.
133 dxg_imm_set_up_resource_set(imm);
134}
135
136// Wait for the current buffer to be available for writing.
137static void dxg_imm_wait(DxgImm* imm) {
138 assert(imm);
139 assert(imm->wait);
140 // We only need to wait upon the first round around both buffers.
141 // First Signal is on fence value 1, 0 is not actually Signaled.
142 if (imm->fenceValue > 2) { // TODO: Do we need this check?
143 // The last buffer (not current) was Signaled with fenceValue - 1.
144 // The current buffer was therefore Signaled two fence values ago, or
145 // fenceValue - 2.
146 dxg_wait(imm->pFence, imm->fenceEvent, imm->fenceValue - 2);
147 }
148 imm->wait = false;
149}
150
151// Draw the current buffer.
152static void dxg_imm_draw(DxgImm* imm) {
153 assert(imm);
154 ResourceSet* const pResourceSet = &imm->resources[imm->cur];
155 ID3D12Resource* const pCurrentBuffer = pResourceSet->pVertexBuffer;
156 ID3D12GraphicsCommandList* const pCmdList = pResourceSet->cmdRec.pCmdList;
157 const D3D12_VIEWPORT* const pViewport = &imm->graphicsState.viewport;
158 const D3D12_RECT scissor = {
159 .bottom = pViewport->Height,
160 .left = 0,
161 .right = pViewport->Width,
162 .top = 0,
163 };
164 const D3D12_VERTEX_BUFFER_VIEW vertexBufferView = {
165 .BufferLocation = pCurrentBuffer->lpVtbl->GetGPUVirtualAddress(pCurrentBuffer),
166 .SizeInBytes = verts_byte_count(imm->vertsWritten),
167 .StrideInBytes = vertex_size_bytes(),
168 };
169 pCmdList->lpVtbl->RSSetViewports(pCmdList, 1, pViewport);
170 pCmdList->lpVtbl->RSSetScissorRects(pCmdList, 1, &scissor);
171 pCmdList->lpVtbl->OMSetRenderTargets(
172 pCmdList, 1, &imm->graphicsState.hBackBufferView, false, &imm->graphicsState.hDepthStencilView);
173 pCmdList->lpVtbl->SetPipelineState(pCmdList, imm->pPipelineState);
174 pCmdList->lpVtbl->SetGraphicsRootSignature(pCmdList, imm->pRootSignature);
175 pCmdList->lpVtbl->IASetPrimitiveTopology(pCmdList, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
176 pCmdList->lpVtbl->IASetVertexBuffers(pCmdList, 0, 1, &vertexBufferView);
177 pCmdList->lpVtbl->DrawInstanced(pCmdList, imm->vertsWritten, 1, 0, 0);
178 pCmdList->lpVtbl->Close(pCmdList);
179 ID3D12CommandList* const cmdLists[] = {(ID3D12CommandList*)pCmdList};
180 ID3D12CommandQueue* const pCmdQueue = imm->pCmdQueue;
181 pCmdQueue->lpVtbl->ExecuteCommandLists(pCmdQueue, 1, cmdLists);
182}
183
184DxgImm* dxg_imm_init(ID3D12Device* pDevice, ID3D12CommandQueue* pCmdQueue, DXGI_FORMAT swapChainRtvFormat, DXGI_SAMPLE_DESC swapChainSampleDesc, size_t bufferSizeVerts) {
185 assert(pDevice);
186 assert(pCmdQueue);
187
188 DxgImm* imm = calloc(1, sizeof(DxgImm));
189 if (!imm) {
190 return 0;
191 }
192
193 imm->pDevice = pDevice;
194 imm->pCmdQueue = pCmdQueue;
195 imm->bufferSizeVerts = bufferSizeVerts;
196 imm->fenceValue = 0;
197
198 // TODO: Move this to the application side.
199 const D3D_SHADER_MODEL model = D3D_SHADER_MODEL_6_5;
200 D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { model };
201 HRESULT result = pDevice->lpVtbl->CheckFeatureSupport(
202 pDevice, D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
203 if (FAILED(result) || (shaderModel.HighestShaderModel < model)) {
204 DEBUG_PRINT("ERROR: Shader Model 6.5 is not supported!\n");
205 TrapIfFailed(result);
206 }
207
208 const D3D12_SHADER_BYTECODE vs_bytecode = {
209 .pShaderBytecode = imm_vs,
210 .BytecodeLength = sizeof(imm_vs)
211 };
212
213 const D3D12_SHADER_BYTECODE ps_bytecode = {
214 .pShaderBytecode = imm_ps,
215 .BytecodeLength = sizeof(imm_ps)
216 };
217
218 // TODO: Find out how many root parameters to use.
219 // Let's do bindless rendering to keep things flexible.
220 const D3D12_ROOT_SIGNATURE_DESC rootsig_desc = {
221 .NumParameters = 0,
222 .pParameters = NULL,
223 .NumStaticSamplers = 0,
224 .pStaticSamplers = NULL,
225 .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
226 };
227
228 ID3DBlob* pRootSignature = NULL;
229 ID3DBlob* pErrors = NULL;
230 result = D3D12SerializeRootSignature(
231 &rootsig_desc,
232 D3D_ROOT_SIGNATURE_VERSION_1,
233 &pRootSignature,
234 &pErrors);
235 if (FAILED(result)) {
236 if (pErrors) {
237 DEBUG_PRINT(pErrors->lpVtbl->GetBufferPointer(pErrors));
238 }
239 TrapIfFailed(result);
240 }
241
242 TrapIfFailed(imm->pDevice->lpVtbl->CreateRootSignature(
243 imm->pDevice,
244 0,
245 pRootSignature->lpVtbl->GetBufferPointer(pRootSignature),
246 pRootSignature->lpVtbl->GetBufferSize(pRootSignature),
247 &IID_ID3D12RootSignature,
248 &imm->pRootSignature));
249
250 const D3D12_INPUT_ELEMENT_DESC input_layout[] = {
251 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
252 };
253 const D3D12_INPUT_LAYOUT_DESC input_layout_desc = {
254 .pInputElementDescs = input_layout,
255 .NumElements = COUNTOF(input_layout)
256 };
257
258 const D3D12_GRAPHICS_PIPELINE_STATE_DESC gpso = {
259 .pRootSignature = imm->pRootSignature,
260 .VS = vs_bytecode,
261 .PS = ps_bytecode,
262 .BlendState = CD3DX12_BLEND_DESC_DEFAULT(),
263 .SampleMask = PointSampling,
264 .RasterizerState = CD3DX12_RASTERIZER_DESC_DEFAULT(),
265 .InputLayout = input_layout_desc,
266 .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
267 .NumRenderTargets = 1,
268 .RTVFormats = {swapChainRtvFormat},
269 .SampleDesc = swapChainSampleDesc
270 };
271 TrapIfFailed(imm->pDevice->lpVtbl->CreateGraphicsPipelineState(
272 imm->pDevice, &gpso, &IID_ID3D12PipelineState, &imm->pPipelineState));
273
274 const size_t bufferSize = verts_byte_count(bufferSizeVerts);
275 for (int i = 0; i < 2; ++i) {
276 imm->resources[i].pVertexBuffer = create_buffer(pDevice, bufferSize);
277 if (!imm->resources[i].pVertexBuffer) {
278 dxg_imm_destroy(&imm);
279 }
280 TrapIfFailed(dxg_cmdrec_init(&imm->resources[i].cmdRec, pDevice));
281 }
282 imm->cur = 0;
283 dxg_imm_set_up_resource_set(imm);
284
285 TrapIfFailed(pDevice->lpVtbl->CreateFence(
286 pDevice, imm->fenceValue, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, &imm->pFence));
287
288 if ((imm->fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
289 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
290 }
291
292 return imm;
293}
294
295void dxg_imm_destroy(DxgImm** ppImm) {
296 assert(ppImm);
297 DxgImm* imm = *ppImm;
298 if (imm) {
299 for (int i = 0; i < 2; ++i) {
300 SafeRelease(imm->resources[i].pVertexBuffer);
301 dxg_cmdrec_destroy(&imm->resources[i].cmdRec);
302 }
303 SafeRelease(imm->pRootSignature);
304 SafeRelease(imm->pPipelineState);
305 SafeRelease(imm->pFence);
306 if (imm->fenceEvent != NULL) {
307 CloseHandle(imm->fenceEvent);
308 }
309 free(imm);
310 *ppImm = 0;
311 }
312}
313
314void dxg_imm_set_graphics_state(
315 DxgImm* imm,
316 const D3D12_VIEWPORT* pViewport,
317 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView,
318 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView) {
319 assert(imm);
320 assert(pViewport);
321 assert(hBackBufferView.ptr);
322 assert(hDepthStencilView.ptr);
323 imm->graphicsState = (GraphicsState) {
324 .viewport = *pViewport,
325 .hBackBufferView = hBackBufferView,
326 .hDepthStencilView = hDepthStencilView,
327 };
328}
329
330void dxg_imm_flush(DxgImm* imm) {
331 assert(imm);
332 if (imm->vertsWritten > 0) {
333 dxg_imm_draw(imm);
334 // Signal the fence so that the current buffer can be reused once the
335 // draw has finished.
336 ID3D12CommandQueue* pCmdQueue = imm->pCmdQueue;
337 imm->fenceValue++;
338 pCmdQueue->lpVtbl->Signal(pCmdQueue, imm->pFence, imm->fenceValue);
339 // Next draw should Wait for the next buffer. Wait lazily on the next
340 // draw to avoid a stall here.
341 imm->wait = true;
342 dxg_imm_next_resource_set(imm);
343 }
344}
345
346void dxg_imm_draw_triangles(DxgImm* imm, const float* pVerts, size_t numTris) {
347 assert(imm);
348 assert(pVerts);
349 // TODO: This could be a loop to handle the case where the max buffer
350 // capacity cannot hold numTris. Or maybe we should rely on the caller
351 // to specify a big enough capacity, but that makes the API less
352 // friendly.
353 size_t triCapacity = dxg_imm_verts_left(imm) / 3;
354 if (triCapacity == 0) {
355 dxg_imm_flush(imm);
356 }
357 // If we just flushed the previous buffer, then we have to wait on the next
358 // one. The wait is done here, and not inside the branch above, because the
359 // client code can also flush the buffer.
360 if (imm->wait) {
361 dxg_imm_wait(imm);
362 }
363 // Re-evaluate capacity. It must be >0 now.
364 triCapacity = dxg_imm_verts_left(imm) / 3;
365 assert(triCapacity > 0);
366 const size_t numVerts = MIN(triCapacity, numTris) * 3;
367 dxg_imm_copy_verts(imm, pVerts, numVerts);
368}