diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 6e2074870..53bf89a61 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -62,11 +62,17 @@ public: API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; + /// + /// If checked, enables Global SDF rendering. This can be used in materials, shaders, and particles. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + bool EnableGlobalSDF = false; + #if USE_EDITOR /// /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). /// - API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Global SDF\")") bool GenerateSDFOnModelImport = false; #endif diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index b99ec5308..b0cbcd321 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -150,6 +150,17 @@ public: return _viewport; } + template + const T* FindCustomBuffer(const StringView& name) const + { + for (CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return (const T*)e; + } + return nullptr; + } + template T* GetCustomBuffer(const StringView& name) { diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 4442e5b15..2fe47bc48 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -110,6 +110,7 @@ public: GPUTexture* CascadeMips[4] = {}; Vector3 Positions[4]; HashSet NonEmptyChunks[4]; + GlobalSignDistanceFieldPass::BindingData Result; ~GlobalSignDistanceFieldCustomBuffer() { @@ -212,6 +213,17 @@ void GlobalSignDistanceFieldPass::Dispose() ChunksCache.SetCapacity(0); } +bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) +{ + auto* sdfData = buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")); + if (sdfData && sdfData->LastFrameUsed == Engine::FrameCount) + { + result = sdfData->Result; + return false; + } + return true; +} + bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) { // Skip if not supported @@ -221,6 +233,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex return true; auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (sdfData.LastFrameUsed == currentFrame) + { + result = sdfData.Result; + return false; + } + + PROFILE_GPU_CPU("Global SDF"); + // TODO: configurable via graphics settings const int32 resolution = 256; const int32 mipFactor = 4; @@ -229,238 +251,231 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const float distanceExtent = 2500.0f; const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; - // Skip if already done in the current frame - const auto currentFrame = Engine::FrameCount; - if (sdfData.LastFrameUsed != currentFrame) + // Initialize buffers + sdfData.LastFrameUsed = currentFrame; + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + bool updated = false; + for (GPUTexture*& cascade : sdfData.Cascades) { - PROFILE_GPU_CPU("Global SDF"); - - // Initialize buffers - sdfData.LastFrameUsed = currentFrame; - auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - bool updated = false; - for (GPUTexture*& cascade : sdfData.Cascades) + if (cascade && cascade->Width() != desc.Width) { - if (cascade && cascade->Width() != desc.Width) - { - RenderTargetPool::Release(cascade); - cascade = nullptr; - } + RenderTargetPool::Release(cascade); + cascade = nullptr; + } + if (!cascade) + { + cascade = RenderTargetPool::Get(desc); if (!cascade) - { - cascade = RenderTargetPool::Get(desc); - if (!cascade) - return true; - updated = true; - PROFILE_GPU_CPU("Init"); - context->ClearUA(cascade, Vector4::One); - } + return true; + updated = true; + PROFILE_GPU_CPU("Init"); + context->ClearUA(cascade, Vector4::One); } - desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + } + desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + { + if (cascadeMip && cascadeMip->Width() != desc.Width) { - if (cascadeMip && cascadeMip->Width() != desc.Width) - { - RenderTargetPool::Release(cascadeMip); - cascadeMip = nullptr; - } + RenderTargetPool::Release(cascadeMip); + cascadeMip = nullptr; + } + if (!cascadeMip) + { + cascadeMip = RenderTargetPool::Get(desc); if (!cascadeMip) + return true; + updated = true; + } + } + GPUTexture* tmpMip = nullptr; + if (updated) + LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + + // Rasterize world geometry into Global SDF + renderContext.View.Pass = DrawPass::GlobalSDF; + uint32 viewMask = renderContext.View.RenderLayersMask; + const bool useCache = !updated && !renderContext.Task->IsCameraCut; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); + const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); + auto& chunks = ChunksCache; + chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); + bool anyDraw = false; + const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; + //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + for (int32 cascade = 0; cascade < 4; cascade++) + { + // Reduce frequency of the updates + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) + continue; + const float distance = cascadesDistances[cascade]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const float snapping = voxelSize * mipFactor; + const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; + // TODO: cascade scrolling on movement to reduce dirty chunks? + //const Vector3 center = Vector3::Zero; + sdfData.Positions[cascade] = center; + BoundingBox cascadeBounds(center - distance, center + distance); + // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) + const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); + GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + + // Clear cascade before rasterization + { + PROFILE_CPU_NAMED("Clear"); + chunks.Clear(); + _modelsBuffer->Clear(); + _modelsTextures.Clear(); + } + + // Draw all objects from all scenes into the cascade + _modelsBufferCount = 0; + _voxelSize = voxelSize; + _cascadeBounds = cascadeBounds; + for (auto* scene : renderContext.List->Scenes) + { + // TODO: optimize for moving camera (copy sdf) + // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) + for (auto& e : scene->Actors) { - cascadeMip = RenderTargetPool::Get(desc); - if (!cascadeMip) - return true; - updated = true; + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + { + e.Actor->Draw(renderContext); + } } } - GPUTexture* tmpMip = nullptr; - if (updated) - LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); - // Rasterize world geometry into Global SDF - renderContext.View.Pass = DrawPass::GlobalSDF; - uint32 viewMask = renderContext.View.RenderLayersMask; - const bool useCache = !updated && !renderContext.Task->IsCameraCut; - static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); - const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); - auto& chunks = ChunksCache; - chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); - bool anyDraw = false; - const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; - //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; - for (int32 cascade = 0; cascade < 4; cascade++) + // Send models data to the GPU { - // Reduce frequency of the updates - if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) - continue; - const float distance = cascadesDistances[cascade]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; - const float snapping = voxelSize * mipFactor; - const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; - // TODO: cascade scrolling on movement to reduce dirty chunks? - //const Vector3 center = Vector3::Zero; - sdfData.Positions[cascade] = center; - BoundingBox cascadeBounds(center - distance, center + distance); - // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) - const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade - const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); - GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + PROFILE_GPU_CPU("Update Models"); + _modelsBuffer->Flush(context); + } - // Clear cascade before rasterization + // Perform batched chunks rasterization + if (!anyDraw) + { + anyDraw = true; + context->ResetSR(); + tmpMip = RenderTargetPool::Get(desc); + if (!tmpMip) + return true; + } + ModelsRasterizeData data; + data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; + data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; + data.MaxDistance = maxDistance; + data.CascadeResolution = resolution; + data.CascadeMipResolution = resolutionMip; + data.CascadeMipFactor = mipFactor; + context->BindUA(0, cascadeView); + context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); + if (_cb1) + context->BindCB(1, _cb1); + const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; + auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; + { + PROFILE_GPU_CPU("Clear Chunks"); + for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { - PROFILE_CPU_NAMED("Clear"); - chunks.Clear(); - _modelsBuffer->Clear(); - _modelsTextures.Clear(); - } + auto& key = it->Item; + if (chunks.ContainsKey(key)) + continue; - // Draw all objects from all scenes into the cascade - _modelsBufferCount = 0; - _voxelSize = voxelSize; - _cascadeBounds = cascadeBounds; - for (auto* scene : renderContext.List->Scenes) - { - // TODO: optimize for moving camera (copy sdf) - // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) - for (auto& e : scene->Actors) - { - if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) - { - e.Actor->Draw(renderContext); - } - } - } - - // Send models data to the GPU - { - PROFILE_GPU_CPU("Update Models"); - _modelsBuffer->Flush(context); - } - - // Perform batched chunks rasterization - if (!anyDraw) - { - anyDraw = true; - context->ResetSR(); - tmpMip = RenderTargetPool::Get(desc); - if (!tmpMip) - return true; - } - ModelsRasterizeData data; - data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; - data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; - data.MaxDistance = maxDistance; - data.CascadeResolution = resolution; - data.CascadeMipResolution = resolutionMip; - data.CascadeMipFactor = mipFactor; - context->BindUA(0, cascadeView); - context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); - if (_cb1) - context->BindCB(1, _cb1); - const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; - auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; - { - PROFILE_GPU_CPU("Clear Chunks"); - for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) - { - auto& key = it->Item; - if (chunks.ContainsKey(key)) - continue; - - // Clear empty chunk - nonEmptyChunks.Remove(it); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - if (_cb1) - context->UpdateCB(_cb1, &data); - context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - } - } - // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds - { - PROFILE_GPU_CPU("Rasterize Chunks"); - for (auto& e : chunks) - { - // Rasterize non-empty chunk - auto& key = e.Key; - auto& chunk = e.Value; - for (int32 i = 0; i < chunk.ModelsCount; i++) - { - int32 model = chunk.Models[i]; - data.Models[i] = model; - context->BindSR(i + 1, _modelsTextures[model]); - } - ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); - GPUShaderProgramCS* cs; - if (key.Layer == 0) - { - // First layer so can override existing chunk data - cs = _csRasterizeModel0; - nonEmptyChunks.Add(key); - } - else - { - // Another layer so need combine with existing chunk data - cs = _csRasterizeModel1; - } - context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) - -#if GLOBAL_SDF_DEBUG_CHUNKS - // Debug draw chunk bounds in world space with number of models in it - if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) - { - Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; - BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); - DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); - DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); - } -#endif - } - } - - // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) - { - PROFILE_GPU_CPU("Generate Mip"); + // Clear empty chunk + nonEmptyChunks.Remove(it); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; if (_cb1) context->UpdateCB(_cb1, &data); - context->ResetUA(); - context->BindSR(0, cascadeView); - context->BindUA(0, cascadeMipView); - const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); - int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; - context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); - context->UnBindSR(0); - GPUTextureView* tmpMipView = tmpMip->ViewVolume(); - for (int32 i = 1; i < floodFillIterations; i++) + context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches + } + } + // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds + { + PROFILE_GPU_CPU("Rasterize Chunks"); + for (auto& e : chunks) + { + // Rasterize non-empty chunk + auto& key = e.Key; + auto& chunk = e.Value; + for (int32 i = 0; i < chunk.ModelsCount; i++) { - context->ResetUA(); - context->BindSR(0, cascadeMipView); - context->BindUA(0, tmpMipView); - context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); - Swap(tmpMipView, cascadeMipView); + int32 model = chunk.Models[i]; + data.Models[i] = model; + context->BindSR(i + 1, _modelsTextures[model]); } - if (floodFillIterations % 2 == 0) - Swap(tmpMipView, cascadeMipView); + ASSERT_LOW_LAYER(chunk.ModelsCount != 0); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ModelsCount = chunk.ModelsCount; + if (_cb1) + context->UpdateCB(_cb1, &data); + GPUShaderProgramCS* cs; + if (key.Layer == 0) + { + // First layer so can override existing chunk data + cs = _csRasterizeModel0; + nonEmptyChunks.Add(key); + } + else + { + // Another layer so need combine with existing chunk data + cs = _csRasterizeModel1; + } + context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) + +#if GLOBAL_SDF_DEBUG_CHUNKS + // Debug draw chunk bounds in world space with number of models in it + if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) + { + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; + BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); + DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); + DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); + } +#endif } } - RenderTargetPool::Release(tmpMip); - if (anyDraw) + // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) { - context->UnBindCB(1); + PROFILE_GPU_CPU("Generate Mip"); + if (_cb1) + context->UpdateCB(_cb1, &data); context->ResetUA(); - context->FlushState(); - context->ResetSR(); - context->FlushState(); + context->BindSR(0, cascadeView); + context->BindUA(0, cascadeMipView); + const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); + int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; + context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + context->UnBindSR(0); + GPUTextureView* tmpMipView = tmpMip->ViewVolume(); + for (int32 i = 1; i < floodFillIterations; i++) + { + context->ResetUA(); + context->BindSR(0, cascadeMipView); + context->BindUA(0, tmpMipView); + context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + Swap(tmpMipView, cascadeMipView); + } + if (floodFillIterations % 2 == 0) + Swap(tmpMipView, cascadeMipView); } } + RenderTargetPool::Release(tmpMip); + if (anyDraw) + { + context->UnBindCB(1); + context->ResetUA(); + context->FlushState(); + context->ResetSR(); + context->FlushState(); + } + // Copy results static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.CascadeMips), "Invalid cascades count."); @@ -476,6 +491,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize; } result.GlobalSDF.Resolution = (float)resolution; + sdfData.Result = result; return false; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index b4a9cf984..f4c503222 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -46,6 +46,14 @@ private: BoundingBox _cascadeBounds; public: + /// + /// Gets the Global SDF (only if enabled in Graphics Settings). + /// + /// The rendering context buffers. + /// The result Global SDF data for binding to the shaders. + /// True if there is no valid Global SDF rendered during this frame, otherwise false. + bool Get(const RenderBuffers* buffers, BindingData& result); + /// /// Renders the Global SDF. /// diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 8174ebeb5..d6d21c1be 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -29,6 +29,7 @@ #include "AntiAliasing/SMAA.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Level.h" +#include "Engine/Core/Config/GraphicsSettings.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/QuadOverdrawPass.h" @@ -289,6 +290,7 @@ void Renderer::DrawPostFxMaterial(GPUContext* context, const RenderContext& rend void RenderInner(SceneRenderTask* task, RenderContext& renderContext) { auto context = GPUDevice::Instance->GetMainContext(); + auto* graphicsSettings = GraphicsSettings::Get(); auto& view = renderContext.View; ASSERT(renderContext.Buffers && renderContext.Buffers->GetWidth() > 0); @@ -338,6 +340,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) } #endif + // Global SDF rendering (can be used by materials later on) + if (graphicsSettings->EnableGlobalSDF) + { + GlobalSignDistanceFieldPass::BindingData bindingData; + GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingData); + } + // Fill GBuffer GBufferPass::Instance()->Fill(renderContext, lightBuffer->View());