// This file is part of the FidelityFX SDK. // // Copyright (C) 2024 Advanced Micro Devices, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files(the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and /or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions : // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "hybridshadowsrendermodule.h" #include "core/backend_interface.h" #include "core/framework.h" #include "core/uimanager.h" #include "render/parameterset.h" #include "render/pipelineobject.h" #include "render/renderdefines.h" #include "../thirdparty/samplercpp/samplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp.cpp" #include "core/loaders/textureloader.h" #include "render/dynamicresourcepool.h" #include "render/profiler.h" #include "render/indirectworkload.h" #include "core/scene.h" #include "core/contentmanager.h" using namespace std; using namespace cauldron; static const Texture* CreateBlueNoiseTexture() { byte blueNoise[128][128][4] = {}; for (int x = 0; x < 128; ++x) { for (int y = 0; y < 128; ++y) { float const f0 = samplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp(x, y, 0, 0); float const f1 = samplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp(x, y, 0, 1); float const f2 = samplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp(x, y, 0, 2); float const f3 = samplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp(x, y, 0, 3); blueNoise[x][y][0] = static_cast(f0 * UCHAR_MAX); blueNoise[x][y][1] = static_cast(f1 * UCHAR_MAX); blueNoise[x][y][2] = static_cast(f2 * UCHAR_MAX); blueNoise[x][y][3] = static_cast(f3 * UCHAR_MAX); } } const TextureDesc texDesc = TextureDesc::Tex2D(L"BlueNoise Texture", ResourceFormat::RGBA8_UNORM, 128, 128, 1, 1); MemTextureDataBlock* pDataBlock = new MemTextureDataBlock(reinterpret_cast(blueNoise)); Texture* pBlueNoiseTexture = Texture::CreateContentTexture(&texDesc); CauldronAssert(ASSERT_CRITICAL, pBlueNoiseTexture != nullptr, L"Could not create the texture %ls", texDesc.Name.c_str()); pBlueNoiseTexture->CopyData(pDataBlock); return std::move(pBlueNoiseTexture); } static Vec3 CreateTangentVector(Vec3 normal) { Vec3 up = abs(normal.getZ()) < 0.99999f ? Vec3(0.0, 0.0, 1.0) : Vec3(1.0, 0.0, 0.0); return normalize(cross(up, normal)); } static float ComputeSunSizeLightSpace(const Vec3& lightDirection, float sunSize, const Mat4& lightViewMatrix) { const Vec3 coneVec = normalize(lightDirection) + CreateTangentVector(lightDirection) * sunSize; const Vec3 lightSpaceConeVec = (lightViewMatrix * Vec4(coneVec, 0)).getXYZ(); float length = math::length(Vec2(lightSpaceConeVec.getX(), lightSpaceConeVec.getY())) / lightSpaceConeVec.getZ(); return length; } // Tile size is (8x4) constexpr uint32_t k_tileSizeX = 8; constexpr uint32_t k_tileSizeY = 4; static constexpr float AMD_PI = 3.1415926535897932384626433832795f; static constexpr float GOLDEN_RATIO = 1.6180339887f; void HybridShadowsRenderModule::CreateCopyDepthPipeline() { const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); // Create resource TextureDesc copyDepthTextureDesc = TextureDesc::Tex2D(L"FidelityFXShadowDenoiser_CopyDepth", ResourceFormat::R32_FLOAT, resInfo.RenderWidth, resInfo.RenderHeight, 1, 1, ResourceFlags::AllowUnorderedAccess); m_pCopyDepth = GetDynamicResourcePool()->CreateTexture(©DepthTextureDesc, ResourceState::PixelShaderResource | ResourceState::NonPixelShaderResource, [](TextureDesc& desc, uint32_t displayWidth, uint32_t displayHeight, uint32_t renderingWidth, uint32_t renderingHeight) { desc.Width = renderingWidth; desc.Height = renderingHeight; }); // Root signature RootSignatureDesc signatureDesc; signatureDesc.AddTextureSRVSet(0, ShaderBindStage::Compute, 1); signatureDesc.AddTextureUAVSet(0, ShaderBindStage::Compute, 1); m_pCopyDepthRootSignature = RootSignature::CreateRootSignature(L"CopyDepth_RootSignature", signatureDesc); // Setup the pipeline object PipelineDesc psoDesc; psoDesc.SetRootSignature(m_pCopyDepthRootSignature); // Setup the shaders to build on the pipeline object psoDesc.AddShaderDesc(ShaderBuildDesc::Compute(L"copydepth.hlsl", L"main", ShaderModel::SM6_0, nullptr)); m_pCopyDepthPipelineObj = PipelineObject::CreatePipelineObject(L"CopyDepth_PipelineObj", psoDesc); // Create parameter set to bind constant buffer and texture m_pCopyDepthParameters = ParameterSet::CreateParameterSet(m_pCopyDepthRootSignature); m_pCopyDepthParameters->SetTextureSRV(m_pDepthTarget, ViewDimension::Texture2D, 0); m_pCopyDepthParameters->SetTextureUAV(m_pCopyDepth, ViewDimension::Texture2D, 0); } void HybridShadowsRenderModule::CreateResources() { const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); const uint32_t xTiles = DivideRoundingUp(resInfo.RenderWidth, k_tileSizeX); const uint32_t yTiles = DivideRoundingUp(resInfo.RenderHeight, k_tileSizeY); const uint32_t tileCount = xTiles * yTiles; const size_t tileSize = sizeof(uint32_t) * 4; BufferDesc workQueueCountDesc{}; workQueueCountDesc.Type = BufferType::Data; workQueueCountDesc.Flags = static_cast(ResourceFlags::AllowUnorderedAccess | ResourceFlags::AllowIndirect); workQueueCountDesc.Size = sizeof(uint32_t) * 3; workQueueCountDesc.Stride = sizeof(uint32_t); workQueueCountDesc.Name = L"FidelityFXClassifier_WorkQueueCount"; m_pWorkQueueCount = GetDynamicResourcePool()->CreateBuffer(&workQueueCountDesc, ResourceState::CopyDest); BufferDesc workQueueDesc{}; workQueueDesc.Type = BufferType::Data; workQueueDesc.Flags = ResourceFlags::AllowUnorderedAccess; workQueueDesc.Size = tileSize * tileCount; workQueueDesc.Stride = tileSize; workQueueDesc.Name = L"FidelityFXClassifier_WorkQueue"; m_pWorkQueue = GetDynamicResourcePool()->CreateBuffer( &workQueueDesc, ResourceState::UnorderedAccess, [](BufferDesc& desc, uint32_t displayWidth, uint32_t displayHeight, uint32_t renderingWidth, uint32_t renderingHeight) { const uint32_t xTiles = DivideRoundingUp(renderingWidth, k_tileSizeX); const uint32_t yTiles = DivideRoundingUp(renderingHeight, k_tileSizeY); const uint32_t tileCount = xTiles * yTiles; const size_t tileSize = sizeof(uint32_t) * 4; desc.Size = tileSize * tileCount; }); // RayHitTexture TextureDesc rayHitTextureDesc = TextureDesc::Tex2D( L"FidelityFXClassifier_RayHitTexture", ResourceFormat::R32_UINT, xTiles, yTiles, 1, 1, ResourceFlags::AllowUnorderedAccess); m_pRayHitTexture = GetDynamicResourcePool()->CreateTexture( &rayHitTextureDesc, ResourceState::UnorderedAccess, [](TextureDesc& desc, uint32_t displayWidth, uint32_t displayHeight, uint32_t renderingWidth, uint32_t renderingHeight) { desc.Width = DivideRoundingUp(renderingWidth, k_tileSizeX); desc.Height = DivideRoundingUp(renderingHeight, k_tileSizeY); }); // ShadowMaskOutput TextureDesc shadowMaskOutputDesc = TextureDesc::Tex2D(L"FidelityFX_HybridShadows_ShadowMaskOutput", ResourceFormat::RGBA8_UNORM, resInfo.RenderWidth, resInfo.RenderHeight, 1, 1, ResourceFlags::AllowUnorderedAccess); m_pShadowMaskOutput = GetDynamicResourcePool()->CreateTexture( &shadowMaskOutputDesc, ResourceState::UnorderedAccess, [](TextureDesc& desc, uint32_t displayWidth, uint32_t displayHeight, uint32_t renderingWidth, uint32_t renderingHeight) { desc.Width = renderingWidth; desc.Height = renderingHeight; }); GetScene()->SetScreenSpaceShadowTexture(m_pShadowMaskOutput); } void HybridShadowsRenderModule::CreateDebugTilesPipeline() { // Root Signature RootSignatureDesc signatureDesc; signatureDesc.AddConstantBufferView(0, ShaderBindStage::Compute, 1); // CB signatureDesc.AddBufferSRVSet(0, ShaderBindStage::Compute, 1); // WorkTiles signatureDesc.AddTextureUAVSet(0, ShaderBindStage::Compute, 1); // ColorOutput m_pDebugTilesRootSignature = RootSignature::CreateRootSignature(L"FidelityFX_HybridShadows_DebugTilesSignature", signatureDesc); // Setup the pipeline object PipelineDesc psoDesc; psoDesc.SetRootSignature(m_pDebugTilesRootSignature); std::wstring shaderPath = L"debugtiles.hlsl"; psoDesc.AddShaderDesc(ShaderBuildDesc::Compute(shaderPath.c_str(), L"MainCS", ShaderModel::SM6_5, nullptr)); std::vector pAdditionalParameters; pAdditionalParameters.push_back(L"-enable-16bit-types"); m_pDebugTilesPipelineObj = PipelineObject::CreatePipelineObject(L"FidelityFX_HybridShadows_DebugTilesPipelineObj", psoDesc, &pAdditionalParameters); // Create parameter set to bind constant buffer and texture m_pDebugTilesParameters = ParameterSet::CreateParameterSet(m_pDebugTilesRootSignature); // Update necessary scene frame information m_pDebugTilesParameters->SetRootConstantBufferResource(GetDynamicBufferPool()->GetResource(), sizeof(DebugTilesConstantBuffer), 0); m_pDebugTilesParameters->SetBufferSRV(m_pWorkQueue, 0); m_pDebugTilesParameters->SetTextureUAV(m_pColorOutput, ViewDimension::Texture2D, 0); } void HybridShadowsRenderModule::CreateRayTracingPipelines() { m_pBlueNoise = CreateBlueNoiseTexture(); // Root Signature RootSignatureDesc signatureDesc; signatureDesc.AddConstantBufferView(0, ShaderBindStage::Compute, 1); // CB signatureDesc.AddBufferSRVSet(0, ShaderBindStage::Compute, 1); // WorkTiles signatureDesc.AddTextureSRVSet(1, ShaderBindStage::Compute, 1); // Depth signatureDesc.AddTextureSRVSet(2, ShaderBindStage::Compute, 1); // Normals signatureDesc.AddTextureSRVSet(3, ShaderBindStage::Compute, 1); // BlueNoise signatureDesc.AddRTAccelerationStructureSet(4, ShaderBindStage::Compute, 1); // AccellerationStructure signatureDesc.AddTextureUAVSet(0, ShaderBindStage::Compute, 1); // rayHit m_pRayTracingRootSignature = RootSignature::CreateRootSignature(L"FidelityFX_HybridShadows_RayTracingSignature", signatureDesc); // Setup the pipeline object PipelineDesc psoDesc; psoDesc.SetRootSignature(m_pRayTracingRootSignature); std::wstring shaderPath = L"traceshadows.hlsl"; psoDesc.AddShaderDesc(ShaderBuildDesc::Compute(shaderPath.c_str(), L"TraceOpaqueOnly", ShaderModel::SM6_5, nullptr)); std::vector pAdditionalParameters; pAdditionalParameters.push_back(L"-enable-16bit-types"); m_pRayTracingPipelineObj = PipelineObject::CreatePipelineObject(L"FidelityFX_HybridShadows_RayTracingPipelineObj", psoDesc, &pAdditionalParameters); // Create parameter set to bind constant buffer and texture m_pRayTracingParameters = ParameterSet::CreateParameterSet(m_pRayTracingRootSignature); // Update necessary scene frame information m_pRayTracingParameters->SetRootConstantBufferResource(GetDynamicBufferPool()->GetResource(), sizeof(RTConstantBuffer), 0); m_pRayTracingParameters->SetBufferSRV(m_pWorkQueue, 0); m_pRayTracingParameters->SetTextureSRV(m_pDepthTarget, ViewDimension::Texture2D, 1); m_pRayTracingParameters->SetTextureSRV(m_pNormalTarget, ViewDimension::Texture2D, 2); m_pRayTracingParameters->SetTextureSRV(m_pBlueNoise, ViewDimension::Texture2D, 3); // BlueNoise m_pRayTracingParameters->SetTextureUAV(m_pRayHitTexture, ViewDimension::Texture2D, 0); // Resolve raytracing pass { RootSignatureDesc signatureDesc; signatureDesc.AddTextureSRVSet(0, ShaderBindStage::Compute, 1); // rayHit signatureDesc.AddTextureUAVSet(0, ShaderBindStage::Compute, 1); // ColorOutput m_pResolveRayTracingRootSignature = RootSignature::CreateRootSignature(L"FidelityFX_HybridShadows_ResolveRayTracingSignature", signatureDesc); PipelineDesc psoDesc; psoDesc.SetRootSignature(m_pResolveRayTracingRootSignature); std::wstring shaderPath = L"resolveraytracing.hlsl"; psoDesc.AddShaderDesc(ShaderBuildDesc::Compute(shaderPath.c_str(), L"main", ShaderModel::SM6_5, nullptr)); std::vector pAdditionalParameters; pAdditionalParameters.push_back(L"-enable-16bit-types"); m_pResolveRayTracingPipelineObj = PipelineObject::CreatePipelineObject(L"FidelityFX_HybridShadows_ResolveRayTracingPipelineObj", psoDesc, &pAdditionalParameters); m_pResolveRayTracingParameters = ParameterSet::CreateParameterSet(m_pResolveRayTracingRootSignature); m_pResolveRayTracingParameters->SetTextureSRV(m_pRayHitTexture, ViewDimension::Texture2D, 0); m_pResolveRayTracingParameters->SetTextureUAV(m_pShadowMaskOutput, ViewDimension::Texture2D, 0); } } void HybridShadowsRenderModule::CreateDebugRayTracingPipeline() { // Root Signature RootSignatureDesc signatureDesc; signatureDesc.AddTextureUAVSet(0, ShaderBindStage::Compute, 1); // screenSpace RT texture signatureDesc.AddTextureUAVSet(1, ShaderBindStage::Compute, 1); // color m_pDebugRayTracingRootSignature = RootSignature::CreateRootSignature(L"FidelityFX_HybridShadows_DebugRayTracingSignature", signatureDesc); PipelineDesc psoDesc; psoDesc.SetRootSignature(m_pDebugRayTracingRootSignature); std::wstring shaderPath = L"copydebugraytracing.hlsl"; psoDesc.AddShaderDesc(ShaderBuildDesc::Compute(shaderPath.c_str(), L"main", ShaderModel::SM6_0, nullptr)); m_pDebugRayTracingPipelineObj = PipelineObject::CreatePipelineObject(L"FidelityFX_HybridShadows_DebugRayTracingPipelineObj", psoDesc, nullptr); m_pDebugRayTracingParameters = ParameterSet::CreateParameterSet(m_pDebugRayTracingRootSignature); m_pDebugRayTracingParameters->SetTextureUAV(m_pShadowMaskOutput, ViewDimension::Texture2D, 0); m_pDebugRayTracingParameters->SetTextureUAV(m_pColorOutput, ViewDimension::Texture2D, 1); } void HybridShadowsRenderModule::Init(const json&) { CauldronAssert(ASSERT_CRITICAL, !GetFramework()->GetConfig()->MotionVectorGeneration.empty(), L"Error : HybridShadowsRenderModule requires MotionVectorGeneration be set"); // Config Asserts CauldronAssert(ASSERT_CRITICAL, GetDevice()->FeatureSupported(DeviceFeature::RT_1_1), L"Error : HybridShadowsRenderModule requires RT1.1"); CauldronAssert(ASSERT_CRITICAL, GetDevice()->FeatureSupported(DeviceFeature::FP16), L"Error : HybridShadowsRenderModule requires FP16"); // Fetch needed resource m_pDepthTarget = GetFramework()->GetRenderTexture(L"DepthTarget"); m_pNormalTarget = GetFramework()->GetRenderTexture(L"GBufferNormalRT"); m_pColorOutput = GetFramework()->GetColorTargetForCallback(GetName()); m_pMotionVectors = GetFramework()->GetRenderTexture(L"GBufferMotionVectorRT"); // Create Resources for Effect CreateResources(); // UI elements UISection* uiSection = GetUIManager()->RegisterUIElements("Hybrid Shadows", UISectionType::Sample); uiSection->RegisterUIElement("Run Hybrid Shadows", m_bRunHybridShadows); uiSection->RegisterUIElement("Use Denoiser", m_bUseDenoiser); uiSection->RegisterUIElement>("Sun solid angle", m_sunSolidAngle, 0.f, 1.f, nullptr, true, false, "%.2f deg"); const static std::vector debugOptions = {"Disabled", "Show RayTraced Tiles", "Show Ray minT", "Show Ray maxT", "Show Ray length", "Show RayTracing Texture"}; uiSection->RegisterUIElement("Debug Mode", m_DebugMode, debugOptions); uiSection->RegisterUIElement>("TileCutOff", (int&)m_TileCutoff, 0, 32); uiSection->RegisterUIElement>("PCF offset", m_blockerOffset, 0.0f, 0.008f); uiSection->RegisterUIElement("Reject Lit Pixels for Ray Tracing", m_bRejectLitPixels); uiSection->RegisterUIElement("Use Shadow Maps to determine RayT", m_bUseCascadesForRayT); m_pIndirectWorkLoad = IndirectWorkload::CreateIndirectWorkload(IndirectCommandType::Dispatch); CreateCopyDepthPipeline(); CreateRayTracingPipelines(); CreateDebugTilesPipeline(); CreateDebugRayTracingPipeline(); InitEffect(); GetFramework()->ConfigureRuntimeShaderRecompiler( [this](void) { DestroyEffect(); }, [this](void) { InitEffect(); }); ////////////////////////////////////////////////////////////////////////// // Register additional execution callbacks during the frame // Register a post-lighting callback for tile debug pass ExecuteCallback callbackTileDebug = [this](double deltaTime, CommandList* pCmdList) { this->TileDebugCallback(deltaTime, pCmdList); }; ExecutionTuple callbackTileDebugTuple = std::make_pair(L"HybridShadowsRenderModule::TileDebugCallback", std::make_pair(this, callbackTileDebug)); GetFramework()->RegisterExecutionCallback(L"TranslucencyRenderModule", false, callbackTileDebugTuple); SetModuleReady(true); SetModuleEnabled(true); } void HybridShadowsRenderModule::TileDebugCallback(double deltaTime, cauldron::CommandList* pCmdList) { if (m_DebugMode == 0 || !m_bRunHybridShadows) return; const ResolutionInfo& resinfo = GetFramework()->GetResolutionInfo(); if (m_DebugMode == 5) { GPUScopedProfileCapture sampleMarker(pCmdList, L"Debug-RayTracing (HybridShadows)"); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pColorOutput->GetResource(), ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource, ResourceState::UnorderedAccess)); barriers.push_back(Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::CopyDest, ResourceState::IndirectArgument)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } // Bind everything m_pDebugRayTracingParameters->Bind(pCmdList, m_pDebugRayTracingPipelineObj); SetPipelineState(pCmdList, m_pDebugRayTracingPipelineObj); Dispatch(pCmdList, DivideRoundingUp(resinfo.RenderWidth, 8), DivideRoundingUp(resinfo.RenderHeight, 8), 1); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pColorOutput->GetResource(), ResourceState::UnorderedAccess, ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource)); barriers.push_back(Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::IndirectArgument, ResourceState::CopyDest)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } return; } GPUScopedProfileCapture sampleMarker(pCmdList, L"Debug-Tiles (HybridShadows)"); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pColorOutput->GetResource(), ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource, ResourceState::UnorderedAccess)); barriers.push_back(Barrier::Transition( m_pWorkQueueCount->GetResource(), ResourceState::CopyDest, ResourceState::IndirectArgument)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } static DebugTilesConstantBuffer dtConstantBuffer{}; dtConstantBuffer.debugMode = m_DebugMode; BufferAddressInfo cb = GetDynamicBufferPool()->AllocConstantBuffer(sizeof(DebugTilesConstantBuffer), &dtConstantBuffer); m_pDebugTilesParameters->UpdateRootConstantBuffer(&cb, 0); // Bind everything m_pDebugTilesParameters->SetBufferSRV(m_pWorkQueue, 0); m_pDebugTilesParameters->Bind(pCmdList, m_pDebugTilesPipelineObj); SetPipelineState(pCmdList, m_pDebugTilesPipelineObj); ExecuteIndirect(pCmdList, m_pIndirectWorkLoad, m_pWorkQueueCount, 1, 0); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pColorOutput->GetResource(), ResourceState::UnorderedAccess, ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource)); barriers.push_back(Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::IndirectArgument, ResourceState::CopyDest)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } } HybridShadowsRenderModule::~HybridShadowsRenderModule() { if (ModuleEnabled()) { EnableModule(false); // Destroy the context UpdateEffectContext(false); // Destroy the FidelityFX interface memory free(m_ClassifierCtxDesc.backendInterface.scratchBuffer); delete m_pDebugRayTracingPipelineObj; delete m_pDebugRayTracingRootSignature; delete m_pDebugRayTracingParameters; delete m_pIndirectWorkLoad; delete m_pRayTracingRootSignature; delete m_pRayTracingPipelineObj; delete m_pRayTracingParameters; delete m_pResolveRayTracingRootSignature; delete m_pResolveRayTracingPipelineObj; delete m_pResolveRayTracingParameters; delete m_pDebugTilesRootSignature; delete m_pDebugTilesPipelineObj; delete m_pDebugTilesParameters; delete m_pCopyDepthRootSignature; delete m_pCopyDepthPipelineObj; delete m_pCopyDepthParameters; delete m_pBlueNoise; } } void HybridShadowsRenderModule::InitEffect() { // Setup FidelityFX interface. { const size_t scratchBufferSize = SDKWrapper::ffxGetScratchMemorySize(FFX_CLASSIFIER_CONTEXT_COUNT + FFX_DENOISER_CONTEXT_COUNT); void* scratchBuffer = calloc(scratchBufferSize, 1u); FfxErrorCode errorCode = SDKWrapper::ffxGetInterface( &m_SDKInterface, GetDevice(), scratchBuffer, scratchBufferSize, FFX_CLASSIFIER_CONTEXT_COUNT + FFX_DENOISER_CONTEXT_COUNT); CAULDRON_ASSERT(errorCode == FFX_OK); CauldronAssert(ASSERT_CRITICAL, m_SDKInterface.fpGetSDKVersion(&m_SDKInterface) == FFX_SDK_MAKE_VERSION(1, 1, 2), L"FidelityFX HybridShadows 2.1 sample requires linking with a 1.1.2 version SDK backend"); CauldronAssert(ASSERT_CRITICAL, ffxClassifierGetEffectVersion() == FFX_SDK_MAKE_VERSION(1, 3, 0), L"FidelityFX HybridShadows 2.1 sample requires linking with a 1.3 version FidelityFX Classifier library"); CauldronAssert(ASSERT_CRITICAL, ffxDenoiserGetEffectVersion() == FFX_SDK_MAKE_VERSION(1, 3, 0), L"FidelityFX HybridShadows 2.1 sample requires linking with a 1.3 version FidelityFX Denoiser library"); m_SDKInterface.fpRegisterConstantBufferAllocator(&m_SDKInterface, SDKWrapper::ffxAllocateConstantBuffer); } m_ClassifierCtxDesc.backendInterface = m_SDKInterface; m_DenoiserCtxDesc.backendInterface = m_SDKInterface; // Create the context UpdateEffectContext(true); } void HybridShadowsRenderModule::DestroyEffect() { // Destroy the context UpdateEffectContext(false); // Destroy the FidelityFX interface memory if (m_ClassifierCtxDesc.backendInterface.scratchBuffer != nullptr) { free(m_ClassifierCtxDesc.backendInterface.scratchBuffer); m_ClassifierCtxDesc.backendInterface.scratchBuffer = nullptr; } } void HybridShadowsRenderModule::UpdateEffectContext(bool enabled) { const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); if (enabled) { // Classifier m_ClassifierCtxDesc.flags = 0; m_ClassifierCtxDesc.flags |= FFX_CLASSIFIER_SHADOW; switch (m_ClassificationMode) { case ClassificationMode::ClassifyByNormals: m_ClassifierCtxDesc.flags |= FFX_CLASSIFIER_CLASSIFY_BY_NORMALS; break; case ClassificationMode::ClassifyByCascades: m_ClassifierCtxDesc.flags |= FFX_CLASSIFIER_CLASSIFY_BY_CASCADES; break; default: CauldronCritical(L"Invalid Classification Mode"); } m_ClassifierCtxDesc.flags |= GetConfig()->InvertedDepth ? FFX_CLASSIFIER_ENABLE_DEPTH_INVERTED : 0; m_ClassifierCtxDesc.resolution.width = resInfo.RenderWidth; m_ClassifierCtxDesc.resolution.height = resInfo.RenderHeight; ffxClassifierContextCreate(&m_ClassifierContext, &m_ClassifierCtxDesc); // Denoiser m_DenoiserCtxDesc.flags = 0; m_DenoiserCtxDesc.flags = FFX_DENOISER_SHADOWS; m_DenoiserCtxDesc.flags |= GetConfig()->InvertedDepth ? FFX_CLASSIFIER_ENABLE_DEPTH_INVERTED : 0; m_DenoiserCtxDesc.windowSize.width = resInfo.RenderWidth; m_DenoiserCtxDesc.windowSize.height = resInfo.RenderHeight; ffxDenoiserContextCreate(&m_DenoiserContext, &m_DenoiserCtxDesc); } else { // Flush anything out of the pipes before destroying the context GetDevice()->FlushAllCommandQueues(); ffxClassifierContextDestroy(&m_ClassifierContext); ffxDenoiserContextDestroy(&m_DenoiserContext); } } void HybridShadowsRenderModule::ResolveRayTracingToShadowTexture(cauldron::CommandList* pCmdList) { const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); { std::vector preResolveBarriers; preResolveBarriers.push_back(Barrier::Transition( m_pRayHitTexture->GetResource(), ResourceState::UnorderedAccess, ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource)); } m_pResolveRayTracingParameters->Bind(pCmdList, m_pResolveRayTracingPipelineObj); SetPipelineState(pCmdList, m_pResolveRayTracingPipelineObj); Dispatch(pCmdList, DivideRoundingUp(resInfo.RenderWidth, k_tileSizeX), DivideRoundingUp(resInfo.RenderHeight, k_tileSizeY), 1); { std::vector postResolveBarriers; postResolveBarriers.push_back(Barrier::Transition( m_pRayHitTexture->GetResource(), ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource, ResourceState::UnorderedAccess)); } } void HybridShadowsRenderModule::RunRayTracingShadowDispatch(const FfxClassifierShadowDispatchDescription& shadowClassifierDispatchParams, const float sunSize, cauldron::CommandList* pCmdList) { GPUScopedProfileCapture sampleMarker(pCmdList, L"Trace Shadow Rays"); { std::vector preTracebarriers; preTracebarriers.push_back(Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::UnorderedAccess, ResourceState::IndirectArgument)); ResourceBarrier(pCmdList, (uint32_t)preTracebarriers.size(), preTracebarriers.data()); } m_pRayTracingParameters->SetAccelerationStructure(GetScene()->GetASManager()->GetTLAS(), 4); const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); m_RTConstantBuffer.textureSize = Vec4((float)resInfo.RenderWidth, (float)resInfo.RenderHeight, 1.f / resInfo.RenderWidth, 1.f / resInfo.RenderHeight); memcpy(&m_RTConstantBuffer.lightDir, &shadowClassifierDispatchParams.lightDir, sizeof(Vec3)); static uint32_t frame = 0; m_RTConstantBuffer.pixelThickness_bUseCascadesForRayT_noisePhase_sunSize[0] = 1e-4f; m_RTConstantBuffer.pixelThickness_bUseCascadesForRayT_noisePhase_sunSize[1] = shadowClassifierDispatchParams.bUseCascadesForRayT; m_RTConstantBuffer.pixelThickness_bUseCascadesForRayT_noisePhase_sunSize[2] = (frame++ & 0xff) * GOLDEN_RATIO; m_RTConstantBuffer.pixelThickness_bUseCascadesForRayT_noisePhase_sunSize[3] = sunSize; memcpy(&m_RTConstantBuffer.viewToWorld, &shadowClassifierDispatchParams.viewToWorld, sizeof(Mat4)); BufferAddressInfo cb = GetDynamicBufferPool()->AllocConstantBuffer(sizeof(RTConstantBuffer), &m_RTConstantBuffer); m_pRayTracingParameters->UpdateRootConstantBuffer(&cb, 0); // Bind everything m_pRayTracingParameters->SetBufferSRV(m_pWorkQueue, 0); m_pRayTracingParameters->Bind(pCmdList, m_pRayTracingPipelineObj); SetPipelineState(pCmdList, m_pRayTracingPipelineObj); ExecuteIndirect(pCmdList, m_pIndirectWorkLoad, m_pWorkQueueCount, 1, 0); { std::vector postTracebarriers; postTracebarriers.push_back(Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::IndirectArgument, ResourceState::CopyDest)); ResourceBarrier(pCmdList, (uint32_t)postTracebarriers.size(), postTracebarriers.data()); } } void HybridShadowsRenderModule::RunCopyDepth(cauldron::CommandList* pCmdList) { const ResolutionInfo& resInfo = GetFramework()->GetResolutionInfo(); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pCopyDepth->GetResource(), ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource, ResourceState::UnorderedAccess)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } m_pCopyDepthParameters->Bind(pCmdList, m_pCopyDepthPipelineObj); SetPipelineState(pCmdList, m_pCopyDepthPipelineObj); Dispatch(pCmdList, DivideRoundingUp(resInfo.RenderWidth, 64u), DivideRoundingUp(resInfo.RenderHeight, 64u), 1); { std::vector barriers; barriers.push_back(Barrier::Transition( m_pCopyDepth->GetResource(), ResourceState::UnorderedAccess, ResourceState::NonPixelShaderResource | ResourceState::PixelShaderResource)); ResourceBarrier(pCmdList, static_cast(barriers.size()), barriers.data()); } } void HybridShadowsRenderModule::RunFfxShadowDenoiser(cauldron::CommandList* pCmdList) { GPUScopedProfileCapture sampleMarker(pCmdList, L"FidelityFX Shadow Denoiser"); static uint32_t frameIndex = 0; FfxDenoiserShadowsDispatchDescription denoiserDispatchDescription{}; denoiserDispatchDescription.commandList = SDKWrapper::ffxGetCommandList(pCmdList); denoiserDispatchDescription.hitMaskResults = SDKWrapper::ffxGetResource(m_pRayHitTexture->GetResource(), L"FidelityFX_ShadowDenoiser_RayHit", FFX_RESOURCE_STATE_GENERIC_READ); denoiserDispatchDescription.depth = SDKWrapper::ffxGetResource(m_pCopyDepth->GetResource(), L"FidelityFX_ShadowDenoiser_InputDepth", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); denoiserDispatchDescription.velocity = SDKWrapper::ffxGetResource(m_pMotionVectors->GetResource(), L"FidelityFX_ShadowDenoiser_InputVelocity", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); denoiserDispatchDescription.normal = SDKWrapper::ffxGetResource(m_pNormalTarget->GetResource(), L"FidelityFX_ShadowDenoiser_InputNormals", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); denoiserDispatchDescription.shadowMaskOutput = SDKWrapper::ffxGetResource(m_pShadowMaskOutput->GetResource(), L"FidelityFX_ShadowDenoiser_ShadowMaskOutput", FFX_RESOURCE_STATE_UNORDERED_ACCESS); // Cauldron's GBuffer stores normals in the [0, 1] range, the FidelityFX Shadow Denoiser expects them in the [-1, 1] range. denoiserDispatchDescription.normalsUnpackMul = 2.f; denoiserDispatchDescription.normalsUnpackAdd = -1.f; const CameraComponent* pCam = GetScene()->GetCurrentCamera(); memcpy(&denoiserDispatchDescription.eye, &pCam->GetCameraPos(), sizeof(Vec3)); denoiserDispatchDescription.frameIndex = frameIndex; memcpy(&denoiserDispatchDescription.projectionInverse, &pCam->GetInverseProjection(), sizeof(Mat4)); const auto reprojMat = pCam->GetProjection() * (pCam->GetPreviousView() * pCam->GetInverseViewProjection()); memcpy(&denoiserDispatchDescription.reprojectionMatrix, &reprojMat, sizeof(Mat4)); memcpy(&denoiserDispatchDescription.viewProjectionInverse, &pCam->GetInverseViewProjection(), sizeof(Mat4)); denoiserDispatchDescription.depthSimilaritySigma = 1.f; float mvScale[2] = {1.f, 1.f}; memcpy(denoiserDispatchDescription.motionVectorScale, mvScale, sizeof(mvScale)); CAULDRON_ASSERT(FFX_OK == ffxDenoiserContextDispatchShadows(&m_DenoiserContext, &denoiserDispatchDescription)); // FidelityFX contexts modify the set resource view heaps, so set the cauldron one back SetAllResourceViewHeaps(pCmdList); frameIndex++; } void HybridShadowsRenderModule::Execute(double deltaTime, CommandList* pCmdList) { if (!m_bRunHybridShadows) { GetScene()->SetScreenSpaceShadowTexture(nullptr); return; } GetScene()->SetScreenSpaceShadowTexture(m_pShadowMaskOutput); static float sunSize; sunSize = tanf(0.5f * (m_sunSolidAngle * (2 * AMD_PI) / 360.0f)); GPUScopedProfileCapture sampleMarker(pCmdList, L"FidelityFX Hybrid Shadows"); FfxClassifierShadowDispatchDescription shadowClassifierDispatchParams{}; { GPUScopedProfileCapture sampleMarker(pCmdList, L"FidelityFX Classifier"); const CameraComponent* pCamera = GetScene()->GetCurrentCamera(); // Init WorkQueueCount constexpr uint32_t offsets[3] = {sizeof(uint32_t) * 0, sizeof(uint32_t) * 1, sizeof(uint32_t) * 2}; constexpr uint32_t values[3] = {0, 1, 1}; WriteBufferImmediate(pCmdList, m_pWorkQueueCount->GetResource(), 3, offsets, values); Barrier barrier = Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::CopyDest, ResourceState::UnorderedAccess); ResourceBarrier(pCmdList, 1, &barrier); shadowClassifierDispatchParams.commandList = SDKWrapper::ffxGetCommandList(pCmdList); shadowClassifierDispatchParams.depth = SDKWrapper::ffxGetResource(m_pDepthTarget->GetResource(), L"FidelityFXClassifier_InputDepth", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); shadowClassifierDispatchParams.normals = SDKWrapper::ffxGetResource(m_pNormalTarget->GetResource(), L"FidelityFXClassifier_InputNormals", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); shadowClassifierDispatchParams.workQueue = SDKWrapper::ffxGetResource(m_pWorkQueue->GetResource(), L"FidelityFXClassifier_WorkQueue", FFX_RESOURCE_STATE_UNORDERED_ACCESS); shadowClassifierDispatchParams.workQueueCount = SDKWrapper::ffxGetResource(m_pWorkQueueCount->GetResource(), L"FidelityFXClassifier_WorkQueueCount", FFX_RESOURCE_STATE_UNORDERED_ACCESS); shadowClassifierDispatchParams.rayHitTexture = SDKWrapper::ffxGetResource(m_pRayHitTexture->GetResource(), L"FidelityFXClassifier_RayHit", FFX_RESOURCE_STATE_UNORDERED_ACCESS); // Cauldron's GBuffer stores normals in the [0, 1] range, the FidelityFX Classifier expects them in the [-1, 1] range. shadowClassifierDispatchParams.normalsUnPackMul = 2.f; shadowClassifierDispatchParams.normalsUnPackAdd = -1.f; shadowClassifierDispatchParams.tileCutOff = m_TileCutoff; shadowClassifierDispatchParams.bRejectLitPixels = m_bRejectLitPixels; shadowClassifierDispatchParams.bUseCascadesForRayT = m_bUseCascadesForRayT && m_ClassificationMode == ClassificationMode::ClassifyByCascades; shadowClassifierDispatchParams.blockerOffset = m_blockerOffset; memcpy(&shadowClassifierDispatchParams.viewToWorld, &pCamera->GetInverseViewProjection(), sizeof(Mat4)); const std::vector& lightingComponents = LightComponentMgr::Get()->GetComponentList(); for (auto* pComp : lightingComponents) { // Skip inactive lights if (!pComp->GetOwner()->IsActive()) continue; const LightComponent* pLightComp = static_cast(pComp); // Process only a light if it has cascades and is a directional light if (pLightComp->GetCascadesCount() <= 0 || pLightComp->GetType() != LightType::Directional) { Barrier barrier2 = Barrier::Transition(m_pWorkQueueCount->GetResource(), ResourceState::UnorderedAccess, ResourceState::CopyDest); ResourceBarrier(pCmdList, 1, &barrier2); return; } memcpy(&shadowClassifierDispatchParams.lightDir, &-pLightComp->GetDirection(), sizeof(Vec3)); memcpy(&shadowClassifierDispatchParams.lightView, &pLightComp->GetView(), sizeof(Mat4)); memcpy(&shadowClassifierDispatchParams.inverseLightView, &pLightComp->GetInverseView(), sizeof(Mat4)); ShadowMapResourcePool* pShadowMapResourcePool = GetFramework()->GetShadowMapResourcePool(); CauldronAssert(ASSERT_CRITICAL, pLightComp->GetCascadesCount() <= 4, L"HybridShadowsRenderModule does not support lights with more than 4 cascades"); shadowClassifierDispatchParams.cascadeCount = pLightComp->GetCascadesCount(); for (uint32_t cascade = 0; cascade < shadowClassifierDispatchParams.cascadeCount; ++cascade) { int shadowMapIndex = pLightComp->GetShadowMapIndex(cascade); shadowClassifierDispatchParams.shadowMaps[cascade] = SDKWrapper::ffxGetResource(pShadowMapResourcePool->GetRenderTarget(shadowMapIndex)->GetResource(), L"FidelityFXClassifier_ShadowMap", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ); const Vec4 scaleAndOffset = pShadowMapResourcePool->GetTransformation(pLightComp->GetShadowMapRect(cascade)); const Mat4 matTextureScale = Mat4::scale(Vec3(scaleAndOffset.getX(), scaleAndOffset.getY(), 1.0f)); const Mat4 matTextureTranslation = Mat4::translation(Vec3(scaleAndOffset.getZ(), scaleAndOffset.getW(), 0.f)); const Mat4 mShadowTexture = matTextureTranslation * matTextureScale * pLightComp->GetShadowProjection(cascade); memcpy(&shadowClassifierDispatchParams.cascadeScale[cascade], &Vec4(mShadowTexture.getCol0().getX(), mShadowTexture.getCol1().getY(), mShadowTexture.getCol2().getZ(), 1.0f), sizeof(Vec4)); memcpy(&shadowClassifierDispatchParams.cascadeOffset[cascade], &Vec4(mShadowTexture.getCol3().getX(), mShadowTexture.getCol3().getY(), mShadowTexture.getCol3().getZ(), 0.0f), sizeof(Vec4)); } shadowClassifierDispatchParams.cascadeSize = (float)pLightComp->GetShadowResolution(); shadowClassifierDispatchParams.sunSizeLightSpace = ComputeSunSizeLightSpace(pLightComp->GetDirection(), sunSize, pLightComp->GetView()); // Only process one light break; } CAULDRON_ASSERT(FFX_OK == ffxClassifierContextShadowDispatch(&m_ClassifierContext, &shadowClassifierDispatchParams)); // FidelityFX contexts modify the set resource view heaps, so set the cauldron one back SetAllResourceViewHeaps(pCmdList); } RunRayTracingShadowDispatch(shadowClassifierDispatchParams, sunSize, pCmdList); if (m_bUseDenoiser) { // Copy depth to a R32 texture RunCopyDepth(pCmdList); // Denoise RT output and write it to the Shadow Texture RunFfxShadowDenoiser(pCmdList); } else { // Write RT output to the shadow texture directly GPUScopedProfileCapture sampleMarker(pCmdList, L"Write to Shadow Texture"); ResolveRayTracingToShadowTexture(pCmdList); } } void HybridShadowsRenderModule::OnResize(const ResolutionInfo& resInfo) { if (!ModuleEnabled()) return; // Need to recreate the context on resource resize UpdateEffectContext(false); // Destroy UpdateEffectContext(true); // Re-create }