Add Global SDF Overdraw debug mode for content optimizations
This commit is contained in:
Binary file not shown.
@@ -2112,6 +2112,7 @@ namespace FlaxEditor.Viewport
|
||||
new ViewModeOptions(ViewMode.LODPreview, "LOD Preview", Editor.Instance.Options.Options.Input.LODPreview),
|
||||
new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity", Editor.Instance.Options.Options.Input.MaterialComplexity),
|
||||
new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw", Editor.Instance.Options.Options.Input.QuadOverdraw),
|
||||
new ViewModeOptions(ViewMode.GlobalSDFOverdraw, "Global SDF Overdraw"),
|
||||
}),
|
||||
new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF", Editor.Instance.Options.Options.Input.GloablSDF),
|
||||
new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas", Editor.Instance.Options.Options.Input.GlobalSurfaceAtlas),
|
||||
|
||||
@@ -1005,6 +1005,11 @@ API_ENUM() enum class ViewMode
|
||||
/// Draw Global Illumination debug preview (eg. irradiance probes).
|
||||
/// </summary>
|
||||
GlobalIllumination = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Draw Global Sign Distant Field (SDF) overdraw to visualize performance of SDF tracing.
|
||||
/// </summary>
|
||||
GlobalSDFOverdraw = 27,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -923,6 +923,7 @@ void GlobalSurfaceAtlasPass::OnCollectDrawCalls(RenderContextBatch& renderContex
|
||||
return;
|
||||
if (GBufferPass::IsDebugView(renderContext.View.Mode) ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDF ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDFOverdraw ||
|
||||
renderContext.View.Mode == ViewMode::QuadOverdraw ||
|
||||
renderContext.View.Mode == ViewMode::MaterialComplexity)
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "GlobalSignDistanceFieldPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "RenderList.h"
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Matrix3x4.h"
|
||||
@@ -81,6 +82,15 @@ GPU_CB_STRUCT(ModelsRasterizeData {
|
||||
uint32 MipMipOffsetX;
|
||||
});
|
||||
|
||||
GPU_CB_STRUCT(OverdrawData {
|
||||
ShaderGBufferData GBuffer;
|
||||
Float3 CascadeBoundsMin;
|
||||
float CascadeBoundsResolution;
|
||||
Float3 CascadeBoundsMax;
|
||||
float Padding30;
|
||||
uint32 ChunksBuffer[512]; // (MaxVolumeRes/ChunkRes)^3 = (256/32)^3
|
||||
});
|
||||
|
||||
struct RasterizeChunk
|
||||
{
|
||||
uint16 ModelsCount;
|
||||
@@ -108,6 +118,7 @@ struct RasterizeObject
|
||||
};
|
||||
|
||||
constexpr int32 RasterizeChunkKeyHashResolution = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
||||
#define KEY_GET_HASH(key) key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X
|
||||
|
||||
struct RasterizeChunkKey
|
||||
{
|
||||
@@ -115,6 +126,7 @@ struct RasterizeChunkKey
|
||||
uint32 Layer;
|
||||
Int3 Coord;
|
||||
|
||||
// Moves to the next layer and permutates the hash
|
||||
FORCE_INLINE void NextLayer()
|
||||
{
|
||||
Layer++;
|
||||
@@ -152,6 +164,7 @@ struct CascadeData
|
||||
|
||||
// Cache
|
||||
Dictionary<RasterizeChunkKey, RasterizeChunk> Chunks;
|
||||
Dictionary<RasterizeChunkKey, RasterizeChunk> DebugOverdrawChunks;
|
||||
Array<RasterizeObject> RasterizeObjects;
|
||||
Array<byte> ObjectsData;
|
||||
Array<GPUTextureView*> ObjectsTextures;
|
||||
@@ -182,7 +195,7 @@ struct CascadeData
|
||||
{
|
||||
for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++)
|
||||
{
|
||||
key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X;
|
||||
key.Hash = KEY_GET_HASH(key);
|
||||
StaticChunks.Remove(key);
|
||||
}
|
||||
}
|
||||
@@ -195,6 +208,7 @@ class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer,
|
||||
public:
|
||||
int32 FrameIndex = 0;
|
||||
int32 Resolution = 0;
|
||||
bool DebugOverdraw = false;
|
||||
GPUTexture* Texture = nullptr;
|
||||
GPUTexture* TextureMip = nullptr;
|
||||
Vector3 Origin = Vector3::Zero;
|
||||
@@ -308,6 +322,7 @@ public:
|
||||
FrameIndex = 0;
|
||||
AsyncRenderContext = renderContext;
|
||||
AsyncRenderContext.View.Pass = DrawPass::GlobalSDF;
|
||||
DebugOverdraw = renderContext.View.Mode == ViewMode::GlobalSDFOverdraw;
|
||||
const bool useCache = !reset && !GLOBAL_SDF_DEBUG_FORCE_REDRAW;
|
||||
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);
|
||||
@@ -472,6 +487,8 @@ void GlobalSignDistanceFieldCustomBuffer::UpdateCascadeChunks(CascadeData& casca
|
||||
PROFILE_CPU();
|
||||
|
||||
// Update static chunks
|
||||
if (DebugOverdraw)
|
||||
cascade.DebugOverdrawChunks = cascade.Chunks;
|
||||
for (auto it = cascade.Chunks.Begin(); it.IsNotEnd(); ++it)
|
||||
{
|
||||
auto& e = *it;
|
||||
@@ -642,6 +659,7 @@ bool GlobalSignDistanceFieldPass::setupResources()
|
||||
_objectsBuffer = New<DynamicStructuredBuffer>(0, (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer"));
|
||||
|
||||
// Create pipeline state
|
||||
// TODO: don't compile those shaders in Release builds (and skip PSOs)
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psDebug)
|
||||
{
|
||||
@@ -650,6 +668,14 @@ bool GlobalSignDistanceFieldPass::setupResources()
|
||||
if (_psDebug->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psOverdraw)
|
||||
{
|
||||
_psOverdraw = device->CreatePipelineState();
|
||||
psDesc.PS = shader->GetPS("PS_Overdraw");
|
||||
psDesc.BlendMode = BlendingMode::AlphaBlend;
|
||||
if (_psOverdraw->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -659,6 +685,7 @@ bool GlobalSignDistanceFieldPass::setupResources()
|
||||
void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj)
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDebug);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psOverdraw);
|
||||
_csRasterizeModel0 = nullptr;
|
||||
_csRasterizeModel1 = nullptr;
|
||||
_csRasterizeHeightfield = nullptr;
|
||||
@@ -1094,12 +1121,80 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC
|
||||
context->UpdateCB(_cb0, &data);
|
||||
context->BindCB(0, _cb0);
|
||||
}
|
||||
context->BindSR(0, bindingData.Texture ? bindingData.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingData.TextureMip ? bindingData.TextureMip->ViewVolume() : nullptr);
|
||||
context->SetState(_psDebug);
|
||||
context->SetRenderTarget(output->View());
|
||||
context->SetViewportAndScissors(outputSize.X, outputSize.Y);
|
||||
context->DrawFullscreenTriangle();
|
||||
if (renderContext.View.Mode == ViewMode::GlobalSDFOverdraw)
|
||||
{
|
||||
auto& sdfData = *renderContext.Buffers->GetCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField"));
|
||||
int32 chunkRes = sdfData.Resolution / GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
||||
OverdrawData data;
|
||||
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
|
||||
GPUConstantBuffer* cb2 = _shader->GetShader()->GetCB(2);
|
||||
context->BindCB(2, cb2);
|
||||
context->BindSR(0, renderContext.Buffers->DepthBuffer);
|
||||
context->BindSR(1, renderContext.Buffers->GBuffer0);
|
||||
context->SetState(_psOverdraw);
|
||||
context->Clear(output->View(), Color::Transparent);
|
||||
|
||||
// Rasterize all cascades to show overdraw
|
||||
for (int32 cascadeIndex = sdfData.Cascades.Count() - 1; cascadeIndex >= 0; cascadeIndex--)
|
||||
{
|
||||
auto& cascade = sdfData.Cascades[cascadeIndex];
|
||||
|
||||
// Scale complexity down for far cascades as they are updated less often
|
||||
float cascadeScale = Math::Max(1.0f - (float)cascadeIndex / (float)sdfData.Cascades.Count(), 0.3f);
|
||||
|
||||
// Provide per-chunk overdraw based on objects count and complexity
|
||||
RasterizeChunkKey key;
|
||||
for (key.Coord.Z = 0; key.Coord.Z < chunkRes; key.Coord.Z++)
|
||||
{
|
||||
for (key.Coord.Y = 0; key.Coord.Y < chunkRes; key.Coord.Y++)
|
||||
{
|
||||
for (key.Coord.X = 0; key.Coord.X < chunkRes; key.Coord.X++)
|
||||
{
|
||||
int32 models = 0, heightfields = 0, dynamic = 0;
|
||||
key.Layer = 0;
|
||||
key.Hash = KEY_GET_HASH(key);
|
||||
while (auto* chunk = cascade.DebugOverdrawChunks.TryGet(key))
|
||||
{
|
||||
models += chunk->ModelsCount;
|
||||
heightfields += chunk->HeightfieldsCount;
|
||||
dynamic += chunk->Dynamic ? 1 : 0;
|
||||
key.NextLayer();
|
||||
}
|
||||
|
||||
// Calculate complexity of the chunk that is related to amount of compute shader dispatches and data amount to process
|
||||
uint32 complexity = 0;
|
||||
complexity = (key.Layer - 1) * 30;
|
||||
complexity += (models + heightfields) * 5;
|
||||
complexity += dynamic * 50;
|
||||
if (heightfields > 1)
|
||||
complexity += 20;
|
||||
if (models > 1)
|
||||
complexity += 20;
|
||||
complexity = (uint32)(cascadeScale * complexity);
|
||||
|
||||
int32 i = key.Coord.Z * chunkRes * chunkRes + key.Coord.Y * chunkRes + key.Coord.X;
|
||||
data.ChunksBuffer[i] = Math::Min<uint32>(complexity, MAX_uint8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.CascadeBoundsMin = (Float3)cascade.Bounds.Minimum;
|
||||
data.CascadeBoundsMax = (Float3)cascade.Bounds.Maximum;
|
||||
data.CascadeBoundsResolution = (float)sdfData.Resolution / GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
|
||||
context->UpdateCB(cb2, &data);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
context->UnBindCB(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
context->BindSR(0, bindingData.Texture ? bindingData.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingData.TextureMip ? bindingData.TextureMip->ViewVolume() : nullptr);
|
||||
context->SetState(_psDebug);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSignDistanceFieldPass::GetCullingData(BoundingBox& bounds) const
|
||||
@@ -1144,7 +1239,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas
|
||||
for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++)
|
||||
{
|
||||
key.Layer = 0;
|
||||
key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X;
|
||||
key.Hash = KEY_GET_HASH(key);
|
||||
RasterizeChunk* chunk = &chunks[key];
|
||||
chunk->Dynamic |= dynamic;
|
||||
|
||||
@@ -1205,7 +1300,7 @@ void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture*
|
||||
for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++)
|
||||
{
|
||||
key.Layer = 0;
|
||||
key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X;
|
||||
key.Hash = KEY_GET_HASH(key);
|
||||
RasterizeChunk* chunk = &chunks[key];
|
||||
chunk->Dynamic |= dynamic;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
bool _supported = false;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psDebug = nullptr;
|
||||
GPUPipelineState* _psOverdraw = nullptr;
|
||||
GPUShaderProgramCS* _csRasterizeModel0 = nullptr;
|
||||
GPUShaderProgramCS* _csRasterizeModel1 = nullptr;
|
||||
GPUShaderProgramCS* _csRasterizeHeightfield = nullptr;
|
||||
|
||||
@@ -404,6 +404,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
(EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI) && renderContext.List->Settings.GlobalIllumination.Mode == GlobalIlluminationMode::DDGI);
|
||||
setup.UseGlobalSDF = (graphicsSettings->EnableGlobalSDF && EnumHasAnyFlags(view.Flags, ViewFlags::GlobalSDF)) ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDF ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDFOverdraw ||
|
||||
setup.UseGlobalSurfaceAtlas;
|
||||
setup.UseVolumetricFog = (view.Flags & ViewFlags::Fog) != ViewFlags::None;
|
||||
|
||||
@@ -424,6 +425,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
case ViewMode::ShadingModel:
|
||||
case ViewMode::Reflections:
|
||||
case ViewMode::GlobalSDF:
|
||||
case ViewMode::GlobalSDFOverdraw:
|
||||
case ViewMode::GlobalSurfaceAtlas:
|
||||
case ViewMode::LightmapUVsDensity:
|
||||
case ViewMode::MaterialComplexity:
|
||||
@@ -460,6 +462,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
case ViewMode::LightmapUVsDensity:
|
||||
case ViewMode::GlobalSurfaceAtlas:
|
||||
case ViewMode::GlobalSDF:
|
||||
case ViewMode::GlobalSDFOverdraw:
|
||||
case ViewMode::MaterialComplexity:
|
||||
case ViewMode::VertexColors:
|
||||
drawShadows = false;
|
||||
@@ -596,7 +599,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
GBufferPass::Instance()->Fill(renderContext, lightBuffer);
|
||||
|
||||
// Debug drawing
|
||||
if (renderContext.View.Mode == ViewMode::GlobalSDF)
|
||||
if (renderContext.View.Mode == ViewMode::GlobalSDF || renderContext.View.Mode == ViewMode::GlobalSDFOverdraw)
|
||||
GlobalSignDistanceFieldPass::Instance()->RenderDebug(renderContext, context, lightBuffer);
|
||||
else if (renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas)
|
||||
GlobalSurfaceAtlasPass::Instance()->RenderDebug(renderContext, context, lightBuffer);
|
||||
@@ -604,7 +607,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
renderContext.View.Mode == ViewMode::VertexColors ||
|
||||
renderContext.View.Mode == ViewMode::LightmapUVsDensity ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDF)
|
||||
renderContext.View.Mode == ViewMode::GlobalSDF ||
|
||||
renderContext.View.Mode == ViewMode::GlobalSDFOverdraw)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
|
||||
@@ -352,3 +352,86 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _PS_Overdraw
|
||||
|
||||
#define NO_GBUFFER_SAMPLING
|
||||
#include "./Flax/GBuffer.hlsl"
|
||||
#include "./Flax/Noise.hlsl"
|
||||
|
||||
META_CB_BEGIN(2, OverdrawData)
|
||||
GBufferData GBuffer;
|
||||
float3 CascadeBoundsMin;
|
||||
float CascadeBoundsResolution;
|
||||
float3 CascadeBoundsMax;
|
||||
float Padding30;
|
||||
uint4 ChunksBuffer[512/4]; // (MaxVolumeRes/ChunkRes)^3 = (256/32)^3
|
||||
META_CB_END
|
||||
|
||||
Texture2D Depth : register(t0);
|
||||
Texture2D GBuffer0 : register(t1);
|
||||
|
||||
float GteOutline(float baseDepth, float2 uv, float2 offset, float2 depthSizeInv)
|
||||
{
|
||||
float neighborDepth = SAMPLE_RT_DEPTH(Depth, uv + offset * depthSizeInv);
|
||||
return DEPTH_DIFF(baseDepth, neighborDepth) * 1000.0f;
|
||||
}
|
||||
|
||||
float4 GetHeatmap(float value)
|
||||
{
|
||||
const float4 colors[4] = {
|
||||
float4(0, 0.97f, 0.12f, 1),
|
||||
float4(0.2f, 0.2f, 0.7f, 1),
|
||||
float4(1, 0.57f, 0, 1),
|
||||
float4(1, 0, 0, 1)
|
||||
};
|
||||
const float weights[4] = { 0.0f, 0.5f, 0.8f, 1.0f };
|
||||
|
||||
float4 color;
|
||||
if (value < weights[1])
|
||||
color = lerp(colors[0], colors[1], value / weights[1]);
|
||||
else if (value < weights[2])
|
||||
color = lerp(colors[1], colors[2], (value - weights[1]) / (weights[2] - weights[1]));
|
||||
else
|
||||
color = lerp(colors[2], colors[3], (value - weights[2]) / (weights[3] - weights[2]));
|
||||
return color;
|
||||
}
|
||||
|
||||
// Pixel shader for Global SDF overdraw drawing
|
||||
META_PS(true, FEATURE_LEVEL_SM5)
|
||||
float4 PS_Overdraw(Quad_VS2PS input) : SV_Target
|
||||
{
|
||||
// Use over-exposed diffuse as a base for scene identification
|
||||
float3 diffuse = SAMPLE_RT(GBuffer0, input.TexCoord).rgb;
|
||||
diffuse = saturate(Luminance(diffuse.rgb).xxx * 0.5f + 0.6f);
|
||||
|
||||
// Make depth-based outlines
|
||||
float baseDepth = SAMPLE_RT_DEPTH(Depth, input.TexCoord);
|
||||
float2 depthSize;
|
||||
Depth.GetDimensions(depthSize.x, depthSize.y);
|
||||
float2 depthSizeInv = 1.0f / depthSize;
|
||||
float outline = GteOutline(baseDepth, input.TexCoord, float2(1, 0), depthSizeInv);
|
||||
outline += GteOutline(baseDepth, input.TexCoord, float2(0, 1), depthSizeInv);
|
||||
outline += GteOutline(baseDepth, input.TexCoord, float2(-1, 0), depthSizeInv);
|
||||
outline += GteOutline(baseDepth, input.TexCoord, float2(0, -1), depthSizeInv);
|
||||
outline = 1 - saturate(outline);
|
||||
|
||||
// Get position inside chunk used by the pixel position
|
||||
float3 worldPos = GetWorldPos(GBuffer, input.TexCoord, baseDepth);
|
||||
worldPos += rand3dTo3d(float3(input.TexCoord, diffuse.x)) * 0.1f; // Noise a bit to reduce z-fighting at chunk borders
|
||||
float3 cascadeBoundsSize = CascadeBoundsMax - CascadeBoundsMin;
|
||||
float3 cascadePos = (worldPos - CascadeBoundsMin) / cascadeBoundsSize;
|
||||
cascadePos = floor(cascadePos * CascadeBoundsResolution) / CascadeBoundsResolution;
|
||||
if (any(cascadePos < 0) || any(cascadePos >= 1)) return float4(0, 0, 0, 0); // Skip pixels outside the current cascade
|
||||
//return float4(cascadePos, 1);
|
||||
|
||||
// Read complexity of the Global SDF rasterization at the pixel position
|
||||
cascadePos *= CascadeBoundsResolution;
|
||||
uint i = (uint)(cascadePos.z * CascadeBoundsResolution * CascadeBoundsResolution + cascadePos.y * CascadeBoundsResolution + cascadePos.x);
|
||||
float complexity = (float)ChunksBuffer[i / 4][i % 4] / 255.0f;
|
||||
|
||||
// Mix complexity with depth/diffuse to improve scene readability
|
||||
return float4(diffuse * outline, 1) * GetHeatmap(complexity);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user