Optimize Global SDF drawing with async job system
This commit is contained in:
@@ -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<RasterizeChunkKey> NonEmptyChunks;
|
||||
HashSet<RasterizeChunkKey> StaticChunks;
|
||||
|
||||
FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds)
|
||||
// Cache
|
||||
Dictionary<RasterizeChunkKey, RasterizeChunk> Chunks;
|
||||
Array<RasterizeObject> RasterizeObjects;
|
||||
Array<byte> ObjectsData;
|
||||
Array<GPUTextureView*> ObjectsTextures;
|
||||
Dictionary<uint16, uint16> ObjectIndexToDataIndex;
|
||||
HashSet<GPUTexture*> PendingSDFTextures;
|
||||
HashSet<ScriptingTypeHandle> 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<GPUTexture*> SDFTextures;
|
||||
GlobalSignDistanceFieldPass::BindingData Result;
|
||||
|
||||
// Async objects drawing cache
|
||||
Array<int64, FixedAllocation<1>> AsyncDrawWaitLabels;
|
||||
RenderContext AsyncRenderContext;
|
||||
|
||||
~GlobalSignDistanceFieldCustomBuffer()
|
||||
{
|
||||
WaitForDrawing();
|
||||
for (const auto& e : SDFTextures)
|
||||
{
|
||||
e.Item->Deleted.Unbind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(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<void(int32)> func;
|
||||
func.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::DrawCascadeJob>(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<RasterizeChunkKey, RasterizeChunk> ChunksCache;
|
||||
Array<RasterizeObject> RasterizeObjectsCache;
|
||||
Dictionary<uint16, uint16> ObjectIndexToDataIndexCache;
|
||||
GlobalSignDistanceFieldCustomBuffer* Current = nullptr;
|
||||
ThreadLocal<CascadeData*, 16> 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<DynamicStructuredBuffer>(64u * (uint32)sizeof(ObjectRasterizeData), (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer"));
|
||||
_objectsBuffer = New<DynamicStructuredBuffer>(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<GlobalSignDistanceFieldCustomBuffer>(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<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(Current);
|
||||
texture->ResidentMipsChanged.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureResidentMipsChanged>(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<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(_sdfData);
|
||||
sdf.Texture->ResidentMipsChanged.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureResidentMipsChanged>(_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<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(_sdfData);
|
||||
heightfield->ResidentMipsChanged.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureResidentMipsChanged>(_sdfData);
|
||||
_sdfData->SDFTextures.Add(heightfield);
|
||||
cascade.PendingSDFTextures.Add(heightfield);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,20 +39,15 @@ private:
|
||||
GPUShaderProgramCS* _csGenerateMip = nullptr;
|
||||
GPUConstantBuffer* _cb0 = nullptr;
|
||||
GPUConstantBuffer* _cb1 = nullptr;
|
||||
|
||||
// Rasterization cache
|
||||
class DynamicStructuredBuffer* _objectsBuffer = nullptr;
|
||||
Array<GPUTextureView*> _objectsTextures;
|
||||
uint16 _objectsBufferCount;
|
||||
int32 _cascadeIndex;
|
||||
float _voxelSize, _chunkSize;
|
||||
BoundingBox _cascadeBounds;
|
||||
BoundingBox _cascadeCullingBounds;
|
||||
class GlobalSignDistanceFieldCustomBuffer* _sdfData;
|
||||
Vector3 _sdfDataOriginMin;
|
||||
Vector3 _sdfDataOriginMax;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Calls drawing scene objects in async early in the frame.
|
||||
/// </summary>
|
||||
/// <param name="renderContextBatch">The rendering context batch.</param>
|
||||
void OnCollectDrawCalls(RenderContextBatch& renderContextBatch);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Global SDF (only if enabled in Graphics Settings).
|
||||
/// </summary>
|
||||
@@ -78,10 +73,7 @@ public:
|
||||
/// <param name="output">The output buffer.</param>
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user