diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d13754a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +d3d12/lib/x64/D3D12Core.dll filter=lfs diff=lfs merge=lfs -text +d3d12/lib/x64/d3d12SDKLayers.dll filter=lfs diff=lfs merge=lfs -text +d3d12/lib/x64/dxcompiler.dll filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7421efe --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.user +.vs/ +x64/ +!/d3d12/lib/x64/ +packages/ diff --git a/D3D12Helper.cpp b/D3D12Helper.cpp new file mode 100644 index 0000000..e806103 --- /dev/null +++ b/D3D12Helper.cpp @@ -0,0 +1,383 @@ +/********************************************************************** +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +********************************************************************/ + +#include "HelloMeshNodes.h" + +#include +#include + +#define ERROR_QUIT(value, ...) if(!(value)) { printf("ERROR: "); printf(__VA_ARGS__); printf("\nPress any key to terminate...\n"); _getch(); throw 0; } + +namespace { + // function GetHardwareAdapter() copy-pasted from the publicly distributed sample provided at: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-d3d12createdevice + void GetHardwareAdapter(IDXGIFactory4* pFactory, IDXGIAdapter1** ppAdapter) + { + *ppAdapter = nullptr; + for (UINT adapterIndex = 0; ; ++adapterIndex) + { + IDXGIAdapter1* pAdapter = nullptr; + if (DXGI_ERROR_NOT_FOUND == pFactory->EnumAdapters1(adapterIndex, &pAdapter)) + { + // No more adapters to enumerate. + break; + } + + // Check to see if the adapter supports Direct3D 12, but don't create the + // actual device yet. + if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) + { + *ppAdapter = pAdapter; + return; + } + pAdapter->Release(); + } + } +} + +HelloMeshNodes::~HelloMeshNodes() +{ + if (device_) { + WaitForPreviousFrame(); + CloseHandle(fenceEvent_); + } +} + +void HelloMeshNodes::InitializeDirectX(HWND hwnd) +{ + HRESULT hresult; + + device_ = nullptr; + + CComPtr factory; + hresult = CreateDXGIFactory2(0, IID_PPV_ARGS(&factory)); + ERROR_QUIT(hresult == S_OK, "Failed to create IDXGIFactory4."); + + CComPtr hardwareAdapter; + GetHardwareAdapter(factory, &hardwareAdapter); + hresult = D3D12CreateDevice(hardwareAdapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)); + ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12Device."); + + // Create the command queue. + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + + hresult = device_->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue_)); + ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12CommandQueue."); + + // Create the swap chain. + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.BufferCount = FrameCount; + swapChainDesc.Width = WindowSize; + swapChainDesc.Height = WindowSize; + swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.SampleDesc.Count = 1; + + CComPtr swapChain; + hresult = factory->CreateSwapChainForHwnd( + commandQueue_, + hwnd, + &swapChainDesc, + nullptr, + nullptr, + &swapChain + ); + ERROR_QUIT(hresult == S_OK, "Failed to create IDXGISwapChain1."); + + hresult = factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); + ERROR_QUIT(hresult == S_OK, "Failed to make window association."); + + hresult = swapChain.QueryInterface(&swapChain_); + ERROR_QUIT(hresult == S_OK, "Failed to query IDXGISwapChain3."); + + frameIndex_ = swapChain_->GetCurrentBackBufferIndex(); + + // Create render target view (RTV) descriptor heaps. + { + D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; + rtvHeapDesc.NumDescriptors = FrameCount; + rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hresult = device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&renderViewDescriptorHeap_)); + ERROR_QUIT(hresult == S_OK, "Failed to create RTV descriptor heap."); + + descriptorSize_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + } + + // Create frame resources. + { + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderViewDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); + + // Create a RTV for each frame. + for (UINT n = 0; n < FrameCount; n++) + { + hresult = swapChain_->GetBuffer(n, IID_PPV_ARGS(&renderTargets_[n])); + ERROR_QUIT(hresult == S_OK, "Failed to access render target of swap chain."); + device_->CreateRenderTargetView(renderTargets_[n].p, nullptr, rtvHandle); + rtvHandle.Offset(1, descriptorSize_); + } + } + + // Create a depth-stencil view (DSV) descriptor heap and depth buffer + { + D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {}; + dsvHeapDesc.NumDescriptors = 1; + dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; + dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hresult = device_->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&depthDescriptorHeap_)); + ERROR_QUIT(hresult == S_OK, "Failed to create DSV descriptor heap."); + + depthDescriptorHeap_->SetName(L"Depth/Stencil Resource Heap"); + + D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {}; + depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT; + depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE; + + D3D12_CLEAR_VALUE depthOptimizedClearValue = {}; + depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT; + depthOptimizedClearValue.DepthStencil.Depth = 1.0f; + depthOptimizedClearValue.DepthStencil.Stencil = 0; + + CD3DX12_HEAP_PROPERTIES depthHeapProperties(D3D12_HEAP_TYPE_DEFAULT); + CD3DX12_RESOURCE_DESC depthResourceDescription = CD3DX12_RESOURCE_DESC::Tex2D( + DXGI_FORMAT_D32_FLOAT, + WindowSize, WindowSize, + 1, 0, 1, 0, + D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL); + hresult = device_->CreateCommittedResource( + &depthHeapProperties, + D3D12_HEAP_FLAG_NONE, + &depthResourceDescription, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + &depthOptimizedClearValue, + IID_PPV_ARGS(&depthBuffer_) + ); + ERROR_QUIT(hresult == S_OK, "Failed to create depth buffer."); + + device_->CreateDepthStencilView(depthBuffer_, &depthStencilDesc, depthDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); + } + + hresult = device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator_)); + ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12CommandAllocator."); + + // Create the command list. + hresult = device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator_.p, nullptr, IID_PPV_ARGS(&commandList_)); + ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12GraphicsCommandList."); + + // Command lists are created in the recording state, but there is nothing + // to record yet. The main loop expects it to be closed, so close it now. + hresult = commandList_->Close(); + ERROR_QUIT(hresult == S_OK, "Failed to close ID3D12GraphicsCommandList."); + + // Create sync objects + hresult = device_->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence_)); + ERROR_QUIT(hresult == S_OK, "Failed to create ID3D12Fence."); + fenceValue_ = 1; + + // Create an event handle to use for frame synchronization. + fenceEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); + ERROR_QUIT(fenceEvent_ != nullptr, "Failed to create synchronization event."); + + // Create empty root signature + { + CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; + rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); + + CComPtr signature; + CComPtr error; + hresult = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error); + ERROR_QUIT(hresult == S_OK, "Failed to serialize RootSignature."); + hresult = device_->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&globalRootSignature_)); + ERROR_QUIT(hresult == S_OK, "Failed to create RootSignature."); + } +} + +void HelloMeshNodes::Render() +{ + HRESULT hresult; + // Reset allocator and list + hresult = commandAllocator_->Reset(); + ERROR_QUIT(hresult == S_OK, "Failed to reset ID3D12CommandAllocator."); + + hresult = commandList_->Reset(commandAllocator_.p, pipelineState_.p); + ERROR_QUIT(hresult == S_OK, "Failed to reset ID3D12GraphicsCommandList."); + + RecordCommandList(); + + hresult = commandList_->Close(); + ERROR_QUIT(hresult == S_OK, "Failed to close ID3D12CommandAllocator."); + + // Execute the command list. + commandQueue_->ExecuteCommandLists(1, CommandListCast(&commandList_.p)); + + // Present the frame. + hresult = swapChain_->Present(1, 0); + ERROR_QUIT(hresult == S_OK, "Failed to present frame."); + + WaitForPreviousFrame(); +} + +void HelloMeshNodes::WaitForPreviousFrame() +{ + HRESULT hresult; + // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. + // This is code implemented as such for simplicity. The D3D12HelloFrameBuffering + // sample from Microsoft illustrates how to use fences for efficient resource + // usage and to maximize GPU utilization. + + // Signal and increment the fence value. + const UINT64 fence = fenceValue_; + hresult = commandQueue_->Signal(fence_.p, fence); + ERROR_QUIT(hresult == S_OK, "Failed to signal fence."); + fenceValue_++; + + // Wait until the previous frame is finished. + if (fence_->GetCompletedValue() < fence) + { + hresult = fence_->SetEventOnCompletion(fence, fenceEvent_); + ERROR_QUIT(hresult == S_OK, "Failed to set up fence event."); + WaitForSingleObject(fenceEvent_, INFINITE); + } + + frameIndex_ = swapChain_->GetCurrentBackBufferIndex(); +} + +namespace d3d12 { + HMODULE sDxCompilerDLL = nullptr; + void LoadCompiler() + { + // load compiler + sDxCompilerDLL = LoadLibrary(L"dxcompiler.dll"); + + ERROR_QUIT(sDxCompilerDLL, "Failed to initialize compiler."); + } + + void ReleaseCompiler() + { + if (sDxCompilerDLL) + { + FreeLibrary(sDxCompilerDLL); + sDxCompilerDLL = nullptr; + } + } + + ID3D12Resource* AllocateBuffer(CComPtr pDevice, UINT64 Size, D3D12_RESOURCE_FLAGS ResourceFlags, D3D12_HEAP_TYPE HeapType) + { + ID3D12Resource* pResource; + + CD3DX12_HEAP_PROPERTIES HeapProperties(HeapType); + CD3DX12_RESOURCE_DESC ResourceDesc = CD3DX12_RESOURCE_DESC::Buffer(Size, ResourceFlags); + HRESULT hr = pDevice->CreateCommittedResource(&HeapProperties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_PPV_ARGS(&pResource)); + ERROR_QUIT(SUCCEEDED(hr), "Failed to allocate buffer."); + + return pResource; + } + + void TransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter) + { + CD3DX12_RESOURCE_BARRIER transition = CD3DX12_RESOURCE_BARRIER::Transition(resource, stateBefore, stateAfter); + commandList->ResourceBarrier(1, &transition); + } +} + +namespace window { + HWND Initialize(HelloMeshNodes* ctx) + { + const HINSTANCE hInstance = GetModuleHandleA(NULL); + + WNDCLASSEX windowClass = { 0 }; + windowClass.cbSize = sizeof(WNDCLASSEX); + windowClass.style = CS_HREDRAW | CS_VREDRAW; + windowClass.lpfnWndProc = Proc; + windowClass.hInstance = hInstance; + windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); + windowClass.lpszClassName = L"Hello Mesh Nodes"; + RegisterClassEx(&windowClass); + + RECT windowRect = { 0, 0, WindowSize, WindowSize }; + DWORD style = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU; + AdjustWindowRect(&windowRect, style, FALSE); + + HWND hwnd = CreateWindow(windowClass.lpszClassName, + windowClass.lpszClassName, + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + windowRect.right - windowRect.left, + windowRect.bottom - windowRect.top, + nullptr, // We have no parent window. + nullptr, // We are not using menus. + hInstance, + static_cast(ctx)); + + return hwnd; + } + + + LRESULT CALLBACK Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + HelloMeshNodes* ctx = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + + switch (message) + { + case WM_CREATE: + { + // Save the WindowContext* passed in to CreateWindow. + LPCREATESTRUCT pCreateStruct = reinterpret_cast(lParam); + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(pCreateStruct->lpCreateParams)); + return 0; + } + case WM_PAINT: + ctx->Render(); + return 0; + case WM_KEYDOWN: { + if (lParam == VK_ESCAPE) { + PostQuitMessage(0); + return 0; + } + } + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + + // Handle any messages the switch statement didn't. + return DefWindowProc(hWnd, message, wParam, lParam); + } + + + void MessageLoop() + { + MSG msg = {}; + while (msg.message != WM_QUIT) + { + // Process any messages in the queue. + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } +} \ No newline at end of file diff --git a/HelloMeshNodes.cpp b/HelloMeshNodes.cpp new file mode 100644 index 0000000..e190fb8 --- /dev/null +++ b/HelloMeshNodes.cpp @@ -0,0 +1,307 @@ +/********************************************************************** +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +********************************************************************/ + +#include "HelloMeshNodes.h" +#include "ShaderSource.h" + +#include + +#define ERROR_QUIT(value, ...) if(!(value)) { printf("ERROR: "); printf(__VA_ARGS__); printf("\nPress any key to terminate...\n"); _getch(); throw 0; } + +void HelloMeshNodes::Initialize(HWND hwnd) +{ + EnableExperimentalFeatures(); + + InitializeDirectX(hwnd); + + CheckWorkGraphMeshNodeSupport(); + + // Compile shader libraries with meta data + workGraphLibrary_ = d3d12::CompileShader(shader::workGraphSource, nullptr, L"lib_6_9"); + // Compile pixel shader separately + pixelShaderLibrary_ = d3d12::CompileShader(shader::workGraphSource, L"MeshNodePixelShader", L"ps_6_9"); + + stateObject_ = CreateGWGStateObject(); + setProgramDesc_ = PrepareWorkGraph(stateObject_); +} + +void HelloMeshNodes::EnableExperimentalFeatures() +{ + // Mesh nodes require experimental state object features and shader model 6.9 which are not supported by default. + UUID ExperimentalFeatures[2] = { D3D12ExperimentalShaderModels, D3D12StateObjectsExperiment }; + HRESULT hr = D3D12EnableExperimentalFeatures(_countof(ExperimentalFeatures), ExperimentalFeatures, nullptr, nullptr); + + ERROR_QUIT((hr == S_OK), "Failed to enable experimental features."); +} + +void HelloMeshNodes::CheckWorkGraphMeshNodeSupport() +{ + D3D12_FEATURE_DATA_D3D12_OPTIONS21 Options = {}; + + HRESULT hr = device_->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &Options, sizeof(Options)); + ERROR_QUIT(hr == S_OK, "Failed to check support for work graphs and mesh nodes."); + + // Mesh nodes are supported in D3D12_WORK_GRAPHS_TIER_1_1 + ERROR_QUIT(Options.WorkGraphsTier >= D3D12_WORK_GRAPHS_TIER_1_1, + "Failed to find device with D3D12 Work Graphs 1.1 support. Please check if you have a compatible driver and graphics card installed."); +} + +ID3D12StateObject* HelloMeshNodes::CreateGWGStateObject() +{ + ID3D12StateObject* stateObject = nullptr; + CD3DX12_STATE_OBJECT_DESC stateObjectDesc(D3D12_STATE_OBJECT_TYPE_EXECUTABLE); + + // Configure graphics state for global root signature + auto configSubobject = stateObjectDesc.CreateSubobject(); + configSubobject->SetFlags( + D3D12_STATE_OBJECT_FLAG_WORK_GRAPHS_USE_GRAPHICS_STATE_FOR_GLOBAL_ROOT_SIGNATURE); + + CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT* globalRootSignatureSubobject = stateObjectDesc.CreateSubobject(); + globalRootSignatureSubobject->SetRootSignature(globalRootSignature_); + + CD3DX12_WORK_GRAPH_SUBOBJECT* workGraphDesc = stateObjectDesc.CreateSubobject(); + workGraphDesc->IncludeAllAvailableNodes(); + workGraphDesc->SetProgramName(kProgramName); + + // Work Graph Nodes + { + // Here we add the DXIL library compiled with "lib_6_9" target to the state object desc. + // With mesh nodes, this library will also contain the mesh shaders with the [NodeLaunch("mesh")] attribute. + CD3DX12_DXIL_LIBRARY_SUBOBJECT* libraryDesc = stateObjectDesc.CreateSubobject(); + CD3DX12_SHADER_BYTECODE libraryCode(workGraphLibrary_); + libraryDesc->SetDXILLibrary(&libraryCode); + } + + // Next we need to add the separately compiled pixel shader to the state object desc. + // The pixel shader itself will be compiled with target "ps_6_9" and is added to the state object as a DXIL library. + { + CD3DX12_DXIL_LIBRARY_SUBOBJECT* libraryDesc = stateObjectDesc.CreateSubobject(); + CD3DX12_SHADER_BYTECODE libraryCode(pixelShaderLibrary_); + libraryDesc->SetDXILLibrary(&libraryCode); + } + + // In the following section we add subobject for various graphics states to the state object description. + // These subobjects form "building blocks" and allow us to then create different mesh nodes with them. + + // Subobject to define rasterizer state for generic programs + auto rasterizerSubobject = stateObjectDesc.CreateSubobject(); + rasterizerSubobject->SetFrontCounterClockwise(true); + rasterizerSubobject->SetFillMode(D3D12_FILL_MODE_SOLID); + rasterizerSubobject->SetCullMode(D3D12_CULL_MODE_NONE); + + // Subobject to define depth-stencil state for generic programs + auto depthStencilSubobject = stateObjectDesc.CreateSubobject(); + depthStencilSubobject->SetDepthEnable(true); + + // Subobject to define depth-stencil format for generic programs + auto depthStencilFormatSubobject = stateObjectDesc.CreateSubobject(); + depthStencilFormatSubobject->SetDepthStencilFormat(depthBuffer_->GetDesc().Format); + + // Subobject to define render target formats for generic programs + auto renderTargetFormatSubobject = stateObjectDesc.CreateSubobject(); + renderTargetFormatSubobject->SetNumRenderTargets(1); + renderTargetFormatSubobject->SetRenderTargetFormat(0, renderTargets_[0]->GetDesc().Format); + + // Next we'll create two generic program subobject for our two mesh nodes. + + // LineMeshNode + { + // The line mesh shader defines the [NodeId(...)] attribute, and thus a generic program that references it + // will be automatically turned into a work graph mesh node. + + auto lineProgramSubobject = stateObjectDesc.CreateSubobject(); + + // Add mesh shader to the generic program. + // The exportName is the name of our mesh shader function in the shader library. + lineProgramSubobject->AddExport(L"LineMeshShader"); + // Add the pixel shader to the generic program. + // The exportName is the entry point name of our pixel shader. + lineProgramSubobject->AddExport(L"MeshNodePixelShader"); + + // Add "building blocks" to define the graphics PSO state for our mesh node + lineProgramSubobject->AddSubobject(*rasterizerSubobject); + lineProgramSubobject->AddSubobject(*depthStencilSubobject); + lineProgramSubobject->AddSubobject(*depthStencilFormatSubobject); + lineProgramSubobject->AddSubobject(*renderTargetFormatSubobject); + } + + // TriangleMeshNode + { + // The triangle mesh shader does not define a [NodeId(...)] attribute, + // thus the generic program that we create with it would take the name "TriangleMeshShader". + // Here we'll rename it to "TriangleMeshNode", which is how other nodes in the graph reference it. + + auto triangleProgramSubobject = stateObjectDesc.CreateSubobject(); + + // To later rename the mesh node created with this generic program, we first need to give it a unique name. + const auto genericProgramName = L"TriangleMeshNodeGenericProgram"; + triangleProgramSubobject->SetProgramName(genericProgramName); + + // Mesh and pixel shader are added in the same way as with the line mesh shader above + triangleProgramSubobject->AddExport(L"TriangleMeshShader"); + triangleProgramSubobject->AddExport(L"MeshNodePixelShader"); + + // Same with the "building blocks" for the graphics PSO state + triangleProgramSubobject->AddSubobject(*rasterizerSubobject); + triangleProgramSubobject->AddSubobject(*depthStencilSubobject); + triangleProgramSubobject->AddSubobject(*depthStencilFormatSubobject); + triangleProgramSubobject->AddSubobject(*renderTargetFormatSubobject); + + // Next, we need to rename the created mesh node to "TriangleMeshNode". + // To do this, we need to create a mesh launch override with the same name as our generic program. + auto triangleNodeOverride = workGraphDesc->CreateMeshLaunchNodeOverrides(genericProgramName); + // Here we set the name and array index of our mesh node. + // This name will be used by the rest of the work graph to send records to our mesh node. + // This override will also remove the implicitly created "TriangleMeshShader" mesh node. + triangleNodeOverride->NewName({ L"TriangleMeshNode", 0 }); + // Here we could also override other attributes, such as the node dispatch grid, + // but in our case, those attributes are already set in the HLSL source code. + } + + HRESULT hr = device_->CreateStateObject(stateObjectDesc, IID_PPV_ARGS(&stateObject)); + ERROR_QUIT((hr == S_OK) && stateObject, "Failed to create Work Graph State Object."); + + return stateObject; +} + +D3D12_SET_PROGRAM_DESC HelloMeshNodes::PrepareWorkGraph(CComPtr stateObject) +{ + HRESULT hr; + + CComPtr backingMemoryResource = nullptr; + + CComPtr stateObjectProperties; + CComPtr workGraphProperties; + + hr = stateObject->QueryInterface(IID_PPV_ARGS(&stateObjectProperties)); + ERROR_QUIT(SUCCEEDED(hr), "Failed to query ID3D12StateObjectProperties1."); + hr = stateObject->QueryInterface(IID_PPV_ARGS(&workGraphProperties)); + ERROR_QUIT(SUCCEEDED(hr), "Failed to query ID3D12WorkGraphProperties1."); + + // Set the input record limit. This is required for work graphs with mesh nodes. + // In this case we'll only have a single input record + const auto workGraphIndex = workGraphProperties->GetWorkGraphIndex(kProgramName); + workGraphProperties->SetMaximumInputRecords(workGraphIndex, 1, 1); + + D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS memoryRequirements = {}; + workGraphProperties->GetWorkGraphMemoryRequirements(workGraphIndex, &memoryRequirements); + if (memoryRequirements.MaxSizeInBytes > 0) + { + backingMemoryResource = d3d12::AllocateBuffer(device_, memoryRequirements.MaxSizeInBytes, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_HEAP_TYPE_DEFAULT); + } + + D3D12_SET_PROGRAM_DESC setProgramDesc = {}; + setProgramDesc.Type = D3D12_PROGRAM_TYPE_WORK_GRAPH; + setProgramDesc.WorkGraph.ProgramIdentifier = stateObjectProperties->GetProgramIdentifier(kProgramName); + setProgramDesc.WorkGraph.Flags = D3D12_SET_WORK_GRAPH_FLAG_INITIALIZE; + if (backingMemoryResource) + { + setProgramDesc.WorkGraph.BackingMemory = { backingMemoryResource->GetGPUVirtualAddress(), memoryRequirements.MaxSizeInBytes }; + } + + return setProgramDesc; +} + +void HelloMeshNodes::RecordCommandList() +{ + ID3D12Resource* backbuffer = renderTargets_[frameIndex_].p; + d3d12::TransitionBarrier(commandList_, backbuffer, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + // Setup viewport & scissor + CD3DX12_VIEWPORT viewport(0.f, 0.f, WindowSize, WindowSize); + CD3DX12_RECT scissorRect(0, 0, WindowSize, WindowSize); + commandList_->RSSetViewports(1, &viewport); + commandList_->RSSetScissorRects(1, &scissorRect); + + // Render view and depth handle + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderViewDescriptorHeap_->GetCPUDescriptorHandleForHeapStart(), frameIndex_, descriptorSize_); + CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(depthDescriptorHeap_->GetCPUDescriptorHandleForHeapStart()); + + // Clear render target View. + const float clearColor[] = { 1, 1, 1, 1 }; + commandList_->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); + + // Clear depth buffer + commandList_->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); + + // Set depth & color render targets + commandList_->OMSetRenderTargets(1, &rtvHandle, false, &dsvHandle); + + // Dispatch work graph + D3D12_DISPATCH_GRAPH_DESC dispatchGraphDesc = {}; + dispatchGraphDesc.Mode = D3D12_DISPATCH_MODE_NODE_CPU_INPUT; + dispatchGraphDesc.NodeCPUInput = { }; + dispatchGraphDesc.NodeCPUInput.EntrypointIndex = 0; + // Launch graph with one record + dispatchGraphDesc.NodeCPUInput.NumRecords = 1; + // Record does not contain any data + dispatchGraphDesc.NodeCPUInput.RecordStrideInBytes = 0; + dispatchGraphDesc.NodeCPUInput.pRecords = nullptr; + + commandList_->SetGraphicsRootSignature(globalRootSignature_); + commandList_->SetProgram(&setProgramDesc_); + commandList_->DispatchGraph(&dispatchGraphDesc); + + d3d12::TransitionBarrier(commandList_, backbuffer, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + + // Only initialize in the first frame. Set flag from Init to None for all other frames. + setProgramDesc_.WorkGraph.Flags = D3D12_SET_WORK_GRAPH_FLAG_NONE; +} + +namespace d3d12 { + ID3DBlob* CompileShader(const std::string& shaderCode, const wchar_t* entryPoint, const wchar_t* targetProfile) + { + ID3DBlob* resultBlob = nullptr; + if (d3d12::sDxCompilerDLL) + { + DxcCreateInstanceProc pDxcCreateInstance; + pDxcCreateInstance = (DxcCreateInstanceProc)GetProcAddress(d3d12::sDxCompilerDLL, "DxcCreateInstance"); + + if (pDxcCreateInstance) + { + CComPtr pUtils; + CComPtr pCompiler; + CComPtr pSource; + CComPtr pOperationResult; + + if (SUCCEEDED(pDxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&pUtils))) && SUCCEEDED(pDxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&pCompiler)))) + { + if (SUCCEEDED(pUtils->CreateBlob(shaderCode.c_str(), static_cast(shaderCode.length()), 0, &pSource))) + { + if (SUCCEEDED(pCompiler->Compile(pSource, nullptr, entryPoint, targetProfile, nullptr, 0, nullptr, 0, nullptr, &pOperationResult))) + { + HRESULT hr; + pOperationResult->GetStatus(&hr); + if (SUCCEEDED(hr)) + { + pOperationResult->GetResult((IDxcBlob**)&resultBlob); + } + } + } + } + } + } + + ERROR_QUIT(resultBlob, "Failed to compile GWG Library."); + return resultBlob; + } +} \ No newline at end of file diff --git a/HelloMeshNodes.h b/HelloMeshNodes.h new file mode 100644 index 0000000..38448ae --- /dev/null +++ b/HelloMeshNodes.h @@ -0,0 +1,134 @@ +/********************************************************************** +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +********************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +constexpr UINT WindowSize = 720; + +static const wchar_t* kProgramName = L"Hello Mesh Nodes"; + +class HelloMeshNodes +{ +public: + HelloMeshNodes() = default; + ~HelloMeshNodes(); + // Initialize D3D12 and Work graphs objects + void Initialize(HWND hwnd); + // Record command list, execute the list and present the finished frame + void Render(); + +private: + static constexpr UINT FrameCount = 2; + + // Pipeline objects + CComPtr swapChain_; + CComPtr device_; + CComPtr renderTargets_[FrameCount]; + CComPtr pipelineState_; + CComPtr depthBuffer_; + + UINT descriptorSize_; + CComPtr renderViewDescriptorHeap_; + CComPtr depthDescriptorHeap_; + + CComPtr commandAllocator_; + CComPtr commandQueue_; + CComPtr commandList_; + + // Work graphs objects + CComPtr globalRootSignature_; + CComPtr workGraphLibrary_; + CComPtr pixelShaderLibrary_; + + CComPtr stateObject_; + D3D12_SET_PROGRAM_DESC setProgramDesc_; + + ID3D12Resource* frameBuffer_; + + // Synchronization objects. + UINT frameIndex_; + HANDLE fenceEvent_; + CComPtr fence_; + UINT64 fenceValue_; + + // Initializes common DirectX objects + // - D3D12Device + // - D3D12CommandQueue + // - DXGISwapChain + // - Render View Descriptor Heap + // - Render Targets + // - Depth Descriptor Heap + // - Depth Buffer + // - D3D12CommandAllocator + // - D3D12GraphicsCommandList + // - D3D12RootSignature + void InitializeDirectX(HWND hwnd); + + // Enables experimental D3D12 features for mesh nodes + void EnableExperimentalFeatures(); + + // Checks if work graphs and mesh nodes are supported on the current device + void CheckWorkGraphMeshNodeSupport(); + + // Creates work graphs state object + ID3D12StateObject* CreateGWGStateObject(); + // Prepares work graph state object description for execution + D3D12_SET_PROGRAM_DESC PrepareWorkGraph(CComPtr pStateObject); + + // Records command list: + // - clear render target + // - clear depth buffer + // - dispatch work graph + void RecordCommandList(); + + // wait for previous frame to finish + void WaitForPreviousFrame(); +}; + +namespace d3d12 { + extern HMODULE sDxCompilerDLL; + void LoadCompiler(); + // Compiles work graphs library with required meta data + ID3DBlob* CompileShader(const std::string& shaderCode, const wchar_t* entryPoint, const wchar_t* targetProfil); + void ReleaseCompiler(); + + ID3D12Resource* AllocateBuffer(CComPtr pDevice, UINT64 Size, D3D12_RESOURCE_FLAGS ResourceFlags, D3D12_HEAP_TYPE HeapType); + + void TransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource, + D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter); +} + +namespace window { + HWND Initialize(HelloMeshNodes* ctx); + LRESULT CALLBACK Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + + void MessageLoop(); +} diff --git a/HelloMeshNodes.sln b/HelloMeshNodes.sln new file mode 100644 index 0000000..aa7fff3 --- /dev/null +++ b/HelloMeshNodes.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.33801.447 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HelloMeshNodes", "HelloMeshNodes.vcxproj", "{673AC41E-F813-4C5C-B8B5-71FF540C7672}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Debug|x64.ActiveCfg = Debug|x64 + {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Debug|x64.Build.0 = Debug|x64 + {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Release|x64.ActiveCfg = Release|x64 + {673AC41E-F813-4C5C-B8B5-71FF540C7672}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0CF5096D-C8A2-4E21-BA93-3191154C4B0E} + EndGlobalSection +EndGlobal diff --git a/HelloMeshNodes.vcxproj b/HelloMeshNodes.vcxproj new file mode 100644 index 0000000..aa441fb --- /dev/null +++ b/HelloMeshNodes.vcxproj @@ -0,0 +1,125 @@ + + + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {673ac41e-f813-4c5c-b8b5-71ff540c7672} + HelloMeshNodes + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + true + $(ProjectDir)d3d12\inc;$(IncludePath) + + + false + $(ProjectDir)d3d12\inc;$(IncludePath) + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + 6031 + + + Console + true + d3d12.lib;dxgi.lib;%(AdditionalDependencies) + + + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + 6031 + + + Console + true + true + true + d3d12.lib;dxgi.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + 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}. + + + + + + + \ No newline at end of file diff --git a/HelloMeshNodes.vcxproj.filters b/HelloMeshNodes.vcxproj.filters new file mode 100644 index 0000000..a887004 --- /dev/null +++ b/HelloMeshNodes.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1b0aa6d --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Hello Mesh Nodes + +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). + +To run the sample, open the `HelloMeshNodes.sln` with [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). +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. + +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). + +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: +- [DirectX 12 Ultimate Getting Started Guide](https://devblogs.microsoft.com/directx/directx-12-ultimate-getting-started-guide/) +- [GPU Work Graphs Introduction](https://gpuopen.com/learn/gpu-work-graphs/gpu-work-graphs-intro/) +- [Mesh shaders on AMD RDNA™ graphics cards](https://gpuopen.com/learn/mesh_shaders/mesh_shaders-index/) + +## Koch Snowflake +![koch snowflake](./figures/iterations.png) + +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. + +The work graph calculates and renders the first three iterations of the Koch snowflake. + +## Work Graph Setup + +![work graph](./figures/graph.png) + +The work graph draws the Koch snowflake. +The EntryNode starts by drawing the center triangle and starting the snowflake node with the three lines of the initial triangle. +The SnowflakeNode draws a triangle in each iteration, but the last. +In the last iteration the outline is drawn. We use a depth buffer to ensure the outline always appears up top. \ No newline at end of file diff --git a/ShaderSource.h b/ShaderSource.h new file mode 100644 index 0000000..3437ccd --- /dev/null +++ b/ShaderSource.h @@ -0,0 +1,285 @@ +/********************************************************************** +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +********************************************************************/ + +#pragma once + +// This file contains the entire work graph HLSL source code as a single resource string +// The shader code will be compiled twice: +// - with target lib_6_9 for all work graph nodes, including the two mesh nodes for drawing +// - with target ps_6_9 for the pixel shader. Pixel shader cannot be included in the library object and need to be compiled separately. + +namespace shader { + static const char* workGraphSource = R"( +// ========================= +// Work graph record structs + +// Record used for recursively generating & drawing lines +struct LineRecord +{ + float2 start; + float2 end; +}; + +// Record used to draw a single triangle +struct TriangleDrawRecord +{ + float2 verts[3]; + uint depth; +}; + +// Number of Koch iterations +static const uint maxSnowflakeRecursions = 3; + +// This node creates the triangle base for the Koch snowflake. +[Shader("node")] +[NodeIsProgramEntry] +[NodeLaunch("thread")] +void EntryNode( + // Start recursive Koch fractal on each of the three sides of the triangle + [MaxRecords(3)]NodeOutput SnowflakeNode, + // Fill triangle + [MaxRecords(1)]NodeOutput TriangleMeshNode) +{ + ThreadNodeOutputRecords snowflakeRecords = SnowflakeNode.GetThreadNodeOutputRecords(3); + ThreadNodeOutputRecords drawRecords = TriangleMeshNode.GetThreadNodeOutputRecords(1); + + const float2 v0 = float2(0., .9); + const float2 v1 = float2(+sqrt(3) * .45, -.45); + const float2 v2 = float2(-sqrt(3) * .45, -.45); + + // Line v0 -> v1 + snowflakeRecords.Get(0).start = v0; + snowflakeRecords.Get(0).end = v1; + + // Line v1 -> v2 + snowflakeRecords.Get(1).start = v1; + snowflakeRecords.Get(1).end = v2; + + // Line v2 -> v0 + snowflakeRecords.Get(2).start = v2; + snowflakeRecords.Get(2).end = v0; + + // Triangle record + drawRecords.Get(0).depth = 0; + drawRecords.Get(0).verts[0] = v0; + drawRecords.Get(0).verts[1] = v1; + drawRecords.Get(0).verts[2] = v2; + + snowflakeRecords.OutputComplete(); + drawRecords.OutputComplete(); +}; + +[Shader("node")] +[NodeLaunch("thread")] +[NodeMaxRecursionDepth(maxSnowflakeRecursions)] +void SnowflakeNode( + ThreadNodeInputRecord record, + // Koch fractal recursively splits line into 4 new line segments + [MaxRecords(4)]NodeOutput SnowflakeNode, + // Two of the recursive lines form edges of a triangles, which needs to be filled + [MaxRecords(1)]NodeOutput TriangleMeshNode, + // If recursion is not possible, draw a single line + [MaxRecords(1)]NodeOutput LineMeshNode +) { + const float2 start = record.Get().start; + const float2 end = record.Get().end; + + const bool hasOutput = GetRemainingRecursionLevels() != 0; + + ThreadNodeOutputRecords snowflakeRecords = SnowflakeNode.GetThreadNodeOutputRecords(hasOutput * 4); + ThreadNodeOutputRecords triRecord = TriangleMeshNode.GetThreadNodeOutputRecords(hasOutput); + ThreadNodeOutputRecords lineRecord = LineMeshNode.GetThreadNodeOutputRecords(!hasOutput); + + if (hasOutput) { + const float2 perpendicular = float2(start.y - end.y, end.x - start.x) * sqrt(3) / 6; + + const float2 triangleLeft = lerp(start, end, 1./3.); + const float2 triangleMid = lerp(start, end, .5) + perpendicular; + const float2 triangleRight = lerp(start, end, 2./3.); + + snowflakeRecords.Get(0).start = start; + snowflakeRecords.Get(0).end = triangleLeft; + snowflakeRecords.Get(1).start = triangleLeft; + snowflakeRecords.Get(1).end = triangleMid; + snowflakeRecords.Get(2).start = triangleMid; + snowflakeRecords.Get(2).end = triangleRight; + snowflakeRecords.Get(3).start = triangleRight; + snowflakeRecords.Get(3).end = end; + + triRecord.Get(0).depth = 1 + (maxSnowflakeRecursions - GetRemainingRecursionLevels()); + triRecord.Get(0).verts[0] = triangleLeft; + triRecord.Get(0).verts[1] = triangleMid; + triRecord.Get(0).verts[2] = triangleRight; + } else { + lineRecord.Get(0).start = start; + lineRecord.Get(0).end = end; + } + + snowflakeRecords.OutputComplete(); + lineRecord.OutputComplete(); + triRecord.OutputComplete(); +} + +// ======================================================= +// Vertex and primitive attribute structs for mesh shaders +struct Vertex +{ + float4 position : SV_POSITION; +}; + +struct Primitive { + float4 color : COLOR0; +}; + +// ========== +// Mesh Nodes + +// Mesh shader to draw a line between a start and end position. +// As lines X degree angles, we cannot draw lines a simple 2D boxes. +// +// +// v2----------v3 +// / \ +// v1 v4 +// \ / +// v0-----------v5 +// +// Triangulation: +// - v0 -> v1 -> v2 +// - v0 -> v2 -> v3 +// - v0 -> v3 -> v4 +// - v0 -> v4 -> v5 +[Shader("node")] +// Indicate that we are defining a mesh node +[NodeLaunch("mesh")] +// Mesh nodes do not automatically use the function name of the node as their node id. +// If we want to automatically add the generic program created with this mesh node to the work graph, +// we need to explicitly define a node id for it. +[NodeId("LineMeshNode", 0)] +// Mesh nodes can use [NodeDispatchGrid(...)] and [NodeMaxDispatchGrid(...)] in combination with SV_DispatchGrid. +[NodeDispatchGrid(1, 1, 1)] +// The rest of the attributes are the same as for "normal" mesh shaders. +[NumThreads(32, 1, 1)] +[OutputTopology("triangle")] +void LineMeshShader( + uint gtid : SV_GroupThreadID, + DispatchNodeInputRecord inputRecord, + out indices uint3 triangles[4], + out primitives Primitive prims[4], + out vertices Vertex verts[6]) +{ + const LineRecord record = inputRecord.Get(); + SetMeshOutputCounts(6, 4); + + // Output triangles based on triangulation above + if (gtid < 4) + { + triangles[gtid] = uint3(0, gtid + 1, gtid + 2); + prims[gtid].color = float4(0.03, 0.19, 0.42, 1.0); + } + + // Output vertices + if (gtid < 6) { + const float2 direction = normalize(record.end - record.start); + const float2 perpendicular = float2(direction.y, -direction.x); + + const float lineWidth = 0.0075; + + // Offsets for outer triangle shape + // + // offsets[2] ---- ... + // / + // offsets[1] + // \ + // offsets[0] ---- ... + // + // direction <---+ + // | + // v + // prependicular + // + const float2 offsets[3] = { + perpendicular, + direction * sqrt(3) / 3.0, + -perpendicular, + }; + + // Shift entire line end outwards by sqrt(3) / 3.0 to align with connecting line + const float2 offset = (direction * sqrt(3) / 3.0) + offsets[gtid % 3]; + const float2 position = (gtid < 3)? record.start - offset * lineWidth + : record.end + offset * lineWidth; + + verts[gtid].position = float4(position, 0.25, 1.0); + } +} + +// Color palette for different depth levels +float4 GetTriangleColor(in uint depth) { + switch (depth % 4) { + case 0: return float4(0.13, 0.44, 0.71, 1.0); // Triangle Recursion 0 + case 1: return float4(0.42, 0.68, 0.84, 1.0); // Triangle Recursion 1 + case 2: return float4(0.74, 0.84, 0.91, 1.0); // Triangle Recursion 2 + case 3: return float4(0.94, 0.95, 1.00, 1.0); // Triangle Recursion 3 + default: return 0; + } +} + +[Shader("node")] +[NodeLaunch("mesh")] +// 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. +// The node id will be set using a mesh node launch override when creating the work graph state object (see HelloMeshNodes.cpp:160) +// [NodeId("TriangleMeshNode", 0)] +[NodeDispatchGrid(1, 1, 1)] +[NumThreads(3, 1, 1)] +[OutputTopology("triangle")] +void TriangleMeshShader( + uint gtid : SV_GroupThreadID, + DispatchNodeInputRecord inputRecord, + out indices uint3 triangles[1], + out primitives Primitive prims[1], + out vertices Vertex verts[3]) +{ + const TriangleDrawRecord record = inputRecord.Get(); + + SetMeshOutputCounts(3, 1); + + if (gtid < 1) + { + triangles[0] = uint3(0, 1, 2); + prims[0].color = GetTriangleColor(record.depth); + } + + if (gtid < 3) + { + verts[gtid].position = float4(record.verts[gtid], 0.5, 1); + } +} + +// ================================ +// Pixel Shader for both mesh nodes + +float4 MeshNodePixelShader(in float4 color : COLOR0) : SV_TARGET +{ + return color; +} + )"; +} \ No newline at end of file diff --git a/figures/graph.png b/figures/graph.png new file mode 100644 index 0000000..877056d Binary files /dev/null and b/figures/graph.png differ diff --git a/figures/iterations.png b/figures/iterations.png new file mode 100644 index 0000000..2cc8660 Binary files /dev/null and b/figures/iterations.png differ diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..6c7cab2 --- /dev/null +++ b/license.txt @@ -0,0 +1,19 @@ +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..965079e --- /dev/null +++ b/main.cpp @@ -0,0 +1,50 @@ +/********************************************************************** +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +********************************************************************/ + +#include "HelloMeshNodes.h" + + +extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 715; } +extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; } + +int main() +{ + try + { + d3d12::LoadCompiler(); + + HelloMeshNodes helloMeshNodes = {}; + + HWND hwnd = window::Initialize(&helloMeshNodes); + + helloMeshNodes.Initialize(hwnd); + + ShowWindow(hwnd, SW_SHOW); + + window::MessageLoop(); + } + catch (...) {} + + d3d12::ReleaseCompiler(); + + return 0; +} \ No newline at end of file diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..8b3addf --- /dev/null +++ b/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file