From 18c3f274f8b3b10243f39ac72934ecf812705e09 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 26 Jun 2024 18:16:58 +0200 Subject: [PATCH] Optimize Global SDF drawing with async job system --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 704 +++++++++++------- .../Renderer/GlobalSignDistanceFieldPass.h | 22 +- Source/Engine/Renderer/Renderer.cpp | 2 + 3 files changed, 428 insertions(+), 300 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 2d35ac09e..7a676f584 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -18,9 +18,9 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Threading/JobSystem.h" // Some of those constants must match in shader -// TODO: try using R8 format for Global SDF #define GLOBAL_SDF_FORMAT PixelFormat::R16_Float #define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. #define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 // The maximum amount of heightfields to store in a single chunk. @@ -30,7 +30,7 @@ #define GLOBAL_SDF_RASTERIZE_MIP_FACTOR 4 // Global SDF mip resolution downscale factor. #define GLOBAL_SDF_MIP_GROUP_SIZE 4 #define GLOBAL_SDF_MIP_FLOODS 5 // Amount of flood fill passes for mip. -#define GLOBAL_SDF_DEBUG_CHUNKS 0 +#define GLOBAL_SDF_DEBUG_CHUNKS 0 // Toggles debug drawing of Global SDF chunks bounds including objects count label (only for the first cascade) #define GLOBAL_SDF_DEBUG_FORCE_REDRAW 0 // Forces to redraw all SDF cascades every frame #define GLOBAL_SDF_ACTOR_IS_STATIC(actor) EnumHasAllFlags(actor->GetStaticFlags(), StaticFlags::Lightmap | StaticFlags::Transform) @@ -130,13 +130,30 @@ uint32 GetHash(const RasterizeChunkKey& key) struct CascadeData { + bool Dirty; + int32 Index; + float ChunkSize; + float MaxDistance; Float3 Position; float VoxelSize; BoundingBox Bounds; + BoundingBox CullingBounds; + BoundingBox RasterizeBounds; + Vector3 OriginMin; + Vector3 OriginMax; HashSet NonEmptyChunks; HashSet StaticChunks; - FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) + // Cache + Dictionary Chunks; + Array RasterizeObjects; + Array ObjectsData; + Array ObjectsTextures; + Dictionary ObjectIndexToDataIndex; + HashSet PendingSDFTextures; + HashSet PendingObjectTypes; + + void OnSceneRenderingDirty(const BoundingBox& objectBounds) { if (StaticChunks.IsEmpty() || !Bounds.Intersects(objectBounds)) return; @@ -147,9 +164,8 @@ struct CascadeData Vector3::Subtract(objectBoundsCascade.Minimum, Bounds.Minimum, objectBoundsCascade.Minimum); Vector3::Clamp(objectBounds.Maximum + objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Maximum); Vector3::Subtract(objectBoundsCascade.Maximum, Bounds.Minimum, objectBoundsCascade.Maximum); - const float chunkSize = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); - const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + const Int3 objectChunkMin(objectBoundsCascade.Minimum / ChunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / ChunkSize); // Invalidate static chunks intersecting with dirty bounds RasterizeChunkKey key; @@ -181,8 +197,13 @@ public: HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; + // Async objects drawing cache + Array> AsyncDrawWaitLabels; + RenderContext AsyncRenderContext; + ~GlobalSignDistanceFieldCustomBuffer() { + WaitForDrawing(); for (const auto& e : SDFTextures) { e.Item->Deleted.Unbind(this); @@ -215,6 +236,145 @@ public: } } + const float CascadesDistanceScales[4] = { 1.0f, 2.5f, 5.0f, 10.0f }; + + void GetOptions(const RenderContext& renderContext, int32& resolution, int32& cascadesCount, int32& resolutionMip, float& distance) + { + switch (Graphics::GlobalSDFQuality) + { + case Quality::Low: + resolution = 128; + cascadesCount = 2; + break; + case Quality::Medium: + resolution = 128; + cascadesCount = 3; + break; + case Quality::High: + resolution = 192; + cascadesCount = 4; + break; + case Quality::Ultra: + default: + resolution = 256; + cascadesCount = 4; + break; + } + resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); + auto& giSettings = renderContext.List->Settings.GlobalIllumination; + distance = GraphicsSettings::Get()->GlobalSDFDistance; + if (giSettings.Mode == GlobalIlluminationMode::DDGI) + distance = Math::Max(distance, giSettings.Distance); + distance = Math::Min(distance, renderContext.View.Far); + } + + void DrawCascadeActors(const CascadeData& cascade); + void UpdateCascadeChunks(CascadeData& cascade); + void WriteCascadeObjects(CascadeData& cascade); + void DrawCascadeJob(int32 cascadeIndex); + + void StartDrawing(const RenderContext& renderContext, bool enableAsync = false, bool reset = false) + { + if (AsyncDrawWaitLabels.HasItems()) + return; // Already started earlier this frame + int32 resolution, cascadesCount, resolutionMip; + float distance; + GetOptions(renderContext, resolution, cascadesCount, resolutionMip, distance); + if (Cascades.Count() != cascadesCount || Resolution != resolution || Origin != renderContext.View.Origin) + return; // Not yet initialized + PROFILE_CPU(); + + // Calculate origin for Global SDF by shifting it towards the view direction to account for better view frustum coverage + const float distanceExtent = distance / CascadesDistanceScales[cascadesCount - 1]; + Float3 viewPosition = renderContext.View.Position; + { + Float3 viewDirection = renderContext.View.Direction; + const float cascade0Distance = distanceExtent * CascadesDistanceScales[0]; + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewPosition, viewPosition + viewDirection * (cascade0Distance * 2.0f), viewPosition - cascade0Distance, viewPosition + cascade0Distance); + const float viewOriginOffset = (float)viewRayHit.Y * cascade0Distance * 0.6f; + viewPosition += viewDirection * viewOriginOffset; + } + + // Setup data for rendering + if (FrameIndex++ > 128) + FrameIndex = 0; + AsyncRenderContext = renderContext; + AsyncRenderContext.View.Pass = DrawPass::GlobalSDF; + const bool useCache = !reset && !GLOBAL_SDF_DEBUG_FORCE_REDRAW && GPU_SPREAD_WORKLOAD; + 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); + const bool updateEveryFrame = false; // true if update all cascades every frame + const int32 maxCascadeUpdatesPerFrame = 1; // maximum cascades to update at a single frame + + // Rasterize world geometry into Global SDF + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + // Reduce frequency of the updates + auto& cascade = Cascades[cascadeIndex]; + cascade.Index = cascadeIndex; + cascade.Dirty = !useCache || RenderTools::ShouldUpdateCascade(FrameIndex, cascadeIndex, cascadesCount, maxCascadeUpdatesPerFrame, updateEveryFrame); + if (!cascade.Dirty) + continue; + const float cascadeDistance = distanceExtent * CascadesDistanceScales[cascadeIndex]; + const float cascadeMaxDistance = cascadeDistance * 2; + const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution; + const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); + const Float3 center = Float3::Floor(viewPosition / cascadeChunkSize) * cascadeChunkSize; + //const Float3 center = Float3::Zero; + BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); + + // Clear cascade before rasterization + cascade.Chunks.Clear(); + // TODO: consider using for RendererAllocation Chunks and RasterizeObjects to share memory with other rendering internals (ensure to release memory after SDF draw ends) + cascade.Chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); + // TODO: cache RasterizeObjects size from the previous frame (for this cascade) and preallocate it here once RendererAllocation is used + cascade.RasterizeObjects.Clear(); + cascade.PendingSDFTextures.Clear(); + + // Check if cascade center has been moved + if (!(useCache && Float3::NearEqual(cascade.Position, center, cascadeVoxelSize))) + { + // TODO: optimize for moving camera (use chunkCoords scrolling) + cascade.StaticChunks.Clear(); + } + + // Setup cascade info + cascade.Position = center; + cascade.VoxelSize = cascadeVoxelSize; + cascade.ChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + cascade.MaxDistance = cascadeMaxDistance; + cascade.Bounds = cascadeBounds; + cascade.RasterizeBounds = cascadeBounds; + cascade.RasterizeBounds.Minimum += 0.1f; // Adjust to prevent overflowing chunk keys (cascade bounds are used for clamping object bounds) + cascade.RasterizeBounds.Maximum -= 0.1f; // Adjust to prevent overflowing chunk keys (cascade bounds are used for clamping object bounds) + cascade.CullingBounds = cascadeBounds.MakeOffsetted(Origin); + const float objectMargin = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + cascade.OriginMin = -Origin - objectMargin; + cascade.OriginMax = -Origin + objectMargin; + } + if (enableAsync) + { + // Draw all dirty cascades in async (separate job for each cascade) + Function func; + func.Bind(this); + AsyncDrawWaitLabels.Add(JobSystem::Dispatch(func, cascadesCount)); + } + else + { + // Synchronized drawing in sequence + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + DrawCascadeJob(cascadeIndex); + } + } + + void WaitForDrawing() + { + for (int64 label : AsyncDrawWaitLabels) + JobSystem::Wait(label); + AsyncDrawWaitLabels.Clear(); + } + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) { for (auto& cascade : Cascades) @@ -256,9 +416,161 @@ public: namespace { - Dictionary ChunksCache; - Array RasterizeObjectsCache; - Dictionary ObjectIndexToDataIndexCache; + GlobalSignDistanceFieldCustomBuffer* Current = nullptr; + ThreadLocal CurrentCascade; +} + +void GlobalSignDistanceFieldCustomBuffer::DrawCascadeActors(const CascadeData& cascade) +{ + PROFILE_CPU(); + const BoundingBox cullingBounds = cascade.CullingBounds; + const uint32 viewMask = AsyncRenderContext.View.RenderLayersMask; + // 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, cascade.VoxelSize * 2.0f); // Skip too small objects for this cascade + int32 actorsDrawn = 0; + SceneRendering::DrawCategory drawCategories[] = { SceneRendering::SceneDraw, SceneRendering::SceneDrawAsync }; + for (auto* scene : AsyncRenderContext.List->Scenes) + { + for (SceneRendering::DrawCategory drawCategory : drawCategories) + { + auto& list = scene->Actors[drawCategory]; + for (const auto& e : list) + { + if (e.Bounds.Radius >= minObjectRadius && viewMask & e.LayerMask && CollisionsHelper::BoxIntersectsSphere(cullingBounds, e.Bounds)) + { + //PROFILE_CPU_ACTOR(e.Actor); + e.Actor->Draw(AsyncRenderContext); +#if COMPILE_WITH_PROFILER + actorsDrawn++; +#endif + } + } + } + } + ZoneValue(actorsDrawn); +} + +void GlobalSignDistanceFieldCustomBuffer::UpdateCascadeChunks(CascadeData& cascade) +{ + PROFILE_CPU(); + + // Update static chunks + for (auto it = cascade.Chunks.Begin(); it.IsNotEnd(); ++it) + { + auto& e = *it; + if (e.Key.Layer != 0) + continue; + if (e.Value.Dynamic) + { + // Remove static chunk with dynamic objects + cascade.StaticChunks.Remove(e.Key); + } + else if (cascade.StaticChunks.Contains(e.Key)) + { + // Skip updating static chunk + auto key = e.Key; + while (cascade.Chunks.Remove(key)) + key.NextLayer(); + } + else + { + // Add to cache (render now but skip next frame) + cascade.StaticChunks.Add(e.Key); + } + } +} + +void GlobalSignDistanceFieldCustomBuffer::WriteCascadeObjects(CascadeData& cascade) +{ + PROFILE_CPU(); + + // Write all objects to the buffer + int32 objectsBufferCount = 0; + cascade.ObjectsData.Clear(); + cascade.ObjectsTextures.Clear(); + cascade.ObjectIndexToDataIndex.Clear(); + for (const auto& e : cascade.Chunks) + { + auto& chunk = e.Value; + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + auto objectIndex = chunk.Models[i]; + if (cascade.ObjectIndexToDataIndex.ContainsKey(objectIndex)) + continue; + const auto& object = cascade.RasterizeObjects.Get()[objectIndex]; + + // Pick the SDF mip for the cascade + int32 mipLevelIndex = 1; + float worldUnitsPerVoxel = object.SDF->WorldUnitsPerVoxel * object.LocalToWorld.Scale.MaxValue() * 4; + const int32 mipLevels = object.SDF->Texture->MipLevels(); + while (cascade.VoxelSize > worldUnitsPerVoxel && mipLevelIndex < mipLevels) + { + mipLevelIndex++; + worldUnitsPerVoxel *= 2.0f; + } + mipLevelIndex--; + + // Add object data for the GPU buffer + uint16 dataIndex = objectsBufferCount++; + ObjectRasterizeData objectData; + Platform::MemoryClear(&objectData, sizeof(objectData)); + Matrix localToWorld, worldToLocal, volumeToWorld; + Matrix::Transformation(object.LocalToWorld.Scale, object.LocalToWorld.Orientation, object.LocalToWorld.Translation - Origin, localToWorld); + Matrix::Invert(localToWorld, worldToLocal); + BoundingBox localVolumeBounds(object.SDF->LocalBoundsMin, object.SDF->LocalBoundsMax); + Float3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; + Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); + Matrix::Invert(worldToVolume, volumeToWorld); + objectData.WorldToVolume.SetMatrixTranspose(worldToVolume); + objectData.VolumeToWorld.SetMatrixTranspose(volumeToWorld); + objectData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; + objectData.VolumeToUVWMul = object.SDF->LocalToUVWMul; + objectData.VolumeToUVWAdd = object.SDF->LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * object.SDF->LocalToUVWMul; + objectData.MipOffset = (float)mipLevelIndex; + objectData.DecodeMul = 2.0f * object.SDF->MaxDistance; + objectData.DecodeAdd = -object.SDF->MaxDistance; + cascade.ObjectsData.Add((const byte*)&objectData, sizeof(objectData)); + cascade.ObjectsTextures.Add(object.SDF->Texture->ViewVolume()); + cascade.PendingObjectTypes.Add(object.Actor->GetTypeHandle()); + cascade.ObjectIndexToDataIndex.Add(objectIndex, dataIndex); + } + for (int32 i = 0; i < chunk.HeightfieldsCount; i++) + { + auto objectIndex = chunk.Heightfields[i]; + if (cascade.ObjectIndexToDataIndex.ContainsKey(objectIndex)) + continue; + const auto& object = cascade.RasterizeObjects.Get()[objectIndex]; + + // Add object data for the GPU buffer + uint16 dataIndex = objectsBufferCount++; + ObjectRasterizeData objectData; + Platform::MemoryClear(&objectData, sizeof(objectData)); + Matrix localToWorld, worldToLocal; + Matrix::Transformation(object.LocalToWorld.Scale, object.LocalToWorld.Orientation, object.LocalToWorld.Translation - Origin, localToWorld); + Matrix::Invert(localToWorld, worldToLocal); + objectData.WorldToVolume.SetMatrixTranspose(worldToLocal); + objectData.VolumeToWorld.SetMatrixTranspose(localToWorld); + objectData.VolumeToUVWMul = Float3(object.LocalToUV.X, 1.0f, object.LocalToUV.Y); + objectData.VolumeToUVWAdd = Float3(object.LocalToUV.Z, 0.0f, object.LocalToUV.W); + objectData.MipOffset = (float)cascade.Index * 0.5f; // Use lower-quality mip for far cascades + cascade.ObjectsData.Add((const byte*)&objectData, sizeof(objectData)); + cascade.ObjectsTextures.Add(object.Heightfield->View()); + cascade.PendingObjectTypes.Add(object.Actor->GetTypeHandle()); + cascade.ObjectIndexToDataIndex.Add(objectIndex, dataIndex); + } + } +} + +void GlobalSignDistanceFieldCustomBuffer::DrawCascadeJob(int32 cascadeIndex) +{ + auto& cascade = Cascades[cascadeIndex]; + if (!cascade.Dirty) + return; + PROFILE_CPU(); + CurrentCascade.Set(&cascade); + DrawCascadeActors(cascade); + UpdateCascadeChunks(cascade); + WriteCascadeObjects(cascade); } String GlobalSignDistanceFieldPass::ToString() const @@ -309,7 +621,7 @@ bool GlobalSignDistanceFieldPass::setupResources() // Init buffer if (!_objectsBuffer) - _objectsBuffer = New(64u * (uint32)sizeof(ObjectRasterizeData), (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer")); + _objectsBuffer = New(0, (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer")); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; @@ -347,12 +659,22 @@ void GlobalSignDistanceFieldPass::Dispose() // Cleanup SAFE_DELETE(_objectsBuffer); - _objectsTextures.Resize(0); SAFE_DELETE_GPU_RESOURCE(_psDebug); _shader = nullptr; - ChunksCache.SetCapacity(0); - RasterizeObjectsCache.SetCapacity(0); - ObjectIndexToDataIndexCache.SetCapacity(0); +} + +void GlobalSignDistanceFieldPass::OnCollectDrawCalls(RenderContextBatch& renderContextBatch) +{ + // Check if Global SDF will be used this frame + PROFILE_CPU_NAMED("Global SDF"); + if (checkIfSkipPass()) + return; + RenderContext& renderContext = renderContextBatch.GetMainContext(); + if (renderContext.List->Scenes.Count() == 0) + return; + auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); + Current = &sdfData; + sdfData.StartDrawing(renderContext, renderContextBatch.EnableAsync); } bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) @@ -386,44 +708,19 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex PROFILE_GPU_CPU("Global SDF"); // Setup options - int32 resolution, cascadesCount; - switch (Graphics::GlobalSDFQuality) - { - case Quality::Low: - resolution = 128; - cascadesCount = 2; - break; - case Quality::Medium: - resolution = 128; - cascadesCount = 3; - break; - case Quality::High: - resolution = 192; - cascadesCount = 4; - break; - case Quality::Ultra: - default: - resolution = 256; - cascadesCount = 4; - break; - } - const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); - auto& giSettings = renderContext.List->Settings.GlobalIllumination; - float distance = GraphicsSettings::Get()->GlobalSDFDistance; - if (giSettings.Mode == GlobalIlluminationMode::DDGI) - distance = Math::Max(distance, giSettings.Distance); - distance = Math::Min(distance, renderContext.View.Far); - const float cascadesDistanceScales[] = { 1.0f, 2.5f, 5.0f, 10.0f }; - const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; + int32 resolution, cascadesCount, resolutionMip; + float distance; + sdfData.GetOptions(renderContext, resolution, cascadesCount, resolutionMip, distance); + const float distanceExtent = distance / sdfData.CascadesDistanceScales[cascadesCount - 1]; // Initialize buffers - bool updated = false; + bool reset = false; if (sdfData.Cascades.Count() != cascadesCount || sdfData.Resolution != resolution) { sdfData.Cascades.Resize(cascadesCount); sdfData.Resolution = resolution; sdfData.FrameIndex = 0; - updated = true; + reset = true; auto desc = GPUTextureDescription::New3D(resolution * cascadesCount, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); { GPUTexture*& texture = sdfData.Texture; @@ -463,10 +760,10 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex if (sdfData.Origin != renderContext.View.Origin) { sdfData.Origin = renderContext.View.Origin; - updated = true; + reset = true; } GPUTexture* tmpMip = nullptr; - if (updated) + if (reset) { PROFILE_GPU_CPU_NAMED("Init"); for (auto& cascade : sdfData.Cascades) @@ -480,126 +777,60 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (SceneRendering* scene : renderContext.List->Scenes) sdfData.ListenSceneRendering(scene); - // Calculate origin for Global SDF by shifting it towards the view direction to account for better view frustum coverage - Float3 viewPosition = renderContext.View.Position; - { - Float3 viewDirection = renderContext.View.Direction; - const float cascade0Distance = distanceExtent * cascadesDistanceScales[0]; - const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewPosition, viewPosition + viewDirection * (cascade0Distance * 2.0f), viewPosition - cascade0Distance, viewPosition + cascade0Distance); - const float viewOriginOffset = (float)viewRayHit.Y * cascade0Distance * 0.6f; - viewPosition += viewDirection * viewOriginOffset; - } + // Ensure that async objects drawing ended + Current = &sdfData; + sdfData.StartDrawing(renderContext, false, reset); // (ignored if not started earlier this frame) + sdfData.WaitForDrawing(); // Rasterize world geometry into Global SDF - renderContext.View.Pass = DrawPass::GlobalSDF; - uint32 viewMask = renderContext.View.RenderLayersMask; - const bool useCache = !updated && !GLOBAL_SDF_DEBUG_FORCE_REDRAW && GPU_SPREAD_WORKLOAD; - 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 bool updateEveryFrame = false; // true if update all cascades every frame - const int32 maxCascadeUpdatesPerFrame = 1; // maximum cascades to update at a single frame GPUTextureView* textureView = sdfData.Texture->ViewVolume(); GPUTextureView* textureMipView = sdfData.TextureMip->ViewVolume(); - if (sdfData.FrameIndex++ > 128) - sdfData.FrameIndex = 0; for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { - // Reduce frequency of the updates - if (useCache && !RenderTools::ShouldUpdateCascade(sdfData.FrameIndex, cascadeIndex, cascadesCount, maxCascadeUpdatesPerFrame, updateEveryFrame)) - continue; auto& cascade = sdfData.Cascades[cascadeIndex]; - const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; - const float cascadeMaxDistance = cascadeDistance * 2; - const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution; - const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); - const Float3 center = Float3::Floor(viewPosition / cascadeChunkSize) * cascadeChunkSize; - //const Float3 center = Float3::Zero; - BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); - // 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, cascadeVoxelSize * 2.0f); // Skip too small objects for this cascade + if (!cascade.Dirty) + continue; - // Clear cascade before rasterization + // Process all pending SDF textures tracking + for (auto& e : cascade.PendingSDFTextures) { - PROFILE_CPU_NAMED("Clear"); - chunks.Clear(); - RasterizeObjectsCache.Clear(); - _objectsBuffer->Clear(); - _objectsTextures.Clear(); - } - - // Check if cascade center has been moved - if (!(useCache && Float3::NearEqual(cascade.Position, center, cascadeVoxelSize))) - { - // TODO: optimize for moving camera (copy sdf for cached chunks) - cascade.StaticChunks.Clear(); - } - cascade.Position = center; - cascade.VoxelSize = cascadeVoxelSize; - cascade.Bounds = cascadeBounds; - - // Draw all objects from all scenes into the cascade - _objectsBufferCount = 0; - _voxelSize = cascadeVoxelSize; - _chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - _cascadeBounds = cascadeBounds; - _cascadeBounds.Minimum += 0.1f; // Adjust to prevent overflowing chunk keys (cascade bounds are used for clamping object bounds) - _cascadeBounds.Maximum -= 0.1f; // Adjust to prevent overflowing chunk keys (cascade bounds are used for clamping object bounds) - _cascadeIndex = cascadeIndex; - _sdfData = &sdfData; - const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; - _sdfDataOriginMin = -sdfData.Origin - objectMargin; - _sdfDataOriginMax = -sdfData.Origin + objectMargin; - { - PROFILE_CPU_NAMED("Draw"); - BoundingBox cascadeBoundsWorld = cascadeBounds.MakeOffsetted(sdfData.Origin); - _cascadeCullingBounds = cascadeBoundsWorld; - int32 actorsDrawn = 0; - SceneRendering::DrawCategory drawCategories[] = { SceneRendering::SceneDraw, SceneRendering::SceneDrawAsync }; - for (auto* scene : renderContext.List->Scenes) + GPUTexture* texture = e.Item; + if (Current->SDFTextures.Add(texture)) { - for (SceneRendering::DrawCategory drawCategory : drawCategories) - { - auto& list = scene->Actors[drawCategory]; - for (const auto& e : list) - { - if (e.Bounds.Radius >= minObjectRadius && viewMask & e.LayerMask && CollisionsHelper::BoxIntersectsSphere(cascadeBoundsWorld, e.Bounds)) - { - //PROFILE_CPU_ACTOR(e.Actor); - e.Actor->Draw(renderContext); - actorsDrawn++; - } - } - } + texture->Deleted.Bind(Current); + texture->ResidentMipsChanged.Bind(Current); } - ZoneValue(actorsDrawn); } + cascade.PendingSDFTextures.Clear(); + + // Process all pending object types tracking + for (auto& e : cascade.PendingObjectTypes) + sdfData.ObjectTypes.Add(e.Item); // Perform batched chunks rasterization anyDraw = true; context->ResetSR(); ModelsRasterizeData data; - data.CascadeCoordToPosMul = (Float3)cascadeBounds.GetSize() / (float)resolution; - data.CascadeCoordToPosAdd = (Float3)cascadeBounds.Minimum + cascadeVoxelSize * 0.5f; - data.MaxDistance = cascadeMaxDistance; + data.CascadeCoordToPosMul = (Float3)cascade.Bounds.GetSize() / (float)resolution; + data.CascadeCoordToPosAdd = (Float3)cascade.Bounds.Minimum + cascade.VoxelSize * 0.5f; + data.MaxDistance = cascade.MaxDistance; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; data.CascadeIndex = cascadeIndex; data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; - data.CascadeVoxelSize = cascadeVoxelSize; + data.CascadeVoxelSize = cascade.VoxelSize; context->BindUA(0, textureView); context->BindCB(1, _cb1); - const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; + constexpr int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; bool anyChunkDispatch = false; + if (!reset) { PROFILE_GPU_CPU_NAMED("Clear Chunks"); for (auto it = cascade.NonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { auto& key = it->Item; - if (chunks.ContainsKey(key)) + if (cascade.Chunks.ContainsKey(key) || cascade.StaticChunks.Contains(key)) continue; // Clear empty chunk @@ -614,121 +845,21 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { PROFILE_GPU_CPU_NAMED("Rasterize Chunks"); - // Update static chunks - for (auto it = chunks.Begin(); it.IsNotEnd(); ++it) - { - auto& e = *it; - if (e.Key.Layer != 0) - continue; - if (e.Value.Dynamic) - { - // Remove static chunk with dynamic objects - cascade.StaticChunks.Remove(e.Key); - } - else if (cascade.StaticChunks.Contains(e.Key)) - { - // Skip updating static chunk - auto key = e.Key; - while (chunks.Remove(key)) - key.NextLayer(); - } - else - { - // Add to cache (render now but skip next frame) - cascade.StaticChunks.Add(e.Key); - } - } - // Send models data to the GPU - const auto& objectIndexToDataIndex = ObjectIndexToDataIndexCache; - if (chunks.Count() != 0) + const auto& objectIndexToDataIndex = cascade.ObjectIndexToDataIndex; + GPUTextureView** objectsTextures = cascade.ObjectsTextures.Get(); + if (cascade.Chunks.Count() != 0) { - PROFILE_GPU_CPU_NAMED("Update Objects"); - auto& objectIndexToDataIndexCache = ObjectIndexToDataIndexCache; - objectIndexToDataIndexCache.Clear(); - - // Write used objects to the buffer - const auto& rasterizeObjectsCache = RasterizeObjectsCache; - for (const auto& e : chunks) - { - auto& chunk = e.Value; - for (int32 i = 0; i < chunk.ModelsCount; i++) - { - auto objectIndex = chunk.Models[i]; - if (objectIndexToDataIndexCache.ContainsKey(objectIndex)) - continue; - const auto& object = rasterizeObjectsCache.Get()[objectIndex]; - - // Pick the SDF mip for the cascade - int32 mipLevelIndex = 1; - float worldUnitsPerVoxel = object.SDF->WorldUnitsPerVoxel * object.LocalToWorld.Scale.MaxValue() * 4; - const int32 mipLevels = object.SDF->Texture->MipLevels(); - while (_voxelSize > worldUnitsPerVoxel && mipLevelIndex < mipLevels) - { - mipLevelIndex++; - worldUnitsPerVoxel *= 2.0f; - } - mipLevelIndex--; - - // Add object data for the GPU buffer - uint16 dataIndex = _objectsBufferCount++; - ObjectRasterizeData objectData; - Matrix localToWorld, worldToLocal, volumeToWorld; - Matrix::Transformation(object.LocalToWorld.Scale, object.LocalToWorld.Orientation, object.LocalToWorld.Translation - _sdfData->Origin, localToWorld); - Matrix::Invert(localToWorld, worldToLocal); - BoundingBox localVolumeBounds(object.SDF->LocalBoundsMin, object.SDF->LocalBoundsMax); - Float3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; - Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); - Matrix::Invert(worldToVolume, volumeToWorld); - objectData.WorldToVolume.SetMatrixTranspose(worldToVolume); - objectData.VolumeToWorld.SetMatrixTranspose(volumeToWorld); - objectData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; - objectData.VolumeToUVWMul = object.SDF->LocalToUVWMul; - objectData.VolumeToUVWAdd = object.SDF->LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * object.SDF->LocalToUVWMul; - objectData.MipOffset = (float)mipLevelIndex; - objectData.DecodeMul = 2.0f * object.SDF->MaxDistance; - objectData.DecodeAdd = -object.SDF->MaxDistance; - _objectsBuffer->Write(objectData); - _objectsTextures.Add(object.SDF->Texture->ViewVolume()); - _sdfData->ObjectTypes.Add(object.Actor->GetTypeHandle()); - - // Cache the mapping - objectIndexToDataIndexCache.Add(objectIndex, dataIndex); - } - for (int32 i = 0; i < chunk.HeightfieldsCount; i++) - { - auto objectIndex = chunk.Heightfields[i]; - if (objectIndexToDataIndexCache.ContainsKey(objectIndex)) - continue; - const auto& object = rasterizeObjectsCache.Get()[objectIndex]; - - // Add object data for the GPU buffer - uint16 dataIndex = _objectsBufferCount++; - ObjectRasterizeData objectData; - Matrix localToWorld, worldToLocal; - Matrix::Transformation(object.LocalToWorld.Scale, object.LocalToWorld.Orientation, object.LocalToWorld.Translation - _sdfData->Origin, localToWorld); - Matrix::Invert(localToWorld, worldToLocal); - objectData.WorldToVolume.SetMatrixTranspose(worldToLocal); - objectData.VolumeToWorld.SetMatrixTranspose(localToWorld); - objectData.VolumeToUVWMul = Float3(object.LocalToUV.X, 1.0f, object.LocalToUV.Y); - objectData.VolumeToUVWAdd = Float3(object.LocalToUV.Z, 0.0f, object.LocalToUV.W); - objectData.MipOffset = (float)_cascadeIndex * 0.5f; // Use lower-quality mip for far cascades - _objectsBuffer->Write(objectData); - _objectsTextures.Add(object.Heightfield->View()); - _sdfData->ObjectTypes.Add(object.Actor->GetTypeHandle()); - - // Cache the mapping - objectIndexToDataIndexCache.Add(objectIndex, dataIndex); - } - } - - // Flush buffer + // Flush buffer but don't allocate any CPU memory by swapping Data pointer with the cascade ObjectsData + PROFILE_CPU_NAMED("Update Objects"); + _objectsBuffer->Data.Swap(cascade.ObjectsData); _objectsBuffer->Flush(context); + _objectsBuffer->Data.Swap(cascade.ObjectsData); } context->BindSR(0, _objectsBuffer->GetBuffer() ? _objectsBuffer->GetBuffer()->View() : nullptr); // Rasterize non-empty chunks (first layer so can override existing chunk data) - for (const auto& e : chunks) + for (const auto& e : cascade.Chunks) { if (e.Key.Layer != 0) continue; @@ -739,7 +870,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { auto objectIndex = objectIndexToDataIndex.At(chunk.Models[i]); data.Objects[i] = objectIndex; - context->BindSR(i + 1, _objectsTextures[objectIndex]); + context->BindSR(i + 1, objectsTextures[objectIndex]); } for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) context->UnBindSR(i + 1); @@ -758,7 +889,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { auto objectIndex = objectIndexToDataIndex.At(chunk.Heightfields[i]); data.Objects[i] = objectIndex; - context->BindSR(i + 1, _objectsTextures[objectIndex]); + context->BindSR(i + 1, objectsTextures[objectIndex]); } for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) context->UnBindSR(i + 1); @@ -774,21 +905,21 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex int32 count = chunk.ModelsCount + chunk.HeightfieldsCount; RasterizeChunkKey tmp = e.Key; tmp.NextLayer(); - while (chunks.ContainsKey(tmp)) + while (cascade.Chunks.ContainsKey(tmp)) { - count += chunks[tmp].ModelsCount + chunks[tmp].HeightfieldsCount; + count += cascade.Chunks[tmp].ModelsCount + cascade.Chunks[tmp].HeightfieldsCount; tmp.NextLayer(); } - Float3 chunkMin = cascadeBounds.Minimum + Float3(e.Key.Coord) * cascadeChunkSize; - BoundingBox chunkBounds(chunkMin, chunkMin + cascadeChunkSize); + Float3 chunkMin = cascade.Bounds.Minimum + Float3(e.Key.Coord) * cascade.ChunkSize; + BoundingBox chunkBounds(chunkMin, chunkMin + cascade.ChunkSize); DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); DebugDraw::DrawText(StringUtils::ToString(count), chunkBounds.GetCenter(), Color::Red); } #endif } - // Rasterize non-empty chunks (additive layers so so need combine with existing chunk data) - for (const auto& e : chunks) + // Rasterize non-empty chunks (additive layers so need combine with existing chunk data) + for (const auto& e : cascade.Chunks) { if (e.Key.Layer == 0) continue; @@ -802,7 +933,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { auto objectIndex = objectIndexToDataIndex.At(chunk.Models[i]); data.Objects[i] = objectIndex; - context->BindSR(i + 1, _objectsTextures[objectIndex]); + context->BindSR(i + 1, objectsTextures[objectIndex]); } for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) context->UnBindSR(i + 1); @@ -818,7 +949,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { auto objectIndex = objectIndexToDataIndex.At(chunk.Heightfields[i]); data.Objects[i] = objectIndex; - context->BindSR(i + 1, _objectsTextures[objectIndex]); + context->BindSR(i + 1, objectsTextures[objectIndex]); } for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) context->UnBindSR(i + 1); @@ -831,13 +962,13 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) - if (updated || anyChunkDispatch) + if (reset || anyChunkDispatch) { PROFILE_GPU_CPU_NAMED("Generate Mip"); context->ResetUA(); const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); static_assert((GLOBAL_SDF_MIP_FLOODS % 2) == 1, "Invalid Global SDF mip flood iterations count."); - int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; + int32 floodFillIterations = cascade.Chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; if (!tmpMip) { // Use temporary texture to flood fill mip @@ -850,7 +981,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex GPUTextureView* tmpMipView = tmpMip->ViewVolume(); // Tex -> Mip - // TODO: use push constants on DX12/Vulkan to provide those 4 uints to the shader data.GenerateMipTexResolution = data.CascadeResolution; data.GenerateMipCoordScale = data.CascadeMipFactor; data.GenerateMipTexOffsetX = data.CascadeIndex * data.CascadeResolution; @@ -903,7 +1033,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; - const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; + const float cascadeDistance = distanceExtent * sdfData.CascadesDistanceScales[cascadeIndex]; const float cascadeMaxDistance = cascadeDistance * 2; const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution; const Float3 center = cascade.Position; @@ -952,26 +1082,33 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->DrawFullscreenTriangle(); } +void GlobalSignDistanceFieldPass::GetCullingData(BoundingBox& bounds) const +{ + auto& cascade = *CurrentCascade.Get(); + bounds = cascade.CullingBounds; +} + void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Transform& localToWorld, const BoundingBox& objectBounds) { if (!sdf.Texture) return; + auto& cascade = *CurrentCascade.Get(); const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); const int32 residentMipLevels = sdf.Texture->ResidentMipLevels(); if (residentMipLevels != 0) { // Setup object data BoundingBox objectBoundsCascade; - Vector3::Clamp(objectBounds.Minimum + _sdfDataOriginMin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); - Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); - Vector3::Clamp(objectBounds.Maximum + _sdfDataOriginMax, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); - Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); - const Int3 objectChunkMin(objectBoundsCascade.Minimum / _chunkSize); - const Int3 objectChunkMax(objectBoundsCascade.Maximum / _chunkSize); + Vector3::Clamp(objectBounds.Minimum + cascade.OriginMin, cascade.RasterizeBounds.Minimum, cascade.RasterizeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, cascade.RasterizeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + cascade.OriginMax, cascade.RasterizeBounds.Minimum, cascade.RasterizeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, cascade.RasterizeBounds.Minimum, objectBoundsCascade.Maximum); + const Int3 objectChunkMin(objectBoundsCascade.Minimum / cascade.ChunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / cascade.ChunkSize); // Add object data - const uint16 dataIndex = RasterizeObjectsCache.Count(); - auto& data = RasterizeObjectsCache.AddOne(); + const uint16 dataIndex = cascade.RasterizeObjects.Count(); + auto& data = cascade.RasterizeObjects.AddOne(); data.Actor = actor; data.SDF = &sdf; data.LocalToWorld = localToWorld; @@ -979,7 +1116,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas // Inject object into the intersecting cascade chunks RasterizeChunkKey key; - auto& chunks = ChunksCache; + auto& chunks = cascade.Chunks; for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) { for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) @@ -1005,11 +1142,9 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas } // Track streaming for textures used in static chunks to invalidate cache - if (!dynamic && residentMipLevels != sdf.Texture->MipLevels() && !_sdfData->SDFTextures.Contains(sdf.Texture)) + if (!dynamic && residentMipLevels != sdf.Texture->MipLevels() && !Current->SDFTextures.Contains(sdf.Texture)) { - sdf.Texture->Deleted.Bind(_sdfData); - sdf.Texture->ResidentMipsChanged.Bind(_sdfData); - _sdfData->SDFTextures.Add(sdf.Texture); + cascade.PendingSDFTextures.Add(sdf.Texture); } } @@ -1017,22 +1152,23 @@ void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* { if (!heightfield) return; + auto& cascade = *CurrentCascade.Get(); const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); const int32 residentMipLevels = heightfield->ResidentMipLevels(); if (residentMipLevels != 0) { // Setup object data BoundingBox objectBoundsCascade; - Vector3::Clamp(objectBounds.Minimum + _sdfDataOriginMin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); - Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); - Vector3::Clamp(objectBounds.Maximum + _sdfDataOriginMax, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); - Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); - const Int3 objectChunkMin(objectBoundsCascade.Minimum / _chunkSize); - const Int3 objectChunkMax(objectBoundsCascade.Maximum / _chunkSize); + Vector3::Clamp(objectBounds.Minimum + cascade.OriginMin, cascade.RasterizeBounds.Minimum, cascade.RasterizeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, cascade.RasterizeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + cascade.OriginMax, cascade.RasterizeBounds.Minimum, cascade.RasterizeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, cascade.RasterizeBounds.Minimum, objectBoundsCascade.Maximum); + const Int3 objectChunkMin(objectBoundsCascade.Minimum / cascade.ChunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / cascade.ChunkSize); // Add object data - const uint16 dataIndex = RasterizeObjectsCache.Count(); - auto& data = RasterizeObjectsCache.AddOne(); + const uint16 dataIndex = cascade.RasterizeObjects.Count(); + auto& data = cascade.RasterizeObjects.AddOne(); data.Actor = actor; data.Heightfield = heightfield; data.LocalToWorld = localToWorld; @@ -1041,7 +1177,7 @@ void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* // Inject object into the intersecting cascade chunks RasterizeChunkKey key; - auto& chunks = ChunksCache; + auto& chunks = cascade.Chunks; for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) { for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) @@ -1067,10 +1203,8 @@ void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* } // Track streaming for textures used in static chunks to invalidate cache - if (!dynamic && residentMipLevels != heightfield->MipLevels() && !_sdfData->SDFTextures.Contains(heightfield)) + if (!dynamic && residentMipLevels != heightfield->MipLevels() && !Current->SDFTextures.Contains(heightfield)) { - heightfield->Deleted.Bind(_sdfData); - heightfield->ResidentMipsChanged.Bind(_sdfData); - _sdfData->SDFTextures.Add(heightfield); + cascade.PendingSDFTextures.Add(heightfield); } } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 71ee97bea..104aae790 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -39,20 +39,15 @@ private: GPUShaderProgramCS* _csGenerateMip = nullptr; GPUConstantBuffer* _cb0 = nullptr; GPUConstantBuffer* _cb1 = nullptr; - - // Rasterization cache class DynamicStructuredBuffer* _objectsBuffer = nullptr; - Array _objectsTextures; - uint16 _objectsBufferCount; - int32 _cascadeIndex; - float _voxelSize, _chunkSize; - BoundingBox _cascadeBounds; - BoundingBox _cascadeCullingBounds; - class GlobalSignDistanceFieldCustomBuffer* _sdfData; - Vector3 _sdfDataOriginMin; - Vector3 _sdfDataOriginMax; public: + /// + /// Calls drawing scene objects in async early in the frame. + /// + /// The rendering context batch. + void OnCollectDrawCalls(RenderContextBatch& renderContextBatch); + /// /// Gets the Global SDF (only if enabled in Graphics Settings). /// @@ -78,10 +73,7 @@ public: /// The output buffer. void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); - void GetCullingData(BoundingBox& bounds) const - { - bounds = _cascadeCullingBounds; - } + void GetCullingData(BoundingBox& bounds) const; // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Transform& localToWorld, const BoundingBox& objectBounds); diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index b33e3ad47..d6fbb17cc 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -409,6 +409,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont JobSystem::SetJobStartingOnDispatch(false); task->OnCollectDrawCalls(renderContextBatch, SceneRendering::DrawCategory::SceneDraw); task->OnCollectDrawCalls(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync); + if (setup.UseGlobalSDF) + GlobalSignDistanceFieldPass::Instance()->OnCollectDrawCalls(renderContextBatch); if (setup.UseGlobalSurfaceAtlas) GlobalSurfaceAtlasPass::Instance()->OnCollectDrawCalls(renderContextBatch);