Merge remote-tracking branch 'origin/gi'
# Conflicts: # Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
This commit is contained in:
@@ -481,7 +481,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
|
||||
BytesContainer Material::LoadSurface(bool createDefaultIfMissing)
|
||||
{
|
||||
BytesContainer result;
|
||||
if (WaitForLoaded())
|
||||
if (WaitForLoaded() && !LastLoadFailed())
|
||||
return result;
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
|
||||
@@ -15,6 +15,5 @@ void GraphicsSettings::Apply()
|
||||
Graphics::AllowCSMBlending = AllowCSMBlending;
|
||||
Graphics::GlobalSDFQuality = GlobalSDFQuality;
|
||||
Graphics::GIQuality = GIQuality;
|
||||
Graphics::GlobalSurfaceAtlasResolution = GlobalSurfaceAtlasResolution;
|
||||
Graphics::PostProcessSettings = PostProcessSettings;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,12 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(2100), DefaultValue(Quality.High), EditorDisplay(\"Global Illumination\")")
|
||||
Quality GIQuality = Quality::High;
|
||||
|
||||
/// <summary>
|
||||
/// The Global Illumination probes spacing distance (in world units). Defines the quality of the GI resolution. Adjust to 200-500 to improve performance and lower frequency GI data.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2120), Limit(50, 1000), EditorDisplay(\"Global Illumination\")")
|
||||
float GIProbesSpacing = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The Global Surface Atlas resolution. Adjust it if atlas `flickers` due to overflow (eg. to 4096).
|
||||
/// </summary>
|
||||
|
||||
@@ -435,6 +435,14 @@ namespace Math
|
||||
return amount <= 0 ? 0 : amount >= 1 ? 1 : amount * amount * amount * (amount * (amount * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
// Determines whether the specified value is close to zero (0.0)
|
||||
// @param a The integer value
|
||||
// @returns True if the specified value is close to zero (0.0). otherwise false
|
||||
inline int32 IsZero(int32 a)
|
||||
{
|
||||
return a == 0;
|
||||
}
|
||||
|
||||
// Determines whether the specified value is close to zero (0.0f)
|
||||
// @param a The floating value
|
||||
// @returns True if the specified value is close to zero (0.0f). otherwise false
|
||||
@@ -443,6 +451,14 @@ namespace Math
|
||||
return Abs(a) < ZeroTolerance;
|
||||
}
|
||||
|
||||
// Determines whether the specified value is close to one (1.0)
|
||||
// @param a The integer value
|
||||
// @returns True if the specified value is close to one (1.0). otherwise false
|
||||
inline bool IsOne(int32 a)
|
||||
{
|
||||
return a == 1;
|
||||
}
|
||||
|
||||
// Determines whether the specified value is close to one (1.0f)
|
||||
// @param a The floating value
|
||||
// @returns True if the specified value is close to one (1.0f). otherwise false
|
||||
|
||||
@@ -864,6 +864,11 @@ API_ENUM() enum class ViewMode
|
||||
/// Draw Global Surface Atlas preview.
|
||||
/// </summary>
|
||||
GlobalSurfaceAtlas = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Draw Global Illumination debug preview (eg. irradiance probes).
|
||||
/// </summary>
|
||||
GlobalIllumination = 26,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,6 @@ Quality Graphics::ShadowMapsQuality = Quality::Medium;
|
||||
bool Graphics::AllowCSMBlending = false;
|
||||
Quality Graphics::GlobalSDFQuality = Quality::High;
|
||||
Quality Graphics::GIQuality = Quality::High;
|
||||
int32 Graphics::GlobalSurfaceAtlasResolution = 2048;
|
||||
PostProcessSettings Graphics::PostProcessSettings;
|
||||
|
||||
#if GRAPHICS_API_NULL
|
||||
|
||||
@@ -63,11 +63,6 @@ public:
|
||||
/// </summary>
|
||||
API_FIELD() static Quality GIQuality;
|
||||
|
||||
/// <summary>
|
||||
/// The Global Surface Atlas resolution. Adjust it if atlas `flickers` due to overflow.
|
||||
/// </summary>
|
||||
API_FIELD() static int32 GlobalSurfaceAtlasResolution;
|
||||
|
||||
/// <summary>
|
||||
/// The default Post Process settings. Can be overriden by PostFxVolume on a level locally, per camera or for a whole map.
|
||||
/// </summary>
|
||||
|
||||
@@ -502,7 +502,7 @@ void MaterialParameter::Bind(BindMeta& meta) const
|
||||
GlobalSignDistanceFieldPass::BindingData bindingData;
|
||||
if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData))
|
||||
Platform::MemoryClear(&bindingData, sizeof(bindingData));
|
||||
bindingData.BindCascades(meta.Context, _registerIndex);
|
||||
meta.Context->BindSR(_registerIndex, bindingData.Texture ? bindingData.Texture->ViewVolume() : nullptr);
|
||||
*((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable
|
||||
/// Defines how quickly GI blends between the the current frame and the history buffer. Lower values update GI faster, but with more jittering and noise. If the camera in your game doesn't move much, we recommend values closer to 1.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)")
|
||||
float TemporalResponse = 0.8f;
|
||||
float TemporalResponse = 0.9f;
|
||||
|
||||
/// <summary>
|
||||
/// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance.
|
||||
|
||||
@@ -74,7 +74,8 @@ GPUContextDX12::GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE ty
|
||||
, _isCompute(0)
|
||||
, _rtDirtyFlag(0)
|
||||
, _psDirtyFlag(0)
|
||||
, _cbDirtyFlag(0)
|
||||
, _cbGraphicsDirtyFlag(0)
|
||||
, _cbComputeDirtyFlag(0)
|
||||
, _samplersDirtyFlag(0)
|
||||
, _rtDepth(nullptr)
|
||||
, _ibHandle(nullptr)
|
||||
@@ -214,7 +215,8 @@ void GPUContextDX12::Reset()
|
||||
// Setup initial state
|
||||
_currentState = nullptr;
|
||||
_rtDirtyFlag = false;
|
||||
_cbDirtyFlag = false;
|
||||
_cbGraphicsDirtyFlag = false;
|
||||
_cbComputeDirtyFlag = false;
|
||||
_samplersDirtyFlag = false;
|
||||
_rtCount = 0;
|
||||
_rtDepth = nullptr;
|
||||
@@ -453,19 +455,30 @@ void GPUContextDX12::flushUAVs()
|
||||
|
||||
void GPUContextDX12::flushCBs()
|
||||
{
|
||||
if (!_cbDirtyFlag)
|
||||
return;
|
||||
_cbDirtyFlag = false;
|
||||
for (uint32 slotIndex = 0; slotIndex < ARRAY_COUNT(_cbHandles); slotIndex++)
|
||||
if (_cbGraphicsDirtyFlag && !_isCompute)
|
||||
{
|
||||
auto cb = _cbHandles[slotIndex];
|
||||
if (cb)
|
||||
_cbGraphicsDirtyFlag = false;
|
||||
for (uint32 i = 0; i < ARRAY_COUNT(_cbHandles); i++)
|
||||
{
|
||||
ASSERT(cb->GPUAddress != 0);
|
||||
if (_isCompute)
|
||||
_commandList->SetComputeRootConstantBufferView(slotIndex, cb->GPUAddress);
|
||||
else
|
||||
_commandList->SetGraphicsRootConstantBufferView(slotIndex, cb->GPUAddress);
|
||||
const auto cb = _cbHandles[i];
|
||||
if (cb)
|
||||
{
|
||||
ASSERT(cb->GPUAddress != 0);
|
||||
_commandList->SetGraphicsRootConstantBufferView(i, cb->GPUAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_cbComputeDirtyFlag && _isCompute)
|
||||
{
|
||||
_cbComputeDirtyFlag = false;
|
||||
for (uint32 i = 0; i < ARRAY_COUNT(_cbHandles); i++)
|
||||
{
|
||||
const auto cb = _cbHandles[i];
|
||||
if (cb)
|
||||
{
|
||||
ASSERT(cb->GPUAddress != 0);
|
||||
_commandList->SetComputeRootConstantBufferView(i, cb->GPUAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,7 +775,7 @@ void GPUContextDX12::ClearUA(GPUTexture* texture, const Float4& value)
|
||||
|
||||
SetResourceState(texDX12, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
flushRBs();
|
||||
|
||||
|
||||
auto uav = ((GPUTextureViewDX12*)(texDX12->IsVolume() ? texDX12->ViewVolume() : texDX12->View(0)))->UAV();
|
||||
Descriptor desc;
|
||||
GetActiveHeapDescriptor(uav, desc);
|
||||
@@ -867,7 +880,8 @@ void GPUContextDX12::ResetUA()
|
||||
|
||||
void GPUContextDX12::ResetCB()
|
||||
{
|
||||
_cbDirtyFlag = false;
|
||||
_cbGraphicsDirtyFlag = false;
|
||||
_cbComputeDirtyFlag = false;
|
||||
Platform::MemoryClear(_cbHandles, sizeof(_cbHandles));
|
||||
}
|
||||
|
||||
@@ -877,7 +891,8 @@ void GPUContextDX12::BindCB(int32 slot, GPUConstantBuffer* cb)
|
||||
auto cbDX12 = static_cast<GPUConstantBufferDX12*>(cb);
|
||||
if (_cbHandles[slot] != cbDX12)
|
||||
{
|
||||
_cbDirtyFlag = true;
|
||||
_cbGraphicsDirtyFlag = true;
|
||||
_cbComputeDirtyFlag = true;
|
||||
_cbHandles[slot] = cbDX12;
|
||||
}
|
||||
}
|
||||
@@ -988,7 +1003,8 @@ void GPUContextDX12::UpdateCB(GPUConstantBuffer* cb, const void* data)
|
||||
{
|
||||
if (_cbHandles[i] == cbDX12)
|
||||
{
|
||||
_cbDirtyFlag = true;
|
||||
_cbGraphicsDirtyFlag = true;
|
||||
_cbComputeDirtyFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1296,7 +1312,7 @@ void GPUContextDX12::CopyResource(GPUResource* dstResource, GPUResource* srcReso
|
||||
{
|
||||
_commandList->CopyResource(dstResourceDX12->GetResource(), srcResourceDX12->GetResource());
|
||||
}
|
||||
// Texture -> Texture
|
||||
// Texture -> Texture
|
||||
else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture)
|
||||
{
|
||||
auto dstTextureDX12 = static_cast<GPUTextureDX12*>(dstResource);
|
||||
@@ -1388,7 +1404,7 @@ void GPUContextDX12::CopySubresource(GPUResource* dstResource, uint32 dstSubreso
|
||||
uint64 bytesCount = srcResource->GetMemoryUsage();
|
||||
_commandList->CopyBufferRegion(dstBufferDX12->GetResource(), 0, srcBufferDX12->GetResource(), 0, bytesCount);
|
||||
}
|
||||
// Texture -> Texture
|
||||
// Texture -> Texture
|
||||
else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture)
|
||||
{
|
||||
auto dstTextureDX12 = static_cast<GPUTextureDX12*>(dstResource);
|
||||
|
||||
@@ -51,7 +51,8 @@ private:
|
||||
int32 _isCompute : 1;
|
||||
int32 _rtDirtyFlag : 1;
|
||||
int32 _psDirtyFlag : 1;
|
||||
int32 _cbDirtyFlag : 1;
|
||||
int32 _cbGraphicsDirtyFlag : 1;
|
||||
int32 _cbComputeDirtyFlag : 1;
|
||||
int32 _samplersDirtyFlag : 1;
|
||||
|
||||
GPUTextureViewDX12* _rtDepth;
|
||||
|
||||
@@ -389,8 +389,9 @@ inline void SetDebugObjectName(T* resource, const Char* data, UINT size)
|
||||
if (data && size > 0)
|
||||
resource->SetName(data);
|
||||
#else
|
||||
char* ansi = (char*)Allocator::Allocate(size);
|
||||
char* ansi = (char*)Allocator::Allocate(size + 1);
|
||||
StringUtils::ConvertUTF162ANSI(data, ansi, size);
|
||||
ansi[size] ='\0';
|
||||
SetDebugObjectName(resource, ansi, size);
|
||||
Allocator::Free(ansi);
|
||||
#endif
|
||||
|
||||
@@ -452,6 +452,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLER:
|
||||
{
|
||||
// Sampler
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_SAMPLER_BINDED);
|
||||
const VkSampler sampler = _samplerHandles[slot];
|
||||
ASSERT(sampler);
|
||||
needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index);
|
||||
@@ -460,6 +461,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
||||
{
|
||||
// Shader Resource (Texture)
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_SR_BINDED);
|
||||
auto handle = (GPUTextureViewVulkan*)handles[slot];
|
||||
if (!handle)
|
||||
{
|
||||
@@ -490,6 +492,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
||||
{
|
||||
// Shader Resource (Buffer)
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_SR_BINDED);
|
||||
auto sr = handles[slot];
|
||||
if (!sr)
|
||||
{
|
||||
@@ -505,6 +508,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
||||
{
|
||||
// Unordered Access (Texture)
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_UA_BINDED);
|
||||
auto ua = handles[slot];
|
||||
ASSERT(ua);
|
||||
VkImageView imageView;
|
||||
@@ -516,6 +520,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
||||
{
|
||||
// Unordered Access (Buffer)
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_UA_BINDED);
|
||||
auto ua = handles[slot];
|
||||
if (!ua)
|
||||
{
|
||||
@@ -531,6 +536,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
||||
{
|
||||
// Unordered Access (Buffer)
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_UA_BINDED);
|
||||
auto ua = handles[slot];
|
||||
if (!ua)
|
||||
{
|
||||
@@ -546,6 +552,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
|
||||
{
|
||||
// Constant Buffer
|
||||
ASSERT_LOW_LAYER(slot < GPU_MAX_CB_BINDED);
|
||||
auto cb = handles[slot];
|
||||
ASSERT(cb);
|
||||
VkBuffer buffer;
|
||||
|
||||
@@ -42,6 +42,8 @@ void DirectionalLight::Draw(RenderContext& renderContext)
|
||||
data.ShadowsMode = ShadowsMode;
|
||||
data.CascadeCount = CascadeCount;
|
||||
data.ContactShadowsLength = ContactShadowsLength;
|
||||
data.StaticFlags = GetStaticFlags();
|
||||
data.ID = GetID();
|
||||
renderContext.List->DirectionalLights.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,8 @@ void PointLight::Draw(RenderContext& renderContext)
|
||||
data.ContactShadowsLength = ContactShadowsLength;
|
||||
data.IndirectLightingIntensity = IndirectLightingIntensity;
|
||||
data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr;
|
||||
data.StaticFlags = GetStaticFlags();
|
||||
data.ID = GetID();
|
||||
renderContext.List->PointLights.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ void SkyLight::Draw(RenderContext& renderContext)
|
||||
data.IndirectLightingIntensity = IndirectLightingIntensity;
|
||||
data.Radius = GetScaledRadius();
|
||||
data.Image = GetSource();
|
||||
data.StaticFlags = GetStaticFlags();
|
||||
data.ID = GetID();
|
||||
renderContext.List->SkyLights.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,8 @@ void SpotLight::Draw(RenderContext& renderContext)
|
||||
data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr;
|
||||
Float3::Transform(Float3::Up, GetOrientation(), data.UpVector);
|
||||
data.OuterConeAngle = outerConeAngle;
|
||||
data.StaticFlags = GetStaticFlags();
|
||||
data.ID = GetID();
|
||||
renderContext.List->SpotLights.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ void ParticleEmitter::InitCompilationOptions(ShaderCompilationOptions& options)
|
||||
BytesContainer ParticleEmitter::LoadSurface(bool createDefaultIfMissing)
|
||||
{
|
||||
BytesContainer result;
|
||||
if (WaitForLoaded())
|
||||
if (WaitForLoaded() && !LastLoadFailed())
|
||||
return result;
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Core/Math/Int3.h"
|
||||
#include "Engine/Core/Math/Matrix3x3.h"
|
||||
#include "Engine/Core/Math/Quaternion.h"
|
||||
#include "Engine/Core/Config/GraphicsSettings.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
@@ -33,8 +34,7 @@
|
||||
|
||||
// This must match HLSL
|
||||
#define DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT 4096 // Maximum amount of probes to update at once during rays tracing and blending
|
||||
#define DDGI_TRACE_RAYS_GROUP_SIZE_X 32
|
||||
#define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller)
|
||||
#define DDGI_TRACE_RAYS_LIMIT 256 // Limit of rays per-probe (runtime value can be smaller)
|
||||
#define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side)
|
||||
#define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side)
|
||||
#define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8
|
||||
@@ -49,6 +49,7 @@ PACK_STRUCT(struct Data0
|
||||
Float2 Padding0;
|
||||
float ResetBlend;
|
||||
float TemporalTime;
|
||||
Int4 ProbeScrollClears[4];
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct Data1
|
||||
@@ -68,16 +69,14 @@ public:
|
||||
float ProbesSpacing = 0.0f;
|
||||
Int3 ProbeScrollOffsets;
|
||||
Int3 ProbeScrollDirections;
|
||||
bool ProbeScrollClear[3];
|
||||
Int3 ProbeScrollClears;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
ProbesOrigin = Float3::Zero;
|
||||
ProbeScrollOffsets = Int3::Zero;
|
||||
ProbeScrollDirections = Int3::Zero;
|
||||
ProbeScrollClear[0] = false;
|
||||
ProbeScrollClear[1] = false;
|
||||
ProbeScrollClear[2] = false;
|
||||
ProbeScrollClears = Int3::Zero;
|
||||
}
|
||||
} Cascades[4];
|
||||
|
||||
@@ -88,6 +87,8 @@ public:
|
||||
GPUTexture* ProbesState = nullptr; // Probes state: (RGB: world-space offset, A: state)
|
||||
GPUTexture* ProbesIrradiance = nullptr; // Probes irradiance (RGB: sRGB color)
|
||||
GPUTexture* ProbesDistance = nullptr; // Probes distance (R: mean distance, G: mean distance^2)
|
||||
GPUBuffer* ActiveProbes = nullptr; // List with indices of the active probes (built during probes classification to use indirect dispatches for probes updating), counter at 0
|
||||
GPUBuffer* UpdateProbesInitArgs = nullptr; // Indirect dispatch buffer for active-only probes updating (trace+blend)
|
||||
DynamicDiffuseGlobalIlluminationPass::BindingData Result;
|
||||
|
||||
FORCE_INLINE void Release()
|
||||
@@ -96,6 +97,8 @@ public:
|
||||
RenderTargetPool::Release(ProbesState);
|
||||
RenderTargetPool::Release(ProbesIrradiance);
|
||||
RenderTargetPool::Release(ProbesDistance);
|
||||
SAFE_DELETE_GPU_RESOURCE(ActiveProbes);
|
||||
SAFE_DELETE_GPU_RESOURCE(UpdateProbesInitArgs);
|
||||
}
|
||||
|
||||
~DDGICustomBuffer()
|
||||
@@ -174,7 +177,11 @@ bool DynamicDiffuseGlobalIlluminationPass::setupResources()
|
||||
if (!_cb0 || !_cb1)
|
||||
return true;
|
||||
_csClassify = shader->GetCS("CS_Classify");
|
||||
_csTraceRays = shader->GetCS("CS_TraceRays");
|
||||
_csUpdateProbesInitArgs = shader->GetCS("CS_UpdateProbesInitArgs");
|
||||
_csTraceRays[0] = shader->GetCS("CS_TraceRays", 0);
|
||||
_csTraceRays[1] = shader->GetCS("CS_TraceRays", 1);
|
||||
_csTraceRays[2] = shader->GetCS("CS_TraceRays", 2);
|
||||
_csTraceRays[3] = shader->GetCS("CS_TraceRays", 3);
|
||||
_csUpdateProbesIrradiance = shader->GetCS("CS_UpdateProbes", 0);
|
||||
_csUpdateProbesDistance = shader->GetCS("CS_UpdateProbes", 1);
|
||||
_csUpdateBordersIrradianceRow = shader->GetCS("CS_UpdateBorders", 0);
|
||||
@@ -201,7 +208,11 @@ void DynamicDiffuseGlobalIlluminationPass::OnShaderReloading(Asset* obj)
|
||||
{
|
||||
LastFrameShaderReload = Engine::FrameCount;
|
||||
_csClassify = nullptr;
|
||||
_csTraceRays = nullptr;
|
||||
_csUpdateProbesInitArgs = nullptr;
|
||||
_csTraceRays[0] = nullptr;
|
||||
_csTraceRays[1] = nullptr;
|
||||
_csTraceRays[2] = nullptr;
|
||||
_csTraceRays[3] = nullptr;
|
||||
_csUpdateProbesIrradiance = nullptr;
|
||||
_csUpdateProbesDistance = nullptr;
|
||||
_csUpdateBordersIrradianceRow = nullptr;
|
||||
@@ -221,7 +232,6 @@ void DynamicDiffuseGlobalIlluminationPass::Dispose()
|
||||
// Cleanup
|
||||
_cb0 = nullptr;
|
||||
_cb1 = nullptr;
|
||||
_csTraceRays = nullptr;
|
||||
_shader = nullptr;
|
||||
SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting);
|
||||
#if USE_EDITOR
|
||||
@@ -268,26 +278,32 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
|
||||
// Setup options
|
||||
auto& settings = renderContext.List->Settings.GlobalIllumination;
|
||||
// TODO: implement GI Quality to affect cascades update rate, probes spacing and rays count per probe
|
||||
const float probesSpacing = 100.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades)
|
||||
switch (Graphics::GIQuality)
|
||||
auto* graphicsSettings = GraphicsSettings::Get();
|
||||
const float probesSpacing = Math::Clamp(graphicsSettings->GIProbesSpacing, 10.0f, 1000.0f); // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades)
|
||||
int32 probeRaysCount; // Amount of rays to trace randomly around each probe
|
||||
switch (Graphics::GIQuality) // Ensure to match CS_TraceRays permutations
|
||||
{
|
||||
case Quality::Low:
|
||||
probeRaysCount = 96;
|
||||
break;
|
||||
case Quality::Medium:
|
||||
probeRaysCount = 128;
|
||||
break;
|
||||
case Quality::High:
|
||||
probeRaysCount = 192;
|
||||
break;
|
||||
case Quality::Ultra:
|
||||
default:
|
||||
probeRaysCount = 256;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
bool debugProbes = false; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only
|
||||
ASSERT_LOW_LAYER(probeRaysCount <= DDGI_TRACE_RAYS_LIMIT);
|
||||
bool debugProbes = renderContext.View.Mode == ViewMode::GlobalIllumination;
|
||||
const float indirectLightingIntensity = settings.Intensity;
|
||||
const float probeHistoryWeight = Math::Clamp(settings.TemporalResponse, 0.0f, 0.98f);
|
||||
const float distance = settings.Distance;
|
||||
const Color fallbackIrradiance = settings.FallbackIrradiance;
|
||||
const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality
|
||||
|
||||
// Automatically calculate amount of cascades to cover the GI distance at the current probes spacing
|
||||
const int32 idealProbesCount = 20; // Ideal amount of probes per-cascade to try to fit in order to cover whole distance
|
||||
@@ -298,7 +314,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
idealDistance *= 2;
|
||||
cascadesCount++;
|
||||
}
|
||||
|
||||
|
||||
// Calculate the probes count based on the amount of cascades and the distance to cover
|
||||
const float cascadesDistanceScales[] = { 1.0f, 3.0f, 6.0f, 10.0f }; // Scales each cascade further away from the camera origin
|
||||
const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1];
|
||||
@@ -365,14 +381,19 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
// Allocate probes textures
|
||||
uint64 memUsage = 0;
|
||||
auto desc = GPUTextureDescription::New2D(probesCountTotalX, probesCountTotalY, PixelFormat::Unknown);
|
||||
// TODO rethink probes data placement in memory -> what if we get [50x50x30] resolution? That's 75000 probes! Use sparse storage with active-only probes
|
||||
#define INIT_TEXTURE(texture, format, width, height) desc.Format = format; desc.Width = width; desc.Height = height; ddgiData.texture = RenderTargetPool::Get(desc); if (!ddgiData.texture) return true; memUsage += ddgiData.texture->GetMemoryUsage()
|
||||
desc.Flags = GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess;
|
||||
INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, Math::Min(probesCountCascade, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT));
|
||||
INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountTotalX, probesCountTotalY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag)
|
||||
INIT_TEXTURE(ProbesState, PixelFormat::R8G8B8A8_SNorm, probesCountTotalX, probesCountTotalY);
|
||||
INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2));
|
||||
INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2));
|
||||
#undef INIT_TEXTURE
|
||||
#define INIT_BUFFER(buffer, name) ddgiData.buffer = GPUDevice::Instance->CreateBuffer(TEXT(name)); if (!ddgiData.buffer || ddgiData.buffer->Init(desc2)) return true; memUsage += ddgiData.buffer->GetMemoryUsage();
|
||||
GPUBufferDescription desc2 = GPUBufferDescription::Raw((probesCountCascade + 1) * sizeof(uint32), GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess);
|
||||
INIT_BUFFER(ActiveProbes, "DDGI.ActiveProbes");
|
||||
desc2 = GPUBufferDescription::Buffer(sizeof(GPUDispatchIndirectArgs) * Math::DivideAndRoundUp(probesCountCascade, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT), GPUBufferFlags::Argument | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_UInt, nullptr, sizeof(uint32));
|
||||
INIT_BUFFER(UpdateProbesInitArgs, "DDGI.UpdateProbesInitArgs");
|
||||
#undef INIT_BUFFER
|
||||
LOG(Info, "Dynamic Diffuse Global Illumination memory usage: {0} MB, probes: {1}", memUsage / 1024 / 1024, probesCountTotal);
|
||||
clear = true;
|
||||
}
|
||||
@@ -389,8 +410,8 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
}
|
||||
|
||||
// Calculate which cascades should be updated this frame
|
||||
const uint64 cascadeFrequencies[] = { 1, 2, 3, 5 };
|
||||
// TODO: prevent updating 2 cascades at once on Low quality
|
||||
const uint64 cascadeFrequencies[] = { 2, 3, 5, 7 };
|
||||
//const uint64 cascadeFrequencies[] = { 1, 2, 3, 5 };
|
||||
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
|
||||
bool cascadeSkipUpdate[4];
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
@@ -405,7 +426,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
continue;
|
||||
auto& cascade = ddgiData.Cascades[cascadeIndex];
|
||||
|
||||
// Reset the volume origin and scroll offsets for each axis
|
||||
// Reset the volume origin and scroll offsets for each axis once it overflows
|
||||
for (int32 axis = 0; axis < 3; axis++)
|
||||
{
|
||||
if (cascade.ProbeScrollOffsets.Raw[axis] != 0 && (cascade.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0))
|
||||
@@ -423,7 +444,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
const float value = translation.Raw[axis] / cascade.ProbesSpacing;
|
||||
const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value);
|
||||
cascade.ProbeScrollOffsets.Raw[axis] += scroll;
|
||||
cascade.ProbeScrollClear[axis] = scroll != 0;
|
||||
cascade.ProbeScrollClears.Raw[axis] = scroll;
|
||||
cascade.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1;
|
||||
}
|
||||
}
|
||||
@@ -437,13 +458,11 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
{
|
||||
auto& cascade = ddgiData.Cascades[cascadeIndex];
|
||||
int32 probeScrollClear = cascade.ProbeScrollClear[0] + cascade.ProbeScrollClear[1] * 2 + cascade.ProbeScrollClear[2] * 4; // Pack clear flags into bits
|
||||
ddgiData.Result.Constants.ProbesOriginAndSpacing[cascadeIndex] = Float4(cascade.ProbesOrigin, cascade.ProbesSpacing);
|
||||
ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, probeScrollClear);
|
||||
ddgiData.Result.Constants.ProbeScrollDirections[cascadeIndex] = Int4(cascade.ProbeScrollDirections, 0);
|
||||
ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, 0);
|
||||
}
|
||||
ddgiData.Result.Constants.RayMaxDistance = 10000.0f; // TODO: adjust to match perf/quality ratio (make it based on Global SDF and Global Surface Atlas distance)
|
||||
ddgiData.Result.Constants.ViewDir = renderContext.View.Direction;
|
||||
ddgiData.Result.Constants.ViewPos = renderContext.View.Position;
|
||||
ddgiData.Result.Constants.RaysCount = probeRaysCount;
|
||||
ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight;
|
||||
ddgiData.Result.Constants.IrradianceGamma = 5.0f;
|
||||
@@ -465,6 +484,11 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
data.GlobalSDF = bindingDataSDF.Constants;
|
||||
data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants;
|
||||
data.ResetBlend = clear ? 1.0f : 0.0f;
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
{
|
||||
auto& cascade = ddgiData.Cascades[cascadeIndex];
|
||||
data.ProbeScrollClears[cascadeIndex] = Int4(cascade.ProbeScrollClears, 0);
|
||||
}
|
||||
if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
// Use temporal offset in the dithering factor (gets cleaned out by TAA)
|
||||
@@ -482,26 +506,6 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
context->BindCB(0, _cb0);
|
||||
}
|
||||
|
||||
// Classify probes (activation/deactivation and relocation)
|
||||
{
|
||||
PROFILE_GPU_CPU("Probes Classification");
|
||||
uint32 threadGroups = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE);
|
||||
bindingDataSDF.BindCascades(context, 0);
|
||||
bindingDataSDF.BindCascadeMips(context, 4);
|
||||
context->BindUA(0, ddgiData.Result.ProbesState);
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
{
|
||||
if (cascadeSkipUpdate[cascadeIndex])
|
||||
continue;
|
||||
Data1 data;
|
||||
data.CascadeIndex = cascadeIndex;
|
||||
context->UpdateCB(_cb1, &data);
|
||||
context->BindCB(1, _cb1);
|
||||
context->Dispatch(_csClassify, threadGroups, 1, 1);
|
||||
}
|
||||
context->ResetUA();
|
||||
}
|
||||
|
||||
// Update probes
|
||||
{
|
||||
PROFILE_GPU_CPU("Probes Update");
|
||||
@@ -513,10 +517,38 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
continue;
|
||||
anyDirty = true;
|
||||
|
||||
// Classify probes (activation/deactivation and relocation)
|
||||
{
|
||||
PROFILE_GPU_CPU("Classify Probes");
|
||||
uint32 activeProbesCount = 0;
|
||||
context->UpdateBuffer(ddgiData.ActiveProbes, &activeProbesCount, sizeof(uint32), 0);
|
||||
threadGroupsX = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE);
|
||||
context->BindSR(0, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
|
||||
context->BindUA(0, ddgiData.Result.ProbesState);
|
||||
context->BindUA(1, ddgiData.ActiveProbes->View());
|
||||
Data1 data;
|
||||
data.CascadeIndex = cascadeIndex;
|
||||
context->UpdateCB(_cb1, &data);
|
||||
context->BindCB(1, _cb1);
|
||||
context->Dispatch(_csClassify, threadGroupsX, 1, 1);
|
||||
context->ResetUA();
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
// Build indirect args for probes updating (loop over active-only probes)
|
||||
{
|
||||
PROFILE_GPU_CPU("Init Args");
|
||||
context->BindSR(0, ddgiData.ActiveProbes->View());
|
||||
context->BindUA(0, ddgiData.UpdateProbesInitArgs->View());
|
||||
context->Dispatch(_csUpdateProbesInitArgs, 1, 1, 1);
|
||||
context->ResetUA();
|
||||
}
|
||||
|
||||
// Update probes in batches so ProbesTrace texture can be smaller
|
||||
uint32 arg = 0;
|
||||
for (int32 probesOffset = 0; probesOffset < probesCountCascade; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT)
|
||||
{
|
||||
uint32 probesBatchSize = Math::Min(probesCountCascade - probesOffset, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT);
|
||||
Data1 data;
|
||||
data.CascadeIndex = cascadeIndex;
|
||||
data.ProbeIndexOffset = probesOffset;
|
||||
@@ -528,26 +560,20 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
PROFILE_GPU_CPU("Trace Rays");
|
||||
|
||||
// Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe)
|
||||
ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0);
|
||||
bindingDataSDF.BindCascades(context, 0);
|
||||
bindingDataSDF.BindCascadeMips(context, 4);
|
||||
context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr);
|
||||
context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr);
|
||||
context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View());
|
||||
context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View());
|
||||
context->BindSR(12, ddgiData.Result.ProbesState);
|
||||
context->BindSR(13, skybox);
|
||||
context->BindSR(0, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
|
||||
context->BindSR(2, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr);
|
||||
context->BindSR(3, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr);
|
||||
context->BindSR(4, bindingDataSurfaceAtlas.Objects ? bindingDataSurfaceAtlas.Objects->View() : nullptr);
|
||||
context->BindSR(5, bindingDataSurfaceAtlas.AtlasDepth->View());
|
||||
context->BindSR(6, bindingDataSurfaceAtlas.AtlasLighting->View());
|
||||
context->BindSR(7, ddgiData.Result.ProbesState);
|
||||
context->BindSR(8, skybox);
|
||||
context->BindSR(9, ddgiData.ActiveProbes->View());
|
||||
context->BindUA(0, ddgiData.ProbesTrace->View());
|
||||
context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesBatchSize, 1);
|
||||
context->DispatchIndirect(_csTraceRays[(int32)Graphics::GIQuality], ddgiData.UpdateProbesInitArgs, arg);
|
||||
context->ResetUA();
|
||||
context->ResetSR();
|
||||
#if 0
|
||||
// Probes trace debug preview
|
||||
context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y);
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
context->Draw(ddgiData.ProbesTrace);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update probes irradiance and distance textures (one thread-group per probe)
|
||||
@@ -555,11 +581,16 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
|
||||
PROFILE_GPU_CPU("Update Probes");
|
||||
context->BindSR(0, ddgiData.Result.ProbesState);
|
||||
context->BindSR(1, ddgiData.ProbesTrace->View());
|
||||
context->BindSR(2, ddgiData.ActiveProbes->View());
|
||||
context->BindUA(0, ddgiData.Result.ProbesIrradiance);
|
||||
context->Dispatch(_csUpdateProbesIrradiance, probesBatchSize, 1, 1);
|
||||
context->DispatchIndirect(_csUpdateProbesIrradiance, ddgiData.UpdateProbesInitArgs, arg);
|
||||
context->BindUA(0, ddgiData.Result.ProbesDistance);
|
||||
context->Dispatch(_csUpdateProbesDistance, probesBatchSize, 1, 1);
|
||||
context->DispatchIndirect(_csUpdateProbesDistance, ddgiData.UpdateProbesInitArgs, arg);
|
||||
context->ResetUA();
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
arg += sizeof(GPUDispatchIndirectArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ public:
|
||||
{
|
||||
Float4 ProbesOriginAndSpacing[4];
|
||||
Int4 ProbesScrollOffsets[4];
|
||||
Int4 ProbeScrollDirections[4];
|
||||
uint32 ProbesCounts[3];
|
||||
uint32 CascadesCount;
|
||||
float IrradianceGamma;
|
||||
@@ -25,7 +24,7 @@ public:
|
||||
float RayMaxDistance;
|
||||
float IndirectLightingIntensity;
|
||||
Float4 RaysRotation;
|
||||
Float3 ViewDir;
|
||||
Float3 ViewPos;
|
||||
uint32 RaysCount;
|
||||
Float3 FallbackIrradiance;
|
||||
float Padding0;
|
||||
@@ -46,7 +45,8 @@ private:
|
||||
GPUConstantBuffer* _cb0 = nullptr;
|
||||
GPUConstantBuffer* _cb1 = nullptr;
|
||||
GPUShaderProgramCS* _csClassify;
|
||||
GPUShaderProgramCS* _csTraceRays;
|
||||
GPUShaderProgramCS* _csUpdateProbesInitArgs;
|
||||
GPUShaderProgramCS* _csTraceRays[4];
|
||||
GPUShaderProgramCS* _csUpdateProbesIrradiance;
|
||||
GPUShaderProgramCS* _csUpdateProbesDistance;
|
||||
GPUShaderProgramCS* _csUpdateBordersIrradianceRow;
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Core/Config/GraphicsSettings.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
@@ -34,7 +34,7 @@
|
||||
#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes)
|
||||
#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame
|
||||
#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations)
|
||||
#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS 0 // Debug draws culled chunks bounds (non-empty
|
||||
#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS 0 // Debug draws culled chunks bounds (non-empty)
|
||||
|
||||
#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS || GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
@@ -86,7 +86,8 @@ struct GlobalSurfaceAtlasTile : RectPack<GlobalSurfaceAtlasTile, uint16>
|
||||
struct GlobalSurfaceAtlasObject
|
||||
{
|
||||
uint64 LastFrameUsed;
|
||||
uint64 LastFrameDirty;
|
||||
uint64 LastFrameUpdated;
|
||||
uint64 LightingUpdateFrame; // Index of the frame to update lighting for this object (calculated when object gets dirty or overriden by dynamic lights)
|
||||
Actor* Actor;
|
||||
GlobalSurfaceAtlasTile* Tiles[6];
|
||||
float Radius;
|
||||
@@ -120,6 +121,12 @@ struct GlobalSurfaceAtlasObject
|
||||
}
|
||||
};
|
||||
|
||||
struct GlobalSurfaceAtlasLight
|
||||
{
|
||||
uint64 LastFrameUsed = 0;
|
||||
uint64 LastFrameUpdated = 0;
|
||||
};
|
||||
|
||||
class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener
|
||||
{
|
||||
public:
|
||||
@@ -134,10 +141,12 @@ public:
|
||||
GPUTexture* AtlasLighting = nullptr;
|
||||
GPUBuffer* ChunksBuffer = nullptr;
|
||||
GPUBuffer* CulledObjectsBuffer = nullptr;
|
||||
DynamicTypedBuffer ObjectsBuffer;
|
||||
int32 CulledObjectsCounterIndex = -1;
|
||||
GlobalSurfaceAtlasPass::BindingData Result;
|
||||
GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles
|
||||
Dictionary<void*, GlobalSurfaceAtlasObject> Objects;
|
||||
Dictionary<Guid, GlobalSurfaceAtlasLight> Lights;
|
||||
|
||||
// Cached data to be reused during RasterizeActor
|
||||
uint64 CurrentFrame;
|
||||
@@ -148,12 +157,18 @@ public:
|
||||
float DistanceScalingEnd;
|
||||
float DistanceScaling;
|
||||
|
||||
GlobalSurfaceAtlasCustomBuffer()
|
||||
: ObjectsBuffer(256 * (GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE + GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE * 3 / 4), PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer"))
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE void ClearObjects()
|
||||
{
|
||||
CulledObjectsCounterIndex = -1;
|
||||
LastFrameAtlasDefragmentation = Engine::FrameCount;
|
||||
SAFE_DELETE(AtlasTiles);
|
||||
Objects.Clear();
|
||||
Lights.Clear();
|
||||
}
|
||||
|
||||
FORCE_INLINE void Clear()
|
||||
@@ -188,7 +203,13 @@ public:
|
||||
if (object)
|
||||
{
|
||||
// Dirty object to redraw
|
||||
object->LastFrameDirty = 0;
|
||||
object->LastFrameUpdated = 0;
|
||||
}
|
||||
GlobalSurfaceAtlasLight* light = Lights.TryGet(a->GetID());
|
||||
if (light)
|
||||
{
|
||||
// Dirty light to redraw
|
||||
light->LastFrameUpdated = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,12 +286,20 @@ bool GlobalSurfaceAtlasPass::setupResources()
|
||||
if (_psClear->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
psDesc.DepthTestEnable = false;
|
||||
psDesc.DepthWriteEnable = false;
|
||||
psDesc.DepthFunc = ComparisonFunc::Never;
|
||||
if (!_psClearLighting)
|
||||
{
|
||||
_psClearLighting = device->CreatePipelineState();
|
||||
psDesc.VS = shader->GetVS("VS_Atlas");
|
||||
psDesc.PS = shader->GetPS("PS_ClearLighting");
|
||||
if (_psClearLighting->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psDirectLighting0)
|
||||
{
|
||||
_psDirectLighting0 = device->CreatePipelineState();
|
||||
psDesc.DepthTestEnable = false;
|
||||
psDesc.DepthWriteEnable = false;
|
||||
psDesc.DepthFunc = ComparisonFunc::Never;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
psDesc.PS = shader->GetPS("PS_Lighting", 0);
|
||||
@@ -294,6 +323,7 @@ bool GlobalSurfaceAtlasPass::setupResources()
|
||||
void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj)
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_psClear);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psClearLighting);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting);
|
||||
@@ -309,9 +339,9 @@ void GlobalSurfaceAtlasPass::Dispose()
|
||||
|
||||
// Cleanup
|
||||
SAFE_DELETE(_vertexBuffer);
|
||||
SAFE_DELETE(_objectsBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(_culledObjectsSizeBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psClear);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psClearLighting);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting);
|
||||
@@ -345,8 +375,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
PROFILE_GPU_CPU("Global Surface Atlas");
|
||||
|
||||
// Setup options
|
||||
const int32 resolution = Math::Clamp(Graphics::GlobalSurfaceAtlasResolution, 256, GPU_MAX_TEXTURE_SIZE);
|
||||
const float resolutionInv = 1.0f / resolution;
|
||||
auto* graphicsSettings = GraphicsSettings::Get();
|
||||
const int32 resolution = Math::Clamp(graphicsSettings->GlobalSurfaceAtlasResolution, 256, GPU_MAX_TEXTURE_SIZE);
|
||||
const float resolutionInv = 1.0f / (float)resolution;
|
||||
auto& giSettings = renderContext.List->Settings.GlobalIllumination;
|
||||
const float distance = giSettings.Distance;
|
||||
|
||||
@@ -394,8 +425,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
surfaceAtlasData.AtlasTiles = New<GlobalSurfaceAtlasTile>(0, 0, resolution, resolution);
|
||||
if (!_vertexBuffer)
|
||||
_vertexBuffer = New<DynamicVertexBuffer>(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer"));
|
||||
if (!_objectsBuffer)
|
||||
_objectsBuffer = New<DynamicTypedBuffer>(256 * (GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE + GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE * 3 / 4), PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer"));
|
||||
|
||||
// Utility for writing into tiles vertex buffer
|
||||
const Float2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv);
|
||||
@@ -430,7 +459,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
// Add objects into the atlas
|
||||
{
|
||||
PROFILE_CPU_NAMED("Draw");
|
||||
_objectsBuffer->Clear();
|
||||
surfaceAtlasData.ObjectsBuffer.Clear();
|
||||
_dirtyObjectsBuffer.Clear();
|
||||
_surfaceAtlasData = &surfaceAtlasData;
|
||||
renderContext.View.Pass = DrawPass::GlobalSurfaceAtlas;
|
||||
@@ -589,7 +618,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
// Send objects data to the GPU
|
||||
{
|
||||
PROFILE_GPU_CPU("Update Objects");
|
||||
_objectsBuffer->Flush(context);
|
||||
surfaceAtlasData.ObjectsBuffer.Flush(context);
|
||||
}
|
||||
|
||||
// Init constants
|
||||
@@ -607,10 +636,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
// Each chunk (ChunksBuffer) contains uint with address of the culled objects data start in CulledObjectsBuffer.
|
||||
// If chunk has address=0 then it's unused/empty.
|
||||
// Chunk [0,0,0] is unused and it's address=0 is used for atomic counter for writing into CulledObjectsBuffer.
|
||||
// Each chunk data contains objects count + all objects with tiles copied into buffer.
|
||||
// This allows to quickly convert world-space position into chunk, then read chunk data start and loop over culled objects (less objects and data already in place).
|
||||
// Each chunk data contains objects count + all objects addresses.
|
||||
// This allows to quickly convert world-space position into chunk, then read chunk data start and loop over culled objects.
|
||||
PROFILE_GPU_CPU("Cull Objects");
|
||||
uint32 objectsBufferCapacity = (uint32)((float)_objectsBuffer->Data.Count() * 1.3f);
|
||||
uint32 objectsBufferCapacity = (uint32)((float)surfaceAtlasData.Objects.Count() * 1.3f);
|
||||
|
||||
// Copy counter from ChunksBuffer into staging buffer to access current chunks memory usage to adapt dynamically to the scene complexity
|
||||
if (surfaceAtlasData.ChunksBuffer)
|
||||
@@ -634,7 +663,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
_culledObjectsSizeBuffer->Unmap();
|
||||
if (counter > 0)
|
||||
{
|
||||
objectsBufferCapacity = counter * sizeof(Float4);
|
||||
objectsBufferCapacity = counter;
|
||||
notReady = false;
|
||||
}
|
||||
}
|
||||
@@ -652,28 +681,28 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
}
|
||||
}
|
||||
}
|
||||
if (surfaceAtlasData.CulledObjectsCounterIndex != -1)
|
||||
if (surfaceAtlasData.CulledObjectsCounterIndex != -1 && surfaceAtlasData.CulledObjectsBuffer)
|
||||
{
|
||||
// Copy current counter value
|
||||
_culledObjectsSizeFrames[surfaceAtlasData.CulledObjectsCounterIndex] = currentFrame;
|
||||
context->CopyBuffer(_culledObjectsSizeBuffer, surfaceAtlasData.ChunksBuffer, sizeof(uint32), surfaceAtlasData.CulledObjectsCounterIndex * sizeof(uint32), 0);
|
||||
context->CopyBuffer(_culledObjectsSizeBuffer, surfaceAtlasData.CulledObjectsBuffer, sizeof(uint32), surfaceAtlasData.CulledObjectsCounterIndex * sizeof(uint32), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate buffer for culled objects (estimated size)
|
||||
objectsBufferCapacity = Math::Min(Math::AlignUp(objectsBufferCapacity, 4096u), (uint32)MAX_int32);
|
||||
objectsBufferCapacity = Math::Min(Math::AlignUp<uint32>(objectsBufferCapacity * sizeof(uint32), 4096u), (uint32)MAX_int32);
|
||||
if (!surfaceAtlasData.CulledObjectsBuffer)
|
||||
surfaceAtlasData.CulledObjectsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.CulledObjectsBuffer"));
|
||||
if (surfaceAtlasData.CulledObjectsBuffer->GetSize() < objectsBufferCapacity)
|
||||
{
|
||||
const GPUBufferDescription desc = GPUBufferDescription::Buffer(objectsBufferCapacity, GPUBufferFlags::UnorderedAccess | GPUBufferFlags::ShaderResource, PixelFormat::R32G32B32A32_Float, nullptr, sizeof(Float4));
|
||||
const auto desc = GPUBufferDescription::Raw(objectsBufferCapacity, GPUBufferFlags::UnorderedAccess | GPUBufferFlags::ShaderResource);
|
||||
if (surfaceAtlasData.CulledObjectsBuffer->Init(desc))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear chunks counter (chunk at 0 is used for a counter so chunks buffer is aligned)
|
||||
uint32 counter = 1; // Indicate that 1st float4 is used so value 0 can be used as invalid chunk address
|
||||
context->UpdateBuffer(surfaceAtlasData.ChunksBuffer, &counter, sizeof(counter), 0);
|
||||
// Clear chunks counter (uint at 0 is used for a counter)
|
||||
uint32 counter = 1; // Move write location for culled objects after counter
|
||||
context->UpdateBuffer(surfaceAtlasData.CulledObjectsBuffer, &counter, sizeof(counter), 0);
|
||||
|
||||
// Cull objects into chunks (1 thread per chunk)
|
||||
Data0 data;
|
||||
@@ -686,7 +715,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
context->BindCB(0, _cb0);
|
||||
static_assert(GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION % GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE == 0, "Invalid chunks resolution/groups setting.");
|
||||
const int32 chunkDispatchGroups = GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION / GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE;
|
||||
context->BindSR(0, _objectsBuffer->GetBuffer()->View());
|
||||
context->BindSR(0, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View());
|
||||
context->BindUA(0, surfaceAtlasData.ChunksBuffer->View());
|
||||
context->BindUA(1, surfaceAtlasData.CulledObjectsBuffer->View());
|
||||
context->Dispatch(_csCullObjects, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
|
||||
@@ -700,11 +729,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
{
|
||||
for (int32 x = 0; x < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; x++)
|
||||
{
|
||||
Float3 chunkCoord(x, y, z);
|
||||
Float3 chunkMin = result.GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * result.GlobalSurfaceAtlas.ChunkSize;
|
||||
Float3 chunkMax = chunkMin + result.GlobalSurfaceAtlas.ChunkSize;
|
||||
Float3 chunkCoord((float)x, (float)y, (float)z);
|
||||
Float3 chunkMin = result.Constants.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * result.Constants.ChunkSize;
|
||||
Float3 chunkMax = chunkMin + result.Constants.ChunkSize;
|
||||
BoundingBox chunkBounds(chunkMin, chunkMax);
|
||||
if (Float3::Distance(chunkBounds.GetCenter(), result.GlobalSurfaceAtlas.ViewPos) >= 2000.0f)
|
||||
if (Float3::Distance(chunkBounds.GetCenter(), result.Constants.ViewPos) >= 2000.0f)
|
||||
continue;
|
||||
|
||||
int32 count = 0;
|
||||
@@ -733,37 +762,136 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
result.Atlas[4] = surfaceAtlasData.AtlasLighting;
|
||||
result.Chunks = surfaceAtlasData.ChunksBuffer;
|
||||
result.CulledObjects = surfaceAtlasData.CulledObjectsBuffer;
|
||||
result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer();
|
||||
surfaceAtlasData.Result = result;
|
||||
|
||||
// Render direct lighting into atlas
|
||||
if (surfaceAtlasData.Objects.Count() != 0)
|
||||
{
|
||||
PROFILE_GPU_CPU("Direct Lighting");
|
||||
|
||||
// Copy emissive light into the final direct lighting atlas
|
||||
// TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles together with indirect lighting
|
||||
{
|
||||
PROFILE_GPU_CPU("Copy Emissive");
|
||||
context->CopyTexture(surfaceAtlasData.AtlasLighting, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0);
|
||||
}
|
||||
|
||||
context->SetViewportAndScissors(Viewport(0, 0, (float)resolution, (float)resolution));
|
||||
context->SetRenderTarget(surfaceAtlasData.AtlasLighting->View());
|
||||
context->BindSR(0, surfaceAtlasData.AtlasGBuffer0->View());
|
||||
context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View());
|
||||
context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View());
|
||||
context->BindSR(3, surfaceAtlasData.AtlasDepth->View());
|
||||
context->BindSR(4, _objectsBuffer->GetBuffer()->View());
|
||||
bindingDataSDF.BindCascades(context, 5);
|
||||
bindingDataSDF.BindCascadeMips(context, 9);
|
||||
context->BindSR(4, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View());
|
||||
context->BindSR(5, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(6, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
|
||||
context->BindCB(0, _cb0);
|
||||
Data0 data;
|
||||
data.ViewWorldPos = renderContext.View.Position;
|
||||
data.GlobalSDF = bindingDataSDF.Constants;
|
||||
data.GlobalSurfaceAtlas = result.Constants;
|
||||
|
||||
// Collect objects to update lighting this frame (dirty objects and dirty lights)
|
||||
bool allLightingDirty = false;
|
||||
for (auto& light : renderContext.List->DirectionalLights)
|
||||
{
|
||||
GlobalSurfaceAtlasLight& lightData = surfaceAtlasData.Lights[light.ID];
|
||||
lightData.LastFrameUsed = currentFrame;
|
||||
uint32 redrawFramesCount = (light.StaticFlags & StaticFlags::Lightmap) ? 120 : 4;
|
||||
if (surfaceAtlasData.CurrentFrame - lightData.LastFrameUpdated < (redrawFramesCount + (light.ID.D & redrawFramesCount)))
|
||||
continue;
|
||||
lightData.LastFrameUpdated = currentFrame;
|
||||
|
||||
// Mark all objects to shade
|
||||
allLightingDirty = true;
|
||||
}
|
||||
if (renderContext.View.Flags & ViewFlags::GI && (renderContext.List->DirectionalLights.Count() != 1 || renderContext.List->DirectionalLights[0].StaticFlags & StaticFlags::Lightmap))
|
||||
{
|
||||
switch (renderContext.List->Settings.GlobalIllumination.Mode)
|
||||
{
|
||||
case GlobalIlluminationMode::DDGI:
|
||||
{
|
||||
DynamicDiffuseGlobalIlluminationPass::BindingData bindingDataDDGI;
|
||||
if (!DynamicDiffuseGlobalIlluminationPass::Instance()->Get(renderContext.Buffers, bindingDataDDGI))
|
||||
{
|
||||
GlobalSurfaceAtlasLight& lightData = surfaceAtlasData.Lights[Guid(0, 0, 0, 1)];
|
||||
lightData.LastFrameUsed = currentFrame;
|
||||
uint32 redrawFramesCount = 4; // GI Bounce redraw minimum frequency
|
||||
if (surfaceAtlasData.CurrentFrame - lightData.LastFrameUpdated < redrawFramesCount)
|
||||
break;
|
||||
lightData.LastFrameUpdated = currentFrame;
|
||||
|
||||
// Mark all objects to shade
|
||||
allLightingDirty = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& light : renderContext.List->PointLights)
|
||||
{
|
||||
GlobalSurfaceAtlasLight& lightData = surfaceAtlasData.Lights[light.ID];
|
||||
lightData.LastFrameUsed = currentFrame;
|
||||
uint32 redrawFramesCount = (light.StaticFlags & StaticFlags::Lightmap) ? 120 : 4;
|
||||
if (surfaceAtlasData.CurrentFrame - lightData.LastFrameUpdated < (redrawFramesCount + (light.ID.D & redrawFramesCount)))
|
||||
continue;
|
||||
lightData.LastFrameUpdated = currentFrame;
|
||||
|
||||
if (!allLightingDirty)
|
||||
{
|
||||
// Mark objects to shade
|
||||
for (auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
auto& object = e.Value;
|
||||
Float3 lightToObject = object.Bounds.GetCenter() - light.Position;
|
||||
if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius))
|
||||
continue;
|
||||
object.LightingUpdateFrame = currentFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& light : renderContext.List->SpotLights)
|
||||
{
|
||||
GlobalSurfaceAtlasLight& lightData = surfaceAtlasData.Lights[light.ID];
|
||||
lightData.LastFrameUsed = currentFrame;
|
||||
uint32 redrawFramesCount = (light.StaticFlags & StaticFlags::Lightmap) ? 120 : 4;
|
||||
if (surfaceAtlasData.CurrentFrame - lightData.LastFrameUpdated < (redrawFramesCount + (light.ID.D & redrawFramesCount)))
|
||||
continue;
|
||||
lightData.LastFrameUpdated = currentFrame;
|
||||
|
||||
if (!allLightingDirty)
|
||||
{
|
||||
// Mark objects to shade
|
||||
for (auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
auto& object = e.Value;
|
||||
Float3 lightToObject = object.Bounds.GetCenter() - light.Position;
|
||||
if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius))
|
||||
continue;
|
||||
object.LightingUpdateFrame = currentFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy emissive light into the final direct lighting atlas
|
||||
{
|
||||
PROFILE_GPU_CPU("Copy Emissive");
|
||||
_vertexBuffer->Clear();
|
||||
for (const auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
const auto& object = e.Value;
|
||||
if (!allLightingDirty && object.LightingUpdateFrame != currentFrame)
|
||||
continue;
|
||||
for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
|
||||
{
|
||||
auto* tile = object.Tiles[tileIndex];
|
||||
if (!tile)
|
||||
continue;
|
||||
VB_WRITE_TILE(tile);
|
||||
}
|
||||
}
|
||||
if (_vertexBuffer->Data.Count() != 0)
|
||||
{
|
||||
context->BindSR(7, surfaceAtlasData.AtlasEmissive);
|
||||
context->SetState(_psClearLighting);
|
||||
VB_DRAW();
|
||||
}
|
||||
}
|
||||
|
||||
// Shade object tiles influenced by lights to calculate direct lighting
|
||||
// TODO: reduce redraw frequency for static lights (StaticFlags::Lightmap)
|
||||
for (auto& light : renderContext.List->DirectionalLights)
|
||||
{
|
||||
// Collect tiles to shade
|
||||
@@ -771,6 +899,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
for (const auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
const auto& object = e.Value;
|
||||
if (!allLightingDirty && object.LightingUpdateFrame != currentFrame)
|
||||
continue;
|
||||
for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
|
||||
{
|
||||
auto* tile = object.Tiles[tileIndex];
|
||||
@@ -779,8 +909,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
VB_WRITE_TILE(tile);
|
||||
}
|
||||
}
|
||||
if (_vertexBuffer->Data.Count() == 0)
|
||||
continue;
|
||||
|
||||
// Draw draw light
|
||||
PROFILE_GPU_CPU("Directional Light");
|
||||
const bool useShadow = CanRenderShadow(renderContext.View, light);
|
||||
// TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace
|
||||
light.SetupLightData(&data.Light, useShadow);
|
||||
@@ -797,6 +930,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
for (const auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
const auto& object = e.Value;
|
||||
if (!allLightingDirty && object.LightingUpdateFrame != currentFrame)
|
||||
continue;
|
||||
Float3 lightToObject = object.Bounds.GetCenter() - light.Position;
|
||||
if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius))
|
||||
continue;
|
||||
@@ -808,8 +943,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
VB_WRITE_TILE(tile);
|
||||
}
|
||||
}
|
||||
if (_vertexBuffer->Data.Count() == 0)
|
||||
continue;
|
||||
|
||||
// Draw draw light
|
||||
PROFILE_GPU_CPU("Point Light");
|
||||
const bool useShadow = CanRenderShadow(renderContext.View, light);
|
||||
light.SetupLightData(&data.Light, useShadow);
|
||||
data.Light.Color *= light.IndirectLightingIntensity;
|
||||
@@ -825,6 +963,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
for (const auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
const auto& object = e.Value;
|
||||
if (!allLightingDirty && object.LightingUpdateFrame != currentFrame)
|
||||
continue;
|
||||
Float3 lightToObject = object.Bounds.GetCenter() - light.Position;
|
||||
if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius))
|
||||
continue;
|
||||
@@ -836,8 +976,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
VB_WRITE_TILE(tile);
|
||||
}
|
||||
}
|
||||
if (_vertexBuffer->Data.Count() == 0)
|
||||
continue;
|
||||
|
||||
// Draw draw light
|
||||
PROFILE_GPU_CPU("Spot Light");
|
||||
const bool useShadow = CanRenderShadow(renderContext.View, light);
|
||||
light.SetupLightData(&data.Light, useShadow);
|
||||
data.Light.Color *= light.IndirectLightingIntensity;
|
||||
@@ -846,9 +989,17 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
context->SetState(_psDirectLighting1);
|
||||
VB_DRAW();
|
||||
}
|
||||
|
||||
// Remove unused lights
|
||||
for (auto it = surfaceAtlasData.Lights.Begin(); it.IsNotEnd(); ++it)
|
||||
{
|
||||
if (it->Value.LastFrameUsed != currentFrame)
|
||||
surfaceAtlasData.Lights.Remove(it);
|
||||
}
|
||||
|
||||
// Draw draw indirect light from Global Illumination
|
||||
if (renderContext.View.Flags & ViewFlags::GI)
|
||||
{
|
||||
// Draw draw indirect light from Global Illumination
|
||||
switch (renderContext.List->Settings.GlobalIllumination.Mode)
|
||||
{
|
||||
case GlobalIlluminationMode::DDGI:
|
||||
@@ -860,6 +1011,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
for (const auto& e : surfaceAtlasData.Objects)
|
||||
{
|
||||
const auto& object = e.Value;
|
||||
if (!allLightingDirty && object.LightingUpdateFrame != currentFrame)
|
||||
continue;
|
||||
for (int32 tileIndex = 0; tileIndex < 6; tileIndex++)
|
||||
{
|
||||
auto* tile = object.Tiles[tileIndex];
|
||||
@@ -868,6 +1021,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
|
||||
VB_WRITE_TILE(tile);
|
||||
}
|
||||
}
|
||||
if (_vertexBuffer->Data.Count() == 0)
|
||||
break;
|
||||
PROFILE_GPU_CPU("DDGI");
|
||||
data.DDGI = bindingDataDDGI.Constants;
|
||||
context->BindSR(5, bindingDataDDGI.ProbesState);
|
||||
context->BindSR(6, bindingDataDDGI.ProbesDistance);
|
||||
@@ -927,12 +1083,13 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
|
||||
context->UpdateCB(_cb0, &data);
|
||||
context->BindCB(0, _cb0);
|
||||
}
|
||||
bindingDataSDF.BindCascades(context, 0);
|
||||
bindingDataSDF.BindCascadeMips(context, 4);
|
||||
context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
|
||||
context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
|
||||
context->BindSR(10, bindingData.AtlasDepth->View());
|
||||
context->BindSR(12, skybox);
|
||||
context->BindSR(0, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
|
||||
context->BindSR(2, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
|
||||
context->BindSR(3, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
|
||||
context->BindSR(4, bindingData.Objects ? bindingData.Objects->View() : nullptr);
|
||||
context->BindSR(6, bindingData.AtlasDepth->View());
|
||||
context->BindSR(7, skybox);
|
||||
context->SetState(_psDebug);
|
||||
{
|
||||
Float2 outputSizeThird = outputSize * 0.333f;
|
||||
@@ -943,7 +1100,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
|
||||
context->SetRenderTarget(tempBuffer->View());
|
||||
|
||||
// Full screen - direct light
|
||||
context->BindSR(11, bindingData.AtlasLighting->View());
|
||||
context->BindSR(5, bindingData.AtlasLighting->View());
|
||||
context->SetViewport(outputSize.X, outputSize.Y);
|
||||
context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y));
|
||||
context->DrawFullscreenTriangle();
|
||||
@@ -957,12 +1114,13 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Rebind resources
|
||||
bindingDataSDF.BindCascades(context, 0);
|
||||
bindingDataSDF.BindCascadeMips(context, 4);
|
||||
context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
|
||||
context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
|
||||
context->BindSR(10, bindingData.AtlasDepth->View());
|
||||
context->BindSR(12, skybox);
|
||||
context->BindSR(0, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
|
||||
context->BindSR(1, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
|
||||
context->BindSR(2, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
|
||||
context->BindSR(3, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
|
||||
context->BindSR(4, bindingData.Objects ? bindingData.Objects->View() : nullptr);
|
||||
context->BindSR(6, bindingData.AtlasDepth->View());
|
||||
context->BindSR(7, skybox);
|
||||
context->BindCB(0, _cb0);
|
||||
context->SetState(_psDebug);
|
||||
context->SetRenderTarget(output->View());
|
||||
@@ -972,23 +1130,23 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
|
||||
context->UpdateCB(_cb0, &data);
|
||||
|
||||
// Bottom left - diffuse
|
||||
context->BindSR(11, bindingData.AtlasGBuffer0->View());
|
||||
context->BindSR(5, bindingData.AtlasGBuffer0->View());
|
||||
context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Bottom middle - normals
|
||||
context->BindSR(11, bindingData.AtlasGBuffer1->View());
|
||||
context->BindSR(5, bindingData.AtlasGBuffer1->View());
|
||||
context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeThird.Y, outputSizeThird.X, outputSizeThird.Y));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Bottom right - roughness/metalness/ao
|
||||
context->BindSR(11, bindingData.AtlasGBuffer2->View());
|
||||
context->BindSR(5, bindingData.AtlasGBuffer2->View());
|
||||
context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeTwoThird.Y, outputSizeThird.X, outputSizeThird.Y));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask)
|
||||
void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask, bool useVisibility)
|
||||
{
|
||||
GlobalSurfaceAtlasCustomBuffer& surfaceAtlasData = *_surfaceAtlasData;
|
||||
Float3 boundsSize = localBounds.GetSize() * actor->GetScale();
|
||||
@@ -1058,7 +1216,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
|
||||
|
||||
// Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed)
|
||||
uint32 redrawFramesCount = actor->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4;
|
||||
if (surfaceAtlasData.CurrentFrame - object->LastFrameDirty >= (redrawFramesCount + (actor->GetID().D & redrawFramesCount)))
|
||||
if (surfaceAtlasData.CurrentFrame - object->LastFrameUpdated >= (redrawFramesCount + (actor->GetID().D & redrawFramesCount)))
|
||||
dirty = true;
|
||||
|
||||
// Mark object as used
|
||||
@@ -1069,7 +1227,8 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
|
||||
object->Radius = (float)actorObjectBounds.Radius;
|
||||
if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
|
||||
{
|
||||
object->LastFrameDirty = surfaceAtlasData.CurrentFrame;
|
||||
object->LastFrameUpdated = surfaceAtlasData.CurrentFrame;
|
||||
object->LightingUpdateFrame = surfaceAtlasData.CurrentFrame;
|
||||
_dirtyObjectsBuffer.Add(actorObject);
|
||||
}
|
||||
|
||||
@@ -1078,14 +1237,14 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
|
||||
object->Bounds.Transformation.GetWorld(localToWorldBounds);
|
||||
Matrix worldToLocalBounds;
|
||||
Matrix::Invert(localToWorldBounds, worldToLocalBounds);
|
||||
uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Float4);
|
||||
auto* objectData = _objectsBuffer->WriteReserve<Float4>(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE);
|
||||
uint32 objectAddress = surfaceAtlasData.ObjectsBuffer.Data.Count() / sizeof(Float4);
|
||||
auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve<Float4>(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE);
|
||||
objectData[0] = *(Float4*)&actorObjectBounds;
|
||||
objectData[1] = Float4::Zero; // w unused
|
||||
objectData[1] = Float4::Zero;
|
||||
objectData[2] = Float4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41);
|
||||
objectData[3] = Float4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42);
|
||||
objectData[4] = Float4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43);
|
||||
objectData[5] = Float4(object->Bounds.Extents, 0.0f); // w unused
|
||||
objectData[5] = Float4(object->Bounds.Extents, useVisibility ? 1.0f : 0.0f);
|
||||
auto tileOffsets = reinterpret_cast<uint16*>(&objectData[1]); // xyz used for tile offsets packed into uint16
|
||||
auto objectDataSize = reinterpret_cast<uint32*>(&objectData[1].W); // w used for object size (count of Float4s for object+tiles)
|
||||
*objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE;
|
||||
@@ -1130,7 +1289,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
|
||||
// Per-tile data
|
||||
const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
|
||||
const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING;
|
||||
auto* tileData = _objectsBuffer->WriteReserve<Float4>(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE);
|
||||
auto* tileData = surfaceAtlasData.ObjectsBuffer.WriteReserve<Float4>(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE);
|
||||
tileData[0] = Float4(tile->X, tile->Y, tileWidth, tileHeight) * surfaceAtlasData.ResolutionInv;
|
||||
tileData[1] = Float4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41);
|
||||
tileData[2] = Float4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42);
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
};
|
||||
GPUBuffer* Chunks;
|
||||
GPUBuffer* CulledObjects;
|
||||
GPUBuffer* Objects;
|
||||
ConstantsData Constants;
|
||||
};
|
||||
|
||||
@@ -45,6 +46,7 @@ private:
|
||||
bool _supported = false;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psClear = nullptr;
|
||||
GPUPipelineState* _psClearLighting = nullptr;
|
||||
GPUPipelineState* _psDirectLighting0 = nullptr;
|
||||
GPUPipelineState* _psDirectLighting1 = nullptr;
|
||||
GPUPipelineState* _psIndirectLighting = nullptr;
|
||||
@@ -54,7 +56,6 @@ private:
|
||||
|
||||
// Cache
|
||||
class GPUBuffer* _culledObjectsSizeBuffer = nullptr;
|
||||
class DynamicTypedBuffer* _objectsBuffer = nullptr;
|
||||
class DynamicVertexBuffer* _vertexBuffer = nullptr;
|
||||
class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData;
|
||||
Array<void*> _dirtyObjectsBuffer;
|
||||
@@ -79,7 +80,7 @@ public:
|
||||
void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output);
|
||||
|
||||
// Rasterize actor into the Global Surface Atlas. Call it from actor Draw() method during DrawPass::GlobalSurfaceAtlas.
|
||||
void RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask = MAX_uint32);
|
||||
void RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask = MAX_uint32, bool useVisibility = true);
|
||||
|
||||
private:
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
|
||||
@@ -61,14 +61,18 @@ PACK_STRUCT(struct ModelsRasterizeData
|
||||
Int3 ChunkCoord;
|
||||
float MaxDistance;
|
||||
Float3 CascadeCoordToPosMul;
|
||||
int ObjectsCount;
|
||||
int32 ObjectsCount;
|
||||
Float3 CascadeCoordToPosAdd;
|
||||
int32 CascadeResolution;
|
||||
float Padding0;
|
||||
int32 CascadeIndex;
|
||||
float CascadeVoxelSize;
|
||||
int32 CascadeMipResolution;
|
||||
int32 CascadeMipFactor;
|
||||
uint32 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
|
||||
uint32 GenerateMipTexResolution;
|
||||
uint32 GenerateMipCoordScale;
|
||||
uint32 GenerateMipTexOffsetX;
|
||||
uint32 GenerateMipMipOffsetX;
|
||||
});
|
||||
|
||||
struct RasterizeModel
|
||||
@@ -125,8 +129,6 @@ uint32 GetHash(const RasterizeChunkKey& key)
|
||||
|
||||
struct CascadeData
|
||||
{
|
||||
GPUTexture* Texture = nullptr;
|
||||
GPUTexture* Mip = nullptr;
|
||||
Float3 Position;
|
||||
float VoxelSize;
|
||||
BoundingBox Bounds;
|
||||
@@ -163,18 +165,14 @@ struct CascadeData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~CascadeData()
|
||||
{
|
||||
RenderTargetPool::Release(Texture);
|
||||
RenderTargetPool::Release(Mip);
|
||||
}
|
||||
};
|
||||
|
||||
class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener
|
||||
{
|
||||
public:
|
||||
int32 Resolution = 0;
|
||||
GPUTexture* Texture = nullptr;
|
||||
GPUTexture* TextureMip = nullptr;
|
||||
Array<CascadeData, FixedAllocation<4>> Cascades;
|
||||
HashSet<ScriptingTypeHandle> ObjectTypes;
|
||||
HashSet<GPUTexture*> SDFTextures;
|
||||
@@ -187,6 +185,8 @@ public:
|
||||
e.Item->Deleted.Unbind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(this);
|
||||
e.Item->ResidentMipsChanged.Unbind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureResidentMipsChanged>(this);
|
||||
}
|
||||
RenderTargetPool::Release(Texture);
|
||||
RenderTargetPool::Release(TextureMip);
|
||||
}
|
||||
|
||||
void OnSDFTextureDeleted(ScriptingObject* object)
|
||||
@@ -300,8 +300,7 @@ bool GlobalSignDistanceFieldPass::setupResources()
|
||||
_csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1);
|
||||
_csRasterizeHeightfield = shader->GetCS("CS_RasterizeHeightfield");
|
||||
_csClearChunk = shader->GetCS("CS_ClearChunk");
|
||||
_csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0);
|
||||
_csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1);
|
||||
_csGenerateMip = shader->GetCS("CS_GenerateMip");
|
||||
|
||||
// Init buffer
|
||||
if (!_objectsBuffer)
|
||||
@@ -329,8 +328,7 @@ void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj)
|
||||
_csRasterizeModel1 = nullptr;
|
||||
_csRasterizeHeightfield = nullptr;
|
||||
_csClearChunk = nullptr;
|
||||
_csGenerateMip0 = nullptr;
|
||||
_csGenerateMip1 = nullptr;
|
||||
_csGenerateMip = nullptr;
|
||||
_cb0 = nullptr;
|
||||
_cb1 = nullptr;
|
||||
invalidateResources();
|
||||
@@ -351,18 +349,6 @@ void GlobalSignDistanceFieldPass::Dispose()
|
||||
ChunksCache.SetCapacity(0);
|
||||
}
|
||||
|
||||
void GlobalSignDistanceFieldPass::BindingData::BindCascades(GPUContext* context, int32 srvSlot)
|
||||
{
|
||||
for (int32 i = 0; i < 4; i++)
|
||||
context->BindSR(srvSlot + i, Cascades[i] ? Cascades[i]->ViewVolume() : nullptr);
|
||||
}
|
||||
|
||||
void GlobalSignDistanceFieldPass::BindingData::BindCascadeMips(GPUContext* context, int32 srvSlot)
|
||||
{
|
||||
for (int32 i = 0; i < 4; i++)
|
||||
context->BindSR(srvSlot + i, CascadeMips[i] ? CascadeMips[i]->ViewVolume() : nullptr);
|
||||
}
|
||||
|
||||
bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result)
|
||||
{
|
||||
auto* sdfData = buffers ? buffers->FindCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField")) : nullptr;
|
||||
@@ -428,14 +414,13 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
sdfData.Cascades.Resize(cascadesCount);
|
||||
sdfData.Resolution = resolution;
|
||||
updated = true;
|
||||
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
||||
for (auto& cascade : sdfData.Cascades)
|
||||
auto desc = GPUTextureDescription::New3D(resolution * cascadesCount, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
||||
{
|
||||
GPUTexture*& texture = cascade.Texture;
|
||||
GPUTexture*& texture = sdfData.Texture;
|
||||
if (texture && texture->Width() != desc.Width)
|
||||
{
|
||||
RenderTargetPool::Release(texture);
|
||||
texture = nullptr;
|
||||
sdfData.Texture = nullptr;
|
||||
}
|
||||
if (!texture)
|
||||
{
|
||||
@@ -444,10 +429,11 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
return true;
|
||||
}
|
||||
}
|
||||
desc.Width = desc.Height = desc.Depth = resolutionMip;
|
||||
desc.Width = resolutionMip * cascadesCount;
|
||||
desc.Height = desc.Depth = resolutionMip;
|
||||
for (auto& cascade : sdfData.Cascades)
|
||||
{
|
||||
GPUTexture*& texture = cascade.Mip;
|
||||
GPUTexture*& texture = sdfData.TextureMip;
|
||||
if (texture && texture->Width() != desc.Width)
|
||||
{
|
||||
RenderTargetPool::Release(texture);
|
||||
@@ -469,10 +455,12 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
{
|
||||
cascade.NonEmptyChunks.Clear();
|
||||
cascade.StaticChunks.Clear();
|
||||
context->ClearUA(cascade.Texture, Float4::One);
|
||||
context->ClearUA(cascade.Mip, Float4::One);
|
||||
}
|
||||
LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0].Texture->GetMemoryUsage() + sdfData.Cascades[0].Mip->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024);
|
||||
uint64 memoryUsage = sdfData.Texture->GetMemoryUsage();
|
||||
context->ClearUA(sdfData.Texture, Float4::One);
|
||||
memoryUsage += sdfData.TextureMip->GetMemoryUsage();
|
||||
context->ClearUA(sdfData.TextureMip, Float4::One);
|
||||
LOG(Info, "Global SDF memory usage: {0} MB", memoryUsage / 1024 / 1024);
|
||||
}
|
||||
for (SceneRendering* scene : renderContext.List->Scenes)
|
||||
sdfData.ListenSceneRendering(scene);
|
||||
@@ -498,6 +486,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
bool anyDraw = false;
|
||||
const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 };
|
||||
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
|
||||
GPUTextureView* textureView = sdfData.Texture->ViewVolume();
|
||||
GPUTextureView* textureMipView = sdfData.TextureMip->ViewVolume();
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
{
|
||||
// Reduce frequency of the updates
|
||||
@@ -514,8 +504,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
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 * 0.5f); // Skip too small objects for this cascade
|
||||
GPUTextureView* cascadeView = cascade.Texture->ViewVolume();
|
||||
GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume();
|
||||
|
||||
// Clear cascade before rasterization
|
||||
{
|
||||
@@ -539,6 +527,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
_objectsBufferCount = 0;
|
||||
_voxelSize = cascadeVoxelSize;
|
||||
_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;
|
||||
{
|
||||
@@ -556,24 +546,18 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
}
|
||||
|
||||
// Perform batched chunks rasterization
|
||||
if (!anyDraw)
|
||||
{
|
||||
anyDraw = true;
|
||||
context->ResetSR();
|
||||
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
||||
tmpMip = RenderTargetPool::Get(desc);
|
||||
if (!tmpMip)
|
||||
return true;
|
||||
}
|
||||
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.CascadeResolution = resolution;
|
||||
data.CascadeMipResolution = resolutionMip;
|
||||
data.CascadeIndex = cascadeIndex;
|
||||
data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR;
|
||||
data.CascadeVoxelSize = cascadeVoxelSize;
|
||||
context->BindUA(0, cascadeView);
|
||||
context->BindUA(0, textureView);
|
||||
context->BindCB(1, _cb1);
|
||||
const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE;
|
||||
bool anyChunkDispatch = false;
|
||||
@@ -738,25 +722,55 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
if (updated || anyChunkDispatch)
|
||||
{
|
||||
PROFILE_GPU_CPU("Generate Mip");
|
||||
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);
|
||||
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;
|
||||
context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
||||
context->UnBindSR(0);
|
||||
if (!tmpMip)
|
||||
{
|
||||
// Use temporary texture to flood fill mip
|
||||
auto desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
|
||||
tmpMip = RenderTargetPool::Get(desc);
|
||||
if (!tmpMip)
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
data.GenerateMipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution;
|
||||
context->UpdateCB(_cb1, &data);
|
||||
context->BindSR(0, textureView);
|
||||
context->BindUA(0, textureMipView);
|
||||
context->Dispatch(_csGenerateMip, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
||||
|
||||
data.GenerateMipTexResolution = data.CascadeMipResolution;
|
||||
data.GenerateMipCoordScale = 1;
|
||||
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 ((i & 1) == 1)
|
||||
{
|
||||
// Mip -> Tmp
|
||||
context->BindSR(0, textureMipView);
|
||||
context->BindUA(0, tmpMipView);
|
||||
data.GenerateMipTexOffsetX = data.CascadeIndex * data.CascadeMipResolution;
|
||||
data.GenerateMipMipOffsetX = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tmp -> Mip
|
||||
context->BindSR(0, tmpMipView);
|
||||
context->BindUA(0, textureMipView);
|
||||
data.GenerateMipTexOffsetX = 0;
|
||||
data.GenerateMipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution;
|
||||
}
|
||||
context->UpdateCB(_cb1, &data);
|
||||
context->Dispatch(_csGenerateMip, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
|
||||
}
|
||||
if (floodFillIterations % 2 == 0)
|
||||
Swap(tmpMipView, cascadeMipView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,26 +785,22 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
|
||||
}
|
||||
|
||||
// Copy results
|
||||
static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
|
||||
static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
|
||||
result.Texture = sdfData.Texture;
|
||||
result.TextureMip = sdfData.TextureMip;
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
|
||||
{
|
||||
auto& cascade = sdfData.Cascades[cascadeIndex];
|
||||
const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex];
|
||||
const float cascadeMaxDistance = cascadeDistance * 2;
|
||||
const float cascadeVoxelSize = cascadeMaxDistance / resolution;
|
||||
const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution;
|
||||
const Float3 center = cascade.Position;
|
||||
result.Constants.CascadePosDistance[cascadeIndex] = Float4(center, cascadeDistance);
|
||||
result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, cascadeDistance);
|
||||
result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = cascadeVoxelSize;
|
||||
result.Cascades[cascadeIndex] = cascade.Texture;
|
||||
result.CascadeMips[cascadeIndex] = cascade.Mip;
|
||||
}
|
||||
for (int32 cascadeIndex = cascadesCount; cascadeIndex < 4; cascadeIndex++)
|
||||
{
|
||||
result.Constants.CascadePosDistance[cascadeIndex] = result.Constants.CascadePosDistance[cascadesCount - 1];
|
||||
result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = result.Constants.CascadeVoxelSize.Raw[cascadesCount - 1];
|
||||
result.Cascades[cascadeIndex] = nullptr;
|
||||
result.CascadeMips[cascadeIndex] = nullptr;
|
||||
}
|
||||
result.Constants.Resolution = (float)resolution;
|
||||
result.Constants.CascadesCount = cascadesCount;
|
||||
@@ -820,8 +830,8 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC
|
||||
context->UpdateCB(_cb0, &data);
|
||||
context->BindCB(0, _cb0);
|
||||
}
|
||||
bindingData.BindCascades(context, 0);
|
||||
bindingData.BindCascadeMips(context, 4);
|
||||
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);
|
||||
|
||||
@@ -23,12 +23,9 @@ public:
|
||||
// Binding data for the GPU.
|
||||
struct BindingData
|
||||
{
|
||||
GPUTexture* Cascades[4];
|
||||
GPUTexture* CascadeMips[4];
|
||||
GPUTexture* Texture;
|
||||
GPUTexture* TextureMip;
|
||||
ConstantsData Constants;
|
||||
|
||||
void BindCascades(GPUContext* context, int32 srvSlot);
|
||||
void BindCascadeMips(GPUContext* context, int32 srvSlot);
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -39,8 +36,7 @@ private:
|
||||
GPUShaderProgramCS* _csRasterizeModel1 = nullptr;
|
||||
GPUShaderProgramCS* _csRasterizeHeightfield = nullptr;
|
||||
GPUShaderProgramCS* _csClearChunk = nullptr;
|
||||
GPUShaderProgramCS* _csGenerateMip0 = nullptr;
|
||||
GPUShaderProgramCS* _csGenerateMip1 = nullptr;
|
||||
GPUShaderProgramCS* _csGenerateMip = nullptr;
|
||||
GPUConstantBuffer* _cb0 = nullptr;
|
||||
GPUConstantBuffer* _cb1 = nullptr;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ struct RendererDirectionalLightData
|
||||
float ShadowsSharpness;
|
||||
float VolumetricScatteringIntensity;
|
||||
|
||||
StaticFlags StaticFlags;
|
||||
float IndirectLightingIntensity;
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
@@ -43,6 +44,8 @@ struct RendererDirectionalLightData
|
||||
float ContactShadowsLength;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
Guid ID;
|
||||
|
||||
void SetupLightData(LightData* data, bool useShadow) const;
|
||||
};
|
||||
|
||||
@@ -76,11 +79,13 @@ struct RendererSpotLightData
|
||||
float IndirectLightingIntensity;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
StaticFlags StaticFlags;
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
int8 UseInverseSquaredFalloff : 1;
|
||||
|
||||
GPUTexture* IESTexture;
|
||||
Guid ID;
|
||||
|
||||
void SetupLightData(LightData* data, bool useShadow) const;
|
||||
};
|
||||
@@ -111,11 +116,13 @@ struct RendererPointLightData
|
||||
float IndirectLightingIntensity;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
StaticFlags StaticFlags;
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
int8 UseInverseSquaredFalloff : 1;
|
||||
|
||||
GPUTexture* IESTexture;
|
||||
Guid ID;
|
||||
|
||||
void SetupLightData(LightData* data, bool useShadow) const;
|
||||
};
|
||||
@@ -131,10 +138,12 @@ struct RendererSkyLightData
|
||||
Float3 AdditiveColor;
|
||||
float IndirectLightingIntensity;
|
||||
|
||||
StaticFlags StaticFlags;
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
|
||||
CubeTexture* Image;
|
||||
Guid ID;
|
||||
|
||||
void SetupLightData(LightData* data, bool useShadow) const;
|
||||
};
|
||||
@@ -211,7 +220,6 @@ struct DrawBatch
|
||||
class RenderListAllocation
|
||||
{
|
||||
public:
|
||||
|
||||
static FLAXENGINE_API void* Allocate(uintptr size);
|
||||
static FLAXENGINE_API void Free(void* ptr, uintptr size);
|
||||
|
||||
@@ -222,7 +230,6 @@ public:
|
||||
uintptr _size;
|
||||
|
||||
public:
|
||||
|
||||
FORCE_INLINE Data()
|
||||
{
|
||||
}
|
||||
@@ -329,7 +336,7 @@ struct DrawCallsList
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(RenderList);
|
||||
DECLARE_SCRIPTING_TYPE(RenderList);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the new renderer list object or reuses already allocated one.
|
||||
@@ -349,7 +356,6 @@ DECLARE_SCRIPTING_TYPE(RenderList);
|
||||
static void CleanupCache();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// All scenes for rendering.
|
||||
/// </summary>
|
||||
@@ -458,11 +464,9 @@ public:
|
||||
Float3 FrustumCornersVs[8];
|
||||
|
||||
private:
|
||||
|
||||
DynamicVertexBuffer _instanceBuffer;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Blends the postprocessing settings into the final options.
|
||||
/// </summary>
|
||||
@@ -527,7 +531,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init cache for given task
|
||||
/// </summary>
|
||||
@@ -540,7 +543,6 @@ public:
|
||||
void Clear();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Adds the draw call to the draw lists.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Config.h"
|
||||
#include "Engine/GraphicsDevice/Vulkan/Types.h"
|
||||
|
||||
// Use glslang for HLSL to SPIR-V translation
|
||||
@@ -682,6 +683,10 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat
|
||||
{
|
||||
auto& descriptor = descriptorsCollector.Descriptors[i];
|
||||
|
||||
// Skip cases (eg. AppendStructuredBuffer counter buffer)
|
||||
if (descriptor.Slot == MAX_uint16)
|
||||
continue;
|
||||
|
||||
auto& d = header.DescriptorInfo.DescriptorTypes[header.DescriptorInfo.DescriptorTypesCount++];
|
||||
d.Binding = descriptor.Binding;
|
||||
d.Set = stageSet;
|
||||
@@ -694,12 +699,15 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat
|
||||
switch (descriptor.BindingType)
|
||||
{
|
||||
case SpirvShaderResourceBindingType::CB:
|
||||
ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_CB_BINDED);
|
||||
bindings.UsedCBsMask |= 1 << descriptor.Slot;
|
||||
break;
|
||||
case SpirvShaderResourceBindingType::SRV:
|
||||
ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_SR_BINDED);
|
||||
bindings.UsedSRsMask |= 1 << descriptor.Slot;
|
||||
break;
|
||||
case SpirvShaderResourceBindingType::UAV:
|
||||
ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_UA_BINDED);
|
||||
bindings.UsedUAsMask |= 1 << descriptor.Slot;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ void Terrain::Draw(RenderContext& renderContext)
|
||||
Matrix::Invert(chunk->GetWorld(), worldToLocal);
|
||||
BoundingBox::Transform(chunk->GetBounds(), worldToLocal, localBounds);
|
||||
BoundingSphere::FromBox(chunk->GetBounds(), chunkSphere);
|
||||
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetWorld(), localBounds, 1 << 2);
|
||||
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetWorld(), localBounds, 1 << 2, false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -170,8 +170,7 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri
|
||||
format = TEXT("Texture3D {0} : register(t{1});");
|
||||
break;
|
||||
case MaterialParameterType::GlobalSDF:
|
||||
format = TEXT("Texture3D<float> {0}_Tex[4] : register(t{1});");
|
||||
registers = 4;
|
||||
format = TEXT("Texture3D<float> {0}_Tex : register(t{1});");
|
||||
zeroOffset = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user