├── .gitignore ├── screenshot.jpg ├── .gitattributes ├── imported ├── binoutput.patch ├── cameracomponent.patch ├── dxil.patch ├── patch-ffx.cmake ├── agilitysdk-version.patch ├── CMakeLists.txt └── update-agilitysdk.cmake ├── meshNodeSample ├── config │ └── meshnodesampleconfig.json ├── samplecameracomponent.h ├── shaders │ ├── insectpixelshader.hlsl │ ├── shadingcommon.h │ ├── workgraphcommon.h │ ├── grasspixelshader.hlsl │ ├── shading.hlsl │ ├── rock.hlsl │ ├── heightmap.hlsl │ ├── sparsegrassmeshshader.hlsl │ ├── terrainrenderer.hlsl │ ├── world.hlsl │ ├── utils.hlsl │ ├── densegrassmeshshader.hlsl │ ├── beemeshshader.hlsl │ ├── skybox.hlsl │ ├── mushroommeshshader.hlsl │ ├── butterflymeshshader.hlsl │ ├── common.hlsl │ └── splinerenderer.hlsl ├── shadercompiler.h ├── workgraphrendermodule.h ├── CMakeLists.txt ├── fsr2rendermodule.h ├── main.cpp ├── shadercompiler.cpp ├── samplecameracomponent.cpp └── fsr2rendermodule.cpp ├── license.txt ├── .clang-format ├── CMakeLists.txt └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake build folder 2 | build 3 | # Binary output folder 4 | bin -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/WorkGraphsMeshNodeSample/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Declare files that will always have LF line endings on checkout. 5 | *.c text eol=lf 6 | *.cpp text eol=lf 7 | *.h text eol=lf 8 | *.hpp text eol=lf 9 | *.idl text eol=lf 10 | *.json text eol=lf 11 | *.hlsl text eol=lf 12 | *.patch text eol=lf 13 | 14 | # Denote all files that are truly binary and should not be modified. 15 | *.png binary -------------------------------------------------------------------------------- /imported/binoutput.patch: -------------------------------------------------------------------------------- 1 | diff --git a/common.cmake b/common.cmake 2 | index 88aeb5d..4a2283b 100644 3 | --- a/common.cmake 4 | +++ b/common.cmake 5 | @@ -40,7 +40,7 @@ endif() 6 | set(SAMPLE_ROOT ${CMAKE_HOME_DIRECTORY}/samples) 7 | set(SDK_ROOT ${CMAKE_HOME_DIRECTORY}/sdk) 8 | set(FRAMEWORK_ROOT ${CMAKE_HOME_DIRECTORY}/framework) 9 | -set(BIN_OUTPUT ${CMAKE_HOME_DIRECTORY}/bin) 10 | +set(BIN_OUTPUT ${CMAKE_SOURCE_DIR}/bin) 11 | set(CAULDRON_ROOT ${FRAMEWORK_ROOT}/cauldron) 12 | set(RENDERMODULE_ROOT ${FRAMEWORK_ROOT}/rendermodules) 13 | set(FFX_API_CAULDRON_ROOT ${SAMPLE_ROOT}/ffx_cauldron) 14 | -------------------------------------------------------------------------------- /imported/cameracomponent.patch: -------------------------------------------------------------------------------- 1 | diff --git a/framework/cauldron/framework/inc/core/components/cameracomponent.h b/framework/cauldron/framework/inc/core/components/cameracomponent.h 2 | index 114b5db..33a274d 100644 3 | --- a/framework/cauldron/framework/inc/core/components/cameracomponent.h 4 | +++ b/framework/cauldron/framework/inc/core/components/cameracomponent.h 5 | @@ -245,7 +245,7 @@ namespace cauldron 6 | */ 7 | static void SetJitterCallbackFunc(CameraJitterCallback callbackFunc) { s_pSetJitterCallback = callbackFunc; } 8 | 9 | - private: 10 | + protected: 11 | CameraComponent() = delete; 12 | 13 | void ResetCamera(); 14 | -------------------------------------------------------------------------------- /meshNodeSample/config/meshnodesampleconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mesh Node Sample": { 3 | "RenderResources": { 4 | "GBufferColorTarget": { 5 | "Format": "RGB10A2_UNORM", 6 | "RenderResolution": true 7 | }, 8 | "GBufferNormalTarget": { 9 | "Format": "RGBA16_FLOAT", 10 | "RenderResolution": true 11 | }, 12 | "GBufferMotionVectorTarget": { 13 | "Format": "RG16_FLOAT", 14 | "RenderResolution": true 15 | }, 16 | "GBufferDepthTarget": "DepthTarget" 17 | }, 18 | 19 | "RenderModules": [ 20 | "WorkGraphRenderModule", 21 | "FSR2RenderModule", 22 | "ToneMappingRenderModule" 23 | ], 24 | 25 | "Allocations": { 26 | "GPUResourceViewCount": 200000, 27 | "CPUResourceViewCount": 200000 28 | }, 29 | 30 | "InvertedDepth": false, 31 | "Content": { 32 | "SceneExposure": 0.5 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | UseTab: Never 4 | ColumnLimit: 160 5 | Language: Cpp 6 | AccessModifierOffset: -4 7 | BreakBeforeBraces: Custom 8 | BraceWrapping: 9 | AfterCaseLabel: true 10 | AfterClass: true 11 | AfterControlStatement: true 12 | AfterEnum: true 13 | AfterFunction: true 14 | AfterNamespace: true 15 | AfterObjCDeclaration: true 16 | AfterStruct: true 17 | AfterUnion: true 18 | AfterExternBlock: false 19 | BeforeCatch: true 20 | BeforeElse: true 21 | IndentBraces: false 22 | SplitEmptyFunction: true 23 | SplitEmptyRecord: true 24 | SplitEmptyNamespace: true 25 | ConstructorInitializerAllOnOneLineOrOnePerLine : false 26 | BreakConstructorInitializers: BeforeComma 27 | DerivePointerAlignment: false 28 | IndentCaseLabels: false 29 | NamespaceIndentation: All 30 | AlignConsecutiveAssignments: true 31 | AlignConsecutiveDeclarations: true 32 | AlignEscapedNewlines: Left 33 | AlignTrailingComments: true 34 | AlignOperands: true 35 | AllowShortFunctionsOnASingleLine: false 36 | AllowShortIfStatementsOnASingleLine: false 37 | AllowShortLoopsOnASingleLine: false 38 | AllowShortBlocksOnASingleLine: false 39 | ReflowComments: false 40 | SortIncludes: false 41 | SortUsingDeclarations: false 42 | BinPackArguments: false 43 | BinPackParameters: false 44 | ExperimentalAutoDetectBinPacking: false 45 | AllowAllParametersOfDeclarationOnNextLine: true 46 | AlignConsecutiveMacros: true 47 | AlignAfterOpenBracket: true 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of the AMD Work Graph Mesh Node Sample. 2 | # 3 | # Copyright (C) 2024 Advanced Micro Devices, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files(the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and /or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions : 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.17) 24 | 25 | project("Work Graphs Mesh Node Sample" VERSION 0.1.0 LANGUAGES CXX) 26 | 27 | # Import FidelityFX & Cauldron 28 | add_subdirectory(imported) 29 | 30 | # Add Work Graph Mesh Node Sample 31 | add_subdirectory(meshNodeSample) 32 | 33 | set_property(DIRECTORY ${CMAKE_PROJECT_DIR} PROPERTY VS_STARTUP_PROJECT MeshNodeSample) 34 | -------------------------------------------------------------------------------- /meshNodeSample/samplecameracomponent.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "core/components/cameracomponent.h" 25 | 26 | class MeshNodeSampleCameraComponent : public cauldron::CameraComponent 27 | { 28 | public: 29 | MeshNodeSampleCameraComponent(cauldron::Entity* pOwner, cauldron::ComponentData* pData, cauldron::CameraComponentMgr* pManager); 30 | 31 | void Update(double deltaTime) override; 32 | }; 33 | 34 | void InitCameraEntity(void*); -------------------------------------------------------------------------------- /meshNodeSample/shaders/insectpixelshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | DeferredPixelShaderOutput InsectPixelShader(const InsectVertex input) 25 | { 26 | DeferredPixelShaderOutput output; 27 | 28 | output.motion = input.clipSpaceMotion.xy; 29 | output.baseColor.rgb = input.color.rgb; 30 | output.baseColor.a = 1.f; 31 | 32 | // compute normal from object space position derivatives 33 | output.normal.xyz = normalize(cross(ddy(input.objectSpacePosition.xyz), ddx(input.objectSpacePosition.xyz))); 34 | output.normal.w = 1.0; 35 | 36 | return output; 37 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/shadingcommon.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #if __cplusplus 25 | #include "misc/math.h" 26 | #endif // __cplusplus 27 | 28 | static const unsigned int s_shadingThreadGroupSizeX = 8; 29 | static const unsigned int s_shadingThreadGroupSizeY = 8; 30 | 31 | #if __cplusplus 32 | struct ShadingCBData 33 | { 34 | Mat4 InverseViewProjection; 35 | Vec4 CameraPosition; 36 | }; 37 | #else 38 | // Fullscreen.hlsl binds to b0, so contants need to start at b1 if using fullscreen.hlsl 39 | cbuffer ShadingCBData : register(b1) 40 | { 41 | matrix InverseViewProjection; 42 | float4 CameraPosition; 43 | } 44 | #endif // __cplusplus -------------------------------------------------------------------------------- /meshNodeSample/shadercompiler.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | // windows headers 25 | #define WIN32_LEAN_AND_MEAN 26 | #include 27 | // for BSTR typedef 28 | #include 29 | 30 | // DXC header 31 | #include 32 | 33 | class ShaderCompiler 34 | { 35 | public: 36 | ShaderCompiler(); 37 | ~ShaderCompiler(); 38 | 39 | IDxcBlob* CompileShader(const wchar_t* shaderFilePath, const wchar_t* target, const wchar_t* entryPoint); 40 | 41 | private: 42 | IDxcUtils* m_pUtils = nullptr; 43 | IDxcCompiler* m_pCompiler = nullptr; 44 | IDxcIncludeHandler* m_pIncludeHandler = nullptr; 45 | }; -------------------------------------------------------------------------------- /meshNodeSample/shaders/workgraphcommon.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #if __cplusplus 25 | #include "misc/math.h" 26 | #endif // __cplusplus 27 | 28 | #if __cplusplus 29 | struct WorkGraphCBData { 30 | Mat4 ViewProjection; 31 | Mat4 PreviousViewProjection; 32 | Mat4 InverseViewProjection; 33 | Vec4 CameraPosition; 34 | Vec4 PreviousCameraPosition; 35 | uint32_t ShaderTime; 36 | uint32_t PreviousShaderTime; 37 | float WindStrength; 38 | float WindDirection; 39 | }; 40 | #else 41 | cbuffer WorkGraphCBData : register(b0) 42 | { 43 | matrix ViewProjection; 44 | matrix PreviousViewProjection; 45 | matrix InverseViewProjection; 46 | float4 CameraPosition; 47 | float4 PreviousCameraPosition; 48 | uint ShaderTime; 49 | uint PreviousShaderTime; 50 | float WindStrength; 51 | float WindDirection; 52 | } 53 | #endif // __cplusplus -------------------------------------------------------------------------------- /imported/dxil.patch: -------------------------------------------------------------------------------- 1 | diff --git a/framework/cauldron/framework/libs/dxc/CMakeLists.txt b/framework/cauldron/framework/libs/dxc/CMakeLists.txt 2 | index 6695b76..fad43bb 100644 3 | --- a/framework/cauldron/framework/libs/dxc/CMakeLists.txt 4 | +++ b/framework/cauldron/framework/libs/dxc/CMakeLists.txt 5 | @@ -2,8 +2,7 @@ add_library(dxc INTERFACE) 6 | target_include_directories(dxc INTERFACE BEFORE "inc/") 7 | 8 | set(dxc_binaries 9 | - ${CMAKE_CURRENT_SOURCE_DIR}/bin/x64/dxcompiler.dll 10 | - ${CMAKE_CURRENT_SOURCE_DIR}/bin/x64/dxil.dll) 11 | + ${CMAKE_CURRENT_SOURCE_DIR}/bin/x64/dxcompiler.dll) 12 | 13 | copyTargetCommand("${dxc_binaries}" ${BIN_OUTPUT} copied_dxc_bin) 14 | add_dependencies(dxc copied_dxc_bin) 15 | \ No newline at end of file 16 | diff --git a/framework/cauldron/framework/src/render/win/shaderbuilder_win.cpp b/framework/cauldron/framework/src/render/win/shaderbuilder_win.cpp 17 | index 4847f86..47e2b50 100644 18 | --- a/framework/cauldron/framework/src/render/win/shaderbuilder_win.cpp 19 | +++ b/framework/cauldron/framework/src/render/win/shaderbuilder_win.cpp 20 | @@ -312,14 +312,10 @@ namespace cauldron 21 | ComPtr pCompiledResult; 22 | pCompiler->Compile(&shaderCodeBuffer, arguments.data(), static_cast(arguments.size()), &includeFileHandler, IID_PPV_ARGS(&pCompiledResult)); 23 | 24 | - // Handle any errors if they occurred 25 | - ComPtr pErrors; // wide version currently doesn't appear to be supported 26 | - pCompiledResult->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&pErrors), nullptr); 27 | - if (pErrors && pErrors->GetStringLength() > 0) 28 | + HRESULT compileStatus; 29 | + if (FAILED(pCompiledResult->GetStatus(&compileStatus)) || FAILED(compileStatus)) 30 | { 31 | - std::string errorString = pErrors->GetStringPointer(); 32 | - std::wstring errorWString = StringToWString(errorString.c_str()); 33 | - CauldronCritical(L"%ls : %ls", (shaderFile)? filePath.c_str() : L"ShaderCodeString", errorWString.c_str()); 34 | + CauldronCritical(L"%ls", (shaderFile)? filePath.c_str() : L"ShaderCodeString"); 35 | return nullptr; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /meshNodeSample/shaders/grasspixelshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | DeferredPixelShaderOutput GrassPixelShader(const GrassVertex input, bool isFrontFace : SV_IsFrontFace) 25 | { 26 | DeferredPixelShaderOutput output; 27 | output.motion = input.clipSpaceMotion.xy; 28 | 29 | const float3 biomeWeights = GetBiomeWeights(input.worldSpacePosition.xz); 30 | const float biomeFactor = lerp(1.0, 0.75, biomeWeights.y); 31 | 32 | float selfshadow = clamp(pow((input.worldSpacePosition.y - input.rootHeight) / input.height, 1.5), 0, 1); 33 | output.baseColor.rgb = pow(float3(0.41, 0.44, 0.29) * 2, 2.2) * selfshadow * biomeFactor; 34 | output.baseColor.rgb *= 0.75 + 0.25 * PerlinNoise2D(0.25 * input.worldSpacePosition.xz); 35 | output.baseColor.a = 1; 36 | 37 | float3 normal = normalize(input.worldSpaceNormal); 38 | 39 | if (!isFrontFace) { 40 | normal = -normal; 41 | } 42 | 43 | float3 groundNormal = normalize(lerp(normalize(input.worldSpaceGroundNormal), float3(0, 1, 0), 0.5)); 44 | output.normal.xyz = normalize(lerp(groundNormal, normal, 0.25)); 45 | output.normal.w = 1.0; 46 | 47 | return output; 48 | } -------------------------------------------------------------------------------- /imported/patch-ffx.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of the AMD Work Graph Mesh Node Sample. 2 | # 3 | # Copyright (C) 2024 Advanced Micro Devices, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files(the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and /or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions : 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | # This scripts applies small modifications to the FidelityFX & Cauldron SDK 24 | # Patches: 25 | # - Update Microsoft Agility SDK to 714 26 | # - patch camera component to allow for custom implementation 27 | 28 | # Update Agility SDK 29 | include(update-agilitysdk.cmake) 30 | 31 | find_package(Git) 32 | 33 | message(STATUS "Patching cameracomponent.h") 34 | # Patch camera component 35 | execute_process(COMMAND "${GIT_EXECUTABLE}" apply "${CMAKE_CURRENT_SOURCE_DIR}/cameracomponent.patch" 36 | WORKING_DIRECTORY "${FFX_ROOT}" 37 | ERROR_QUIET 38 | OUTPUT_STRIP_TRAILING_WHITESPACE) 39 | 40 | message(STATUS "Patching common.cmake") 41 | # Patch bin output directory 42 | execute_process(COMMAND "${GIT_EXECUTABLE}" apply "${CMAKE_CURRENT_SOURCE_DIR}/binoutput.patch" 43 | WORKING_DIRECTORY "${FFX_ROOT}" 44 | ERROR_QUIET 45 | OUTPUT_STRIP_TRAILING_WHITESPACE) 46 | 47 | message(STATUS "Patching dxil.dll copy") 48 | # Patch copying of dxil.dll to output directory 49 | execute_process(COMMAND "${GIT_EXECUTABLE}" apply "${CMAKE_CURRENT_SOURCE_DIR}/dxil.patch" 50 | WORKING_DIRECTORY "${FFX_ROOT}" 51 | #ERROR_QUIET 52 | OUTPUT_STRIP_TRAILING_WHITESPACE) 53 | -------------------------------------------------------------------------------- /imported/agilitysdk-version.patch: -------------------------------------------------------------------------------- 1 | diff --git a/framework/cauldron/framework/libs/agilitysdk/CAULDRONREADME.md b/framework/cauldron/framework/libs/agilitysdk/CAULDRONREADME.md 2 | index e445450..470c187 100644 3 | --- a/framework/cauldron/framework/libs/agilitysdk/CAULDRONREADME.md 4 | +++ b/framework/cauldron/framework/libs/agilitysdk/CAULDRONREADME.md 5 | @@ -1,7 +1,7 @@ 6 | # DX12 Agility SDK 7 | 8 | ## Current Version 9 | -1.608.2 10 | +1.715.0-preview 11 | 12 | ## How to update 13 | 1. Download the latest version (as a .nupkg) from https://devblogs.microsoft.com/directx/directx12agility/ 14 | diff --git a/framework/cauldron/framework/src/render/dx12/device_dx12.cpp b/framework/cauldron/framework/src/render/dx12/device_dx12.cpp 15 | index 6782b97..580ff02 100644 16 | --- a/framework/cauldron/framework/src/render/dx12/device_dx12.cpp 17 | +++ b/framework/cauldron/framework/src/render/dx12/device_dx12.cpp 18 | @@ -36,7 +36,7 @@ 19 | using namespace Microsoft::WRL; 20 | 21 | // D3D12SDKVersion needs to line up with the version number on Microsoft's DirectX12 Agility SDK Download page 22 | -extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 608; } 23 | +extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 715; } 24 | extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; } 25 | 26 | namespace cauldron 27 | diff --git a/sdk/tools/ffx_shader_compiler/libs/agilitysdk/FFX_SDK_README.md b/sdk/tools/ffx_shader_compiler/libs/agilitysdk/FFX_SDK_README.md 28 | index d3ae9cd..db89102 100644 29 | --- a/sdk/tools/ffx_shader_compiler/libs/agilitysdk/FFX_SDK_README.md 30 | +++ b/sdk/tools/ffx_shader_compiler/libs/agilitysdk/FFX_SDK_README.md 31 | @@ -1,7 +1,7 @@ 32 | # DX12 Agility SDK 33 | 34 | ## Current Version 35 | -1.608.2 36 | +1.715.0-preview 37 | 38 | ## How to update 39 | 1. Download the latest version (as a .nupkg) from https://devblogs.microsoft.com/directx/directx12agility/ 40 | diff --git a/sdk/tools/ffx_shader_compiler/src/hlsl_compiler.cpp b/sdk/tools/ffx_shader_compiler/src/hlsl_compiler.cpp 41 | index 5375d3d..39884e1 100644 42 | --- a/sdk/tools/ffx_shader_compiler/src/hlsl_compiler.cpp 43 | +++ b/sdk/tools/ffx_shader_compiler/src/hlsl_compiler.cpp 44 | @@ -24,7 +24,7 @@ 45 | #include "utils.h" 46 | 47 | // D3D12SDKVersion needs to line up with the version number on Microsoft's DirectX12 Agility SDK Download page 48 | -extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 608; } 49 | +extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 715; } 50 | extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; } 51 | 52 | struct DxcCustomIncludeHandler : public IDxcIncludeHandler 53 | -------------------------------------------------------------------------------- /imported/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of the AMD Work Graph Mesh Node Sample. 2 | # 3 | # Copyright (C) 2024 Advanced Micro Devices, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files(the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and /or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions : 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | include(FetchContent) 24 | 25 | FetchContent_Declare( 26 | ffxsdk 27 | GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK 28 | GIT_TAG 55ff22bb6981a9b9c087b9465101769fc0acd447 # fsr3-v3.0.4 29 | ) 30 | 31 | FetchContent_GetProperties(ffxsdk) 32 | 33 | # manually import FidelityFX SDK 34 | if (NOT ffxsdk_POPULATED) 35 | message(STATUS "Downloading FidelityFX SDK") 36 | FetchContent_Populate(ffxsdk) 37 | message(STATUS "Downloaded FidelityFX SDK to ${ffxsdk_SOURCE_DIR}") 38 | 39 | # set root directory of FidelityFX SDK for patches 40 | set(FFX_ROOT ${ffxsdk_SOURCE_DIR}) 41 | # Apply patches to FidelityFX SDK 42 | include(patch-ffx.cmake) 43 | 44 | # don't build any FFX samples 45 | set(BUILD_TYPE FFX_NONE) 46 | # build FFX SDK with Cauldron backend 47 | set(FFX_API CAULDRON) 48 | # enable FSR2 in FFX SDK. 49 | set(FFX_FSR2 ON) 50 | # FFX_FSR is required for FFX_FSR2, but also enables FFX sample, which also requires FFX_FSR1 51 | set(FFX_FSR ON) 52 | set(FFX_FSR1 ON) 53 | 54 | # FFX uses CMAKE_HOME_DIRECTORY as root directory for all internal paths 55 | # since FFX is not the top-level repository here, we need to change CMAKE_HOME_DIRECTORY such that all the paths still match up 56 | set(CMAKE_HOME_DIRECTORY ${ffxsdk_SOURCE_DIR}) 57 | add_subdirectory(${ffxsdk_SOURCE_DIR} ${ffxsdk_BINARY_DIR}) 58 | 59 | # Move FFX_FSR sample to folder in solution 60 | set_target_properties(FFX_FSR PROPERTIES FOLDER "FFX Samples") 61 | endif() 62 | 63 | # set root directory of FidelityFX SDK 64 | set(FFX_ROOT ${ffxsdk_SOURCE_DIR} PARENT_SCOPE) 65 | 66 | # propagate configurations to top level; only DX12 is supported for this sample 67 | set(CMAKE_CONFIGURATION_TYPES "DebugDX12;ReleaseDX12;RelWithDebInfoDX12" PARENT_SCOPE) -------------------------------------------------------------------------------- /meshNodeSample/shaders/shading.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "fullscreen.hlsl" 23 | #include "shadingcommon.h" 24 | #include "upscaler.h" 25 | #include "skybox.hlsl" 26 | 27 | //-------------------------------------------------------------------------------------- 28 | // Texture definitions 29 | //-------------------------------------------------------------------------------------- 30 | RWTexture2D RenderTarget : register(u0); 31 | 32 | Texture2D BaseColor : register(t0); 33 | Texture2D Normal : register(t1); 34 | 35 | //-------------------------------------------------------------------------------------- 36 | // Main function 37 | //-------------------------------------------------------------------------------------- 38 | [numthreads(s_shadingThreadGroupSizeX, s_shadingThreadGroupSizeY, 1)] 39 | 40 | void MainCS(uint3 dtID : SV_DispatchThreadID) 41 | { 42 | if (any(dtID.xy > UpscalerInfo.FullScreenScaleRatio.xy)) { 43 | return; 44 | } 45 | 46 | const float2 uv = GetUV(dtID.xy, 1.f / UpscalerInfo.FullScreenScaleRatio.xy); 47 | const float3 clip = float3(2 * uv.x - 1, 1 - 2 * uv.y, 1); 48 | const float3 viewDirection = normalize(PerspectiveProject(InverseViewProjection, clip) - CameraPosition.xyz); 49 | 50 | const LightingData lightingData = GetLightingData(); 51 | 52 | const float4 baseColor = BaseColor[dtID.xy]; 53 | 54 | if (baseColor.a < 0.5) { 55 | RenderTarget[dtID.xy] = float4(GetSkyboxColor(viewDirection, lightingData), 1); 56 | 57 | return; 58 | } 59 | 60 | const float3 normal = Normal[dtID.xy]; 61 | 62 | // Ambient 63 | const float sunZenithDot = lightingData.sunDirection.y; 64 | const float sunZenithDot01 = (sunZenithDot + 1.0) * 0.5; 65 | const float3 ambientContrib = baseColor.rgb * 0.2 * saturate(pow(dot(normal, -viewDirection), .25)) * 1 * 66 | (skybox::SunZenith_Gradient(0.5) + skybox::ViewZenith_Gradient(sunZenithDot01)); 67 | 68 | // Diffuse 69 | const float3 diffuseContrib = saturate(dot(normal, lightingData.globalLightDirection)); 70 | 71 | float3 color = ambientContrib + diffuseContrib * baseColor.rgb; 72 | 73 | RenderTarget[dtID.xy] = float4(color.rgb, 1); 74 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Work Graphs Mesh Node Sample 2 | 3 | ![](./screenshot.jpg) 4 | 5 | This sample shows fully GPU-driven rendering through work graphs and mesh nodes in action. 6 | If you wish to learn more about this sample, work graphs or mesh nodes, you can check out our accompanying blog post on [GPUOpen](https://gpuopen.com/learn/work_graphs_mesh_nodes). 7 | 8 | ## Building the sample 9 | 10 | ### Prerequisites 11 | 12 | To build the Work Graphs Mesh Node Sample, you must first install the following tools: 13 | 14 | - [CMake 3.17](https://cmake.org/download/) 15 | - [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) 16 | - [Windows 10 SDK 10.0.18362.0](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) 17 | - [Vulkan SDK 1.3.239](https://vulkan.lunarg.com/) (build dependency of Cauldron) 18 | 19 | You will also need a mesh node compatible driver. Information on driver availability can be found [here](https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-getting_started). 20 | 21 | ### Getting up and running 22 | 23 | Clone the repository 24 | ``` 25 | git clone https://github.com/GPUOpen-LibrariesAndSDKs/WorkGraphsMeshNodeSample.git 26 | ``` 27 | 28 | Inside the cloned repository, run 29 | ``` 30 | cmake -B build . 31 | ``` 32 | This will download the [FidelityFX SDK](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/tree/release-FSR3-3.0.4), [Agility SDK](https://www.nuget.org/packages/Microsoft.Direct3D.D3D12) and [Direct X Shader Compiler](https://www.nuget.org/packages/Microsoft.Direct3D.DXC) and put them all together with the sample project. 33 | You can find the scripts for this in the [`imported`](./imported/) folder. 34 | 35 | Open the generated Visual Studio project with 36 | ``` 37 | cmake --open build 38 | ``` 39 | 40 | Build & run the `MeshNodeSample` project. 41 | 42 | ### Controls 43 | 44 | | Key | Action | 45 | | ---------------------|---------------------------------------------------------------------------------| 46 | | **Left Mouse + Drag**| Rotates the camera view direction. | 47 | | **Mouse wheel** | Increase/Decrease camera movement speed. | 48 | | **A** | Strafe the camera to the left. | 49 | | **W** | Move the camera forward. | 50 | | **S** | Strafe the camera to the right. | 51 | | **D** | Move the camera backward. | 52 | | **Q** | Move the camera upwards. | 53 | | **E** | Move the camera downwards. | 54 | | **F1** | Toggles the main UI on/off. | 55 | | **F2** | Toggles the performance UI on/off. | 56 | | **F3** | Toggles the message log UI on/off. (Defaults to on in debug and off in release) | 57 | | **M** | Toggles magnifying glass. | 58 | | **L** | Toggles magnifying glass lock when enabled. | 59 | | **ESC** | Shutsdown and quits sample. | 60 | | **Alt-Enter** | Toggles fullscreen mode. | -------------------------------------------------------------------------------- /meshNodeSample/workgraphrendermodule.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "render/rendermodule.h" 25 | #include "render/shaderbuilder.h" 26 | 27 | // d3dx12 for work graphs 28 | #include "d3dx12/d3dx12.h" 29 | 30 | // Forward declaration of Cauldron classes 31 | namespace cauldron 32 | { 33 | class Buffer; 34 | class ParameterSet; 35 | class PipelineObject; 36 | class RasterView; 37 | class RootSignature; 38 | class Texture; 39 | } // namespace cauldron 40 | 41 | class WorkGraphRenderModule : public cauldron::RenderModule 42 | { 43 | public: 44 | WorkGraphRenderModule(); 45 | virtual ~WorkGraphRenderModule(); 46 | 47 | /** 48 | * @brief Initialize work graphs, UI & other contexts 49 | */ 50 | void Init(const json& initData) override; 51 | 52 | /** 53 | * @brief Execute the work graph. 54 | */ 55 | void Execute(double deltaTime, cauldron::CommandList* pCmdList) override; 56 | 57 | /** 58 | * @brief Called by the framework when resolution changes. 59 | */ 60 | void OnResize(const cauldron::ResolutionInfo& resInfo) override; 61 | 62 | private: 63 | /** 64 | * @brief Create and initialize textures required for rendering and shading. 65 | */ 66 | void InitTextures(); 67 | /** 68 | * @brief Create and initialize the work graph program with mesh nodes. 69 | */ 70 | void InitWorkGraphProgram(); 71 | /** 72 | * @brief Create and initialize the shading compute pipeline. 73 | */ 74 | void InitShadingPipeline(); 75 | 76 | // time variable for shader animations in milliseconds 77 | uint32_t m_shaderTime = 0; 78 | 79 | // UI controlled settings 80 | float m_WindStrength = 1.f; 81 | float m_WindDirection = 0.f; 82 | 83 | const cauldron::Texture* m_pGBufferDepthOutput = nullptr; 84 | const cauldron::RasterView* m_pGBufferDepthRasterView = nullptr; 85 | const cauldron::Texture* m_pGBufferColorOutput = nullptr; 86 | const cauldron::Texture* m_pGBufferNormalOutput = nullptr; 87 | const cauldron::Texture* m_pGBufferMotionOutput = nullptr; 88 | std::array m_pGBufferRasterViews; 89 | 90 | cauldron::RootSignature* m_pWorkGraphRootSignature = nullptr; 91 | cauldron::ParameterSet* m_pWorkGraphParameterSet = nullptr; 92 | ID3D12StateObject* m_pWorkGraphStateObject = nullptr; 93 | cauldron::Buffer* m_pWorkGraphBackingMemoryBuffer = nullptr; 94 | // Program description for binding the work graph 95 | // contains work graph identifier & backing memory 96 | D3D12_SET_PROGRAM_DESC m_WorkGraphProgramDesc = {}; 97 | // Index of entry point node 98 | UINT m_WorkGraphEntryPointIndex = 0; 99 | 100 | const cauldron::Texture* m_pShadingOutput = nullptr; 101 | cauldron::RootSignature* m_pShadingRootSignature = nullptr; 102 | cauldron::ParameterSet* m_pShadingParameterSet = nullptr; 103 | cauldron::PipelineObject* m_pShadingPipeline = nullptr; 104 | }; -------------------------------------------------------------------------------- /meshNodeSample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of the AMD Work Graph Mesh Node Sample. 2 | # 3 | # Copyright (C) 2024 Advanced Micro Devices, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files(the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and /or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions : 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | # Declare project 24 | project(MeshNodeSample) 25 | 26 | # --------------------------------------------- 27 | # Import FFX config 28 | # --------------------------------------------- 29 | # FidelityFX uses CMAKE_HOME_DIRECTORY as base for all paths 30 | set(CMAKE_HOME_DIRECTORY ${FFX_ROOT}) 31 | include(${FFX_ROOT}/common.cmake) 32 | include(${FFX_ROOT}/sample.cmake) 33 | 34 | # Set compile definitions from Cauldron/FidelityFX 35 | set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS 36 | $<$:_DX12 _WIN> 37 | $<$:_DX12 _WIN _RELEASE> 38 | $<$:_DX12 _WIN _RELEASE> 39 | FFX_API_CAULDRON 40 | NOMINMAX 41 | ) 42 | 43 | # Output exe to bin directory 44 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT}) 45 | foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) 46 | string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) 47 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${BIN_OUTPUT} ) 48 | endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) 49 | 50 | # --------------------------------------------- 51 | # Sample render module 52 | # --------------------------------------------- 53 | 54 | file(GLOB meshnodesample_src 55 | ${CMAKE_CURRENT_SOURCE_DIR}/*.h 56 | ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 57 | file(GLOB meshnodesample_shaders 58 | ${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.h 59 | ${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.hlsl) 60 | set_source_files_properties(${meshnodesample_shaders} PROPERTIES VS_TOOL_OVERRIDE "Text") 61 | copyCommand("${meshnodesample_shaders}" ${SHADER_OUTPUT}) 62 | 63 | # Add config file 64 | set(config_file ${CMAKE_CURRENT_SOURCE_DIR}/config/meshnodesampleconfig.json) 65 | copyCommand("${config_file}" ${CONFIG_OUTPUT}) 66 | 67 | # Add the sample to the solution 68 | add_executable(${PROJECT_NAME} WIN32 ${default_icon_src} ${config_file} ${meshnodesample_src} ${meshnodesample_shaders} ${ffx_remap}) 69 | 70 | # Setup the correct exe based on backend name 71 | set(EXE_OUT_NAME ${PROJECT_NAME}_) 72 | 73 | # Link everything (including the compiler for now) 74 | target_link_libraries(${PROJECT_NAME} LINK_PUBLIC Framework RenderModules d3dcompiler ffx_fsr2_x64) 75 | set_target_properties(${PROJECT_NAME} PROPERTIES 76 | OUTPUT_NAME_DEBUGDX12 "${EXE_OUT_NAME}DX12D" 77 | OUTPUT_NAME_DEBUGVK "${EXE_OUT_NAME}VKD" 78 | OUTPUT_NAME_RELEASEDX12 "${EXE_OUT_NAME}DX12" 79 | OUTPUT_NAME_RELEASEVK "${EXE_OUT_NAME}VK" 80 | VS_DEBUGGER_WORKING_DIRECTORY "${BIN_OUTPUT}") 81 | 82 | # Add manifest info 83 | addManifest(${PROJECT_NAME}) 84 | 85 | # Add dependency information 86 | add_dependencies(${PROJECT_NAME} Framework) 87 | add_dependencies(${PROJECT_NAME} RenderModules) 88 | add_dependencies(${PROJECT_NAME} ffx_fsr2_x64) 89 | 90 | # Link the correct backend in 91 | 92 | target_link_libraries(${PROJECT_NAME} LINK_PUBLIC ffx_backend_cauldron_x64) 93 | add_dependencies(${PROJECT_NAME} ffx_backend_cauldron_x64) 94 | target_include_directories(${PROJECT_NAME} PUBLIC ${FFX_API_CAULDRON_ROOT} ${CMAKE_SOURCE_DIR}) 95 | 96 | # And solution layout definitions 97 | source_group("" FILES ${ffx_remap}) 98 | source_group("Icon" FILES ${default_icon_src}) 99 | source_group("Config" FILES ${config_file}) 100 | source_group("Sample" FILES ${meshnodesample_src}) 101 | source_group("Sample\\Shaders" FILES ${meshnodesample_shaders}) 102 | -------------------------------------------------------------------------------- /meshNodeSample/fsr2rendermodule.h: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "render/rendermodule.h" 25 | #include "core/framework.h" 26 | #include "core/uimanager.h" 27 | 28 | #include 29 | 30 | #include 31 | 32 | namespace cauldron 33 | { 34 | class Texture; 35 | } // namespace cauldron 36 | 37 | /** 38 | * FSR2RenderModule takes care of: 39 | * - creating UI section that enable users to select upscaling options 40 | * - creating GPU resources 41 | * - clearing and/or generating the reactivity masks 42 | * - dispatch workloads for upscaling using FSR 2 43 | */ 44 | class FSR2RenderModule : public cauldron::RenderModule 45 | { 46 | public: 47 | /** 48 | * @brief Constructor with default behavior. 49 | */ 50 | FSR2RenderModule() 51 | : RenderModule(L"FSR2RenderModule") 52 | { 53 | } 54 | 55 | /** 56 | * @brief Tear down the FSR 2 API Context and release resources. 57 | */ 58 | virtual ~FSR2RenderModule(); 59 | 60 | /** 61 | * @brief Initialize FSR 2 API Context, create resources, and setup UI section for FSR 2. 62 | */ 63 | void Init(const json& initData); 64 | 65 | /** 66 | * @brief If render module is enabled, initialize the FSR 2 API Context. If disabled, destroy the FSR 2 API Context. 67 | */ 68 | void EnableModule(bool enabled) override; 69 | 70 | /** 71 | * @brief Setup parameters that the FSR 2 API needs this frame and then call the FFX Dispatch. 72 | */ 73 | void Execute(double deltaTime, cauldron::CommandList* pCmdList) override; 74 | 75 | /** 76 | * @brief Recreate the FSR 2 API Context to resize internal resources. Called by the framework when the resolution changes. 77 | */ 78 | void OnResize(const cauldron::ResolutionInfo& resInfo) override; 79 | 80 | private: 81 | // Enum representing the FSR 2 quality modes. 82 | enum class FSR2ScalePreset 83 | { 84 | Quality = 0, // 1.5f 85 | Balanced, // 1.7f 86 | Performance, // 2.f 87 | UltraPerformance, // 3.f 88 | Custom // 1.f - 3.f range 89 | }; 90 | 91 | static void FfxMsgCallback(FfxMsgType type, const wchar_t* message); 92 | 93 | void InitUI(); 94 | void UpdatePreset(const int32_t* pOldPreset); 95 | void UpdateUpscaleRatio(const float* pOldRatio); 96 | 97 | cauldron::ResolutionInfo UpdateResolution(uint32_t displayWidth, uint32_t displayHeight); 98 | void UpdateFSR2Context(bool enabled); 99 | 100 | FSR2ScalePreset m_ScalePreset = FSR2ScalePreset::Custom; 101 | float m_UpscaleRatio = 1.f; 102 | float m_Sharpness = 0.8f; 103 | uint32_t m_JitterIndex = 0; 104 | float m_JitterX = 0.f; 105 | float m_JitterY = 0.f; 106 | 107 | bool m_UpscaleRatioEnabled = false; 108 | bool m_RCASSharpen = true; 109 | 110 | // FidelityFX Super Resolution 2 information 111 | FfxFsr2ContextDescription m_InitializationParameters = {}; 112 | FfxFsr2Context m_FSR2Context; 113 | 114 | // For UI params 115 | cauldron::UISection m_UISection; 116 | 117 | // FidelityFX Super Resolution 2 resources 118 | const cauldron::Texture* m_pColorTarget = nullptr; 119 | const cauldron::Texture* m_pDepthTarget = nullptr; 120 | const cauldron::Texture* m_pMotionVectors = nullptr; 121 | 122 | // For resolution updates 123 | std::function m_pUpdateFunc = nullptr; 124 | 125 | }; 126 | -------------------------------------------------------------------------------- /meshNodeSample/shaders/rock.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | // Rock generation is single-threaded, this we use a coalescing node to generate multiple rocks in parallel. 25 | // Rocks are rendered with the same spline mesh node as the trees. 26 | [Shader("node")] 27 | [NodeLaunch("coalescing")] 28 | [NumThreads(maxSplinesPerRecord, 1, 1)] 29 | void GenerateRock( 30 | [MaxRecords(maxSplinesPerRecord)] 31 | GroupNodeInputRecords inputRecord, 32 | 33 | uint threadId : SV_GroupThreadID, 34 | 35 | [MaxRecords(1)] 36 | [NodeId("DrawSpline")] 37 | NodeOutput output) 38 | { 39 | GroupNodeOutputRecords outputRecord = output.GetGroupNodeOutputRecords(1); 40 | 41 | outputRecord.Get().dispatchGrid = uint3(inputRecord.Count(), 1, 1); 42 | 43 | if (threadId < inputRecord.Count()) { 44 | const float2 basePositionXZ = inputRecord.Get(threadId).position; 45 | const uint seed = CombineSeed(asuint(basePositionXZ.x), asuint(basePositionXZ.y)); 46 | 47 | const float3 basePosition = GetTerrainPosition(basePositionXZ); 48 | const float3 terrainNormal = GetTerrainNormal(basePositionXZ); 49 | const float3 basePositionUp = lerp(float3(0, 1, 0), terrainNormal, 1 + Random(seed, 456) * 0.5); 50 | 51 | const float rotationAngle = Random(seed, 14658) * 2 * PI; 52 | 53 | const float upScale = lerp(0.5, 1.2, Random(seed, 546)); 54 | const float a = 1.05f + Random(seed, 6514); 55 | const float2 sideScale = lerp(0.6, 5.0, Random(seed, 9487)) * float2(a, 1); 56 | 57 | const float f = 1.05f + Random(seed, 1564); 58 | const float c = lerp(0.5, 0.9, Random(seed, 49827)); 59 | 60 | outputRecord.Get(0).color[threadId] = float3(0.1, 0.1, 0.1) * 3.5; 61 | outputRecord.Get(0).rotationOffset[threadId] = rotationAngle; 62 | outputRecord.Get(0).windStrength[threadId] = 0; 63 | outputRecord.Get(0).controlPointCount[threadId] = 4; 64 | 65 | int controlPointIndex = threadId * splineMaxControlPointCount; 66 | 67 | outputRecord.Get(0).controlPointPositions[controlPointIndex] = basePosition - terrainNormal; 68 | outputRecord.Get(0).controlPointVertexCounts[controlPointIndex] = 1; 69 | outputRecord.Get(0).controlPointRadii[controlPointIndex] = 0; 70 | outputRecord.Get(0).controlPointNoiseAmplitudes[controlPointIndex] = 0.0; 71 | controlPointIndex++; 72 | 73 | outputRecord.Get(0).controlPointPositions[controlPointIndex] = basePosition; 74 | outputRecord.Get(0).controlPointVertexCounts[controlPointIndex] = round(lerp(5, 7, Random(seed, 4145))); 75 | outputRecord.Get(0).controlPointRadii[controlPointIndex] = sideScale; 76 | outputRecord.Get(0).controlPointNoiseAmplitudes[controlPointIndex] = 0.5 * upScale; 77 | controlPointIndex++; 78 | 79 | outputRecord.Get(0).controlPointPositions[controlPointIndex] = basePosition + upScale * basePositionUp; 80 | outputRecord.Get(0).controlPointVertexCounts[controlPointIndex] = round(lerp(5, 7, Random(seed, 4578))); 81 | outputRecord.Get(0).controlPointRadii[controlPointIndex] = c * sideScale; 82 | outputRecord.Get(0).controlPointNoiseAmplitudes[controlPointIndex] = 0.5 * upScale; 83 | controlPointIndex++; 84 | 85 | outputRecord.Get(0).controlPointPositions[controlPointIndex] = basePosition + f * upScale * basePositionUp; 86 | outputRecord.Get(0).controlPointVertexCounts[controlPointIndex] = 1; 87 | outputRecord.Get(0).controlPointRadii[controlPointIndex] = Random(seed, 89514); 88 | outputRecord.Get(0).controlPointNoiseAmplitudes[controlPointIndex] = 0; 89 | } 90 | 91 | outputRecord.OutputComplete(); 92 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/heightmap.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "utils.hlsl" 25 | 26 | // x = mountain 27 | // y = woodland 28 | // z = grassland 29 | float3 GetBiomeWeights(in float2 position) 30 | { 31 | const float2 pos = position * 0.01; 32 | 33 | float mountainFactor = 0; 34 | mountainFactor += 1 * PerlinNoise2D(0.5 * pos); 35 | mountainFactor += 2 * PerlinNoise2D(0.2 * pos + float2(38, 23)); 36 | mountainFactor += 4 * PerlinNoise2D(0.1 * pos); 37 | 38 | float woodlandMountainFactor = 1.f - smoothstep(0, 1, 4 * pow(mountainFactor - 0.5, 2)); 39 | woodlandMountainFactor = woodlandMountainFactor - pow(4 * PerlinNoise2D(0.1 * pos), 4); 40 | mountainFactor = clamp(pow(clamp(mountainFactor, 0, 1), 2), 0, 1); 41 | 42 | float woodlandFactor = 0; 43 | woodlandFactor += 1 * pow(4 * PerlinNoise2D(0.3 * pos), 3); 44 | woodlandFactor += 5 * pow(1 * PerlinNoise2D(0.1 * pos), 2); 45 | woodlandFactor = clamp(smoothstep(0, 1, woodlandFactor), 0, 1); 46 | 47 | woodlandFactor = smoothstep(0, 1, max(woodlandMountainFactor, woodlandFactor - mountainFactor)); 48 | 49 | float grasslandFactor = clamp(1 - (mountainFactor + woodlandFactor), 0, 1); 50 | 51 | return float3(mountainFactor, woodlandFactor, grasslandFactor); 52 | } 53 | 54 | float GetTerrainHeight(in float2 pos) 55 | { 56 | const float3 biomes = GetBiomeWeights(pos); 57 | 58 | // scale position down for low-frequency perlin noise 59 | const float2 samplePosition = pos / 400.0; 60 | 61 | // Add multiple perlin noise layers to achieve base terrain height 62 | float baseHeight = 0; 63 | baseHeight += 1.0 * PerlinNoise2D(1.0 * samplePosition + float2(34, 98)); 64 | baseHeight += 0.35 * PerlinNoise2D(2.0 * samplePosition + float2(73, 42)); 65 | baseHeight += 0.25 * max(PerlinNoise2D(3.2 * samplePosition + float2(+0.5, -0.5)), 66 | PerlinNoise2D(3.5 * samplePosition + float2(-0.5, +0.5))); 67 | baseHeight += 0.15 * PerlinNoise2D(4.0 * samplePosition); 68 | baseHeight += 0.08 * PerlinNoise2D(8.0 * samplePosition); 69 | baseHeight += 0.07 * PerlinNoise2D(9.0 * samplePosition); 70 | 71 | // square height to make hills a bit more pronounced and scale to final height 72 | float height = 140.0 * baseHeight * baseHeight; 73 | 74 | // Add additional high-frequency noise in mountain biome 75 | float mountainHeight = 0; 76 | mountainHeight += 0.97 * PerlinNoise2D(1.0 * samplePosition); 77 | mountainHeight += 0.95 * max(PerlinNoise2D(2.8 * samplePosition + float2(+2.3, -4.5)), 78 | PerlinNoise2D(3.1 * samplePosition + float2(-6.5, +3.6))); 79 | mountainHeight += 0.75 * PerlinNoise2D(2.0 * samplePosition + float2(34, 56)); 80 | 81 | height += 70.0 * mountainHeight * mountainHeight * smoothstep(0.5, 1.0, biomes.x); 82 | 83 | // raise mountain biome up 84 | height += 40.0 * smoothstep(0.0, 1.0, biomes.x); 85 | 86 | return height; 87 | } 88 | 89 | float GetTerrainHeight(in float x, in float y) 90 | { 91 | return GetTerrainHeight(float2(x, y)); 92 | } 93 | 94 | float3 GetTerrainPosition(in float x, in float z) 95 | { 96 | return float3(x, GetTerrainHeight(x, z), z); 97 | } 98 | 99 | float3 GetTerrainPosition(in float2 pos) 100 | { 101 | return float3(pos.x, GetTerrainHeight(pos.x, pos.y), pos.y); 102 | } 103 | 104 | float3 GetTerrainNormal(in float x, in float z) 105 | { 106 | const float height = GetTerrainHeight(x, z); 107 | 108 | static const float h = 0.01; 109 | float dx = (height - GetTerrainHeight(x + h, z)); 110 | float dz = (height - GetTerrainHeight(x, z + h)); 111 | 112 | float3 a = normalize(float3(h, -dx, 0)); 113 | float3 b = normalize(float3(0, -dz, h)); 114 | 115 | return normalize(cross(b, a)); 116 | } 117 | 118 | float3 GetTerrainNormal(in float2 pos) 119 | { 120 | return GetTerrainNormal(pos.x, pos.y); 121 | } -------------------------------------------------------------------------------- /meshNodeSample/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | // Framework and Windows implementation 23 | #include "core/framework.h" 24 | #include "core/win/framework_win.h" 25 | 26 | // Config file parsing 27 | #include "misc/fileio.h" 28 | 29 | // Custom camera component 30 | #include "samplecameracomponent.h" 31 | 32 | // Content manager to fix texture load bug 33 | #include "core/contentmanager.h" 34 | 35 | // Render Module Registry 36 | #include "rendermoduleregistry.h" 37 | // Render Modules 38 | #include "fsr2rendermodule.h" 39 | #include "workgraphrendermodule.h" 40 | 41 | // D3D12 header to enable experimental shader models 42 | #include "d3d12.h" 43 | 44 | using namespace cauldron; 45 | 46 | class MeshNodeSample final : public Framework 47 | { 48 | public: 49 | MeshNodeSample(const FrameworkInitParams* pInitParams) 50 | : Framework(pInitParams) 51 | { 52 | } 53 | 54 | ~MeshNodeSample() = default; 55 | 56 | // Overrides 57 | void ParseSampleConfig() override 58 | { 59 | const auto configFileName = L"configs/meshnodesampleconfig.json"; 60 | 61 | json sampleConfig; 62 | CauldronAssert(ASSERT_CRITICAL, ParseJsonFile(configFileName, sampleConfig), L"Could not parse JSON file %ls", configFileName); 63 | 64 | // Get the sample configuration 65 | json configData = sampleConfig["Mesh Node Sample"]; 66 | 67 | // Let the framework parse all the "known" options for us 68 | ParseConfigData(configData); 69 | } 70 | 71 | void RegisterSampleModules() override 72 | { 73 | // Init all pre-registered render modules 74 | rendermodule::RegisterAvailableRenderModules(); 75 | 76 | // Register sample render module 77 | RenderModuleFactory::RegisterModule("WorkGraphRenderModule"); 78 | // Register FSR 2 render module 79 | RenderModuleFactory::RegisterModule("FSR2RenderModule"); 80 | } 81 | 82 | int32_t PreRun() override 83 | { 84 | const auto status = Framework::PreRun(); 85 | 86 | // Init custom camera entity & component 87 | Task createCameraTask(InitCameraEntity, nullptr); 88 | GetTaskManager()->AddTask(createCameraTask); 89 | 90 | // Cauldron is missing its media folder, thus these textures are not available. 91 | // Due to a bug, Cauldron will not shutdown if these textures are not loaded, 92 | // thus we decrement the pending texture loads manually with three nullptr textures 93 | Texture* texturePtr = nullptr; 94 | GetContentManager()->StartManagingContent(L"SpecularIBL", texturePtr); 95 | GetContentManager()->StartManagingContent(L"DiffuseIBL", texturePtr); 96 | GetContentManager()->StartManagingContent(L"BrdfLut", texturePtr); 97 | 98 | return status; 99 | } 100 | 101 | int32_t DoSampleInit() override 102 | { 103 | // Enable FSR 2 upscaling and AA 104 | GetFramework()->GetRenderModule("FSR2RenderModule")->EnableModule(true); 105 | 106 | return 0; 107 | } 108 | 109 | void DoSampleShutdown() override 110 | { 111 | // Shutdown (disable) FSR 2 render module 112 | GetFramework()->GetRenderModule("FSR2RenderModule")->EnableModule(false); 113 | } 114 | }; 115 | 116 | static FrameworkInitParamsInternal s_WindowsParams; 117 | 118 | ////////////////////////////////////////////////////////////////////////// 119 | // WinMain 120 | int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) 121 | { 122 | // Enable experimental D3D12 features for mesh nodes 123 | std::array meshNodesExperimentalFeatures = {D3D12ExperimentalShaderModels, D3D12StateObjectsExperiment}; 124 | CauldronThrowOnFail( 125 | D3D12EnableExperimentalFeatures(static_cast(meshNodesExperimentalFeatures.size()), meshNodesExperimentalFeatures.data(), nullptr, nullptr)); 126 | 127 | // Create the sample and kick it off to the framework to run 128 | FrameworkInitParams initParams = {}; 129 | initParams.Name = L"Mesh Node Sample"; 130 | initParams.CmdLine = lpCmdLine; 131 | initParams.AdditionalParams = &s_WindowsParams; 132 | 133 | // Setup the windows info 134 | s_WindowsParams.InstanceHandle = hInstance; 135 | s_WindowsParams.CmdShow = nCmdShow; 136 | 137 | MeshNodeSample frameworkInstance(&initParams); 138 | return RunFramework(&frameworkInstance); 139 | } 140 | -------------------------------------------------------------------------------- /meshNodeSample/shadercompiler.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "shadercompiler.h" 23 | 24 | #include "misc/assert.h" 25 | 26 | #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING // To avoid receiving deprecation error since we are using \ 27 | // C++11 only 28 | #include 29 | using namespace std::experimental; 30 | 31 | template 32 | inline void SafeRelease(Interface*& pInterfaceToRelease) 33 | { 34 | if (pInterfaceToRelease != nullptr) 35 | { 36 | pInterfaceToRelease->Release(); 37 | 38 | pInterfaceToRelease = nullptr; 39 | } 40 | } 41 | 42 | ShaderCompiler::ShaderCompiler() 43 | { 44 | HMODULE dxilModule = LoadLibraryW(L"dxil.dll"); 45 | HMODULE dxcompilerModule = LoadLibraryW(L"dxcompiler.dll"); 46 | 47 | cauldron::CauldronAssert(cauldron::ASSERT_CRITICAL, dxcompilerModule, L"Failed to load dxcompiler.dll"); 48 | 49 | DxcCreateInstanceProc pfnDxcCreateInstance = DxcCreateInstanceProc(GetProcAddress(dxcompilerModule, "DxcCreateInstance")); 50 | 51 | cauldron::CauldronAssert(cauldron::ASSERT_CRITICAL, pfnDxcCreateInstance, L"Failed to load DxcCreateInstance from dxcompiler.dll"); 52 | 53 | if (FAILED(pfnDxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&m_pUtils)))) 54 | { 55 | cauldron::CauldronCritical(L"Failed to create DXC utils"); 56 | } 57 | 58 | if (FAILED(pfnDxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&m_pCompiler)))) 59 | { 60 | // delete utils if compiler creation fails 61 | SafeRelease(m_pUtils); 62 | 63 | cauldron::CauldronCritical(L"Failed to create DXC compiler"); 64 | } 65 | 66 | if (FAILED(m_pUtils->CreateDefaultIncludeHandler(&m_pIncludeHandler))) 67 | { 68 | // delete utils & compiler if include handler creation fails 69 | SafeRelease(m_pCompiler); 70 | SafeRelease(m_pUtils); 71 | 72 | cauldron::CauldronCritical(L"Failed to create DXC compiler"); 73 | } 74 | } 75 | 76 | ShaderCompiler::~ShaderCompiler() 77 | { 78 | SafeRelease(m_pIncludeHandler); 79 | SafeRelease(m_pCompiler); 80 | SafeRelease(m_pUtils); 81 | } 82 | 83 | IDxcBlob* ShaderCompiler::CompileShader(const wchar_t* shaderFilePath, const wchar_t* target, const wchar_t* entryPoint) 84 | { 85 | IDxcBlobEncoding* source = nullptr; 86 | 87 | const auto shaderSourceFilePath = std::wstring(L"Shaders\\") + shaderFilePath; 88 | 89 | if (FAILED(m_pUtils->LoadFile(shaderSourceFilePath.c_str(), nullptr, &source)) || (source == nullptr)) 90 | { 91 | cauldron::CauldronCritical(L"Failed to load %s", shaderFilePath); 92 | } 93 | 94 | const auto shadersFolderPath = filesystem::current_path() / L"shaders"; 95 | const auto shaderIncludeArgument = std::wstring(L"-I") + shadersFolderPath.wstring(); 96 | 97 | std::vector arguments = { 98 | L"-enable-16bit-types", 99 | // use HLSL 2021 100 | L"-HV", 101 | L"2021", 102 | // column major matrices 103 | DXC_ARG_PACK_MATRIX_COLUMN_MAJOR, 104 | // include path for "shaders" folder 105 | shaderIncludeArgument.c_str(), 106 | }; 107 | 108 | IDxcOperationResult* result = nullptr; 109 | const auto hr = m_pCompiler->Compile( 110 | source, shaderFilePath, entryPoint, target, arguments.data(), static_cast(arguments.size()), nullptr, 0, m_pIncludeHandler, &result); 111 | 112 | // release source blob 113 | SafeRelease(source); 114 | 115 | if (FAILED(hr)) 116 | { 117 | SafeRelease(result); 118 | 119 | cauldron::CauldronCritical(L"Failed to compile shader %s", shaderFilePath); 120 | } 121 | 122 | HRESULT compileStatus; 123 | if (FAILED(result->GetStatus(&compileStatus))) 124 | { 125 | SafeRelease(result); 126 | 127 | cauldron::CauldronCritical(L"Failed to get compilation status for shader %s", shaderFilePath); 128 | } 129 | 130 | std::wstring errorString = L""; 131 | 132 | // try get error string from DXC result 133 | { 134 | IDxcBlobEncoding* errorStringBlob = nullptr; 135 | if (SUCCEEDED(result->GetErrorBuffer(&errorStringBlob)) && (errorStringBlob != nullptr)) 136 | { 137 | IDxcBlobWide* errorStringBlob16 = nullptr; 138 | m_pUtils->GetBlobAsUtf16(errorStringBlob, &errorStringBlob16); 139 | 140 | errorString = std::wstring(errorStringBlob16->GetStringPointer(), errorStringBlob16->GetStringLength()); 141 | 142 | SafeRelease(errorStringBlob16); 143 | } 144 | SafeRelease(errorStringBlob); 145 | } 146 | 147 | if (FAILED(compileStatus)) 148 | { 149 | SafeRelease(result); 150 | 151 | cauldron::CauldronCritical(L"Failed to compile shader %s\n%s", shaderFilePath, errorString.c_str()); 152 | } 153 | 154 | IDxcBlob* outputBlob = nullptr; 155 | if (FAILED(result->GetResult(&outputBlob))) 156 | { 157 | SafeRelease(result); 158 | 159 | cauldron::CauldronCritical(L"Failed to get binary shader blob for shader %s", shaderFilePath); 160 | } 161 | 162 | SafeRelease(result); 163 | 164 | return outputBlob; 165 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/sparsegrassmeshshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | static const int sparseGrassGroupSize = 128; 25 | static const int numOutputVerticesLimit = 128; 26 | static const int numOutputTrianglesLimit = 64; 27 | 28 | // multiple 8x4 patches fill a detailed tile 29 | static const int2 sparseGrassThreadGroupGridSize = int2(8, 4); 30 | static const int2 spraseGrassThreadGroupsPerAxis = grassPatchesPerDetailedTile / sparseGrassThreadGroupGridSize; 31 | 32 | float2 GetPatchPosition(in int patch, in int patchOffset, in int2 gridBase) 33 | { 34 | const int x = patch % sparseGrassThreadGroupGridSize.x; 35 | const int z = patch / sparseGrassThreadGroupGridSize.x; 36 | 37 | const int offsetX = patchOffset % spraseGrassThreadGroupsPerAxis.x; 38 | const int offsetZ = patchOffset / spraseGrassThreadGroupsPerAxis.x; 39 | 40 | const int2 grid = int2(x, z) + int2(offsetX, offsetZ) * sparseGrassThreadGroupGridSize; 41 | 42 | return grassSpacing * gridBase + 43 | (grid + GetGrassOffset(gridBase + grid)) * grassSpacing * (8.0 / sparseGrassThreadGroupGridSize.x); 44 | } 45 | 46 | 47 | [Shader("node")] 48 | [NodeLaunch("mesh")] 49 | [NodeId("DrawSparseGrassPatch", 0)] 50 | [NodeMaxDispatchGrid(maxSparseGrassPatchesPerRecord, sparseGrassThreadGroupsPerRecord, 1)] 51 | // This limit was set through instrumentation and is not required on AMD GPUs. 52 | // If you wish to change any of the procedural generation parameters, 53 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 54 | // You can learn more at: 55 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 56 | [NodeMaxInputRecordsPerGraphEntryRecord(100, true)] 57 | [NumThreads(sparseGrassGroupSize, 1, 1)] 58 | [OutputTopology("triangle")] 59 | void SparseGrassMeshShader( 60 | uint gtid : SV_GroupThreadID, 61 | uint2 gid : SV_GroupID, 62 | DispatchNodeInputRecord inputRecord, 63 | out indices uint3 tris[numOutputTrianglesLimit], 64 | out primitives GrassCullPrimitive prims[numOutputTrianglesLimit], 65 | out vertices GrassVertex verts[numOutputVerticesLimit]) 66 | { 67 | const int bladeCount = sparseGrassThreadGroupGridSize.x * sparseGrassThreadGroupGridSize.y; 68 | // 4 vertices per blade 69 | const int vertexCount = bladeCount * 4; 70 | // 2 triangles per blade 71 | const int triangleCount = bladeCount * 2; 72 | 73 | SetMeshOutputCounts(vertexCount, triangleCount); 74 | 75 | GrassVertex vertex; 76 | 77 | float low = .05; 78 | float high = .45; 79 | 80 | float3 color = pow(float3(0.41, 0.44, 0.29), 2.2) * .775; 81 | 82 | const int2 gridBase = inputRecord.Get().position[gid.x] * grassPatchesPerDetailedTile; 83 | 84 | static const float3 grassColor = float3(0.130139, 0.149961, 0.059513); 85 | 86 | if (gtid < vertexCount) { 87 | int vertexId = gtid; 88 | int patch = vertexId / 4; 89 | int vi = vertexId % 4; 90 | bool isLow = vi == 0 || vi == 1; 91 | bool isRight = vi == 0 || vi == 2; 92 | 93 | const float2 pos = GetPatchPosition(patch, gid.y, gridBase); 94 | 95 | float3 patchNormal = GetTerrainNormal(pos); 96 | 97 | float3 center = float3(pos.x, GetTerrainHeight(pos), pos.y); 98 | 99 | // Fade grass into the ground in the distance 100 | const float distanceScale = smoothstep(sparseGrassMaxDistance * 0.9, sparseGrassMaxDistance, distance(center, GetCameraPosition())); 101 | center.y -= high * distanceScale; 102 | 103 | float3 center2cam = normalize(GetCameraPosition() - center); 104 | 105 | float3 forward = normalize(float3(center2cam.x, min(0.2, center2cam.y), center2cam.z)); 106 | float3 right = normalize(cross(center2cam, float3(0, 1, 0))); 107 | 108 | float3 up = normalize(cross(right, forward)); 109 | 110 | vertex.worldSpacePosition = center; 111 | vertex.worldSpacePosition += right * BitSign((uint)isRight, 0) * grassSpacing; 112 | vertex.worldSpacePosition += up * (isLow ? (low) : high); 113 | 114 | vertex.worldSpacePosition.xz += center2cam.xz * (length(center2cam) / 1000.); 115 | vertex.worldSpacePosition.y = center.y + (isLow ? (low + center2cam.y * 0.2) : high); 116 | 117 | vertex.rootHeight = center.y + .08; 118 | vertex.height = high; 119 | vertex.worldSpaceNormal = isLow ? normalize(float3(center2cam.x, 0, center2cam.z)) : float3(0, 1, 0); 120 | vertex.worldSpaceGroundNormal = patchNormal; 121 | 122 | ComputeClipSpacePositionAndMotion(vertex, vertex.worldSpacePosition); 123 | 124 | verts[vertexId] = vertex; 125 | } 126 | 127 | if (gtid < triangleCount) { 128 | const int patch = gtid / 2; 129 | const int base = 4 * patch; 130 | 131 | const float2 pos = GetPatchPosition(patch, gid.y, gridBase); 132 | const float3 center = float3(pos.x, GetTerrainHeight(pos), pos.y); 133 | 134 | const float3 terrainNormal = GetTerrainNormal(pos); 135 | const float3 biomeWeight = GetBiomeWeights(pos); 136 | 137 | const bool cull = (Random(asuint(pos.x), asuint(pos.y), 2378) < biomeWeight.x * 2) || 138 | (terrainNormal.y < 0.55); 139 | 140 | uint3 tri = (gtid % 2) == 0 ? uint3(base, base + 1, base + 2) : uint3(base + 3, base + 2, base + 1); 141 | 142 | tris[gtid] = tri; 143 | prims[gtid].cull = cull; 144 | } 145 | } -------------------------------------------------------------------------------- /imported/update-agilitysdk.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of the AMD Work Graph Mesh Node Sample. 2 | # 3 | # Copyright (C) 2024 Advanced Micro Devices, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files(the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and /or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions : 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | # This script will download the Microsoft Agility SDK & DirectX Shader compiler from the official NuGet package repository 24 | # Update these URLs and perform a clean build if you wish to use a newer version of these packages. 25 | set(AGILITY_SDK_URL "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/1.715.0-preview") 26 | set(DXC_URL "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.DXC/1.8.2404.55-mesh-nodes-preview") 27 | 28 | # Check if Agility SDK NuGet package was already downloaded 29 | if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK.zip) 30 | message(STATUS "Downloading Agility SDK from ${AGILITY_SDK_URL}") 31 | 32 | file(DOWNLOAD ${AGILITY_SDK_URL} ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK.zip STATUS DOWNLOAD_RESULT) 33 | 34 | list(GET DOWNLOAD_RESULT 0 DOWNLOAD_RESULT_CODE) 35 | if(NOT DOWNLOAD_RESULT_CODE EQUAL 0) 36 | message(FATAL_ERROR "Failed to download Agility SDK! Error: ${DOWNLOAD_RESULT}.") 37 | endif() 38 | 39 | message(STATUS "Successfully downloaded Agility SDK") 40 | else() 41 | message(STATUS "Found local copy of ${AGILITY_SDK_URL} in ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK.zip. Skipping download.") 42 | endif() 43 | 44 | message(STATUS "Extracting Agility SDK") 45 | 46 | # extract agility SDK zip 47 | file(ARCHIVE_EXTRACT 48 | INPUT ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK.zip 49 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK) 50 | 51 | # validate agility SDK binaries 52 | if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/D3D12Core.dll OR 53 | NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/d3d12SDKLayers.dll) 54 | message(FATAL_ERROR "Failed to extract Agility SDK!") 55 | endif() 56 | 57 | message(STATUS "Successfully extracted Agility SDK") 58 | 59 | set(CAULDRON_AGILITY_SDK_PATH ${FFX_ROOT}/framework/cauldron/framework/libs/agilitysdk) 60 | 61 | # copy Agility SDK binaries 62 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/D3D12Core.dll ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/D3D12Core.dll) 63 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/D3D12Core.pdb ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/D3D12Core.pdb) 64 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/d3d12SDKLayers.dll ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/d3d12SDKLayers.dll) 65 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/d3d12SDKLayers.pdb ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/d3d12SDKLayers.pdb) 66 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/d3dconfig.exe ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/d3dconfig.exe) 67 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/bin/x64/d3dconfig.pdb ${CAULDRON_AGILITY_SDK_PATH}/bin/x64/d3dconfig.pdb) 68 | 69 | # copy Agility SDK headers 70 | file(COPY ${CMAKE_CURRENT_BINARY_DIR}/agilitySDK/build/native/include DESTINATION ${CAULDRON_AGILITY_SDK_PATH}) 71 | 72 | message(STATUS "Successfully copied Agility SDK to Cauldron source") 73 | 74 | # Check if DXC NuGet package was already downloaded 75 | if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/dxc.zip) 76 | message(STATUS "Downloading DirectX Shader Compiler from ${DXC_URL}") 77 | 78 | file(DOWNLOAD ${DXC_URL} ${CMAKE_CURRENT_BINARY_DIR}/dxc.zip STATUS DOWNLOAD_RESULT) 79 | 80 | list(GET DOWNLOAD_RESULT 0 DOWNLOAD_RESULT_CODE) 81 | if(NOT DOWNLOAD_RESULT_CODE EQUAL 0) 82 | message(FATAL_ERROR "Failed to download DirectX Shader Compiler! Error: ${DOWNLOAD_RESULT}.") 83 | endif() 84 | 85 | message(STATUS "Successfully downloaded DirectX Shader Compiler") 86 | else() 87 | message(STATUS "Found local copy of ${DXC_URL} in ${CMAKE_CURRENT_BINARY_DIR}/dxc.zip. Skipping download.") 88 | endif() 89 | 90 | message(STATUS "Extracting DirectX Shader Compiler") 91 | 92 | # extract dxc zip 93 | file(ARCHIVE_EXTRACT 94 | INPUT ${CMAKE_CURRENT_BINARY_DIR}/dxc.zip 95 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/dxc) 96 | 97 | # validate DXC binaries 98 | if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/bin/x64/dxcompiler.dll) 99 | message(FATAL_ERROR "Failed to extract DirectX Shader Compiler!") 100 | endif() 101 | 102 | message(STATUS "Successfully extracted DirectX Shader Compiler") 103 | 104 | set(CAULDRON_DXC_PATH ${FFX_ROOT}/framework/cauldron/framework/libs/dxc) 105 | 106 | # copy dxc binaries 107 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/bin/x64/dxcompiler.dll ${CAULDRON_DXC_PATH}/bin/x64/dxcompiler.dll) 108 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/bin/x64/dxc.exe ${CAULDRON_DXC_PATH}/bin/x64/dxc.exe) 109 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/lib/x64/dxcompiler.lib ${CAULDRON_DXC_PATH}/lib/x64/dxcompiler.lib) 110 | 111 | # copy dxc headers 112 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/include/d3d12shader.h ${CAULDRON_DXC_PATH}/inc/d3d12shader.h) 113 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/include/dxcapi.h ${CAULDRON_DXC_PATH}/inc/dxcapi.h) 114 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/include/dxcerrors.h ${CAULDRON_DXC_PATH}/inc/dxcerrors.h) 115 | file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/dxc/build/native/include/dxcisense.h ${CAULDRON_DXC_PATH}/inc/dxcisense.h) 116 | 117 | message(STATUS "Successfully copied DirectX Shader Compiler to Cauldron source") 118 | 119 | message(STATUS "Patching Agility SDK version") 120 | 121 | # find git and apply a patch to FFX 122 | find_package(Git) 123 | execute_process(COMMAND "${GIT_EXECUTABLE}" apply "${CMAKE_CURRENT_SOURCE_DIR}/agilitysdk-version.patch" 124 | WORKING_DIRECTORY "${FFX_ROOT}" 125 | ERROR_QUIET 126 | OUTPUT_STRIP_TRAILING_WHITESPACE) -------------------------------------------------------------------------------- /meshNodeSample/shaders/terrainrenderer.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | struct TransformedVertex { 25 | float4 clipSpacePosition : SV_POSITION; 26 | float3 worldSpacePosition : NORMAL0; 27 | float3 normal : NORMAL1; 28 | float2 clipSpaceMotion : TEXCOORD0; 29 | }; 30 | 31 | uint3 GetPrimitive(uint index, in uint primitivesPerRow) 32 | { 33 | uint verticesPerRow = primitivesPerRow + 1; 34 | 35 | uint cell = index / 2; 36 | uint row = cell / primitivesPerRow; 37 | cell = cell % primitivesPerRow; 38 | 39 | uint base = (row * verticesPerRow) + cell; 40 | 41 | // c - d 42 | // | / | 43 | // a - b 44 | const uint a = base; 45 | const uint b = base + 1; 46 | const uint c = base + verticesPerRow; 47 | const uint d = base + verticesPerRow + 1; 48 | 49 | return (index % 2) == 0 ? uint3(a, c, d) : uint3(d, b, a); 50 | } 51 | 52 | [Shader("node")] 53 | [NodeLaunch("mesh")] 54 | [NodeId("DrawTerrainChunk", 0)] 55 | [NodeMaxDispatchGrid(8, 8, 1)] 56 | // This limit reflects the maximum dispatch size of the chunk grid and is not required on AMD GPUs. 57 | // If you wish to change any of the procedural generation parameters, 58 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 59 | // You can learn more at: 60 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 61 | [NodeMaxInputRecordsPerGraphEntryRecord(32 * 32, true)] 62 | [NumThreads(128, 1, 1)] 63 | [OutputTopology("triangle")] 64 | void TerrainMeshShader( 65 | uint gtid : SV_GroupThreadID, 66 | uint2 gid : SV_GroupID, 67 | DispatchNodeInputRecord inputRecord, 68 | out indices uint3 tris[128], 69 | out vertices TransformedVertex verts[81]) 70 | { 71 | const DrawTerrainChunkRecord record = inputRecord.Get(); 72 | 73 | const int levelOfDetail = record.levelOfDetail; 74 | // number of thread groups per chunk axis for LOD 0 75 | const int baseThreadGroupsPerChunkAxis = 8; 76 | 77 | const int threadGroupsPerChunkAxis = 78 | baseThreadGroupsPerChunkAxis / clamp(1L << levelOfDetail, 1, baseThreadGroupsPerChunkAxis); 79 | const int threadGroupIdScale = baseThreadGroupsPerChunkAxis / threadGroupsPerChunkAxis; 80 | 81 | const int primitivesPerAxis = 8; 82 | const int verticesPerAxis = primitivesPerAxis + 1; 83 | const float scale = chunkSize / float(primitivesPerAxis * baseThreadGroupsPerChunkAxis); 84 | 85 | const int vertexCount = verticesPerAxis * verticesPerAxis; 86 | const int primitiveCount = primitivesPerAxis * primitivesPerAxis * 2; 87 | 88 | SetMeshOutputCounts(vertexCount, primitiveCount); 89 | 90 | const int2 tile = record.chunkGridPosition * baseThreadGroupsPerChunkAxis + int2(gid.xy) * threadGroupIdScale; 91 | 92 | const bool4 localLevelOfDetailTransition = 93 | bool4(record.levelOfDetailTransition.x && (gid.x == 0), 94 | record.levelOfDetailTransition.y && (gid.y == 0), 95 | record.levelOfDetailTransition.z && (gid.x == (record.dispatchGrid.x - 1)), 96 | record.levelOfDetailTransition.w && (gid.y == (record.dispatchGrid.y - 1))); 97 | 98 | if (gtid < vertexCount) { 99 | TransformedVertex vertex; 100 | 101 | int2 localVertexIndex = int2(gtid % verticesPerAxis, gtid / verticesPerAxis); 102 | 103 | // collapse vertices along LOD borders 104 | if (localLevelOfDetailTransition.x && (localVertexIndex.x == 0) && ((localVertexIndex.y % 2) == 1)) { 105 | localVertexIndex.y = int(localVertexIndex.y / 2) * 2; 106 | } 107 | if (localLevelOfDetailTransition.y && (localVertexIndex.y == 0) && ((localVertexIndex.x % 2) == 1)) { 108 | localVertexIndex.x = int(localVertexIndex.x / 2) * 2; 109 | } 110 | if (localLevelOfDetailTransition.z && (localVertexIndex.x == 8) && ((localVertexIndex.y % 2) == 1)) { 111 | localVertexIndex.y = int(localVertexIndex.y / 2) * 2; 112 | } 113 | if (localLevelOfDetailTransition.w && (localVertexIndex.y == 8) && ((localVertexIndex.x % 2) == 1)) { 114 | localVertexIndex.x = int(localVertexIndex.x / 2) * 2; 115 | } 116 | 117 | localVertexIndex *= threadGroupIdScale; 118 | 119 | const int2 globalVertexIndex = tile * primitivesPerAxis + localVertexIndex; 120 | 121 | const float2 globalVertexPosition = globalVertexIndex * scale; 122 | 123 | const float3 worldSpacePosition = 124 | float3(globalVertexPosition.x, GetTerrainHeight(globalVertexPosition), globalVertexPosition.y); 125 | 126 | vertex.normal = GetTerrainNormal(worldSpacePosition.xz); 127 | vertex.worldSpacePosition = worldSpacePosition; 128 | ComputeClipSpacePositionAndMotion(vertex, worldSpacePosition); 129 | 130 | verts[gtid] = vertex; 131 | } 132 | 133 | { 134 | tris[gtid] = GetPrimitive(gtid, 8); 135 | } 136 | } 137 | 138 | DeferredPixelShaderOutput TerrainPixelShader(TransformedVertex input) 139 | { 140 | DeferredPixelShaderOutput output; 141 | 142 | output.normal = float4(normalize(input.normal), 1); 143 | output.motion = input.clipSpaceMotion; 144 | 145 | const float3 biomeWeights = GetBiomeWeights(input.worldSpacePosition.xz); 146 | const float biomeFactor = lerp(1.0, 0.75, biomeWeights.y); 147 | 148 | const float3 grassColor = pow(float3(0.41, 0.44, 0.29) * 2, 2.2) * 0.65 * biomeFactor * 149 | (0.75 + 0.25 * PerlinNoise2D(0.25 * input.worldSpacePosition.xz)); 150 | const float3 rockColor = float3(0.24, 0.24, 0.24); 151 | 152 | output.baseColor.rgb = lerp(grassColor, rockColor, biomeWeights.x); 153 | output.baseColor.a = 1.0; 154 | 155 | return output; 156 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/world.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | // Record for launching a grid of chunks 25 | // grid size & offset are computed based on current camera view 26 | struct ChunkGridRecord { 27 | uint2 grid : SV_DispatchGrid; 28 | int2 offset; 29 | }; 30 | 31 | float2 ComputeFarPlaneCorner(in float clipX, in float clipY) 32 | { 33 | // compute position of frustum corner on far plane 34 | const float3 cornerWorldPosition = PerspectiveProject(InverseViewProjection, float3(clipX, clipY, 1.f)); 35 | 36 | const float2 viewVector = cornerWorldPosition.xz - GetCameraPosition().xz; 37 | const float viewVectorLength = length(viewVector); 38 | // limit view vector to maximum terrain distance 39 | const float viewVectorScale = min(worldGridMaxDistance / viewVectorLength, 1.f); 40 | 41 | return GetCameraPosition().xz + viewVector * viewVectorScale; 42 | } 43 | 44 | void minmax(inout float2 minTerrainPosition, inout float2 maxTerrainPosition, in float2 position) 45 | { 46 | minTerrainPosition = min(minTerrainPosition, position); 47 | maxTerrainPosition = max(maxTerrainPosition, position); 48 | } 49 | 50 | [Shader("node")] 51 | [NodeLaunch("thread")] 52 | void World( 53 | [MaxRecords(1)] 54 | [NodeId("ChunkGrid")] 55 | NodeOutput chunkGridOutput) 56 | { 57 | // This node computes the world-space extends of the chunk grid based on the current camera view frustum 58 | 59 | // Compute bounding box of view frustum 60 | // Start with camera position 61 | float2 minTerrainPosition = GetCameraPosition().xz; 62 | float2 maxTerrainPosition = minTerrainPosition; 63 | 64 | // Add far plane corners to view frustum bounding box 65 | minmax(minTerrainPosition, maxTerrainPosition, ComputeFarPlaneCorner(-1, -1)); 66 | minmax(minTerrainPosition, maxTerrainPosition, ComputeFarPlaneCorner(-1, +1)); 67 | minmax(minTerrainPosition, maxTerrainPosition, ComputeFarPlaneCorner(+1, -1)); 68 | minmax(minTerrainPosition, maxTerrainPosition, ComputeFarPlaneCorner(+1, +1)); 69 | 70 | // Compute & round chunk coordinates 71 | const int2 minChunkPosition = floor(minTerrainPosition / chunkSize); 72 | const int2 maxChunkPosition = ceil(maxTerrainPosition / chunkSize); 73 | 74 | // Dispatch one thread group per chunk 75 | ThreadNodeOutputRecords chunkGridRecord = chunkGridOutput.GetThreadNodeOutputRecords(1); 76 | 77 | chunkGridRecord.Get().grid = clamp(maxChunkPosition - minChunkPosition, 0, 32); 78 | chunkGridRecord.Get().offset = minChunkPosition; 79 | 80 | chunkGridRecord.OutputComplete(); 81 | } 82 | 83 | int GetTerrainChunkLevelOfDetail(in int2 chunkGridPosition) 84 | { 85 | const float2 chunkWorldPosition = chunkGridPosition * chunkSize; 86 | const float3 chunkWorldCenterPosition = GetTerrainPosition(chunkWorldPosition + chunkSize * 0.5); 87 | const float distanceToCamera = distance(GetCameraPosition(), chunkWorldCenterPosition); 88 | 89 | return clamp(distanceToCamera / (3 * chunkSize), 0, 3); 90 | } 91 | 92 | [Shader("node")] 93 | [NodeLaunch("broadcasting")] 94 | [NodeMaxDispatchGrid(32, 32, 1)] 95 | // each thread corresponds to one tile 96 | [NumThreads(tilesPerChunk, tilesPerChunk, 1)] 97 | void ChunkGrid( 98 | DispatchNodeInputRecord inputRecord, 99 | 100 | int2 groupId : SV_GroupId, 101 | int2 groupThreadId : SV_GroupThreadID, 102 | 103 | [MaxRecords(1)] 104 | [NodeId("DrawTerrainChunk")] 105 | NodeOutput terrainOutput, 106 | 107 | [MaxRecords(tilesPerChunk * tilesPerChunk)] 108 | [NodeId("Tile")] 109 | [NodeArraySize(3)] 110 | NodeOutputArray tileOutput) 111 | { 112 | const ChunkGridRecord input = inputRecord.Get(); 113 | const int2 chunkGridPosition = input.offset + groupId; 114 | const float2 chunkWorldPosition = chunkGridPosition * chunkSize; 115 | 116 | const ClipPlanes clipPlanes = ComputeClipPlanes(); 117 | 118 | const AxisAlignedBoundingBox chunkBoundingBox = GetGridBoundingBox(chunkGridPosition, chunkSize, -100, 300); 119 | const bool isChunkVisible = chunkBoundingBox.IsVisible(clipPlanes); 120 | 121 | // Terrain output 122 | { 123 | const bool hasTerrainOutput = isChunkVisible; 124 | 125 | GroupNodeOutputRecords terrainOutputRecord = 126 | terrainOutput.GetGroupNodeOutputRecords(hasTerrainOutput); 127 | 128 | if (hasTerrainOutput) { 129 | const int levelOfDetail = GetTerrainChunkLevelOfDetail(chunkGridPosition); 130 | const uint dispatchSize = 8 / clamp(1U << levelOfDetail, 1, 8); 131 | 132 | terrainOutputRecord.Get().dispatchGrid = uint3(dispatchSize, dispatchSize, 1); 133 | terrainOutputRecord.Get().chunkGridPosition = chunkGridPosition; 134 | terrainOutputRecord.Get().levelOfDetail = levelOfDetail; 135 | 136 | terrainOutputRecord.Get().levelOfDetailTransition.x = 137 | GetTerrainChunkLevelOfDetail(chunkGridPosition + int2(-1, 0)) > levelOfDetail; 138 | terrainOutputRecord.Get().levelOfDetailTransition.y = 139 | GetTerrainChunkLevelOfDetail(chunkGridPosition + int2(0, -1)) > levelOfDetail; 140 | terrainOutputRecord.Get().levelOfDetailTransition.z = 141 | GetTerrainChunkLevelOfDetail(chunkGridPosition + int2(1, 0)) > levelOfDetail; 142 | terrainOutputRecord.Get().levelOfDetailTransition.w = 143 | GetTerrainChunkLevelOfDetail(chunkGridPosition + int2(0, 1)) > levelOfDetail; 144 | } 145 | 146 | terrainOutputRecord.OutputComplete(); 147 | } 148 | 149 | // Tile output 150 | if (isChunkVisible) 151 | { 152 | const int2 threadGridPosition = chunkGridPosition * tilesPerChunk + groupThreadId; 153 | const float2 threadWorldPosition = threadGridPosition * tileSize; 154 | 155 | const AxisAlignedBoundingBox tileBoundingBox = GetGridBoundingBox(threadGridPosition, tileSize, -100, 300); 156 | 157 | const bool hasTileOutput = tileBoundingBox.IsVisible(clipPlanes); 158 | 159 | // Get biome weights in center of tile 160 | const float3 biomeWeights = GetBiomeWeights(threadWorldPosition + tileSize * 0.5); 161 | 162 | // Classify biome tile to launch by dominant biome 163 | const uint biome = biomeWeights.x > biomeWeights.y ? (biomeWeights.x > biomeWeights.z ? 0 : 2) 164 | : (biomeWeights.y > biomeWeights.z ? 1 : 2); 165 | 166 | ThreadNodeOutputRecords tileOutputRecord = 167 | tileOutput[biome].GetThreadNodeOutputRecords(hasTileOutput); 168 | 169 | if (hasTileOutput) { 170 | 171 | tileOutputRecord.Get().position = chunkGridPosition * tilesPerChunk + groupThreadId; 172 | } 173 | 174 | tileOutputRecord.OutputComplete(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /meshNodeSample/samplecameracomponent.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "samplecameracomponent.h" 23 | 24 | #include "core/contentmanager.h" 25 | #include "core/framework.h" 26 | #include "core/inputmanager.h" 27 | #include "core/scene.h" 28 | 29 | MeshNodeSampleCameraComponent::MeshNodeSampleCameraComponent(cauldron::Entity* pOwner, cauldron::ComponentData* pData, cauldron::CameraComponentMgr* pManager) 30 | : CameraComponent(pOwner, pData, pManager) 31 | { 32 | m_Speed = 50.f; 33 | m_ArcBallMode = false; 34 | } 35 | 36 | void MeshNodeSampleCameraComponent::Update(double deltaTime) 37 | { 38 | using namespace cauldron; 39 | 40 | // Always update temporal information 41 | m_PrevViewMatrix = m_ViewMatrix; 42 | m_PrevViewProjectionMatrix = m_ViewProjectionMatrix; 43 | m_PrevProjJittered = m_ProjJittered; 44 | 45 | // If this camera is the currently active camera for the scene, check for input 46 | if (GetScene()->GetCurrentCamera() == this) 47 | { 48 | const InputState& inputState = GetInputManager()->GetInputState(); 49 | 50 | // Read in inputs 51 | 52 | // Scale speed with mouse wheel rotation 53 | if (inputState.GetMouseAxisDelta(Mouse_Wheel)) 54 | { 55 | m_Speed = m_Speed * ((inputState.GetMouseAxisDelta(Mouse_Wheel) > 0) ? 1.5f : (1.f / 1.5f)); 56 | // clamp speed 57 | m_Speed = std::max(m_Speed, 1.f); 58 | m_Speed = std::min(m_Speed, 200.f); 59 | } 60 | 61 | // Use right game pad stick to pitch and yaw the camera 62 | bool hasRotation = false; 63 | if (inputState.GetGamePadAxisState(Pad_RightThumbX) || inputState.GetGamePadAxisState(Pad_RightThumbY)) 64 | { 65 | // All rotations (per frame) are of 0.005 radians 66 | m_Yaw -= inputState.GetGamePadAxisState(Pad_RightThumbX) / 200.f; 67 | m_Pitch += inputState.GetGamePadAxisState(Pad_RightThumbY) / 200.f; 68 | hasRotation = true; 69 | } 70 | 71 | // Left click + mouse move == free cam look & WASDEQ movement (+ mouse wheel in/out) 72 | else if (inputState.GetMouseButtonState(Mouse_LButton)) 73 | { 74 | // All rotations (per frame) are of 0.002 radians 75 | m_Yaw -= inputState.GetMouseAxisDelta(Mouse_XAxis) / 500.f; 76 | m_Pitch += inputState.GetMouseAxisDelta(Mouse_YAxis) / 500.f; 77 | hasRotation = true; 78 | } 79 | 80 | // If hitting the 'r' key or back button on game pad, reset camera to original transform 81 | if (inputState.GetKeyState(Key_R) || inputState.GetGamePadButtonState(Pad_Back)) 82 | { 83 | ResetCamera(); 84 | UpdateMatrices(); 85 | return; 86 | } 87 | 88 | Vec4 eyePos = Vec4(m_InvViewMatrix.getTranslation(), 0.f); 89 | Vec4 polarVector = PolarToVector(m_Yaw, m_Pitch); 90 | 91 | // WASDQE == camera translation 92 | float x(0.f), y(0.f), z(0.f); 93 | x -= (inputState.GetKeyState(Key_A)) ? 1.f : 0.f; 94 | x += (inputState.GetKeyState(Key_D)) ? 1.f : 0.f; 95 | y -= (inputState.GetKeyState(Key_Q)) ? 1.f : 0.f; 96 | y += (inputState.GetKeyState(Key_E)) ? 1.f : 0.f; 97 | z -= (inputState.GetKeyState(Key_W)) ? 1.f : 0.f; 98 | z += (inputState.GetKeyState(Key_S)) ? 1.f : 0.f; 99 | 100 | // Controller input can also translate 101 | x += inputState.GetGamePadAxisState(Pad_LeftThumbX); 102 | z -= inputState.GetGamePadAxisState(Pad_LeftThumbY); 103 | y -= inputState.GetGamePadAxisState(Pad_LTrigger); 104 | y += inputState.GetGamePadAxisState(Pad_RTrigger); 105 | Vec4 movement = Vec4(x, y, z, 0.f); 106 | 107 | Mat4& transform = m_pOwner->GetTransform(); 108 | 109 | // Update from inputs 110 | if (hasRotation || dot(movement.getXYZ(), movement.getXYZ())) 111 | { 112 | // Setup new eye position 113 | eyePos = 114 | m_InvViewMatrix.getCol3() + (m_InvViewMatrix * movement * m_Speed * static_cast(deltaTime)); // InvViewMatrix is the owner's transform 115 | } 116 | 117 | // Limit maximum camera height 118 | eyePos[1] = std::min(eyePos[1], 400.f); 119 | 120 | // Update camera jitter if we need it 121 | if (CameraComponent::s_pSetJitterCallback) 122 | { 123 | s_pSetJitterCallback(m_jitterValues); 124 | m_Dirty = true; 125 | } 126 | else 127 | { 128 | // Reset jitter if disabled 129 | if (m_jitterValues.getX() != 0.f || m_jitterValues.getY() != 0.f) 130 | { 131 | m_jitterValues = Vec2(0.f, 0.f); 132 | m_Dirty = true; 133 | } 134 | } 135 | 136 | LookAt(eyePos, eyePos - 10 * polarVector); 137 | UpdateMatrices(); 138 | } 139 | } 140 | 141 | void InitCameraEntity(void*) 142 | { 143 | using namespace cauldron; 144 | 145 | ContentBlock* pContentBlock = new ContentBlock(); 146 | 147 | // Memory backing camera creation 148 | EntityDataBlock* pCameraDataBlock = new EntityDataBlock(); 149 | pContentBlock->EntityDataBlocks.push_back(pCameraDataBlock); 150 | pCameraDataBlock->pEntity = new Entity(L"MeshNodeDemoCamera"); 151 | CauldronAssert(ASSERT_CRITICAL, pCameraDataBlock->pEntity, L"Could not allocate default perspective camera entity"); 152 | 153 | // Use the same matrix setup as Cauldron 1.4 (note that Cauldron kept view-matrix native transforms, and our 154 | // entity needs the inverse of that) 155 | Mat4 transform = LookAtMatrix(Vec4(120.65f, 24.44f, -15.74f, 0.f), // eye position 156 | Vec4(120.45f, 24.44f, -14.74f, 0.f), // look-at position 157 | Vec4(0.f, 1.f, 0.f, 0.f)); // up 158 | transform = InverseMatrix(transform); 159 | pCameraDataBlock->pEntity->SetTransform(transform); 160 | 161 | // Setup default camera parameters 162 | CameraComponentData defaultPerspCameraCompData; 163 | defaultPerspCameraCompData.Name = L"MeshNodeDemoCamera"; 164 | defaultPerspCameraCompData.Perspective.AspectRatio = GetFramework()->GetAspectRatio(); 165 | defaultPerspCameraCompData.Perspective.Yfov = CAULDRON_PI2 / defaultPerspCameraCompData.Perspective.AspectRatio; 166 | defaultPerspCameraCompData.Znear = 0.5f; 167 | defaultPerspCameraCompData.Zfar = 2000.f; 168 | 169 | CameraComponentData* pCameraComponentData = new CameraComponentData(defaultPerspCameraCompData); 170 | pCameraDataBlock->ComponentsData.push_back(pCameraComponentData); 171 | MeshNodeSampleCameraComponent* pCameraComponent = 172 | new MeshNodeSampleCameraComponent(pCameraDataBlock->pEntity, pCameraComponentData, CameraComponentMgr::Get()); 173 | pCameraDataBlock->pEntity->AddComponent(pCameraComponent); 174 | 175 | pCameraDataBlock->Components.push_back(pCameraComponent); 176 | 177 | pContentBlock->ActiveCamera = pCameraDataBlock->pEntity; 178 | 179 | GetContentManager()->StartManagingContent(L"MeshNodeDemoCameraEntities", pContentBlock, false); 180 | } 181 | -------------------------------------------------------------------------------- /meshNodeSample/shaders/utils.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #define PI 3.14159265359 25 | 26 | // ======================== 27 | // Projection Utils 28 | 29 | float3 PerspectiveDivision(in float4 vec) 30 | { 31 | return vec.xyz / vec.w; 32 | } 33 | 34 | float3 PerspectiveProject(in float4x4 projectionMatrix, in float4 vec) 35 | { 36 | return PerspectiveDivision(mul(projectionMatrix, vec)); 37 | } 38 | 39 | float3 PerspectiveProject(in float4x4 projectionMatrix, in float3 vec) 40 | { 41 | return PerspectiveProject(projectionMatrix, float4(vec, 1)); 42 | } 43 | 44 | // ======================== 45 | // Bit Utils 46 | 47 | bool IsBitSet(in uint data, in int bitIndex) 48 | { 49 | return data & (1u << bitIndex); 50 | } 51 | 52 | int BitSign(in uint data, in int bitIndex) 53 | { 54 | return IsBitSet(data, bitIndex) ? 1 : -1; 55 | } 56 | 57 | // ======================== 58 | // Randon & Noise functions 59 | 60 | // Random gradient at 2D position 61 | float2 PerlinNoiseDir2D(in int2 position) 62 | { 63 | const int2 pos = position % 289; 64 | 65 | float f = 0; 66 | f = (34 * pos.x + 1); 67 | f = f * pos.x % 289 + pos.y; 68 | f = (34 * f + 1) * f % 289; 69 | f = frac(f / 43) * 2 - 1; 70 | 71 | float x = f - round(f); 72 | float y = abs(f) - 0.5; 73 | 74 | return normalize(float2(x, y)); 75 | } 76 | 77 | float PerlinNoise2D(in float2 position) 78 | { 79 | const int2 gridPositon = floor(position); 80 | const float2 gridOffset = frac(position); 81 | 82 | const float d00 = dot(PerlinNoiseDir2D(gridPositon + int2(0, 0)), gridOffset - float2(0, 0)); 83 | const float d01 = dot(PerlinNoiseDir2D(gridPositon + int2(0, 1)), gridOffset - float2(0, 1)); 84 | const float d10 = dot(PerlinNoiseDir2D(gridPositon + int2(1, 0)), gridOffset - float2(1, 0)); 85 | const float d11 = dot(PerlinNoiseDir2D(gridPositon + int2(1, 1)), gridOffset - float2(1, 1)); 86 | 87 | const float2 interpolationWeights = gridOffset * gridOffset * gridOffset * (gridOffset * (gridOffset * 6 - 15) + 10); 88 | 89 | const float d0 = lerp(d00, d01, interpolationWeights.y); 90 | const float d1 = lerp(d10, d11, interpolationWeights.y); 91 | 92 | return lerp(d0, d1, interpolationWeights.x); 93 | } 94 | 95 | uint Hash(uint seed) 96 | { 97 | seed = (seed ^ 61u) ^ (seed >> 16u); 98 | seed *= 9u; 99 | seed = seed ^ (seed >> 4u); 100 | seed *= 0x27d4eb2du; 101 | seed = seed ^ (seed >> 15u); 102 | return seed; 103 | } 104 | 105 | uint CombineSeed(uint a, uint b) 106 | { 107 | return a ^ Hash(b) + 0x9e3779b9 + (a << 6) + (a >> 2); 108 | } 109 | 110 | uint CombineSeed(uint a, uint b, uint c) 111 | { 112 | return CombineSeed(CombineSeed(a, b), c); 113 | } 114 | 115 | uint CombineSeed(uint a, uint b, uint c, uint d) 116 | { 117 | return CombineSeed(CombineSeed(a, b), c, d); 118 | } 119 | 120 | uint Hash(in float seed) 121 | { 122 | return Hash(asuint(seed)); 123 | } 124 | 125 | uint Hash(in float3 vec) 126 | { 127 | return CombineSeed(Hash(vec.x), Hash(vec.y), Hash(vec.z)); 128 | } 129 | 130 | uint Hash(in float4 vec) 131 | { 132 | return CombineSeed(Hash(vec.x), Hash(vec.y), Hash(vec.z), Hash(vec.w)); 133 | } 134 | 135 | float Random(uint seed) 136 | { 137 | return Hash(seed) / float(~0u); 138 | } 139 | 140 | float Random(uint a, uint b) 141 | { 142 | return Random(CombineSeed(a, b)); 143 | } 144 | 145 | float Random(uint a, uint b, uint c) 146 | { 147 | return Random(CombineSeed(a, b), c); 148 | } 149 | 150 | float Random(uint a, uint b, uint c, uint d) 151 | { 152 | return Random(CombineSeed(a, b), c, d); 153 | } 154 | 155 | float Random(uint a, uint b, uint c, uint d, uint e) 156 | { 157 | return Random(CombineSeed(a, b), c, d, e); 158 | } 159 | 160 | // ======================== 161 | 162 | float ToRadians(in float degrees) 163 | { 164 | return PI * (degrees / 180.0); 165 | } 166 | 167 | template 168 | T IdentityMatrix() 169 | { 170 | // float4x4 identity matrix should(TM) convert to identity matrix for smaller matrices 171 | return (T)float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 172 | } 173 | 174 | template <> 175 | float3x3 IdentityMatrix() 176 | { 177 | return float3x3(1, 0, 0, 0, 1, 0, 0, 0, 0); 178 | } 179 | 180 | template <> 181 | float4x4 IdentityMatrix() 182 | { 183 | return float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 184 | } 185 | 186 | float2 RotateAroundPoint2d(in const float2 position, in const float angle, in const float2 rotationPoint) 187 | { 188 | // Move reference position to origin 189 | const float2 p = position - rotationPoint; 190 | 191 | const float s = sin(angle); 192 | const float c = cos(angle); 193 | 194 | return float2(p.x * c - p.y * s, p.x * s + p.y * c) + rotationPoint; 195 | } 196 | 197 | // ===================================== 198 | // Bounding box & visibility test utils 199 | 200 | struct ClipPlanes { 201 | float4 planes[6]; 202 | }; 203 | 204 | float4 PlaneNormalize(in float4 plane) 205 | { 206 | const float l = length(plane.xyz); 207 | 208 | if (l > 0.0) { 209 | return plane / l; 210 | } 211 | 212 | return 0; 213 | } 214 | 215 | ClipPlanes ComputeClipPlanes(in float4x4 viewProjectionMatrix) 216 | { 217 | ClipPlanes result; 218 | 219 | result.planes[0] = PlaneNormalize(viewProjectionMatrix[3] + viewProjectionMatrix[0]); 220 | result.planes[1] = PlaneNormalize(viewProjectionMatrix[3] - viewProjectionMatrix[0]); 221 | result.planes[2] = PlaneNormalize(viewProjectionMatrix[3] + viewProjectionMatrix[1]); 222 | result.planes[3] = PlaneNormalize(viewProjectionMatrix[3] - viewProjectionMatrix[1]); 223 | result.planes[4] = PlaneNormalize(viewProjectionMatrix[3] + viewProjectionMatrix[2]); 224 | result.planes[5] = PlaneNormalize(viewProjectionMatrix[3] - viewProjectionMatrix[2]); 225 | 226 | return result; 227 | } 228 | 229 | bool IsSphereVisible(const in float3 center, const in float radius, const in float4 clipPlanes[6]) 230 | { 231 | for (int i = 0; i < 6; ++i) { 232 | if (dot(float4(center, 1), clipPlanes[i]) < -radius) { 233 | return false; 234 | } 235 | } 236 | 237 | return true; 238 | } 239 | 240 | bool IsSphereVisible(const in float3 center, const in float radius, const in ClipPlanes clipPlanes) 241 | { 242 | return IsSphereVisible(center, radius, clipPlanes.planes); 243 | } 244 | 245 | bool IsPointVisible(const in float3 position, const in float4 clipPlanes[6]) 246 | { 247 | return IsSphereVisible(position, 0, clipPlanes); 248 | } 249 | 250 | bool IsPointVisible(const in float3 position, const in ClipPlanes clipPlanes) 251 | { 252 | return IsPointVisible(position, clipPlanes.planes); 253 | } 254 | 255 | struct AxisAlignedBoundingBox { 256 | float3 min; 257 | float3 max; 258 | 259 | void Transform(const in float4x4 transform) 260 | { 261 | const float3 center = (max + min) * 0.5; 262 | const float3 extents = max - center; 263 | 264 | const float3 transformedCenter = mul(transform, float4(center, 1.0)).xyz; 265 | 266 | float3x3 absMatrix = abs((float3x3)transform); 267 | float3 transformedExtents = mul(absMatrix, extents); 268 | 269 | min = transformedCenter - transformedExtents; 270 | max = transformedCenter + transformedExtents; 271 | } 272 | 273 | bool IsVisible(const in float4 clipPlanes[6]) 274 | { 275 | for (int i = 0; i < 6; ++i) { 276 | float4 plane = clipPlanes[i]; 277 | 278 | float3 axis = float3(plane.x < 0.f ? min.x : max.x, // 279 | plane.y < 0.f ? min.y : max.y, // 280 | plane.z < 0.f ? min.z : max.z); 281 | 282 | if ((dot(plane.xyz, axis) + plane.w) < 0.0f) { 283 | return false; 284 | } 285 | } 286 | 287 | return true; 288 | } 289 | 290 | bool IsVisible(const in ClipPlanes clipPlanes) 291 | { 292 | return IsVisible(clipPlanes.planes); 293 | } 294 | 295 | bool IsVisible(const in float4x4 transform, const in float4 clipPlanes[6]) 296 | { 297 | AxisAlignedBoundingBox tmp = {min, max}; 298 | tmp.Transform(transform); 299 | 300 | return tmp.IsVisible(clipPlanes); 301 | } 302 | 303 | bool IsVisible(const in float4x4 transform, const in ClipPlanes clipPlanes) 304 | { 305 | return IsVisible(transform, clipPlanes.planes); 306 | } 307 | }; -------------------------------------------------------------------------------- /meshNodeSample/shaders/densegrassmeshshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | float3 bezier(float3 v0, float3 v1, float3 v2, float t) 25 | { 26 | float3 a = lerp(v0, v1, t); 27 | float3 b = lerp(v1, v2, t); 28 | return lerp(a, b, t); 29 | } 30 | 31 | float3 bezierDerivative(float3 v0, float3 v1, float3 v2, float t) 32 | { 33 | return 2. * (1. - t) * (v1 - v0) + 2. * t * (v2 - v1); 34 | } 35 | 36 | // The following function (MakePersistentLength) is taken from 37 | // https://github.com/klejah/ResponsiveGrassDemo/blob/6ce514717467acc80fd965a6f7695d5151ba8c03/ResponsiveGrassDemo/shader/Grass/GrassUpdateForcesShader.cs#L67 38 | // Licensed under BSD 3-Clause: 39 | // 40 | // Copyright (c) 2016, klejah 41 | // All rights reserved. 42 | // 43 | // Redistribution and use in source and binary forms, with or without 44 | // modification, are permitted provided that the following conditions are met: 45 | // 46 | // * Redistributions of source code must retain the above copyright notice, this 47 | // list of conditions and the following disclaimer. 48 | // 49 | // * Redistributions in binary form must reproduce the above copyright notice, 50 | // this list of conditions and the following disclaimer in the documentation 51 | // and/or other materials provided with the distribution. 52 | // 53 | // * Neither the name of the copyright holder nor the names of its 54 | // contributors may be used to endorse or promote products derived from 55 | // this software without specific prior written permission. 56 | void MakePersistentLength(in float3 v0, inout float3 v1, inout float3 v2, in float height) 57 | { 58 | // Persistent length 59 | float3 v01 = v1 - v0; 60 | float3 v12 = v2 - v1; 61 | float lv01 = length(v01); 62 | float lv12 = length(v12); 63 | 64 | float L1 = lv01 + lv12; 65 | float L0 = length(v2 - v0); 66 | float L = (2.0f * L0 + L1) / 3.0f; // http://steve.hollasch.net/cgindex/curves/cbezarclen.html 67 | 68 | float ldiff = height / L; 69 | v01 = v01 * ldiff; 70 | v12 = v12 * ldiff; 71 | v1 = v0 + v01; 72 | v2 = v1 + v12; 73 | } 74 | 75 | static const int denseGrassGroupSize = 128; 76 | static const int maxNumOutputVerticesLimit = 256; 77 | static const int maxNumOutputTrianglesLimit = 192; 78 | static const int numOutputVerticesLimit = 128; 79 | static const int numOutputTrianglesLimit = 96; 80 | 81 | // 4 vertices per edge; 2 edges per blade 82 | static const int numGrassBladeVerticesPerEdge = 4; 83 | static const int numGrassBladeVertices = 2 * numGrassBladeVerticesPerEdge; 84 | static const int numGrassBladeTriangles = 6; 85 | static const int maxNumGrassBlades = 86 | min(32, 87 | min(maxNumOutputVerticesLimit / numGrassBladeVertices, maxNumOutputTrianglesLimit / numGrassBladeTriangles)); 88 | static const int maxNumOutputGrassBlades = 89 | min(32, min(numOutputVerticesLimit / numGrassBladeVertices, numOutputTrianglesLimit / numGrassBladeTriangles)); 90 | static const int numOutputVertices = maxNumOutputGrassBlades * numGrassBladeVertices; 91 | static const int numOutputTriangles = maxNumOutputGrassBlades * numGrassBladeTriangles; 92 | 93 | static const int numOutputVertexIterations = (numOutputVertices + (denseGrassGroupSize - 1)) / denseGrassGroupSize; 94 | static const int numOutputTriangleIterations = (numOutputTriangles + (denseGrassGroupSize - 1)) / denseGrassGroupSize; 95 | 96 | [Shader("node")] 97 | [NodeLaunch("mesh")] 98 | [NodeId("DrawDenseGrassPatch", 0)] 99 | [NodeMaxDispatchGrid(maxDenseGrassPatchesPerRecord, 1, 1)] 100 | // This limit was set through instrumentation and is not required on AMD GPUs. 101 | // If you wish to change any of the procedural generation parameters, 102 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 103 | // You can learn more at: 104 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 105 | [NodeMaxInputRecordsPerGraphEntryRecord(400, true)] 106 | [NumThreads(denseGrassGroupSize, 1, 1)] 107 | [OutputTopology("triangle")] 108 | void DenseGrassMeshShader( 109 | uint gtid : SV_GroupThreadID, 110 | uint gid : SV_GroupID, 111 | DispatchNodeInputRecord inputRecord, 112 | out indices uint3 tris[numOutputTriangles], 113 | out vertices GrassVertex verts[numOutputVertices]) 114 | { 115 | const float3 patchCenter = inputRecord.Get().position[gid]; 116 | const float patchHeight = inputRecord.Get().height[gid]; 117 | const uint bladeOffset = inputRecord.Get().bladeOffset[gid]; 118 | const float patchWindStrength = GetWindStrength(); 119 | const float3 patchNormal = GetTerrainNormal(patchCenter.xz); 120 | const int seed = CombineSeed(asuint(int(patchCenter.x / grassSpacing)), asuint(int(patchCenter.z / grassSpacing))); 121 | 122 | const float dist = distance(patchCenter, GetCameraPosition()); 123 | const float bladeCountF = 124 | lerp(float(maxNumGrassBlades), 2., pow(saturate(dist / (denseGrassMaxDistance * 1.05)), 0.75)); 125 | 126 | const int tileBladeCount = ceil(bladeCountF); 127 | const int threadGroupBladeOffset = bladeOffset * maxNumOutputGrassBlades; 128 | const int threadGroupBladeCount = clamp(tileBladeCount - threadGroupBladeOffset, 0, maxNumOutputGrassBlades); 129 | 130 | const int vertexCount = threadGroupBladeCount * numGrassBladeVertices; 131 | const int triangleCount = threadGroupBladeCount * numGrassBladeTriangles; 132 | 133 | SetMeshOutputCounts(vertexCount, triangleCount); 134 | 135 | const int vertId = gtid; 136 | if (vertId < vertexCount) { 137 | const int bladeId = (vertId / numGrassBladeVertices) + threadGroupBladeOffset; 138 | const int vertIdLocal = vertId % numGrassBladeVertices; 139 | 140 | const float height = patchHeight + float(Random(seed, bladeId, 20)) / 40.; 141 | 142 | // Position the grass in a circle around the hitPosition and angled using the hitNormal 143 | float3 tangent = normalize(cross(float3(0, 0, 1), patchNormal)); 144 | float3 bitangent = normalize(cross(patchNormal, tangent)); 145 | 146 | float bladeDirectionAngle = 2. * PI * Random(seed, 4, bladeId); 147 | float2 bladeDirection = float2(cos(bladeDirectionAngle), sin(bladeDirectionAngle)) * height * 0.3; 148 | 149 | float offsetAngle = 2. * PI * Random(seed, bladeId); 150 | float offsetRadius = grassSpacing * sqrt(Random(seed, 19, bladeId)); 151 | float3 bladeOffset = offsetRadius * (cos(offsetAngle) * tangent + sin(offsetAngle) * bitangent); 152 | 153 | float3 v0 = patchCenter + bladeOffset; 154 | float3 v1 = v0 + float3(0, height, 0); 155 | float3 v2 = v1 + float3(bladeDirection.x, 0, bladeDirection.y); 156 | 157 | float3 v1prev = v1; 158 | float3 v2prev = v2 + patchWindStrength * GetWindOffset(v0.xz, GetPreviousTime()); 159 | 160 | v2 += patchWindStrength * GetWindOffset(v0.xz, GetTime()); 161 | 162 | MakePersistentLength(v0, v1, v2, height); 163 | MakePersistentLength(v0, v1prev, v2prev, height); 164 | 165 | float width = 0.03; 166 | 167 | width *= maxNumGrassBlades / bladeCountF; 168 | 169 | if (bladeId == (tileBladeCount - 1)) { 170 | width *= frac(bladeCountF); 171 | } 172 | 173 | GrassVertex vertex; 174 | vertex.height = patchHeight; 175 | vertex.worldSpaceGroundNormal = patchNormal; 176 | vertex.rootHeight = v0.y; 177 | 178 | const float3 sideVec = normalize(float3(bladeDirection.y, 0, -bladeDirection.x)); 179 | const float3 offset = BitSign(vertIdLocal, 0) * width * sideVec; 180 | 181 | v0 += offset * 1.0; 182 | v1 += offset * 0.7; 183 | v2 += offset * 0.3; 184 | 185 | v1prev += offset * 0.7; 186 | v2prev += offset * 0.3; 187 | 188 | float t = (vertIdLocal / 2) / float(numGrassBladeVerticesPerEdge - 1); 189 | vertex.worldSpacePosition = bezier(v0, v1, v2, t); 190 | vertex.worldSpaceNormal = cross(sideVec, normalize(bezierDerivative(v0, v1, v2, t))); 191 | 192 | ComputeClipSpacePositionAndMotion(vertex, vertex.worldSpacePosition, bezier(v0, v1prev, v2prev, t)); 193 | 194 | verts[vertId] = vertex; 195 | } 196 | 197 | const int triId = gtid; 198 | if (triId < triangleCount) { 199 | const int bladeId = triId / numGrassBladeTriangles; 200 | const int triIdLocal = triId % numGrassBladeTriangles; 201 | 202 | const int offset = bladeId * numGrassBladeVertices + 2 * (triIdLocal / 2); 203 | 204 | tris[triId] = offset + (((triIdLocal & 1) == 0) ? uint3(0, 1, 2) : uint3(3, 2, 1)); 205 | } 206 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/beemeshshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | // Static "vertex buffer" for bees 25 | static const int numBeeVertices = 11; 26 | static const float3 beePositions[numBeeVertices] = { 27 | float3(0.84, 0.0, -0.0), 28 | float3(-1.0, 0.0, -0.0), 29 | float3(-0.083, 0.722, -0.682), 30 | float3(-0.083, 0.722, 0.682), 31 | float3(1.063, 0.361, -0.275), 32 | float3(1.063, 0.361, 0.275), 33 | float3(0.353, 0.6, 0.0), 34 | float3(-0.283, 1.283, 1.415), 35 | float3(0.753, 1.228, 1.865), 36 | float3(-0.283, 1.283, -1.415), 37 | float3(0.753, 1.228, -1.865), 38 | }; 39 | // Static vertex color attributes 40 | static const float3 beeColors[numBeeVertices] = { 41 | float3(0.72, 0.56, 0.032), 42 | float3(0, 0, 0), 43 | float3(0.72, 0.56, 0.032), 44 | float3(0.72, 0.56, 0.032), 45 | float3(0, 0, 0), 46 | float3(0, 0, 0), 47 | 48 | float3(0.85, 0.85, 0.85), 49 | float3(0.85, 0.85, 0.85), 50 | float3(0.85, 0.85, 0.85), 51 | float3(0.85, 0.85, 0.85), 52 | float3(0.85, 0.85, 0.85), 53 | }; 54 | 55 | // Static "index buffer" for bees 56 | static const int numBeeTriangles = 10; 57 | static const uint3 beeTriangles[numBeeTriangles] = { 58 | uint3(1, 3, 2), 59 | uint3(4, 5, 0), 60 | uint3(9, 6, 10), 61 | uint3(2, 5, 4), 62 | uint3(7, 6, 8), 63 | uint3(5, 3, 0), 64 | uint3(3, 1, 0), 65 | uint3(4, 0, 2), 66 | uint3(2, 0, 1), 67 | uint3(3, 5, 2), 68 | }; 69 | 70 | float3 GetInsectPosition(float time) 71 | { 72 | return 1.2 * float3(PerlinNoise2D(float2(time * 0.001, 0)), 73 | PerlinNoise2D(float2(time * 0.001, 5)), 74 | PerlinNoise2D(float2(time * 0.001, 9))); 75 | } 76 | 77 | static const int beeGroupSize = 128; 78 | 79 | // customizable bee limit 80 | static const int maxNumBees = min(32, min(256 / numBeeVertices, 192 / numBeeTriangles)); 81 | static const int numOutputVertices = maxNumBees * numBeeVertices; 82 | static const int numOutputTriangles = maxNumBees * numBeeTriangles; 83 | 84 | static const int numOutputVertexIterations = (numOutputVertices + (beeGroupSize - 1)) / beeGroupSize; 85 | static const int numOutputTriangleIterations = (numOutputTriangles + (beeGroupSize - 1)) / beeGroupSize; 86 | 87 | [Shader("node")] 88 | [NodeLaunch("mesh")] 89 | [NodeId("DrawBees", 0)] 90 | [NodeMaxDispatchGrid(maxInsectsPerRecord, 1, 1)] 91 | // This limit was set through instrumentation and is not required on AMD GPUs. 92 | // If you wish to change any of the procedural generation parameters, 93 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 94 | // You can learn more at: 95 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 96 | [NodeMaxInputRecordsPerGraphEntryRecord(20, true)] 97 | [NumThreads(beeGroupSize, 1, 1)] 98 | [OutputTopology("triangle")] 99 | void BeeMeshShader( 100 | uint gtid : SV_GroupThreadID, 101 | uint gid : SV_GroupID, 102 | DispatchNodeInputRecord inputRecord, 103 | out indices uint3 tris[numOutputTriangles], 104 | out vertices InsectVertex verts[numOutputVertices]) 105 | { 106 | const int numBees = maxNumBees; 107 | const int vertexCount = numBees * numBeeVertices; 108 | const int triangleCount = numBees * numBeeTriangles; 109 | 110 | SetMeshOutputCounts(vertexCount, triangleCount); 111 | 112 | const float3 patchCenter = inputRecord.Get().position[gid]; 113 | const int seed = CombineSeed(asuint(patchCenter.x), asuint(patchCenter.z)); 114 | 115 | [[unroll]] 116 | for (int i = 0; i < numOutputTriangleIterations; ++i) 117 | { 118 | const int triId = gtid + beeGroupSize * i; 119 | 120 | if (triId < triangleCount) { 121 | const int insectId = triId / numBeeTriangles; 122 | const int insectTriangleId = triId % numBeeTriangles; 123 | 124 | tris[triId] = beeTriangles[insectTriangleId] + insectId * numBeeVertices; 125 | } 126 | } 127 | 128 | [[unroll]] 129 | for (int i = 0; i < numOutputVertexIterations; ++i) 130 | { 131 | const int vertId = gtid + beeGroupSize * i; 132 | 133 | if (vertId < vertexCount) { 134 | const int insectId = vertId / numBeeVertices; 135 | const int insectVertexId = vertId % numBeeVertices; 136 | 137 | // start time before night start 138 | const float nightStart = nightStartTime - Random(seed, 7843); 139 | // end time after night end 140 | const float nightEnd = nightEndTime + Random(seed, 732); 141 | 142 | // scale insects to 0 at night 143 | const float nightScale = max(smoothstep(nightStart - 1, nightStart, GetTimeOfDay()), 144 | 1 - smoothstep(nightEnd, nightEnd + 1, GetTimeOfDay())); 145 | // slowly scale insects to 0 in the distance 146 | // for simplicity, we omit this scaling from the motion vector, as it only affects very distant insects 147 | const float distanceScale = 148 | smoothstep(beeFadeStartDistance, beeMaxDistance, distance(patchCenter, GetCameraPosition())); 149 | 150 | const float scale = (.01 + 0.03 * Random(seed, insectId, 8)) * (1 - nightScale) * (1 - distanceScale); 151 | 152 | // radius scale for positioning insects 153 | static const float R = 0.2; 154 | const float angle = 2 * PI * Random(seed, insectId, 8); 155 | const float radius = sqrt(R * Random(seed, insectId, 98)); 156 | 157 | // compute random position offset for insect 158 | // insects will rotate around this position 159 | const float3 insectBasePosition = 160 | float3(radius * cos(angle), 0.75 + 0.5 * Random(seed, insectId, 988), radius * sin(angle)); 161 | 162 | const float timeOffset = 1e6 * Random(seed, insectId, 55); 163 | const float time = GetTime() + timeOffset; 164 | const float timePrev = GetPreviousTime() + timeOffset; 165 | 166 | // compute local insect position offsets 167 | const float3 insectPositionOffset = GetInsectPosition(time); 168 | const float3 insectPositionOffsetDelta = GetInsectPosition(time - 10); 169 | const float3 prevInsectPositionOffset = GetInsectPosition(timePrev); 170 | const float3 prevInsectPositionOffsetDelta = GetInsectPosition(timePrev - 10); 171 | 172 | const float3 insectPosition = insectBasePosition + insectPositionOffset; 173 | const float3 prevInsectPosition = insectBasePosition + prevInsectPositionOffset; 174 | 175 | // compute forward vectors for rotating insects to face movement direction 176 | const float2 forward = normalize(insectPositionOffset.xz - insectPositionOffsetDelta.xz); 177 | const float2 prevForward = normalize(prevInsectPositionOffset.xz - prevInsectPositionOffsetDelta.xz); 178 | 179 | float3 vertexPosition = beePositions[insectVertexId] * scale; 180 | float3 prevVertexPosition = vertexPosition; 181 | 182 | // rotate wing vertices around insect center 183 | if (insectVertexId > 6) { 184 | // compute wing animation angle 185 | static const float wingDownAngle = -0.15; 186 | static const float wingAmplitude = 0.4; 187 | const float phase = wingDownAngle + wingAmplitude * cos(2 * PI * frac(time * 0.005)); 188 | const float phasePrev = wingDownAngle + wingAmplitude * cos(2 * PI * frac(timePrev * 0.005)); 189 | 190 | // insect center for rotating wings 191 | static const float3 rotatePoint = beePositions[6] * scale; 192 | 193 | float wingAngle = sign(vertexPosition.z) * phase; 194 | float prevWingAngle = sign(vertexPosition.z) * phasePrev; 195 | vertexPosition.yz = RotateAroundPoint2d(vertexPosition.yz, wingAngle, rotatePoint.yz); 196 | prevVertexPosition.yz = RotateAroundPoint2d(prevVertexPosition.yz, prevWingAngle, rotatePoint.yz); 197 | } 198 | // rotate insect towards movement direction 199 | vertexPosition.xz = float2(vertexPosition.x * forward.x - vertexPosition.z * forward.y, 200 | vertexPosition.x * forward.y + vertexPosition.z * forward.x); 201 | prevVertexPosition.xz = float2(prevVertexPosition.x * prevForward.x - prevVertexPosition.z * prevForward.y, 202 | prevVertexPosition.x * prevForward.y + prevVertexPosition.z * prevForward.x); 203 | 204 | InsectVertex vertex; 205 | vertex.objectSpacePosition = insectPosition + vertexPosition; 206 | vertex.color = beeColors[insectVertexId]; 207 | 208 | ComputeClipSpacePositionAndMotion(vertex, 209 | patchCenter + vertex.objectSpacePosition, 210 | patchCenter + prevInsectPosition + prevVertexPosition); 211 | 212 | verts[vertId] = vertex; 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/skybox.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "shadingcommon.h" 25 | #include "utils.hlsl" 26 | 27 | // namespace contains skybox lookup gradients based on the lookup textures from https://kelvinvanhoorn.com/2022/03/17/skybox-tutorial-part-1/ 28 | namespace skybox { 29 | 30 | 31 | float SunZenith_Gradient_b(float x) 32 | { 33 | const int count = 5; 34 | const float xs[count] = { 0.0, 0.375, 0.515625, 0.625, 0.9921875 }; 35 | const float ys[count] = { 36 | 0.09119905696129081, 0.17743197428764693, 0.5457624421381186, 0.8458267513775317, 0.8470588235294116 }; 37 | for (int i = 0; i < (count - 1); ++i) { 38 | if (xs[i] <= x && x < xs[i + 1]) { 39 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 40 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 41 | } 42 | } 43 | return ys[count - 1]; 44 | } 45 | 46 | float SunZenith_Gradient_g(float x) 47 | { 48 | const int count = 4; 49 | const float xs[count] = { 0.0, 0.3828125, 0.6171875, 0.9921875 }; 50 | const float ys[count] = { 0.055852483484183674, 0.1324827899848155, 0.678304263482747, 0.5670582583360976 }; 51 | for (int i = 0; i < (count - 1); ++i) { 52 | if (xs[i] <= x && x < xs[i + 1]) { 53 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 54 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 55 | } 56 | } 57 | return ys[count - 1]; 58 | } 59 | 60 | float SunZenith_Gradient_r(float x) 61 | { 62 | const int count = 4; 63 | const float xs[count] = { 0.0, 0.375, 0.6171875, 0.9921875 }; 64 | const float ys[count] = { 0.05266772050977707, 0.0846283802271616, 0.37306694022972375, 0.2979585185575305 }; 65 | for (int i = 0; i < (count - 1); ++i) { 66 | if (xs[i] <= x && x < xs[i + 1]) { 67 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 68 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 69 | } 70 | } 71 | return ys[count - 1]; 72 | } 73 | 74 | float3 SunZenith_Gradient(float x) 75 | { 76 | return float3(SunZenith_Gradient_r(x), SunZenith_Gradient_g(x), SunZenith_Gradient_b(x)); 77 | } 78 | 79 | float ViewZenith_Gradient_b(float x) 80 | { 81 | const int count = 6; 82 | const float xs[count] = { 0.0, 0.375, 0.484375, 0.5390625, 0.6484375, 0.9921875 }; 83 | const float ys[count] = { 0.020563663948531673, 84 | 0.3419597608904451, 85 | 0.04161602600815216, 86 | 0.1095349428231567, 87 | 0.8149446289251109, 88 | 0.9900099167444902 }; 89 | for (int i = 0; i < (count - 1); ++i) { 90 | if (xs[i] <= x && x < xs[i + 1]) { 91 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 92 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 93 | } 94 | } 95 | return ys[count - 1]; 96 | } 97 | 98 | float ViewZenith_Gradient_g(float x) 99 | { 100 | const int count = 5; 101 | const float xs[count] = { 0.0, 0.359375, 0.53125, 0.6171875, 0.9921875 }; 102 | const float ys[count] = { 103 | 0.010815067628993518, 0.19845916353774884, 0.5742594966049783, 0.7342900528103369, 0.748949825687303 }; 104 | for (int i = 0; i < (count - 1); ++i) { 105 | if (xs[i] <= x && x < xs[i + 1]) { 106 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 107 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 108 | } 109 | } 110 | return ys[count - 1]; 111 | } 112 | 113 | float ViewZenith_Gradient_r(float x) 114 | { 115 | const int count = 6; 116 | const float xs[count] = { 0.0, 0.359375, 0.4921875, 0.59375, 0.640625, 0.9921875 }; 117 | const float ys[count] = { 0.009057957120245073, 118 | 0.15106019459324935, 119 | 0.9180293234405212, 120 | 0.47611197653854354, 121 | 0.5406124274378104, 122 | 0.500948270549165 }; 123 | for (int i = 0; i < (count - 1); ++i) { 124 | if (xs[i] <= x && x < xs[i + 1]) { 125 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 126 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 127 | } 128 | } 129 | return ys[count - 1]; 130 | } 131 | 132 | float3 ViewZenith_Gradient(float x) 133 | { 134 | return float3(ViewZenith_Gradient_r(x), ViewZenith_Gradient_g(x), ViewZenith_Gradient_b(x)); 135 | } 136 | 137 | float SunView_Gradient_b(float x) 138 | { 139 | const int count = 5; 140 | const float xs[count] = { 0.0, 0.4765625, 0.6328125, 0.984375, 0.9921875 }; 141 | const float ys[count] = { 142 | 0.0, 0.025240814536934993, 0.35683113031269376, 0.6095478205423517, 0.6113290117056305 }; 143 | for (int i = 0; i < (count - 1); ++i) { 144 | if (xs[i] <= x && x < xs[i + 1]) { 145 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 146 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 147 | } 148 | } 149 | return ys[count - 1]; 150 | } 151 | 152 | float SunView_Gradient_g(float x) 153 | { 154 | const int count = 5; 155 | const float xs[count] = { 0.0, 0.3828125, 0.515625, 0.6171875, 0.9921875 }; 156 | const float ys[count] = { 157 | 0.0038584077592145796, 0.11097681754988054, 0.7754699617187375, 0.5145669601324688, 0.6615264387746628 }; 158 | for (int i = 0; i < (count - 1); ++i) { 159 | if (xs[i] <= x && x < xs[i + 1]) { 160 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 161 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 162 | } 163 | } 164 | return ys[count - 1]; 165 | } 166 | 167 | float SunView_Gradient_r(float x) 168 | { 169 | const int count = 5; 170 | const float xs[count] = { 0.0, 0.3828125, 0.515625, 0.6328125, 0.9921875 }; 171 | const float ys[count] = { 172 | 0.005119371666456357, 0.147390195920971, 0.9702326103489829, 0.26112308728542827, 0.39095905970609257 }; 173 | for (int i = 0; i < (count - 1); ++i) { 174 | if (xs[i] <= x && x < xs[i + 1]) { 175 | float t = (x - xs[i]) / (xs[i + 1] - xs[i]); 176 | return lerp(ys[i], ys[i + 1], smoothstep(0., 1., t)); 177 | } 178 | } 179 | return ys[count - 1]; 180 | } 181 | 182 | float3 SunView_Gradient(float x) 183 | { 184 | return float3(SunView_Gradient_r(x), SunView_Gradient_g(x), SunView_Gradient_b(x)); 185 | } 186 | 187 | } // namespace skybox 188 | 189 | struct LightingData { 190 | float3 sunDirection; 191 | float3 moonDirection; 192 | 193 | float3 globalLightDirection; 194 | }; 195 | 196 | LightingData GetLightingData() 197 | { 198 | LightingData result; 199 | 200 | const float southAngle = ToRadians(0.0); 201 | const float latitude = ToRadians(0.0); 202 | const float timeOfDay = 12.0; 203 | 204 | const float3 up = float3(0, 1, 0); 205 | const float3 south = float3(cos(southAngle), 0, sin(southAngle)); 206 | 207 | const float3 right = normalize(cross(south, up)); 208 | 209 | const float sunAngle = (timeOfDay / 24.f) * 2 * PI; 210 | 211 | const float3 sunVector = -cos(sunAngle) * up + -sin(sunAngle) * right; 212 | result.sunDirection = normalize(cos(latitude) * sunVector + sin(latitude) * south); 213 | result.moonDirection = normalize(cos(latitude) * -sunVector + sin(latitude) * -south); 214 | 215 | result.globalLightDirection = (result.sunDirection.y < 0) ? result.moonDirection : result.sunDirection; 216 | 217 | return result; 218 | } 219 | 220 | // Skybox based on https://kelvinvanhoorn.com/2022/03/17/skybox-tutorial-part-1/ 221 | float3 GetSkyboxColor(in float3 direction, in LightingData lightingData) 222 | { 223 | const float sunViewDot = dot(lightingData.sunDirection, direction); 224 | const float moonViewDot = dot(lightingData.moonDirection, direction); 225 | const float sunZenithDot = lightingData.sunDirection.y; 226 | const float viewZenithDot = direction.y; 227 | const float sunMoonDot = dot(lightingData.sunDirection, lightingData.moonDirection); 228 | 229 | float sunViewDot01 = (sunViewDot + 1.0) * 0.5; 230 | float sunZenithDot01 = (sunZenithDot + 1.0) * 0.5; 231 | 232 | float3 sunZenithColor = skybox::SunZenith_Gradient(sunZenithDot01); 233 | 234 | float3 viewZenithColor = skybox::ViewZenith_Gradient(sunZenithDot01) ; 235 | float vzMask = pow(saturate(1.0 - viewZenithDot), 4); 236 | 237 | float3 sunViewColor = skybox::SunView_Gradient(sunZenithDot01) * 0.7; 238 | float svMask = pow(saturate(sunViewDot), 4); 239 | 240 | float3 skyColor = sunZenithColor + vzMask * viewZenithColor + svMask * sunViewColor; 241 | 242 | const float sunRadius = 0.05; 243 | const float sunMask = step(1 - (sunRadius * sunRadius), sunViewDot); 244 | const float sunVisible = clamp((lightingData.sunDirection.y + 5 * sunRadius) / (5 * sunRadius), 0, 1); 245 | 246 | const float3 sunColor = sunMask * sunVisible; 247 | 248 | const float moonRadius = 0.03; 249 | const float moonMask = step(1 - (moonRadius * moonRadius), moonViewDot); 250 | const float moonVisible = clamp((lightingData.moonDirection.y + 5 * moonRadius) / (5 * moonRadius), 0, 1); 251 | 252 | const float3 moonColor = moonMask * moonVisible; 253 | 254 | return skyColor + sunColor + moonColor; 255 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/mushroommeshshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | struct ShroomType { 25 | int hatPoints; 26 | int stemPoints; 27 | 28 | float hatRingInnerRadius; 29 | float hatRingOuterRadius; 30 | float stemRingRadius; 31 | 32 | float hatRingInnerDepth; 33 | float hatRingOuterDepth; 34 | 35 | float hatTilt; 36 | float shroomCountBiasPow; 37 | float positionNoise; 38 | 39 | float3 stemColor; 40 | float3 hatColor; 41 | }; 42 | 43 | // Mushroom definitions 44 | static const int numShroomTypes = 2; 45 | static const ShroomType shroomTypes[numShroomTypes] = { // 46 | { 47 | // brown shroom 48 | 7, // int hatPoints; 49 | 5, // int stemPoints; 50 | .25, // float hatRingInnerRadius; 51 | .4, // float hatRingOuterRadius; 52 | .1, // float stemRingRadius; 53 | .075, // float hatRingInnerDepth; 54 | .25, // float hatRingOuterDepth; 55 | .65, // float hatTilt; 56 | 1, // float shroomCountBiasPow; 57 | .025, // float positionNoise; 58 | float3(0.33, 0.21, 0.14) * .8, // float3 stemColor 59 | float3(0.33, 0.21, 0.14) * .8 // float3 hatColor 60 | }, 61 | { 62 | // red shroom 63 | 8, // int hatPoints; 64 | 5, // int stemPoints; 65 | .25, // float hatRingInnerRadius; 66 | .4, // float hatRingOuterRadius; 67 | .1, // float stemRingRadius; 68 | .05, // float hatRingInnerDepth; 69 | .15, // float hatRingOuterDepth; 70 | .1, // float hatTilt; 71 | 1, // float shroomCountBiasPow; 72 | .025, // float positionNoise; 73 | float3(1, 1, 1) * .6, // float3 stemColor 74 | float3(.225, 0.05, 0.) * .5 // float3 hatColor 75 | }, 76 | }; 77 | 78 | static const float radiusMin = 0.2; 79 | static const float radiusV = 0.2; 80 | static const float scale = .6; 81 | 82 | static const int mushroomGroupSize = 128; 83 | static const int numOutputVerticesLimit = 256; 84 | static const int numOutputTrianglesLimit = 192; 85 | 86 | static const int numOutputVertexIterations = (numOutputVerticesLimit + (mushroomGroupSize - 1)) / mushroomGroupSize; 87 | static const int numOutputTriangleIterations = (numOutputTrianglesLimit + (mushroomGroupSize - 1)) / mushroomGroupSize; 88 | 89 | [Shader("node")] 90 | [NodeLaunch("mesh")] 91 | [NodeId("DrawMushroomPatch", 0)] 92 | [NodeMaxDispatchGrid(maxMushroomsPerRecord, 1, 1)] 93 | // This limit was set through instrumentation and is not required on AMD GPUs. 94 | // If you wish to change any of the procedural generation parameters, 95 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 96 | // You can learn more at: 97 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 98 | [NodeMaxInputRecordsPerGraphEntryRecord(50, true)] 99 | [NumThreads(mushroomGroupSize, 1, 1)] 100 | [OutputTopology("triangle")] 101 | void MushroomMeshShader( 102 | uint gtid : SV_GroupThreadID, 103 | uint gid : SV_GroupID, 104 | DispatchNodeInputRecord inputRecord, 105 | out indices uint3 tris[numOutputTrianglesLimit], 106 | out vertices InsectVertex verts[numOutputVerticesLimit]) 107 | { 108 | const float3 patchCenter = inputRecord.Get().position[gid]; 109 | 110 | const int seed = CombineSeed(asuint(patchCenter.x), asuint(patchCenter.z)); 111 | 112 | const int shroomType = numShroomTypes * Random(seed, 11002); 113 | 114 | const int vertsPerStem = 2 * shroomTypes[shroomType].stemPoints; 115 | const int vertsPerHat = 2 * shroomTypes[shroomType].hatPoints + 1; 116 | const int vertsPerShroom = vertsPerStem + vertsPerHat; 117 | 118 | const int trisPerStem = 2 * shroomTypes[shroomType].stemPoints; 119 | const int trisPerHat = 3 * shroomTypes[shroomType].hatPoints; 120 | const int trisPerShroom = trisPerStem + trisPerHat; 121 | 122 | const int maxNumShrooms = min(numOutputVerticesLimit / vertsPerShroom, numOutputTrianglesLimit / trisPerShroom); 123 | 124 | const float radiusLookup[5] = { 125 | shroomTypes[shroomType].stemRingRadius, 126 | shroomTypes[shroomType].stemRingRadius, 127 | 0., 128 | shroomTypes[shroomType].hatRingInnerRadius, 129 | shroomTypes[shroomType].hatRingOuterRadius, 130 | }; 131 | const float heightLookup[5] = { 132 | 0., 133 | 0.1, 134 | .2, 135 | .2 - shroomTypes[shroomType].hatRingInnerDepth, 136 | .2 - shroomTypes[shroomType].hatRingOuterDepth, 137 | }; 138 | 139 | 140 | const int numShrooms = 141 | min(maxNumShrooms, 1 + maxNumShrooms * pow(Random(seed, 99990001), shroomTypes[shroomType].shroomCountBiasPow)); 142 | 143 | const int vertexCount = numShrooms * vertsPerShroom; 144 | const int triangleCount = numShrooms * trisPerShroom; 145 | 146 | SetMeshOutputCounts(vertexCount, triangleCount); 147 | 148 | [[unroll]] 149 | for (int i = 0; i < numOutputTriangleIterations; ++i) 150 | { 151 | const int triId = gtid + mushroomGroupSize * i; 152 | 153 | if (triId < triangleCount) { 154 | const int shroomIdx = triId / trisPerShroom; 155 | int ti = triId % trisPerShroom; 156 | 157 | const bool isHat = ti < trisPerHat; 158 | 159 | if (!isHat) { 160 | ti -= trisPerHat; 161 | } 162 | 163 | const int points = isHat ? shroomTypes[shroomType].hatPoints : shroomTypes[shroomType].stemPoints; 164 | const int ring = ti / points; 165 | 166 | const int baseVertex = isHat ? (1 + (ring == 2) * points) : (vertsPerHat + ring * points); 167 | 168 | const int vi = ti - ring * points; 169 | 170 | const int a = baseVertex + vi; 171 | const int b = baseVertex + ((vi + 1) % points); 172 | int c = (ring == 1) ? b : a; 173 | c += ((ring == 1) ^ !isHat) ? points : -points; 174 | c = max(c, 0); 175 | 176 | tris[triId] = shroomIdx * vertsPerShroom + uint3(a, b, c); 177 | } 178 | } 179 | 180 | [[unroll]] 181 | for (int i = 0; i < numOutputVertexIterations; ++i) 182 | { 183 | const int vertId = gtid + mushroomGroupSize * i; 184 | 185 | if (vertId < vertexCount) { 186 | const int shroomIdx = vertId / vertsPerShroom; 187 | int vi = vertId % vertsPerShroom; 188 | float3 pos = float3(0, 0, 0); 189 | bool isHat = vi < vertsPerHat; 190 | 191 | vi -= (!isHat) * vertsPerHat; 192 | 193 | int points = isHat ? shroomTypes[shroomType].hatPoints : shroomTypes[shroomType].stemPoints; 194 | 195 | int ring = (vi + (shroomTypes[shroomType].hatPoints - 1) * isHat) / points; 196 | 197 | float angle = frac((vi - isHat) / float(points)); 198 | 199 | angle += (isHat && ring == 2) * -1. / (2 * points); 200 | 201 | angle *= 2 * PI; 202 | 203 | float radius = radiusLookup[2 * isHat + ring]; 204 | 205 | pos.x += radius * cos(angle); 206 | pos.y += heightLookup[2 * isHat + ring]; 207 | pos.z += radius * sin(angle); 208 | 209 | float theta = 2 * PI * shroomIdx / numShrooms + .1 * Random(seed, shroomIdx) * PI * 2; 210 | float rad = sqrt(Random(seed, 19, shroomIdx)) * shroomTypes[shroomType].hatTilt; 211 | 212 | float3 offset = float3(sin(theta), 0, cos(theta)); 213 | float3 dir = rad * offset; 214 | 215 | dir.y = sqrt(1 - dir.x * dir.x - dir.z * dir.z); 216 | 217 | float3x3 rotation; 218 | rotation[0] = normalize(cross(dir, float3(0, 0, 1))); 219 | rotation[1] = dir; 220 | rotation[2] = normalize(cross(rotation[0], dir)); 221 | rotation[0] = normalize(cross(dir, rotation[2])); 222 | 223 | if (isHat) { 224 | // add a little bit of noise 225 | pos.x += shroomTypes[shroomType].positionNoise * (2. * Random(vertId, 0xFEFA, seed) - 1.); 226 | pos.y += shroomTypes[shroomType].positionNoise * (2. * Random(vertId, 0xFEFB, seed) - 1.); 227 | pos.z += shroomTypes[shroomType].positionNoise * (2. * Random(vertId, 0xFEFC, seed) - 1.); 228 | 229 | pos = mul(transpose(rotation), pos); 230 | } 231 | 232 | float r = Random(seed, 877, shroomIdx); 233 | 234 | float distance = radiusMin + radiusV * r; 235 | 236 | static const float heightMin = .5; 237 | static const float heightV = 1.; 238 | if (isHat || ring == 1) { 239 | pos.y += heightMin + heightV * (1 - r); 240 | } 241 | 242 | InsectVertex vertex; 243 | vertex.color.rgb = isHat ? shroomTypes[shroomType].hatColor : shroomTypes[shroomType].stemColor; 244 | if (!isHat && ring == 0) { 245 | vertex.color.xyz *= .1; 246 | } 247 | vertex.color.rgb *= 3.5; 248 | 249 | vertex.objectSpacePosition = scale * pos + distance * offset; 250 | 251 | ComputeClipSpacePositionAndMotion(vertex, patchCenter + vertex.objectSpacePosition); 252 | 253 | verts[vertId] = vertex; 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/butterflymeshshader.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | // Static "vertex buffer" for butterflies 25 | static const int numButterflyVertices = 16; 26 | static const float3 butterflyPositions[numButterflyVertices] = { 27 | float3(-0.548, 0.0, -0.0), 28 | float3(-0.41, 0.0, 0.226), 29 | float3(-0.41, -0.0, -0.226), 30 | float3(-0.948, 0.239, 0.528), 31 | float3(-1.048, 0.238, 0.468), 32 | float3(-0.948, 0.239, -0.528), 33 | float3(-1.048, 0.238, -0.468), 34 | float3(0.747, 0.0, 0.125), 35 | float3(0.747, -0.0, -0.125), 36 | float3(-0.194, 0.139, -0.0), 37 | float3(0.384, -0.046, -0.0), 38 | float3(-0.297, 0.092, -0.0), 39 | float3(-0.651, 0.324, 2.446), 40 | float3(1.621, -0.0, 0.785), 41 | float3(-0.651, 0.324, -2.446), 42 | float3(1.621, -0.0, -0.785), 43 | }; 44 | // Static vertex color attributes 45 | static const float3 butterflyColors[numButterflyVertices] = { 46 | float3(0., 0., 0.), 47 | float3(0., 0., 0.), 48 | float3(0., 0., 0.), 49 | float3(0., 0., 0.), 50 | float3(0., 0., 0.), 51 | float3(0., 0., 0.), 52 | float3(0., 0., 0.), 53 | float3(0., 0., 0.), 54 | float3(0., 0., 0.), 55 | float3(0., 0., 0.), 56 | float3(0., 0., 0.), 57 | float3(0., 0., 0.), 58 | float3(0.2, 0.2, .7), 59 | float3(0.2, 0.2, .7), 60 | float3(0.2, 0.2, .7), 61 | float3(0.2, 0.2, .7) 62 | }; 63 | 64 | // Static "index buffer" for butterflies 65 | static const int numButterflyTriangles = 14; 66 | static const uint3 butterflyTriangles[numButterflyTriangles] = { 67 | uint3(1, 10, 0), 68 | uint3(3, 4, 0), 69 | uint3(5, 0, 6), 70 | uint3(10, 2, 0), 71 | uint3(7, 1, 9), 72 | uint3(1, 0, 9), 73 | uint3(2, 9, 0), 74 | uint3(2, 8, 9), 75 | uint3(7, 9, 8), 76 | uint3(7, 10, 1), 77 | uint3(7, 8, 10), 78 | uint3(2, 10, 8), 79 | uint3(12, 13, 11), 80 | uint3(11, 14, 15), 81 | }; 82 | 83 | float3 GetInsectPosition(float time) 84 | { 85 | return 4 * float3(PerlinNoise2D(float2(time * 0.001, 0)), 86 | PerlinNoise2D(float2(time * 0.001, 5)), 87 | PerlinNoise2D(float2(time * 0.001, 9))); 88 | } 89 | 90 | static const int butterflyGroupSize = 128; 91 | 92 | // customizable butterfly limit 93 | static const int maxNumButterflies = min(32, min(256 / numButterflyVertices, 192 / numButterflyTriangles)); 94 | static const int numOutputVertices = maxNumButterflies * numButterflyVertices; 95 | static const int numOutputTriangles = maxNumButterflies * numButterflyTriangles; 96 | 97 | static const int numOutputVertexIterations = (numOutputVertices + (butterflyGroupSize - 1)) / butterflyGroupSize; 98 | static const int numOutputTriangleIterations = (numOutputTriangles + (butterflyGroupSize - 1)) / butterflyGroupSize; 99 | 100 | [Shader("node")] 101 | [NodeLaunch("mesh")] 102 | [NodeId("DrawButterflies", 0)] 103 | [NodeMaxDispatchGrid(maxInsectsPerRecord, 1, 1)] 104 | // This limit was set through instrumentation and is not required on AMD GPUs. 105 | // If you wish to change any of the procedural generation parameters, 106 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 107 | // You can learn more at: 108 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 109 | [NodeMaxInputRecordsPerGraphEntryRecord(10, true)] 110 | [NumThreads(butterflyGroupSize, 1, 1)] 111 | [OutputTopology("triangle")] 112 | void ButterflyMeshShader( 113 | uint gtid : SV_GroupThreadID, 114 | uint gid : SV_GroupID, 115 | DispatchNodeInputRecord inputRecord, 116 | out indices uint3 tris[numOutputTriangles], 117 | out vertices InsectVertex verts[numOutputVertices]) 118 | { 119 | const int numButterflies = maxNumButterflies; 120 | const int vertexCount = numButterflies * numButterflyVertices; 121 | const int triangleCount = numButterflies * numButterflyTriangles; 122 | 123 | SetMeshOutputCounts(vertexCount, triangleCount); 124 | 125 | const float3 patchCenter = inputRecord.Get().position[gid]; 126 | const int seed = CombineSeed(asuint(patchCenter.x), asuint(patchCenter.z)); 127 | 128 | [[unroll]] 129 | for (int i = 0; i < numOutputTriangleIterations; ++i) 130 | { 131 | const int triId = gtid + butterflyGroupSize * i; 132 | 133 | if (triId < triangleCount) { 134 | const int insectId = triId / numButterflyTriangles; 135 | const int insectTriangleId = triId % numButterflyTriangles; 136 | 137 | tris[triId] = butterflyTriangles[insectTriangleId] + insectId * numButterflyVertices; 138 | } 139 | } 140 | 141 | [[unroll]] 142 | for (int i = 0; i < numOutputVertexIterations; ++i) 143 | { 144 | const int vertId = gtid + butterflyGroupSize * i; 145 | 146 | if (vertId < vertexCount) { 147 | const int insectId = vertId / numButterflyVertices; 148 | const int insectVertexId = vertId % numButterflyVertices; 149 | 150 | // start time before night start 151 | const float nightStart = nightStartTime - Random(seed, 4561); 152 | // end time after night end 153 | const float nightEnd = nightEndTime + Random(seed, 6456); 154 | 155 | // scale insects to 0 at night 156 | const float nightScale = max(smoothstep(nightStart - 1, nightStart, GetTimeOfDay()), 157 | 1 - smoothstep(nightEnd, nightEnd + 1, GetTimeOfDay())); 158 | // slowly scale insects to 0 in the distance 159 | // for simplicity, we omit this scaling from the motion vector, as it only affects very distant insects 160 | const float distanceScale = smoothstep( 161 | butterflyFadeStartDistance, butterflyMaxDistance, distance(patchCenter, GetCameraPosition())); 162 | 163 | const float scale = (.01 + 0.03 * Random(seed, insectId, 8)) * (1 - nightScale) * (1 - distanceScale); 164 | 165 | // radius scale for positioning insects 166 | static const float R = 2; 167 | const float angle = 2 * PI * Random(seed, insectId, 8); 168 | const float radius = sqrt(R * Random(seed, insectId, 98)); 169 | 170 | // compute random position offset for insect 171 | // insects will rotate around this position 172 | const float3 insectBasePosition = 173 | float3(radius * cos(angle), 0.75 + 0.5 * Random(seed, insectId, 988), radius * sin(angle)); 174 | 175 | const float timeOffset = 1e6 * Random(seed, insectId, 55); 176 | const float time = GetTime() + timeOffset; 177 | const float timePrev = GetPreviousTime() + timeOffset; 178 | 179 | // compute local insect position offsets 180 | const float3 insectPositionOffset = GetInsectPosition(time * .25); 181 | const float3 insectPositionOffsetDelta = GetInsectPosition((time - 10) * .25); 182 | const float3 prevInsectPositionOffset = GetInsectPosition(timePrev * .25); 183 | const float3 prevInsectPositionOffsetDelta = GetInsectPosition((timePrev - 10) * .25); 184 | 185 | const float3 insectPosition = insectBasePosition + insectPositionOffset; 186 | const float3 prevInsectPosition = insectBasePosition + prevInsectPositionOffset; 187 | 188 | // compute forward vectors for rotating insects to face movement direction 189 | const float2 forward = -normalize(insectPositionOffset.xz - insectPositionOffsetDelta.xz); 190 | const float2 prevForward = -normalize(prevInsectPositionOffset.xz - prevInsectPositionOffsetDelta.xz); 191 | 192 | float3 vertexPosition = butterflyPositions[insectVertexId] * scale; 193 | float3 prevVertexPosition = vertexPosition; 194 | 195 | // rotate wing vertices around insect center 196 | if (insectVertexId >= 12) { 197 | // compute wing animation angle 198 | static const float wingDownAngle = -0.15; 199 | static const float wingAmplitude = 0.6; 200 | const float phase = wingDownAngle + wingAmplitude * cos(2 * PI * frac(time * 0.005)); 201 | const float phasePrev = wingDownAngle + wingAmplitude * cos(2 * PI * frac(timePrev * 0.005)); 202 | 203 | // insect center for rotating wings 204 | static const float3 rotatePoint = float3(0.353, 0.6, 0.0) * scale; 205 | 206 | float wingAngle = sign(vertexPosition.z) * phase; 207 | float prevWingAngle = sign(vertexPosition.z) * phasePrev; 208 | vertexPosition.yz = RotateAroundPoint2d(vertexPosition.yz, wingAngle, rotatePoint.yz); 209 | prevVertexPosition.yz = RotateAroundPoint2d(prevVertexPosition.yz, prevWingAngle, rotatePoint.yz); 210 | } 211 | // rotate insect towards movement direction 212 | vertexPosition.xz = float2(vertexPosition.x * forward.x - vertexPosition.z * forward.y, 213 | vertexPosition.x * forward.y + vertexPosition.z * forward.x); 214 | prevVertexPosition.xz = float2(prevVertexPosition.x * prevForward.x - prevVertexPosition.z * prevForward.y, 215 | prevVertexPosition.x * prevForward.y + prevVertexPosition.z * prevForward.x); 216 | 217 | InsectVertex vertex; 218 | vertex.objectSpacePosition = insectPosition + vertexPosition; 219 | 220 | if (insectVertexId >= 12) { 221 | // compute random wing color 222 | vertex.color = 0.95 * normalize(float3(Random(insectId, seed, 'r'), // 223 | Random(insectId, seed, 'g'), // 224 | Random(insectId, seed, 'b'))); 225 | } else { 226 | vertex.color = butterflyColors[insectVertexId]; 227 | } 228 | 229 | ComputeClipSpacePositionAndMotion(vertex, 230 | patchCenter + vertex.objectSpacePosition, 231 | patchCenter + prevInsectPosition + prevVertexPosition); 232 | 233 | verts[vertId] = vertex; 234 | } 235 | } 236 | } -------------------------------------------------------------------------------- /meshNodeSample/shaders/common.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #pragma once 23 | 24 | #include "workgraphcommon.h" 25 | #include "utils.hlsl" 26 | #include "heightmap.hlsl" 27 | 28 | // ================== 29 | // Constants 30 | 31 | // World grid definitions 32 | // DO NOT CHANGE THESE! 33 | static const uint grassPatchesPerDetailedTile = 16; 34 | static const uint detailedTilesPerTile = 8; 35 | static const uint tilesPerChunk = 8; 36 | 37 | // World grid sizes are defined by grass blade spacing 38 | static const float grassSpacing = 0.25; 39 | static const float detailedTileSize = grassPatchesPerDetailedTile * grassSpacing; 40 | static const float tileSize = detailedTilesPerTile * detailedTileSize; 41 | static const float chunkSize = tilesPerChunk * tileSize; 42 | 43 | // Distance limits for procedural generation 44 | static const float worldGridMaxDistance = 2000.f; 45 | 46 | static const float denseGrassMaxDistance = 80.f; 47 | static const float sparseGrassMaxDistance = 250.f; 48 | 49 | static const float flowerMaxDistance = 300.f; 50 | static const float flowerSparseStartDistance = 100.f; 51 | 52 | static const float mushroomMaxDistance = denseGrassMaxDistance; 53 | 54 | static const float butterflyMaxDistance = 25.f; 55 | static const float butterflyFadeStartDistance = 20.f; 56 | static const float beeMaxDistance = 40.f; 57 | static const float beeFadeStartDistance = 30.f; 58 | 59 | // Night time definition: start at 18:00 till 6:00 60 | // Bees and butterflies won't be rendered/generated at night 61 | static const float nightStartTime = 18.f; 62 | static const float nightEndTime = 6.f; 63 | 64 | // Radius of curved world 65 | static const float earthRadius = 6000.f; 66 | 67 | // =================================== 68 | // Record structs for work graph nodes 69 | 70 | // Record for each tile in a chunk & detailed tile in a tile 71 | struct TileRecord { 72 | int2 position; 73 | }; 74 | 75 | // Record for drawing terrain segments inside a chunk 76 | struct DrawTerrainChunkRecord { 77 | uint3 dispatchGrid : SV_DispatchGrid; 78 | int2 chunkGridPosition; 79 | int levelOfDetail; 80 | // indicated if neighboring terrain tiles have higher LOD 81 | // x = (-1, 0) 82 | // y = (0, -1) 83 | // z = (1, 0) 84 | // w = (0, 1) 85 | bool4 levelOfDetailTransition; 86 | }; 87 | 88 | struct GenerateTreeRecord { 89 | float2 position; 90 | }; 91 | 92 | static const uint maxSplinesPerRecord = 32; 93 | static const uint splineMaxControlPointCount = 8; 94 | 95 | // Record for drawing multiple splines. Each spline is defined as a series of control points. 96 | // Each control point defines a vertex ring with varying radius and vertex count. 97 | struct DrawSplineRecord { 98 | uint3 dispatchGrid : SV_DispatchGrid; 99 | float3 color[maxSplinesPerRecord]; 100 | float rotationOffset[maxSplinesPerRecord]; 101 | // x is overall wind strength, y is blending factor for individual vertices 102 | float2 windStrength[maxSplinesPerRecord]; 103 | uint controlPointCount[maxSplinesPerRecord]; 104 | float3 controlPointPositions[maxSplinesPerRecord * splineMaxControlPointCount]; 105 | uint controlPointVertexCounts[maxSplinesPerRecord * splineMaxControlPointCount]; 106 | float2 controlPointRadii[maxSplinesPerRecord * splineMaxControlPointCount]; 107 | float controlPointNoiseAmplitudes[maxSplinesPerRecord * splineMaxControlPointCount]; 108 | }; 109 | 110 | // Each thread in a biome tile can generate one insects 111 | static const uint maxInsectsPerRecord = detailedTilesPerTile * detailedTilesPerTile; 112 | 113 | // Record for insects 114 | // Used by 115 | // - DrawBees 116 | // - DrawButterflies 117 | struct DrawInsectRecord { 118 | uint3 dispatchGrid : SV_DispatchGrid; 119 | float3 position[maxInsectsPerRecord]; 120 | }; 121 | 122 | // Each thread in a biome tile can generate up to 3 mushrooms 123 | static const uint maxMushroomsPerDetailedTile = 3; 124 | static const uint maxMushroomsPerRecord = detailedTilesPerTile * detailedTilesPerTile * maxMushroomsPerDetailedTile; 125 | 126 | // Record for mushrooms 127 | // Used by 128 | // - DrawMushroomPatch 129 | struct DrawMushroomRecord { 130 | uint3 dispatchGrid : SV_DispatchGrid; 131 | float3 position[maxMushroomsPerRecord]; 132 | }; 133 | 134 | // Each thread in a biome tile can generate up to 12 flowers 135 | static const int maxFlowersPerDetailedTile = 12; 136 | static const int maxFlowersPerRecord = (detailedTilesPerTile * detailedTilesPerTile) * maxFlowersPerDetailedTile; 137 | // scaling factor for dispatch grid size when using sparse flowers 138 | static const int flowersInSparseFlowerThreadGroup = 5; 139 | 140 | // Record for flowers 141 | // Used by 142 | // - DrawFlowerPatch 143 | struct DrawFlowerRecord { 144 | uint3 dispatchGrid : SV_DispatchGrid; 145 | uint flowerPatchCount; 146 | float2 position[maxFlowersPerRecord]; 147 | }; 148 | 149 | // Each thread in a detailed tile corresponds to one or two dense grass patches 150 | static const uint maxDenseGrassPatchesPerRecord = 2 * grassPatchesPerDetailedTile * grassPatchesPerDetailedTile; 151 | 152 | // Record for dense grass 153 | // Used by 154 | // - DrawDenseGrassPatch 155 | struct DrawDenseGrassRecord { 156 | uint3 dispatchGrid : SV_DispatchGrid; 157 | float3 position[maxDenseGrassPatchesPerRecord]; 158 | float height[maxDenseGrassPatchesPerRecord]; 159 | uint bladeOffset[maxDenseGrassPatchesPerRecord]; 160 | }; 161 | 162 | // Each thread in a biome tile corresponds to a sparse grass patch 163 | static const uint maxSparseGrassPatchesPerRecord = detailedTilesPerTile * detailedTilesPerTile; 164 | // Number of sparse grass mesh shader thread groups needed to render one detailed tile 165 | static const uint sparseGrassThreadGroupsPerRecord = 8; 166 | 167 | // record for DrawSparseGrassPatch 168 | struct DrawSparseGrassRecord { 169 | uint3 dispatchGrid : SV_DispatchGrid; 170 | int2 position[maxSparseGrassPatchesPerRecord]; 171 | }; 172 | 173 | // ===================================== 174 | // Common MS & PS input & output structs 175 | 176 | // Vertex definition for InsectPixelShader 177 | // Used by 178 | // - BeeMeshShader 179 | // - ButterflyMeshShader 180 | // - FlowerMeshShader 181 | // - MushroomMeshShader 182 | struct InsectVertex { 183 | float4 clipSpacePosition : SV_POSITION; 184 | // Vertex position in object space for computing normals 185 | // object to world space transforms are always only translations 186 | // thus we can safely compute normals in object space 187 | float3 objectSpacePosition : POSITION0; 188 | float2 clipSpaceMotion : TEXCOORD0; 189 | float3 color : NORMAL0; 190 | }; 191 | 192 | // Vertex definition for GrassPixelShader 193 | // Used by 194 | // - DenseGrassMeshShader 195 | // - SparseGrassMeshShader 196 | struct GrassVertex { 197 | float4 clipSpacePosition : SV_POSITION; 198 | float3 worldSpacePosition : POSITION0; 199 | float2 clipSpaceMotion : TEXCOORD0; 200 | float3 worldSpaceNormal : NORMAL0; 201 | float3 worldSpaceGroundNormal : NORMAL1; 202 | float rootHeight : BLENDWEIGHT0; 203 | float height : BLENDWEIGHT1; 204 | }; 205 | 206 | // Primitive definition for mesh shaders 207 | // Used by 208 | // - SparseGrassMeshShader 209 | struct GrassCullPrimitive { 210 | bool cull : SV_CullPrimitive; 211 | }; 212 | 213 | // Output struct for deferred pixel shaders 214 | struct DeferredPixelShaderOutput { 215 | float4 baseColor : SV_Target0; 216 | float4 normal : SV_Target1; 217 | float2 motion : SV_Target2; 218 | }; 219 | 220 | // ====================================================== 221 | // Common functions for accessing data in constant buffer 222 | 223 | float GetTimeOfDay() 224 | { 225 | return 12; 226 | } 227 | 228 | float3 GetCameraPosition() 229 | { 230 | return CameraPosition.xyz; 231 | } 232 | 233 | float3 GetPreviousCameraPosition() 234 | { 235 | return PreviousCameraPosition.xyz; 236 | } 237 | 238 | ClipPlanes ComputeClipPlanes() 239 | { 240 | return ComputeClipPlanes(ViewProjection); 241 | } 242 | 243 | uint GetTime() 244 | { 245 | return ShaderTime; 246 | } 247 | 248 | uint GetPreviousTime() 249 | { 250 | return PreviousShaderTime; 251 | } 252 | 253 | float GetWindStrength() 254 | { 255 | return WindStrength; 256 | } 257 | 258 | // Rotation of wind direction around y-Axis; 0 = float3(1, 0, 0); 259 | float GetWindDirection() 260 | { 261 | return WindDirection; 262 | } 263 | 264 | // ===================================================== 265 | // Common functions for grass placement & wind animation 266 | 267 | float2 GetGrassOffset(in const int2 grid) 268 | { 269 | float theta = 2. * PI * Random(grid.x, grid.y, 1337); 270 | float radius = sqrt(Random(grid.x, 19, grid.y)); 271 | static const float patchCenterVariance = 0.4; 272 | 273 | return patchCenterVariance * radius * float2(cos(theta), sin(theta)); 274 | } 275 | 276 | // Returns 2D wind offset as 3D vector for convenience 277 | float3 GetWindOffset(in const float2 pos, in const float time) 278 | { 279 | float posOnSineWave = cos(GetWindDirection()) * pos.x - sin(GetWindDirection()) * pos.y; 280 | 281 | float t = 0.007 * time + posOnSineWave + 4 * PerlinNoise2D(0.1 * pos); 282 | float windx = 2 * sin(.5 * t); 283 | float windz = 1 * sin(1. * t); 284 | 285 | return 0.04 * float3(windx, 0, windz); 286 | } 287 | 288 | // ================================================== 289 | // Common functions for curved world & motion vectors 290 | 291 | // Computes position on curved world relative to current camera position 292 | float3 GetCurvedWorldSpacePosition(in float3 worldSpacePosition, in bool previousCenter = false) 293 | { 294 | const float2 center = previousCenter ? GetPreviousCameraPosition().xz : GetCameraPosition().xz; 295 | const float2 centerToPos = worldSpacePosition.xz - center; 296 | const float distanceToCenter = length(centerToPos); 297 | const float2 direction = centerToPos / distanceToCenter; 298 | 299 | const float alpha = distanceToCenter / earthRadius; 300 | const float s = sin(alpha); 301 | const float c = cos(alpha); 302 | 303 | const float3 curvedPosUp = normalize(float3(direction.x * s, c, direction.y * s)); 304 | const float3 centerToCurvedPos = 305 | float3(direction.x * s * earthRadius, (c * earthRadius) - earthRadius, direction.y * s * earthRadius); 306 | 307 | const float heightScale = smoothstep(2000, 1000, distanceToCenter); 308 | 309 | return float3(center.x, 0, center.y) + centerToCurvedPos + // base postion 310 | curvedPosUp * worldSpacePosition.y * heightScale; // add rotated y component 311 | } 312 | 313 | // Computes bounding box for a grid element (e.g. chunk) on curved world 314 | AxisAlignedBoundingBox GetGridBoundingBox(in int2 gridPosition, 315 | in float elementSize, 316 | in float minHeight, 317 | in float maxHeight) 318 | { 319 | AxisAlignedBoundingBox result; 320 | 321 | const float3 minWorldPosition = float3(gridPosition.x, 0, gridPosition.y) * elementSize; 322 | const float3 maxWorldPosition = float3(gridPosition.x + 1, 0, gridPosition.y + 1) * elementSize; 323 | 324 | result.min = GetCurvedWorldSpacePosition(minWorldPosition) + float3(0, minHeight, 0); 325 | result.max = GetCurvedWorldSpacePosition(maxWorldPosition) + float3(0, maxHeight, 0); 326 | 327 | return result; 328 | } 329 | 330 | // Computes position on curved world, projects it into clip space & assigns it to vertex.clipSpacePosition 331 | // Computes motion vector and assigns it to vertex.clipSpaceMotion 332 | template 333 | void ComputeClipSpacePositionAndMotion(inout T vertex, 334 | in float3 worldSpacePosition, 335 | in float3 previousWorldSpacePosition) 336 | { 337 | // project vertex onto curved world 338 | const float3 curvedWorldSpacePosition = GetCurvedWorldSpacePosition(worldSpacePosition); 339 | const float3 previousCurvedWorldSpacePosition = GetCurvedWorldSpacePosition(previousWorldSpacePosition, true); 340 | 341 | vertex.clipSpacePosition = mul(ViewProjection, float4(curvedWorldSpacePosition, 1)); 342 | 343 | const float4 previousClipSpacePosition = 344 | mul(PreviousViewProjection, float4(previousCurvedWorldSpacePosition, 1)); 345 | vertex.clipSpaceMotion = (previousClipSpacePosition.xy / previousClipSpacePosition.w) - 346 | (vertex.clipSpacePosition.xy / vertex.clipSpacePosition.w); 347 | } 348 | 349 | // Computes position on curved world, projects it into clip space & assigns it to vertex.clipSpacePosition 350 | // Computes motion vector and assigns it to vertex.clipSpaceMotion 351 | template 352 | void ComputeClipSpacePositionAndMotion(inout T vertex, in float3 worldSpacePosition) 353 | { 354 | ComputeClipSpacePositionAndMotion(vertex, worldSpacePosition, worldSpacePosition); 355 | } -------------------------------------------------------------------------------- /meshNodeSample/fsr2rendermodule.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "fsr2rendermodule.h" 23 | 24 | #include 25 | 26 | #include 27 | 28 | #include "core/scene.h" 29 | #include "render/dynamicresourcepool.h" 30 | #include "render/profiler.h" 31 | #include "render/rasterview.h" 32 | #include "render/uploadheap.h" 33 | #include "validation_remap.h" 34 | 35 | using namespace cauldron; 36 | 37 | void FSR2RenderModule::Init(const json& initData) 38 | { 39 | // Fetch needed resources 40 | m_pColorTarget = GetFramework()->GetColorTargetForCallback(GetName()); 41 | m_pDepthTarget = GetFramework()->GetRenderTexture(L"GBufferDepthTarget"); 42 | m_pMotionVectors = GetFramework()->GetRenderTexture(L"GBufferMotionVectorTarget"); 43 | 44 | CauldronAssert(ASSERT_CRITICAL, m_pColorTarget && m_pDepthTarget && m_pMotionVectors, L"Could not get one of the needed resources for FSR2 Rendermodule."); 45 | 46 | // Set our render resolution function as that to use during resize to get render width/height from display 47 | // width/height 48 | m_pUpdateFunc = [this](uint32_t displayWidth, uint32_t displayHeight) { return this->UpdateResolution(displayWidth, displayHeight); }; 49 | 50 | // UI 51 | InitUI(); 52 | 53 | ////////////////////////////////////////////////////////////////////////// 54 | // Finish up init 55 | 56 | // Start disabled as this will be enabled externally 57 | SetModuleEnabled(false); 58 | 59 | // That's all we need for now 60 | SetModuleReady(true); 61 | } 62 | 63 | FSR2RenderModule::~FSR2RenderModule() 64 | { 65 | // Protection 66 | if (ModuleEnabled()) 67 | EnableModule(false); // Destroy FSR context 68 | } 69 | 70 | void FSR2RenderModule::EnableModule(bool enabled) 71 | { 72 | // If disabling the render module, we need to disable the upscaler with the framework 73 | if (enabled) 74 | { 75 | // Setup everything needed when activating FSR 76 | // Will also enable upscaling 77 | UpdatePreset(nullptr); 78 | 79 | // Toggle this now so we avoid the context changes in OnResize 80 | SetModuleEnabled(enabled); 81 | 82 | // Setup Cauldron FidelityFX interface. 83 | const size_t scratchBufferSize = ffxGetScratchMemorySize(FFX_FSR2_CONTEXT_COUNT); 84 | void* scratchBuffer = calloc(scratchBufferSize, 1); 85 | FfxErrorCode errorCode = 86 | ffxGetInterface(&m_InitializationParameters.backendInterface, GetDevice(), scratchBuffer, scratchBufferSize, FFX_FSR2_CONTEXT_COUNT); 87 | CauldronAssert(ASSERT_CRITICAL, errorCode == FFX_OK, L"Could not initialize the FidelityFX SDK backend"); 88 | 89 | // Create the FSR2 context 90 | UpdateFSR2Context(true); 91 | 92 | // Set the jitter callback to use 93 | CameraJitterCallback jitterCallback = [this](Vec2& values) { 94 | // Increment jitter index for frame 95 | ++m_JitterIndex; 96 | 97 | // Update FSR2 jitter for built in TAA 98 | const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); 99 | const int32_t jitterPhaseCount = ffxFsr2GetJitterPhaseCount(resInfo.RenderWidth, resInfo.DisplayWidth); 100 | ffxFsr2GetJitterOffset(&m_JitterX, &m_JitterY, m_JitterIndex, jitterPhaseCount); 101 | 102 | values = Vec2(2.f * m_JitterX / resInfo.RenderWidth, 2.f * m_JitterY / resInfo.RenderHeight); 103 | }; 104 | CameraComponent::SetJitterCallbackFunc(jitterCallback); 105 | 106 | // ... and register UI elements for active upscaler 107 | GetUIManager()->RegisterUIElements(m_UISection); 108 | } 109 | else 110 | { 111 | // Toggle this now so we avoid the context changes in OnResize 112 | SetModuleEnabled(enabled); 113 | 114 | GetFramework()->EnableUpscaling(false); 115 | 116 | // Destroy the FSR2 context 117 | UpdateFSR2Context(false); 118 | 119 | // Destroy the FidelityFX interface memory 120 | free(m_InitializationParameters.backendInterface.scratchBuffer); 121 | 122 | // Deregister UI elements for inactive upscaler 123 | GetUIManager()->UnRegisterUIElements(m_UISection); 124 | } 125 | } 126 | 127 | void FSR2RenderModule::InitUI() 128 | { 129 | // Build UI options, but don't register them yet. Registration/Deregistration will be controlled by enabling/disabling the render module 130 | m_UISection.SectionName = "Upscaling"; // We will piggy-back on existing upscaling section" 131 | m_UISection.SectionType = UISectionType::Sample; 132 | 133 | // Setup scale preset options 134 | const char* preset[] = {"Quality (1.5x)", "Balanced (1.7x)", "Performance (2x)", "Ultra Performance (3x)", "Custom"}; 135 | std::vector presetComboOptions; 136 | presetComboOptions.assign(preset, preset + _countof(preset)); 137 | std::function presetCallback = [this](void* pParams) { this->UpdatePreset(static_cast(pParams)); }; 138 | m_UISection.AddCombo("Scale Preset", reinterpret_cast(&m_ScalePreset), &presetComboOptions, presetCallback); 139 | 140 | // Setup scale factor (disabled for all but custom) 141 | std::function ratioCallback = [this](void* pParams) { this->UpdateUpscaleRatio(static_cast(pParams)); }; 142 | m_UISection.AddFloatSlider("Custom Scale", &m_UpscaleRatio, 1.f, 3.f, ratioCallback, &m_UpscaleRatioEnabled); 143 | 144 | // Sharpening 145 | m_UISection.AddCheckBox("RCAS Sharpening", &m_RCASSharpen); 146 | m_UISection.AddFloatSlider("Sharpness", &m_Sharpness, 0.f, 1.f, nullptr, &m_RCASSharpen); 147 | } 148 | 149 | void FSR2RenderModule::UpdatePreset(const int32_t* pOldPreset) 150 | { 151 | switch (m_ScalePreset) 152 | { 153 | case FSR2ScalePreset::Quality: 154 | m_UpscaleRatio = 1.5f; 155 | break; 156 | case FSR2ScalePreset::Balanced: 157 | m_UpscaleRatio = 1.7f; 158 | break; 159 | case FSR2ScalePreset::Performance: 160 | m_UpscaleRatio = 2.0f; 161 | break; 162 | case FSR2ScalePreset::UltraPerformance: 163 | m_UpscaleRatio = 3.0f; 164 | break; 165 | case FSR2ScalePreset::Custom: 166 | default: 167 | // Leave the upscale ratio at whatever it was 168 | break; 169 | } 170 | 171 | // Update whether we can update the custom scale slider 172 | m_UpscaleRatioEnabled = (m_ScalePreset == FSR2ScalePreset::Custom); 173 | 174 | // Update resolution since rendering ratios have changed 175 | GetFramework()->EnableUpscaling(true, m_pUpdateFunc); 176 | } 177 | 178 | void FSR2RenderModule::UpdateUpscaleRatio(const float* pOldRatio) 179 | { 180 | // Disable/Enable FSR2 since resolution ratios have changed 181 | GetFramework()->EnableUpscaling(true, m_pUpdateFunc); 182 | } 183 | 184 | void FSR2RenderModule::FfxMsgCallback(FfxMsgType type, const wchar_t* message) 185 | { 186 | if (type == FFX_MESSAGE_TYPE_ERROR) 187 | { 188 | CauldronWarning(L"FSR2_API_DEBUG_ERROR: %ls", message); 189 | } 190 | else if (type == FFX_MESSAGE_TYPE_WARNING) 191 | { 192 | CauldronWarning(L"FSR2_API_DEBUG_WARNING: %ls", message); 193 | } 194 | } 195 | 196 | void FSR2RenderModule::UpdateFSR2Context(bool enabled) 197 | { 198 | if (enabled) 199 | { 200 | const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); 201 | m_InitializationParameters.maxRenderSize.width = resInfo.RenderWidth; 202 | m_InitializationParameters.maxRenderSize.height = resInfo.RenderHeight; 203 | m_InitializationParameters.displaySize.width = resInfo.DisplayWidth; 204 | m_InitializationParameters.displaySize.height = resInfo.DisplayHeight; 205 | 206 | // Enable auto-exposure by default 207 | m_InitializationParameters.flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE; 208 | 209 | // Note, inverted depth and display mode are currently handled statically for the run of the sample. 210 | // If they become changeable at runtime, we'll need to modify how this information is queried 211 | static bool s_InvertedDepth = GetConfig()->InvertedDepth; 212 | 213 | // Setup inverted depth flag according to sample usage 214 | if (s_InvertedDepth) 215 | m_InitializationParameters.flags |= FFX_FSR2_ENABLE_DEPTH_INVERTED | FFX_FSR2_ENABLE_DEPTH_INFINITE; 216 | 217 | // Input data is HDR 218 | m_InitializationParameters.flags |= FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE; 219 | 220 | // Motion vectors include frame-to-frame jitter 221 | m_InitializationParameters.flags |= FFX_FSR2_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; 222 | 223 | // Do eror checking in debug 224 | #if defined(_DEBUG) 225 | m_InitializationParameters.flags |= FFX_FSR2_ENABLE_DEBUG_CHECKING; 226 | m_InitializationParameters.fpMessage = &FSR2RenderModule::FfxMsgCallback; 227 | #endif // #if defined(_DEBUG) 228 | 229 | // Create the FSR2 context 230 | FfxErrorCode errorCode = ffxFsr2ContextCreate(&m_FSR2Context, &m_InitializationParameters); 231 | CauldronAssert(ASSERT_CRITICAL, errorCode == FFX_OK, L"Couldn't create the FidelityFX SDK FSR2 context."); 232 | } 233 | 234 | else 235 | { 236 | // Destroy the FSR2 context 237 | ffxFsr2ContextDestroy(&m_FSR2Context); 238 | } 239 | } 240 | 241 | ResolutionInfo FSR2RenderModule::UpdateResolution(uint32_t displayWidth, uint32_t displayHeight) 242 | { 243 | return { 244 | static_cast((float)displayWidth / m_UpscaleRatio), static_cast((float)displayHeight / m_UpscaleRatio), displayWidth, displayHeight}; 245 | } 246 | 247 | void FSR2RenderModule::OnResize(const ResolutionInfo& resInfo) 248 | { 249 | if (!ModuleEnabled()) 250 | return; 251 | 252 | // Need to recreate the FSR2 context on resource resize 253 | UpdateFSR2Context(false); // Destroy 254 | UpdateFSR2Context(true); // Re-create 255 | 256 | // Rest jitter index 257 | m_JitterIndex = 0; 258 | } 259 | 260 | void FSR2RenderModule::Execute(double deltaTime, CommandList* pCmdList) 261 | { 262 | GPUScopedProfileCapture sampleMarker(pCmdList, L"FFX FSR2"); 263 | const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); 264 | CameraComponent* pCamera = GetScene()->GetCurrentCamera(); 265 | 266 | // All cauldron resources come into a render module in a generic read state (ResourceState::NonPixelShaderResource | 267 | // ResourceState::PixelShaderResource) 268 | FfxFsr2DispatchDescription dispatchParameters = {}; 269 | dispatchParameters.commandList = ffxGetCommandList(pCmdList); 270 | dispatchParameters.color = ffxGetResource(m_pColorTarget->GetResource(), L"FSR2_Input_OutputColor", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 271 | dispatchParameters.depth = ffxGetResource(m_pDepthTarget->GetResource(), L"FSR2_InputDepth", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 272 | dispatchParameters.motionVectors = ffxGetResource(m_pMotionVectors->GetResource(), L"FSR2_InputMotionVectors", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 273 | dispatchParameters.exposure = ffxGetResource(nullptr, L"FSR2_InputExposure", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 274 | dispatchParameters.output = dispatchParameters.color; 275 | 276 | dispatchParameters.reactive = ffxGetResource(nullptr, L"FSR2_EmptyInputReactiveMap", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 277 | dispatchParameters.transparencyAndComposition = ffxGetResource(nullptr, L"FSR2_EmptyTransparencyAndCompositionMap", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); 278 | 279 | // Jitter is calculated earlier in the frame using a callback from the camera update 280 | dispatchParameters.jitterOffset.x = m_JitterX; 281 | dispatchParameters.jitterOffset.y = -m_JitterY; 282 | dispatchParameters.motionVectorScale.x = resInfo.fRenderWidth() / 2.f; 283 | dispatchParameters.motionVectorScale.y = -resInfo.fRenderHeight() / 2.f; 284 | dispatchParameters.reset = false; 285 | dispatchParameters.enableSharpening = m_RCASSharpen; 286 | dispatchParameters.sharpness = m_Sharpness; 287 | 288 | // Cauldron keeps time in seconds, but FSR expects miliseconds 289 | dispatchParameters.frameTimeDelta = static_cast(deltaTime * 1000.f); 290 | 291 | dispatchParameters.preExposure = GetScene()->GetSceneExposure(); 292 | dispatchParameters.renderSize.width = resInfo.RenderWidth; 293 | dispatchParameters.renderSize.height = resInfo.RenderHeight; 294 | 295 | // Note, inverted depth and display mode are currently handled statically for the run of the sample. 296 | // If they become changeable at runtime, we'll need to modify how this information is queried 297 | static bool s_InvertedDepth = GetConfig()->InvertedDepth; 298 | 299 | // Setup camera params as required 300 | dispatchParameters.cameraFovAngleVertical = pCamera->GetFovY(); 301 | if (s_InvertedDepth) 302 | { 303 | dispatchParameters.cameraFar = pCamera->GetNearPlane(); 304 | dispatchParameters.cameraNear = FLT_MAX; 305 | } 306 | else 307 | { 308 | dispatchParameters.cameraFar = pCamera->GetFarPlane(); 309 | dispatchParameters.cameraNear = pCamera->GetNearPlane(); 310 | } 311 | 312 | FfxErrorCode errorCode = ffxFsr2ContextDispatch(&m_FSR2Context, &dispatchParameters); 313 | FFX_ASSERT(errorCode == FFX_OK); 314 | 315 | // FidelityFX contexts modify the set resource view heaps, so set the cauldron one back 316 | SetAllResourceViewHeaps(pCmdList); 317 | 318 | // We are now done with upscaling 319 | GetFramework()->SetUpscalingState(UpscalerState::PostUpscale); 320 | } 321 | -------------------------------------------------------------------------------- /meshNodeSample/shaders/splinerenderer.hlsl: -------------------------------------------------------------------------------- 1 | // This file is part of the AMD Work Graph Mesh Node Sample. 2 | // 3 | // Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 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 | #include "common.hlsl" 23 | 24 | struct TransformedVertex { 25 | float4 clipSpacePosition : SV_POSITION; 26 | float3 objectSpacePosition : NORMAL0; 27 | float2 clipSpaceMotion : TEXCOORD0; 28 | float3 color : NORMAL1; 29 | }; 30 | 31 | // Returns number of line sections in a vertex ring 32 | int GetRingSectionCount(in int ringVertexCount) { 33 | return ringVertexCount == 1 ? 0 : ringVertexCount; 34 | } 35 | 36 | static const int splineGroupSize = 128; 37 | static const int numOutputVerticesLimit = 64; 38 | static const int numOutputTrianglesLimit = 128; 39 | 40 | [Shader("node")] 41 | [NodeLaunch("mesh")] 42 | [NodeId("DrawSpline", 0)] 43 | [NodeMaxDispatchGrid(maxSplinesPerRecord, 1, 1)] 44 | // This limit was set through instrumentation and is not required on AMD GPUs. 45 | // If you wish to change any of the procedural generation parameters, 46 | // and you are running on a non-AMD GPU, you may need to adjust this limit. 47 | // You can learn more at: 48 | // https://gpuopen.com/learn/work_graphs_mesh_nodes/work_graphs_mesh_nodes-tips_tricks_best_practices 49 | [NodeMaxInputRecordsPerGraphEntryRecord(10000, true)] 50 | [NumThreads(splineGroupSize, 1, 1)] 51 | [OutputTopology("triangle")] 52 | void SplineMeshShader( 53 | uint threadId : SV_GroupThreadID, 54 | uint gid : SV_GroupID, 55 | DispatchNodeInputRecord inputRecord, 56 | out indices uint3 tris[numOutputTrianglesLimit], 57 | out vertices TransformedVertex verts[numOutputVerticesLimit]) 58 | { 59 | const uint splineControlPointCount = clamp(inputRecord.Get().controlPointCount[gid], 0, splineMaxControlPointCount); 60 | const uint splineSectionCount = clamp(int(splineControlPointCount) - 1, 0, splineMaxControlPointCount - 1); 61 | 62 | const uint controlPointOffset = gid * splineMaxControlPointCount; 63 | 64 | uint vertexOutputCount = 0; 65 | uint primitiveOutputCount = 0; 66 | 67 | // control point for which the current thread will create a vertex 68 | int threadVertexControlPoint = 0; 69 | // index on control point ring which the current thread will generate 70 | int threadVertexControlPointVertex = 0; 71 | 72 | // control point section for which the current thread will create a triangle 73 | int threadPrimitiveSection = 0; 74 | // index on control point ring which the current thread will generate 75 | int threadPrimitiveSectionTriangle = 0; 76 | int threadPrimitiveSectionVertexOffset = 0; 77 | 78 | { 79 | // Number of vertices in last vertex ring 80 | int lastRingVertexCount = 0; 81 | 82 | // count vertices in first ring 83 | { 84 | const int controlPointVertexCount = inputRecord.Get().controlPointVertexCounts[controlPointOffset]; 85 | 86 | // check if current thread will generate a vertex on this ring 87 | if (threadId < controlPointVertexCount) 88 | { 89 | threadVertexControlPoint = 0; 90 | threadVertexControlPointVertex = threadId; 91 | } 92 | 93 | // Count total number of vertices 94 | vertexOutputCount += controlPointVertexCount; 95 | 96 | // Update last ring size 97 | lastRingVertexCount = controlPointVertexCount; 98 | } 99 | 100 | // count vertex & triangles count for every ring 101 | for (int ring = 1; ring < splineControlPointCount; ++ring) 102 | { 103 | const int controlPointVertexCount = inputRecord.Get().controlPointVertexCounts[controlPointOffset + ring]; 104 | 105 | if ((vertexOutputCount <= threadId) && ((vertexOutputCount + controlPointVertexCount) > threadId)) 106 | { 107 | threadVertexControlPoint = ring; 108 | threadVertexControlPointVertex = threadId - int(vertexOutputCount); 109 | } 110 | 111 | // lower sections are on last ring 112 | const int lowerSectionCount = GetRingSectionCount(lastRingVertexCount); 113 | // upper sections are on current ring 114 | const int upperSectionCount = GetRingSectionCount(controlPointVertexCount); 115 | 116 | // Count number of regular sections (i.e. one edge of the lower ring maps to one section on the upper ring) 117 | const int sectionCount = min(lowerSectionCount, upperSectionCount); 118 | const int regularTriangleCount = sectionCount * 2; 119 | // Count number of irregular trianlges (i.e. a edge connects with a single vertex on the other ring) 120 | const int irregularTriangleCount = max(lowerSectionCount, upperSectionCount) - sectionCount; 121 | 122 | // Total number of triangles on this ring 123 | const int triangleCount = regularTriangleCount + irregularTriangleCount; 124 | 125 | if ((primitiveOutputCount <= threadId) && ((primitiveOutputCount + triangleCount) > threadId)) 126 | { 127 | // Primitives are generated from lower to upper ring, i.e. we need the index of the last ring 128 | threadPrimitiveSection = ring - 1; 129 | threadPrimitiveSectionTriangle = threadId - primitiveOutputCount; 130 | // Get index of first vertex in last ring 131 | threadPrimitiveSectionVertexOffset = vertexOutputCount - lastRingVertexCount; 132 | } 133 | 134 | // Count total number of vertices & triangles 135 | vertexOutputCount += controlPointVertexCount; 136 | primitiveOutputCount += triangleCount; 137 | 138 | // Update last ring size 139 | lastRingVertexCount = controlPointVertexCount; 140 | } 141 | } 142 | 143 | vertexOutputCount = min(vertexOutputCount, numOutputVerticesLimit); 144 | primitiveOutputCount = min(primitiveOutputCount, numOutputTrianglesLimit); 145 | 146 | SetMeshOutputCounts(vertexOutputCount, primitiveOutputCount); 147 | 148 | if (threadId < vertexOutputCount) { 149 | TransformedVertex vertex; 150 | 151 | // Base position to compute object-local positions 152 | const float3 splineBasePosition = inputRecord.Get().controlPointPositions[controlPointOffset]; 153 | 154 | const float3 controlPointPosition = inputRecord.Get().controlPointPositions[controlPointOffset + threadVertexControlPoint]; 155 | const uint controlPointVertexCount = inputRecord.Get().controlPointVertexCounts[controlPointOffset + threadVertexControlPoint]; 156 | 157 | // Compute forward vector based on previous and next control point positions 158 | float3 forward = float3(0, 0, 0); 159 | // Add direction from previous control point 160 | if (threadVertexControlPoint > 0) 161 | { 162 | const float3 previousControlPointPosition = inputRecord.Get().controlPointPositions[controlPointOffset + threadVertexControlPoint - 1]; 163 | forward += controlPointPosition - previousControlPointPosition; 164 | } 165 | // Add direction to next control point 166 | if (threadVertexControlPoint < (splineControlPointCount - 1)) 167 | { 168 | const float3 nextControlPointPosition = inputRecord.Get().controlPointPositions[controlPointOffset + threadVertexControlPoint + 1]; 169 | forward += nextControlPointPosition - controlPointPosition; 170 | } 171 | 172 | forward = normalize(forward); 173 | 174 | // get perpendicular vector to forward 175 | float3 right = normalize(cross(forward, float3(1, 0, 0))); 176 | float3 up = normalize(cross(forward, right)); 177 | 178 | const float rotationOffset = inputRecord.Get().rotationOffset[gid]; 179 | const float vertexAlpha = rotationOffset + (threadVertexControlPointVertex / float(controlPointVertexCount)) * 2.f * PI; 180 | 181 | const float2 radius = inputRecord.Get().controlPointRadii[controlPointOffset + threadVertexControlPoint]; 182 | const float noiseAmplitude = inputRecord.Get().controlPointNoiseAmplitudes[controlPointOffset + threadVertexControlPoint]; 183 | 184 | // random noise value in [-noiseAmplitude; noiseAmplitude] 185 | const float noise = (Random(Hash(controlPointPosition), Hash(vertexAlpha)) * 2.0 - 1.0) * noiseAmplitude; 186 | 187 | const float3 worldSpaceBasePosition = 188 | controlPointPosition + // base position 189 | cos(vertexAlpha) * right * radius.y + // position on vertex ring in right direction 190 | sin(vertexAlpha) * up * radius.x + // position on vertex ring in up direction 191 | forward * noise; // random noise offset in spline direction 192 | 193 | // x = wind strength for current spline 194 | // y = factor for how much the actual vertex position in influencing the wind offset 195 | // 0 = wind offset is only determined by control point position 196 | // 1 = wind offset is only determined by vertex position 197 | const float2 windStrength = inputRecord.Get().windStrength[gid]; 198 | const float3 windReferencePosition = lerp(controlPointPosition, worldSpaceBasePosition, windStrength.y); 199 | // Get Height above terrain scaled by wind strength 200 | const float vertexHeight = max(windReferencePosition.y - GetTerrainHeight(windReferencePosition.xz), 0) * windStrength.x; 201 | 202 | // Compute wind offset for current and last frame 203 | const float3 windOffset = 204 | vertexHeight * GetWindStrength() * GetWindOffset(windReferencePosition.xz, GetTime()); 205 | const float3 previousWindOffset = 206 | vertexHeight * GetWindStrength() * GetWindOffset(windReferencePosition.xz, GetPreviousTime()); 207 | 208 | // compute position relative to first control point 209 | // this improve floating-point precision of ddx & ddy derivatives in pixel shader 210 | vertex.objectSpacePosition = worldSpaceBasePosition - splineBasePosition + windOffset; 211 | vertex.color = inputRecord.Get().color[gid]; 212 | 213 | ComputeClipSpacePositionAndMotion( 214 | vertex, worldSpaceBasePosition + windOffset, worldSpaceBasePosition + previousWindOffset); 215 | 216 | 217 | verts[threadId] = vertex; 218 | } 219 | 220 | if (threadId < primitiveOutputCount) { 221 | // Get number of vertices in current (lower) and next (upper) ring 222 | const int lowerVertexCount = inputRecord.Get().controlPointVertexCounts[controlPointOffset + threadPrimitiveSection]; 223 | const int upperVertexCount = inputRecord.Get().controlPointVertexCounts[controlPointOffset + threadPrimitiveSection + 1]; 224 | 225 | // Get number of sections in current and next ring 226 | const int lowerSectionCount = GetRingSectionCount(lowerVertexCount); 227 | const int upperSectionCount = GetRingSectionCount(upperVertexCount); 228 | 229 | // Get index of first vertex in current and next ring 230 | const int lowerVertexOffset = threadPrimitiveSectionVertexOffset; 231 | const int upperVertexOffset = lowerVertexOffset + lowerVertexCount; 232 | 233 | // The number of full sections (i.e. one edge in the lower ring corresponds to one edge in the upper ring) 234 | // is determined by the smaller vertex ring. 235 | // Irregular triangles will be generated from the larger ring towards the smaller ring 236 | const bool isLower = lowerSectionCount <= upperSectionCount; 237 | 238 | // Total number of regular sections 239 | const int sectionCount = isLower ? lowerSectionCount : upperSectionCount; 240 | // Ratio between sections in the smaller and larger ring 241 | const float sectionFactor = isLower ? upperSectionCount / float(lowerSectionCount) : lowerSectionCount / float(upperSectionCount); 242 | 243 | const int regularTriangleCount = sectionCount * 2; 244 | // check if current triangle is irregular (i.e. connects an edge to a single vertex) 245 | const bool isIrregularTriangle = threadPrimitiveSectionTriangle >= regularTriangleCount; 246 | // index of irregular triangle 247 | const int irregularTriangleIndex = threadPrimitiveSectionTriangle - regularTriangleCount; 248 | 249 | // running counters 250 | int lowerSection = 0; 251 | int upperSection = 0; 252 | int sectionJumpCount = 0; 253 | 254 | // indices for smaller and larger (=other) section 255 | int section = 0; 256 | int otherSection = 0; 257 | for (; section < sectionCount; ++section) 258 | { 259 | // compute which section on the larger ring should match up with the current section 260 | int otherSectionTarget = section * sectionFactor; 261 | 262 | // Generate irregular triangles for skipped sections 263 | for (; otherSection < otherSectionTarget; ++otherSection) 264 | { 265 | if (isIrregularTriangle && (sectionJumpCount == irregularTriangleIndex)) 266 | { 267 | lowerSection = isLower ? section : otherSection; 268 | upperSection = isLower ? otherSection : section; 269 | } 270 | sectionJumpCount++; 271 | } 272 | 273 | // Generate a regular triangles for both sections 274 | if (!isIrregularTriangle && (section == (threadPrimitiveSectionTriangle / 2))) 275 | { 276 | lowerSection = isLower ? section : otherSection; 277 | upperSection = isLower ? otherSection : section; 278 | } 279 | 280 | otherSection++; 281 | } 282 | // Generate remaining irregular triangles to close the ring 283 | for (; otherSection < max(lowerSectionCount, upperSectionCount); ++otherSection) 284 | { 285 | if (isIrregularTriangle && (sectionJumpCount == irregularTriangleIndex)) 286 | { 287 | lowerSection = isLower ? section : otherSection; 288 | upperSection = isLower ? otherSection : section; 289 | } 290 | sectionJumpCount++; 291 | } 292 | 293 | uint3 tri = 0; 294 | 295 | if (isIrregularTriangle) { 296 | if (isLower) { 297 | tri = uint3(lowerVertexOffset + ((lowerSection + 1) % lowerVertexCount), 298 | upperVertexOffset + ((upperSection + 1) % upperVertexCount), 299 | upperVertexOffset + ((upperSection + 0) % upperVertexCount)); 300 | } else { 301 | tri = uint3(lowerVertexOffset + ((lowerSection + 0) % lowerVertexCount), 302 | lowerVertexOffset + ((lowerSection + 1) % lowerVertexCount), 303 | upperVertexOffset + ((upperSection + 0) % upperVertexCount)); 304 | } 305 | } else { 306 | if (threadPrimitiveSectionTriangle & 0x1) { 307 | tri = uint3(upperVertexOffset + ((upperSection + 0) % upperVertexCount), 308 | lowerVertexOffset + ((lowerSection + 1) % lowerVertexCount), 309 | upperVertexOffset + ((upperSection + 1) % upperVertexCount)); 310 | } else { 311 | tri = uint3(lowerVertexOffset + ((lowerSection + 0) % lowerVertexCount), 312 | lowerVertexOffset + ((lowerSection + 1) % lowerVertexCount), 313 | upperVertexOffset + ((upperSection + 0) % upperVertexCount)); 314 | } 315 | } 316 | 317 | tris[threadId] = tri; 318 | } 319 | } 320 | 321 | DeferredPixelShaderOutput SplinePixelShader(TransformedVertex input) 322 | { 323 | DeferredPixelShaderOutput output; 324 | 325 | output.baseColor = float4(input.color, 1); 326 | output.motion = input.clipSpaceMotion; 327 | 328 | // compute normal from object space position derivatives 329 | output.normal.xyz = normalize(cross(ddy(input.objectSpacePosition.xyz), ddx(input.objectSpacePosition.xyz))); 330 | output.normal.w = 1.0; 331 | 332 | return output; 333 | } --------------------------------------------------------------------------------