├── .gitattributes ├── .gitignore ├── D3D12Helper.cpp ├── HelloMeshNodes.cpp ├── HelloMeshNodes.h ├── HelloMeshNodes.sln ├── HelloMeshNodes.vcxproj ├── HelloMeshNodes.vcxproj.filters ├── README.md ├── ShaderSource.h ├── figures ├── graph.png └── iterations.png ├── license.txt ├── main.cpp └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | d3d12/lib/x64/D3D12Core.dll filter=lfs diff=lfs merge=lfs -text 2 | d3d12/lib/x64/d3d12SDKLayers.dll filter=lfs diff=lfs merge=lfs -text 3 | d3d12/lib/x64/dxcompiler.dll filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | .vs/ 3 | x64/ 4 | !/d3d12/lib/x64/ 5 | packages/ 6 | -------------------------------------------------------------------------------- /D3D12Helper.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | ********************************************************************/ 22 | 23 | #include "HelloMeshNodes.h" 24 | 25 | #include 26 | #include 27 | 28 | #define ERROR_QUIT(value, ...) if(!(value)) { printf("ERROR: "); printf(__VA_ARGS__); printf("\nPress any key to terminate...\n"); _getch(); throw 0; } 29 | 30 | namespace { 31 | // function GetHardwareAdapter() copy-pasted from the publicly distributed sample provided at: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-d3d12createdevice 32 | void GetHardwareAdapter(IDXGIFactory4* pFactory, IDXGIAdapter1** ppAdapter) 33 | { 34 | *ppAdapter = nullptr; 35 | for (UINT adapterIndex = 0; ; ++adapterIndex) 36 | { 37 | IDXGIAdapter1* pAdapter = nullptr; 38 | if (DXGI_ERROR_NOT_FOUND == pFactory->EnumAdapters1(adapterIndex, &pAdapter)) 39 | { 40 | // No more adapters to enumerate. 41 | break; 42 | } 43 | 44 | // Check to see if the adapter supports Direct3D 12, but don't create the 45 | // actual device yet. 46 | if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) 47 | { 48 | *ppAdapter = pAdapter; 49 | return; 50 | } 51 | pAdapter->Release(); 52 | } 53 | } 54 | } 55 | 56 | HelloMeshNodes::~HelloMeshNodes() 57 | { 58 | if (device_) { 59 | WaitForPreviousFrame(); 60 | CloseHandle(fenceEvent_); 61 | } 62 | } 63 | 64 | void HelloMeshNodes::InitializeDirectX(HWND hwnd) 65 | { 66 | HRESULT hresult; 67 | 68 | device_ = nullptr; 69 | 70 | CComPtr factory; 71 | hresult = CreateDXGIFactory2(0, IID_PPV_ARGS(&factory)); 72 | ERROR_QUIT(hresult == S_OK, "Failed to create IDXGIFactory4."); 73 | 74 | CComPtr hardwareAdapter; 75 | GetHardwareAdapter(factory, &hardwareAdapter); 76 | hresult = D3D12CreateDevice(hardwareAdapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)); 77 | ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12Device."); 78 | 79 | // Create the command queue. 80 | D3D12_COMMAND_QUEUE_DESC queueDesc = {}; 81 | queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 82 | queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 83 | 84 | hresult = device_->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue_)); 85 | ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12CommandQueue."); 86 | 87 | // Create the swap chain. 88 | DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; 89 | swapChainDesc.BufferCount = FrameCount; 90 | swapChainDesc.Width = WindowSize; 91 | swapChainDesc.Height = WindowSize; 92 | swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 93 | swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 94 | swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 95 | swapChainDesc.SampleDesc.Count = 1; 96 | 97 | CComPtr swapChain; 98 | hresult = factory->CreateSwapChainForHwnd( 99 | commandQueue_, 100 | hwnd, 101 | &swapChainDesc, 102 | nullptr, 103 | nullptr, 104 | &swapChain 105 | ); 106 | ERROR_QUIT(hresult == S_OK, "Failed to create IDXGISwapChain1."); 107 | 108 | hresult = factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); 109 | ERROR_QUIT(hresult == S_OK, "Failed to make window association."); 110 | 111 | hresult = swapChain.QueryInterface(&swapChain_); 112 | ERROR_QUIT(hresult == S_OK, "Failed to query IDXGISwapChain3."); 113 | 114 | frameIndex_ = swapChain_->GetCurrentBackBufferIndex(); 115 | 116 | // Create render target view (RTV) descriptor heaps. 117 | { 118 | D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; 119 | rtvHeapDesc.NumDescriptors = FrameCount; 120 | rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; 121 | rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 122 | hresult = device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&renderViewDescriptorHeap_)); 123 | ERROR_QUIT(hresult == S_OK, "Failed to create RTV descriptor heap."); 124 | 125 | descriptorSize_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); 126 | } 127 | 128 | // Create frame resources. 129 | { 130 | CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderViewDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); 131 | 132 | // Create a RTV for each frame. 133 | for (UINT n = 0; n < FrameCount; n++) 134 | { 135 | hresult = swapChain_->GetBuffer(n, IID_PPV_ARGS(&renderTargets_[n])); 136 | ERROR_QUIT(hresult == S_OK, "Failed to access render target of swap chain."); 137 | device_->CreateRenderTargetView(renderTargets_[n].p, nullptr, rtvHandle); 138 | rtvHandle.Offset(1, descriptorSize_); 139 | } 140 | } 141 | 142 | // Create a depth-stencil view (DSV) descriptor heap and depth buffer 143 | { 144 | D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {}; 145 | dsvHeapDesc.NumDescriptors = 1; 146 | dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; 147 | dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 148 | hresult = device_->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&depthDescriptorHeap_)); 149 | ERROR_QUIT(hresult == S_OK, "Failed to create DSV descriptor heap."); 150 | 151 | depthDescriptorHeap_->SetName(L"Depth/Stencil Resource Heap"); 152 | 153 | D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {}; 154 | depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT; 155 | depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; 156 | depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE; 157 | 158 | D3D12_CLEAR_VALUE depthOptimizedClearValue = {}; 159 | depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT; 160 | depthOptimizedClearValue.DepthStencil.Depth = 1.0f; 161 | depthOptimizedClearValue.DepthStencil.Stencil = 0; 162 | 163 | CD3DX12_HEAP_PROPERTIES depthHeapProperties(D3D12_HEAP_TYPE_DEFAULT); 164 | CD3DX12_RESOURCE_DESC depthResourceDescription = CD3DX12_RESOURCE_DESC::Tex2D( 165 | DXGI_FORMAT_D32_FLOAT, 166 | WindowSize, WindowSize, 167 | 1, 0, 1, 0, 168 | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL); 169 | hresult = device_->CreateCommittedResource( 170 | &depthHeapProperties, 171 | D3D12_HEAP_FLAG_NONE, 172 | &depthResourceDescription, 173 | D3D12_RESOURCE_STATE_DEPTH_WRITE, 174 | &depthOptimizedClearValue, 175 | IID_PPV_ARGS(&depthBuffer_) 176 | ); 177 | ERROR_QUIT(hresult == S_OK, "Failed to create depth buffer."); 178 | 179 | device_->CreateDepthStencilView(depthBuffer_, &depthStencilDesc, depthDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); 180 | } 181 | 182 | hresult = device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator_)); 183 | ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12CommandAllocator."); 184 | 185 | // Create the command list. 186 | hresult = device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator_.p, nullptr, IID_PPV_ARGS(&commandList_)); 187 | ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12GraphicsCommandList."); 188 | 189 | // Command lists are created in the recording state, but there is nothing 190 | // to record yet. The main loop expects it to be closed, so close it now. 191 | hresult = commandList_->Close(); 192 | ERROR_QUIT(hresult == S_OK, "Failed to close ID3D12GraphicsCommandList."); 193 | 194 | // Create sync objects 195 | hresult = device_->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence_)); 196 | ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12Fence."); 197 | fenceValue_ = 1; 198 | 199 | // Create an event handle to use for frame synchronization. 200 | fenceEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); 201 | ERROR_QUIT(fenceEvent_ != nullptr, "Failed to create synchronization event."); 202 | 203 | // Create empty root signature 204 | { 205 | CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; 206 | rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); 207 | 208 | CComPtr signature; 209 | CComPtr error; 210 | hresult = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error); 211 | ERROR_QUIT(hresult == S_OK, "Failed to serialize RootSignature."); 212 | hresult = device_->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&globalRootSignature_)); 213 | ERROR_QUIT(hresult == S_OK, "Failed to create RootSignature."); 214 | } 215 | } 216 | 217 | void HelloMeshNodes::Render() 218 | { 219 | HRESULT hresult; 220 | // Reset allocator and list 221 | hresult = commandAllocator_->Reset(); 222 | ERROR_QUIT(hresult == S_OK, "Failed to reset ID3D12CommandAllocator."); 223 | 224 | hresult = commandList_->Reset(commandAllocator_.p, pipelineState_.p); 225 | ERROR_QUIT(hresult == S_OK, "Failed to reset ID3D12GraphicsCommandList."); 226 | 227 | RecordCommandList(); 228 | 229 | hresult = commandList_->Close(); 230 | ERROR_QUIT(hresult == S_OK, "Failed to close ID3D12CommandAllocator."); 231 | 232 | // Execute the command list. 233 | commandQueue_->ExecuteCommandLists(1, CommandListCast(&commandList_.p)); 234 | 235 | // Present the frame. 236 | hresult = swapChain_->Present(1, 0); 237 | ERROR_QUIT(hresult == S_OK, "Failed to present frame."); 238 | 239 | WaitForPreviousFrame(); 240 | } 241 | 242 | void HelloMeshNodes::WaitForPreviousFrame() 243 | { 244 | HRESULT hresult; 245 | // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. 246 | // This is code implemented as such for simplicity. The D3D12HelloFrameBuffering 247 | // sample from Microsoft illustrates how to use fences for efficient resource 248 | // usage and to maximize GPU utilization. 249 | 250 | // Signal and increment the fence value. 251 | const UINT64 fence = fenceValue_; 252 | hresult = commandQueue_->Signal(fence_.p, fence); 253 | ERROR_QUIT(hresult == S_OK, "Failed to signal fence."); 254 | fenceValue_++; 255 | 256 | // Wait until the previous frame is finished. 257 | if (fence_->GetCompletedValue() < fence) 258 | { 259 | hresult = fence_->SetEventOnCompletion(fence, fenceEvent_); 260 | ERROR_QUIT(hresult == S_OK, "Failed to set up fence event."); 261 | WaitForSingleObject(fenceEvent_, INFINITE); 262 | } 263 | 264 | frameIndex_ = swapChain_->GetCurrentBackBufferIndex(); 265 | } 266 | 267 | namespace d3d12 { 268 | HMODULE sDxCompilerDLL = nullptr; 269 | void LoadCompiler() 270 | { 271 | // load compiler 272 | sDxCompilerDLL = LoadLibrary(L"dxcompiler.dll"); 273 | 274 | ERROR_QUIT(sDxCompilerDLL, "Failed to initialize compiler."); 275 | } 276 | 277 | void ReleaseCompiler() 278 | { 279 | if (sDxCompilerDLL) 280 | { 281 | FreeLibrary(sDxCompilerDLL); 282 | sDxCompilerDLL = nullptr; 283 | } 284 | } 285 | 286 | ID3D12Resource* AllocateBuffer(CComPtr pDevice, UINT64 Size, D3D12_RESOURCE_FLAGS ResourceFlags, D3D12_HEAP_TYPE HeapType) 287 | { 288 | ID3D12Resource* pResource; 289 | 290 | CD3DX12_HEAP_PROPERTIES HeapProperties(HeapType); 291 | CD3DX12_RESOURCE_DESC ResourceDesc = CD3DX12_RESOURCE_DESC::Buffer(Size, ResourceFlags); 292 | HRESULT hr = pDevice->CreateCommittedResource(&HeapProperties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_PPV_ARGS(&pResource)); 293 | ERROR_QUIT(SUCCEEDED(hr), "Failed to allocate buffer."); 294 | 295 | return pResource; 296 | } 297 | 298 | void TransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter) 299 | { 300 | CD3DX12_RESOURCE_BARRIER transition = CD3DX12_RESOURCE_BARRIER::Transition(resource, stateBefore, stateAfter); 301 | commandList->ResourceBarrier(1, &transition); 302 | } 303 | } 304 | 305 | namespace window { 306 | HWND Initialize(HelloMeshNodes* ctx) 307 | { 308 | const HINSTANCE hInstance = GetModuleHandleA(NULL); 309 | 310 | WNDCLASSEX windowClass = { 0 }; 311 | windowClass.cbSize = sizeof(WNDCLASSEX); 312 | windowClass.style = CS_HREDRAW | CS_VREDRAW; 313 | windowClass.lpfnWndProc = Proc; 314 | windowClass.hInstance = hInstance; 315 | windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); 316 | windowClass.lpszClassName = L"Hello Mesh Nodes"; 317 | RegisterClassEx(&windowClass); 318 | 319 | RECT windowRect = { 0, 0, WindowSize, WindowSize }; 320 | DWORD style = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU; 321 | AdjustWindowRect(&windowRect, style, FALSE); 322 | 323 | HWND hwnd = CreateWindow(windowClass.lpszClassName, 324 | windowClass.lpszClassName, 325 | style, 326 | CW_USEDEFAULT, 327 | CW_USEDEFAULT, 328 | windowRect.right - windowRect.left, 329 | windowRect.bottom - windowRect.top, 330 | nullptr, // We have no parent window. 331 | nullptr, // We are not using menus. 332 | hInstance, 333 | static_cast(ctx)); 334 | 335 | return hwnd; 336 | } 337 | 338 | 339 | LRESULT CALLBACK Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { 340 | HelloMeshNodes* ctx = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); 341 | 342 | switch (message) 343 | { 344 | case WM_CREATE: 345 | { 346 | // Save the WindowContext* passed in to CreateWindow. 347 | LPCREATESTRUCT pCreateStruct = reinterpret_cast(lParam); 348 | SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(pCreateStruct->lpCreateParams)); 349 | return 0; 350 | } 351 | case WM_PAINT: 352 | ctx->Render(); 353 | return 0; 354 | case WM_KEYDOWN: { 355 | if (lParam == VK_ESCAPE) { 356 | PostQuitMessage(0); 357 | return 0; 358 | } 359 | } 360 | case WM_DESTROY: 361 | PostQuitMessage(0); 362 | return 0; 363 | } 364 | 365 | // Handle any messages the switch statement didn't. 366 | return DefWindowProc(hWnd, message, wParam, lParam); 367 | } 368 | 369 | 370 | void MessageLoop() 371 | { 372 | MSG msg = {}; 373 | while (msg.message != WM_QUIT) 374 | { 375 | // Process any messages in the queue. 376 | if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 377 | { 378 | TranslateMessage(&msg); 379 | DispatchMessage(&msg); 380 | } 381 | } 382 | } 383 | } -------------------------------------------------------------------------------- /HelloMeshNodes.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | ********************************************************************/ 22 | 23 | #include "HelloMeshNodes.h" 24 | #include "ShaderSource.h" 25 | 26 | #include 27 | 28 | #define ERROR_QUIT(value, ...) if(!(value)) { printf("ERROR: "); printf(__VA_ARGS__); printf("\nPress any key to terminate...\n"); _getch(); throw 0; } 29 | 30 | void HelloMeshNodes::Initialize(HWND hwnd) 31 | { 32 | EnableExperimentalFeatures(); 33 | 34 | InitializeDirectX(hwnd); 35 | 36 | CheckWorkGraphMeshNodeSupport(); 37 | 38 | // Compile shader libraries with meta data 39 | workGraphLibrary_ = d3d12::CompileShader(shader::workGraphSource, nullptr, L"lib_6_9"); 40 | // Compile pixel shader separately 41 | pixelShaderLibrary_ = d3d12::CompileShader(shader::workGraphSource, L"MeshNodePixelShader", L"ps_6_9"); 42 | 43 | stateObject_ = CreateGWGStateObject(); 44 | setProgramDesc_ = PrepareWorkGraph(stateObject_); 45 | } 46 | 47 | void HelloMeshNodes::EnableExperimentalFeatures() 48 | { 49 | // Mesh nodes require experimental state object features and shader model 6.9 which are not supported by default. 50 | UUID ExperimentalFeatures[2] = { D3D12ExperimentalShaderModels, D3D12StateObjectsExperiment }; 51 | HRESULT hr = D3D12EnableExperimentalFeatures(_countof(ExperimentalFeatures), ExperimentalFeatures, nullptr, nullptr); 52 | 53 | ERROR_QUIT((hr == S_OK), "Failed to enable experimental features."); 54 | } 55 | 56 | void HelloMeshNodes::CheckWorkGraphMeshNodeSupport() 57 | { 58 | D3D12_FEATURE_DATA_D3D12_OPTIONS21 Options = {}; 59 | 60 | HRESULT hr = device_->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &Options, sizeof(Options)); 61 | ERROR_QUIT(hr == S_OK, "Failed to check support for work graphs and mesh nodes."); 62 | 63 | // Mesh nodes are supported in D3D12_WORK_GRAPHS_TIER_1_1 64 | ERROR_QUIT(Options.WorkGraphsTier >= D3D12_WORK_GRAPHS_TIER_1_1, 65 | "Failed to find device with D3D12 Work Graphs 1.1 support. Please check if you have a compatible driver and graphics card installed."); 66 | } 67 | 68 | ID3D12StateObject* HelloMeshNodes::CreateGWGStateObject() 69 | { 70 | ID3D12StateObject* stateObject = nullptr; 71 | CD3DX12_STATE_OBJECT_DESC stateObjectDesc(D3D12_STATE_OBJECT_TYPE_EXECUTABLE); 72 | 73 | // Configure graphics state for global root signature 74 | auto configSubobject = stateObjectDesc.CreateSubobject(); 75 | configSubobject->SetFlags( 76 | D3D12_STATE_OBJECT_FLAG_WORK_GRAPHS_USE_GRAPHICS_STATE_FOR_GLOBAL_ROOT_SIGNATURE); 77 | 78 | CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT* globalRootSignatureSubobject = stateObjectDesc.CreateSubobject(); 79 | globalRootSignatureSubobject->SetRootSignature(globalRootSignature_); 80 | 81 | CD3DX12_WORK_GRAPH_SUBOBJECT* workGraphDesc = stateObjectDesc.CreateSubobject(); 82 | workGraphDesc->IncludeAllAvailableNodes(); 83 | workGraphDesc->SetProgramName(kProgramName); 84 | 85 | // Work Graph Nodes 86 | { 87 | // Here we add the DXIL library compiled with "lib_6_9" target to the state object desc. 88 | // With mesh nodes, this library will also contain the mesh shaders with the [NodeLaunch("mesh")] attribute. 89 | CD3DX12_DXIL_LIBRARY_SUBOBJECT* libraryDesc = stateObjectDesc.CreateSubobject(); 90 | CD3DX12_SHADER_BYTECODE libraryCode(workGraphLibrary_); 91 | libraryDesc->SetDXILLibrary(&libraryCode); 92 | } 93 | 94 | // Next we need to add the separately compiled pixel shader to the state object desc. 95 | // The pixel shader itself will be compiled with target "ps_6_9" and is added to the state object as a DXIL library. 96 | { 97 | CD3DX12_DXIL_LIBRARY_SUBOBJECT* libraryDesc = stateObjectDesc.CreateSubobject(); 98 | CD3DX12_SHADER_BYTECODE libraryCode(pixelShaderLibrary_); 99 | libraryDesc->SetDXILLibrary(&libraryCode); 100 | } 101 | 102 | // In the following section we add subobject for various graphics states to the state object description. 103 | // These subobjects form "building blocks" and allow us to then create different mesh nodes with them. 104 | 105 | // Subobject to define rasterizer state for generic programs 106 | auto rasterizerSubobject = stateObjectDesc.CreateSubobject(); 107 | rasterizerSubobject->SetFrontCounterClockwise(true); 108 | rasterizerSubobject->SetFillMode(D3D12_FILL_MODE_SOLID); 109 | rasterizerSubobject->SetCullMode(D3D12_CULL_MODE_NONE); 110 | 111 | // Subobject to define depth-stencil state for generic programs 112 | auto depthStencilSubobject = stateObjectDesc.CreateSubobject(); 113 | depthStencilSubobject->SetDepthEnable(true); 114 | 115 | // Subobject to define depth-stencil format for generic programs 116 | auto depthStencilFormatSubobject = stateObjectDesc.CreateSubobject(); 117 | depthStencilFormatSubobject->SetDepthStencilFormat(depthBuffer_->GetDesc().Format); 118 | 119 | // Subobject to define render target formats for generic programs 120 | auto renderTargetFormatSubobject = stateObjectDesc.CreateSubobject(); 121 | renderTargetFormatSubobject->SetNumRenderTargets(1); 122 | renderTargetFormatSubobject->SetRenderTargetFormat(0, renderTargets_[0]->GetDesc().Format); 123 | 124 | // Next we'll create two generic program subobject for our two mesh nodes. 125 | 126 | // LineMeshNode 127 | { 128 | // The line mesh shader defines the [NodeId(...)] attribute, and thus a generic program that references it 129 | // will be automatically turned into a work graph mesh node. 130 | 131 | auto lineProgramSubobject = stateObjectDesc.CreateSubobject(); 132 | 133 | // Add mesh shader to the generic program. 134 | // The exportName is the name of our mesh shader function in the shader library. 135 | lineProgramSubobject->AddExport(L"LineMeshShader"); 136 | // Add the pixel shader to the generic program. 137 | // The exportName is the entry point name of our pixel shader. 138 | lineProgramSubobject->AddExport(L"MeshNodePixelShader"); 139 | 140 | // Add "building blocks" to define the graphics PSO state for our mesh node 141 | lineProgramSubobject->AddSubobject(*rasterizerSubobject); 142 | lineProgramSubobject->AddSubobject(*depthStencilSubobject); 143 | lineProgramSubobject->AddSubobject(*depthStencilFormatSubobject); 144 | lineProgramSubobject->AddSubobject(*renderTargetFormatSubobject); 145 | } 146 | 147 | // TriangleMeshNode 148 | { 149 | // The triangle mesh shader does not define a [NodeId(...)] attribute, 150 | // thus the generic program that we create with it would take the name "TriangleMeshShader". 151 | // Here we'll rename it to "TriangleMeshNode", which is how other nodes in the graph reference it. 152 | 153 | auto triangleProgramSubobject = stateObjectDesc.CreateSubobject(); 154 | 155 | // To later rename the mesh node created with this generic program, we first need to give it a unique name. 156 | const auto genericProgramName = L"TriangleMeshNodeGenericProgram"; 157 | triangleProgramSubobject->SetProgramName(genericProgramName); 158 | 159 | // Mesh and pixel shader are added in the same way as with the line mesh shader above 160 | triangleProgramSubobject->AddExport(L"TriangleMeshShader"); 161 | triangleProgramSubobject->AddExport(L"MeshNodePixelShader"); 162 | 163 | // Same with the "building blocks" for the graphics PSO state 164 | triangleProgramSubobject->AddSubobject(*rasterizerSubobject); 165 | triangleProgramSubobject->AddSubobject(*depthStencilSubobject); 166 | triangleProgramSubobject->AddSubobject(*depthStencilFormatSubobject); 167 | triangleProgramSubobject->AddSubobject(*renderTargetFormatSubobject); 168 | 169 | // Next, we need to rename the created mesh node to "TriangleMeshNode". 170 | // To do this, we need to create a mesh launch override with the same name as our generic program. 171 | auto triangleNodeOverride = workGraphDesc->CreateMeshLaunchNodeOverrides(genericProgramName); 172 | // Here we set the name and array index of our mesh node. 173 | // This name will be used by the rest of the work graph to send records to our mesh node. 174 | // This override will also remove the implicitly created "TriangleMeshShader" mesh node. 175 | triangleNodeOverride->NewName({ L"TriangleMeshNode", 0 }); 176 | // Here we could also override other attributes, such as the node dispatch grid, 177 | // but in our case, those attributes are already set in the HLSL source code. 178 | } 179 | 180 | HRESULT hr = device_->CreateStateObject(stateObjectDesc, IID_PPV_ARGS(&stateObject)); 181 | ERROR_QUIT((hr == S_OK) && stateObject, "Failed to create Work Graph State Object."); 182 | 183 | return stateObject; 184 | } 185 | 186 | D3D12_SET_PROGRAM_DESC HelloMeshNodes::PrepareWorkGraph(CComPtr stateObject) 187 | { 188 | HRESULT hr; 189 | 190 | CComPtr backingMemoryResource = nullptr; 191 | 192 | CComPtr stateObjectProperties; 193 | CComPtr workGraphProperties; 194 | 195 | hr = stateObject->QueryInterface(IID_PPV_ARGS(&stateObjectProperties)); 196 | ERROR_QUIT(SUCCEEDED(hr), "Failed to query ID3D12StateObjectProperties1."); 197 | hr = stateObject->QueryInterface(IID_PPV_ARGS(&workGraphProperties)); 198 | ERROR_QUIT(SUCCEEDED(hr), "Failed to query ID3D12WorkGraphProperties1."); 199 | 200 | // Set the input record limit. This is required for work graphs with mesh nodes. 201 | // In this case we'll only have a single input record 202 | const auto workGraphIndex = workGraphProperties->GetWorkGraphIndex(kProgramName); 203 | workGraphProperties->SetMaximumInputRecords(workGraphIndex, 1, 1); 204 | 205 | D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS memoryRequirements = {}; 206 | workGraphProperties->GetWorkGraphMemoryRequirements(workGraphIndex, &memoryRequirements); 207 | if (memoryRequirements.MaxSizeInBytes > 0) 208 | { 209 | backingMemoryResource = d3d12::AllocateBuffer(device_, memoryRequirements.MaxSizeInBytes, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_HEAP_TYPE_DEFAULT); 210 | } 211 | 212 | D3D12_SET_PROGRAM_DESC setProgramDesc = {}; 213 | setProgramDesc.Type = D3D12_PROGRAM_TYPE_WORK_GRAPH; 214 | setProgramDesc.WorkGraph.ProgramIdentifier = stateObjectProperties->GetProgramIdentifier(kProgramName); 215 | setProgramDesc.WorkGraph.Flags = D3D12_SET_WORK_GRAPH_FLAG_INITIALIZE; 216 | if (backingMemoryResource) 217 | { 218 | setProgramDesc.WorkGraph.BackingMemory = { backingMemoryResource->GetGPUVirtualAddress(), memoryRequirements.MaxSizeInBytes }; 219 | } 220 | 221 | return setProgramDesc; 222 | } 223 | 224 | void HelloMeshNodes::RecordCommandList() 225 | { 226 | ID3D12Resource* backbuffer = renderTargets_[frameIndex_].p; 227 | d3d12::TransitionBarrier(commandList_, backbuffer, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); 228 | 229 | // Setup viewport & scissor 230 | CD3DX12_VIEWPORT viewport(0.f, 0.f, WindowSize, WindowSize); 231 | CD3DX12_RECT scissorRect(0, 0, WindowSize, WindowSize); 232 | commandList_->RSSetViewports(1, &viewport); 233 | commandList_->RSSetScissorRects(1, &scissorRect); 234 | 235 | // Render view and depth handle 236 | CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderViewDescriptorHeap_->GetCPUDescriptorHandleForHeapStart(), frameIndex_, descriptorSize_); 237 | CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(depthDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); 238 | 239 | // Clear render target View. 240 | const float clearColor[] = { 1, 1, 1, 1 }; 241 | commandList_->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); 242 | 243 | // Clear depth buffer 244 | commandList_->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); 245 | 246 | // Set depth & color render targets 247 | commandList_->OMSetRenderTargets(1, &rtvHandle, false, &dsvHandle); 248 | 249 | // Dispatch work graph 250 | D3D12_DISPATCH_GRAPH_DESC dispatchGraphDesc = {}; 251 | dispatchGraphDesc.Mode = D3D12_DISPATCH_MODE_NODE_CPU_INPUT; 252 | dispatchGraphDesc.NodeCPUInput = { }; 253 | dispatchGraphDesc.NodeCPUInput.EntrypointIndex = 0; 254 | // Launch graph with one record 255 | dispatchGraphDesc.NodeCPUInput.NumRecords = 1; 256 | // Record does not contain any data 257 | dispatchGraphDesc.NodeCPUInput.RecordStrideInBytes = 0; 258 | dispatchGraphDesc.NodeCPUInput.pRecords = nullptr; 259 | 260 | commandList_->SetGraphicsRootSignature(globalRootSignature_); 261 | commandList_->SetProgram(&setProgramDesc_); 262 | commandList_->DispatchGraph(&dispatchGraphDesc); 263 | 264 | d3d12::TransitionBarrier(commandList_, backbuffer, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); 265 | 266 | // Only initialize in the first frame. Set flag from Init to None for all other frames. 267 | setProgramDesc_.WorkGraph.Flags = D3D12_SET_WORK_GRAPH_FLAG_NONE; 268 | } 269 | 270 | namespace d3d12 { 271 | ID3DBlob* CompileShader(const std::string& shaderCode, const wchar_t* entryPoint, const wchar_t* targetProfile) 272 | { 273 | ID3DBlob* resultBlob = nullptr; 274 | if (d3d12::sDxCompilerDLL) 275 | { 276 | DxcCreateInstanceProc pDxcCreateInstance; 277 | pDxcCreateInstance = (DxcCreateInstanceProc)GetProcAddress(d3d12::sDxCompilerDLL, "DxcCreateInstance"); 278 | 279 | if (pDxcCreateInstance) 280 | { 281 | CComPtr pUtils; 282 | CComPtr pCompiler; 283 | CComPtr pSource; 284 | CComPtr pOperationResult; 285 | 286 | if (SUCCEEDED(pDxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&pUtils))) && SUCCEEDED(pDxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&pCompiler)))) 287 | { 288 | if (SUCCEEDED(pUtils->CreateBlob(shaderCode.c_str(), static_cast(shaderCode.length()), 0, &pSource))) 289 | { 290 | if (SUCCEEDED(pCompiler->Compile(pSource, nullptr, entryPoint, targetProfile, nullptr, 0, nullptr, 0, nullptr, &pOperationResult))) 291 | { 292 | HRESULT hr; 293 | pOperationResult->GetStatus(&hr); 294 | if (SUCCEEDED(hr)) 295 | { 296 | pOperationResult->GetResult((IDxcBlob**)&resultBlob); 297 | } 298 | } 299 | } 300 | } 301 | } 302 | } 303 | 304 | ERROR_QUIT(resultBlob, "Failed to compile GWG Library."); 305 | return resultBlob; 306 | } 307 | } -------------------------------------------------------------------------------- /HelloMeshNodes.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | ********************************************************************/ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | constexpr UINT WindowSize = 720; 35 | 36 | static const wchar_t* kProgramName = L"Hello Mesh Nodes"; 37 | 38 | class HelloMeshNodes 39 | { 40 | public: 41 | HelloMeshNodes() = default; 42 | ~HelloMeshNodes(); 43 | // Initialize D3D12 and Work graphs objects 44 | void Initialize(HWND hwnd); 45 | // Record command list, execute the list and present the finished frame 46 | void Render(); 47 | 48 | private: 49 | static constexpr UINT FrameCount = 2; 50 | 51 | // Pipeline objects 52 | CComPtr swapChain_; 53 | CComPtr device_; 54 | CComPtr renderTargets_[FrameCount]; 55 | CComPtr pipelineState_; 56 | CComPtr depthBuffer_; 57 | 58 | UINT descriptorSize_; 59 | CComPtr renderViewDescriptorHeap_; 60 | CComPtr depthDescriptorHeap_; 61 | 62 | CComPtr commandAllocator_; 63 | CComPtr commandQueue_; 64 | CComPtr commandList_; 65 | 66 | // Work graphs objects 67 | CComPtr globalRootSignature_; 68 | CComPtr workGraphLibrary_; 69 | CComPtr pixelShaderLibrary_; 70 | 71 | CComPtr stateObject_; 72 | D3D12_SET_PROGRAM_DESC setProgramDesc_; 73 | 74 | ID3D12Resource* frameBuffer_; 75 | 76 | // Synchronization objects. 77 | UINT frameIndex_; 78 | HANDLE fenceEvent_; 79 | CComPtr fence_; 80 | UINT64 fenceValue_; 81 | 82 | // Initializes common DirectX objects 83 | // - D3D12Device 84 | // - D3D12CommandQueue 85 | // - DXGISwapChain 86 | // - Render View Descriptor Heap 87 | // - Render Targets 88 | // - Depth Descriptor Heap 89 | // - Depth Buffer 90 | // - D3D12CommandAllocator 91 | // - D3D12GraphicsCommandList 92 | // - D3D12RootSignature 93 | void InitializeDirectX(HWND hwnd); 94 | 95 | // Enables experimental D3D12 features for mesh nodes 96 | void EnableExperimentalFeatures(); 97 | 98 | // Checks if work graphs and mesh nodes are supported on the current device 99 | void CheckWorkGraphMeshNodeSupport(); 100 | 101 | // Creates work graphs state object 102 | ID3D12StateObject* CreateGWGStateObject(); 103 | // Prepares work graph state object description for execution 104 | D3D12_SET_PROGRAM_DESC PrepareWorkGraph(CComPtr pStateObject); 105 | 106 | // Records command list: 107 | // - clear render target 108 | // - clear depth buffer 109 | // - dispatch work graph 110 | void RecordCommandList(); 111 | 112 | // wait for previous frame to finish 113 | void WaitForPreviousFrame(); 114 | }; 115 | 116 | namespace d3d12 { 117 | extern HMODULE sDxCompilerDLL; 118 | void LoadCompiler(); 119 | // Compiles work graphs library with required meta data 120 | ID3DBlob* CompileShader(const std::string& shaderCode, const wchar_t* entryPoint, const wchar_t* targetProfil); 121 | void ReleaseCompiler(); 122 | 123 | ID3D12Resource* AllocateBuffer(CComPtr pDevice, UINT64 Size, D3D12_RESOURCE_FLAGS ResourceFlags, D3D12_HEAP_TYPE HeapType); 124 | 125 | void TransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource, 126 | D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter); 127 | } 128 | 129 | namespace window { 130 | HWND Initialize(HelloMeshNodes* ctx); 131 | LRESULT CALLBACK Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 132 | 133 | void MessageLoop(); 134 | } 135 | -------------------------------------------------------------------------------- /HelloMeshNodes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.33801.447 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HelloMeshNodes", "HelloMeshNodes.vcxproj", "{673AC41E-F813-4C5C-B8B5-71FF540C7672}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Debug|x64.ActiveCfg = Debug|x64 15 | {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Debug|x64.Build.0 = Debug|x64 16 | {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Release|x64.ActiveCfg = Release|x64 17 | {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0CF5096D-C8A2-4E21-BA93-3191154C4B0E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /HelloMeshNodes.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | x64 9 | 10 | 11 | Release 12 | x64 13 | 14 | 15 | 16 | 16.0 17 | Win32Proj 18 | {673ac41e-f813-4c5c-b8b5-71ff540c7672} 19 | HelloMeshNodes 20 | 10.0 21 | 22 | 23 | 24 | Application 25 | true 26 | v143 27 | Unicode 28 | 29 | 30 | Application 31 | false 32 | v143 33 | true 34 | Unicode 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | true 50 | $(ProjectDir)d3d12\inc;$(IncludePath) 51 | 52 | 53 | false 54 | $(ProjectDir)d3d12\inc;$(IncludePath) 55 | 56 | 57 | 58 | Level3 59 | true 60 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 61 | true 62 | 6031 63 | 64 | 65 | Console 66 | true 67 | d3d12.lib;dxgi.lib;%(AdditionalDependencies) 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | 78 | 79 | Level3 80 | true 81 | true 82 | true 83 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 84 | true 85 | 6031 86 | 87 | 88 | Console 89 | true 90 | true 91 | true 92 | d3d12.lib;dxgi.lib;%(AdditionalDependencies) 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /HelloMeshNodes.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello Mesh Nodes 2 | 3 | HelloMeshNodes is a minimal, "Hello World"-style sample for [D3D12 Mesh Nodes](https://microsoft.github.io/DirectX-Specs/d3d/WorkGraphs.html#mesh-nodes). The goal of this sample is to provide short sample files which set up the minimum required in order to exercise a recursive work graph with draw nodes rendering a [Koch snowflake](https://en.wikipedia.org/wiki/Koch_snowflake). 4 | 5 | To run the sample, open the `HelloMeshNodes.sln` with [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). 6 | Build and run the `HelloMeshNodes` project. Visual Studio will automatically download the required Agility SDK and DirectX shader compiler packages from the NuGet package repository. 7 | 8 | You can find more information in mesh nodes and this sample in our accompanying blog post on [GPUOpen](https://gpuopen.com/learn/work_graphs_mesh_nodes). 9 | 10 | This sample focuses on the new mesh node feature of GPU work graphs in DirectX 12. If you are not already comfortable with DirectX 12, work graphs or mesh shaders, you may wish to learn more by reading up on these topics before continuing: 11 | - [DirectX 12 Ultimate Getting Started Guide](https://devblogs.microsoft.com/directx/directx-12-ultimate-getting-started-guide/) 12 | - [GPU Work Graphs Introduction](https://gpuopen.com/learn/gpu-work-graphs/gpu-work-graphs-intro/) 13 | - [Mesh shaders on AMD RDNA™ graphics cards](https://gpuopen.com/learn/mesh_shaders/mesh_shaders-index/) 14 | 15 | ## Koch Snowflake 16 | ![koch snowflake](./figures/iterations.png) 17 | 18 | The Koch snowflake is built up iteratively, in a sequence of stages. The first stage is an equilateral triangle, and each successive stage is formed by adding outward bends to each side of the previous stage, making smaller equilateral triangles. 19 | 20 | The work graph calculates and renders the first three iterations of the Koch snowflake. 21 | 22 | ## Work Graph Setup 23 | 24 | ![work graph](./figures/graph.png) 25 | 26 | The work graph draws the Koch snowflake. 27 | The EntryNode starts by drawing the center triangle and starting the snowflake node with the three lines of the initial triangle. 28 | The SnowflakeNode draws a triangle in each iteration, but the last. 29 | In the last iteration the outline is drawn. We use a depth buffer to ensure the outline always appears up top. -------------------------------------------------------------------------------- /ShaderSource.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | ********************************************************************/ 22 | 23 | #pragma once 24 | 25 | // This file contains the entire work graph HLSL source code as a single resource string 26 | // The shader code will be compiled twice: 27 | // - with target lib_6_9 for all work graph nodes, including the two mesh nodes for drawing 28 | // - with target ps_6_9 for the pixel shader. Pixel shader cannot be included in the library object and need to be compiled separately. 29 | 30 | namespace shader { 31 | static const char* workGraphSource = R"( 32 | // ========================= 33 | // Work graph record structs 34 | 35 | // Record used for recursively generating & drawing lines 36 | struct LineRecord 37 | { 38 | float2 start; 39 | float2 end; 40 | }; 41 | 42 | // Record used to draw a single triangle 43 | struct TriangleDrawRecord 44 | { 45 | float2 verts[3]; 46 | uint depth; 47 | }; 48 | 49 | // Number of Koch iterations 50 | static const uint maxSnowflakeRecursions = 3; 51 | 52 | // This node creates the triangle base for the Koch snowflake. 53 | [Shader("node")] 54 | [NodeIsProgramEntry] 55 | [NodeLaunch("thread")] 56 | void EntryNode( 57 | // Start recursive Koch fractal on each of the three sides of the triangle 58 | [MaxRecords(3)]NodeOutput SnowflakeNode, 59 | // Fill triangle 60 | [MaxRecords(1)]NodeOutput TriangleMeshNode) 61 | { 62 | ThreadNodeOutputRecords snowflakeRecords = SnowflakeNode.GetThreadNodeOutputRecords(3); 63 | ThreadNodeOutputRecords drawRecords = TriangleMeshNode.GetThreadNodeOutputRecords(1); 64 | 65 | const float2 v0 = float2(0., .9); 66 | const float2 v1 = float2(+sqrt(3) * .45, -.45); 67 | const float2 v2 = float2(-sqrt(3) * .45, -.45); 68 | 69 | // Line v0 -> v1 70 | snowflakeRecords.Get(0).start = v0; 71 | snowflakeRecords.Get(0).end = v1; 72 | 73 | // Line v1 -> v2 74 | snowflakeRecords.Get(1).start = v1; 75 | snowflakeRecords.Get(1).end = v2; 76 | 77 | // Line v2 -> v0 78 | snowflakeRecords.Get(2).start = v2; 79 | snowflakeRecords.Get(2).end = v0; 80 | 81 | // Triangle record 82 | drawRecords.Get(0).depth = 0; 83 | drawRecords.Get(0).verts[0] = v0; 84 | drawRecords.Get(0).verts[1] = v1; 85 | drawRecords.Get(0).verts[2] = v2; 86 | 87 | snowflakeRecords.OutputComplete(); 88 | drawRecords.OutputComplete(); 89 | }; 90 | 91 | [Shader("node")] 92 | [NodeLaunch("thread")] 93 | [NodeMaxRecursionDepth(maxSnowflakeRecursions)] 94 | void SnowflakeNode( 95 | ThreadNodeInputRecord record, 96 | // Koch fractal recursively splits line into 4 new line segments 97 | [MaxRecords(4)]NodeOutput SnowflakeNode, 98 | // Two of the recursive lines form edges of a triangles, which needs to be filled 99 | [MaxRecords(1)]NodeOutput TriangleMeshNode, 100 | // If recursion is not possible, draw a single line 101 | [MaxRecords(1)]NodeOutput LineMeshNode 102 | ) { 103 | const float2 start = record.Get().start; 104 | const float2 end = record.Get().end; 105 | 106 | const bool hasOutput = GetRemainingRecursionLevels() != 0; 107 | 108 | ThreadNodeOutputRecords snowflakeRecords = SnowflakeNode.GetThreadNodeOutputRecords(hasOutput * 4); 109 | ThreadNodeOutputRecords triRecord = TriangleMeshNode.GetThreadNodeOutputRecords(hasOutput); 110 | ThreadNodeOutputRecords lineRecord = LineMeshNode.GetThreadNodeOutputRecords(!hasOutput); 111 | 112 | if (hasOutput) { 113 | const float2 perpendicular = float2(start.y - end.y, end.x - start.x) * sqrt(3) / 6; 114 | 115 | const float2 triangleLeft = lerp(start, end, 1./3.); 116 | const float2 triangleMid = lerp(start, end, .5) + perpendicular; 117 | const float2 triangleRight = lerp(start, end, 2./3.); 118 | 119 | snowflakeRecords.Get(0).start = start; 120 | snowflakeRecords.Get(0).end = triangleLeft; 121 | snowflakeRecords.Get(1).start = triangleLeft; 122 | snowflakeRecords.Get(1).end = triangleMid; 123 | snowflakeRecords.Get(2).start = triangleMid; 124 | snowflakeRecords.Get(2).end = triangleRight; 125 | snowflakeRecords.Get(3).start = triangleRight; 126 | snowflakeRecords.Get(3).end = end; 127 | 128 | triRecord.Get(0).depth = 1 + (maxSnowflakeRecursions - GetRemainingRecursionLevels()); 129 | triRecord.Get(0).verts[0] = triangleLeft; 130 | triRecord.Get(0).verts[1] = triangleMid; 131 | triRecord.Get(0).verts[2] = triangleRight; 132 | } else { 133 | lineRecord.Get(0).start = start; 134 | lineRecord.Get(0).end = end; 135 | } 136 | 137 | snowflakeRecords.OutputComplete(); 138 | lineRecord.OutputComplete(); 139 | triRecord.OutputComplete(); 140 | } 141 | 142 | // ======================================================= 143 | // Vertex and primitive attribute structs for mesh shaders 144 | struct Vertex 145 | { 146 | float4 position : SV_POSITION; 147 | }; 148 | 149 | struct Primitive { 150 | float4 color : COLOR0; 151 | }; 152 | 153 | // ========== 154 | // Mesh Nodes 155 | 156 | // Mesh shader to draw a line between a start and end position. 157 | // As lines X degree angles, we cannot draw lines a simple 2D boxes. 158 | // 159 | // 160 | // v2----------v3 161 | // / \ 162 | // v1 v4 163 | // \ / 164 | // v0-----------v5 165 | // 166 | // Triangulation: 167 | // - v0 -> v1 -> v2 168 | // - v0 -> v2 -> v3 169 | // - v0 -> v3 -> v4 170 | // - v0 -> v4 -> v5 171 | [Shader("node")] 172 | // Indicate that we are defining a mesh node 173 | [NodeLaunch("mesh")] 174 | // Mesh nodes do not automatically use the function name of the node as their node id. 175 | // If we want to automatically add the generic program created with this mesh node to the work graph, 176 | // we need to explicitly define a node id for it. 177 | [NodeId("LineMeshNode", 0)] 178 | // Mesh nodes can use [NodeDispatchGrid(...)] and [NodeMaxDispatchGrid(...)] in combination with SV_DispatchGrid. 179 | [NodeDispatchGrid(1, 1, 1)] 180 | // The rest of the attributes are the same as for "normal" mesh shaders. 181 | [NumThreads(32, 1, 1)] 182 | [OutputTopology("triangle")] 183 | void LineMeshShader( 184 | uint gtid : SV_GroupThreadID, 185 | DispatchNodeInputRecord inputRecord, 186 | out indices uint3 triangles[4], 187 | out primitives Primitive prims[4], 188 | out vertices Vertex verts[6]) 189 | { 190 | const LineRecord record = inputRecord.Get(); 191 | SetMeshOutputCounts(6, 4); 192 | 193 | // Output triangles based on triangulation above 194 | if (gtid < 4) 195 | { 196 | triangles[gtid] = uint3(0, gtid + 1, gtid + 2); 197 | prims[gtid].color = float4(0.03, 0.19, 0.42, 1.0); 198 | } 199 | 200 | // Output vertices 201 | if (gtid < 6) { 202 | const float2 direction = normalize(record.end - record.start); 203 | const float2 perpendicular = float2(direction.y, -direction.x); 204 | 205 | const float lineWidth = 0.0075; 206 | 207 | // Offsets for outer triangle shape 208 | // 209 | // offsets[2] ---- ... 210 | // / 211 | // offsets[1] 212 | // \ 213 | // offsets[0] ---- ... 214 | // 215 | // direction <---+ 216 | // | 217 | // v 218 | // prependicular 219 | // 220 | const float2 offsets[3] = { 221 | perpendicular, 222 | direction * sqrt(3) / 3.0, 223 | -perpendicular, 224 | }; 225 | 226 | // Shift entire line end outwards by sqrt(3) / 3.0 to align with connecting line 227 | const float2 offset = (direction * sqrt(3) / 3.0) + offsets[gtid % 3]; 228 | const float2 position = (gtid < 3)? record.start - offset * lineWidth 229 | : record.end + offset * lineWidth; 230 | 231 | verts[gtid].position = float4(position, 0.25, 1.0); 232 | } 233 | } 234 | 235 | // Color palette for different depth levels 236 | float4 GetTriangleColor(in uint depth) { 237 | switch (depth % 4) { 238 | case 0: return float4(0.13, 0.44, 0.71, 1.0); // Triangle Recursion 0 239 | case 1: return float4(0.42, 0.68, 0.84, 1.0); // Triangle Recursion 1 240 | case 2: return float4(0.74, 0.84, 0.91, 1.0); // Triangle Recursion 2 241 | case 3: return float4(0.94, 0.95, 1.00, 1.0); // Triangle Recursion 3 242 | default: return 0; 243 | } 244 | } 245 | 246 | [Shader("node")] 247 | [NodeLaunch("mesh")] 248 | // To demonstrate how to override a mesh node id when creating the work graph, we don't specify a node id for this mesh shader. 249 | // The node id will be set using a mesh node launch override when creating the work graph state object (see HelloMeshNodes.cpp:160) 250 | // [NodeId("TriangleMeshNode", 0)] 251 | [NodeDispatchGrid(1, 1, 1)] 252 | [NumThreads(3, 1, 1)] 253 | [OutputTopology("triangle")] 254 | void TriangleMeshShader( 255 | uint gtid : SV_GroupThreadID, 256 | DispatchNodeInputRecord inputRecord, 257 | out indices uint3 triangles[1], 258 | out primitives Primitive prims[1], 259 | out vertices Vertex verts[3]) 260 | { 261 | const TriangleDrawRecord record = inputRecord.Get(); 262 | 263 | SetMeshOutputCounts(3, 1); 264 | 265 | if (gtid < 1) 266 | { 267 | triangles[0] = uint3(0, 1, 2); 268 | prims[0].color = GetTriangleColor(record.depth); 269 | } 270 | 271 | if (gtid < 3) 272 | { 273 | verts[gtid].position = float4(record.verts[gtid], 0.5, 1); 274 | } 275 | } 276 | 277 | // ================================ 278 | // Pixel Shader for both mesh nodes 279 | 280 | float4 MeshNodePixelShader(in float4 color : COLOR0) : SV_TARGET 281 | { 282 | return color; 283 | } 284 | )"; 285 | } -------------------------------------------------------------------------------- /figures/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/WorkGraphsHelloMeshNodes/bfbad642439f0ed6b6d25f69f172d19081301fae/figures/graph.png -------------------------------------------------------------------------------- /figures/iterations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/WorkGraphsHelloMeshNodes/bfbad642439f0ed6b6d25f69f172d19081301fae/figures/iterations.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | ********************************************************************/ 22 | 23 | #include "HelloMeshNodes.h" 24 | 25 | 26 | extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 715; } 27 | extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; } 28 | 29 | int main() 30 | { 31 | try 32 | { 33 | d3d12::LoadCompiler(); 34 | 35 | HelloMeshNodes helloMeshNodes = {}; 36 | 37 | HWND hwnd = window::Initialize(&helloMeshNodes); 38 | 39 | helloMeshNodes.Initialize(hwnd); 40 | 41 | ShowWindow(hwnd, SW_SHOW); 42 | 43 | window::MessageLoop(); 44 | } 45 | catch (...) {} 46 | 47 | d3d12::ReleaseCompiler(); 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------