diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-02 16:39:36 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-02 16:39:36 -0800 |
| commit | 6c8ae19be66cee247980a48e736a4e05d14de179 (patch) | |
| tree | d860767907bf0cbe17ec66422e11bea700cf56d9 /dxg/src | |
| parent | 8f594c8ebd11f0e5f8a0c6369c3fe7383d250cbe (diff) | |
Diffstat (limited to 'dxg/src')
| -rw-r--r-- | dxg/src/dxcommon.c | 173 | ||||
| -rw-r--r-- | dxg/src/dxg.c | 1 | ||||
| -rw-r--r-- | dxg/src/imm.c | 368 |
3 files changed, 541 insertions, 1 deletions
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; | ||
| 7 | D3D12_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 | |||
| 20 | D3D12_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 | |||
| 35 | D3D12_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 | |||
| 53 | void 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 | |||
| 74 | HRESULT 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 | |||
| 100 | void dxg_cmdrec_destroy(CommandRecorder* pRec) { | ||
| 101 | assert(pRec); | ||
| 102 | SafeRelease(pRec->pCmdList); | ||
| 103 | SafeRelease(pRec->pCmdAllocator); | ||
| 104 | } | ||
| 105 | |||
| 106 | HRESULT 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 | |||
| 124 | void 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 | |||
| 163 | void dxg_upload_buffer_destroy(UploadBuffer* pBuf, ID3D12Device* pDevice) { | ||
| 164 | assert(pDevice); | ||
| 165 | assert(pBuf); | ||
| 166 | SafeRelease(pBuf->pUploadBuffer); | ||
| 167 | } | ||
| 168 | |||
| 169 | void 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 @@ | |||
| 1 | int 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 | |||
| 3 | Geometry is given by client code and buffered in an upload-heap buffer stored | ||
| 4 | in host memory. | ||
| 5 | When the buffer fills up or the client is done, a draw call is issued. The draw | ||
| 6 | call reads directly from the buffer in host memory; there is no intermediate | ||
| 7 | buffer copy. | ||
| 8 | The renderer double-buffers two host-side buffers so that the client can | ||
| 9 | continue specifying more data into a second buffer while the contents of the | ||
| 10 | first buffer are rendered. | ||
| 11 | If the first buffer is still being rendered while the client loops around, then | ||
| 12 | the client must wait before issuing further geometry. | ||
| 13 | Once the render of the first buffer completes, the process starts again, | ||
| 14 | ping-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 | |||
| 27 | static 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 | |||
| 61 | typedef 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. | ||
| 68 | typedef struct ResourceSet { | ||
| 69 | ID3D12Resource* pVertexBuffer; | ||
| 70 | CommandRecorder cmdRec; | ||
| 71 | } ResourceSet; | ||
| 72 | |||
| 73 | typedef 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 | |||
| 90 | static inline size_t vertex_size_bytes() { | ||
| 91 | return 3 * sizeof(float); | ||
| 92 | } | ||
| 93 | |||
| 94 | static inline size_t verts_byte_count(size_t numVerts) { | ||
| 95 | return numVerts * vertex_size_bytes(); | ||
| 96 | } | ||
| 97 | |||
| 98 | static 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 | |||
| 104 | static 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. | ||
| 113 | static 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. | ||
| 122 | static 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. | ||
| 137 | static 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. | ||
| 152 | static 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 | |||
| 184 | DxgImm* 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 | |||
| 295 | void 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 | |||
| 314 | void 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 | |||
| 330 | void 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 | |||
| 346 | void 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 | } | ||
