|
|
|
|
@@ -110,6 +110,7 @@ public:
|
|
|
|
|
GPUTexture* CascadeMips[4] = {};
|
|
|
|
|
Vector3 Positions[4];
|
|
|
|
|
HashSet<RasterizeChunkKey> NonEmptyChunks[4];
|
|
|
|
|
GlobalSignDistanceFieldPass::BindingData Result;
|
|
|
|
|
|
|
|
|
|
~GlobalSignDistanceFieldCustomBuffer()
|
|
|
|
|
{
|
|
|
|
|
@@ -212,6 +213,17 @@ void GlobalSignDistanceFieldPass::Dispose()
|
|
|
|
|
ChunksCache.SetCapacity(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result)
|
|
|
|
|
{
|
|
|
|
|
auto* sdfData = buffers->FindCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField"));
|
|
|
|
|
if (sdfData && sdfData->LastFrameUsed == Engine::FrameCount)
|
|
|
|
|
{
|
|
|
|
|
result = sdfData->Result;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result)
|
|
|
|
|
{
|
|
|
|
|
// Skip if not supported
|
|
|
|
|
@@ -221,6 +233,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
|
|
|
|
return true;
|
|
|
|
|
auto& sdfData = *renderContext.Buffers->GetCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField"));
|
|
|
|
|
|
|
|
|
|
// Skip if already done in the current frame
|
|
|
|
|
const auto currentFrame = Engine::FrameCount;
|
|
|
|
|
if (sdfData.LastFrameUsed == currentFrame)
|
|
|
|
|
{
|
|
|
|
|
result = sdfData.Result;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PROFILE_GPU_CPU("Global SDF");
|
|
|
|
|
|
|
|
|
|
// TODO: configurable via graphics settings
|
|
|
|
|
const int32 resolution = 256;
|
|
|
|
|
const int32 mipFactor = 4;
|
|
|
|
|
@@ -229,238 +251,231 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
|
|
|
|
const float distanceExtent = 2500.0f;
|
|
|
|
|
const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f };
|
|
|
|
|
|
|
|
|
|
// Skip if already done in the current frame
|
|
|
|
|
const auto currentFrame = Engine::FrameCount;
|
|
|
|
|
if (sdfData.LastFrameUsed != currentFrame)
|
|
|
|
|
// Initialize buffers
|
|
|
|
|
sdfData.LastFrameUsed = currentFrame;
|
|
|
|
|
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
|
|
|
|
bool updated = false;
|
|
|
|
|
for (GPUTexture*& cascade : sdfData.Cascades)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Global SDF");
|
|
|
|
|
|
|
|
|
|
// Initialize buffers
|
|
|
|
|
sdfData.LastFrameUsed = currentFrame;
|
|
|
|
|
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
|
|
|
|
bool updated = false;
|
|
|
|
|
for (GPUTexture*& cascade : sdfData.Cascades)
|
|
|
|
|
if (cascade && cascade->Width() != desc.Width)
|
|
|
|
|
{
|
|
|
|
|
if (cascade && cascade->Width() != desc.Width)
|
|
|
|
|
{
|
|
|
|
|
RenderTargetPool::Release(cascade);
|
|
|
|
|
cascade = nullptr;
|
|
|
|
|
}
|
|
|
|
|
RenderTargetPool::Release(cascade);
|
|
|
|
|
cascade = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!cascade)
|
|
|
|
|
{
|
|
|
|
|
cascade = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!cascade)
|
|
|
|
|
{
|
|
|
|
|
cascade = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!cascade)
|
|
|
|
|
return true;
|
|
|
|
|
updated = true;
|
|
|
|
|
PROFILE_GPU_CPU("Init");
|
|
|
|
|
context->ClearUA(cascade, Vector4::One);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
updated = true;
|
|
|
|
|
PROFILE_GPU_CPU("Init");
|
|
|
|
|
context->ClearUA(cascade, Vector4::One);
|
|
|
|
|
}
|
|
|
|
|
desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
|
|
|
|
for (GPUTexture*& cascadeMip : sdfData.CascadeMips)
|
|
|
|
|
}
|
|
|
|
|
desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
|
|
|
|
for (GPUTexture*& cascadeMip : sdfData.CascadeMips)
|
|
|
|
|
{
|
|
|
|
|
if (cascadeMip && cascadeMip->Width() != desc.Width)
|
|
|
|
|
{
|
|
|
|
|
if (cascadeMip && cascadeMip->Width() != desc.Width)
|
|
|
|
|
{
|
|
|
|
|
RenderTargetPool::Release(cascadeMip);
|
|
|
|
|
cascadeMip = nullptr;
|
|
|
|
|
}
|
|
|
|
|
RenderTargetPool::Release(cascadeMip);
|
|
|
|
|
cascadeMip = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!cascadeMip)
|
|
|
|
|
{
|
|
|
|
|
cascadeMip = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!cascadeMip)
|
|
|
|
|
return true;
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
GPUTexture* tmpMip = nullptr;
|
|
|
|
|
if (updated)
|
|
|
|
|
LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024);
|
|
|
|
|
|
|
|
|
|
// Rasterize world geometry into Global SDF
|
|
|
|
|
renderContext.View.Pass = DrawPass::GlobalSDF;
|
|
|
|
|
uint32 viewMask = renderContext.View.RenderLayersMask;
|
|
|
|
|
const bool useCache = !updated && !renderContext.Task->IsCameraCut;
|
|
|
|
|
static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size.");
|
|
|
|
|
const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE);
|
|
|
|
|
auto& chunks = ChunksCache;
|
|
|
|
|
chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false);
|
|
|
|
|
bool anyDraw = false;
|
|
|
|
|
const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 };
|
|
|
|
|
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
|
|
|
|
|
for (int32 cascade = 0; cascade < 4; cascade++)
|
|
|
|
|
{
|
|
|
|
|
// Reduce frequency of the updates
|
|
|
|
|
if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0)
|
|
|
|
|
continue;
|
|
|
|
|
const float distance = cascadesDistances[cascade];
|
|
|
|
|
const float maxDistance = distance * 2;
|
|
|
|
|
const float voxelSize = maxDistance / resolution;
|
|
|
|
|
const float snapping = voxelSize * mipFactor;
|
|
|
|
|
const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping;
|
|
|
|
|
// TODO: cascade scrolling on movement to reduce dirty chunks?
|
|
|
|
|
//const Vector3 center = Vector3::Zero;
|
|
|
|
|
sdfData.Positions[cascade] = center;
|
|
|
|
|
BoundingBox cascadeBounds(center - distance, center + distance);
|
|
|
|
|
// TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality)
|
|
|
|
|
const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade
|
|
|
|
|
const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume();
|
|
|
|
|
GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume();
|
|
|
|
|
|
|
|
|
|
// Clear cascade before rasterization
|
|
|
|
|
{
|
|
|
|
|
PROFILE_CPU_NAMED("Clear");
|
|
|
|
|
chunks.Clear();
|
|
|
|
|
_modelsBuffer->Clear();
|
|
|
|
|
_modelsTextures.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw all objects from all scenes into the cascade
|
|
|
|
|
_modelsBufferCount = 0;
|
|
|
|
|
_voxelSize = voxelSize;
|
|
|
|
|
_cascadeBounds = cascadeBounds;
|
|
|
|
|
for (auto* scene : renderContext.List->Scenes)
|
|
|
|
|
{
|
|
|
|
|
// TODO: optimize for moving camera (copy sdf)
|
|
|
|
|
// TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering)
|
|
|
|
|
for (auto& e : scene->Actors)
|
|
|
|
|
{
|
|
|
|
|
cascadeMip = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!cascadeMip)
|
|
|
|
|
return true;
|
|
|
|
|
updated = true;
|
|
|
|
|
if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds))
|
|
|
|
|
{
|
|
|
|
|
e.Actor->Draw(renderContext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
GPUTexture* tmpMip = nullptr;
|
|
|
|
|
if (updated)
|
|
|
|
|
LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024);
|
|
|
|
|
|
|
|
|
|
// Rasterize world geometry into Global SDF
|
|
|
|
|
renderContext.View.Pass = DrawPass::GlobalSDF;
|
|
|
|
|
uint32 viewMask = renderContext.View.RenderLayersMask;
|
|
|
|
|
const bool useCache = !updated && !renderContext.Task->IsCameraCut;
|
|
|
|
|
static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size.");
|
|
|
|
|
const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE);
|
|
|
|
|
auto& chunks = ChunksCache;
|
|
|
|
|
chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false);
|
|
|
|
|
bool anyDraw = false;
|
|
|
|
|
const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 };
|
|
|
|
|
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
|
|
|
|
|
for (int32 cascade = 0; cascade < 4; cascade++)
|
|
|
|
|
// Send models data to the GPU
|
|
|
|
|
{
|
|
|
|
|
// Reduce frequency of the updates
|
|
|
|
|
if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0)
|
|
|
|
|
continue;
|
|
|
|
|
const float distance = cascadesDistances[cascade];
|
|
|
|
|
const float maxDistance = distance * 2;
|
|
|
|
|
const float voxelSize = maxDistance / resolution;
|
|
|
|
|
const float snapping = voxelSize * mipFactor;
|
|
|
|
|
const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping;
|
|
|
|
|
// TODO: cascade scrolling on movement to reduce dirty chunks?
|
|
|
|
|
//const Vector3 center = Vector3::Zero;
|
|
|
|
|
sdfData.Positions[cascade] = center;
|
|
|
|
|
BoundingBox cascadeBounds(center - distance, center + distance);
|
|
|
|
|
// TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality)
|
|
|
|
|
const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade
|
|
|
|
|
const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume();
|
|
|
|
|
GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume();
|
|
|
|
|
PROFILE_GPU_CPU("Update Models");
|
|
|
|
|
_modelsBuffer->Flush(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear cascade before rasterization
|
|
|
|
|
// Perform batched chunks rasterization
|
|
|
|
|
if (!anyDraw)
|
|
|
|
|
{
|
|
|
|
|
anyDraw = true;
|
|
|
|
|
context->ResetSR();
|
|
|
|
|
tmpMip = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!tmpMip)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ModelsRasterizeData data;
|
|
|
|
|
data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution;
|
|
|
|
|
data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f;
|
|
|
|
|
data.MaxDistance = maxDistance;
|
|
|
|
|
data.CascadeResolution = resolution;
|
|
|
|
|
data.CascadeMipResolution = resolutionMip;
|
|
|
|
|
data.CascadeMipFactor = mipFactor;
|
|
|
|
|
context->BindUA(0, cascadeView);
|
|
|
|
|
context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr);
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->BindCB(1, _cb1);
|
|
|
|
|
const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE;
|
|
|
|
|
auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade];
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Clear Chunks");
|
|
|
|
|
for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_CPU_NAMED("Clear");
|
|
|
|
|
chunks.Clear();
|
|
|
|
|
_modelsBuffer->Clear();
|
|
|
|
|
_modelsTextures.Clear();
|
|
|
|
|
}
|
|
|
|
|
auto& key = it->Item;
|
|
|
|
|
if (chunks.ContainsKey(key))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Draw all objects from all scenes into the cascade
|
|
|
|
|
_modelsBufferCount = 0;
|
|
|
|
|
_voxelSize = voxelSize;
|
|
|
|
|
_cascadeBounds = cascadeBounds;
|
|
|
|
|
for (auto* scene : renderContext.List->Scenes)
|
|
|
|
|
{
|
|
|
|
|
// TODO: optimize for moving camera (copy sdf)
|
|
|
|
|
// TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering)
|
|
|
|
|
for (auto& e : scene->Actors)
|
|
|
|
|
{
|
|
|
|
|
if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds))
|
|
|
|
|
{
|
|
|
|
|
e.Actor->Draw(renderContext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send models data to the GPU
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Update Models");
|
|
|
|
|
_modelsBuffer->Flush(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Perform batched chunks rasterization
|
|
|
|
|
if (!anyDraw)
|
|
|
|
|
{
|
|
|
|
|
anyDraw = true;
|
|
|
|
|
context->ResetSR();
|
|
|
|
|
tmpMip = RenderTargetPool::Get(desc);
|
|
|
|
|
if (!tmpMip)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ModelsRasterizeData data;
|
|
|
|
|
data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution;
|
|
|
|
|
data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f;
|
|
|
|
|
data.MaxDistance = maxDistance;
|
|
|
|
|
data.CascadeResolution = resolution;
|
|
|
|
|
data.CascadeMipResolution = resolutionMip;
|
|
|
|
|
data.CascadeMipFactor = mipFactor;
|
|
|
|
|
context->BindUA(0, cascadeView);
|
|
|
|
|
context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr);
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->BindCB(1, _cb1);
|
|
|
|
|
const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE;
|
|
|
|
|
auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade];
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Clear Chunks");
|
|
|
|
|
for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it)
|
|
|
|
|
{
|
|
|
|
|
auto& key = it->Item;
|
|
|
|
|
if (chunks.ContainsKey(key))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Clear empty chunk
|
|
|
|
|
nonEmptyChunks.Remove(it);
|
|
|
|
|
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->UpdateCB(_cb1, &data);
|
|
|
|
|
context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
|
|
|
|
|
// TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Rasterize Chunks");
|
|
|
|
|
for (auto& e : chunks)
|
|
|
|
|
{
|
|
|
|
|
// Rasterize non-empty chunk
|
|
|
|
|
auto& key = e.Key;
|
|
|
|
|
auto& chunk = e.Value;
|
|
|
|
|
for (int32 i = 0; i < chunk.ModelsCount; i++)
|
|
|
|
|
{
|
|
|
|
|
int32 model = chunk.Models[i];
|
|
|
|
|
data.Models[i] = model;
|
|
|
|
|
context->BindSR(i + 1, _modelsTextures[model]);
|
|
|
|
|
}
|
|
|
|
|
ASSERT_LOW_LAYER(chunk.ModelsCount != 0);
|
|
|
|
|
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
data.ModelsCount = chunk.ModelsCount;
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->UpdateCB(_cb1, &data);
|
|
|
|
|
GPUShaderProgramCS* cs;
|
|
|
|
|
if (key.Layer == 0)
|
|
|
|
|
{
|
|
|
|
|
// First layer so can override existing chunk data
|
|
|
|
|
cs = _csRasterizeModel0;
|
|
|
|
|
nonEmptyChunks.Add(key);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Another layer so need combine with existing chunk data
|
|
|
|
|
cs = _csRasterizeModel1;
|
|
|
|
|
}
|
|
|
|
|
context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
|
|
|
|
|
// TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?)
|
|
|
|
|
|
|
|
|
|
#if GLOBAL_SDF_DEBUG_CHUNKS
|
|
|
|
|
// Debug draw chunk bounds in world space with number of models in it
|
|
|
|
|
if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS)
|
|
|
|
|
{
|
|
|
|
|
Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize;
|
|
|
|
|
BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize);
|
|
|
|
|
DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false);
|
|
|
|
|
DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Generate Mip");
|
|
|
|
|
// Clear empty chunk
|
|
|
|
|
nonEmptyChunks.Remove(it);
|
|
|
|
|
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->UpdateCB(_cb1, &data);
|
|
|
|
|
context->ResetUA();
|
|
|
|
|
context->BindSR(0, cascadeView);
|
|
|
|
|
context->BindUA(0, cascadeMipView);
|
|
|
|
|
const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE);
|
|
|
|
|
int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS;
|
|
|
|
|
context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
|
|
|
|
context->UnBindSR(0);
|
|
|
|
|
GPUTextureView* tmpMipView = tmpMip->ViewVolume();
|
|
|
|
|
for (int32 i = 1; i < floodFillIterations; i++)
|
|
|
|
|
context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
|
|
|
|
|
// TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds
|
|
|
|
|
{
|
|
|
|
|
PROFILE_GPU_CPU("Rasterize Chunks");
|
|
|
|
|
for (auto& e : chunks)
|
|
|
|
|
{
|
|
|
|
|
// Rasterize non-empty chunk
|
|
|
|
|
auto& key = e.Key;
|
|
|
|
|
auto& chunk = e.Value;
|
|
|
|
|
for (int32 i = 0; i < chunk.ModelsCount; i++)
|
|
|
|
|
{
|
|
|
|
|
context->ResetUA();
|
|
|
|
|
context->BindSR(0, cascadeMipView);
|
|
|
|
|
context->BindUA(0, tmpMipView);
|
|
|
|
|
context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
|
|
|
|
Swap(tmpMipView, cascadeMipView);
|
|
|
|
|
int32 model = chunk.Models[i];
|
|
|
|
|
data.Models[i] = model;
|
|
|
|
|
context->BindSR(i + 1, _modelsTextures[model]);
|
|
|
|
|
}
|
|
|
|
|
if (floodFillIterations % 2 == 0)
|
|
|
|
|
Swap(tmpMipView, cascadeMipView);
|
|
|
|
|
ASSERT_LOW_LAYER(chunk.ModelsCount != 0);
|
|
|
|
|
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
|
|
|
|
data.ModelsCount = chunk.ModelsCount;
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->UpdateCB(_cb1, &data);
|
|
|
|
|
GPUShaderProgramCS* cs;
|
|
|
|
|
if (key.Layer == 0)
|
|
|
|
|
{
|
|
|
|
|
// First layer so can override existing chunk data
|
|
|
|
|
cs = _csRasterizeModel0;
|
|
|
|
|
nonEmptyChunks.Add(key);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Another layer so need combine with existing chunk data
|
|
|
|
|
cs = _csRasterizeModel1;
|
|
|
|
|
}
|
|
|
|
|
context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
|
|
|
|
|
// TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?)
|
|
|
|
|
|
|
|
|
|
#if GLOBAL_SDF_DEBUG_CHUNKS
|
|
|
|
|
// Debug draw chunk bounds in world space with number of models in it
|
|
|
|
|
if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS)
|
|
|
|
|
{
|
|
|
|
|
Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize;
|
|
|
|
|
BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize);
|
|
|
|
|
DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false);
|
|
|
|
|
DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderTargetPool::Release(tmpMip);
|
|
|
|
|
if (anyDraw)
|
|
|
|
|
// Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res)
|
|
|
|
|
{
|
|
|
|
|
context->UnBindCB(1);
|
|
|
|
|
PROFILE_GPU_CPU("Generate Mip");
|
|
|
|
|
if (_cb1)
|
|
|
|
|
context->UpdateCB(_cb1, &data);
|
|
|
|
|
context->ResetUA();
|
|
|
|
|
context->FlushState();
|
|
|
|
|
context->ResetSR();
|
|
|
|
|
context->FlushState();
|
|
|
|
|
context->BindSR(0, cascadeView);
|
|
|
|
|
context->BindUA(0, cascadeMipView);
|
|
|
|
|
const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE);
|
|
|
|
|
int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS;
|
|
|
|
|
context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
|
|
|
|
context->UnBindSR(0);
|
|
|
|
|
GPUTextureView* tmpMipView = tmpMip->ViewVolume();
|
|
|
|
|
for (int32 i = 1; i < floodFillIterations; i++)
|
|
|
|
|
{
|
|
|
|
|
context->ResetUA();
|
|
|
|
|
context->BindSR(0, cascadeMipView);
|
|
|
|
|
context->BindUA(0, tmpMipView);
|
|
|
|
|
context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
|
|
|
|
Swap(tmpMipView, cascadeMipView);
|
|
|
|
|
}
|
|
|
|
|
if (floodFillIterations % 2 == 0)
|
|
|
|
|
Swap(tmpMipView, cascadeMipView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderTargetPool::Release(tmpMip);
|
|
|
|
|
if (anyDraw)
|
|
|
|
|
{
|
|
|
|
|
context->UnBindCB(1);
|
|
|
|
|
context->ResetUA();
|
|
|
|
|
context->FlushState();
|
|
|
|
|
context->ResetSR();
|
|
|
|
|
context->FlushState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy results
|
|
|
|
|
static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
|
|
|
|
|
static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.CascadeMips), "Invalid cascades count.");
|
|
|
|
|
@@ -476,6 +491,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
|
|
|
|
result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize;
|
|
|
|
|
}
|
|
|
|
|
result.GlobalSDF.Resolution = (float)resolution;
|
|
|
|
|
sdfData.Result = result;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|