From 7e2e764e1eaa194b171ca530f1aba56b87caf68a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 8 Feb 2022 17:56:45 +0100 Subject: [PATCH 001/144] Fix `CollisionsHelper::ClosestPointPointTriangle` --- Source/Engine/Core/Math/CollisionsHelper.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index 8ec9d0212..b966523ec 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -93,14 +93,20 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec const float d1 = Vector3::Dot(ab, ap); const float d2 = Vector3::Dot(ac, ap); if (d1 <= 0.0f && d2 <= 0.0f) + { result = vertex1; //Barycentric coordinates (1,0,0) + return; + } // Check if P in vertex region outside B const Vector3 bp = point - vertex2; const float d3 = Vector3::Dot(ab, bp); const float d4 = Vector3::Dot(ac, bp); if (d3 >= 0.0f && d4 <= d3) + { result = vertex2; // Barycentric coordinates (0,1,0) + return; + } // Check if P in edge region of AB, if so return projection of P onto AB const float vc = d1 * d4 - d3 * d2; @@ -108,6 +114,7 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec { const float v = d1 / (d1 - d3); result = vertex1 + v * ab; //Barycentric coordinates (1-v,v,0) + return; } //Check if P in vertex region outside C @@ -115,7 +122,10 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec const float d5 = Vector3::Dot(ab, cp); const float d6 = Vector3::Dot(ac, cp); if (d6 >= 0.0f && d5 <= d6) + { result = vertex3; //Barycentric coordinates (0,0,1) + return; + } //Check if P in edge region of AC, if so return projection of P onto AC const float vb = d5 * d2 - d1 * d6; @@ -123,6 +133,7 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec { const float w = d2 / (d2 - d6); result = vertex1 + w * ac; //Barycentric coordinates (1-w,0,w) + return; } //Check if P in edge region of BC, if so return projection of P onto BC @@ -131,6 +142,7 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec { const float w = (d4 - d3) / (d4 - d3 + (d5 - d6)); result = vertex2 + w * (vertex3 - vertex2); //Barycentric coordinates (0,1-w,w) + return; } //P inside face region. Compute Q through its Barycentric coordinates (u,v,w) From f67371d52400baac425f72b3aa765f1c3515d42c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 8 Feb 2022 18:04:44 +0100 Subject: [PATCH 002/144] Add additional `UploadMipMapAsync` for `GPUTexture` update with custom row/slice pitch --- .../Async/Tasks/GPUUploadTextureMipTask.h | 20 ++++++++--------- .../Engine/Graphics/Textures/GPUTexture.cpp | 22 +++++++++++-------- Source/Engine/Graphics/Textures/GPUTexture.h | 14 +++++++++++- .../Graphics/Textures/StreamingTexture.cpp | 2 +- Source/Engine/Render2D/FontTextureAtlas.cpp | 4 +++- Source/Engine/Terrain/TerrainPatch.cpp | 8 +++++-- 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h index f96b93d68..2573505ba 100644 --- a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h +++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h @@ -16,7 +16,7 @@ class GPUUploadTextureMipTask : public GPUTask protected: GPUTextureReference _texture; - int32 _mipIndex; + int32 _mipIndex, _rowPitch, _slicePitch; BytesContainer _data; public: @@ -27,11 +27,15 @@ public: /// The target texture. /// The target texture mip data. /// The data to upload. + /// The data row pitch. + /// The data slice pitch. /// True if copy data to the temporary buffer, otherwise the input data will be used directly. Then ensure it is valid during the copy operation period (for the next few frames). - GPUUploadTextureMipTask(GPUTexture* texture, int32 mipIndex, Span data, bool copyData) + GPUUploadTextureMipTask(GPUTexture* texture, int32 mipIndex, Span data, int32 rowPitch, int32 slicePitch, bool copyData) : GPUTask(Type::UploadTexture) , _texture(texture) , _mipIndex(mipIndex) + , _rowPitch(rowPitch) + , _slicePitch(slicePitch) { _texture.OnUnload.Bind(this); @@ -66,18 +70,14 @@ protected: return Result::MissingResources; ASSERT(texture->IsAllocated()); - // Cache data - const int32 arraySize = texture->ArraySize(); - uint32 rowPitch, slicePitch; - texture->ComputePitch(_mipIndex, rowPitch, slicePitch); - ASSERT((uint32)_data.Length() >= slicePitch * arraySize); - // Update all array slices const byte* dataSource = _data.Get(); + const int32 arraySize = texture->ArraySize(); + ASSERT(_data.Length() >= _slicePitch * arraySize); for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { - context->GPU->UpdateTexture(texture, arrayIndex, _mipIndex, dataSource, rowPitch, slicePitch); - dataSource += slicePitch; + context->GPU->UpdateTexture(texture, arrayIndex, _mipIndex, dataSource, _rowPitch, _slicePitch); + dataSource += _slicePitch; } return Result::Ok; diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 4c37a7ed4..f9cf13cab 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -595,17 +595,21 @@ void GPUTexture::OnReleaseGPU() _residentMipLevels = 0; } -GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex) +GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, bool copyData) { - ASSERT(IsRegularTexture() && IsAllocated()); - //ASSERT(Math::IsInRange(mipIndex, HighestResidentMipIndex() - 1, MipLevels() - 1) && mipIndex < MipLevels() && data.IsValid()); + uint32 rowPitch, slicePitch; + ComputePitch(mipIndex, rowPitch, slicePitch); + return UploadMipMapAsync(data, mipIndex, rowPitch, slicePitch, copyData); +} + +GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData) +{ + ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); - ASSERT(data.Length() == SlicePitch(mipIndex) * ArraySize()); - - // Create task - auto task = ::New(this, mipIndex, data, false); - ASSERT(task && task->HasReference(this)); - + ASSERT(data.Length() >= slicePitch); + // TODO: support texture data upload to the GPU on a main thread during rendering without this async task (faster direct upload) + auto task = ::New(this, mipIndex, data, rowPitch, slicePitch, copyData); + ASSERT_LOW_LAYER(task && task->HasReference(this)); return task; } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index be817a49e..28007196d 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -524,8 +524,20 @@ public: /// /// Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing) /// Mip level index. + /// If true, the data will be copied to the async execution task instead of using the input pointer provided. /// Created async task or null if cannot. - GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex); + GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, bool copyData = false); + + /// + /// Uploads mip map data to the GPU. Creates async GPU task. + /// + /// Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing) + /// Mip level index. + /// The data row pitch. + /// The data slice pitch. + /// If true, the data will be copied to the async execution task instead of using the input pointer provided. + /// Created async task or null if cannot. + GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData = false); /// /// Stops current thread execution to gather texture data from the GPU. diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 0d7062e93..0290048fc 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -316,7 +316,7 @@ private: public: StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex) - : GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span(nullptr, 0), false) + : GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span(nullptr, 0), 0, 0, false) , _streamingTexture(texture) , _dataLock(_streamingTexture->GetOwner()->LockData()) { diff --git a/Source/Engine/Render2D/FontTextureAtlas.cpp b/Source/Engine/Render2D/FontTextureAtlas.cpp index 9595d5c79..88bc47e0d 100644 --- a/Source/Engine/Render2D/FontTextureAtlas.cpp +++ b/Source/Engine/Render2D/FontTextureAtlas.cpp @@ -210,7 +210,9 @@ void FontTextureAtlas::Flush() // Upload data to the GPU BytesContainer data; data.Link(_data); - _texture->UploadMipMapAsync(data, 0)->Start(); + auto task = _texture->UploadMipMapAsync(data, 0); + if (task) + task->Start(); // Clear dirty flag _isDirty = false; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 28d103f05..f4320400c 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -1627,7 +1627,9 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int LOG(Warning, "Failed to update splatmap texture. It's not allocated."); continue; } - t->UploadMipMapAsync(dataSplatmap->Mips[mipIndex].Data, mipIndex)->Start(); + auto task = t->UploadMipMapAsync(dataSplatmap->Mips[mipIndex].Data, mipIndex); + if (task) + task->Start(); } } else @@ -1745,7 +1747,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int // Update terrain texture (on a GPU) for (int32 mipIndex = 0; mipIndex < _dataHeightmap->Mips.Count(); mipIndex++) { - texture->UploadMipMapAsync(_dataHeightmap->Mips[mipIndex].Data, mipIndex)->Start(); + auto task = texture->UploadMipMapAsync(_dataHeightmap->Mips[mipIndex].Data, mipIndex); + if (task) + task->Start(); } #if 1 From 9a3a6c8a72c2c67e6ce4e56e2ce5cb6bf6d49905 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 8 Feb 2022 18:05:20 +0100 Subject: [PATCH 003/144] Fix depth pitch in `UpdateTexture` on D3D11 for volume textures --- Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index dd422bf1c..53234330e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -725,7 +725,8 @@ void GPUContextDX11::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32 auto textureDX11 = static_cast(texture); const int32 subresourceIndex = RenderToolsDX::CalcSubresourceIndex(mipIndex, arrayIndex, texture->MipLevels()); - _context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, static_cast(rowPitch), slicePitch); + const uint32 depthPitch = texture->IsVolume() ? slicePitch / texture->Depth() : slicePitch; + _context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, (UINT)rowPitch, (UINT)depthPitch); //D3D11_MAPPED_SUBRESOURCE mapped; //_device->GetIM()->Map(_resource, textureMipIndex, D3D11_MAP_WRITE_DISCARD, 0, &mapped); From 70ca42794aa78a8473c7bf07495af5d26a30b855 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 8 Feb 2022 18:06:02 +0100 Subject: [PATCH 004/144] Add `ClearUA` to GPUContext to clear texture with float values --- .../DirectX/DX11/GPUContextDX11.cpp | 7 +++++++ .../DirectX/DX11/GPUContextDX11.h | 1 + .../DirectX/DX12/GPUContextDX12.cpp | 14 ++++++++++++++ .../DirectX/DX12/GPUContextDX12.h | 1 + .../Engine/GraphicsDevice/Null/GPUContextNull.h | 4 ++++ .../GraphicsDevice/Vulkan/GPUContextVulkan.cpp | 17 +++++++++++++++++ .../GraphicsDevice/Vulkan/GPUContextVulkan.h | 1 + 7 files changed, 45 insertions(+) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 53234330e..f558152f1 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -188,6 +188,13 @@ void GPUContextDX11::ClearUA(GPUTexture* texture, const uint32 value[4]) _context->ClearUnorderedAccessViewUint(uav, value); } +void GPUContextDX11::ClearUA(GPUTexture* texture, const Vector4& value) +{ + ASSERT(texture != nullptr && texture->IsUnorderedAccess()); + auto uav = ((GPUTextureViewDX11*)(texture->IsVolume() ? texture->ViewVolume() : texture->View()))->UAV(); + _context->ClearUnorderedAccessViewFloat(uav, value.Raw); +} + void GPUContextDX11::ResetRenderTarget() { if (_rtCount != 0 || _rtDepth) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h index 65aa31a5f..766aefe45 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h @@ -110,6 +110,7 @@ public: void ClearUA(GPUBuffer* buf, const Vector4& value) override; void ClearUA(GPUBuffer* buf, const uint32 value[4]) override; void ClearUA(GPUTexture* texture, const uint32 value[4]) override; + void ClearUA(GPUTexture* texture, const Vector4& value) override; void ResetRenderTarget() override; void SetRenderTarget(GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 966ce6dc8..2e2f57b06 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -755,6 +755,20 @@ void GPUContextDX12::ClearUA(GPUTexture* texture, const uint32 value[4]) _commandList->ClearUnorderedAccessViewUint(desc.GPU, uav, texDX12->GetResource(), value, 0, nullptr); } +void GPUContextDX12::ClearUA(GPUTexture* texture, const Vector4& value) +{ + ASSERT(texture != nullptr && texture->IsUnorderedAccess()); + auto texDX12 = reinterpret_cast(texture); + + SetResourceState(texDX12, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + flushRBs(); + + auto uav = ((GPUTextureViewDX12*)(texDX12->IsVolume() ? texDX12->ViewVolume() : texDX12->View(0)))->UAV(); + Descriptor desc; + GetActiveHeapDescriptor(uav, desc); + _commandList->ClearUnorderedAccessViewFloat(desc.GPU, uav, texDX12->GetResource(), value.Raw, 0, nullptr); +} + void GPUContextDX12::ResetRenderTarget() { if (_rtDepth || _rtCount != 0) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index 029745f37..b2285bd84 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -160,6 +160,7 @@ public: void ClearUA(GPUBuffer* buf, const Vector4& value) override; void ClearUA(GPUBuffer* buf, const uint32 value[4]) override; void ClearUA(GPUTexture* texture, const uint32 value[4]) override; + void ClearUA(GPUTexture* texture, const Vector4& value) override; void ResetRenderTarget() override; void SetRenderTarget(GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; diff --git a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h index 3731ef204..64c4b1aca 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h @@ -64,6 +64,10 @@ public: { } + void ClearUA(GPUTexture* texture, const Vector4& value) override + { + } + void ResetRenderTarget() override { } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index f4c4f9330..f392a256a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -874,6 +874,23 @@ void GPUContextVulkan::ClearUA(GPUTexture* texture, const uint32 value[4]) } } +void GPUContextVulkan::ClearUA(GPUTexture* texture, const Vector4& value) +{ + const auto texVulkan = static_cast(texture); + if (texVulkan) + { + auto rtVulkan = ((GPUTextureViewVulkan*)(texVulkan->IsVolume() ? texVulkan->ViewVolume() : texVulkan->View(0))); + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); + if (cmdBuffer->IsInsideRenderPass()) + EndRenderPass(); + + AddImageBarrier(rtVulkan, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + FlushBarriers(); + + vkCmdClearColorImage(cmdBuffer->GetHandle(), rtVulkan->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (const VkClearColorValue*)value.Raw, 1, &rtVulkan->Info.subresourceRange); + } +} + void GPUContextVulkan::ResetRenderTarget() { if (_rtDepth || _rtCount != 0) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index 5665ef29b..a559d0e87 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -176,6 +176,7 @@ public: void ClearUA(GPUBuffer* buf, const Vector4& value) override; void ClearUA(GPUBuffer* buf, const uint32 value[4]) override; void ClearUA(GPUTexture* texture, const uint32 value[4]) override; + void ClearUA(GPUTexture* texture, const Vector4& value) override; void ResetRenderTarget() override; void SetRenderTarget(GPUTextureView* rt) override; void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override; From 88879e441e1d65d4c86d7f6985e800d52f3a9965 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 8 Feb 2022 18:06:56 +0100 Subject: [PATCH 005/144] Add some missing changes --- Source/Engine/Graphics/GPUBuffer.cpp | 1 + Source/Engine/Graphics/GPUContext.h | 7 +++++++ Source/Engine/Graphics/Models/MeshBase.h | 1 - Source/Engine/Graphics/RenderBuffers.cpp | 8 +++++--- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 72a1c07bc..bf52943db 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -244,6 +244,7 @@ bool GPUBuffer::DownloadData(BytesContainer& result) // Ensure not running on main thread if (IsInMainThread()) { + // TODO: support mesh data download from GPU on a main thread during rendering LOG(Warning, "Cannot download GPU buffer data on a main thread. Use staging readback buffer or invoke this function from another thread."); return true; } diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 941dac961..59699417e 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -218,6 +218,13 @@ public: /// The clear value. virtual void ClearUA(GPUTexture* texture, const uint32 value[4]) = 0; + /// + /// Clears an unordered access texture with a float value. + /// + /// The texture to clear. + /// The clear value. + virtual void ClearUA(GPUTexture* texture, const Vector4& value) = 0; + public: /// diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 4bb000917..5a76e4f46 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -95,7 +95,6 @@ public: /// /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit. /// - /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer. API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const { return _use16BitIndexBuffer; diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 681910307..df2962e52 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -7,6 +7,9 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Engine/Engine.h" +// How many frames keep cached buffers for temporal or optional effects? +#define LAZY_FRAMES_COUNT 4 + RenderBuffers::RenderBuffers(const SpawnParams& params) : ScriptingObject(params) { @@ -35,8 +38,7 @@ void RenderBuffers::Prepare() if (VolumetricFog) { ASSERT(VolumetricFogHistory); - - if (frameIndex - LastFrameVolumetricFog >= 4) + if (frameIndex - LastFrameVolumetricFog >= LAZY_FRAMES_COUNT) { RenderTargetPool::Release(VolumetricFog); VolumetricFog = nullptr; @@ -48,7 +50,7 @@ void RenderBuffers::Prepare() } } #define UPDATE_LAZY_KEEP_RT(name) \ - if (name && frameIndex - LastFrame##name >= 4) \ + if (name && frameIndex - LastFrame##name >= LAZY_FRAMES_COUNT) \ { \ RenderTargetPool::Release(name); \ name = nullptr; \ From 9a4aa20bbb452ef26f892c6370580283be60271c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 9 Feb 2022 09:23:44 +0100 Subject: [PATCH 006/144] Add Windows 11 detection --- Source/Engine/Platform/Windows/WindowsPlatform.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 075bf7f90..8ef586722 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -177,8 +177,12 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM VersionMajor = 6; VersionMinor = 2; } - - if (VersionMajor == 0 && VersionMinor == 0) + else if (VersionMajor >= 10 && VersionBuild >= 22000) + { + // Windows 11 + windowsName.Replace(TEXT("10"), TEXT("11")); + } + else if (VersionMajor == 0 && VersionMinor == 0) { String windowsVersion; GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); From 116817601616ebb3e04a8666145b3c46bb157405 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 9 Feb 2022 10:01:21 +0100 Subject: [PATCH 007/144] Optimize texture data async upload during rendering --- .../Engine/Graphics/Textures/GPUTexture.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index f9cf13cab..6a8da9b68 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -607,7 +607,25 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); - // TODO: support texture data upload to the GPU on a main thread during rendering without this async task (faster direct upload) + + // Optimize texture upload invoked during rendering + if (IsInMainThread() && GPUDevice::Instance->IsRendering()) + { + // Update all array slices + const byte* dataSource = data.Get(); + for (int32 arrayIndex = 0; arrayIndex < _desc.ArraySize; arrayIndex++) + { + GPUDevice::Instance->GetMainContext()->UpdateTexture(this, arrayIndex, mipIndex, dataSource, rowPitch, slicePitch); + dataSource += slicePitch; + } + if (mipIndex == HighestResidentMipIndex() - 1) + { + // Mark as mip loaded + SetResidentMipLevels(ResidentMipLevels() + 1); + } + return nullptr; + } + auto task = ::New(this, mipIndex, data, rowPitch, slicePitch, copyData); ASSERT_LOW_LAYER(task && task->HasReference(this)); return task; From 79cacbf36e585496873ae43705a911280e3ff42a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 10:15:05 +0100 Subject: [PATCH 008/144] Add GetNormal to Triangle --- Source/Engine/Core/Math/Triangle.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Core/Math/Triangle.h b/Source/Engine/Core/Math/Triangle.h index 72aee5409..a830f7e3e 100644 --- a/Source/Engine/Core/Math/Triangle.h +++ b/Source/Engine/Core/Math/Triangle.h @@ -49,6 +49,13 @@ public: { } +public: + + Vector3 GetNormal() const + { + return Vector3::Normalize((V1 - V0) ^ (V2 - V0)); + } + public: // Determines if there is an intersection between the current object and a Ray From 788b1a75319aea3f2c724c8b2e443b75a2bf34be Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 10:15:38 +0100 Subject: [PATCH 009/144] Add Distance to box/point for Bounding Box --- Source/Engine/Core/Math/BoundingBox.cpp | 20 +++++++++++++ Source/Engine/Core/Math/BoundingBox.h | 40 ++++++++++++++----------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index df6150cf5..04b404622 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -13,6 +13,26 @@ String BoundingBox::ToString() const return String::Format(TEXT("{}"), *this); } +void BoundingBox::GetCorners(Vector3 corners[8]) const +{ + corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z); + corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z); + corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z); + corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z); + corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z); + corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z); + corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z); + corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z); +} + +BoundingBox BoundingBox::MakeOffsetted(const Vector3& offset) const +{ + BoundingBox result; + result.Minimum = Minimum + offset; + result.Maximum = Maximum + offset; + return result; +} + void BoundingBox::FromPoints(const Vector3* points, int32 pointsCount, BoundingBox& result) { ASSERT(points && pointsCount > 0); diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h index 4652bb3f6..575f484fa 100644 --- a/Source/Engine/Core/Math/BoundingBox.h +++ b/Source/Engine/Core/Math/BoundingBox.h @@ -76,17 +76,7 @@ public: /// Gets the eight corners of the bounding box. /// /// An array of points representing the eight corners of the bounding box. - void GetCorners(Vector3 corners[8]) const - { - corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z); - corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z); - corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z); - corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z); - corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z); - corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z); - corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z); - corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z); - } + void GetCorners(Vector3 corners[8]) const; /// /// Calculates volume of the box. @@ -200,13 +190,7 @@ public: /// /// The offset. /// The result. - BoundingBox MakeOffsetted(const Vector3& offset) const - { - BoundingBox result; - result.Minimum = Minimum + offset; - result.Maximum = Maximum + offset; - return result; - } + BoundingBox MakeOffsetted(const Vector3& offset) const; public: @@ -421,6 +405,26 @@ public: { return CollisionsHelper::BoxContainsSphere(*this, sphere); } + + /// + /// Determines the distance between a Bounding Box and a point. + /// + /// The point to test. + /// The distance between bounding box and a point. + FORCE_INLINE float Distance(const Vector3& point) const + { + return CollisionsHelper::DistanceBoxPoint(*this, point); + } + + /// + /// Determines the distance between two Bounding Boxed. + /// + /// The bounding box to test. + /// The distance between bounding boxes. + FORCE_INLINE float Distance(const BoundingBox& box) const + { + return CollisionsHelper::DistanceBoxBox(*this, box); + } }; template<> From 9dc49f7165b1139c08b1ad4f65e34b8c757ab8bc Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 11:46:48 +0100 Subject: [PATCH 010/144] Optimize Vector3 method to be inlined more often --- Source/Engine/Core/Math/Vector3.cpp | 43 ------------------------- Source/Engine/Core/Math/Vector3.h | 50 +++++++++++++++++++---------- 2 files changed, 33 insertions(+), 60 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 79ef60eaa..72fb2d080 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -165,49 +165,6 @@ void Vector3::Clamp(const Vector3& value, const Vector3& min, const Vector3& max result = Vector3(x, y, z); } -float Vector3::Distance(const Vector3& value1, const Vector3& value2) -{ - const float x = value1.X - value2.X; - const float y = value1.Y - value2.Y; - const float z = value1.Z - value2.Z; - return Math::Sqrt(x * x + y * y + z * z); -} - -float Vector3::DistanceSquared(const Vector3& value1, const Vector3& value2) -{ - const float x = value1.X - value2.X; - const float y = value1.Y - value2.Y; - const float z = value1.Z - value2.Z; - return x * x + y * y + z * z; -} - -Vector3 Vector3::Normalize(const Vector3& input) -{ - Vector3 output = input; - const float length = input.Length(); - if (!Math::IsZero(length)) - { - const float inv = 1.0f / length; - output.X *= inv; - output.Y *= inv; - output.Z *= inv; - } - return output; -} - -void Vector3::Normalize(const Vector3& input, Vector3& result) -{ - result = input; - const float length = input.Length(); - if (!Math::IsZero(length)) - { - const float inv = 1.0f / length; - result.X *= inv; - result.Y *= inv; - result.Z *= inv; - } -} - void Vector3::Hermite(const Vector3& value1, const Vector3& tangent1, const Vector3& value2, const Vector3& tangent2, float amount, Vector3& result) { const float squared = amount * amount; diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index aedbc91bb..c47e71cf2 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -586,11 +586,6 @@ public: static Vector3 Floor(const Vector3& v); static Vector3 Frac(const Vector3& v); - static float ScalarProduct(const Vector3& a, const Vector3& b) - { - return a.X * b.X + a.Y * b.Y + a.Z * b.Z; - } - public: // Restricts a value to be within a specified range @@ -611,18 +606,42 @@ public: // @param value1 The first vector // @param value2 The second vector // @returns The distance between the two vectors - static float Distance(const Vector3& value1, const Vector3& value2); + static float Distance(const Vector3& value1, const Vector3& value2) + { + const float x = value1.X - value2.X; + const float y = value1.Y - value2.Y; + const float z = value1.Z - value2.Z; + return Math::Sqrt(x * x + y * y + z * z); + } // Calculates the squared distance between two vectors // @param value1 The first vector // @param value2 The second vector // @returns The squared distance between the two vectors - static float DistanceSquared(const Vector3& value1, const Vector3& value2); + static float DistanceSquared(const Vector3& value1, const Vector3& value2) + { + const float x = value1.X - value2.X; + const float y = value1.Y - value2.Y; + const float z = value1.Z - value2.Z; + return x * x + y * y + z * z; + } // Performs vector normalization (scales vector up to unit length) // @param inout Input vector to normalize // @returns Output vector that is normalized (has unit length) - static Vector3 Normalize(const Vector3& input); + static Vector3 Normalize(const Vector3& input) + { + Vector3 output = input; + const float length = input.Length(); + if (!Math::IsZero(length)) + { + const float inv = 1.0f / length; + output.X *= inv; + output.Y *= inv; + output.Z *= inv; + } + return output; + } // Performs vector normalization (scales vector up to unit length). This is a faster version that does not performs check for length equal 0 (it assumes that input vector is not empty). // @param inout Input vector to normalize (cannot be zero). @@ -636,7 +655,10 @@ public: // Performs vector normalization (scales vector up to unit length) // @param inout Input vector to normalize // @param output Output vector that is normalized (has unit length) - static void Normalize(const Vector3& input, Vector3& result); + static FORCE_INLINE void Normalize(const Vector3& input, Vector3& result) + { + result = Normalize(input); + } // dot product with another vector static float Dot(const Vector3& a, const Vector3& b) @@ -650,10 +672,7 @@ public: // @param result When the method completes, contains the cross product of the two vectors static void Cross(const Vector3& a, const Vector3& b, Vector3& result) { - result = Vector3( - a.Y * b.Z - a.Z * b.Y, - a.Z * b.X - a.X * b.Z, - a.X * b.Y - a.Y * b.X); + result = Vector3(a.Y * b.Z - a.Z * b.Y,a.Z * b.X - a.X * b.Z,a.X * b.Y - a.Y * b.X); } // Calculates the cross product of two vectors @@ -662,10 +681,7 @@ public: // @returns Cross product of the two vectors static Vector3 Cross(const Vector3& a, const Vector3& b) { - return Vector3( - a.Y * b.Z - a.Z * b.Y, - a.Z * b.X - a.X * b.Z, - a.X * b.Y - a.Y * b.X); + return Vector3(a.Y * b.Z - a.Z * b.Y,a.Z * b.X - a.X * b.Z,a.X * b.Y - a.Y * b.X); } // Performs a linear interpolation between two vectors From 7979831f5ca575e80141d7843aced667aea92d45 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 14:34:43 +0100 Subject: [PATCH 011/144] Add `JobSystem.Execute` utility for quick jobs running --- Source/Engine/Threading/JobSystem.cpp | 17 +++++++++++++++++ Source/Engine/Threading/JobSystem.h | 9 ++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index de3d1c301..c16a1596d 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -212,6 +212,23 @@ int32 JobSystemThread::Run() #endif +void JobSystem::Execute(const Function& job, int32 jobCount) +{ + // TODO: disable async if called on job thread? or maybe Wait should handle waiting in job thread to do the processing? + if (jobCount > 1) + { + // Async + const int64 jobWaitHandle = Dispatch(job, jobCount); + Wait(jobWaitHandle); + } + else if (jobCount > 0) + { + // Sync + for (int32 i = 0; i < jobCount; i++) + job(i); + } +} + int64 JobSystem::Dispatch(const Function& job, int32 jobCount) { PROFILE_CPU(); diff --git a/Source/Engine/Threading/JobSystem.h b/Source/Engine/Threading/JobSystem.h index 2b6545156..e009e2f29 100644 --- a/Source/Engine/Threading/JobSystem.h +++ b/Source/Engine/Threading/JobSystem.h @@ -9,7 +9,14 @@ /// API_CLASS(Static) class FLAXENGINE_API JobSystem { -DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem); + DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem); + + /// + /// Executes the job (utility to call dispatch and wait for the end). + /// + /// The job. Argument is an index of the job execution. + /// The job executions count. + API_FUNCTION() static void Execute(const Function& job, int32 jobCount = 1); /// /// Dispatches the job for the execution. From 311b95b9fc83eebfb8652652aaaff7fea34bb618 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 14:37:04 +0100 Subject: [PATCH 012/144] Add support for using mipmaps with 3D textures --- Source/Engine/Graphics/Textures/GPUTexture.cpp | 5 ----- Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp | 4 +++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 6a8da9b68..a49b97070 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -431,11 +431,6 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) LOG(Warning, "Cannot create texture. Only 2D Texture can be used as a Depth Stencil. Description: {0}", desc.ToString()); return true; } - if (desc.MipLevels != 1) - { - LOG(Warning, "Cannot create texture. Volume texture cannot have more than 1 mip level. Description: {0}", desc.ToString()); - return true; - } if (desc.ArraySize != 1) { LOG(Warning, "Cannot create texture. Volume texture cannot create array of volume textures. Description: {0}", desc.ToString()); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index f558152f1..37d4a663f 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -732,7 +732,9 @@ void GPUContextDX11::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32 auto textureDX11 = static_cast(texture); const int32 subresourceIndex = RenderToolsDX::CalcSubresourceIndex(mipIndex, arrayIndex, texture->MipLevels()); - const uint32 depthPitch = texture->IsVolume() ? slicePitch / texture->Depth() : slicePitch; + uint32 depthPitch = slicePitch; + if (texture->IsVolume()) + depthPitch /= Math::Max(1, texture->Depth() >> mipIndex); _context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, (UINT)rowPitch, (UINT)depthPitch); //D3D11_MAPPED_SUBRESOURCE mapped; From 6b3e911b4015b5175e187fc513fd060d52b42314 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 14 Feb 2022 14:37:22 +0100 Subject: [PATCH 013/144] Remove old log for drag&drop on Windows --- .../Platform/Windows/WindowsWindow.DragDrop.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp index d5f63f5cf..0e6489fc4 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp @@ -6,7 +6,6 @@ #if USE_EDITOR -#include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Engine.h" #include "Engine/Platform/IGuiData.h" @@ -575,27 +574,15 @@ public: int64 ExitFlag = 0; -public: - // [ThreadPoolTask] bool Run() override { - const uint64 beginFrame = Engine::FrameCount; - Scripting::GetScriptsDomain()->Dispatch(); - while (Platform::AtomicRead(&ExitFlag) == 0) { - // Render single frame Engine::OnDraw(); - - // Wait for a while Platform::Sleep(20); } - - const uint64 endFrame = Engine::FrameCount; - LOG(Info, "Rendered {0} frames during DoDragDrop stall.", endFrame - beginFrame); - return false; } }; From a857e6daa04fe2c7fb89b766cfc3f60123aef995 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 15 Feb 2022 12:14:02 +0100 Subject: [PATCH 014/144] Fix memory leak in RenderTargetPool on texture init fail --- Source/Engine/Graphics/RenderTargetPool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index 102d22bc3..26fa3f6ac 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -73,6 +73,7 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) auto newRenderTarget = GPUDevice::Instance->CreateTexture(name); if (newRenderTarget->Init(desc)) { + Delete(newRenderTarget); LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString()); return nullptr; } From 5d4c168e1e4f192a7ddf5c00f8d5ca91d8b10a58 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 15 Feb 2022 12:14:31 +0100 Subject: [PATCH 015/144] Add Vector3::Clamp for easier inline in optimized builds --- Source/Engine/Core/Math/Vector3.cpp | 17 ----------------- Source/Engine/Core/Math/Vector3.h | 5 ++++- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 72fb2d080..17d45a0a2 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -148,23 +148,6 @@ Vector3 Vector3::Clamp(const Vector3& value, const Vector3& min, const Vector3& return Vector3(x, y, z); } -void Vector3::Clamp(const Vector3& value, const Vector3& min, const Vector3& max, Vector3& result) -{ - float x = value.X; - x = x > max.X ? max.X : x; - x = x < min.X ? min.X : x; - - float y = value.Y; - y = y > max.Y ? max.Y : y; - y = y < min.Y ? min.Y : y; - - float z = value.Z; - z = z > max.Z ? max.Z : z; - z = z < min.Z ? min.Z : z; - - result = Vector3(x, y, z); -} - void Vector3::Hermite(const Vector3& value1, const Vector3& tangent1, const Vector3& value2, const Vector3& tangent2, float amount, Vector3& result) { const float squared = amount * amount; diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index c47e71cf2..bc994cb18 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -600,7 +600,10 @@ public: // @param min The minimum value, // @param max The maximum value // @param result When the method completes, contains the clamped value - static void Clamp(const Vector3& value, const Vector3& min, const Vector3& max, Vector3& result); + static void Clamp(const Vector3& value, const Vector3& min, const Vector3& max, Vector3& result) + { + result = Vector3(Math::Clamp(value.X, min.X, max.X), Math::Clamp(value.Y, min.Y, max.Y), Math::Clamp(value.Z, min.Z, max.Z)); + } // Calculates the distance between two vectors // @param value1 The first vector From 8082f5f909f3c0b434daa5b455b6d92d7b15b09e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 21 Feb 2022 20:14:46 +0100 Subject: [PATCH 016/144] Minor fixes and changes --- Source/Engine/Core/Collections/Dictionary.h | 5 +++-- Source/Engine/Core/Math/CollisionsHelper.cpp | 1 - Source/Engine/Core/Math/Vector3.cpp | 17 ----------------- Source/Engine/Core/Math/Vector3.h | 5 ++++- Source/Engine/Level/Actor.h | 2 +- Source/Engine/Level/Actors/SplineModel.cpp | 2 +- 6 files changed, 9 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 941ef88d5..6e2d0e85d 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -544,14 +544,15 @@ public: /// Ensures that collection has given capacity. /// /// The minimum required capacity. - void EnsureCapacity(int32 minCapacity) + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, bool preserveContents = true) { if (_size >= minCapacity) return; if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) minCapacity = DICTIONARY_DEFAULT_CAPACITY; const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); - SetCapacity(capacity); + SetCapacity(capacity, preserveContents); } /// diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index b966523ec..f778fa4ec 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -1124,7 +1124,6 @@ bool CollisionsHelper::BoxIntersectsSphere(const BoundingBox& box, const Boundin Vector3 vector; Vector3::Clamp(sphere.Center, box.Minimum, box.Maximum, vector); const float distance = Vector3::DistanceSquared(sphere.Center, vector); - return distance <= sphere.Radius * sphere.Radius; } diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 17d45a0a2..83faa28a3 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -131,23 +131,6 @@ Vector3 Vector3::Frac(const Vector3& v) ); } -Vector3 Vector3::Clamp(const Vector3& value, const Vector3& min, const Vector3& max) -{ - float x = value.X; - x = x > max.X ? max.X : x; - x = x < min.X ? min.X : x; - - float y = value.Y; - y = y > max.Y ? max.Y : y; - y = y < min.Y ? min.Y : y; - - float z = value.Z; - z = z > max.Z ? max.Z : z; - z = z < min.Z ? min.Z : z; - - return Vector3(x, y, z); -} - void Vector3::Hermite(const Vector3& value1, const Vector3& tangent1, const Vector3& value2, const Vector3& tangent2, float amount, Vector3& result) { const float squared = amount * amount; diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index bc994cb18..99ca7702d 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -593,7 +593,10 @@ public: // @param min The minimum value, // @param max The maximum value // @returns Clamped value - static Vector3 Clamp(const Vector3& value, const Vector3& min, const Vector3& max); + static Vector3 Clamp(const Vector3& value, const Vector3& min, const Vector3& max) + { + return Vector3(Math::Clamp(value.X, min.X, max.X), Math::Clamp(value.Y, min.Y, max.Y), Math::Clamp(value.Z, min.Z, max.Z)); + } // Restricts a value to be within a specified range // @param value The value to clamp diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index b31654c72..474694e99 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -640,7 +640,7 @@ public: public: /// - /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. models are rendered during all passed but other actors are invoked only during GBufferFill pass). + /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. geometry is rendered during all pass types but other actors are drawn only during GBufferFill pass). /// /// The rendering context. virtual void Draw(RenderContext& renderContext); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 6794ccbb2..5160c993c 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -258,7 +258,7 @@ void SplineModel::UpdateDeformationBuffer() AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) { - const float alpha = (chunk == chunksPerSegment - 1)? 1.0f : ((float)chunk * chunksPerSegmentInv); + const float alpha = (chunk == chunksPerSegment - 1) ? 1.0f : ((float)chunk * chunksPerSegmentInv); // Evaluate transformation at the curve AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); From 8c075c78cb0b618796fd83c1c8cdc42954ea7763 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 21 Feb 2022 20:15:07 +0100 Subject: [PATCH 017/144] Add `LineHitBox` to `Collisions.hlsl` --- Source/Shaders/Collisions.hlsl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/Shaders/Collisions.hlsl b/Source/Shaders/Collisions.hlsl index e68871e27..4ac81b4aa 100644 --- a/Source/Shaders/Collisions.hlsl +++ b/Source/Shaders/Collisions.hlsl @@ -18,4 +18,21 @@ bool RayHitRect(float3 r, float3 rectCenter, float3 rectX, float3 rectY, float3 return inExtentX && inExtentY; } +// Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd). +// Returns the intersections on the line (x - closest, y - furthest). +// Line hits the box if: intersections.x < intersections.y. +// Hit point is: hitPoint = lineStart + (lineEnd - lineStart) * intersections.x/y. +float2 LineHitBox(float3 lineStart, float3 lineEnd, float3 boxMin, float3 boxMax) +{ + float3 invDirection = 1.0f / (lineEnd - lineStart); + float3 enterIntersection = (boxMin - lineStart) * invDirection; + float3 exitIntersection = (boxMax - lineStart) * invDirection; + float3 minIntersections = min(enterIntersection, exitIntersection); + float3 maxIntersections = max(enterIntersection, exitIntersection); + float2 intersections; + intersections.x = max(minIntersections.x, max(minIntersections.y, minIntersections.z)); + intersections.y = min(maxIntersections.x, min(maxIntersections.y, maxIntersections.z)); + return saturate(intersections); +} + #endif From 3fe1e2c7631667bf5fb3b773916b81d3e4a1f6a4 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Feb 2022 17:06:19 +0100 Subject: [PATCH 018/144] Refactor SceneRendering to simplify actors impl of drawing flow at high level --- Source/Engine/Foliage/Foliage.cpp | 17 +- Source/Engine/Foliage/Foliage.h | 1 - Source/Engine/Graphics/RenderTask.cpp | 25 +- Source/Engine/Graphics/RenderTask.h | 4 + Source/Engine/Level/Actor.cpp | 40 +- Source/Engine/Level/Actor.h | 14 +- Source/Engine/Level/Actors/AnimatedModel.cpp | 12 +- Source/Engine/Level/Actors/AnimatedModel.h | 1 - Source/Engine/Level/Actors/Camera.cpp | 4 +- Source/Engine/Level/Actors/Camera.h | 1 + Source/Engine/Level/Actors/Decal.cpp | 9 +- .../Engine/Level/Actors/DirectionalLight.cpp | 5 +- Source/Engine/Level/Actors/DirectionalLight.h | 8 +- .../Engine/Level/Actors/EnvironmentProbe.cpp | 13 +- .../Level/Actors/ExponentialHeightFog.cpp | 6 +- .../Level/Actors/ExponentialHeightFog.h | 10 +- .../Level/Actors/ModelInstanceActor.cpp | 6 +- Source/Engine/Level/Actors/PointLight.cpp | 9 +- Source/Engine/Level/Actors/Sky.cpp | 6 +- Source/Engine/Level/Actors/Sky.h | 9 +- Source/Engine/Level/Actors/SkyLight.cpp | 5 +- Source/Engine/Level/Actors/SkyLight.h | 11 +- Source/Engine/Level/Actors/Skybox.cpp | 5 +- Source/Engine/Level/Actors/Skybox.h | 8 +- Source/Engine/Level/Actors/SplineModel.cpp | 7 +- Source/Engine/Level/Actors/SplineModel.h | 1 - Source/Engine/Level/Actors/SpotLight.cpp | 7 +- Source/Engine/Level/Actors/StaticModel.cpp | 10 +- Source/Engine/Level/Actors/StaticModel.h | 1 - Source/Engine/Level/Level.cpp | 10 +- Source/Engine/Level/Scene/Scene.cpp | 1 - Source/Engine/Level/Scene/SceneRendering.cpp | 401 +++--------------- Source/Engine/Level/Scene/SceneRendering.h | 76 +--- Source/Engine/Particles/ParticleEffect.cpp | 13 +- Source/Engine/Particles/ParticleEffect.h | 1 - Source/Engine/Renderer/RenderList.cpp | 1 + Source/Engine/Renderer/RenderList.h | 6 + Source/Engine/Terrain/Terrain.cpp | 13 +- Source/Engine/Terrain/Terrain.h | 1 - Source/Engine/UI/SpriteRender.cpp | 13 +- Source/Engine/UI/SpriteRender.h | 1 - Source/Engine/UI/TextRender.cpp | 15 +- Source/Engine/UI/TextRender.h | 1 - 43 files changed, 191 insertions(+), 617 deletions(-) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 5d86ba4d3..1d367c5ef 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -559,7 +559,7 @@ void Foliage::OnFoliageTypeModelLoaded(int32 index) } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } { PROFILE_CPU_NAMED("Create Clusters"); @@ -606,7 +606,7 @@ void Foliage::RebuildClusters() _box = BoundingBox(_transform.Translation, _transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); return; } @@ -696,7 +696,7 @@ void Foliage::RebuildClusters() _box = totalBounds; BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } // Insert all instances to the clusters @@ -1016,11 +1016,6 @@ void Foliage::Draw(RenderContext& renderContext) #endif } -void Foliage::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - bool Foliage::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { int32 instanceIndex; @@ -1227,12 +1222,12 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie void Foliage::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Foliage::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); // Base Actor::OnEnable(); @@ -1240,7 +1235,7 @@ void Foliage::OnEnable() void Foliage::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Foliage/Foliage.h b/Source/Engine/Foliage/Foliage.h index e4f32529f..19e45fbeb 100644 --- a/Source/Engine/Foliage/Foliage.h +++ b/Source/Engine/Foliage/Foliage.h @@ -197,7 +197,6 @@ public: // [Actor] void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 42e7f6805..23ac08591 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -229,6 +229,8 @@ SceneRenderTask::~SceneRenderTask() { if (Buffers) Buffers->DeleteObjectNow(); + if (_customActorsScene) + Delete(_customActorsScene); } void SceneRenderTask::CameraCut() @@ -270,18 +272,27 @@ void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext) } } +void AddActorToSceneRendering(SceneRendering* s, Actor* a) +{ + if (a && a->IsActiveInHierarchy()) + { + s->AddActor(a); + for (Actor* child : a->Children) + AddActorToSceneRendering(s, child); + } +} + void SceneRenderTask::OnCollectDrawCalls(RenderContext& renderContext) { // Draw actors (collect draw calls) if ((ActorsSource & ActorsSources::CustomActors) != 0) { - for (auto a : CustomActors) - { - if (a && a->GetIsActive()) - { - a->DrawHierarchy(renderContext); - } - } + if (_customActorsScene == nullptr) + _customActorsScene = New(); + _customActorsScene->Clear(); + for (Actor* a : CustomActors) + AddActorToSceneRendering(_customActorsScene, a); + _customActorsScene->Draw(renderContext); } if ((ActorsSource & ActorsSources::Scenes) != 0) { diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 741e9c7ed..0604ccdfc 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -220,6 +220,10 @@ public: API_CLASS() class FLAXENGINE_API SceneRenderTask : public RenderTask { DECLARE_SCRIPTING_TYPE(SceneRenderTask); +protected: + class SceneRendering* _customActorsScene = nullptr; + +public: /// /// Finalizes an instance of the class. diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 4eff57a54..72c35da56 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -81,6 +81,7 @@ Actor::Actor(const SpawnParams& params) , _physicsScene(nullptr) , HideFlags(HideFlags::None) { + _drawNoCulling = 0; } SceneRendering* Actor::GetSceneRendering() const @@ -1221,45 +1222,6 @@ void Actor::Draw(RenderContext& renderContext) { } -void Actor::DrawGeneric(RenderContext& renderContext) -{ - // Generic drawing uses only GBuffer Fill Pass and simple frustum culling (see SceneRendering for more optimized drawing) - if (renderContext.View.Pass & DrawPass::GBuffer) - { - Draw(renderContext); - } -} - -void Actor::DrawHierarchy(RenderContext& renderContext) -{ - // Draw actor itself - DrawGeneric(renderContext); - - // Draw children - if (renderContext.View.IsOfflinePass) - { - for (int32 i = 0; i < Children.Count(); i++) - { - auto child = Children[i]; - if (child->GetIsActive() && child->GetStaticFlags() & renderContext.View.StaticFlagsMask) - { - child->DrawHierarchy(renderContext); - } - } - } - else - { - for (int32 i = 0; i < Children.Count(); i++) - { - auto child = Children[i]; - if (child->GetIsActive()) - { - child->DrawHierarchy(renderContext); - } - } - } -} - #if USE_EDITOR void Actor::OnDebugDraw() diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 474694e99..8d580fdde 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -30,6 +30,7 @@ DECLARE_SCENE_OBJECT(Actor); friend Level; friend PrefabManager; friend Scene; + friend SceneRendering; friend Prefab; friend PrefabInstanceData; @@ -39,6 +40,7 @@ protected: int8 _isActiveInHierarchy : 1; int8 _isPrefabRoot : 1; int8 _isEnabled : 1; + int8 _drawNoCulling : 1; byte _layer; byte _tag; Scene* _scene; @@ -645,18 +647,6 @@ public: /// The rendering context. virtual void Draw(RenderContext& renderContext); - /// - /// Draws this actor. Called during custom actor rendering or any other generic rendering from code. - /// - /// The rendering context. - virtual void DrawGeneric(RenderContext& renderContext); - - /// - /// Draws this actor and all its children (full scene hierarchy part). - /// - /// The rendering context. - void DrawHierarchy(RenderContext& renderContext); - #if USE_EDITOR /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 0e59f36c0..c6a770de7 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -545,7 +545,7 @@ void AnimatedModel::UpdateBounds() BoundingBox::Transform(_boxLocal, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void AnimatedModel::UpdateSockets() @@ -725,14 +725,6 @@ void AnimatedModel::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void AnimatedModel::DrawGeneric(RenderContext& renderContext) -{ - if (renderContext.View.RenderLayersMask.Mask & GetLayerMask() && renderContext.View.CullingFrustum.Intersects(_box)) - { - Draw(renderContext); - } -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -878,5 +870,5 @@ void AnimatedModel::OnTransformChanged() BoundingBox::Transform(_boxLocal, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 556a1d1cf..087099a84 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -375,7 +375,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; BoundingBox GetEditorBox() const override; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index ab736c673..76cb7d667 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -354,7 +354,7 @@ void Camera::OnEnable() { Cameras.Add(this); #if USE_EDITOR - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #endif // Base @@ -364,7 +364,7 @@ void Camera::OnEnable() void Camera::OnDisable() { #if USE_EDITOR - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #endif Cameras.Remove(this); if (CutSceneCamera == this) diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 938fe1bdc..35d2df94d 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -52,6 +52,7 @@ private: ModelInstanceEntries _previewModelBuffer; BoundingBox _previewModelBox; Matrix _previewModelWorld; + int32 _sceneRenderingKey = -1; #endif public: diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index a55f51c85..ebf9f7361 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -69,12 +69,13 @@ BoundingBox Decal::GetEditorBox() const void Decal::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Decal::Draw(RenderContext& renderContext) { if ((renderContext.View.Flags & ViewFlags::Decals) != 0 && + renderContext.View.Pass & DrawPass::GBuffer && Material && Material->IsLoaded() && Material->IsDecal()) @@ -121,7 +122,7 @@ bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) void Decal::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -135,7 +136,7 @@ void Decal::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -156,5 +157,5 @@ void Decal::OnTransformChanged() BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 854b36aac..e8bcfbba4 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -10,6 +10,7 @@ DirectionalLight::DirectionalLight(const SpawnParams& params) : LightWithShadow(params) { + _drawNoCulling = 1; Brightness = 8.0f; } @@ -67,7 +68,7 @@ bool DirectionalLight::IntersectsItself(const Ray& ray, float& distance, Vector3 void DirectionalLight::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -81,7 +82,7 @@ void DirectionalLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h index ab6330953..ae966559e 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.h +++ b/Source/Engine/Level/Actors/DirectionalLight.h @@ -9,9 +9,11 @@ /// API_CLASS() class FLAXENGINE_API DirectionalLight : public LightWithShadow { -DECLARE_SCENE_OBJECT(DirectionalLight); -public: + DECLARE_SCENE_OBJECT(DirectionalLight); +private: + int32 _sceneRenderingKey = -1; +public: /// /// The number of cascades used for slicing the range of depth covered by the light. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades. /// @@ -19,7 +21,6 @@ public: int32 CascadeCount = 4; public: - // [LightWithShadow] void Draw(RenderContext& renderContext) override; void Serialize(SerializeStream& stream, const void* otherObj) override; @@ -27,7 +28,6 @@ public: bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; protected: - // [LightWithShadow] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 9778836eb..6ad0c6091 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -127,12 +127,15 @@ void EnvironmentProbe::UpdateBounds() _sphere = BoundingSphere(GetPosition(), GetScaledRadius()); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void EnvironmentProbe::Draw(RenderContext& renderContext) { - if (Brightness > ZeroTolerance && (renderContext.View.Flags & ViewFlags::Reflections) != 0 && HasProbeLoaded()) + if (Brightness > ZeroTolerance && + (renderContext.View.Flags & ViewFlags::Reflections) != 0 && + renderContext.View.Pass & DrawPass::GBuffer && + HasProbeLoaded()) { renderContext.List->EnvironmentProbes.Add(this); } @@ -156,7 +159,7 @@ void EnvironmentProbe::OnDebugDrawSelected() void EnvironmentProbe::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj) @@ -199,7 +202,7 @@ bool EnvironmentProbe::IntersectsItself(const Ray& ray, float& distance, Vector3 void EnvironmentProbe::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -213,7 +216,7 @@ void EnvironmentProbe::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 34d1dd255..5521b888d 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -16,6 +16,8 @@ ExponentialHeightFog::ExponentialHeightFog(const SpawnParams& params) : Actor(params) { + _drawNoCulling = 1; + // Load shader _shader = Content::LoadAsyncInternal(TEXT("Shaders/Fog")); if (_shader == nullptr) @@ -203,7 +205,7 @@ void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderCon void ExponentialHeightFog::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -217,7 +219,7 @@ void ExponentialHeightFog::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index dc205ce62..302fd07b3 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -14,14 +14,13 @@ /// API_CLASS() class FLAXENGINE_API ExponentialHeightFog : public Actor, public IFogRenderer { -DECLARE_SCENE_OBJECT(ExponentialHeightFog); + DECLARE_SCENE_OBJECT(ExponentialHeightFog); private: - AssetReference _shader; GPUPipelineStatePermutationsPs<2> _psFog; + int32 _sceneRenderingKey = -1; public: - /// /// The fog density factor. /// @@ -61,7 +60,6 @@ public: float FogCutoffDistance = 0.0f; public: - /// /// Directional light used for Directional Inscattering. /// @@ -90,7 +88,6 @@ public: Color DirectionalInscatteringColor = Color(0.25, 0.25f, 0.125f); public: - /// /// Whether to enable Volumetric fog. Graphics quality settings control the resolution of the fog simulation. /// @@ -133,7 +130,6 @@ public: float VolumetricFogDistance = 6000.0f; private: - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -142,7 +138,6 @@ private: #endif public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -163,7 +158,6 @@ public: void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) override; protected: - // [Actor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 69e78fd6a..e71d66f05 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -34,12 +34,12 @@ MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 void ModelInstanceActor::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ModelInstanceActor::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); // Base Actor::OnEnable(); @@ -47,7 +47,7 @@ void ModelInstanceActor::OnEnable() void ModelInstanceActor::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 36aa08805..9c827a877 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -66,12 +66,12 @@ void PointLight::UpdateBounds() BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void PointLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -85,7 +85,7 @@ void PointLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); @@ -106,6 +106,7 @@ void PointLight::Draw(RenderContext& renderContext) const float radius = GetScaledRadius(); if ((renderContext.View.Flags & ViewFlags::PointLights) != 0 && brightness > ZeroTolerance + && renderContext.View.Pass & DrawPass::GBuffer && radius > ZeroTolerance && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { @@ -165,7 +166,7 @@ void PointLight::OnDebugDrawSelected() void PointLight::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void PointLight::Serialize(SerializeStream& stream, const void* otherObj) diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 046602010..c5107ae0c 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -29,6 +29,8 @@ Sky::Sky(const SpawnParams& params) , _psSky(nullptr) , _psFog(nullptr) { + _drawNoCulling = 1; + // Load shader _shader = Content::LoadAsyncInternal(TEXT("Shaders/Sky")); if (_shader == nullptr) @@ -237,7 +239,7 @@ void Sky::EndPlay() void Sky::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -251,7 +253,7 @@ void Sky::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 57c2fd42a..5f2f78a2f 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -16,19 +16,17 @@ class GPUPipelineState; /// API_CLASS() class FLAXENGINE_API Sky : public Actor, public IAtmosphericFogRenderer, public ISkyRenderer { -DECLARE_SCENE_OBJECT(Sky); + DECLARE_SCENE_OBJECT(Sky); private: - AssetReference _shader; GPUPipelineState* _psSky; GPUPipelineState* _psFog; + int32 _sceneRenderingKey = -1; public: - ~Sky(); public: - /// /// Directional light that is used to simulate the sun. /// @@ -48,7 +46,6 @@ public: float SunPower = 8.0f; private: - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -59,7 +56,6 @@ private: void InitConfig(AtmosphericFogData& config) const; public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -81,7 +77,6 @@ public: void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: - // [Actor] void EndPlay() override; void OnEnable() override; diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 2fd4d226b..a5d4fb0a3 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -17,6 +17,7 @@ SkyLight::SkyLight(const SpawnParams& params) : Light(params) , _radius(1000000.0f) { + _drawNoCulling = 1; Brightness = 2.0f; UpdateBounds(); } @@ -170,7 +171,7 @@ bool SkyLight::HasContentLoaded() const void SkyLight::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -184,7 +185,7 @@ void SkyLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Light::OnDisable(); diff --git a/Source/Engine/Level/Actors/SkyLight.h b/Source/Engine/Level/Actors/SkyLight.h index ad5a7f7f4..605be3af2 100644 --- a/Source/Engine/Level/Actors/SkyLight.h +++ b/Source/Engine/Level/Actors/SkyLight.h @@ -11,9 +11,8 @@ /// API_CLASS() class FLAXENGINE_API SkyLight : public Light { -DECLARE_SCENE_OBJECT(SkyLight); + DECLARE_SCENE_OBJECT(SkyLight); public: - /// /// Sky light source mode. /// @@ -31,12 +30,11 @@ public: }; private: - AssetReference _bakedProbe; float _radius; + int32 _sceneRenderingKey = -1; public: - /// /// Additional color to add. Source texture colors are summed with it. Can be used to apply custom ambient color. /// @@ -62,7 +60,6 @@ public: AssetReference CustomTexture; public: - /// /// Gets the radius. /// @@ -98,7 +95,6 @@ public: } public: - /// /// Bakes that probe. /// @@ -112,11 +108,9 @@ public: void SetProbeData(TextureData& data); private: - void UpdateBounds(); public: - // [Light] void Draw(RenderContext& renderContext) override; #if USE_EDITOR @@ -127,7 +121,6 @@ public: bool HasContentLoaded() const override; protected: - // [Light] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 25510a5ff..580145da3 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -14,6 +14,7 @@ Skybox::Skybox(const SpawnParams& params) : Actor(params) { + _drawNoCulling = 1; } void Skybox::setupProxy() @@ -121,7 +122,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M void Skybox::OnEnable() { - GetSceneRendering()->AddCommonNoCulling(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -135,7 +136,7 @@ void Skybox::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommonNoCulling(this); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index ba64be45d..3e44dd015 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -13,13 +13,12 @@ /// API_CLASS() class FLAXENGINE_API Skybox : public Actor, public ISkyRenderer { -DECLARE_SCENE_OBJECT(Skybox); + DECLARE_SCENE_OBJECT(Skybox); private: - AssetReference _proxyMaterial; + int32 _sceneRenderingKey = -1; public: - /// /// The cube texture to draw. /// @@ -51,11 +50,9 @@ public: float Exposure = 0.0f; private: - void setupProxy(); public: - // [Actor] #if USE_EDITOR BoundingBox GetEditorBox() const override @@ -74,7 +71,6 @@ public: void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: - // [Actor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 5160c993c..f432e1da5 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -210,7 +210,7 @@ void SplineModel::OnSplineUpdated() BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SplineModel::UpdateDeformationBuffer() @@ -431,11 +431,6 @@ void SplineModel::Draw(RenderContext& renderContext) } } -void SplineModel::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { return false; diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 94bbb28c0..b21edbb3a 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -115,7 +115,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 1cfcda89f..8dbea083c 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -114,12 +114,12 @@ void SpotLight::UpdateBounds() BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateCommon(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SpotLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddCommon(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif @@ -133,7 +133,7 @@ void SpotLight::OnDisable() #if USE_EDITOR GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveCommon(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base LightWithShadow::OnDisable(); @@ -154,6 +154,7 @@ void SpotLight::Draw(RenderContext& renderContext) const float radius = GetScaledRadius(); const float outerConeAngle = GetOuterConeAngle(); if ((renderContext.View.Flags & ViewFlags::SpotLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && brightness > ZeroTolerance && radius > ZeroTolerance && outerConeAngle > ZeroTolerance diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index fe3c7f9be..cbb700dda 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -199,7 +199,7 @@ void StaticModel::UpdateBounds() } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool StaticModel::HasContentLoaded() const @@ -267,14 +267,6 @@ void StaticModel::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void StaticModel::DrawGeneric(RenderContext& renderContext) -{ - if (renderContext.View.RenderLayersMask.Mask & GetLayerMask() && renderContext.View.CullingFrustum.Intersects(_box)) - { - Draw(renderContext); - } -} - bool StaticModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) { bool result = false; diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index d51e50aee..60f0355bf 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -189,7 +189,6 @@ public: // [ModelInstanceActor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 39e358faf..5ff5d8c89 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -378,9 +378,10 @@ void Level::DrawActors(RenderContext& renderContext) //ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) + for (Scene* scene : Scenes) { - Scenes[i]->Rendering.Draw(renderContext); + if (scene->IsActiveInHierarchy()) + scene->Rendering.Draw(renderContext); } } @@ -390,9 +391,10 @@ void Level::CollectPostFxVolumes(RenderContext& renderContext) //ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) + for (Scene* scene : Scenes) { - Scenes[i]->Rendering.CollectPostFxVolumes(renderContext); + if (scene->IsActiveInHierarchy()) + scene->Rendering.CollectPostFxVolumes(renderContext); } } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 166373e27..6c25c2f89 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -60,7 +60,6 @@ NavMeshBoundsVolume* SceneNavigation::FindNavigationBoundsOverlap(const Bounding Scene::Scene(const SpawnParams& params) : Actor(params) - , Rendering(this) , LightmapsData(this) , CSGData(this) { diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 252793eae..51dea146b 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -1,369 +1,53 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +#define SCENE_RENDERING_USE_PROFILER 0 + #include "SceneRendering.h" -#include "Scene.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" - -#define SCENE_RENDERING_USE_PROFILER 0 -#define SCENE_RENDERING_USE_SIMD 0 - +#include "Engine/Renderer/RenderList.h" #if SCENE_RENDERING_USE_PROFILER #include "Engine/Profiler/ProfilerCPU.h" #endif -#if SCENE_RENDERING_USE_SIMD - -#include "Engine/Core/SIMD.h" - -ALIGN_BEGIN(16) struct CullDataSIMD -{ - float xs[8]; - float ys[8]; - float zs[8]; - float ds[8]; -} ALIGN_END(16); - -#endif - -int32 SceneRendering::DrawEntries::Add(Actor* obj) -{ - int32 key = 0; - for (; key < List.Count(); key++) - { - if (List[key].Actor == nullptr) - break; - } - if (key == List.Count()) - List.AddOne(); - auto& e = List[key]; - e.Actor = obj; - e.LayerMask = obj->GetLayerMask(); - e.Bounds = obj->GetSphere(); - return key; -} - -void SceneRendering::DrawEntries::Update(Actor* obj, int32 key) -{ - if (List.IsEmpty()) - return; - auto& e = List[key]; - ASSERT_LOW_LAYER(obj == e.Actor); - e.LayerMask = obj->GetLayerMask(); - e.Bounds = obj->GetSphere(); -} - -void SceneRendering::DrawEntries::Remove(Actor* obj, int32 key) -{ - if (List.IsEmpty()) - return; - auto& e = List[key]; - ASSERT_LOW_LAYER(obj == e.Actor); - e.Actor = nullptr; - e.LayerMask = 0; -} - -void SceneRendering::DrawEntries::Clear() -{ - List.Clear(); -} - -void SceneRendering::DrawEntries::CullAndDraw(RenderContext& renderContext) -{ - auto& view = renderContext.View; - const BoundingFrustum frustum = view.CullingFrustum; -#if SCENE_RENDERING_USE_SIMD - CullDataSIMD cullData; - { - // Near - auto plane = view.Frustum.GetNear(); - cullData.xs[0] = plane.Normal.X; - cullData.ys[0] = plane.Normal.Y; - cullData.zs[0] = plane.Normal.Z; - cullData.ds[0] = plane.D; - - // Far - plane = view.Frustum.GetFar(); - cullData.xs[1] = plane.Normal.X; - cullData.ys[1] = plane.Normal.Y; - cullData.zs[1] = plane.Normal.Z; - cullData.ds[1] = plane.D; - - // Left - plane = view.Frustum.GetLeft(); - cullData.xs[2] = plane.Normal.X; - cullData.ys[2] = plane.Normal.Y; - cullData.zs[2] = plane.Normal.Z; - cullData.ds[2] = plane.D; - - // Right - plane = view.Frustum.GetRight(); - cullData.xs[3] = plane.Normal.X; - cullData.ys[3] = plane.Normal.Y; - cullData.zs[3] = plane.Normal.Z; - cullData.ds[3] = plane.D; - - // Top - plane = view.Frustum.GetTop(); - cullData.xs[4] = plane.Normal.X; - cullData.ys[4] = plane.Normal.Y; - cullData.zs[4] = plane.Normal.Z; - cullData.ds[4] = plane.D; - - // Bottom - plane = view.Frustum.GetBottom(); - cullData.xs[5] = plane.Normal.X; - cullData.ys[5] = plane.Normal.Y; - cullData.zs[5] = plane.Normal.Z; - cullData.ds[5] = plane.D; - - // Extra 0 - cullData.xs[6] = 0; - cullData.ys[6] = 0; - cullData.zs[6] = 0; - cullData.ds[6] = 0; - - // Extra 1 - cullData.xs[7] = 0; - cullData.ys[7] = 0; - cullData.zs[7] = 0; - cullData.ds[7] = 0; - } - - SimdVector4 px = SIMD::Load(cullData.xs); - SimdVector4 py = SIMD::Load(cullData.ys); - SimdVector4 pz = SIMD::Load(cullData.zs); - SimdVector4 pd = SIMD::Load(cullData.ds); - SimdVector4 px2 = SIMD::Load(&cullData.xs[4]); - SimdVector4 py2 = SIMD::Load(&cullData.ys[4]); - SimdVector4 pz2 = SIMD::Load(&cullData.zs[4]); - SimdVector4 pd2 = SIMD::Load(&cullData.ds[4]); - - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - - SimdVector4 cx = SIMD::Splat(e.Bounds.Center.X); - SimdVector4 cy = SIMD::Splat(e.Bounds.Center.Y); - SimdVector4 cz = SIMD::Splat(e.Bounds.Center.Z); - SimdVector4 r = SIMD::Splat(-e.Bounds.Radius); - - SimdVector4 t = SIMD::Mul(cx, px); - t = SIMD::Add(t, SIMD::Mul(cy, py)); - t = SIMD::Add(t, SIMD::Mul(cz, pz)); - t = SIMD::Add(t, pd); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - t = SIMD::Mul(cx, px2); - t = SIMD::Add(t, SIMD::Mul(cy, py2)); - t = SIMD::Add(t, SIMD::Mul(cz, pz2)); - t = SIMD::Add(t, pd2); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - if (view.RenderLayersMask.Mask & e.LayerMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#else - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - if (view.RenderLayersMask.Mask & e.LayerMask && frustum.Intersects(e.Bounds)) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#endif -} - -void SceneRendering::DrawEntries::CullAndDrawOffline(RenderContext& renderContext) -{ - auto& view = renderContext.View; - const BoundingFrustum frustum = view.CullingFrustum; -#if SCENE_RENDERING_USE_SIMD - CullDataSIMD cullData; - { - // Near - auto plane = view.Frustum.GetNear(); - cullData.xs[0] = plane.Normal.X; - cullData.ys[0] = plane.Normal.Y; - cullData.zs[0] = plane.Normal.Z; - cullData.ds[0] = plane.D; - - // Far - plane = view.Frustum.GetFar(); - cullData.xs[1] = plane.Normal.X; - cullData.ys[1] = plane.Normal.Y; - cullData.zs[1] = plane.Normal.Z; - cullData.ds[1] = plane.D; - - // Left - plane = view.Frustum.GetLeft(); - cullData.xs[2] = plane.Normal.X; - cullData.ys[2] = plane.Normal.Y; - cullData.zs[2] = plane.Normal.Z; - cullData.ds[2] = plane.D; - - // Right - plane = view.Frustum.GetRight(); - cullData.xs[3] = plane.Normal.X; - cullData.ys[3] = plane.Normal.Y; - cullData.zs[3] = plane.Normal.Z; - cullData.ds[3] = plane.D; - - // Top - plane = view.Frustum.GetTop(); - cullData.xs[4] = plane.Normal.X; - cullData.ys[4] = plane.Normal.Y; - cullData.zs[4] = plane.Normal.Z; - cullData.ds[4] = plane.D; - - // Bottom - plane = view.Frustum.GetBottom(); - cullData.xs[5] = plane.Normal.X; - cullData.ys[5] = plane.Normal.Y; - cullData.zs[5] = plane.Normal.Z; - cullData.ds[5] = plane.D; - - // Extra 0 - cullData.xs[6] = 0; - cullData.ys[6] = 0; - cullData.zs[6] = 0; - cullData.ds[6] = 0; - - // Extra 1 - cullData.xs[7] = 0; - cullData.ys[7] = 0; - cullData.zs[7] = 0; - cullData.ds[7] = 0; - } - - SimdVector4 px = SIMD::Load(cullData.xs); - SimdVector4 py = SIMD::Load(cullData.ys); - SimdVector4 pz = SIMD::Load(cullData.zs); - SimdVector4 pd = SIMD::Load(cullData.ds); - SimdVector4 px2 = SIMD::Load(&cullData.xs[4]); - SimdVector4 py2 = SIMD::Load(&cullData.ys[4]); - SimdVector4 pz2 = SIMD::Load(&cullData.zs[4]); - SimdVector4 pd2 = SIMD::Load(&cullData.ds[4]); - - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - - SimdVector4 cx = SIMD::Splat(e.Bounds.Center.X); - SimdVector4 cy = SIMD::Splat(e.Bounds.Center.Y); - SimdVector4 cz = SIMD::Splat(e.Bounds.Center.Z); - SimdVector4 r = SIMD::Splat(-e.Bounds.Radius); - - SimdVector4 t = SIMD::Mul(cx, px); - t = SIMD::Add(t, SIMD::Mul(cy, py)); - t = SIMD::Add(t, SIMD::Mul(cz, pz)); - t = SIMD::Add(t, pd); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - t = SIMD::Mul(cx, px2); - t = SIMD::Add(t, SIMD::Mul(cy, py2)); - t = SIMD::Add(t, SIMD::Mul(cz, pz2)); - t = SIMD::Add(t, pd2); - t = SIMD::Sub(t, r); - if (SIMD::MoveMask(t)) - continue; - - if (view.RenderLayersMask.Mask & e.LayerMask && e.Actor->GetStaticFlags() & renderContext.View.StaticFlagsMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#else - for (int32 i = 0; i < List.Count(); i++) - { - auto e = List[i]; - if (view.RenderLayersMask.Mask & e.LayerMask && frustum.Intersects(e.Bounds) && e.Actor->GetStaticFlags() & view.StaticFlagsMask) - { -#if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif -#endif - e.Actor->Draw(renderContext); - } - } -#endif -} - -SceneRendering::SceneRendering(::Scene* scene) - : Scene(scene) -{ -} - void SceneRendering::Draw(RenderContext& renderContext) { - // Skip if disabled - if (!Scene->GetIsActive()) - return; auto& view = renderContext.View; + const BoundingFrustum frustum = view.CullingFrustum; + renderContext.List->Scenes.Add(this); // Draw all visual components if (view.IsOfflinePass) { - Geometry.CullAndDrawOffline(renderContext); - if (view.Pass & DrawPass::GBuffer) + for (int32 i = 0; i < Actors.Count(); i++) { - Common.CullAndDrawOffline(renderContext); - for (int32 i = 0; i < CommonNoCulling.Count(); i++) + auto& e = Actors[i]; + if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)) && e.Actor->GetStaticFlags() & view.StaticFlagsMask) { - auto actor = CommonNoCulling[i]; - if (actor->GetStaticFlags() & view.StaticFlagsMask && view.RenderLayersMask.HasLayer(actor->GetLayer())) - actor->Draw(renderContext); +#if SCENE_RENDERING_USE_PROFILER + PROFILE_CPU(); +#if TRACY_ENABLE + ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); +#endif +#endif + e.Actor->Draw(renderContext); } } } else { - Geometry.CullAndDraw(renderContext); - if (view.Pass & DrawPass::GBuffer) + for (int32 i = 0; i < Actors.Count(); i++) { - Common.CullAndDraw(renderContext); - for (int32 i = 0; i < CommonNoCulling.Count(); i++) + auto& e = Actors[i]; + if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds))) { - auto actor = CommonNoCulling[i]; - if (view.RenderLayersMask.HasLayer(actor->GetLayer())) - { #if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); + PROFILE_CPU(); #if TRACY_ENABLE - ___tracy_scoped_zone.Name(*actor->GetName(), actor->GetName().Length()); + ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); #endif #endif - actor->Draw(renderContext); - } + e.Actor->Draw(renderContext); } } } @@ -395,10 +79,47 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { - Geometry.Clear(); - Common.Clear(); - CommonNoCulling.Clear(); + Actors.Clear(); #if USE_EDITOR PhysicsDebug.Clear(); #endif } + +int32 SceneRendering::AddActor(Actor* a) +{ + int32 key = 0; + for (; key < Actors.Count(); key++) + { + if (Actors[key].Actor == nullptr) + break; + } + if (key == Actors.Count()) + Actors.AddOne(); + auto& e = Actors[key]; + e.Actor = a; + e.LayerMask = a->GetLayerMask(); + e.Bounds = a->GetSphere(); + e.NoCulling = a->_drawNoCulling; + return key; +} + +void SceneRendering::UpdateActor(Actor* a, int32 key) +{ + if (Actors.IsEmpty()) + return; + auto& e = Actors[key]; + ASSERT_LOW_LAYER(a == e.Actor); + e.LayerMask = a->GetLayerMask(); + e.Bounds = a->GetSphere(); +} + +void SceneRendering::RemoveActor(Actor* a, int32& key) +{ + if (Actors.IsEmpty()) + return; + auto& e = Actors[key]; + ASSERT_LOW_LAYER(a == e.Actor); + e.Actor = nullptr; + e.LayerMask = 0; + key = -1; +} diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 0f6c32c73..afcb078ea 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -6,7 +6,6 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Level/Actor.h" -#include "Engine/Level/Types.h" class SceneRenderTask; struct PostProcessSettings; @@ -19,7 +18,6 @@ struct RenderView; class FLAXENGINE_API IPostFxSettingsProvider { public: - /// /// Collects the settings for rendering of the specified task. /// @@ -39,46 +37,29 @@ public: /// class FLAXENGINE_API SceneRendering { - friend Scene; #if USE_EDITOR typedef Function PhysicsDebugCallback; friend class ViewportIconsRendererService; #endif - struct DrawEntry +public: + struct DrawActor { Actor* Actor; uint32 LayerMask; + int8 NoCulling : 1; BoundingSphere Bounds; }; - struct DrawEntries - { - Array List; - - int32 Add(Actor* obj); - void Update(Actor* obj, int32 key); - void Remove(Actor* obj, int32 key); - void Clear(); - void CullAndDraw(RenderContext& renderContext); - void CullAndDrawOffline(RenderContext& renderContext); - }; + Array Actors; + Array PostFxProviders; private: - - Scene* Scene; - DrawEntries Geometry; - DrawEntries Common; - Array CommonNoCulling; - Array PostFxProviders; #if USE_EDITOR Array PhysicsDebug; Array ViewportIcons; #endif - explicit SceneRendering(::Scene* scene); - public: - /// /// Draws the scene. Performs the optimized actors culling and draw calls submission for the current render pass (defined by the render view). /// @@ -97,48 +78,9 @@ public: void Clear(); public: - - FORCE_INLINE int32 AddGeometry(Actor* obj) - { - return Geometry.Add(obj); - } - - FORCE_INLINE void UpdateGeometry(Actor* obj, int32 key) - { - Geometry.Update(obj, key); - } - - FORCE_INLINE void RemoveGeometry(Actor* obj, int32& key) - { - Geometry.Remove(obj, key); - key = -1; - } - - FORCE_INLINE int32 AddCommon(Actor* obj) - { - return Common.Add(obj); - } - - FORCE_INLINE void UpdateCommon(Actor* obj, int32 key) - { - Common.Update(obj, key); - } - - FORCE_INLINE void RemoveCommon(Actor* obj, int32& key) - { - Common.Remove(obj, key); - key = -1; - } - - FORCE_INLINE void AddCommonNoCulling(Actor* obj) - { - CommonNoCulling.Add(obj); - } - - FORCE_INLINE void RemoveCommonNoCulling(Actor* obj) - { - CommonNoCulling.Remove(obj); - } + int32 AddActor(Actor* a); + void UpdateActor(Actor* a, int32 key); + void RemoveActor(Actor* a, int32& key); FORCE_INLINE void AddPostFxProvider(IPostFxSettingsProvider* obj) { @@ -151,7 +93,6 @@ public: } #if USE_EDITOR - template FORCE_INLINE void AddPhysicsDebug(T* obj) { @@ -177,6 +118,5 @@ public: { ViewportIcons.Remove(obj); } - #endif }; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 4b088951b..7cf2420ca 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -312,7 +312,7 @@ void ParticleEffect::UpdateBounds() _box = bounds; BoundingSphere::FromBox(bounds, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ParticleEffect::Sync() @@ -498,11 +498,6 @@ void ParticleEffect::Draw(RenderContext& renderContext) Particles::DrawParticles(renderContext, this); } -void ParticleEffect::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -520,7 +515,7 @@ void ParticleEffect::OnDebugDrawSelected() void ParticleEffect::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ParticleEffect::Serialize(SerializeStream& stream, const void* otherObj) @@ -699,7 +694,7 @@ void ParticleEffect::EndPlay() void ParticleEffect::OnEnable() { GetScene()->Ticking.Update.AddTick(this); - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); GetScene()->Ticking.Update.AddTickExecuteInEditor(this); @@ -715,7 +710,7 @@ void ParticleEffect::OnDisable() GetScene()->Ticking.Update.RemoveTickExecuteInEditor(this); GetSceneRendering()->RemoveViewportIcon(this); #endif - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); GetScene()->Ticking.Update.RemoveTick(this); // Base diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index c0c13b642..454eba474 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -385,7 +385,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 09e6bc6b2..43dcfd82e 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -392,6 +392,7 @@ void RenderList::Init(RenderContext& renderContext) void RenderList::Clear() { + Scenes.Clear(); DrawCalls.Clear(); BatchedDrawCalls.Clear(); for (auto& list : DrawCallsLists) diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 1b514f824..1a83065ac 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -11,6 +11,7 @@ enum class StaticFlags; class RenderBuffers; +class SceneRendering; class LightWithShadow; class IPostFxSettingsProvider; class CubeTexture; @@ -345,6 +346,11 @@ DECLARE_SCRIPTING_TYPE(RenderList); public: + /// + /// All scenes for rendering. + /// + Array Scenes; + /// /// Draw calls list (for all draw passes). /// diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index c405b390e..76c02e75d 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -47,7 +47,7 @@ void Terrain::UpdateBounds() } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Terrain::CacheNeighbors() @@ -557,11 +557,6 @@ void Terrain::Draw(RenderContext& renderContext) } } -void Terrain::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - #if USE_EDITOR //#include "Engine/Debug/DebugDraw.h" @@ -742,7 +737,7 @@ RigidBody* Terrain::GetAttachedRigidBody() const void Terrain::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->AddPhysicsDebug(this); #endif @@ -753,7 +748,7 @@ void Terrain::OnEnable() void Terrain::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->RemovePhysicsDebug(this); #endif @@ -794,7 +789,7 @@ void Terrain::OnLayerChanged() UpdateLayerBits(); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void Terrain::OnActiveInTreeChanged() diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 8a442582e..95a41bf22 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -441,7 +441,6 @@ public: // [PhysicsColliderActor] void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index 32d26623a..2e9ac8392 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -131,11 +131,6 @@ void SpriteRender::Draw(RenderContext& renderContext) model->LODs[0].Draw(renderContext, _materialInstance, world, GetStaticFlags(), false, DrawModes, GetPerInstanceRandom()); } -void SpriteRender::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - void SpriteRender::Serialize(SerializeStream& stream, const void* otherObj) { // Base @@ -173,7 +168,7 @@ void SpriteRender::Deserialize(DeserializeStream& stream, ISerializeModifier* mo void SpriteRender::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void SpriteRender::OnEndPlay() @@ -193,7 +188,7 @@ void SpriteRender::OnEndPlay() void SpriteRender::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); // Base Actor::OnEnable(); @@ -201,7 +196,7 @@ void SpriteRender::OnEnable() void SpriteRender::OnDisable() { - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -218,5 +213,5 @@ void SpriteRender::OnTransformChanged() BoundingSphere::Transform(localSphere, world, _sphere); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/UI/SpriteRender.h b/Source/Engine/UI/SpriteRender.h index 00b232266..e28399860 100644 --- a/Source/Engine/UI/SpriteRender.h +++ b/Source/Engine/UI/SpriteRender.h @@ -95,7 +95,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void OnLayerChanged() override; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 10e17d16b..0ec85ff5d 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -330,7 +330,7 @@ void TextRender::UpdateLayout() BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool TextRender::HasContentLoaded() const @@ -400,11 +400,6 @@ void TextRender::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } -void TextRender::DrawGeneric(RenderContext& renderContext) -{ - Draw(renderContext); -} - #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -426,7 +421,7 @@ void TextRender::OnDebugDrawSelected() void TextRender::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } bool TextRender::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) @@ -495,7 +490,7 @@ void TextRender::OnEnable() { UpdateLayout(); } - _sceneRenderingKey = GetSceneRendering()->AddGeometry(this); + _sceneRenderingKey = GetSceneRendering()->AddActor(this); } void TextRender::OnDisable() @@ -505,7 +500,7 @@ void TextRender::OnDisable() _isLocalized = false; Localization::LocalizationChanged.Unbind(this); } - GetSceneRendering()->RemoveGeometry(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); // Base Actor::OnDisable(); @@ -520,5 +515,5 @@ void TextRender::OnTransformChanged() BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateGeometry(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index 1b19737ad..c6a49b84e 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -166,7 +166,6 @@ public: // [Actor] bool HasContentLoaded() const override; void Draw(RenderContext& renderContext) override; - void DrawGeneric(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; #endif From 8271a2b71863bc94db9823e2b92da8eef1a8ef71 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Sun, 27 Feb 2022 13:29:58 +0100 Subject: [PATCH 019/144] Add `MeshAccelerationStructure` utility for robust triangles geometry queries --- .../ModelTool/MeshAccelerationStructure.cpp | 522 ++++++++++++++++++ .../ModelTool/MeshAccelerationStructure.h | 75 +++ 2 files changed, 597 insertions(+) create mode 100644 Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp create mode 100644 Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp new file mode 100644 index 000000000..aa04e4a6d --- /dev/null +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -0,0 +1,522 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#if COMPILE_WITH_MODEL_TOOL + +#include "MeshAccelerationStructure.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Profiler/ProfilerCPU.h" + +void MeshAccelerationStructure::BuildBVH(int32 node, int32 maxLeafSize, Array& scratch) +{ + auto& root = _bvh[node]; + ASSERT_LOW_LAYER(root.Leaf.IsLeaf); + if (root.Leaf.TriangleCount <= maxLeafSize) + return; + + // Spawn two leaves + const int32 childIndex = _bvh.Count(); + _bvh.AddDefault(2); + auto& left = _bvh.Get()[childIndex]; + auto& right = _bvh.Get()[childIndex + 1]; + left.Leaf.IsLeaf = 1; + right.Leaf.IsLeaf = 1; + left.Leaf.MeshIndex = root.Leaf.MeshIndex; + right.Leaf.MeshIndex = root.Leaf.MeshIndex; + + // Mid-point splitting based on the largest axis + Vector3 boundsSize; + root.Bounds.GetSize(boundsSize); + int32 axisCount = 0; + int32 axis = 0; +RETRY: + if (axisCount == 0) + { + // Pick the highest axis + axis = 0; + if (boundsSize.Y > boundsSize.X && boundsSize.Y >= boundsSize.Z) + axis = 1; + else if (boundsSize.Z > boundsSize.X) + axis = 2; + } + else if (axisCount == 3) + { + // Failed to split + _bvh.Resize(childIndex); + return; + } + else + { + // Go to the next axis + axis = (axis + 1) % 3; + } + const float midPoint = root.Bounds.Minimum.Raw[axis] + boundsSize.Raw[axis] * 0.5f; + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + int32 indexStart = root.Leaf.TriangleIndex * 3; + int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + left.Leaf.TriangleCount = 0; + right.Leaf.TriangleCount = 0; + if (meshData.Use16BitIndexBuffer) + { + struct Tri + { + uint16 I0, I1, I2; + }; + scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)scratch.Get(); + auto ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + const Tri tri = { ib16[i++], ib16[i++], ib16[i++] }; + const float v0 = vb[tri.I0].Raw[axis]; + const float v1 = vb[tri.I1].Raw[axis]; + const float v2 = vb[tri.I2].Raw[axis]; + const float centroid = (v0 + v1 + v2) * 0.333f; + if (centroid <= midPoint) + dst[left.Leaf.TriangleCount++] = tri; // Left + else + dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + } + Platform::MemoryCopy(ib16 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint16)); + if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) + { + axisCount++; + goto RETRY; + } + + left.Bounds = BoundingBox(vb[dst[0].I0]); + indexStart = 0; + indexEnd = left.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + left.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + + right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + indexStart = left.Leaf.TriangleCount; + indexEnd = root.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + right.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + } + else + { + struct Tri + { + uint32 I0, I1, I2; + }; + scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)scratch.Get(); + auto ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + const Tri tri = { ib32[i++], ib32[i++], ib32[i++] }; + const float v0 = vb[tri.I0].Raw[axis]; + const float v1 = vb[tri.I1].Raw[axis]; + const float v2 = vb[tri.I2].Raw[axis]; + const float centroid = (v0 + v1 + v2) * 0.333f; + if (centroid <= midPoint) + dst[left.Leaf.TriangleCount++] = tri; // Left + else + dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + } + Platform::MemoryCopy(ib32 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint32)); + if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) + { + axisCount++; + goto RETRY; + } + + left.Bounds = BoundingBox(vb[dst[0].I0]); + indexStart = 0; + indexEnd = left.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + left.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + + right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + indexStart = left.Leaf.TriangleCount; + indexEnd = root.Leaf.TriangleCount * 3; + for (int32 i = indexStart; i < indexEnd; i++) + right.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + } + ASSERT_LOW_LAYER(left.Leaf.TriangleCount + right.Leaf.TriangleCount == root.Leaf.TriangleCount); + left.Leaf.TriangleIndex = root.Leaf.TriangleIndex; + right.Leaf.TriangleIndex = left.Leaf.TriangleIndex + left.Leaf.TriangleCount; + + // Convert into a node + root.Node.IsLeaf = 0; + root.Node.ChildIndex = childIndex; + root.Node.ChildrenCount = 2; + + // Split children + BuildBVH(childIndex, maxLeafSize, scratch); + BuildBVH(childIndex + 1, maxLeafSize, scratch); +} + +bool MeshAccelerationStructure::PointQueryBVH(int32 node, const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const +{ + const auto& root = _bvh[node]; + bool hit = false; + if (root.Leaf.IsLeaf) + { + // Find closest triangle + Vector3 p; + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + const int32 indexStart = root.Leaf.TriangleIndex * 3; + const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::Distance(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::Distance(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + else + { + // Check all nested nodes + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + { + const int32 index = root.Node.ChildIndex + i; + if (_bvh[index].Bounds.Distance(point) >= hitDistance) + continue; + if (PointQueryBVH(index, point, hitDistance, hitPoint, hitTriangle)) + hit = true; + } + } + return hit; +} + +bool MeshAccelerationStructure::RayCastBVH(int32 node, const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const +{ + const auto& root = _bvh[node]; + if (!root.Bounds.Intersects(ray)) + return false; + Vector3 normal; + float distance; + bool hit = false; + if (root.Leaf.IsLeaf) + { + // Ray cast along triangles in the leaf + const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Vector3* vb = meshData.VertexBuffer.Get(); + const int32 indexStart = root.Leaf.TriangleIndex * 3; + const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = indexStart; i < indexEnd;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + else + { + // Ray cast all child nodes + Triangle triangle; + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + { + const int32 index = root.Node.ChildIndex + i; + distance = hitDistance; + if (RayCastBVH(index, ray, distance, normal, triangle) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = triangle; + hit = true; + } + } + } + return hit; +} + +void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) +{ + PROFILE_CPU(); + lodIndex = Math::Clamp(lodIndex, model->HighestResidentLODIndex(), model->LODs.Count() - 1); + ModelLOD& lod = model->LODs[lodIndex]; + const int32 meshesStart = _meshes.Count(); + _meshes.AddDefault(lod.Meshes.Count()); + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + auto& mesh = lod.Meshes[i]; + auto& meshData = _meshes[meshesStart + i]; + if (model->IsVirtual()) + { + meshData.Indices = mesh.GetTriangleCount() * 3; + meshData.Vertices = mesh.GetVertexCount(); + mesh.DownloadDataGPU(MeshBufferType::Index, meshData.IndexBuffer); + mesh.DownloadDataGPU(MeshBufferType::Vertex0, meshData.VertexBuffer); + } + else + { + mesh.DownloadDataCPU(MeshBufferType::Index, meshData.IndexBuffer, meshData.Indices); + mesh.DownloadDataCPU(MeshBufferType::Vertex0, meshData.VertexBuffer, meshData.Vertices); + } + if (!meshData.IndexBuffer.IsAllocated() && meshData.IndexBuffer.Length() != 0) + { + // BVH nodes modifies index buffer (sorts data in-place) so clone it + meshData.IndexBuffer.Copy(meshData.IndexBuffer.Get(), meshData.IndexBuffer.Length()); + } + meshData.Use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); + meshData.Bounds = mesh.GetBox(); + } +} + +void MeshAccelerationStructure::Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy) +{ + auto& meshData = _meshes.AddOne(); + if (copy) + { + meshData.VertexBuffer.Copy((const byte*)vb, vertices * sizeof(Vector3)); + } + else + { + meshData.VertexBuffer.Link((const byte*)vb, vertices * sizeof(Vector3)); + } + meshData.IndexBuffer.Copy((const byte*)ib, indices * (use16BitIndex ? sizeof(uint16) : sizeof(uint32))); + meshData.Vertices = vertices; + meshData.Indices = indices; + meshData.Use16BitIndexBuffer = use16BitIndex; +} + +void MeshAccelerationStructure::BuildBVH(int32 maxLeafSize) +{ + if (_meshes.Count() == 0) + return; + PROFILE_CPU(); + + // Estimate memory usage + int32 trianglesCount = 0; + for (const Mesh& meshData : _meshes) + trianglesCount += meshData.Indices / 3; + _bvh.Clear(); + _bvh.EnsureCapacity(trianglesCount / maxLeafSize); + + // Init with the root node and all meshes as leaves + auto& root = _bvh.AddOne(); + root.Node.IsLeaf = 0; + root.Node.ChildIndex = 1; + root.Node.ChildrenCount = _meshes.Count(); + root.Bounds = _meshes[0].Bounds; + for (int32 i = 0; i < _meshes.Count(); i++) + { + const Mesh& meshData = _meshes[i]; + auto& child = _bvh.AddOne(); + child.Leaf.IsLeaf = 1; + child.Leaf.MeshIndex = i; + child.Leaf.TriangleIndex = 0; + child.Leaf.TriangleCount = meshData.Indices / 3; + child.Bounds = meshData.Bounds; + BoundingBox::Merge(root.Bounds, meshData.Bounds, root.Bounds); + } + + // Sub-divide mesh nodes into smaller leaves + Array scratch; + for (int32 i = 0; i < _meshes.Count(); i++) + BuildBVH(i + 1, maxLeafSize, scratch); +} + +bool MeshAccelerationStructure::PointQuery(const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, float maxDistance) const +{ + hitDistance = maxDistance >= MAX_float ? maxDistance : maxDistance * maxDistance; + bool hit = false; + + // BVH + if (_bvh.Count() != 0) + { + Array> stack; + stack.Push(0); + while (stack.HasItems()) + { + const int32 node = stack.Pop(); + auto& root = _bvh[node]; + + // Skip too far nodes + if (root.Bounds.Distance(point) >= hitDistance) + continue; + + if (root.Leaf.IsLeaf) + { + // Check this leaf + hit |= PointQueryBVH(node, point, hitDistance, hitPoint, hitTriangle); + } + else + { + // Check this node children + for (uint32 i = 0; i < root.Node.ChildrenCount; i++) + stack.Push(root.Node.ChildIndex + i); + } + } + //hit = PointQueryBVH(0, point, hitDistance, hitPoint, hitTriangle); + return hit; + } + + // Brute-force + { + Vector3 p; + for (const Mesh& meshData : _meshes) + { + const Vector3* vb = meshData.VertexBuffer.Get(); + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::DistanceSquared(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + CollisionsHelper::ClosestPointPointTriangle(point, v0, v1, v2, p); + const float distance = Vector3::DistanceSquared(point, p); + if (distance < hitDistance) + { + hitDistance = distance; + hitPoint = p; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + if (hit) + hitDistance = Math::Sqrt(hitDistance); + return hit; + } +} + +bool MeshAccelerationStructure::RayCast(const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle, float maxDistance) const +{ + hitDistance = maxDistance; + + // BVH + if (_bvh.Count() != 0) + { + return RayCastBVH(0, ray, hitDistance, hitNormal, hitTriangle); + } + + // Brute-force + { + Vector3 normal; + float distance; + bool hit = false; + for (const Mesh& meshData : _meshes) + { + if (!meshData.Bounds.Intersects(ray)) + continue; + const Vector3* vb = meshData.VertexBuffer.Get(); + if (meshData.Use16BitIndexBuffer) + { + const uint16* ib16 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib16[i++]]; + Vector3 v1 = vb[ib16[i++]]; + Vector3 v2 = vb[ib16[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + else + { + const uint32* ib32 = meshData.IndexBuffer.Get(); + for (int32 i = 0; i < meshData.Indices;) + { + Vector3 v0 = vb[ib32[i++]]; + Vector3 v1 = vb[ib32[i++]]; + Vector3 v2 = vb[ib32[i++]]; + if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, distance, normal) && distance < hitDistance) + { + hitDistance = distance; + hitNormal = normal; + hitTriangle = Triangle(v0, v1, v2); + hit = true; + } + } + } + } + return hit; + } +} + +#endif diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h new file mode 100644 index 000000000..8e3684d63 --- /dev/null +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_MODEL_TOOL + +#include "Engine/Core/Math/Triangle.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Collections/Array.h" + +class Model; + +/// +/// Acceleration Structure utility for robust ray tracing mesh geometry with optimized data structure. +/// +class FLAXENGINE_API MeshAccelerationStructure +{ +private: + struct Mesh + { + BytesContainer IndexBuffer, VertexBuffer; + int32 Indices, Vertices; + bool Use16BitIndexBuffer; + BoundingBox Bounds; + }; + + struct BVH + { + BoundingBox Bounds; + + union + { + struct + { + uint32 IsLeaf : 1; + uint16 TriangleCount : 15; + uint16 MeshIndex : 16; + uint32 TriangleIndex; + } Leaf; + + struct + { + uint32 IsLeaf : 1; + uint32 ChildrenCount : 31; + int32 ChildIndex; + } Node; + }; + }; + + Array> _meshes; + Array _bvh; + + void BuildBVH(int32 node, int32 maxLeafSize, Array& scratch); + bool PointQueryBVH(int32 node, const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const; + bool RayCastBVH(int32 node, const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const; + +public: + // Adds the model geometry for the build to the structure. + void Add(Model* model, int32 lodIndex); + + // Adds the triangles geometry for the build to the structure. + void Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy = false); + + // Builds Bounding Volume Hierarchy (BVH) structure for accelerated geometry queries. + void BuildBVH(int32 maxLeafSize = 16); + + // Queries the closest triangle. + bool PointQuery(const Vector3& point, float& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, float maxDistance = MAX_float) const; + + // Ray traces the triangles. + bool RayCast(const Ray& ray, float& hitDistance, Vector3& hitNormal, Triangle& hitTriangle, float maxDistance = MAX_float) const; +}; + +#endif From 748b857e57cd564b7a1ea92880a75f58eb15d5d6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Sun, 27 Feb 2022 13:31:32 +0100 Subject: [PATCH 020/144] Add ability to use ModelTool module in game build --- Source/Engine/Tools/ModelTool/ModelTool.Build.cs | 10 ++++++---- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs index 720527cb0..aade19933 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs +++ b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs @@ -15,6 +15,11 @@ public class ModelTool : EngineModule { base.Setup(options); + options.PublicDefinitions.Add("COMPILE_WITH_MODEL_TOOL"); + + if (!options.Target.IsEditor) + return; + bool useAssimp = true; bool useAutodeskFbxSdk = false; bool useOpenFBX = true; @@ -56,15 +61,12 @@ public class ModelTool : EngineModule options.PrivateDependencies.Add("UVAtlas"); break; case TargetPlatform.Linux: - case TargetPlatform.Mac: - break; + case TargetPlatform.Mac: break; default: throw new InvalidPlatformException(options.Platform.Target); } options.PrivateDependencies.Add("meshoptimizer"); options.PrivateDependencies.Add("MikkTSpace"); - - options.PublicDefinitions.Add("COMPILE_WITH_MODEL_TOOL"); } /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index fc38b8192..8e60d4e5d 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -2,7 +2,7 @@ #pragma once -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "Engine/Core/Config.h" #include "Engine/Serialization/ISerializable.h" From 07760f7acd0c030c7650b7ad86f5887e5b205007 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Sun, 27 Feb 2022 13:32:05 +0100 Subject: [PATCH 021/144] Add `DynamicStructuredBuffer` utility --- Source/Engine/Graphics/DynamicBuffer.h | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 9f5f651be..aeab4105e 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -170,3 +170,35 @@ protected: desc = GPUBufferDescription::Index(_stride, numElements, GPUResourceUsage::Dynamic); } }; + +/// +/// Dynamic structured buffer that allows to upload data to the GPU from CPU (supports dynamic resizing). +/// +class FLAXENGINE_API DynamicStructuredBuffer : public DynamicBuffer +{ +private: + bool _isUnorderedAccess; + +public: + + /// + /// Init + /// + /// Initial capacity of the buffer (in bytes). + /// Stride in bytes. + /// True if unordered access usage. + /// Buffer name. + DynamicStructuredBuffer(uint32 initialCapacity, uint32 stride, bool isUnorderedAccess = false, const String& name = String::Empty) + : DynamicBuffer(initialCapacity, stride, name) + , _isUnorderedAccess(isUnorderedAccess) + { + } + +protected: + + // [DynamicBuffer] + void InitDesc(GPUBufferDescription& desc, int32 numElements) override + { + desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); + } +}; From 3d35277a876b122321e5a6360e6d459f58c2e3c8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 13:23:52 +0100 Subject: [PATCH 022/144] Bump up version --- Flax.flaxproj | 4 ++-- Source/FlaxEngine.Gen.cs | 4 ++-- Source/FlaxEngine.Gen.h | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 802aee16b..d2b063043 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,8 +2,8 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 3, - "Build": 6227 + "Minor": 4, + "Build": 6330 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.", diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 3d471d37d..697452ffa 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857cc7990797")] -[assembly: AssemblyVersion("1.3.6227")] -[assembly: AssemblyFileVersion("1.3.6227")] +[assembly: AssemblyVersion("1.4.6330")] +[assembly: AssemblyFileVersion("1.4.6330")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 0584eaee0..dc340ad54 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 3, 6227) -#define FLAXENGINE_VERSION_TEXT "1.3.6227" +#define FLAXENGINE_VERSION Version(1, 4, 6330) +#define FLAXENGINE_VERSION_TEXT "1.4.6330" #define FLAXENGINE_VERSION_MAJOR 1 -#define FLAXENGINE_VERSION_MINOR 3 -#define FLAXENGINE_VERSION_BUILD 6227 +#define FLAXENGINE_VERSION_MINOR 4 +#define FLAXENGINE_VERSION_BUILD 6330 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved." From 3dac0d4786cd50bb1b3e8edb28b9d76c636bd4ea Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 13:25:09 +0100 Subject: [PATCH 023/144] Fix error in Forward Shader Feature when rendering directional light shadowmap --- Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 4a69ccc59..6b043fbb2 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -22,6 +22,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanFog) @@ -39,7 +40,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanDirectionalLights.First(); const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; + const bool useShadow = shadowPass->LastDirLightIndex == 0 && canUseShadow; if (useShadow) { data.DirectionalLightShadow = shadowPass->LastDirLight; From cd66981c1dc58ab557310593930c4baba52255b1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 13:27:25 +0100 Subject: [PATCH 024/144] Add `CustomBuffers` for injecting custom state into `RenderBuffers` --- Source/Engine/Graphics/RenderBuffers.cpp | 15 ++++++++++ Source/Engine/Graphics/RenderBuffers.h | 36 ++++++++++++++++++------ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index df2962e52..a98b13d91 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -25,6 +25,11 @@ RenderBuffers::RenderBuffers(const SpawnParams& params) #undef CREATE_TEXTURE } +String RenderBuffers::CustomBuffer::ToString() const +{ + return Name; +} + RenderBuffers::~RenderBuffers() { Release(); @@ -61,6 +66,15 @@ void RenderBuffers::Prepare() UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(LuminanceMap); #undef UPDATE_LAZY_KEEP_RT + for (int32 i = CustomBuffers.Count() - 1; i >= 0; i--) + { + CustomBuffer* e = CustomBuffers[i]; + if (frameIndex - e->LastFrameUsed >= LAZY_FRAMES_COUNT) + { + Delete(e); + CustomBuffers.RemoveAt(i); + } + } } GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) @@ -184,4 +198,5 @@ void RenderBuffers::Release() UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(LuminanceMap); #undef UPDATE_LAZY_KEEP_RT + CustomBuffers.ClearDelete(); } diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 1eebe47f6..b99ec5308 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -21,18 +21,25 @@ /// API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject { -DECLARE_SCRIPTING_TYPE(RenderBuffers); -protected: + DECLARE_SCRIPTING_TYPE(RenderBuffers); + class CustomBuffer : public Object + { + public: + String Name; + uint64 LastFrameUsed = 0; + + String ToString() const override; + }; + +protected: int32 _width = 0; int32 _height = 0; float _aspectRatio = 0.0f; Viewport _viewport; - Array> _resources; public: - union { struct @@ -80,15 +87,16 @@ public: GPUTexture* TemporalAA = nullptr; uint64 LastFrameTemporalAA = 0; -public: + // Maps the custom buffer type into the object that holds the state. + Array CustomBuffers; +public: /// /// Finalizes an instance of the class. /// ~RenderBuffers(); public: - /// /// Prepares buffers for rendering a scene. Called before rendering so other parts can reuse calculated value. /// @@ -102,7 +110,6 @@ public: GPUTexture* RequestHalfResDepth(GPUContext* context); public: - /// /// Gets the buffers width (in pixels). /// @@ -143,6 +150,20 @@ public: return _viewport; } + template + T* GetCustomBuffer(const StringView& name) + { + for (CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return (T*)e; + } + CustomBuffer* result = New(); + result->Name = name; + CustomBuffers.Add(result); + return (T*)result; + } + /// /// Gets the current GPU memory usage by all the buffers (in bytes). /// @@ -162,7 +183,6 @@ public: API_FIELD(ReadOnly) GPUTexture* MotionVectors; public: - /// /// Allocates the buffers. /// From c85ee9ff52352b6749e636d8ad537552d14da10a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 13:28:50 +0100 Subject: [PATCH 025/144] Fix key clearing on actor remove from `SceneRendering` --- Source/Engine/Level/Scene/SceneRendering.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 51dea146b..72acf14e9 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -115,11 +115,12 @@ void SceneRendering::UpdateActor(Actor* a, int32 key) void SceneRendering::RemoveActor(Actor* a, int32& key) { - if (Actors.IsEmpty()) - return; - auto& e = Actors[key]; - ASSERT_LOW_LAYER(a == e.Actor); - e.Actor = nullptr; - e.LayerMask = 0; + if (Actors.HasItems()) + { + auto& e = Actors[key]; + ASSERT_LOW_LAYER(a == e.Actor); + e.Actor = nullptr; + e.LayerMask = 0; + } key = -1; } From 10d09711d9a4aec31b78cefce14ef97921f64ec6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 14:35:11 +0100 Subject: [PATCH 026/144] Add Sign Distant Field (SDF) generation for models --- Flax.sln.DotSettings | 1 + Source/Engine/Content/Assets/Model.cpp | 203 ++++++++++++++++++++++- Source/Engine/Content/Assets/Model.h | 14 ++ Source/Engine/Content/Assets/ModelBase.h | 48 +++++- Source/Engine/Content/Content.Build.cs | 1 + 5 files changed, 259 insertions(+), 8 deletions(-) diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 4c0ea9564..007b8e5a2 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -360,6 +360,7 @@ True True True + True True True True diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index d41d92f16..b237f8a0e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -2,18 +2,27 @@ #include "Model.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/RandomStream.h" #include "Engine/Engine/Engine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Core/Math/Int2.h" +#include "Engine/Debug/DebugDraw.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Threading/JobSystem.h" #include "Engine/Threading/Threading.h" +#include "Engine/Tools/ModelTool/MeshAccelerationStructure.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" #define STREAM_TASK_BASE ThreadPoolTask @@ -37,13 +46,11 @@ REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase"); class StreamModelLODTask : public STREAM_TASK_BASE { private: - WeakAssetReference _asset; int32 _lodIndex; FlaxStorage::LockData _dataLock; public: - /// /// Init /// @@ -57,7 +64,6 @@ public: } public: - // [ThreadPoolTask] bool HasReference(Object* resource) const override { @@ -65,7 +71,6 @@ public: } protected: - // [ThreadPoolTask] bool Run() override { @@ -229,7 +234,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) info.DrawState->PrevLOD = lodIndex; } } - // Check if there was a gap between frames in drawing this model instance + // Check if there was a gap between frames in drawing this model instance else if (modelFrame < frame || info.DrawState->PrevLOD == -1) { // Reset state @@ -560,6 +565,192 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) #endif +bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) +{ + if (!HasAnyLODInitialized()) + return true; + PROFILE_CPU(); + auto startTime = Platform::GetTimeSeconds(); + ScopeLock lock(Locker); + + // Setup SDF texture properties + lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1); + auto& lod = LODs[lodIndex]; + BoundingBox bounds = lod.GetBox(); + Vector3 size = bounds.GetSize(); + SDF.WorldUnitsPerVoxel = 10 / Math::Max(resolutionScale, 0.0001f); + Int3 resolution(Vector3::Ceil(Vector3::Clamp(size / SDF.WorldUnitsPerVoxel, 4, 256))); + Vector3 uvwToLocalMul = size; + Vector3 uvwToLocalAdd = bounds.Minimum; + SDF.LocalToUVWMul = Vector3::One / uvwToLocalMul; + SDF.LocalToUVWAdd = -uvwToLocalAdd / uvwToLocalMul; + SDF.MaxDistance = size.MaxValue(); + SDF.LocalBoundsMin = bounds.Minimum; + SDF.LocalBoundsMax = bounds.Maximum; + // TODO: maybe apply 1 voxel margin around the geometry? + const int32 maxMips = 3; + const int32 mipCount = Math::Min(MipLevelsCount(resolution.X, resolution.Y, resolution.Z, true), maxMips); + if (!SDF.Texture) + SDF.Texture = GPUTexture::New(); + // TODO: use 8bit format for smaller SDF textures (eg. res<100) + if (SDF.Texture->Init(GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, PixelFormat::R16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount))) + { + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); + return true; + } + + // TODO: support GPU to generate model SDF on-the-fly (if called during rendering) + + // Setup acceleration structure for fast ray tracing the mesh triangles + MeshAccelerationStructure scene; + scene.Add(this, lodIndex); + scene.BuildBVH(); + + // Allocate memory for the distant field + Array voxels; + voxels.Resize(resolution.X * resolution.Y * resolution.Z); + Vector3 xyzToLocalMul = uvwToLocalMul / Vector3(resolution); + Vector3 xyzToLocalAdd = uvwToLocalAdd; + + // TODO: use optimized sparse storage for SDF data as hierarchical bricks + // https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf + // http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf + // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf + // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf + // then use R8 format and brick size of 8x8x8 + + // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel + const int32 sampleCount = 12; + Array sampleDirections; + sampleDirections.Resize(sampleCount); + { + RandomStream rand; + sampleDirections.Get()[0] = Vector3::Up; + sampleDirections.Get()[1] = Vector3::Down; + sampleDirections.Get()[2] = Vector3::Left; + sampleDirections.Get()[3] = Vector3::Right; + sampleDirections.Get()[4] = Vector3::Forward; + sampleDirections.Get()[5] = Vector3::Backward; + for (int32 i = 6; i < sampleCount; i++) + sampleDirections.Get()[i] = rand.GetUnitVector(); + } + Function sdfJob = [this, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Job"); + const float encodeScale = 1.0f / SDF.MaxDistance; + float hitDistance; + Vector3 hitNormal, hitPoint; + Triangle hitTriangle; + const int32 zAddress = resolution.Y * resolution.X * z; + for (int32 y = 0; y < resolution.Y; y++) + { + const int32 yAddress = resolution.X * y + zAddress; + for (int32 x = 0; x < resolution.X; x++) + { + float minDistance = SDF.MaxDistance; + Vector3 voxelPos = Vector3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd; + + // Point query to find the distance to the closest surface + scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle); + + // Raycast samples around voxel to count triangle backfaces hit + int32 hitBackCount = 0, hitCount = 0; + for (int32 sample = 0; sample < sampleDirections.Count(); sample++) + { + Ray sampleRay(voxelPos, sampleDirections[sample]); + if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle)) + { + hitCount++; + const bool backHit = Vector3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0; + if (backHit) + hitBackCount++; + } + } + + float distance = minDistance; + // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry + //if ((float)hitBackCount > )hitCount * 0.3f && hitCount != 0) + if ((float)hitBackCount > (float)sampleDirections.Count() * 0.6f && hitCount != 0) + { + // Voxel is inside the geometry so turn it into negative distance to the surface + distance *= -1; + } + const int32 xAddress = x + yAddress; + voxels.Get()[xAddress] = Float16Compressor::Compress(distance * encodeScale); + } + } + }; + JobSystem::Execute(sdfJob, resolution.Z); + + // Upload data to the GPU + BytesContainer data; + data.Link((byte*)voxels.Get(), voxels.Count() * sizeof(Half)); + auto task = SDF.Texture->UploadMipMapAsync(data, 0, resolution.X * sizeof(Half), data.Length(), true); + if (task) + task->Start(); + + // Generate mip maps + Array voxelsMip; + for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++) + { + Int3 resolutionMip = Int3::Max(resolution / 2, Int3::One); + voxelsMip.Resize(resolutionMip.X * resolutionMip.Y * resolutionMip.Z); + + // Downscale mip + Function mipJob = [this, &voxelsMip, &voxels, &resolution, &resolutionMip](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Mip Job"); + const float encodeScale = 1.0f / SDF.MaxDistance; + const float decodeScale = SDF.MaxDistance; + const int32 zAddress = resolutionMip.Y * resolutionMip.X * z; + for (int32 y = 0; y < resolutionMip.Y; y++) + { + const int32 yAddress = resolutionMip.X * y + zAddress; + for (int32 x = 0; x < resolutionMip.X; x++) + { + // Linear box filter around the voxel + float distance = 0; + for (int32 dz = 0; dz < 2; dz++) + { + const int32 dzAddress = (z * 2 + dz) * (resolution.Y * resolution.X); + for (int32 dy = 0; dy < 2; dy++) + { + const int32 dyAddress = (y * 2 + dy) * (resolution.X) + dzAddress; + for (int32 dx = 0; dx < 2; dx++) + { + const int32 dxAddress = (x * 2 + dx) + dyAddress; + const float d = Float16Compressor::Decompress(voxels.Get()[dxAddress]) * decodeScale; + distance += d; + } + } + } + distance *= 1.0f / 8.0f; + + const int32 xAddress = x + yAddress; + voxelsMip.Get()[xAddress] = Float16Compressor::Compress(distance * encodeScale); + } + } + }; + JobSystem::Execute(mipJob, resolutionMip.Z); + + // Upload to the GPU + data.Link((byte*)voxelsMip.Get(), voxelsMip.Count() * sizeof(Half)); + task = SDF.Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * sizeof(Half), data.Length(), true); + if (task) + task->Start(); + + // Go down + voxelsMip.Swap(voxels); + resolution = resolutionMip; + } + +#if !BUILD_RELEASE + auto endTime = Platform::GetTimeSeconds(); + LOG(Info, "Generated SDF {}x{}x{} ({} kB) in {}ms for {}", resolution.X, resolution.Y, resolution.Z, SDF.Texture->GetMemoryUsage() / 1024, (int32)((endTime - startTime) * 1000.0), GetPath()); +#endif + return false; +} + bool Model::Init(const Span& meshesCountPerLod) { if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS) @@ -574,6 +765,7 @@ bool Model::Init(const Span& meshesCountPerLod) // Setup MaterialSlots.Resize(1); MinScreenSize = 0.0f; + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); // Setup LODs for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) @@ -840,6 +1032,7 @@ void Model::unload(bool isReloading) } // Cleanup + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); MaterialSlots.Resize(0); for (int32 i = 0; i < LODs.Count(); i++) LODs[i].Dispose(); diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index 6232147ba..a4d6711eb 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -28,6 +28,11 @@ public: /// API_FIELD(ReadOnly) Array> LODs; + /// + /// The generated Sign Distant Field (SDF) for this model (merged all meshes). Use GenerateSDF to update it. + /// + API_FIELD(ReadOnly) SDFData SDF; + public: /// @@ -200,6 +205,15 @@ public: API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty); #endif + + /// + /// Generates the Sign Distant Field for this model. + /// + /// Can be called in async in case of SDF generation on a CPU (assuming model is not during rendering). + /// The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead. + /// The index of the LOD to use for the SDF building. + /// True if failed, otherwise false. + API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6); private: diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index c194fd38e..6f2065abf 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -24,9 +24,52 @@ class MeshBase; /// API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource { -DECLARE_ASSET_HEADER(ModelBase); -protected: + DECLARE_ASSET_HEADER(ModelBase); +public: + /// + /// The Sign Distant Field (SDF) data for the model. + /// + API_STRUCT() struct SDFData + { + DECLARE_SCRIPTING_TYPE_MINIMAL(SDFData); + /// + /// The SDF volume texture (merged all meshes). + /// + API_FIELD() GPUTexture* Texture = nullptr; + + /// + /// The transformation scale from model local-space to the generated SDF texture space (local-space -> uv). + /// + API_FIELD() Vector3 LocalToUVWMul; + + /// + /// Amount of world-units per SDF texture voxel. + /// + API_FIELD() float WorldUnitsPerVoxel; + + /// + /// The transformation offset from model local-space to the generated SDF texture space (local-space -> uv). + /// + API_FIELD() Vector3 LocalToUVWAdd; + + /// + /// The maximum distance stored in the SDF texture. Used to rescale normalized SDF into world-units (in model local space). + /// + API_FIELD() float MaxDistance; + + /// + /// The bounding box of the SDF texture in the model local-space. + /// + API_FIELD() Vector3 LocalBoundsMin; + + /// + /// The bounding box of the SDF texture in the model local-space. + /// + API_FIELD() Vector3 LocalBoundsMax; + }; + +protected: explicit ModelBase(const SpawnParams& params, const AssetInfo* info, StreamingGroup* group) : BinaryAsset(params, info) , StreamableResource(group) @@ -34,7 +77,6 @@ protected: } public: - /// /// The minimum screen size to draw this model (the bottom limit). Used to cull small models. Set to 0 to disable this feature. /// diff --git a/Source/Engine/Content/Content.Build.cs b/Source/Engine/Content/Content.Build.cs index 36608e3a8..501856f02 100644 --- a/Source/Engine/Content/Content.Build.cs +++ b/Source/Engine/Content/Content.Build.cs @@ -18,6 +18,7 @@ public class Content : EngineModule options.PrivateDependencies.Add("lz4"); options.PrivateDependencies.Add("AudioTool"); options.PrivateDependencies.Add("TextureTool"); + options.PrivateDependencies.Add("ModelTool"); options.PrivateDependencies.Add("Particles"); if (options.Target.IsEditor) From 8cca7f884be96e11b526122078402bc4677615d8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 16:18:00 +0100 Subject: [PATCH 027/144] Add **Global Sign Distance Field** rendering (work in progress) --- Content/Shaders/GlobalSignDistanceField.flax | 3 + Source/Editor/Cooker/Steps/DeployDataStep.cpp | 1 + Source/Editor/Viewport/EditorViewport.cs | 1 + Source/Engine/Foliage/Foliage.cpp | 2 + Source/Engine/Foliage/FoliageType.cpp | 4 + Source/Engine/Graphics/Enums.h | 14 +- Source/Engine/Level/Actors/AnimatedModel.cpp | 6 + Source/Engine/Level/Actors/SplineModel.cpp | 6 + Source/Engine/Level/Actors/StaticModel.cpp | 5 + Source/Engine/Particles/ParticleEffect.cpp | 6 + .../Renderer/GlobalSignDistanceFieldPass.cpp | 595 ++++++++++++++++++ .../Renderer/GlobalSignDistanceFieldPass.h | 75 +++ Source/Engine/Renderer/Renderer.cpp | 13 +- Source/Engine/Terrain/Terrain.cpp | 6 + Source/Engine/UI/SpriteRender.cpp | 2 + Source/Engine/UI/TextRender.cpp | 6 + Source/Shaders/GlobalSignDistanceField.hlsl | 153 +++++ Source/Shaders/GlobalSignDistanceField.shader | 216 +++++++ 18 files changed, 1109 insertions(+), 5 deletions(-) create mode 100644 Content/Shaders/GlobalSignDistanceField.flax create mode 100644 Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp create mode 100644 Source/Engine/Renderer/GlobalSignDistanceFieldPass.h create mode 100644 Source/Shaders/GlobalSignDistanceField.hlsl create mode 100644 Source/Shaders/GlobalSignDistanceField.shader diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax new file mode 100644 index 000000000..571c8bf25 --- /dev/null +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78c2ca540a80769090fe8e855c3a61671556845d2751a31125f42204d34b1b4 +size 8246 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 4e511b0ac..8ff70bb4e 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -68,6 +68,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/MotionBlur")); data.AddRootEngineAsset(TEXT("Shaders/BitonicSort")); data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); + data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 548da2e56..c618dd568 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1407,6 +1407,7 @@ namespace FlaxEditor.Viewport new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"), new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"), new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"), + new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"), }; private void WidgetCamSpeedShowHide(Control cm) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 1d367c5ef..c784178c6 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -845,6 +845,8 @@ bool Foliage::Intersects(const Ray& ray, float& distance, Vector3& normal, int32 void Foliage::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Foliage rendering to Global SDF if (Instances.IsEmpty()) return; auto& view = renderContext.View; diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 75bd33191..bdb57b922 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -208,4 +208,8 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(PlacementRandomRollAngle); DESERIALIZE_BIT(PlacementAlignToNormal); DESERIALIZE_BIT(PlacementRandomYaw); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; } diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index 8b9057f75..c1fca1224 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -702,6 +702,11 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 /// MotionVectors = 1 << 4, + /// + /// The Global Sign Distance Field (SDF) rendering pass. + /// + GlobalSDF = 1 << 5, + /// /// The debug quad overdraw rendering (editor-only). /// @@ -712,13 +717,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 /// The default set of draw passes for the scene objects. /// API_ENUM(Attributes="HideInEditor") - Default = Depth | GBuffer | Forward | Distortion | MotionVectors, + Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF, /// /// The all draw passes combined into a single mask. /// API_ENUM(Attributes="HideInEditor") - All = Depth | GBuffer | Forward | Distortion | MotionVectors, + All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF, }; DECLARE_ENUM_OPERATORS(DrawPass); @@ -847,6 +852,11 @@ API_ENUM() enum class ViewMode /// Draw geometry overdraw to visualize performance of pixels rendering. /// QuadOverdraw = 23, + + /// + /// Draw global Sign Distant Field (SDF) preview. + /// + GlobalSDF = 24, }; /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index c6a770de7..e2d5a5668 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -689,6 +689,8 @@ void AnimatedModel::Update() void AnimatedModel::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Animated Model rendering to Global SDF GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode)); @@ -806,6 +808,10 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m DESERIALIZE(RootMotionTarget); Entries.DeserializeIfExists(stream, "Buffer", modifier); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; } bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index f432e1da5..c84226928 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -350,6 +350,8 @@ void SplineModel::Draw(RenderContext& renderContext) if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) return; auto model = Model.Get(); + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Spline Model rendering to Global SDF if (!Entries.IsValidFor(model)) Entries.Setup(model); @@ -469,6 +471,10 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(DrawModes); Entries.DeserializeIfExists(stream, "Buffer", modifier); + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; } void SplineModel::OnTransformChanged() diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index cbb700dda..6e50fb174 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -209,6 +209,8 @@ bool StaticModel::HasContentLoaded() const void StaticModel::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Static Model rendering to Global SDF GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); @@ -409,6 +411,9 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DrawModes = DrawPass::Depth; } } + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; { const auto member = stream.FindMember("RenderPasses"); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 7cf2420ca..6275509e5 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -494,6 +494,8 @@ bool ParticleEffect::HasContentLoaded() const void ParticleEffect::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position)); Particles::DrawParticles(renderContext, this); } @@ -677,6 +679,10 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* { ApplyModifiedParameters(); } + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; } void ParticleEffect::EndPlay() diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp new file mode 100644 index 000000000..881d8dd6e --- /dev/null +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -0,0 +1,595 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "GlobalSignDistanceFieldPass.h" +#include "GBufferPass.h" +#include "RenderList.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Collections/HashSet.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Level/Actors/StaticModel.h" + +// Some of those constants must match in shader +// TODO: try using R8 format for Global SDF +#define GLOBAL_SDF_FORMAT PixelFormat::R16_Float +#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 +#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_MIP_GROUP_SIZE 4 +#define GLOBAL_SDF_MIP_FLOODS 5 +#define GLOBAL_SDF_DEBUG_CHUNKS 0 + +static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple of 4 due to data packing for GPU constant buffer."); +#if GLOBAL_SDF_DEBUG_CHUNKS +#include "Engine/Debug/DebugDraw.h" +#endif + +PACK_STRUCT(struct ModelRasterizeData + { + Matrix WorldToVolume; // TODO: use 3x4 matrix + Matrix VolumeToWorld; // TODO: use 3x4 matrix + Vector3 VolumeToUVWMul; + float MipOffset; + Vector3 VolumeToUVWAdd; + float MaxDistance; + Vector3 VolumeLocalBoundsExtent; + float Padding0; + }); + +PACK_STRUCT(struct Data + { + Vector3 ViewWorldPos; + float ViewNearPlane; + Vector3 Padding00; + float ViewFarPlane; + Vector4 ViewFrustumWorldRays[4]; + GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; + }); + +PACK_STRUCT(struct ModelsRasterizeData + { + Int3 ChunkCoord; + float MaxDistance; + Vector3 CascadeCoordToPosMul; + int ModelsCount; + Vector3 CascadeCoordToPosAdd; + int32 CascadeResolution; + Vector2 Padding0; + int32 CascadeMipResolution; + int32 CascadeMipFactor; + uint32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + }); + +struct RasterizeModel +{ + Matrix WorldToVolume; + Matrix VolumeToWorld; + Vector3 VolumeToUVWMul; + Vector3 VolumeToUVWAdd; + Vector3 VolumeLocalBoundsExtent; + float MipOffset; + const ModelBase::SDFData* SDF; +}; + +struct RasterizeChunk +{ + int32 ModelsCount = 0; + int32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; +}; + +constexpr int32 RasterizeChunkKeyHashResolution = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + +struct RasterizeChunkKey +{ + uint32 Hash; + int32 Layer; + Int3 Coord; + + friend bool operator==(const RasterizeChunkKey& a, const RasterizeChunkKey& b) + { + return a.Hash == b.Hash && a.Coord == b.Coord && a.Layer == b.Layer; + } +}; + +uint32 GetHash(const RasterizeChunkKey& key) +{ + return key.Hash; +} + +class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + GPUTexture* Cascades[4] = {}; + GPUTexture* CascadeMips[4] = {}; + Vector3 Positions[4]; + HashSet NonEmptyChunks[4]; + + ~GlobalSignDistanceFieldCustomBuffer() + { + for (GPUTexture* cascade : Cascades) + RenderTargetPool::Release(cascade); + for (GPUTexture* mip : CascadeMips) + RenderTargetPool::Release(mip); + } +}; + +namespace +{ + Dictionary ChunksCache; +} + +String GlobalSignDistanceFieldPass::ToString() const +{ + return TEXT("GlobalSignDistanceFieldPass"); +} + +bool GlobalSignDistanceFieldPass::Init() +{ + // Check platform support + auto device = GPUDevice::Instance; + if (device->GetFeatureLevel() < FeatureLevel::SM5 || !device->Limits.HasCompute || !device->Limits.HasTypedUAVLoad) + return false; + if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D)) + return false; + + // Create pipeline states + _psDebug = device->CreatePipelineState(); + + // Load shader + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSignDistanceField")); + if (_shader == nullptr) + return false; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + + // Init buffer + _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); + + return false; +} + +bool GlobalSignDistanceFieldPass::setupResources() +{ + // Check shader + if (!_shader || !_shader->IsLoaded()) + return true; + const auto shader = _shader->GetShader(); + _cb0 = shader->GetCB(0); + _cb1 = shader->GetCB(1); + _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); + _csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1); + _csClearChunk = shader->GetCS("CS_ClearChunk"); + _csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0); + _csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1); + + // Create pipeline state + GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psDebug->IsValid()) + { + psDesc.PS = shader->GetPS("PS_Debug"); + if (_psDebug->Init(psDesc)) + return true; + } + + return false; +} + +#if USE_EDITOR + +void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) +{ + _psDebug->ReleaseGPU(); + _csRasterizeModel0 = nullptr; + _csRasterizeModel1 = nullptr; + _csClearChunk = nullptr; + _csGenerateMip0 = nullptr; + _csGenerateMip1 = nullptr; + _cb0 = nullptr; + _cb1 = nullptr; + invalidateResources(); +} + +#endif + +void GlobalSignDistanceFieldPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + Delete(_modelsBuffer); + _modelsTextures.Resize(0); + SAFE_DELETE_GPU_RESOURCE(_psDebug); + _shader = nullptr; + ChunksCache.Clear(); + ChunksCache.SetCapacity(0); +} + +bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) +{ + // Skip if not supported + if (setupResources()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); + + // TODO: configurable via graphics settings + const int32 resolution = 256; + const int32 mipFactor = 4; + const int32 resolutionMip = Math::DivideAndRoundUp(resolution, mipFactor); + // TODO: configurable via postFx settings + const float distanceExtent = 2500.0f; + const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (sdfData.LastFrameUsed != currentFrame) + { + PROFILE_GPU_CPU("Global SDF"); + + // Initialize buffers + sdfData.LastFrameUsed = currentFrame; + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + bool updated = false; + for (GPUTexture*& cascade : sdfData.Cascades) + { + if (cascade && cascade->Width() != desc.Width) + { + RenderTargetPool::Release(cascade); + cascade = nullptr; + } + if (!cascade) + { + cascade = RenderTargetPool::Get(desc); + if (!cascade) + return true; + updated = true; + PROFILE_GPU_CPU("Init"); + context->ClearUA(cascade, Vector4::One); + } + } + desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + { + if (cascadeMip && cascadeMip->Width() != desc.Width) + { + RenderTargetPool::Release(cascadeMip); + cascadeMip = nullptr; + } + if (!cascadeMip) + { + cascadeMip = RenderTargetPool::Get(desc); + if (!cascadeMip) + return true; + updated = true; + } + } + GPUTexture* tmpMip = nullptr; + if (updated) + LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + + // Rasterize world geometry into Global SDF + renderContext.View.Pass = DrawPass::GlobalSDF; + uint32 viewMask = renderContext.View.RenderLayersMask; + const bool useCache = !updated && !renderContext.Task->IsCameraCut; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); + const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); + auto& chunks = ChunksCache; + chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); + bool anyDraw = false; + const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; + //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + for (int32 cascade = 0; cascade < 4; cascade++) + { + // Reduce frequency of the updates + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) + continue; + const float distance = cascadesDistances[cascade]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const float snapping = voxelSize * mipFactor; + const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; + // TODO: cascade scrolling on movement to reduce dirty chunks? + //const Vector3 center = Vector3::Zero; + sdfData.Positions[cascade] = center; + BoundingBox cascadeBounds(center - distance, center + distance); + // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) + const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); + GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + + // Clear cascade before rasterization + { + PROFILE_CPU_NAMED("Clear"); + chunks.Clear(); + _modelsBuffer->Clear(); + _modelsTextures.Clear(); + } + int32 modelsBufferCount = 0; + + // Draw all objects from all scenes into the cascade + for (auto* scene : renderContext.List->Scenes) + { + // TODO: optimize for moving camera (copy sdf) + // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) + for (auto& e : scene->Actors) + { + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + { + e.Actor->Draw(renderContext); + + // TODO: move to be properly implemented per object type + auto staticModel = ScriptingObject::Cast(e.Actor); + if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered() && staticModel->DrawModes & DrawPass::GlobalSDF) + { + // okay so firstly we need SDF for this model + // TODO: implement SDF generation on model import + if (!staticModel->Model->SDF.Texture) + staticModel->Model->GenerateSDF(); + ModelBase::SDFData& sdf = staticModel->Model->SDF; + if (sdf.Texture) + { + // Setup object data + BoundingBox objectBounds = staticModel->GetBox(); + BoundingBox objectBoundsCascade; + const float objectMargin = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, cascadeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, cascadeBounds.Minimum, objectBoundsCascade.Maximum); + Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + Matrix localToWorld, worldToLocal, volumeToWorld; + staticModel->GetWorld(&localToWorld); + Matrix::Invert(localToWorld, worldToLocal); + BoundingBox localVolumeBounds(sdf.LocalBoundsMin, sdf.LocalBoundsMax); + Vector3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; + Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); + Matrix::Invert(worldToVolume, volumeToWorld); + + // Pick the SDF mip for the cascade + int32 mipLevelIndex = 1; + float worldUnitsPerVoxel = sdf.WorldUnitsPerVoxel * localToWorld.GetScaleVector().MaxValue() * 2; + while (voxelSize > worldUnitsPerVoxel && mipLevelIndex < sdf.Texture->MipLevels()) + { + mipLevelIndex++; + worldUnitsPerVoxel *= 2.0f; + } + mipLevelIndex--; + + // Volume -> Local -> UVW + Vector3 volumeToUVWMul = sdf.LocalToUVWMul; + Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; + + // Add model data for the GPU buffer + int32 modelIndex = modelsBufferCount++; + ModelRasterizeData modelData; + Matrix::Transpose(worldToVolume, modelData.WorldToVolume); + Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld); + modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; + modelData.VolumeToUVWMul = volumeToUVWMul; + modelData.VolumeToUVWAdd = volumeToUVWAdd; + modelData.MipOffset = (float)mipLevelIndex; + modelData.MaxDistance = sdf.MaxDistance; + _modelsBuffer->Write(modelData); + _modelsTextures.Add(sdf.Texture->ViewVolume()); + + // Inject object into the intersecting cascade chunks + RasterizeChunkKey key; + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Layer = 0; + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + RasterizeChunk* chunk = &chunks[key]; + + // Move to the next layer if chunk has overflown + while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) + { + key.Layer++; + key.Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + chunk = &chunks[key]; + } + + chunk->Models[chunk->ModelsCount++] = modelIndex; + } + } + } + } + } + } + } + } + + // Send models data to the GPU + { + PROFILE_GPU_CPU("Update Models"); + _modelsBuffer->Flush(context); + } + + // Perform batched chunks rasterization + if (!anyDraw) + { + anyDraw = true; + context->ResetSR(); + tmpMip = RenderTargetPool::Get(desc); + if (!tmpMip) + return true; + } + ModelsRasterizeData data; + data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; + data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; + data.MaxDistance = maxDistance; + data.CascadeResolution = resolution; + data.CascadeMipResolution = resolutionMip; + data.CascadeMipFactor = mipFactor; + context->BindUA(0, cascadeView); + context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); + if (_cb1) + context->BindCB(1, _cb1); + const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; + auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; + { + PROFILE_GPU_CPU("Clear Chunks"); + for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) + { + auto& key = it->Item; + if (chunks.ContainsKey(key)) + continue; + + // Clear empty chunk + nonEmptyChunks.Remove(it); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + if (_cb1) + context->UpdateCB(_cb1, &data); + context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches + } + } + // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds + { + PROFILE_GPU_CPU("Rasterize Chunks"); + for (auto& e : chunks) + { + // Rasterize non-empty chunk + auto& key = e.Key; + auto& chunk = e.Value; + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + int32 model = chunk.Models[i]; + data.Models[i] = model; + context->BindSR(i + 1, _modelsTextures[model]); + } + ASSERT_LOW_LAYER(chunk.ModelsCount != 0); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ModelsCount = chunk.ModelsCount; + if (_cb1) + context->UpdateCB(_cb1, &data); + GPUShaderProgramCS* cs; + if (key.Layer == 0) + { + // First layer so can override existing chunk data + cs = _csRasterizeModel0; + nonEmptyChunks.Add(key); + } + else + { + // Another layer so need combine with existing chunk data + cs = _csRasterizeModel1; + } + context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) + +#if GLOBAL_SDF_DEBUG_CHUNKS + // Debug draw chunk bounds in world space with number of models in it + if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) + { + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; + BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); + DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); + DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); + } +#endif + } + } + + // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) + { + PROFILE_GPU_CPU("Generate Mip"); + if (_cb1) + context->UpdateCB(_cb1, &data); + context->ResetUA(); + context->BindSR(0, cascadeView); + context->BindUA(0, cascadeMipView); + const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); + int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; + context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + context->UnBindSR(0); + GPUTextureView* tmpMipView = tmpMip->ViewVolume(); + for (int32 i = 1; i < floodFillIterations; i++) + { + context->ResetUA(); + context->BindSR(0, cascadeMipView); + context->BindUA(0, tmpMipView); + context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + Swap(tmpMipView, cascadeMipView); + } + if (floodFillIterations % 2 == 0) + Swap(tmpMipView, cascadeMipView); + } + } + + RenderTargetPool::Release(tmpMip); + if (anyDraw) + { + context->UnBindCB(1); + context->ResetUA(); + context->FlushState(); + context->ResetSR(); + context->FlushState(); + } + } + + // Copy results + static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); + static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.CascadeMips), "Invalid cascades count."); + Platform::MemoryCopy(result.Cascades, sdfData.Cascades, sizeof(result.Cascades)); + Platform::MemoryCopy(result.CascadeMips, sdfData.CascadeMips, sizeof(result.CascadeMips)); + for (int32 cascade = 0; cascade < 4; cascade++) + { + const float distance = cascadesDistances[cascade]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const Vector3 center = sdfData.Positions[cascade]; + result.GlobalSDF.CascadePosDistance[cascade] = Vector4(center, distance); + result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize; + } + result.GlobalSDF.Resolution = (float)resolution; + return false; +} + +void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) +{ + BindingData bindingData; + if (Render(renderContext, context, bindingData)) + { + context->Draw(output, renderContext.Buffers->GBuffer0); + return; + } + + PROFILE_GPU_CPU("Global SDF Debug"); + const Vector2 outputSize(output->Size()); + if (_cb0) + { + Data data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + for (int32 i = 0; i < 4; i++) + data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); + data.GlobalSDF = bindingData.GlobalSDF; + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingData.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingData.CascadeMips[i]->ViewVolume()); + } + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + context->SetViewportAndScissors(outputSize.X, outputSize.Y); + context->DrawFullscreenTriangle(); +} diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h new file mode 100644 index 000000000..e6eac2ab2 --- /dev/null +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "RendererPass.h" + +/// +/// Global Sign Distance Field (SDF) rendering pass. Composites scene geometry into series of 3D volume textures that cover the world around the camera for global distance field sampling. +/// +class GlobalSignDistanceFieldPass : public RendererPass +{ +public: + // Constant buffer data for Global SDF access on a GPU. + PACK_STRUCT(struct GlobalSDFData + { + Vector4 CascadePosDistance[4]; + Vector4 CascadeVoxelSize; + Vector3 Padding; + float Resolution; + }); + + // Binding data for the GPU. + struct BindingData + { + GPUTexture* Cascades[4]; + GPUTexture* CascadeMips[4]; + GlobalSDFData GlobalSDF; + }; + +private: + AssetReference _shader; + GPUPipelineState* _psDebug = nullptr; + GPUShaderProgramCS* _csRasterizeModel0 = nullptr; + GPUShaderProgramCS* _csRasterizeModel1 = nullptr; + GPUShaderProgramCS* _csClearChunk = nullptr; + GPUShaderProgramCS* _csGenerateMip0 = nullptr; + GPUShaderProgramCS* _csGenerateMip1 = nullptr; + GPUConstantBuffer* _cb0 = nullptr; + GPUConstantBuffer* _cb1 = nullptr; + class DynamicStructuredBuffer* _modelsBuffer = nullptr; + Array _modelsTextures; + +public: + /// + /// Renders the Global SDF. + /// + /// The rendering context. + /// The GPU context. + /// The result Global SDF data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, BindingData& result); + + /// + /// Renders the debug view. + /// + /// The rendering context. + /// The GPU context. + /// The output buffer. + void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + +private: +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index caf72d088..8174ebeb5 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -21,6 +21,7 @@ #include "VolumetricFogPass.h" #include "HistogramPass.h" #include "AtmospherePreCompute.h" +#include "GlobalSignDistanceFieldPass.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" @@ -45,7 +46,6 @@ Array PassList(64); class RendererService : public EngineService { public: - RendererService() : EngineService(TEXT("Renderer"), 20) { @@ -81,6 +81,7 @@ bool RendererService::Init() PassList.Add(TAA::Instance()); PassList.Add(SMAA::Instance()); PassList.Add(HistogramPass::Instance()); + PassList.Add(GlobalSignDistanceFieldPass::Instance()); #if USE_EDITOR PassList.Add(QuadOverdrawPass::Instance()); #endif @@ -340,8 +341,14 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) // Fill GBuffer GBufferPass::Instance()->Fill(renderContext, lightBuffer->View()); - // Check if debug emissive light - if (renderContext.View.Mode == ViewMode::Emissive || renderContext.View.Mode == ViewMode::LightmapUVsDensity) + // Debug drawing + if (renderContext.View.Mode == ViewMode::GlobalSDF) + { + GlobalSignDistanceFieldPass::Instance()->RenderDebug(renderContext, context, lightBuffer); + } + if (renderContext.View.Mode == ViewMode::Emissive || + renderContext.View.Mode == ViewMode::LightmapUVsDensity || + renderContext.View.Mode == ViewMode::GlobalSDF) { context->ResetRenderTarget(); context->SetRenderTarget(task->GetOutputView()); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 76c02e75d..40916a095 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -505,6 +505,8 @@ void Terrain::Draw(RenderContext& renderContext) DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); if (drawModes == DrawPass::None) return; + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Terrain rendering to Global SDF PROFILE_CPU(); @@ -727,6 +729,10 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie } #endif } + + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; } RigidBody* Terrain::GetAttachedRigidBody() const diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index 2e9ac8392..3b4f9d3b8 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -106,6 +106,8 @@ bool SpriteRender::HasContentLoaded() const void SpriteRender::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded()) return; auto model = _quadModel.As(); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 0ec85ff5d..93051d500 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -340,6 +340,8 @@ bool TextRender::HasContentLoaded() const void TextRender::Draw(RenderContext& renderContext) { + if (renderContext.View.Pass == DrawPass::GlobalSDF) + return; // TODO: Text rendering to Global SDF if (_isDirty) { UpdateLayout(); @@ -478,6 +480,10 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi DESERIALIZE_MEMBER(Scale, _layoutOptions.Scale); DESERIALIZE_MEMBER(GapScale, _layoutOptions.BaseLinesGapScale); + // [Deprecated on 07.02.2022, expires on 07.02.2024] + if (modifier->EngineBuild <= 6330) + DrawModes |= DrawPass::GlobalSDF; + _isDirty = true; } diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl new file mode 100644 index 000000000..83a98ae00 --- /dev/null +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -0,0 +1,153 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Collisions.hlsl" + +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_MIP_FLOODS 5 + +// Global SDF data for a constant buffer +struct GlobalSDFData +{ + float4 CascadePosDistance[4]; + float4 CascadeVoxelSize; + float3 Padding; + float Resolution; +}; + +// Global SDF ray trace settings. +struct GlobalSDFTrace +{ + float3 WorldPosition; + float MinDistance; + float3 WorldDirection; + float MaxDistance; + float StepScale; + + void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance, float stepScale = 1.0f) + { + WorldPosition = worldPosition; + WorldDirection = worldDirection; + MinDistance = minDistance; + MaxDistance = maxDistance; + StepScale = stepScale; + } +}; + +// Global SDF ray trace hit information. +struct GlobalSDFHit +{ + float HitTime; + uint HitCascade; + uint StepsCount; + + bool IsHit() + { + return HitTime >= 0.0f; + } + + float3 GetHitPosition(const GlobalSDFTrace trace) + { + return trace.WorldPosition + trace.WorldDirection * HitTime; + } +}; + +// Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. +float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) +{ + float distance = data.CascadePosDistance[3].w * 2.0f; + for (uint cascade = minCascade; cascade < 4; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float3 posInCascade = worldPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + if (any(cascadeUV < 0) || any(cascadeUV > 1)) + continue; + // TODO: sample mip first + float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (cascadeDistance < 1.0f) + { + distance = cascadeDistance * cascadeMaxDistance; + break; + } + } + return distance; +} + +// Ray traces the Global SDF. +GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4], Texture3D mips[4], const GlobalSDFTrace trace, uint minCascade = 0) +{ + GlobalSDFHit hit; + hit.HitTime = -1.0f; + hit.HitCascade = 0; + hit.StepsCount = 0; + float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) + float chunkMarginDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) + float nextIntersectionStart = 0.0f; + float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); + float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; + UNROLL + for (uint cascade = minCascade; cascade < 4 && hit.HitTime < 0.0f; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float voxelSize = data.CascadeVoxelSize[cascade]; + float voxelExtent = voxelSize * 0.5f; + float cascadeMinStep = voxelSize; + + // Hit the cascade bounds to find the intersection points + float2 intersections = LineHitBox(trace.WorldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www); + intersections.xy *= traceMaxDistance; + intersections.x = max(intersections.x, nextIntersectionStart); + if (intersections.x >= intersections.y) + break; + + // Skip the current cascade tracing on the next cascade + nextIntersectionStart = intersections.y; + + // Walk over the cascade SDF + uint step = 0; + float stepTime = intersections.x; + LOOP + for (; step < 250 && stepTime < intersections.y; step++) + { + float3 stepPosition = trace.WorldPosition + trace.WorldDirection * stepTime; + + // Sample SDF + float3 posInCascade = stepPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + float stepDistance = mips[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (stepDistance < chunkSizeDistance) + { + float stepDistanceTex = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (stepDistanceTex < chunkMarginDistance * 2) + { + stepDistance = stepDistanceTex; + } + } + else + { + // Assume no SDF nearby so perform a jump + stepDistance = chunkSizeDistance; + } + stepDistance *= cascadeMaxDistance; + + // Detect surface hit + float minSurfaceThickness = voxelExtent * saturate(stepTime / (voxelExtent * 2.0f)); + if (stepDistance < minSurfaceThickness) + { + // Surface hit + hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f); + hit.HitCascade = cascade; + break; + } + + // Move forward + stepTime += max(stepDistance * trace.StepScale, cascadeMinStep); + } + hit.StepsCount += step; + } + return hit; +} diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader new file mode 100644 index 000000000..058e13749 --- /dev/null +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -0,0 +1,216 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" + +#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 +#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 +#define GLOBAL_SDF_MIP_GROUP_SIZE 4 + +struct ModelRasterizeData +{ + float4x4 WorldToVolume; // TODO: use 3x4 matrix + float4x4 VolumeToWorld; // TODO: use 3x4 matrix + float3 VolumeToUVWMul; + float MipOffset; + float3 VolumeToUVWAdd; + float MaxDistance; + float3 VolumeLocalBoundsExtent; + float Padding0; +}; + +META_CB_BEGIN(0, Data) +float3 ViewWorldPos; +float ViewNearPlane; +float3 Padding00; +float ViewFarPlane; +float4 ViewFrustumWorldRays[4]; +GlobalSDFData GlobalSDF; +META_CB_END + +META_CB_BEGIN(1, ModelsRasterizeData) +int3 ChunkCoord; +float MaxDistance; +float3 CascadeCoordToPosMul; +int ModelsCount; +float3 CascadeCoordToPosAdd; +int CascadeResolution; +float2 Padding0; +int CascadeMipResolution; +int CascadeMipFactor; +uint4 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4]; +META_CB_END + +float CombineDistanceToSDF(float sdf, float distanceToSDF) +{ + // Simple sum (aprox) + //return sdf + distanceToSDF; + + // Negative distinace inside the SDF + if (sdf <= 0 && distanceToSDF <= 0) return sdf; + + // Worst-case scenario with triangle edge (C^2 = A^2 + B^2) + return sqrt(Square(max(sdf, 0)) + Square(distanceToSDF)); +} + +#if defined(_CS_RasterizeModel) + +RWTexture3D GlobalSDFTex : register(u0); +StructuredBuffer ModelsBuffer : register(t0); +Texture3D ModelSDFTex[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1); + +float DistanceToModelSDF(float minDistance, ModelRasterizeData modelData, Texture3D modelSDFTex, float3 worldPos) +{ + // Compute SDF volume UVs and distance in world-space to the volume bounds + float3 volumePos = mul(float4(worldPos, 1), modelData.WorldToVolume).xyz; + float3 volumeUV = volumePos * modelData.VolumeToUVWMul + modelData.VolumeToUVWAdd; + float3 volumePosClamped = clamp(volumePos, -modelData.VolumeLocalBoundsExtent, modelData.VolumeLocalBoundsExtent); + float3 worldPosClamped = mul(float4(volumePosClamped, 1), modelData.VolumeToWorld).xyz; + float distanceToVolume = distance(worldPos, worldPosClamped); + + // Skip sampling SDF if there is already a better result + BRANCH if (minDistance <= distanceToVolume) return distanceToVolume; + + // Sample SDF + float volumeDistance = modelSDFTex.SampleLevel(SamplerLinearClamp, volumeUV, modelData.MipOffset).x * modelData.MaxDistance; + + // Combine distance to the volume with distance to the surface inside the model + float result = CombineDistanceToSDF(volumeDistance, distanceToVolume); + if (distanceToVolume > 0) + { + // Prevent negative distance outside the model + result = max(distanceToVolume, result); + } + return result; +} + +// Compute shader for rasterizing model SDF into Global SDF +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(READ_SDF=0) +META_PERMUTATION_1(READ_SDF=1) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_RasterizeModel(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; + float minDistance = MaxDistance; +#if READ_SDF + minDistance *= GlobalSDFTex[voxelCoord]; +#endif + for (int i = 0; i < ModelsCount; i++) + { + ModelRasterizeData modelData = ModelsBuffer[Models[i / 4][i % 4]]; + float modelDistance = DistanceToModelSDF(minDistance, modelData, ModelSDFTex[i], voxelWorldPos); + minDistance = min(minDistance, modelDistance); + } + GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance); +} + +#endif + +#if defined(_CS_ClearChunk) + +RWTexture3D GlobalSDFTex : register(u0); + +// Compute shader for clearing Global SDF chunk +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_ClearChunk(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + GlobalSDFTex[voxelCoord] = 1.0f; +} + +#endif + +#if defined(_CS_GenerateMip) + +RWTexture3D GlobalSDFMip : register(u0); +Texture3D GlobalSDFTex : register(t0); + +float SampleSDF(uint3 voxelCoordMip, int3 offset) +{ +#if SAMPLE_MIP + // Sampling Global SDF Mip + float resolution = CascadeMipResolution; +#else + // Sampling Global SDF Tex + voxelCoordMip *= CascadeMipFactor; + float resolution = CascadeResolution; +#endif + + // Sample SDF + voxelCoordMip = (uint3)clamp((int3)voxelCoordMip + offset, 0, resolution - 1); + float result = GlobalSDFTex[voxelCoordMip].r; + + // Extend by distance to the sampled texel location + float distanceInWorldUnits = length(offset) * (MaxDistance / resolution); + float distanceToVoxel = distanceInWorldUnits / MaxDistance; + result = CombineDistanceToSDF(result, distanceToVoxel); + + return result; +} + +// Compute shader for generating mip for Global SDF (uses flood fill algorithm) +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(SAMPLE_MIP=0) +META_PERMUTATION_1(SAMPLE_MIP=1) +[numthreads(GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE)] +void CS_GenerateMip(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoordMip = DispatchThreadId; + float minDistance = SampleSDF(voxelCoordMip, int3(0, 0, 0)); + + // Find the distance to the closest surface by sampling the nearby area (flood fill) + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(1, 0, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 1, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, 1))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(-1, 0, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, -1, 0))); + minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, -1))); + + GlobalSDFMip[voxelCoordMip] = minDistance; +} + +#endif + +#ifdef _PS_Debug + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); + +// Pixel shader for Global SDF debug drawing +META_PS(true, FEATURE_LEVEL_SM5) +float4 PS_Debug(Quad_VS2PS input) : SV_Target +{ +#if 0 + // Preview Global SDF slice + float zSlice = 0.6f; + float mip = 0; + uint cascade = 0; + float distance01 = GlobalSDFTex[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x; + //float distance01 = GlobalSDFMip[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x; + float distance = distance01 * GlobalSDF.CascadePosDistance[cascade].w; + if (abs(distance) < 1) + return float4(1, 0, 0, 1); + if (distance01 < 0) + return float4(0, 0, 1 - distance01, 1); + return float4(0, 1 - distance01, 0, 1); +#endif + + // Shot a ray from camera into the Global SDF + GlobalSDFTrace trace; + float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; + viewRay = normalize(viewRay - ViewWorldPos); + trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + + // Debug draw + float3 color = saturate(hit.StepsCount / 80.0f).xxx; + if (!hit.IsHit()) + color.rg *= 0.4f; + return float4(color, 1); +} + +#endif From 8d0a779ff4ecca0f38118e1e780726fa4221020f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 18 Mar 2022 17:55:35 +0100 Subject: [PATCH 028/144] Fix uploading volume texture data to GPU in D3D12 --- .../DirectX/DX12/UploadBufferDX12.cpp | 17 +++++++++-------- .../DirectX/DX12/UploadBufferDX12.h | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index eaf4a4afb..7bba6a3ba 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -90,29 +90,30 @@ bool UploadBufferDX12::UploadTexture(GPUContextDX12* context, ID3D12Resource* te _device->GetDevice()->GetCopyableFootprints(&resourceDesc, subresourceIndex, 1, 0, &footprint, &numRows, &rowPitchAligned, &mipSizeAligned); rowPitchAligned = footprint.Footprint.RowPitch; mipSizeAligned = rowPitchAligned * footprint.Footprint.Height; + const uint32 numSlices = resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? Math::Max(1, resourceDesc.DepthOrArraySize >> mipIndex) : 1; + const uint64 sliceSizeAligned = numSlices * mipSizeAligned; // Allocate data - const DynamicAllocation allocation = Allocate(mipSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); - if (allocation.Size != mipSizeAligned) + const DynamicAllocation allocation = Allocate(sliceSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); + if (allocation.Size != sliceSizeAligned) return true; - // Check if can copy rows at once byte* ptr = (byte*)srcData; - ASSERT(srcSlicePitch <= mipSizeAligned); - if (srcRowPitch == rowPitchAligned) + ASSERT(srcSlicePitch <= sliceSizeAligned); + if (srcSlicePitch == sliceSizeAligned) { // Copy data at once Platform::MemoryCopy(allocation.CPUAddress, ptr, srcSlicePitch); } else { - // Use per row copy + // Copy data per-row byte* dst = static_cast(allocation.CPUAddress); ASSERT(srcRowPitch <= rowPitchAligned); - for (uint32 i = 0; i < numRows; i++) + const uint32 numCopies = numSlices * numRows; + for (uint32 i = 0; i < numCopies; i++) { Platform::MemoryCopy(dst, ptr, srcRowPitch); - dst += rowPitchAligned; ptr += srcRowPitch; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h index 0e031d402..dfff8b203 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h @@ -141,9 +141,8 @@ struct DynamicAllocation } /// - /// Returns true if allocation is invalid + /// Returns true if allocation is invalid. /// - /// True if allocation in invalid bool IsInvalid() const { return CPUAddress == nullptr || Size == 0 || Page == nullptr; From abbb0eb85cf6c2c09c0eab12565a579a6eb3f6dc Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 21 Mar 2022 10:50:34 +0100 Subject: [PATCH 029/144] Disable SDF generation for virtual models on a main thread --- Source/Engine/Content/Assets/Model.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index b237f8a0e..34f27a27e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -569,6 +569,12 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) { if (!HasAnyLODInitialized()) return true; + if (IsInMainThread() && IsVirtual()) + { + // TODO: could be supported if algorithm could run on a GPU and called during rendering + LOG(Warning, "Cannot generate SDF for virtual models on a main thread."); + return true; + } PROFILE_CPU(); auto startTime = Platform::GetTimeSeconds(); ScopeLock lock(Locker); @@ -709,6 +715,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) for (int32 x = 0; x < resolutionMip.X; x++) { // Linear box filter around the voxel + // TODO: use min distance for nearby texels (texel distance + distance to texel) float distance = 0; for (int32 dz = 0; dz < 2; dz++) { From 8f99a3df76bc6290496afae9073a57911824ed3c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 21 Mar 2022 10:50:56 +0100 Subject: [PATCH 030/144] Add soft handling for MeshAccelerationStructure build from model on data gather fail --- .../Tools/ModelTool/MeshAccelerationStructure.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index aa04e4a6d..1212b293e 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -294,6 +294,7 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) ModelLOD& lod = model->LODs[lodIndex]; const int32 meshesStart = _meshes.Count(); _meshes.AddDefault(lod.Meshes.Count()); + bool failed = false; for (int32 i = 0; i < lod.Meshes.Count(); i++) { auto& mesh = lod.Meshes[i]; @@ -302,13 +303,18 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) { meshData.Indices = mesh.GetTriangleCount() * 3; meshData.Vertices = mesh.GetVertexCount(); - mesh.DownloadDataGPU(MeshBufferType::Index, meshData.IndexBuffer); - mesh.DownloadDataGPU(MeshBufferType::Vertex0, meshData.VertexBuffer); + failed |= mesh.DownloadDataGPU(MeshBufferType::Index, meshData.IndexBuffer); + failed |= mesh.DownloadDataGPU(MeshBufferType::Vertex0, meshData.VertexBuffer); } else { - mesh.DownloadDataCPU(MeshBufferType::Index, meshData.IndexBuffer, meshData.Indices); - mesh.DownloadDataCPU(MeshBufferType::Vertex0, meshData.VertexBuffer, meshData.Vertices); + failed |= mesh.DownloadDataCPU(MeshBufferType::Index, meshData.IndexBuffer, meshData.Indices); + failed |= mesh.DownloadDataCPU(MeshBufferType::Vertex0, meshData.VertexBuffer, meshData.Vertices); + } + if (failed) + { + _meshes.Resize(meshesStart); + return; } if (!meshData.IndexBuffer.IsAllocated() && meshData.IndexBuffer.Length() != 0) { From d58584e1fd13793c2037b6b0790e4b3a6dd36580 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 21 Mar 2022 10:51:42 +0100 Subject: [PATCH 031/144] Fix crash if D3D device gets `DXGI_ERROR_DEVICE_REMOVED` during init --- Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index 3dcf92ced..0dfcef1ff 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -212,16 +212,17 @@ namespace RenderToolsDX if (errorCode == DXGI_ERROR_DEVICE_REMOVED || errorCode == DXGI_ERROR_DEVICE_RESET || errorCode == DXGI_ERROR_DRIVER_INTERNAL_ERROR) { HRESULT reason = S_OK; + const RendererType rendererType = GPUDevice::Instance ? GPUDevice::Instance->GetRendererType() : RendererType::Unknown; #if GRAPHICS_API_DIRECTX12 - if (GPUDevice::Instance->GetRendererType() == RendererType::DirectX12) + if (rendererType == RendererType::DirectX12) { reason = ((ID3D12Device*)GPUDevice::Instance->GetNativePtr())->GetDeviceRemovedReason(); } #endif #if GRAPHICS_API_DIRECTX11 - if (GPUDevice::Instance->GetRendererType() == RendererType::DirectX11 || - GPUDevice::Instance->GetRendererType() == RendererType::DirectX10_1 || - GPUDevice::Instance->GetRendererType() == RendererType::DirectX10) + if (rendererType == RendererType::DirectX11 || + rendererType == RendererType::DirectX10_1 || + rendererType == RendererType::DirectX10) { reason = ((ID3D11Device*)GPUDevice::Instance->GetNativePtr())->GetDeviceRemovedReason(); } From c10cdc3d90663528ff445bee52ea7ca9210ba243 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 12:55:13 +0100 Subject: [PATCH 032/144] Fix `UsedSRsMask`/`UsedUAsMask` when binding arrays to the shader --- Source/Engine/Graphics/Shaders/GPUShader.h | 2 +- .../Vulkan/DescriptorSetVulkan.cpp | 69 +++--- .../Vulkan/DescriptorSetVulkan.h | 70 ++---- .../GraphicsDevice/Vulkan/GPUBufferVulkan.cpp | 8 +- .../GraphicsDevice/Vulkan/GPUBufferVulkan.h | 4 +- .../Vulkan/GPUContextVulkan.cpp | 213 +++++++++--------- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 4 +- .../Vulkan/GPUPipelineStateVulkan.cpp | 11 +- Source/Engine/GraphicsDevice/Vulkan/Types.h | 6 + .../DirectX/ShaderCompilerD3D.cpp | 6 +- .../DirectX/ShaderCompilerDX.cpp | 40 ++-- .../Vulkan/ShaderCompilerVulkan.cpp | 82 ++++--- 12 files changed, 250 insertions(+), 265 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 35f239e26..28ef2ee62 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 8 +#define GPU_SHADER_CACHE_VERSION 9 /// /// Represents collection of shader programs with permutations and custom names. diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp index 37ef2722e..171e60b8b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp @@ -8,7 +8,6 @@ #include "GPUDeviceVulkan.h" #include "RenderToolsVulkan.h" #include "GPUContextVulkan.h" -#include "GPUAdapterVulkan.h" #include "CmdBufferVulkan.h" #include "Engine/Threading/Threading.h" #include "Engine/Engine/Engine.h" @@ -30,39 +29,48 @@ void DescriptorSetLayoutInfoVulkan::CacheTypesUsageID() _typesUsageID = id; } -void DescriptorSetLayoutInfoVulkan::AddDescriptor(int32 descriptorSetIndex, const VkDescriptorSetLayoutBinding& descriptor) -{ - _layoutTypes[descriptor.descriptorType]++; - - if (descriptorSetIndex >= _setLayouts.Count()) - { - _setLayouts.Resize(descriptorSetIndex + 1); - } - - SetLayout& descSetLayout = _setLayouts[descriptorSetIndex]; - descSetLayout.LayoutBindings.Add(descriptor); - - _hash = Crc::MemCrc32(&descriptor, sizeof(descriptor), _hash); -} - void DescriptorSetLayoutInfoVulkan::AddBindingsForStage(VkShaderStageFlagBits stageFlags, DescriptorSet::Stage descSet, const SpirvShaderDescriptorInfo* descriptorInfo) { - const int32 descriptorSetIndex = (int32)descSet; + const int32 descriptorSetIndex = descSet; + if (descriptorSetIndex >= _setLayouts.Count()) + _setLayouts.Resize(descriptorSetIndex + 1); + SetLayout& descSetLayout = _setLayouts[descriptorSetIndex]; VkDescriptorSetLayoutBinding binding; - binding.descriptorCount = 1; binding.stageFlags = stageFlags; binding.pImmutableSamplers = nullptr; - for (uint32 descriptorIndex = 0; descriptorIndex < descriptorInfo->DescriptorTypesCount; descriptorIndex++) { auto& descriptor = descriptorInfo->DescriptorTypes[descriptorIndex]; binding.binding = descriptorIndex; binding.descriptorType = descriptor.DescriptorType; - AddDescriptor(descriptorSetIndex, binding); + binding.descriptorCount = descriptor.Count; + + _layoutTypes[binding.descriptorType]++; + descSetLayout.LayoutBindings.Add(binding); + _hash = Crc::MemCrc32(&binding, sizeof(binding), _hash); } } +bool DescriptorSetLayoutInfoVulkan::operator==(const DescriptorSetLayoutInfoVulkan& other) const +{ + if (other._setLayouts.Count() != _setLayouts.Count()) + return false; + if (other._typesUsageID != _typesUsageID) + return false; + + for (int32 index = 0; index < other._setLayouts.Count(); index++) + { + const int32 bindingsCount = _setLayouts[index].LayoutBindings.Count(); + if (other._setLayouts[index].LayoutBindings.Count() != bindingsCount) + return false; + if (bindingsCount != 0 && Platform::MemoryCompare(other._setLayouts[index].LayoutBindings.Get(), _setLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) + return false; + } + + return true; +} + DescriptorSetLayoutVulkan::DescriptorSetLayoutVulkan(GPUDeviceVulkan* device) : _device(device) { @@ -370,20 +378,20 @@ PipelineLayoutVulkan::~PipelineLayoutVulkan() } } -uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, uint8* bindingToDynamicOffset) +uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, uint8* bindingToDynamicOffset) { - ASSERT(info.DescriptorTypesCount <= 64); + ASSERT(info.DescriptorTypesCount <= SpirvShaderDescriptorInfo::MaxDescriptors); WriteDescriptors = writeDescriptors; WritesCount = info.DescriptorTypesCount; BindingToDynamicOffset = bindingToDynamicOffset; - uint32 dynamicOffsetIndex = 0; for (uint32 i = 0; i < info.DescriptorTypesCount; i++) { + const auto& descriptor = info.DescriptorTypes[i]; writeDescriptors->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptors->dstBinding = i; - writeDescriptors->descriptorCount = 1; - writeDescriptors->descriptorType = info.DescriptorTypes[i].DescriptorType; + writeDescriptors->descriptorCount = descriptor.Count; + writeDescriptors->descriptorType = descriptor.DescriptorType; switch (writeDescriptors->descriptorType) { @@ -394,25 +402,28 @@ uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescrip break; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - writeDescriptors->pBufferInfo = bufferInfo++; + writeDescriptors->pBufferInfo = bufferInfo; + bufferInfo += descriptor.Count; break; case VK_DESCRIPTOR_TYPE_SAMPLER: case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - writeDescriptors->pImageInfo = imageInfo++; + writeDescriptors->pImageInfo = imageInfo; + imageInfo += descriptor.Count; break; case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + writeDescriptors->pTexelBufferView = texelBufferView; + texelBufferView += descriptor.Count; break; default: - CRASH; + CRASH; break; } writeDescriptors++; } - return dynamicOffsetIndex; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h index da95ac48b..695da1be9 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h @@ -117,7 +117,6 @@ protected: uint32 _typesUsageID = ~0; void CacheTypesUsageID(); - void AddDescriptor(int32 descriptorSetIndex, const VkDescriptorSetLayoutBinding& descriptor); public: @@ -138,11 +137,6 @@ public: return _setLayouts; } - inline const uint32* GetLayoutTypes() const - { - return _layoutTypes; - } - inline uint32 GetTypesUsageID() const { return _typesUsageID; @@ -160,25 +154,7 @@ public: _setLayouts = info._setLayouts; } - inline bool operator ==(const DescriptorSetLayoutInfoVulkan& other) const - { - if (other._setLayouts.Count() != _setLayouts.Count()) - return false; - if (other._typesUsageID != _typesUsageID) - return false; - - for (int32 index = 0; index < other._setLayouts.Count(); index++) - { - const int32 bindingsCount = _setLayouts[index].LayoutBindings.Count(); - if (other._setLayouts[index].LayoutBindings.Count() != bindingsCount) - return false; - - if (bindingsCount != 0 && Platform::MemoryCompare(other._setLayouts[index].LayoutBindings.Get(), _setLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) - return false; - } - - return true; - } + bool operator==(const DescriptorSetLayoutInfoVulkan& other) const; friend inline uint32 GetHash(const DescriptorSetLayoutInfoVulkan& key) { @@ -413,6 +389,7 @@ struct DescriptorSetWriteContainerVulkan { Array DescriptorImageInfo; Array DescriptorBufferInfo; + Array DescriptorTexelBufferView; Array DescriptorWrites; Array BindingToDynamicOffset; @@ -420,6 +397,7 @@ struct DescriptorSetWriteContainerVulkan { DescriptorImageInfo.Resize(0); DescriptorBufferInfo.Resize(0); + DescriptorTexelBufferView.Resize(0); DescriptorWrites.Resize(0); BindingToDynamicOffset.Resize(0); } @@ -436,26 +414,24 @@ public: public: - uint32 SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, byte* bindingToDynamicOffset); + uint32 SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, byte* bindingToDynamicOffset); - bool WriteUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range) const + bool WriteUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); return edited; } - bool WriteDynamicUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 dynamicOffset) const + bool WriteDynamicUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 dynamicOffset, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); @@ -464,63 +440,61 @@ public: return edited; } - bool WriteSampler(uint32 descriptorIndex, VkSampler sampler) const + bool WriteSampler(uint32 descriptorIndex, VkSampler sampler, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->sampler, sampler); return edited; } - bool WriteImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout) const + bool WriteImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageView, imageView); edited |= DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageLayout, layout); return edited; } - bool WriteStorageImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout) const + bool WriteStorageImage(uint32 descriptorIndex, VkImageView imageView, VkImageLayout layout, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); - VkDescriptorImageInfo* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo); - ASSERT(imageInfo); + auto* imageInfo = const_cast(WriteDescriptors[descriptorIndex].pImageInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageView, imageView); edited |= DescriptorSet::CopyAndReturnNotEqual(imageInfo->imageLayout, layout); return edited; } - bool WriteStorageTexelBuffer(uint32 descriptorIndex, const VkBufferView* bufferView) const + bool WriteStorageTexelBuffer(uint32 descriptorIndex, VkBufferView bufferView, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER); - WriteDescriptors[descriptorIndex].pTexelBufferView = bufferView; + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pTexelBufferView + index); + *bufferInfo = bufferView; return true; } - bool WriteStorageBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range) const + bool WriteStorageBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC); - VkDescriptorBufferInfo* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo); - ASSERT(bufferInfo); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pBufferInfo + index); bool edited = DescriptorSet::CopyAndReturnNotEqual(bufferInfo->buffer, buffer); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->offset, offset); edited |= DescriptorSet::CopyAndReturnNotEqual(bufferInfo->range, range); return edited; } - bool WriteUniformTexelBuffer(uint32 descriptorIndex, const VkBufferView* view) const + bool WriteUniformTexelBuffer(uint32 descriptorIndex, VkBufferView view, uint32 index = 0) const { ASSERT(descriptorIndex < WritesCount); ASSERT(WriteDescriptors[descriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); - return DescriptorSet::CopyAndReturnNotEqual(WriteDescriptors[descriptorIndex].pTexelBufferView, view); + auto* bufferInfo = const_cast(WriteDescriptors[descriptorIndex].pTexelBufferView + index); + return DescriptorSet::CopyAndReturnNotEqual(*bufferInfo, view); } void SetDescriptorSet(VkDescriptorSet descriptorSet) const diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp index 8cdf5969d..389d19d3f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp @@ -44,10 +44,10 @@ void GPUBufferViewVulkan::Release() #endif } -void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) +void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { ASSERT_LOW_LAYER(View != VK_NULL_HANDLE); - bufferView = &View; + bufferView = View; context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } @@ -60,10 +60,10 @@ void GPUBufferViewVulkan::DescriptorAsStorageBuffer(GPUContextVulkan* context, V context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } -void GPUBufferViewVulkan::DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) +void GPUBufferViewVulkan::DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { ASSERT_LOW_LAYER(View != VK_NULL_HANDLE); - bufferView = &View; + bufferView = View; context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h index a495cf46c..9b78c266f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h @@ -48,9 +48,9 @@ public: } // [DescriptorOwnerResourceVulkan] - void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) override; + void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) override; void DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range) override; - void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) override; + void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) override; }; /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index f392a256a..e82cc6e24 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -444,117 +444,122 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des const auto& descriptor = descriptorInfo.DescriptorTypes[i]; const int32 descriptorIndex = descriptor.Binding; DescriptorOwnerResourceVulkan** handles = _handles[(int32)descriptor.BindingType]; - - switch (descriptor.DescriptorType) + for (uint32 index = 0; index < descriptor.Count; index++) { - case VK_DESCRIPTOR_TYPE_SAMPLER: - { - // Sampler - const VkSampler sampler = _samplerHandles[descriptor.Slot]; - ASSERT(sampler); - needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler); - break; - } - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: - { - // Shader Resource (Texture) - auto handle = (GPUTextureViewVulkan*)handles[descriptor.Slot]; - if (!handle) + const int32 slot = descriptor.Slot + index; + switch (descriptor.DescriptorType) { - const auto dummy = _device->HelperResources.GetDummyTexture(descriptor.ResourceType); - switch (descriptor.ResourceType) + case VK_DESCRIPTOR_TYPE_SAMPLER: + { + // Sampler + const VkSampler sampler = _samplerHandles[slot]; + ASSERT(sampler); + needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index); + break; + } + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + { + // Shader Resource (Texture) + auto handle = (GPUTextureViewVulkan*)handles[slot]; + if (!handle) { - case SpirvShaderResourceType::Texture1D: - case SpirvShaderResourceType::Texture2D: - handle = static_cast(dummy->View(0)); - break; - case SpirvShaderResourceType::Texture3D: - handle = static_cast(dummy->ViewVolume()); - break; - case SpirvShaderResourceType::TextureCube: - case SpirvShaderResourceType::Texture1DArray: - case SpirvShaderResourceType::Texture2DArray: - handle = static_cast(dummy->ViewArray()); - break; + const auto dummy = _device->HelperResources.GetDummyTexture(descriptor.ResourceType); + switch (descriptor.ResourceType) + { + case SpirvShaderResourceType::Texture1D: + case SpirvShaderResourceType::Texture2D: + handle = static_cast(dummy->View(0)); + break; + case SpirvShaderResourceType::Texture3D: + handle = static_cast(dummy->ViewVolume()); + break; + case SpirvShaderResourceType::TextureCube: + case SpirvShaderResourceType::Texture1DArray: + case SpirvShaderResourceType::Texture2DArray: + handle = static_cast(dummy->ViewArray()); + break; + } } + VkImageView imageView; + VkImageLayout layout; + handle->DescriptorAsImage(this, imageView, layout); + ASSERT(imageView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteImage(descriptorIndex, imageView, layout, index); + break; } - VkImageView imageView; - VkImageLayout layout; - handle->DescriptorAsImage(this, imageView, layout); - ASSERT(imageView != VK_NULL_HANDLE); - needsWrite |= dsWriter.WriteImage(descriptorIndex, imageView, layout); - break; - } - case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: - { - // Shader Resource (Buffer) - auto sr = handles[descriptor.Slot]; - if (!sr) + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - sr = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Shader Resource (Buffer) + auto sr = handles[slot]; + if (!sr) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + sr = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBufferView bufferView; + sr->DescriptorAsUniformTexelBuffer(this, bufferView); + ASSERT(bufferView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteUniformTexelBuffer(descriptorIndex, bufferView, index); + break; } - const VkBufferView* bufferView; - sr->DescriptorAsUniformTexelBuffer(this, bufferView); - needsWrite |= dsWriter.WriteUniformTexelBuffer(descriptorIndex, bufferView); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - { - // Unordered Access (Texture) - auto ua = handles[descriptor.Slot]; - ASSERT(ua); - VkImageView imageView; - VkImageLayout layout; - ua->DescriptorAsStorageImage(this, imageView, layout); - needsWrite |= dsWriter.WriteStorageImage(descriptorIndex, imageView, layout); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - { - // Unordered Access (Buffer) - auto ua = handles[descriptor.Slot]; - if (!ua) + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Unordered Access (Texture) + auto ua = handles[slot]; + ASSERT(ua); + VkImageView imageView; + VkImageLayout layout; + ua->DescriptorAsStorageImage(this, imageView, layout); + needsWrite |= dsWriter.WriteStorageImage(descriptorIndex, imageView, layout, index); + break; } - VkBuffer buffer; - VkDeviceSize offset, range; - ua->DescriptorAsStorageBuffer(this, buffer, offset, range); - needsWrite |= dsWriter.WriteStorageBuffer(descriptorIndex, buffer, offset, range); - break; - } - case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: - { - // Unordered Access (Buffer) - auto ua = handles[descriptor.Slot]; - if (!ua) + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: { - const auto dummy = _device->HelperResources.GetDummyBuffer(); - ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + // Unordered Access (Buffer) + auto ua = handles[slot]; + if (!ua) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBuffer buffer; + VkDeviceSize offset, range; + ua->DescriptorAsStorageBuffer(this, buffer, offset, range); + needsWrite |= dsWriter.WriteStorageBuffer(descriptorIndex, buffer, offset, range, index); + break; + } + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + { + // Unordered Access (Buffer) + auto ua = handles[slot]; + if (!ua) + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + ua = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); + } + VkBufferView bufferView; + ua->DescriptorAsStorageTexelBuffer(this, bufferView); + ASSERT(bufferView != VK_NULL_HANDLE); + needsWrite |= dsWriter.WriteStorageTexelBuffer(descriptorIndex, bufferView, index); + break; + } + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + { + // Constant Buffer + auto cb = handles[slot]; + ASSERT(cb); + VkBuffer buffer; + VkDeviceSize offset, range; + uint32 dynamicOffset; + cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); + needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset, index); + break; + } + default: + // Unknown or invalid descriptor type + CRASH; + break; } - const VkBufferView* bufferView; - ua->DescriptorAsStorageTexelBuffer(this, bufferView); - needsWrite |= dsWriter.WriteStorageTexelBuffer(descriptorIndex, bufferView); - break; - } - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: - { - // Constant Buffer - auto cb = handles[descriptor.Slot]; - ASSERT(cb); - VkBuffer buffer; - VkDeviceSize offset, range; - uint32 dynamicOffset; - cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); - needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset); - break; - } - default: - // Unknown or invalid descriptor type - CRASH; - break; } } } @@ -1494,7 +1499,7 @@ void GPUContextVulkan::CopyResource(GPUResource* dstResource, GPUResource* srcRe ASSERT(bufferCopy.size == dstBufferVulkan->GetSize()); vkCmdCopyBuffer(cmdBuffer->GetHandle(), srcBufferVulkan->GetHandle(), dstBufferVulkan->GetHandle(), 1, &bufferCopy); } - // Texture -> Texture + // Texture -> Texture else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture) { if (dstTextureVulkan->IsStaging()) @@ -1505,7 +1510,7 @@ void GPUContextVulkan::CopyResource(GPUResource* dstResource, GPUResource* srcRe ASSERT(dstTextureVulkan->StagingBuffer && srcTextureVulkan->StagingBuffer); CopyResource(dstTextureVulkan->StagingBuffer, srcTextureVulkan->StagingBuffer); } - // Texture -> Staging Texture + // Texture -> Staging Texture else { // Transition resources @@ -1635,7 +1640,7 @@ void GPUContextVulkan::CopySubresource(GPUResource* dstResource, uint32 dstSubre ASSERT(bufferCopy.size == dstBufferVulkan->GetSize()); vkCmdCopyBuffer(cmdBuffer->GetHandle(), srcBufferVulkan->GetHandle(), dstBufferVulkan->GetHandle(), 1, &bufferCopy); } - // Texture -> Texture + // Texture -> Texture else if (srcType == GPUResource::ObjectType::Texture && dstType == GPUResource::ObjectType::Texture) { const int32 dstMipMaps = dstTextureVulkan->MipLevels(); @@ -1653,7 +1658,7 @@ void GPUContextVulkan::CopySubresource(GPUResource* dstResource, uint32 dstSubre ASSERT(dstTextureVulkan->StagingBuffer && srcTextureVulkan->StagingBuffer); CopyResource(dstTextureVulkan->StagingBuffer, srcTextureVulkan->StagingBuffer); } - // Texture -> Staging Texture + // Texture -> Staging Texture else { // Transition resources diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 20f6d324f..f7fc6a221 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -782,7 +782,7 @@ public: /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. - virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) + virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } @@ -804,7 +804,7 @@ public: /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. - virtual void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) + virtual void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index d7dc050b7..0cccccf38 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -51,9 +51,11 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() uint32 dynamicOffsetsCount = 0; if (DescriptorInfo.DescriptorTypesCount != 0) { + // TODO: merge into a single allocation _pipelineState->DSWriteContainer.DescriptorWrites.AddZeroed(DescriptorInfo.DescriptorTypesCount); _pipelineState->DSWriteContainer.DescriptorImageInfo.AddZeroed(DescriptorInfo.ImageInfosCount); _pipelineState->DSWriteContainer.DescriptorBufferInfo.AddZeroed(DescriptorInfo.BufferInfosCount); + _pipelineState->DSWriteContainer.DescriptorTexelBufferView.AddZeroed(DescriptorInfo.TexelBufferViewsCount); ASSERT(DescriptorInfo.DescriptorTypesCount < 255); _pipelineState->DSWriteContainer.BindingToDynamicOffset.AddDefault(DescriptorInfo.DescriptorTypesCount); @@ -62,9 +64,10 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() VkWriteDescriptorSet* currentDescriptorWrite = _pipelineState->DSWriteContainer.DescriptorWrites.Get(); VkDescriptorImageInfo* currentImageInfo = _pipelineState->DSWriteContainer.DescriptorImageInfo.Get(); VkDescriptorBufferInfo* currentBufferInfo = _pipelineState->DSWriteContainer.DescriptorBufferInfo.Get(); + VkBufferView* currentTexelBufferView = _pipelineState->DSWriteContainer.DescriptorTexelBufferView.Get(); uint8* currentBindingToDynamicOffsetMap = _pipelineState->DSWriteContainer.BindingToDynamicOffset.Get(); - dynamicOffsetsCount = _pipelineState->DSWriter.SetupDescriptorWrites(DescriptorInfo, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentBindingToDynamicOffsetMap); + dynamicOffsetsCount = _pipelineState->DSWriter.SetupDescriptorWrites(DescriptorInfo, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentTexelBufferView, currentBindingToDynamicOffsetMap); } _pipelineState->DynamicOffsets.AddZeroed(dynamicOffsetsCount); @@ -337,9 +340,11 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) if (descriptor == nullptr || descriptor->DescriptorTypesCount == 0) continue; + // TODO: merge into a single allocation for a whole PSO DSWriteContainer.DescriptorWrites.AddZeroed(descriptor->DescriptorTypesCount); DSWriteContainer.DescriptorImageInfo.AddZeroed(descriptor->ImageInfosCount); DSWriteContainer.DescriptorBufferInfo.AddZeroed(descriptor->BufferInfosCount); + DSWriteContainer.DescriptorTexelBufferView.AddZeroed(descriptor->TexelBufferViewsCount); ASSERT(descriptor->DescriptorTypesCount < 255); DSWriteContainer.BindingToDynamicOffset.AddDefault(descriptor->DescriptorTypesCount); @@ -349,6 +354,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) VkWriteDescriptorSet* currentDescriptorWrite = DSWriteContainer.DescriptorWrites.Get(); VkDescriptorImageInfo* currentImageInfo = DSWriteContainer.DescriptorImageInfo.Get(); VkDescriptorBufferInfo* currentBufferInfo = DSWriteContainer.DescriptorBufferInfo.Get(); + VkBufferView* currentTexelBufferView = DSWriteContainer.DescriptorTexelBufferView.Get(); byte* currentBindingToDynamicOffsetMap = DSWriteContainer.BindingToDynamicOffset.Get(); uint32 dynamicOffsetsStart[DescriptorSet::GraphicsStagesCount]; uint32 dynamicOffsetsCount = 0; @@ -360,12 +366,13 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) if (descriptor == nullptr || descriptor->DescriptorTypesCount == 0) continue; - const uint32 numDynamicOffsets = DSWriter[stage].SetupDescriptorWrites(*descriptor, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentBindingToDynamicOffsetMap); + const uint32 numDynamicOffsets = DSWriter[stage].SetupDescriptorWrites(*descriptor, currentDescriptorWrite, currentImageInfo, currentBufferInfo, currentTexelBufferView, currentBindingToDynamicOffsetMap); dynamicOffsetsCount += numDynamicOffsets; currentDescriptorWrite += descriptor->DescriptorTypesCount; currentImageInfo += descriptor->ImageInfosCount; currentBufferInfo += descriptor->BufferInfosCount; + currentTexelBufferView += descriptor->TexelBufferViewsCount; currentBindingToDynamicOffsetMap += descriptor->DescriptorTypesCount; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/Types.h b/Source/Engine/GraphicsDevice/Vulkan/Types.h index a97a777d2..f7ebe3f93 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Types.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Types.h @@ -94,10 +94,16 @@ struct SpirvShaderDescriptorInfo /// The resource type. /// SpirvShaderResourceType ResourceType; + + /// + /// The amount of slots used by the descriptor (eg. array of textures size). + /// + uint32 Count; }; uint16 ImageInfosCount; uint16 BufferInfosCount; + uint32 TexelBufferViewsCount; uint32 DescriptorTypesCount; Descriptor DescriptorTypes[MaxDescriptors]; }; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp index 101ffff5a..b599b7090 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp @@ -151,7 +151,8 @@ namespace case D3D_SIT_TEXTURE: case D3D_SIT_STRUCTURED: case D3D_SIT_BYTEADDRESS: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); break; // Unordered Access @@ -161,7 +162,8 @@ namespace case D3D_SIT_UAV_APPEND_STRUCTURED: case D3D_SIT_UAV_CONSUME_STRUCTURED: case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: - bindings.UsedUAsMask |= 1 << resDesc.BindPoint; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + bindings.UsedUAsMask |= 1 << (resDesc.BindPoint + shift); break; } } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 17ffc61c4..966e20b75 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -381,13 +381,19 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD // Shader Resource case D3D_SIT_TEXTURE: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; - header.SrDimensions[resDesc.BindPoint] = resDesc.Dimension; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + { + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); + header.SrDimensions[resDesc.BindPoint + shift] = resDesc.Dimension; + } break; case D3D_SIT_STRUCTURED: case D3D_SIT_BYTEADDRESS: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; - header.SrDimensions[resDesc.BindPoint] = D3D_SRV_DIMENSION_BUFFER; + for (UINT shift = 0; shift < resDesc.BindCount; shift++) + { + bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); + header.SrDimensions[resDesc.BindPoint + shift] = D3D_SRV_DIMENSION_BUFFER; + } break; // Unordered Access @@ -397,30 +403,10 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD case D3D_SIT_UAV_APPEND_STRUCTURED: case D3D_SIT_UAV_CONSUME_STRUCTURED: case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: - bindings.UsedUAsMask |= 1 << resDesc.BindPoint; - switch (resDesc.Dimension) + for (UINT shift = 0; shift < resDesc.BindCount; shift++) { - case D3D_SRV_DIMENSION_BUFFER: - header.UaDimensions[resDesc.BindPoint] = 1; // D3D12_UAV_DIMENSION_BUFFER; - break; - case D3D_SRV_DIMENSION_TEXTURE1D: - header.UaDimensions[resDesc.BindPoint] = 2; // D3D12_UAV_DIMENSION_TEXTURE1D; - break; - case D3D_SRV_DIMENSION_TEXTURE1DARRAY: - header.UaDimensions[resDesc.BindPoint] = 3; // D3D12_UAV_DIMENSION_TEXTURE1DARRAY; - break; - case D3D_SRV_DIMENSION_TEXTURE2D: - header.UaDimensions[resDesc.BindPoint] = 4; // D3D12_UAV_DIMENSION_TEXTURE2D; - break; - case D3D_SRV_DIMENSION_TEXTURE2DARRAY: - header.UaDimensions[resDesc.BindPoint] = 5; // D3D12_UAV_DIMENSION_TEXTURE2DARRAY; - break; - case D3D_SRV_DIMENSION_TEXTURE3D: - header.UaDimensions[resDesc.BindPoint] = 8; // D3D12_UAV_DIMENSION_TEXTURE3D; - break; - default: - LOG(Error, "Unknown UAV resource {2} of type {0} at slot {1}", resDesc.Dimension, resDesc.BindPoint, String(resDesc.Name)); - return true; + bindings.UsedUAsMask |= 1 << (resDesc.BindPoint + shift); + header.SrDimensions[resDesc.BindPoint + shift] = (byte)resDesc.Dimension; // D3D_SRV_DIMENSION matches D3D12_UAV_DIMENSION } break; } diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index 3de291d23..edf4a0887 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -33,7 +33,6 @@ namespace class Includer : public glslang::TShader::Includer { private: - ShaderCompilationContext* _context; IncludeResult* include(const char* headerName, const char* includerName, int depth) const @@ -46,14 +45,12 @@ private: } public: - Includer(ShaderCompilationContext* context) { _context = context; } public: - // [glslang::TShader::Include] IncludeResult* includeLocal(const char* headerName, const char* includerName, size_t inclusionDepth) override { @@ -210,6 +207,7 @@ struct Descriptor int32 Slot; int32 Binding; int32 Size; + int32 Count; SpirvShaderResourceBindingType BindingType; VkDescriptorType DescriptorType; SpirvShaderResourceType ResourceType; @@ -229,7 +227,7 @@ SpirvShaderResourceType GetTextureType(const glslang::TSampler& sampler) case glslang::EsdCube: return SpirvShaderResourceType::TextureCube; default: - CRASH; + CRASH; return SpirvShaderResourceType::Unknown; } } @@ -244,23 +242,13 @@ bool IsUavType(const glslang::TType& type) class DescriptorsCollector { public: - - int32 Images; - int32 Buffers; - int32 DescriptorsCount; + int32 Images = 0; + int32 Buffers = 0; + int32 TexelBuffers = 0; + int32 DescriptorsCount = 0; Descriptor Descriptors[SpirvShaderDescriptorInfo::MaxDescriptors]; public: - - DescriptorsCollector() - { - Images = 0; - Buffers = 0; - DescriptorsCount = 0; - } - -public: - Descriptor* Add(glslang::TVarEntryInfo& ent) { const glslang::TType& type = ent.symbol->getType(); @@ -374,28 +362,6 @@ public: } } - // Get the output info about shader uniforms usage - switch (descriptorType) - { - case VK_DESCRIPTOR_TYPE_SAMPLER: - case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: - case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - Images++; - break; - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: - case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: - case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: - Buffers++; - break; - default: - LOG(Warning, "Invalid descriptor type {0} for symbol {1}.", (int32)descriptorType, String(name)); - return nullptr; - } - const auto index = DescriptorsCount++; auto& descriptor = Descriptors[index]; descriptor.Binding = index; @@ -405,6 +371,32 @@ public: descriptor.DescriptorType = descriptorType; descriptor.ResourceType = resourceType; descriptor.Name = name; + descriptor.Count = type.isSizedArray() ? type.getCumulativeArraySize() : 1; + + // Get the output info about shader uniforms usage + switch (descriptorType) + { + case VK_DESCRIPTOR_TYPE_SAMPLER: + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + Images += descriptor.Count; + break; + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: + Buffers += descriptor.Count; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + TexelBuffers += descriptor.Count; + break; + default: + LOG(Warning, "Invalid descriptor type {0} for symbol {1}.", (int32)descriptorType, String(name)); + return nullptr; + } + return &descriptor; } }; @@ -412,12 +404,10 @@ public: class MyIoMapResolver : public glslang::TDefaultIoResolverBase { private: - int32 _set; DescriptorsCollector* _collector; public: - MyIoMapResolver(int32 set, DescriptorsCollector* collector, const glslang::TIntermediate& intermediate) : TDefaultIoResolverBase(intermediate) , _set(set) @@ -426,7 +416,6 @@ public: } public: - // [glslang::TDefaultIoResolverBase] bool validateBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override { @@ -455,7 +444,9 @@ public: // Add resource const auto descriptor = _collector->Add(ent); if (descriptor) - return ent.newBinding = reserveSlot(_set, descriptor->Binding); + { + return ent.newBinding = reserveSlot(_set, descriptor->Binding, descriptor->Count); + } return ent.newBinding; } @@ -686,6 +677,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat // Process all descriptors header.DescriptorInfo.ImageInfosCount = descriptorsCollector.Images; header.DescriptorInfo.BufferInfosCount = descriptorsCollector.Buffers; + header.DescriptorInfo.TexelBufferViewsCount = descriptorsCollector.TexelBuffers; for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) { auto& descriptor = descriptorsCollector.Descriptors[i]; @@ -697,6 +689,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat d.BindingType = descriptor.BindingType; d.DescriptorType = descriptor.DescriptorType; d.ResourceType = descriptor.ResourceType; + d.Count = descriptor.Count; switch (descriptor.BindingType) { @@ -732,6 +725,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat // Mark as used and cache some data cc.IsUsed = true; cc.Size = descriptor.Size; + break; } } } From aa9161a16f74eea1b72b0284d28dc5ae7d318fd5 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 12:55:33 +0100 Subject: [PATCH 033/144] Fix some issues --- .../GraphicsDevice/Vulkan/GPUBufferVulkan.cpp | 2 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 19 +++++++++++++++---- .../Renderer/GlobalSignDistanceFieldPass.cpp | 6 +++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp index 389d19d3f..0dda4488f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp @@ -98,7 +98,7 @@ bool GPUBufferVulkan::OnInit() bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; if (useSRV && !(_desc.Flags & GPUBufferFlags::Structured)) bufferInfo.usage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; - if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer) + if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer || _desc.Flags & GPUBufferFlags::Structured) bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; if (useUAV && useSRV) bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 1bc66b5b6..fc8e1f4bc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -209,11 +209,22 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSever type = TEXT("Perf"); } - if (callbackData->pMessageIdName) - LOG(Info, "[Vulkan] {0} {1}:{2}({3}) {4}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessageIdName), String(callbackData->pMessage)); - else - LOG(Info, "[Vulkan] {0} {1}:{2} {3}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessage)); + // Fix invalid characters in hex values (bug in Debug Layer) + char* handleStart = (char*)StringUtils::FindIgnoreCase(callbackData->pMessage, "0x"); + while (handleStart != nullptr) + { + while (*handleStart != ' ' && *handleStart != 0) + *handleStart++ = Math::Clamp(*handleStart, '0', 'z'); + if (*handleStart == 0) + break; + handleStart = (char*)StringUtils::FindIgnoreCase(handleStart, "0x"); + } + const String message(callbackData->pMessage); + if (callbackData->pMessageIdName) + LOG(Info, "[Vulkan] {0} {1}:{2}({3}) {4}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessageIdName), message); + else + LOG(Info, "[Vulkan] {0} {1}:{2} {3}", type, severity, callbackData->messageIdNumber, message); return VK_FALSE; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 881d8dd6e..e919fa76e 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -204,7 +204,7 @@ void GlobalSignDistanceFieldPass::Dispose() RendererPass::Dispose(); // Cleanup - Delete(_modelsBuffer); + SAFE_DELETE(_modelsBuffer); _modelsTextures.Resize(0); SAFE_DELETE_GPU_RESOURCE(_psDebug); _shader = nullptr; @@ -455,7 +455,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex if (_cb1) context->UpdateCB(_cb1, &data); context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches } } // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds @@ -490,7 +490,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex cs = _csRasterizeModel1; } context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) #if GLOBAL_SDF_DEBUG_CHUNKS // Debug draw chunk bounds in world space with number of models in it From 9107897b76a8cd2fa0495d7d1131b7063c8da3d6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 12:56:21 +0100 Subject: [PATCH 034/144] Add missing pipeline barriers after Dispatch on Vulkan to prevent race-conditions with UAVs --- Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index e82cc6e24..8e28da63a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -1107,6 +1107,10 @@ void GPUContextVulkan::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCo vkCmdDispatch(cmdBuffer->GetHandle(), threadGroupCountX, threadGroupCountY, threadGroupCountZ); RENDER_STAT_DISPATCH_CALL(); + // Place a barrier between dispatches, so that UAVs can be read+write in subsequent passes + // TODO: optimize it by moving inputs/outputs into higher-layer so eg. Global SDF can manually optimize it + vkCmdPipelineBarrier(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + #if VK_ENABLE_BARRIERS_DEBUG LOG(Warning, "Dispatch"); #endif @@ -1141,6 +1145,10 @@ void GPUContextVulkan::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* b vkCmdDispatchIndirect(cmdBuffer->GetHandle(), bufferForArgsVulkan->GetHandle(), offsetForArgs); RENDER_STAT_DISPATCH_CALL(); + // Place a barrier between dispatches, so that UAVs can be read+write in subsequent passes + // TODO: optimize it by moving inputs/outputs into higher-layer so eg. Global SDF can manually optimize it + vkCmdPipelineBarrier(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + #if VK_ENABLE_BARRIERS_DEBUG LOG(Warning, "DispatchIndirect"); #endif From 6196bb31fe51b806b732416d976423a1ee59bd8f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 13:20:07 +0100 Subject: [PATCH 035/144] Post merge fixes --- Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp | 2 +- Source/Engine/ShadersCompilation/ShaderCompiler.cpp | 3 ++- Source/Engine/Tools/ModelTool/ModelTool.Build.cs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 966e20b75..790319507 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -406,7 +406,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD for (UINT shift = 0; shift < resDesc.BindCount; shift++) { bindings.UsedUAsMask |= 1 << (resDesc.BindPoint + shift); - header.SrDimensions[resDesc.BindPoint + shift] = (byte)resDesc.Dimension; // D3D_SRV_DIMENSION matches D3D12_UAV_DIMENSION + header.UaDimensions[resDesc.BindPoint + shift] = (byte)resDesc.Dimension; // D3D_SRV_DIMENSION matches D3D12_UAV_DIMENSION } break; } diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 832301b61..eba91df76 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -9,6 +9,7 @@ #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -80,7 +81,7 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) _constantBuffers.Add({ meta->CB[i].Slot, false, 0 }); // [Output] Version number - output->WriteInt32(8); + output->WriteInt32(GPU_SHADER_CACHE_VERSION); // [Output] Additional data start const int32 additionalDataStartPos = output->GetPosition(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs index aade19933..f65db0243 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs +++ b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs @@ -67,6 +67,7 @@ public class ModelTool : EngineModule options.PrivateDependencies.Add("meshoptimizer"); options.PrivateDependencies.Add("MikkTSpace"); + options.PrivateDependencies.Add("Physics"); } /// From 18321937e4dcbb343465e6c83e39c164341b1863 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 15:08:38 +0100 Subject: [PATCH 036/144] Optimize model SDF with 8-bit storage (if possible) --- Content/Shaders/GlobalSignDistanceField.flax | 4 +- Source/Engine/Content/Assets/Model.cpp | 72 +++++++++++++------ .../Renderer/GlobalSignDistanceFieldPass.cpp | 7 +- Source/Shaders/GlobalSignDistanceField.shader | 6 +- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 571c8bf25..88e8ebcc6 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f78c2ca540a80769090fe8e855c3a61671556845d2751a31125f42204d34b1b4 -size 8246 +oid sha256:0b4917350728a690a7b14ddd2c78f2b18a009c5a93663cf7b8853913951b58a6 +size 8265 diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 34f27a27e..07071a921 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -598,8 +598,35 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) const int32 mipCount = Math::Min(MipLevelsCount(resolution.X, resolution.Y, resolution.Z, true), maxMips); if (!SDF.Texture) SDF.Texture = GPUTexture::New(); - // TODO: use 8bit format for smaller SDF textures (eg. res<100) - if (SDF.Texture->Init(GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, PixelFormat::R16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount))) + PixelFormat format = PixelFormat::R16_UNorm; + int32 formatStride = 2; + float formatMaxValue = MAX_uint16; + typedef float (*FormatRead)(void* ptr); + typedef void (*FormatWrite)(void* ptr, float v); + FormatRead formatRead = [](void* ptr) + { + return (float)*(uint16*)ptr; + }; + FormatWrite formatWrite = [](void* ptr, float v) + { + *(uint16*)ptr = (uint16)v; + }; + if (resolution.MaxValue() < 8) + { + // For smaller meshes use more optimized format (gives small perf and memory gain but introduces artifacts on larger meshes) + format = PixelFormat::R8_UNorm; + formatStride = 1; + formatMaxValue = MAX_uint8; + formatRead = [](void* ptr) + { + return (float)*(uint8*)ptr; + }; + formatWrite = [](void* ptr, float v) + { + *(uint8*)ptr = (uint8)v; + }; + } + if (SDF.Texture->Init(GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount))) { SAFE_DELETE_GPU_RESOURCE(SDF.Texture); return true; @@ -613,17 +640,18 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) scene.BuildBVH(); // Allocate memory for the distant field - Array voxels; - voxels.Resize(resolution.X * resolution.Y * resolution.Z); + const int32 voxelsSize = resolution.X * resolution.Y * resolution.Z * formatStride; + void* voxels = Allocator::Allocate(voxelsSize); Vector3 xyzToLocalMul = uvwToLocalMul / Vector3(resolution); Vector3 xyzToLocalAdd = uvwToLocalAdd; + const Vector2 encodeMAD(0.5f / SDF.MaxDistance * formatMaxValue, 0.5f * formatMaxValue); + const Vector2 decodeMAD(2.0f * SDF.MaxDistance / formatMaxValue, -SDF.MaxDistance); - // TODO: use optimized sparse storage for SDF data as hierarchical bricks + // TODO: use optimized sparse storage for SDF data as hierarchical bricks as in papers below: // https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf // http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf - // then use R8 format and brick size of 8x8x8 // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel const int32 sampleCount = 12; @@ -640,10 +668,9 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) for (int32 i = 6; i < sampleCount; i++) sampleDirections.Get()[i] = rand.GetUnitVector(); } - Function sdfJob = [this, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd](int32 z) + Function sdfJob = [this, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) { PROFILE_CPU_NAMED("Model SDF Job"); - const float encodeScale = 1.0f / SDF.MaxDistance; float hitDistance; Vector3 hitNormal, hitPoint; Triangle hitTriangle; @@ -682,7 +709,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) distance *= -1; } const int32 xAddress = x + yAddress; - voxels.Get()[xAddress] = Float16Compressor::Compress(distance * encodeScale); + formatWrite((byte*)voxels + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); } } }; @@ -690,24 +717,24 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) // Upload data to the GPU BytesContainer data; - data.Link((byte*)voxels.Get(), voxels.Count() * sizeof(Half)); - auto task = SDF.Texture->UploadMipMapAsync(data, 0, resolution.X * sizeof(Half), data.Length(), true); + data.Link((byte*)voxels, voxelsSize); + auto task = SDF.Texture->UploadMipMapAsync(data, 0, resolution.X * formatStride, data.Length(), true); if (task) task->Start(); // Generate mip maps - Array voxelsMip; + void* voxelsMip = nullptr; for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++) { Int3 resolutionMip = Int3::Max(resolution / 2, Int3::One); - voxelsMip.Resize(resolutionMip.X * resolutionMip.Y * resolutionMip.Z); + const int32 voxelsMipSize = resolutionMip.X * resolutionMip.Y * resolutionMip.Z * formatStride; + if (voxelsMip == nullptr) + voxelsMip = Allocator::Allocate(voxelsMipSize); // Downscale mip - Function mipJob = [this, &voxelsMip, &voxels, &resolution, &resolutionMip](int32 z) + Function mipJob = [this, &voxelsMip, &voxels, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z) { PROFILE_CPU_NAMED("Model SDF Mip Job"); - const float encodeScale = 1.0f / SDF.MaxDistance; - const float decodeScale = SDF.MaxDistance; const int32 zAddress = resolutionMip.Y * resolutionMip.X * z; for (int32 y = 0; y < resolutionMip.Y; y++) { @@ -726,7 +753,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) for (int32 dx = 0; dx < 2; dx++) { const int32 dxAddress = (x * 2 + dx) + dyAddress; - const float d = Float16Compressor::Decompress(voxels.Get()[dxAddress]) * decodeScale; + const float d = formatRead((byte*)voxels + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y; distance += d; } } @@ -734,23 +761,26 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) distance *= 1.0f / 8.0f; const int32 xAddress = x + yAddress; - voxelsMip.Get()[xAddress] = Float16Compressor::Compress(distance * encodeScale); + formatWrite((byte*)voxelsMip + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); } } }; JobSystem::Execute(mipJob, resolutionMip.Z); // Upload to the GPU - data.Link((byte*)voxelsMip.Get(), voxelsMip.Count() * sizeof(Half)); - task = SDF.Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * sizeof(Half), data.Length(), true); + data.Link((byte*)voxelsMip, voxelsMipSize); + task = SDF.Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * formatStride, data.Length(), true); if (task) task->Start(); // Go down - voxelsMip.Swap(voxels); + Swap(voxelsMip, voxels); resolution = resolutionMip; } + Allocator::Free(voxelsMip); + Allocator::Free(voxels); + #if !BUILD_RELEASE auto endTime = Platform::GetTimeSeconds(); LOG(Info, "Generated SDF {}x{}x{} ({} kB) in {}ms for {}", resolution.X, resolution.Y, resolution.Z, SDF.Texture->GetMemoryUsage() / 1024, (int32)((endTime - startTime) * 1000.0), GetPath()); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index e919fa76e..d1f63f3d9 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -38,9 +38,9 @@ PACK_STRUCT(struct ModelRasterizeData Vector3 VolumeToUVWMul; float MipOffset; Vector3 VolumeToUVWAdd; - float MaxDistance; + float DecodeMul; Vector3 VolumeLocalBoundsExtent; - float Padding0; + float DecodeAdd; }); PACK_STRUCT(struct Data @@ -379,7 +379,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex modelData.VolumeToUVWMul = volumeToUVWMul; modelData.VolumeToUVWAdd = volumeToUVWAdd; modelData.MipOffset = (float)mipLevelIndex; - modelData.MaxDistance = sdf.MaxDistance; + modelData.DecodeMul = 2.0f * sdf.MaxDistance; + modelData.DecodeAdd = -sdf.MaxDistance; _modelsBuffer->Write(modelData); _modelsTextures.Add(sdf.Texture->ViewVolume()); diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index 058e13749..8b4d4d69f 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -15,9 +15,9 @@ struct ModelRasterizeData float3 VolumeToUVWMul; float MipOffset; float3 VolumeToUVWAdd; - float MaxDistance; + float DecodeMul; float3 VolumeLocalBoundsExtent; - float Padding0; + float DecodeAdd; }; META_CB_BEGIN(0, Data) @@ -73,7 +73,7 @@ float DistanceToModelSDF(float minDistance, ModelRasterizeData modelData, Textur BRANCH if (minDistance <= distanceToVolume) return distanceToVolume; // Sample SDF - float volumeDistance = modelSDFTex.SampleLevel(SamplerLinearClamp, volumeUV, modelData.MipOffset).x * modelData.MaxDistance; + float volumeDistance = modelSDFTex.SampleLevel(SamplerLinearClamp, volumeUV, modelData.MipOffset).x * modelData.DecodeMul + modelData.DecodeAdd; // Combine distance to the volume with distance to the surface inside the model float result = CombineDistanceToSDF(volumeDistance, distanceToVolume); From 885d2f0771fdaf8608680fc4a00c32fe4c6668df Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 17:15:21 +0100 Subject: [PATCH 037/144] Move Static Model rasterization into Global SDF code --- Source/Engine/Level/Actors/StaticModel.cpp | 87 ++++----- .../Renderer/GlobalSignDistanceFieldPass.cpp | 165 +++++++++--------- .../Renderer/GlobalSignDistanceFieldPass.h | 7 + 3 files changed, 133 insertions(+), 126 deletions(-) diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 6e50fb174..e3b760912 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -9,6 +9,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Utilities/Encryption.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -209,62 +210,66 @@ bool StaticModel::HasContentLoaded() const void StaticModel::Draw(RenderContext& renderContext) { + const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!Model || !Model->IsLoaded() || !Model->CanBeRendered() || drawModes == DrawPass::None) + return; if (renderContext.View.Pass == DrawPass::GlobalSDF) - return; // TODO: Static Model rendering to Global SDF + { + if (!Model->SDF.Texture) + Model->GenerateSDF(); + GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(Model->SDF, _world, _box); + return; + } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); - const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass); - if (Model && Model->IsLoaded() && drawModes != DrawPass::None) + // Flush vertex colors if need to + if (_vertexColorsDirty) { - // Flush vertex colors if need to - if (_vertexColorsDirty) + for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) { - for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) + auto& vertexColorsData = _vertexColorsData[lodIndex]; + auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex]; + if (vertexColorsData.HasItems()) { - auto& vertexColorsData = _vertexColorsData[lodIndex]; - auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex]; - if (vertexColorsData.HasItems()) + const uint32 size = vertexColorsData.Count() * sizeof(Color32); + if (!vertexColorsBuffer) + vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); + if (vertexColorsBuffer->GetSize() != size) { - const uint32 size = vertexColorsData.Count() * sizeof(Color32); - if (!vertexColorsBuffer) - vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); - if (vertexColorsBuffer->GetSize() != size) - { - if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) - return; - } - GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); - } - else - { - SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer); + if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) + return; } + GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); + } + else + { + SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer); } - _vertexColorsDirty = false; } + _vertexColorsDirty = false; + } #if USE_EDITOR - // Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving - if (!Editor::IsPlayMode) - _drawState.PrevWorld = _world; + // Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving + if (!Editor::IsPlayMode) + _drawState.PrevWorld = _world; #endif - Mesh::DrawInfo draw; - draw.Buffer = &Entries; - draw.World = &_world; - draw.DrawState = &_drawState; - draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - draw.LightmapUVs = &Lightmap.UVsArea; - draw.Flags = _staticFlags; - draw.DrawModes = drawModes; - draw.Bounds = _sphere; - draw.PerInstanceRandom = GetPerInstanceRandom(); - draw.LODBias = _lodBias; - draw.ForcedLOD = _forcedLod; - draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; + Mesh::DrawInfo draw; + draw.Buffer = &Entries; + draw.World = &_world; + draw.DrawState = &_drawState; + draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + draw.LightmapUVs = &Lightmap.UVsArea; + draw.Flags = _staticFlags; + draw.DrawModes = drawModes; + draw.Bounds = _sphere; + draw.PerInstanceRandom = GetPerInstanceRandom(); + draw.LODBias = _lodBias; + draw.ForcedLOD = _forcedLod; + draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr; - Model->Draw(renderContext, draw); - } + Model->Draw(renderContext, draw); GEOMETRY_DRAW_STATE_EVENT_END(_drawState, _world); } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index d1f63f3d9..4442e5b15 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -314,9 +314,11 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex _modelsBuffer->Clear(); _modelsTextures.Clear(); } - int32 modelsBufferCount = 0; // Draw all objects from all scenes into the cascade + _modelsBufferCount = 0; + _voxelSize = voxelSize; + _cascadeBounds = cascadeBounds; for (auto* scene : renderContext.List->Scenes) { // TODO: optimize for moving camera (copy sdf) @@ -326,90 +328,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) { e.Actor->Draw(renderContext); - - // TODO: move to be properly implemented per object type - auto staticModel = ScriptingObject::Cast(e.Actor); - if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered() && staticModel->DrawModes & DrawPass::GlobalSDF) - { - // okay so firstly we need SDF for this model - // TODO: implement SDF generation on model import - if (!staticModel->Model->SDF.Texture) - staticModel->Model->GenerateSDF(); - ModelBase::SDFData& sdf = staticModel->Model->SDF; - if (sdf.Texture) - { - // Setup object data - BoundingBox objectBounds = staticModel->GetBox(); - BoundingBox objectBoundsCascade; - const float objectMargin = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; - Vector3::Clamp(objectBounds.Minimum - objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Minimum); - Vector3::Subtract(objectBoundsCascade.Minimum, cascadeBounds.Minimum, objectBoundsCascade.Minimum); - Vector3::Clamp(objectBounds.Maximum + objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Maximum); - Vector3::Subtract(objectBoundsCascade.Maximum, cascadeBounds.Minimum, objectBoundsCascade.Maximum); - Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); - Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); - Matrix localToWorld, worldToLocal, volumeToWorld; - staticModel->GetWorld(&localToWorld); - Matrix::Invert(localToWorld, worldToLocal); - BoundingBox localVolumeBounds(sdf.LocalBoundsMin, sdf.LocalBoundsMax); - Vector3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; - Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); - Matrix::Invert(worldToVolume, volumeToWorld); - - // Pick the SDF mip for the cascade - int32 mipLevelIndex = 1; - float worldUnitsPerVoxel = sdf.WorldUnitsPerVoxel * localToWorld.GetScaleVector().MaxValue() * 2; - while (voxelSize > worldUnitsPerVoxel && mipLevelIndex < sdf.Texture->MipLevels()) - { - mipLevelIndex++; - worldUnitsPerVoxel *= 2.0f; - } - mipLevelIndex--; - - // Volume -> Local -> UVW - Vector3 volumeToUVWMul = sdf.LocalToUVWMul; - Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; - - // Add model data for the GPU buffer - int32 modelIndex = modelsBufferCount++; - ModelRasterizeData modelData; - Matrix::Transpose(worldToVolume, modelData.WorldToVolume); - Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld); - modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; - modelData.VolumeToUVWMul = volumeToUVWMul; - modelData.VolumeToUVWAdd = volumeToUVWAdd; - modelData.MipOffset = (float)mipLevelIndex; - modelData.DecodeMul = 2.0f * sdf.MaxDistance; - modelData.DecodeAdd = -sdf.MaxDistance; - _modelsBuffer->Write(modelData); - _modelsTextures.Add(sdf.Texture->ViewVolume()); - - // Inject object into the intersecting cascade chunks - RasterizeChunkKey key; - for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) - { - for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) - { - for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) - { - key.Layer = 0; - key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; - RasterizeChunk* chunk = &chunks[key]; - - // Move to the next layer if chunk has overflown - while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) - { - key.Layer++; - key.Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; - chunk = &chunks[key]; - } - - chunk->Models[chunk->ModelsCount++] = modelIndex; - } - } - } - } - } } } } @@ -594,3 +512,80 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->SetViewportAndScissors(outputSize.X, outputSize.Y); context->DrawFullscreenTriangle(); } + +void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) +{ + if (!sdf.Texture) + return; + + // Setup object data + BoundingBox objectBoundsCascade; + const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + Matrix worldToLocal, volumeToWorld; + Matrix::Invert(localToWorld, worldToLocal); + BoundingBox localVolumeBounds(sdf.LocalBoundsMin, sdf.LocalBoundsMax); + Vector3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f; + Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent)); + Matrix::Invert(worldToVolume, volumeToWorld); + + // Pick the SDF mip for the cascade + int32 mipLevelIndex = 1; + float worldUnitsPerVoxel = sdf.WorldUnitsPerVoxel * localToWorld.GetScaleVector().MaxValue() * 2; + while (_voxelSize > worldUnitsPerVoxel && mipLevelIndex < sdf.Texture->MipLevels()) + { + mipLevelIndex++; + worldUnitsPerVoxel *= 2.0f; + } + mipLevelIndex--; + + // Volume -> Local -> UVW + Vector3 volumeToUVWMul = sdf.LocalToUVWMul; + Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; + + // Add model data for the GPU buffer + int32 modelIndex = _modelsBufferCount++; + ModelRasterizeData modelData; + Matrix::Transpose(worldToVolume, modelData.WorldToVolume); + Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld); + modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; + modelData.VolumeToUVWMul = volumeToUVWMul; + modelData.VolumeToUVWAdd = volumeToUVWAdd; + modelData.MipOffset = (float)mipLevelIndex; + modelData.DecodeMul = 2.0f * sdf.MaxDistance; + modelData.DecodeAdd = -sdf.MaxDistance; + _modelsBuffer->Write(modelData); + _modelsTextures.Add(sdf.Texture->ViewVolume()); + + // Inject object into the intersecting cascade chunks + RasterizeChunkKey key; + auto& chunks = ChunksCache; + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Layer = 0; + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + RasterizeChunk* chunk = &chunks[key]; + + // Move to the next layer if chunk has overflown + while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) + { + key.Layer++; + key.Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + chunk = &chunks[key]; + } + + chunk->Models[chunk->ModelsCount++] = modelIndex; + } + } + } +} diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index e6eac2ab2..b4a9cf984 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -37,8 +37,13 @@ private: GPUShaderProgramCS* _csGenerateMip1 = nullptr; GPUConstantBuffer* _cb0 = nullptr; GPUConstantBuffer* _cb1 = nullptr; + + // Rasterization cache class DynamicStructuredBuffer* _modelsBuffer = nullptr; Array _modelsTextures; + int32 _modelsBufferCount; + float _voxelSize; + BoundingBox _cascadeBounds; public: /// @@ -58,6 +63,8 @@ public: /// The output buffer. void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + void RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); + private: #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj); From 832a4bf86abb4fa9e7a3b11ac9f57183874cee32 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 17:16:15 +0100 Subject: [PATCH 038/144] Add utility `Copy` option for various labels in assets editors --- .../CustomEditors/Elements/LabelElement.cs | 34 +++++++++++++++++-- .../Windows/Assets/CubeTextureWindow.cs | 2 +- Source/Editor/Windows/Assets/ModelWindow.cs | 14 ++++---- .../Windows/Assets/SkinnedModelWindow.cs | 4 +-- Source/Editor/Windows/Assets/TextureWindow.cs | 4 +-- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Source/Editor/CustomEditors/Elements/LabelElement.cs b/Source/Editor/CustomEditors/Elements/LabelElement.cs index 3c1ee8ff7..061b1adff 100644 --- a/Source/Editor/CustomEditors/Elements/LabelElement.cs +++ b/Source/Editor/CustomEditors/Elements/LabelElement.cs @@ -1,5 +1,8 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; @@ -11,23 +14,48 @@ namespace FlaxEditor.CustomEditors.Elements /// public class LabelElement : LayoutElement { + private Action _customContextualOptions; + /// /// The label. /// - public readonly Label Label; + public readonly ClickableLabel Label; /// /// Initializes a new instance of the class. /// public LabelElement() { - Label = new Label(0, 0, 100, 18) + Label = new ClickableLabel { - HorizontalAlignment = TextAlignment.Near + Size = new Vector2(100, 18), + HorizontalAlignment = TextAlignment.Near, }; // TODO: auto height for label } + /// + /// Adds a simple context menu with utility to copy label text. Can be extended with more options. + /// + public LabelElement AddCopyContextMenu(Action customOptions = null) + { + Label.RightClick += OnRightClick; + return this; + } + + private void OnRightClick() + { + var menu = new ContextMenu(); + menu.AddButton("Copy text").Clicked += OnCopyText; + _customContextualOptions?.Invoke(menu); + menu.Show(Label, Label.PointFromScreen(Input.MouseScreenPosition)); + } + + private void OnCopyText() + { + Clipboard.Text = Label.Text; + } + /// public override Control Control => Label; } diff --git a/Source/Editor/Windows/Assets/CubeTextureWindow.cs b/Source/Editor/Windows/Assets/CubeTextureWindow.cs index 278dd6f3a..845b40ee1 100644 --- a/Source/Editor/Windows/Assets/CubeTextureWindow.cs +++ b/Source/Editor/Windows/Assets/CubeTextureWindow.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.Windows.Assets var group = layout.Group("General"); group.Label("Format: " + texture.Format); - group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); + group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); group.Label("Mip levels: " + texture.MipLevels); group.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); } diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 28fbe9e98..f47901038 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -191,9 +191,9 @@ namespace FlaxEditor.Windows.Assets minScreenSize.FloatValue.MinValue = 0.0f; minScreenSize.FloatValue.MaxValue = 1.0f; minScreenSize.FloatValue.Value = proxy.Asset.MinScreenSize; - minScreenSize.FloatValue.ValueChanged += () => + minScreenSize.FloatValue.BoxValueChanged += b => { - proxy.Asset.MinScreenSize = minScreenSize.FloatValue.Value; + proxy.Asset.MinScreenSize = b.Value; proxy.Window.MarkAsEdited(); }; } @@ -218,15 +218,15 @@ namespace FlaxEditor.Windows.Assets vertexCount += mesh.VertexCount; } - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)); - group.Label("Size: " + lod.Box.Size); + group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); + group.Label("Size: " + lod.Box.Size).AddCopyContextMenu(); var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); screenSize.FloatValue.MinValue = 0.0f; screenSize.FloatValue.MaxValue = 10.0f; screenSize.FloatValue.Value = lod.ScreenSize; - screenSize.FloatValue.ValueChanged += () => + screenSize.FloatValue.BoxValueChanged += b => { - lod.ScreenSize = screenSize.FloatValue.Value; + lod.ScreenSize = b.Value; proxy.Window.MarkAsEdited(); }; @@ -234,7 +234,7 @@ namespace FlaxEditor.Windows.Assets for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})"); + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); // Material Slot var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 4927de680..069e6aece 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -219,7 +219,7 @@ namespace FlaxEditor.Windows.Assets vertexCount += mesh.VertexCount; } - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)); + group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); group.Label("Size: " + lod.Box.Size); var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); screenSize.FloatValue.MinValue = 0.0f; @@ -235,7 +235,7 @@ namespace FlaxEditor.Windows.Assets for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})"); + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); // Material Slot var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 5918de2a9..84c734365 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -36,9 +36,9 @@ namespace FlaxEditor.Windows.Assets // Texture info var general = layout.Group("General"); general.Label("Format: " + texture.Format); - general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); + general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); general.Label("Mip levels: " + texture.MipLevels); - general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); + general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)).AddCopyContextMenu(); // Texture properties var properties = layout.Group("Properties"); From 891d65dc876be6b79d825f57bccf96d969f00403 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 18:10:47 +0100 Subject: [PATCH 039/144] Add utility to editor CodeDocs for tooltips from C# types and members --- .../SourceCodeEditing/CodeDocsModule.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 3f8d8012f..7042dfb92 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -27,6 +27,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing { } + /// + /// Gets the tooltip text for the type. + /// + /// The type. + /// The type attributes. Optional, if null type attributes will be used. + /// The documentation tooltip. + public string GetTooltip(Type type, object[] attributes = null) + { + return GetTooltip(new ScriptType(type), attributes); + } + /// /// Gets the tooltip text for the type. /// @@ -62,6 +73,19 @@ namespace FlaxEditor.Modules.SourceCodeEditing return text; } + /// + /// Gets the tooltip text for the type member. + /// + /// The type. + /// The member name. + /// The member attributes. Optional, if null member attributes will be used. + /// The documentation tooltip. + public string GetTooltip(Type type, string memberName, object[] attributes = null) + { + var member = new ScriptType(type).GetMember(memberName, MemberTypes.All, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + return GetTooltip(member, attributes); + } + /// /// Gets the tooltip text for the type member. /// From d5060e9067679f61f2add1522e1a69bd841c5b9b Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 22 Mar 2022 18:11:04 +0100 Subject: [PATCH 040/144] Missing change --- Source/Editor/CustomEditors/Elements/LabelElement.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/CustomEditors/Elements/LabelElement.cs b/Source/Editor/CustomEditors/Elements/LabelElement.cs index 061b1adff..5fab106d5 100644 --- a/Source/Editor/CustomEditors/Elements/LabelElement.cs +++ b/Source/Editor/CustomEditors/Elements/LabelElement.cs @@ -40,6 +40,7 @@ namespace FlaxEditor.CustomEditors.Elements public LabelElement AddCopyContextMenu(Action customOptions = null) { Label.RightClick += OnRightClick; + _customContextualOptions = customOptions; return this; } From b08d2001fd6f0074cb5f05b9f8118e74ff1b2039 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 11:32:02 +0100 Subject: [PATCH 041/144] Add Model SDF generation utilities --- .../Editor/Content/Import/ModelImportEntry.cs | 37 +- .../CustomEditors/Elements/ButtonElement.cs | 20 -- .../CustomEditors/LayoutElementsContainer.cs | 13 +- .../Editor/Managed/ManagedEditor.Internal.cpp | 36 +- Source/Editor/Windows/Assets/ModelWindow.cs | 97 ++++-- Source/Engine/Content/Assets/Model.cpp | 316 ++++++------------ Source/Engine/Content/Assets/Model.h | 8 +- Source/Engine/Content/Assets/ModelBase.h | 12 +- Source/Engine/ContentImporters/ImportModel.h | 6 +- .../ContentImporters/ImportModelFile.cpp | 24 +- Source/Engine/Core/Config/GraphicsSettings.h | 24 +- Source/Engine/Graphics/Models/ModelData.cpp | 15 + Source/Engine/Graphics/Models/ModelData.h | 5 + Source/Engine/Level/Actors/StaticModel.cpp | 2 - .../ModelTool/MeshAccelerationStructure.cpp | 29 ++ .../ModelTool/MeshAccelerationStructure.h | 4 + .../Engine/Tools/ModelTool/ModelTool.Build.cs | 1 + .../Tools/ModelTool/ModelTool.Options.cpp | 6 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 305 +++++++++++++++++ Source/Engine/Tools/ModelTool/ModelTool.h | 51 ++- Source/Engine/Tools/ModelTool/SpatialSort.cpp | 2 +- Source/Engine/Tools/ModelTool/SpatialSort.h | 2 +- .../ModelTool/VertexTriangleAdjacency.cpp | 2 +- .../Tools/ModelTool/VertexTriangleAdjacency.h | 2 +- 24 files changed, 696 insertions(+), 323 deletions(-) diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 23a15157a..207e133ea 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -291,6 +291,18 @@ namespace FlaxEditor.Content.Import [EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")] public bool RestoreMaterialsOnReimport { get; set; } = true; + /// + /// If checked, enables generation of Signed Distance Field (SDF). + /// + [EditorOrder(1500), DefaultValue(false), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] + public bool GenerateSDF { get; set; } = false; + + /// + /// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance. + /// + [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] + public float SDFResolution { get; set; } = 1.0f; + /// /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. /// @@ -303,6 +315,8 @@ namespace FlaxEditor.Content.Import [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting"), Tooltip("The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.")] public int ObjectIndex { get; set; } = -1; + private bool Type_Model => Type == ModelType.Model; + [StructLayout(LayoutKind.Sequential)] internal struct InternalOptions { @@ -350,6 +364,10 @@ namespace FlaxEditor.Content.Import public byte ImportTextures; public byte RestoreMaterialsOnReimport; + // SDF + public byte GenerateSDF; + public float SDFResolution; + // Splitting public byte SplitObjects; public int ObjectIndex; @@ -392,6 +410,8 @@ namespace FlaxEditor.Content.Import ImportMaterials = (byte)(ImportMaterials ? 1 : 0), ImportTextures = (byte)(ImportTextures ? 1 : 0), RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0), + GenerateSDF = (byte)(GenerateSDF ? 1 : 0), + SDFResolution = SDFResolution, SplitObjects = (byte)(SplitObjects ? 1 : 0), ObjectIndex = ObjectIndex, }; @@ -431,25 +451,22 @@ namespace FlaxEditor.Content.Import ImportMaterials = options.ImportMaterials != 0; ImportTextures = options.ImportTextures != 0; RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0; + GenerateSDF = options.GenerateSDF != 0; + SDFResolution = options.SDFResolution; SplitObjects = options.SplitObjects != 0; ObjectIndex = options.ObjectIndex; } /// - /// Tries the restore the asset import options from the target resource file. + /// Tries the restore the asset import options from the target resource file. Applies the project default options too. /// /// The options. /// The asset path. /// True settings has been restored, otherwise false. - public static bool TryRestore(ref ModelImportSettings options, string assetPath) + public static void TryRestore(ref ModelImportSettings options, string assetPath) { - if (ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions)) - { - // Restore settings - options.FromInternal(ref internalOptions); - return true; - } - return false; + ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions); + options.FromInternal(ref internalOptions); } } @@ -495,7 +512,7 @@ namespace FlaxEditor.Content.Import #region Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); + internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); #endregion } diff --git a/Source/Editor/CustomEditors/Elements/ButtonElement.cs b/Source/Editor/CustomEditors/Elements/ButtonElement.cs index 69340238d..f7485e542 100644 --- a/Source/Editor/CustomEditors/Elements/ButtonElement.cs +++ b/Source/Editor/CustomEditors/Elements/ButtonElement.cs @@ -16,26 +16,6 @@ namespace FlaxEditor.CustomEditors.Elements /// public readonly Button Button = new Button(); - /// - /// Initializes the element. - /// - /// The text. - public void Init(string text) - { - Button.Text = text; - } - - /// - /// Initializes the element. - /// - /// The text. - /// The color. - public void Init(string text, Color color) - { - Button.Text = text; - Button.SetColors(color); - } - /// public override Control Control => Button; } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 0c9b01942..894e9defd 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -133,11 +133,13 @@ namespace FlaxEditor.CustomEditors /// Adds new button element. /// /// The text. + /// The tooltip text. /// The created element. - public ButtonElement Button(string text) + public ButtonElement Button(string text, string tooltip = null) { var element = new ButtonElement(); - element.Init(text); + element.Button.Text = text; + element.Button.TooltipText = tooltip; OnAddElement(element); return element; } @@ -147,11 +149,14 @@ namespace FlaxEditor.CustomEditors /// /// The text. /// The color. + /// The tooltip text. /// The created element. - public ButtonElement Button(string text, Color color) + public ButtonElement Button(string text, Color color, string tooltip = null) { ButtonElement element = new ButtonElement(); - element.Init(text, color); + element.Button.Text = text; + element.Button.TooltipText = tooltip; + element.Button.SetColors(color); OnAddElement(element); return element; } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 248aa757f..acfe71ad6 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -29,6 +29,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Core/Config/GameSettings.h" +#include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Core/Cache.h" #include "Engine/CSG/CSGBuilder.h" #include "Engine/Debug/DebugLog.h" @@ -196,6 +197,10 @@ struct InternalModelOptions byte ImportTextures; byte RestoreMaterialsOnReimport; + // SDF + byte GenerateSDF; + float SDFResolution; + // Splitting byte SplitObjects; int32 ObjectIndex; @@ -235,6 +240,8 @@ struct InternalModelOptions to->ImportMaterials = from->ImportMaterials; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; + to->GenerateSDF = from->GenerateSDF; + to->SDFResolution = from->SDFResolution; to->SplitObjects = from->SplitObjects; to->ObjectIndex = from->ObjectIndex; } @@ -274,6 +281,8 @@ struct InternalModelOptions to->ImportMaterials = from->ImportMaterials; to->ImportTextures = from->ImportTextures; to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport; + to->GenerateSDF = from->GenerateSDF; + to->SDFResolution = from->SDFResolution; to->SplitObjects = from->SplitObjects; to->ObjectIndex = from->ObjectIndex; } @@ -626,22 +635,23 @@ public: return false; } - static bool GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result) + static void GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result) { + // Initialize defaults + ImportModelFile::Options options; + if (const auto* graphicsSettings = GraphicsSettings::Get()) + { + options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; + } + + // Get options from model String path; MUtils::ToString(pathObj, path); FileSystem::NormalizePath(path); + ImportModelFile::TryGetImportOptions(path, options); - ImportModelFile::Options options; - if (ImportModelFile::TryGetImportOptions(path, options)) - { - // Convert into managed storage - InternalModelOptions::Convert(&options, result); - - return true; - } - - return false; + // Convert into managed storage + InternalModelOptions::Convert(&options, result); } static bool GetAudioImportOptions(MonoString* pathObj, InternalAudioOptions* result) @@ -852,7 +862,7 @@ public: continue; switch (e.Type) { - // Keyboard events + // Keyboard events case InputDevice::EventType::Char: window->OnCharInput(e.CharData.Char); break; @@ -862,7 +872,7 @@ public: case InputDevice::EventType::KeyUp: window->OnKeyUp(e.KeyData.Key); break; - // Mouse events + // Mouse events case InputDevice::EventType::MouseDown: window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index f47901038..d493cd0e6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Windows.Assets PreviewLight.ShadowsDistance = 2000.0f; Task.ViewFlags |= ViewFlags.Shadows; } - + public override void Draw() { base.Draw(); @@ -75,10 +75,7 @@ namespace FlaxEditor.Windows.Assets base.OnClean(); } - /// - /// Updates the highlight/isolate effects on UI. - /// - public void UpdateEffectsOnUI() + private void UpdateEffectsOnUI() { Window._skipEffectsGuiEvents = true; @@ -97,10 +94,7 @@ namespace FlaxEditor.Windows.Assets Window._skipEffectsGuiEvents = false; } - /// - /// Updates the material slots UI parts. Should be called after material slot rename. - /// - public void UpdateMaterialSlotsUI() + private void UpdateMaterialSlotsUI() { Window._skipEffectsGuiEvents = true; @@ -123,12 +117,7 @@ namespace FlaxEditor.Windows.Assets Window._skipEffectsGuiEvents = false; } - /// - /// Sets the material slot index to the mesh. - /// - /// The mesh. - /// New index of the material slot to use. - public void SetMaterialSlot(Mesh mesh, int newSlotIndex) + private void SetMaterialSlot(Mesh mesh, int newSlotIndex) { if (Window._skipEffectsGuiEvents) return; @@ -139,11 +128,7 @@ namespace FlaxEditor.Windows.Assets Window.MarkAsEdited(); } - /// - /// Sets the material slot to isolate. - /// - /// The mesh. - public void SetIsolate(Mesh mesh) + private void SetIsolate(Mesh mesh) { if (Window._skipEffectsGuiEvents) return; @@ -153,11 +138,7 @@ namespace FlaxEditor.Windows.Assets UpdateEffectsOnUI(); } - /// - /// Sets the material slot index to highlight. - /// - /// The mesh. - public void SetHighlight(Mesh mesh) + private void SetHighlight(Mesh mesh) { if (Window._skipEffectsGuiEvents) return; @@ -169,6 +150,8 @@ namespace FlaxEditor.Windows.Assets private class ProxyEditor : ProxyEditorBase { + private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex; + public override void Initialize(LayoutElementsContainer layout) { var proxy = (MeshesPropertiesProxy)Values[0]; @@ -198,6 +181,46 @@ namespace FlaxEditor.Windows.Assets }; } + // SDF + { + var group = layout.Group("SDF"); + + var sdf = proxy.Asset.SDF; + if (sdf.Texture != null) + { + var size = sdf.Texture.Size3; + group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu(); + } + else + { + group.Label("No SDF"); + } + + var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelImportSettings), nameof(ModelImportSettings.SDFResolution))); + resolution.FloatValue.MinValue = 0.0001f; + resolution.FloatValue.MaxValue = 100.0f; + resolution.FloatValue.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; + resolution.FloatValue.BoxValueChanged += b => { proxy.Window._importSettings.SDFResolution = b.Value; }; + + var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); + lodIndex.IntValue.MinValue = 0; + lodIndex.IntValue.MaxValue = lods.Length - 1; + lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6; + _sdfModelLodIndex = lodIndex; + + var buttons = group.CustomContainer(); + var gridControl = buttons.CustomControl; + gridControl.ClipChildren = false; + gridControl.Height = Button.DefaultHeight; + gridControl.SlotsHorizontally = 2; + gridControl.SlotsVertically = 1; + var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button; + rebuildButton.Clicked += OnRebuildSDF; + var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button; + removeButton.Enabled = sdf.Texture != null; + removeButton.Clicked += OnRemoveSDF; + } + // Group per LOD for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) { @@ -260,6 +283,22 @@ namespace FlaxEditor.Windows.Assets proxy.UpdateMaterialSlotsUI(); } + private void OnRebuildSDF() + { + var proxy = (MeshesPropertiesProxy)Values[0]; + proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value); + proxy.Window.MarkAsEdited(); + Presenter.BuildLayoutOnUpdate(); + } + + private void OnRemoveSDF() + { + var proxy = (MeshesPropertiesProxy)Values[0]; + proxy.Asset.SetSDF(new ModelBase.SDFData()); + proxy.Window.MarkAsEdited(); + Presenter.BuildLayoutOnUpdate(); + } + internal override void RefreshInternal() { // Skip updates when model is not loaded @@ -645,14 +684,14 @@ namespace FlaxEditor.Windows.Assets [CustomEditor(typeof(ProxyEditor))] private sealed class ImportPropertiesProxy : PropertiesProxyBase { - private ModelImportSettings ImportSettings = new ModelImportSettings(); + private ModelImportSettings ImportSettings; /// public override void OnLoad(ModelWindow window) { base.OnLoad(window); - ModelImportSettings.TryRestore(ref ImportSettings, window.Item.Path); + ImportSettings = window._importSettings; } public void Reimport() @@ -675,7 +714,7 @@ namespace FlaxEditor.Windows.Assets { var group = layout.Group("Import Settings"); - var importSettingsField = typeof(ImportPropertiesProxy).GetField("ImportSettings", BindingFlags.NonPublic | BindingFlags.Instance); + var importSettingsField = typeof(ImportPropertiesProxy).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance); var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; group.Object(importSettingsValues); @@ -730,6 +769,7 @@ namespace FlaxEditor.Windows.Assets private readonly ModelPreview _preview; private StaticModel _highlightActor; private MeshDataCache _meshData; + private ModelImportSettings _importSettings = new ModelImportSettings(); /// public ModelWindow(Editor editor, AssetItem item) @@ -868,6 +908,7 @@ namespace FlaxEditor.Windows.Assets { _refreshOnLODsLoaded = true; _preview.ViewportCamera.SetArcBallView(Asset.GetBox()); + ModelImportSettings.TryRestore(ref _importSettings, Item.Path); UpdateEffectsOnAsset(); // TODO: disable streaming for this model diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 07071a921..79a7d869c 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -2,14 +2,11 @@ #include "Model.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Math/Int3.h" -#include "Engine/Core/RandomStream.h" #include "Engine/Engine/Engine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -#include "Engine/Core/Math/Int2.h" #include "Engine/Debug/DebugDraw.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" @@ -18,10 +15,11 @@ #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" -#include "Engine/Threading/JobSystem.h" #include "Engine/Threading/Threading.h" +#include "Engine/Tools/ModelTool/ModelTool.h" #include "Engine/Tools/ModelTool/MeshAccelerationStructure.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" @@ -526,17 +524,50 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); } } + + // Download SDF data + if (SDF.Texture) + { + auto sdfChunk = GET_CHUNK(15); + if (sdfChunk == nullptr) + return true; + MemoryWriteStream sdfStream; + sdfStream.WriteInt32(1); // Version + ModelSDFHeader data(SDF, SDF.Texture->GetDescription()); + sdfStream.Write(&data); + TextureData sdfTextureData; + if (SDF.Texture->DownloadData(sdfTextureData)) + return true; + for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++) + { + auto& mip = sdfTextureData.Items[0].Mips[mipLevel]; + ModelSDFMip mipData(mipLevel, mip); + sdfStream.Write(&mipData); + sdfStream.Write(mip.Data.Get(), mip.Data.Length()); + } + sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); + } } else { - ASSERT(!IsVirtual()); - // Load all chunks with a mesh data for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) { if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) return true; } + + if (SDF.Texture) + { + // SDF data from file (only if has no cached texture data) + if (LoadChunk(15)) + return true; + } + else + { + // No SDF texture + ReleaseChunk(15); + } } // Set mesh header data @@ -565,8 +596,9 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) #endif -bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) +bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData) { + ScopeLock lock(Locker); if (!HasAnyLODInitialized()) return true; if (IsInMainThread() && IsVirtual()) @@ -575,219 +607,31 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex) LOG(Warning, "Cannot generate SDF for virtual models on a main thread."); return true; } - PROFILE_CPU(); - auto startTime = Platform::GetTimeSeconds(); - ScopeLock lock(Locker); - - // Setup SDF texture properties + cacheData &= Storage != nullptr; // Cache only if has storage linked lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1); - auto& lod = LODs[lodIndex]; - BoundingBox bounds = lod.GetBox(); - Vector3 size = bounds.GetSize(); - SDF.WorldUnitsPerVoxel = 10 / Math::Max(resolutionScale, 0.0001f); - Int3 resolution(Vector3::Ceil(Vector3::Clamp(size / SDF.WorldUnitsPerVoxel, 4, 256))); - Vector3 uvwToLocalMul = size; - Vector3 uvwToLocalAdd = bounds.Minimum; - SDF.LocalToUVWMul = Vector3::One / uvwToLocalMul; - SDF.LocalToUVWAdd = -uvwToLocalAdd / uvwToLocalMul; - SDF.MaxDistance = size.MaxValue(); - SDF.LocalBoundsMin = bounds.Minimum; - SDF.LocalBoundsMax = bounds.Maximum; - // TODO: maybe apply 1 voxel margin around the geometry? - const int32 maxMips = 3; - const int32 mipCount = Math::Min(MipLevelsCount(resolution.X, resolution.Y, resolution.Z, true), maxMips); - if (!SDF.Texture) - SDF.Texture = GPUTexture::New(); - PixelFormat format = PixelFormat::R16_UNorm; - int32 formatStride = 2; - float formatMaxValue = MAX_uint16; - typedef float (*FormatRead)(void* ptr); - typedef void (*FormatWrite)(void* ptr, float v); - FormatRead formatRead = [](void* ptr) - { - return (float)*(uint16*)ptr; - }; - FormatWrite formatWrite = [](void* ptr, float v) - { - *(uint16*)ptr = (uint16)v; - }; - if (resolution.MaxValue() < 8) - { - // For smaller meshes use more optimized format (gives small perf and memory gain but introduces artifacts on larger meshes) - format = PixelFormat::R8_UNorm; - formatStride = 1; - formatMaxValue = MAX_uint8; - formatRead = [](void* ptr) - { - return (float)*(uint8*)ptr; - }; - formatWrite = [](void* ptr, float v) - { - *(uint8*)ptr = (uint8)v; - }; - } - if (SDF.Texture->Init(GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount))) - { - SAFE_DELETE_GPU_RESOURCE(SDF.Texture); + + // Generate SDF + MemoryWriteStream sdfStream; + if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, cacheData ? &sdfStream : nullptr, GetPath())) return true; - } - // TODO: support GPU to generate model SDF on-the-fly (if called during rendering) + // Set asset data + if (cacheData) + GetOrCreateChunk(15)->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); - // Setup acceleration structure for fast ray tracing the mesh triangles - MeshAccelerationStructure scene; - scene.Add(this, lodIndex); - scene.BuildBVH(); - - // Allocate memory for the distant field - const int32 voxelsSize = resolution.X * resolution.Y * resolution.Z * formatStride; - void* voxels = Allocator::Allocate(voxelsSize); - Vector3 xyzToLocalMul = uvwToLocalMul / Vector3(resolution); - Vector3 xyzToLocalAdd = uvwToLocalAdd; - const Vector2 encodeMAD(0.5f / SDF.MaxDistance * formatMaxValue, 0.5f * formatMaxValue); - const Vector2 decodeMAD(2.0f * SDF.MaxDistance / formatMaxValue, -SDF.MaxDistance); - - // TODO: use optimized sparse storage for SDF data as hierarchical bricks as in papers below: - // https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf - // http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf - // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf - // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf - - // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel - const int32 sampleCount = 12; - Array sampleDirections; - sampleDirections.Resize(sampleCount); - { - RandomStream rand; - sampleDirections.Get()[0] = Vector3::Up; - sampleDirections.Get()[1] = Vector3::Down; - sampleDirections.Get()[2] = Vector3::Left; - sampleDirections.Get()[3] = Vector3::Right; - sampleDirections.Get()[4] = Vector3::Forward; - sampleDirections.Get()[5] = Vector3::Backward; - for (int32 i = 6; i < sampleCount; i++) - sampleDirections.Get()[i] = rand.GetUnitVector(); - } - Function sdfJob = [this, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) - { - PROFILE_CPU_NAMED("Model SDF Job"); - float hitDistance; - Vector3 hitNormal, hitPoint; - Triangle hitTriangle; - const int32 zAddress = resolution.Y * resolution.X * z; - for (int32 y = 0; y < resolution.Y; y++) - { - const int32 yAddress = resolution.X * y + zAddress; - for (int32 x = 0; x < resolution.X; x++) - { - float minDistance = SDF.MaxDistance; - Vector3 voxelPos = Vector3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd; - - // Point query to find the distance to the closest surface - scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle); - - // Raycast samples around voxel to count triangle backfaces hit - int32 hitBackCount = 0, hitCount = 0; - for (int32 sample = 0; sample < sampleDirections.Count(); sample++) - { - Ray sampleRay(voxelPos, sampleDirections[sample]); - if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle)) - { - hitCount++; - const bool backHit = Vector3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0; - if (backHit) - hitBackCount++; - } - } - - float distance = minDistance; - // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry - //if ((float)hitBackCount > )hitCount * 0.3f && hitCount != 0) - if ((float)hitBackCount > (float)sampleDirections.Count() * 0.6f && hitCount != 0) - { - // Voxel is inside the geometry so turn it into negative distance to the surface - distance *= -1; - } - const int32 xAddress = x + yAddress; - formatWrite((byte*)voxels + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); - } - } - }; - JobSystem::Execute(sdfJob, resolution.Z); - - // Upload data to the GPU - BytesContainer data; - data.Link((byte*)voxels, voxelsSize); - auto task = SDF.Texture->UploadMipMapAsync(data, 0, resolution.X * formatStride, data.Length(), true); - if (task) - task->Start(); - - // Generate mip maps - void* voxelsMip = nullptr; - for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++) - { - Int3 resolutionMip = Int3::Max(resolution / 2, Int3::One); - const int32 voxelsMipSize = resolutionMip.X * resolutionMip.Y * resolutionMip.Z * formatStride; - if (voxelsMip == nullptr) - voxelsMip = Allocator::Allocate(voxelsMipSize); - - // Downscale mip - Function mipJob = [this, &voxelsMip, &voxels, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z) - { - PROFILE_CPU_NAMED("Model SDF Mip Job"); - const int32 zAddress = resolutionMip.Y * resolutionMip.X * z; - for (int32 y = 0; y < resolutionMip.Y; y++) - { - const int32 yAddress = resolutionMip.X * y + zAddress; - for (int32 x = 0; x < resolutionMip.X; x++) - { - // Linear box filter around the voxel - // TODO: use min distance for nearby texels (texel distance + distance to texel) - float distance = 0; - for (int32 dz = 0; dz < 2; dz++) - { - const int32 dzAddress = (z * 2 + dz) * (resolution.Y * resolution.X); - for (int32 dy = 0; dy < 2; dy++) - { - const int32 dyAddress = (y * 2 + dy) * (resolution.X) + dzAddress; - for (int32 dx = 0; dx < 2; dx++) - { - const int32 dxAddress = (x * 2 + dx) + dyAddress; - const float d = formatRead((byte*)voxels + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y; - distance += d; - } - } - } - distance *= 1.0f / 8.0f; - - const int32 xAddress = x + yAddress; - formatWrite((byte*)voxelsMip + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); - } - } - }; - JobSystem::Execute(mipJob, resolutionMip.Z); - - // Upload to the GPU - data.Link((byte*)voxelsMip, voxelsMipSize); - task = SDF.Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * formatStride, data.Length(), true); - if (task) - task->Start(); - - // Go down - Swap(voxelsMip, voxels); - resolution = resolutionMip; - } - - Allocator::Free(voxelsMip); - Allocator::Free(voxels); - -#if !BUILD_RELEASE - auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Generated SDF {}x{}x{} ({} kB) in {}ms for {}", resolution.X, resolution.Y, resolution.Z, SDF.Texture->GetMemoryUsage() / 1024, (int32)((endTime - startTime) * 1000.0), GetPath()); -#endif return false; } +void Model::SetSDF(const SDFData& sdf) +{ + ScopeLock lock(Locker); + if (SDF.Texture == sdf.Texture) + return; + SAFE_DELETE_GPU_RESOURCE(SDF.Texture); + SDF = sdf; + ReleaseChunk(15); +} + bool Model::Init(const Span& meshesCountPerLod) { if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS) @@ -1039,6 +883,50 @@ Asset::LoadResult Model::load() } } + // Load SDF + auto chunk15 = GetChunk(15); + if (chunk15 && chunk15->IsLoaded()) + { + MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size()); + int32 version; + sdfStream.ReadInt32(&version); + switch (version) + { + case 1: + { + ModelSDFHeader data; + sdfStream.Read(&data); + if (!SDF.Texture) + SDF.Texture = GPUTexture::New(); + if (SDF.Texture->Init(GPUTextureDescription::New3D(data.Width, data.Height, data.Depth, data.Format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, data.MipLevels))) + return LoadResult::Failed; + SDF.LocalToUVWMul = data.LocalToUVWMul; + SDF.LocalToUVWAdd = data.LocalToUVWAdd; + SDF.WorldUnitsPerVoxel = data.WorldUnitsPerVoxel; + SDF.MaxDistance = data.MaxDistance; + SDF.LocalBoundsMin = data.LocalBoundsMin; + SDF.LocalBoundsMax = data.LocalBoundsMax; + SDF.ResolutionScale = data.ResolutionScale; + SDF.LOD = data.LOD; + for (int32 mipLevel = 0; mipLevel < data.MipLevels; mipLevel++) + { + ModelSDFMip mipData; + sdfStream.Read(&mipData); + void* mipBytes = sdfStream.Read(mipData.SlicePitch); + BytesContainer mipBytesData; + mipBytesData.Link((byte*)mipBytes, mipData.SlicePitch); + auto task = SDF.Texture->UploadMipMapAsync(mipBytesData, mipData.MipIndex, mipData.RowPitch, mipData.SlicePitch, false); + if (task) + task->Start(); + } + break; + } + default: + LOG(Warning, "Unknown SDF data version {0} in {1}", version, ToString()); + break; + } + } + #if BUILD_DEBUG || BUILD_DEVELOPMENT // Validate LODs for (int32 lodIndex = 1; lodIndex < LODs.Count(); lodIndex++) @@ -1092,7 +980,7 @@ bool Model::init(AssetInitData& initData) AssetChunksFlag Model::getChunksToPreload() const { // Note: we don't preload any LODs here because it's done by the Streaming Manager - return GET_CHUNK_FLAG(0); + return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); } void ModelBase::SetupMaterialSlots(int32 slotsCount) diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index a4d6711eb..d58fb469b 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -212,8 +212,14 @@ public: /// Can be called in async in case of SDF generation on a CPU (assuming model is not during rendering). /// The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead. /// The index of the LOD to use for the SDF building. + /// If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets. /// True if failed, otherwise false. - API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6); + API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true); + + /// + /// Sets set SDF data (releases the current one). + /// + API_FUNCTION() void SetSDF(const SDFData& sdf); private: diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 6f2065abf..44e8e8242 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -14,7 +14,7 @@ // Chunk 1: LOD0 // Chunk 2: LOD1 // .. -// +// Chunk 15: SDF #define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) class MeshBase; @@ -63,10 +63,20 @@ public: /// API_FIELD() Vector3 LocalBoundsMin; + /// + /// The SDF texture resolution scale used for building texture. + /// + API_FIELD() float ResolutionScale = 1.0f; + /// /// The bounding box of the SDF texture in the model local-space. /// API_FIELD() Vector3 LocalBoundsMax; + + /// + /// The model LOD index used for the building. + /// + API_FIELD() int32 LOD = 6; }; protected: diff --git a/Source/Engine/ContentImporters/ImportModel.h b/Source/Engine/ContentImporters/ImportModel.h index 9e4b521a6..6d49ca826 100644 --- a/Source/Engine/ContentImporters/ImportModel.h +++ b/Source/Engine/ContentImporters/ImportModel.h @@ -48,9 +48,9 @@ public: private: - static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData); - static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData); - static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData); + static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); + static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); + static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr); }; #endif diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp index 3c85084db..cd56f0025 100644 --- a/Source/Engine/ContentImporters/ImportModelFile.cpp +++ b/Source/Engine/ContentImporters/ImportModelFile.cpp @@ -150,13 +150,13 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context) switch (options.Type) { case ModelTool::ModelType::Model: - result = ImportModel(context, modelData); + result = ImportModel(context, modelData, &options); break; case ModelTool::ModelType::SkinnedModel: - result = ImportSkinnedModel(context, modelData); + result = ImportSkinnedModel(context, modelData, &options); break; case ModelTool::ModelType::Animation: - result = ImportAnimation(context, modelData); + result = ImportAnimation(context, modelData, &options); break; } if (result != CreateAssetResult::Ok) @@ -199,7 +199,7 @@ CreateAssetResult ImportModelFile::Create(CreateAssetContext& context) return ImportModel(context, modelData); } -CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(Model, Model::SerializedVersion); @@ -235,10 +235,22 @@ CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, Mode context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); } + // Generate SDF + if (options && options->GenerateSDF) + { + stream.SetPosition(0); + if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath)) + { + if (context.AllocateChunk(15)) + return CreateAssetResult::CannotAllocateChunk; + context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + } + } + return CreateAssetResult::Ok; } -CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion); @@ -277,7 +289,7 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex return CreateAssetResult::Ok; } -CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData) +CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options) { // Base IMPORT_SETUP(Animation, Animation::SerializedVersion); diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 6411d78e6..6e2074870 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -11,9 +11,9 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); public: - /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. /// @@ -62,8 +62,15 @@ public: API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; -public: +#if USE_EDITOR + /// + /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + bool GenerateSDFOnModelImport = false; +#endif +public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// @@ -71,15 +78,4 @@ public: // [SettingsBase] void Apply() override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(UseVSync); - DESERIALIZE(AAQuality); - DESERIALIZE(SSRQuality); - DESERIALIZE(SSAOQuality); - DESERIALIZE(VolumetricFogQuality); - DESERIALIZE(ShadowsQuality); - DESERIALIZE(ShadowMapsQuality); - DESERIALIZE(AllowCSMBlending); - } }; diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index d1e867c6c..cf7657039 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -600,6 +600,21 @@ bool MaterialSlotEntry::UsesProperties() const Normals.TextureIndex != -1; } +BoundingBox ModelLodData::GetBox() const +{ + if (Meshes.IsEmpty()) + return BoundingBox::Empty; + BoundingBox bounds; + Meshes[0]->CalculateBox(bounds); + for (int32 i = 1; i < Meshes.Count(); i++) + { + BoundingBox b; + Meshes[i]->CalculateBox(b); + BoundingBox::Merge(bounds, b, bounds); + } + return bounds; +} + void ModelData::CalculateLODsScreenSizes() { const float autoComputeLodPowerBase = 0.5f; diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 4d4733fb7..2a7164123 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -409,6 +409,11 @@ public: { Meshes.ClearDelete(); } + + /// + /// Gets the bounding box combined for all meshes in this model LOD. + /// + BoundingBox GetBox() const; }; /// diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index e3b760912..132202dac 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -215,8 +215,6 @@ void StaticModel::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) { - if (!Model->SDF.Texture) - Model->GenerateSDF(); GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(Model->SDF, _world, _box); return; } diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 1212b293e..b6fe6fa9d 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -5,6 +5,7 @@ #include "MeshAccelerationStructure.h" #include "Engine/Core/Math/Math.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Graphics/Models/ModelData.h" #include "Engine/Profiler/ProfilerCPU.h" void MeshAccelerationStructure::BuildBVH(int32 node, int32 maxLeafSize, Array& scratch) @@ -326,6 +327,34 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) } } +void MeshAccelerationStructure::Add(ModelData* modelData, int32 lodIndex, bool copy) +{ + PROFILE_CPU(); + lodIndex = Math::Clamp(lodIndex, 0, modelData->LODs.Count() - 1); + ModelLodData& lod = modelData->LODs[lodIndex]; + const int32 meshesStart = _meshes.Count(); + _meshes.AddDefault(lod.Meshes.Count()); + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + MeshData* mesh = lod.Meshes[i]; + auto& meshData = _meshes[meshesStart + i]; + meshData.Indices = mesh->Indices.Count(); + meshData.Vertices = mesh->Positions.Count(); + if (copy) + { + meshData.IndexBuffer.Copy((const byte*)mesh->Indices.Get(), meshData.Indices * sizeof(uint32)); + meshData.VertexBuffer.Copy((const byte*)mesh->Positions.Get(), meshData.Vertices * sizeof(Vector3)); + } + else + { + meshData.IndexBuffer.Link((const byte*)mesh->Indices.Get(), meshData.Indices * sizeof(uint32)); + meshData.VertexBuffer.Link((const byte*)mesh->Positions.Get(), meshData.Vertices * sizeof(Vector3)); + } + meshData.Use16BitIndexBuffer = false; + mesh->CalculateBox(meshData.Bounds); + } +} + void MeshAccelerationStructure::Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy) { auto& meshData = _meshes.AddOne(); diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h index 8e3684d63..1063b457c 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -10,6 +10,7 @@ #include "Engine/Core/Collections/Array.h" class Model; +class ModelData; /// /// Acceleration Structure utility for robust ray tracing mesh geometry with optimized data structure. @@ -59,6 +60,9 @@ public: // Adds the model geometry for the build to the structure. void Add(Model* model, int32 lodIndex); + // Adds the model geometry for the build to the structure. + void Add(ModelData* modelData, int32 lodIndex, bool copy = false); + // Adds the triangles geometry for the build to the structure. void Add(Vector3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy = false); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs index f65db0243..7a4eb59c3 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Build.cs +++ b/Source/Engine/Tools/ModelTool/ModelTool.Build.cs @@ -74,5 +74,6 @@ public class ModelTool : EngineModule public override void GetFilesToDeploy(List files) { files.Add(Path.Combine(FolderPath, "ModelTool.h")); + files.Add(Path.Combine(FolderPath, "MeshAccelerationStructure.h")); } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp index 2b3620616..964df9d6e 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "ModelTool.h" #include "Engine/Core/Log.h" @@ -62,6 +62,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(ImportMaterials); SERIALIZE(ImportTextures); SERIALIZE(RestoreMaterialsOnReimport); + SERIALIZE(GenerateSDF); + SERIALIZE(SDFResolution); SERIALIZE(SplitObjects); SERIALIZE(ObjectIndex); } @@ -100,6 +102,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(ImportMaterials); DESERIALIZE(ImportTextures); DESERIALIZE(RestoreMaterialsOnReimport); + DESERIALIZE(GenerateSDF); + DESERIALIZE(SDFResolution); DESERIALIZE(SplitObjects); DESERIALIZE(ObjectIndex); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index f014bd571..85776e5d6 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -3,7 +3,19 @@ #if COMPILE_WITH_MODEL_TOOL #include "ModelTool.h" +#include "MeshAccelerationStructure.h" #include "Engine/Core/Log.h" +#include "Engine/Core/RandomStream.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Ray.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/JobSystem.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#if USE_EDITOR #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" @@ -19,6 +31,297 @@ #include "Engine/ContentImporters/CreateCollisionData.h" #include "Editor/Utilities/EditorUtilities.h" #include +#endif + +ModelSDFHeader::ModelSDFHeader(const ModelBase::SDFData& sdf, const GPUTextureDescription& desc) + : LocalToUVWMul(sdf.LocalToUVWMul) + , WorldUnitsPerVoxel(sdf.WorldUnitsPerVoxel) + , LocalToUVWAdd(sdf.LocalToUVWAdd) + , MaxDistance(sdf.MaxDistance) + , LocalBoundsMin(sdf.LocalBoundsMin) + , MipLevels(desc.MipLevels) + , LocalBoundsMax(sdf.LocalBoundsMax) + , Width(desc.Width) + , Height(desc.Height) + , Depth(desc.Depth) + , Format(desc.Format) + , ResolutionScale(sdf.ResolutionScale) + , LOD(sdf.LOD) +{ +} + +ModelSDFMip::ModelSDFMip(int32 mipIndex, uint32 rowPitch, uint32 slicePitch) + : MipIndex(mipIndex) + , RowPitch(rowPitch) + , SlicePitch(slicePitch) +{ +} + +ModelSDFMip::ModelSDFMip(int32 mipIndex, const TextureMipData& mip) + : MipIndex(mipIndex) + , RowPitch(mip.RowPitch) + , SlicePitch(mip.Data.Length()) +{ +} + +bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName) +{ + PROFILE_CPU(); + auto startTime = Platform::GetTimeSeconds(); + + // Setup SDF texture properties + BoundingBox bounds; + if (inputModel) + bounds = inputModel->LODs[lodIndex].GetBox(); + else if (modelData) + bounds = modelData->LODs[lodIndex].GetBox(); + else + return true; + Vector3 size = bounds.GetSize(); + ModelBase::SDFData sdf; + sdf.WorldUnitsPerVoxel = 10 / Math::Max(resolutionScale, 0.0001f); + Int3 resolution(Vector3::Ceil(Vector3::Clamp(size / sdf.WorldUnitsPerVoxel, 4, 256))); + Vector3 uvwToLocalMul = size; + Vector3 uvwToLocalAdd = bounds.Minimum; + sdf.LocalToUVWMul = Vector3::One / uvwToLocalMul; + sdf.LocalToUVWAdd = -uvwToLocalAdd / uvwToLocalMul; + sdf.MaxDistance = size.MaxValue(); + sdf.LocalBoundsMin = bounds.Minimum; + sdf.LocalBoundsMax = bounds.Maximum; + sdf.ResolutionScale = resolutionScale; + sdf.LOD = lodIndex; + // TODO: maybe apply 1 voxel margin around the geometry? + const int32 maxMips = 3; + const int32 mipCount = Math::Min(MipLevelsCount(resolution.X, resolution.Y, resolution.Z, true), maxMips); + PixelFormat format = PixelFormat::R16_UNorm; + int32 formatStride = 2; + float formatMaxValue = MAX_uint16; + typedef float (*FormatRead)(void* ptr); + typedef void (*FormatWrite)(void* ptr, float v); + FormatRead formatRead = [](void* ptr) + { + return (float)*(uint16*)ptr; + }; + FormatWrite formatWrite = [](void* ptr, float v) + { + *(uint16*)ptr = (uint16)v; + }; + if (resolution.MaxValue() < 8) + { + // For smaller meshes use more optimized format (gives small perf and memory gain but introduces artifacts on larger meshes) + format = PixelFormat::R8_UNorm; + formatStride = 1; + formatMaxValue = MAX_uint8; + formatRead = [](void* ptr) + { + return (float)*(uint8*)ptr; + }; + formatWrite = [](void* ptr, float v) + { + *(uint8*)ptr = (uint8)v; + }; + } + GPUTextureDescription textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount); + if (outputSDF) + { + *outputSDF = sdf; + if (!outputSDF->Texture) + outputSDF->Texture = GPUTexture::New(); + if (outputSDF->Texture->Init(textureDesc)) + { + SAFE_DELETE_GPU_RESOURCE(outputSDF->Texture); + return true; + } + } + + // TODO: support GPU to generate model SDF on-the-fly (if called during rendering) + + // Setup acceleration structure for fast ray tracing the mesh triangles + MeshAccelerationStructure scene; + if (inputModel) + scene.Add(inputModel, lodIndex); + else if (modelData) + scene.Add(modelData, lodIndex); + scene.BuildBVH(); + + // Allocate memory for the distant field + const int32 voxelsSize = resolution.X * resolution.Y * resolution.Z * formatStride; + void* voxels = Allocator::Allocate(voxelsSize); + Vector3 xyzToLocalMul = uvwToLocalMul / Vector3(resolution); + Vector3 xyzToLocalAdd = uvwToLocalAdd; + const Vector2 encodeMAD(0.5f / sdf.MaxDistance * formatMaxValue, 0.5f * formatMaxValue); + const Vector2 decodeMAD(2.0f * sdf.MaxDistance / formatMaxValue, -sdf.MaxDistance); + int32 voxelSizeSum = voxelsSize; + + // TODO: use optimized sparse storage for SDF data as hierarchical bricks as in papers below: + // https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf + // http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf + // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf + // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf + + // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel + const int32 sampleCount = 12; + Array sampleDirections; + sampleDirections.Resize(sampleCount); + { + RandomStream rand; + sampleDirections.Get()[0] = Vector3::Up; + sampleDirections.Get()[1] = Vector3::Down; + sampleDirections.Get()[2] = Vector3::Left; + sampleDirections.Get()[3] = Vector3::Right; + sampleDirections.Get()[4] = Vector3::Forward; + sampleDirections.Get()[5] = Vector3::Backward; + for (int32 i = 6; i < sampleCount; i++) + sampleDirections.Get()[i] = rand.GetUnitVector(); + } + Function sdfJob = [&sdf, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Job"); + float hitDistance; + Vector3 hitNormal, hitPoint; + Triangle hitTriangle; + const int32 zAddress = resolution.Y * resolution.X * z; + for (int32 y = 0; y < resolution.Y; y++) + { + const int32 yAddress = resolution.X * y + zAddress; + for (int32 x = 0; x < resolution.X; x++) + { + float minDistance = sdf.MaxDistance; + Vector3 voxelPos = Vector3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd; + + // Point query to find the distance to the closest surface + scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle); + + // Raycast samples around voxel to count triangle backfaces hit + int32 hitBackCount = 0, hitCount = 0; + for (int32 sample = 0; sample < sampleDirections.Count(); sample++) + { + Ray sampleRay(voxelPos, sampleDirections[sample]); + if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle)) + { + hitCount++; + const bool backHit = Vector3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0; + if (backHit) + hitBackCount++; + } + } + + float distance = minDistance; + // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry + //if ((float)hitBackCount > )hitCount * 0.3f && hitCount != 0) + if ((float)hitBackCount > (float)sampleDirections.Count() * 0.6f && hitCount != 0) + { + // Voxel is inside the geometry so turn it into negative distance to the surface + distance *= -1; + } + const int32 xAddress = x + yAddress; + formatWrite((byte*)voxels + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); + } + } + }; + JobSystem::Execute(sdfJob, resolution.Z); + + // Cache SDF data on a CPU + if (outputStream) + { + outputStream->WriteInt32(1); // Version + ModelSDFHeader data(sdf, textureDesc); + outputStream->Write(&data); + ModelSDFMip mipData(0, resolution.X * formatStride, voxelsSize); + outputStream->Write(&mipData); + outputStream->WriteBytes(voxels, voxelsSize); + } + + // Upload data to the GPU + if (outputSDF) + { + BytesContainer data; + data.Link((byte*)voxels, voxelsSize); + auto task = outputSDF->Texture->UploadMipMapAsync(data, 0, resolution.X * formatStride, voxelsSize, true); + if (task) + task->Start(); + } + + // Generate mip maps + void* voxelsMip = nullptr; + for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++) + { + Int3 resolutionMip = Int3::Max(resolution / 2, Int3::One); + const int32 voxelsMipSize = resolutionMip.X * resolutionMip.Y * resolutionMip.Z * formatStride; + if (voxelsMip == nullptr) + voxelsMip = Allocator::Allocate(voxelsMipSize); + + // Downscale mip + Function mipJob = [&voxelsMip, &voxels, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z) + { + PROFILE_CPU_NAMED("Model SDF Mip Job"); + const int32 zAddress = resolutionMip.Y * resolutionMip.X * z; + for (int32 y = 0; y < resolutionMip.Y; y++) + { + const int32 yAddress = resolutionMip.X * y + zAddress; + for (int32 x = 0; x < resolutionMip.X; x++) + { + // Linear box filter around the voxel + // TODO: use min distance for nearby texels (texel distance + distance to texel) + float distance = 0; + for (int32 dz = 0; dz < 2; dz++) + { + const int32 dzAddress = (z * 2 + dz) * (resolution.Y * resolution.X); + for (int32 dy = 0; dy < 2; dy++) + { + const int32 dyAddress = (y * 2 + dy) * (resolution.X) + dzAddress; + for (int32 dx = 0; dx < 2; dx++) + { + const int32 dxAddress = (x * 2 + dx) + dyAddress; + const float d = formatRead((byte*)voxels + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y; + distance += d; + } + } + } + distance *= 1.0f / 8.0f; + + const int32 xAddress = x + yAddress; + formatWrite((byte*)voxelsMip + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); + } + } + }; + JobSystem::Execute(mipJob, resolutionMip.Z); + + // Cache SDF data on a CPU + if (outputStream) + { + ModelSDFMip mipData(mipLevel, resolutionMip.X * formatStride, voxelsMipSize); + outputStream->Write(&mipData); + outputStream->WriteBytes(voxelsMip, voxelsMipSize); + } + + // Upload to the GPU + if (outputSDF) + { + BytesContainer data; + data.Link((byte*)voxelsMip, voxelsMipSize); + auto task = outputSDF->Texture->UploadMipMapAsync(data, mipLevel, resolutionMip.X * formatStride, voxelsMipSize, true); + if (task) + task->Start(); + } + + // Go down + voxelSizeSum += voxelsSize; + Swap(voxelsMip, voxels); + resolution = resolutionMip; + } + + Allocator::Free(voxelsMip); + Allocator::Free(voxels); + +#if !BUILD_RELEASE + auto endTime = Platform::GetTimeSeconds(); + LOG(Info, "Generated SDF {}x{}x{} ({} kB) in {}ms for {}", resolution.X, resolution.Y, resolution.Z, voxelSizeSum / 1024, (int32)((endTime - startTime) * 1000.0), assetName); +#endif + return false; +} + +#if USE_EDITOR void RemoveNamespace(String& name) { @@ -1308,3 +1611,5 @@ bool ModelTool::FindTexture(const String& sourcePath, const String& file, String } #endif + +#endif diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 13351db29..8173df383 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -2,14 +2,17 @@ #pragma once -#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR +#if COMPILE_WITH_MODEL_TOOL #include "Engine/Core/Config.h" +#include "Engine/Content/Assets/ModelBase.h" +#if USE_EDITOR #include "Engine/Serialization/ISerializable.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" +class MemoryWriteStream; class JsonWriter; /// @@ -141,13 +144,52 @@ public: } }; +#endif + +struct ModelSDFHeader +{ + Vector3 LocalToUVWMul; + float WorldUnitsPerVoxel; + Vector3 LocalToUVWAdd; + float MaxDistance; + Vector3 LocalBoundsMin; + int32 MipLevels; + Vector3 LocalBoundsMax; + int32 Width; + int32 Height; + int32 Depth; + PixelFormat Format; + float ResolutionScale; + int32 LOD; + + ModelSDFHeader() = default; + ModelSDFHeader(const ModelBase::SDFData& sdf, const struct GPUTextureDescription& desc); +}; + +struct ModelSDFMip +{ + int32 MipIndex; + uint32 RowPitch; + uint32 SlicePitch; + + ModelSDFMip() = default; + ModelSDFMip(int32 mipIndex, uint32 rowPitch, uint32 slicePitch); + ModelSDFMip(int32 mipIndex, const TextureMipData& mip); +}; + /// -/// Import models and animations helper. +/// Models data importing and processing utility. /// class FLAXENGINE_API ModelTool { public: + // Optional: inputModel or modelData + // Optional: outputSDF or null, outputStream or null + static bool GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName); + +#if USE_EDITOR +public: /// /// Declares the imported data type. /// @@ -206,6 +248,10 @@ public: bool ImportTextures = true; bool RestoreMaterialsOnReimport = true; + // SDF + bool GenerateSDF = false; + float SDFResolution = 1.0f; + // Splitting bool SplitObjects = false; int32 ObjectIndex = -1; @@ -283,6 +329,7 @@ private: #if USE_OPEN_FBX static bool ImportDataOpenFBX(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif +#endif }; #endif diff --git a/Source/Engine/Tools/ModelTool/SpatialSort.cpp b/Source/Engine/Tools/ModelTool/SpatialSort.cpp index 773455a7a..de13084dc 100644 --- a/Source/Engine/Tools/ModelTool/SpatialSort.cpp +++ b/Source/Engine/Tools/ModelTool/SpatialSort.cpp @@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Implementation of the helper class to quickly find vertices close to a given position */ #include "SpatialSort.h" -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include using namespace Assimp; diff --git a/Source/Engine/Tools/ModelTool/SpatialSort.h b/Source/Engine/Tools/ModelTool/SpatialSort.h index 6a6371de7..34d716b70 100644 --- a/Source/Engine/Tools/ModelTool/SpatialSort.h +++ b/Source/Engine/Tools/ModelTool/SpatialSort.h @@ -43,7 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once /** Small helper classes to optimise finding vertizes close to a given location */ -#ifndef AI_SPATIALSORT_H_INC +#if !defined(AI_SPATIALSORT_H_INC) && COMPILE_WITH_MODEL_TOOL && USE_EDITOR #define AI_SPATIALSORT_H_INC #include diff --git a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp index 040d862d6..4dd820853 100644 --- a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp +++ b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "VertexTriangleAdjacency.h" #include "Engine/Core/Math/Math.h" diff --git a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h index 56e9cfd9c..c37d767ba 100644 --- a/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h +++ b/Source/Engine/Tools/ModelTool/VertexTriangleAdjacency.h @@ -2,7 +2,7 @@ #pragma once -#if COMPILE_WITH_MODEL_TOOL +#if COMPILE_WITH_MODEL_TOOL && USE_EDITOR #include "Engine/Core/Config.h" #include "Engine/Core/Types/BaseTypes.h" From a1fbf79ebf1db43e582cd31ef770c45ee790b958 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 11:32:34 +0100 Subject: [PATCH 042/144] Refactor Model Import Options to display only relevant properties for asset Type Move tooltips to the doc comments --- .../Editor/Content/Import/ModelImportEntry.cs | 133 +++++++++++------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 207e133ea..ebd7cf951 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -90,232 +90,269 @@ namespace FlaxEditor.Content.Import public class ModelImportSettings { /// - /// Gets or sets the type of the imported asset. + /// Type of the imported asset. /// - [EditorOrder(0), Tooltip("Type of the imported asset")] + [EditorOrder(0)] public ModelType Type { get; set; } = ModelType.Model; /// - /// True if calculate model normals, otherwise will import them. + /// Enable model normal vectors recalculating. /// - [EditorOrder(20), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model normal vectors recalculating")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(20), DefaultValue(false)] public bool CalculateNormals { get; set; } = false; /// - /// Calculated normals smoothing angle. + /// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175. /// - [VisibleIf("CalculateNormals")] - [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingNormalsAngle))] + [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f)] public float SmoothingNormalsAngle { get; set; } = 175.0f; + private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; + /// /// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). /// - [EditorOrder(35), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(35), DefaultValue(false)] public bool FlipNormals { get; set; } = false; /// - /// True if calculate model tangents, otherwise will import them. + /// Enable model tangent vectors recalculating. /// - [EditorOrder(40), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model tangent vectors recalculating")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(40), DefaultValue(false)] public bool CalculateTangents { get; set; } = false; /// - /// Calculated normals smoothing angle. + /// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45. /// - [VisibleIf("CalculateTangents")] - [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingTangentsAngle))] + [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f)] public float SmoothingTangentsAngle { get; set; } = 45.0f; + private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; + /// /// Enable/disable meshes geometry optimization. /// - [EditorOrder(50), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable meshes geometry optimization")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(50), DefaultValue(true)] public bool OptimizeMeshes { get; set; } = true; /// /// Enable/disable geometry merge for meshes with the same materials. /// - [EditorOrder(60), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable geometry merge for meshes with the same materials")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(60), DefaultValue(true)] public bool MergeMeshes { get; set; } = true; /// /// Enable/disable importing meshes Level of Details. /// - [EditorOrder(70), DefaultValue(true), EditorDisplay("Geometry", "Import LODs"), Tooltip("Enable/disable importing meshes Level of Details")] + [EditorDisplay("Geometry", "Import LODs"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(70), DefaultValue(true)] public bool ImportLODs { get; set; } = true; /// /// Enable/disable importing vertex colors (channel 0 only). /// - [EditorOrder(80), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable importing vertex colors (channel 0 only)")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowModel))] + [EditorOrder(80), DefaultValue(true)] public bool ImportVertexColors { get; set; } = true; /// /// Enable/disable importing blend shapes (morph targets). /// - [EditorOrder(85), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable/disable importing blend shapes (morph targets).")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSkinnedModel))] + [EditorOrder(85), DefaultValue(false)] public bool ImportBlendShapes { get; set; } = false; /// /// The lightmap UVs source. /// - [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable), EditorDisplay("Geometry", "Lightmap UVs Source"), Tooltip("Model lightmap UVs source")] + [EditorDisplay("Geometry", "Lightmap UVs Source"), VisibleIf(nameof(ShowModel))] + [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable)] public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable; /// /// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). /// - [EditorOrder(100), DefaultValue(""), EditorDisplay("Geometry")] + [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(100), DefaultValue("")] public string CollisionMeshesPrefix { get; set; } /// /// Custom uniform import scale. /// - [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform"), Tooltip("Custom uniform import scale")] + [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform")] public float Scale { get; set; } = 1.0f; /// /// Custom import geometry rotation. /// [DefaultValue(typeof(Quaternion), "0,0,0,1")] - [EditorOrder(510), EditorDisplay("Transform"), Tooltip("Custom import geometry rotation")] + [EditorOrder(510), EditorDisplay("Transform")] public Quaternion Rotation { get; set; } = Quaternion.Identity; /// /// Custom import geometry offset. /// [DefaultValue(typeof(Vector3), "0,0,0")] - [EditorOrder(520), EditorDisplay("Transform"), Tooltip("Custom import geometry offset")] + [EditorOrder(520), EditorDisplay("Transform")] public Vector3 Translation { get; set; } = Vector3.Zero; /// /// If checked, the imported geometry will be shifted to the center of mass. /// - [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform"), Tooltip("If checked, the imported geometry will be shifted to the center of mass.")] + [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform")] public bool CenterGeometry { get; set; } = false; /// - /// The imported animation duration mode. + /// Imported animation duration mode. Can use the original value or overriden by settings. /// - [EditorOrder(1000), DefaultValue(AnimationDuration.Imported), EditorDisplay("Animation"), Tooltip("Imported animation duration mode. Can use the original value or overriden by settings.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1000), DefaultValue(AnimationDuration.Imported)] public AnimationDuration Duration { get; set; } = AnimationDuration.Imported; /// - /// The imported animation first frame index. Used only if Duration mode is set to Custom. + /// Imported animation first frame index. Used only if Duration mode is set to Custom. /// - [EditorOrder(1010), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation first frame index. Used only if Duration mode is set to Custom.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] + [EditorOrder(1010), DefaultValue(0.0f), Limit(0)] public float FramesRangeStart { get; set; } = 0; /// - /// The imported animation end frame index. Used only if Duration mode is set to Custom. + /// Imported animation last frame index. Used only if Duration mode is set to Custom. /// - [EditorOrder(1020), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation last frame index. Used only if Duration mode is set to Custom.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] + [EditorOrder(1020), DefaultValue(0.0f), Limit(0)] public float FramesRangeEnd { get; set; } = 0; + private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; + /// /// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used. /// - [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float DefaultFrameRate { get; set; } = 0.0f; /// /// The imported animation sampling rate. If value is 0 then the original animation speed will be used. /// - [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation sampling rate. If value is 0 then the original animation speed will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float SamplingRate { get; set; } = 0.0f; /// /// The imported animation will have removed tracks with no keyframes or unspecified data. /// - [EditorOrder(1040), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation will have removed tracks with no keyframes or unspecified data.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1040), DefaultValue(true)] public bool SkipEmptyCurves { get; set; } = true; /// /// The imported animation channels will be optimized to remove redundant keyframes. /// - [EditorOrder(1050), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation channels will be optimized to remove redundant keyframes.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1050), DefaultValue(true)] public bool OptimizeKeyframes { get; set; } = true; /// /// Enables root motion extraction support from this animation. /// - [EditorOrder(1060), DefaultValue(false), EditorDisplay("Animation"), Tooltip("Enables root motion extraction support from this animation.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1060), DefaultValue(false)] public bool EnableRootMotion { get; set; } = false; /// /// The custom node name to be used as a root motion source. If not specified the actual root node will be used. /// - [EditorOrder(1070), DefaultValue(typeof(string), ""), EditorDisplay("Animation"), Tooltip("The custom node name to be used as a root motion source. If not specified the actual root node will be used.")] + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1070), DefaultValue(typeof(string), "")] public string RootNodeName { get; set; } /// /// If checked, the importer will generate a sequence of LODs based on the base LOD index. /// - [EditorOrder(1100), DefaultValue(false), EditorDisplay("Level Of Detail", "Generate LODs"), Tooltip("If checked, the importer will generate a sequence of LODs based on the base LOD index.")] + [EditorDisplay("Level Of Detail", "Generate LODs"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1100), DefaultValue(false)] public bool GenerateLODs { get; set; } = false; /// /// The index of the LOD from the source model data to use as a reference for following LODs generation. /// - [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1), EditorDisplay("Level Of Detail", "Base LOD"), Tooltip("The index of the LOD from the source model data to use as a reference for following LODs generation.")] + [EditorDisplay("Level Of Detail", "Base LOD"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1)] public int BaseLOD { get; set; } = 0; /// /// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). /// - [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).")] + [EditorDisplay("Level Of Detail", "LOD Count"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs)] public int LODCount { get; set; } = 4; /// /// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%. /// - [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f), EditorDisplay("Level Of Detail"), Tooltip("The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.")] + [EditorDisplay("Level Of Detail"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f)] public float TriangleReduction { get; set; } = 0.5f; /// /// If checked, the importer will create materials for model meshes as specified in the file. /// - [EditorOrder(400), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will create materials for model meshes as specified in the file.")] + [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(400), DefaultValue(true)] public bool ImportMaterials { get; set; } = true; /// /// If checked, the importer will import texture files used by the model and any embedded texture resources. /// - [EditorOrder(410), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will import texture files used by the model and any embedded texture resources.")] + [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(410), DefaultValue(true)] public bool ImportTextures { get; set; } = true; /// /// If checked, the importer will try to restore the model material slots. /// - [EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")] + [EditorDisplay("Materials", "Restore Materials On Reimport"), VisibleIf(nameof(ShowGeometry))] + [EditorOrder(420), DefaultValue(true)] public bool RestoreMaterialsOnReimport { get; set; } = true; /// /// If checked, enables generation of Signed Distance Field (SDF). /// - [EditorOrder(1500), DefaultValue(false), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] + [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] + [EditorOrder(1500), DefaultValue(false)] public bool GenerateSDF { get; set; } = false; /// /// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance. /// - [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] + [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] + [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f)] public float SDFResolution { get; set; } = 1.0f; /// /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. /// - [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting"), Tooltip("If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.")] + [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting")] public bool SplitObjects { get; set; } = false; /// /// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects. /// - [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting"), Tooltip("The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.")] + [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting")] public int ObjectIndex { get; set; } = -1; - private bool Type_Model => Type == ModelType.Model; + private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel; + private bool ShowModel => Type == ModelType.Model; + private bool ShowSkinnedModel => Type == ModelType.SkinnedModel; + private bool ShowAnimation => Type == ModelType.Animation; [StructLayout(LayoutKind.Sequential)] internal struct InternalOptions From 67b6604a6735dd46969b1add39ee626d48815de4 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 11:33:09 +0100 Subject: [PATCH 043/144] Add automatic group panels hiding if all properties are hidden by `VisibleIf` rule --- .../CustomEditors/Editors/GenericEditor.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 4ca761634..fb1ead44a 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -229,6 +229,7 @@ namespace FlaxEditor.CustomEditors.Editors } } + private static HashSet _visibleIfPropertiesListsCache; private VisibleIfCache[] _visibleIfCaches; private bool _isNull; @@ -761,8 +762,13 @@ namespace FlaxEditor.CustomEditors.Editors if (_visibleIfCaches != null) { + if (_visibleIfPropertiesListsCache == null) + _visibleIfPropertiesListsCache = new HashSet(); + else + _visibleIfPropertiesListsCache.Clear(); try { + // Update VisibleIf rules for (int i = 0; i < _visibleIfCaches.Length; i++) { ref var c = ref _visibleIfCaches[i]; @@ -798,6 +804,21 @@ namespace FlaxEditor.CustomEditors.Editors { c.Group.Panel.Visible = visible; } + if (c.PropertiesList != null) + _visibleIfPropertiesListsCache.Add(c.PropertiesList.Properties); + } + + // Hide properties lists with all labels being hidden + foreach (var propertiesList in _visibleIfPropertiesListsCache) + { + propertiesList.Visible = propertiesList.Children.Any(c => c.Visible); + } + + // Hide group panels with all properties lists hidden + foreach (var propertiesList in _visibleIfPropertiesListsCache) + { + if (propertiesList.Parent is DropPanel dropPanel) + dropPanel.Visible = propertiesList.Visible || !dropPanel.Children.All(c => c is PropertiesList && !c.Visible); } } catch (Exception ex) From 67d1e43f6cba4216e1501f3696ae6c8d6ffb0cb1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 12:18:28 +0100 Subject: [PATCH 044/144] Add utility button to generate SDF for all models on a scene --- Source/Editor/Modules/UIModule.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3d82b1a17..799de0a75 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -58,6 +58,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuToolsBakeAllEnvProbes; private ContextMenuButton _menuToolsBuildCSGMesh; private ContextMenuButton _menuToolsBuildNavMesh; + private ContextMenuButton _menuToolsBuildAllMesgesSDF; private ContextMenuButton _menuToolsCancelBuilding; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; private ContextMenuChildMenu _menuWindowApplyWindowLayout; @@ -484,6 +485,7 @@ namespace FlaxEditor.Modules _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes); _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG); _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh); + _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); @@ -708,6 +710,7 @@ namespace FlaxEditor.Modules _menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps"; _menuToolsClearLightmaps.Enabled = canEdit; _menuToolsBakeAllEnvProbes.Enabled = canEdit; + _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps; _menuToolsBuildCSGMesh.Enabled = canEdit; _menuToolsBuildNavMesh.Enabled = canEdit; _menuToolsCancelBuilding.Enabled = GameCooker.IsRunning; @@ -835,6 +838,24 @@ namespace FlaxEditor.Modules Editor.Scene.MarkSceneEdited(scenes); } + private void BuildAllMeshesSDF() + { + // TODO: async maybe with progress reporting? + Editor.Scene.ExecuteOnGraph(node => + { + if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) + { + if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) + { + Editor.Log("Generating SDF for " + staticModel.Model); + if (!staticModel.Model.GenerateSDF()) + staticModel.Model.Save(); + } + } + return true; + }); + } + private void SetTheCurrentSceneViewAsDefault() { var projectInfo = Editor.GameProject; From 92ab3d005eb0b40fc71e47d533f2e791b31a4099 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 12:54:04 +0100 Subject: [PATCH 045/144] Generate SDF for engine models --- Content/Editor/Primitives/Capsule.flax | 4 ++-- Content/Editor/Primitives/Cube.flax | 4 ++-- Content/Editor/Primitives/Sphere.flax | 4 ++-- Content/Engine/Models/Quad.flax | 4 ++-- Content/Engine/Models/Sphere.flax | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Content/Editor/Primitives/Capsule.flax b/Content/Editor/Primitives/Capsule.flax index 10b38bf9d..913a96216 100644 --- a/Content/Editor/Primitives/Capsule.flax +++ b/Content/Editor/Primitives/Capsule.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1d9c920a1931dda8ea9ad3edca0b97935ffe0ceba0bf647ef1611db76c13490 -size 25720 +oid sha256:a65e29fe6fb86f08fa79ad65da87db1f050e60edd3e881837d81a9933a067230 +size 30396 diff --git a/Content/Editor/Primitives/Cube.flax b/Content/Editor/Primitives/Cube.flax index a45f860cc..978850c44 100644 --- a/Content/Editor/Primitives/Cube.flax +++ b/Content/Editor/Primitives/Cube.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff43d080b349c955e1b16396018ef0066f3eacd525f8837bb522af15e174bfda -size 2579 +oid sha256:4f62d95192ce88e2871f8efb9ea716aed6954d1fe3628ac5c923f25236d42397 +size 4981 diff --git a/Content/Editor/Primitives/Sphere.flax b/Content/Editor/Primitives/Sphere.flax index d2d26788f..9ec03d998 100644 --- a/Content/Editor/Primitives/Sphere.flax +++ b/Content/Editor/Primitives/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f6b4fd9f83c269d54f716edb09071d00d71b09a89d829ce936038677648f22 -size 22219 +oid sha256:215797e4f6486d74eef5fcdc17bd574d4da3d32fb363e796c5ca04d9b93c6c6c +size 24621 diff --git a/Content/Engine/Models/Quad.flax b/Content/Engine/Models/Quad.flax index 0bec10f36..3e1ac4692 100644 --- a/Content/Engine/Models/Quad.flax +++ b/Content/Engine/Models/Quad.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0ccf8e4082695391576e65cbaaf4a685d77a75cad668cc99a71149e852390c9 -size 865 +oid sha256:a7f1082e461386e0b2d89ce3cec3ddd7b670bce2a867e9503c841681721236bf +size 965 diff --git a/Content/Engine/Models/Sphere.flax b/Content/Engine/Models/Sphere.flax index 803b9ab40..182cb2e6f 100644 --- a/Content/Engine/Models/Sphere.flax +++ b/Content/Engine/Models/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec6177abb0235b6da27b0aef9f06f9865b15548afcdc34e71448cae6b51b8db6 -size 109567 +oid sha256:6287b7eb7d31e1a8a910b9601c376a2bc870f514b6a569d84f112264fb853099 +size 111969 From d5297f9047a0d3585907a2ec1fe19c68a917e981 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 14:59:14 +0100 Subject: [PATCH 046/144] Add option to always render Global SDF --- Source/Engine/Core/Config/GraphicsSettings.h | 8 +- Source/Engine/Graphics/RenderBuffers.h | 11 + .../Renderer/GlobalSignDistanceFieldPass.cpp | 428 +++++++++--------- .../Renderer/GlobalSignDistanceFieldPass.h | 8 + Source/Engine/Renderer/Renderer.cpp | 9 + 5 files changed, 257 insertions(+), 207 deletions(-) diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 6e2074870..53bf89a61 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -62,11 +62,17 @@ public: API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; + /// + /// If checked, enables Global SDF rendering. This can be used in materials, shaders, and particles. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + bool EnableGlobalSDF = false; + #if USE_EDITOR /// /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). /// - API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") + API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Global SDF\")") bool GenerateSDFOnModelImport = false; #endif diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index b99ec5308..b0cbcd321 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -150,6 +150,17 @@ public: return _viewport; } + template + const T* FindCustomBuffer(const StringView& name) const + { + for (CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return (const T*)e; + } + return nullptr; + } + template T* GetCustomBuffer(const StringView& name) { diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 4442e5b15..2fe47bc48 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -110,6 +110,7 @@ public: GPUTexture* CascadeMips[4] = {}; Vector3 Positions[4]; HashSet NonEmptyChunks[4]; + GlobalSignDistanceFieldPass::BindingData Result; ~GlobalSignDistanceFieldCustomBuffer() { @@ -212,6 +213,17 @@ void GlobalSignDistanceFieldPass::Dispose() ChunksCache.SetCapacity(0); } +bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) +{ + auto* sdfData = buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")); + if (sdfData && sdfData->LastFrameUsed == Engine::FrameCount) + { + result = sdfData->Result; + return false; + } + return true; +} + bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) { // Skip if not supported @@ -221,6 +233,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex return true; auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (sdfData.LastFrameUsed == currentFrame) + { + result = sdfData.Result; + return false; + } + + PROFILE_GPU_CPU("Global SDF"); + // TODO: configurable via graphics settings const int32 resolution = 256; const int32 mipFactor = 4; @@ -229,238 +251,231 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const float distanceExtent = 2500.0f; const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; - // Skip if already done in the current frame - const auto currentFrame = Engine::FrameCount; - if (sdfData.LastFrameUsed != currentFrame) + // Initialize buffers + sdfData.LastFrameUsed = currentFrame; + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + bool updated = false; + for (GPUTexture*& cascade : sdfData.Cascades) { - PROFILE_GPU_CPU("Global SDF"); - - // Initialize buffers - sdfData.LastFrameUsed = currentFrame; - auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - bool updated = false; - for (GPUTexture*& cascade : sdfData.Cascades) + if (cascade && cascade->Width() != desc.Width) { - if (cascade && cascade->Width() != desc.Width) - { - RenderTargetPool::Release(cascade); - cascade = nullptr; - } + RenderTargetPool::Release(cascade); + cascade = nullptr; + } + if (!cascade) + { + cascade = RenderTargetPool::Get(desc); if (!cascade) - { - cascade = RenderTargetPool::Get(desc); - if (!cascade) - return true; - updated = true; - PROFILE_GPU_CPU("Init"); - context->ClearUA(cascade, Vector4::One); - } + return true; + updated = true; + PROFILE_GPU_CPU("Init"); + context->ClearUA(cascade, Vector4::One); } - desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + } + desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + { + if (cascadeMip && cascadeMip->Width() != desc.Width) { - if (cascadeMip && cascadeMip->Width() != desc.Width) - { - RenderTargetPool::Release(cascadeMip); - cascadeMip = nullptr; - } + RenderTargetPool::Release(cascadeMip); + cascadeMip = nullptr; + } + if (!cascadeMip) + { + cascadeMip = RenderTargetPool::Get(desc); if (!cascadeMip) + return true; + updated = true; + } + } + GPUTexture* tmpMip = nullptr; + if (updated) + LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + + // Rasterize world geometry into Global SDF + renderContext.View.Pass = DrawPass::GlobalSDF; + uint32 viewMask = renderContext.View.RenderLayersMask; + const bool useCache = !updated && !renderContext.Task->IsCameraCut; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); + const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); + auto& chunks = ChunksCache; + chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); + bool anyDraw = false; + const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; + //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + for (int32 cascade = 0; cascade < 4; cascade++) + { + // Reduce frequency of the updates + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) + continue; + const float distance = cascadesDistances[cascade]; + const float maxDistance = distance * 2; + const float voxelSize = maxDistance / resolution; + const float snapping = voxelSize * mipFactor; + const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; + // TODO: cascade scrolling on movement to reduce dirty chunks? + //const Vector3 center = Vector3::Zero; + sdfData.Positions[cascade] = center; + BoundingBox cascadeBounds(center - distance, center + distance); + // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) + const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); + GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + + // Clear cascade before rasterization + { + PROFILE_CPU_NAMED("Clear"); + chunks.Clear(); + _modelsBuffer->Clear(); + _modelsTextures.Clear(); + } + + // Draw all objects from all scenes into the cascade + _modelsBufferCount = 0; + _voxelSize = voxelSize; + _cascadeBounds = cascadeBounds; + for (auto* scene : renderContext.List->Scenes) + { + // TODO: optimize for moving camera (copy sdf) + // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) + for (auto& e : scene->Actors) { - cascadeMip = RenderTargetPool::Get(desc); - if (!cascadeMip) - return true; - updated = true; + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + { + e.Actor->Draw(renderContext); + } } } - GPUTexture* tmpMip = nullptr; - if (updated) - LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); - // Rasterize world geometry into Global SDF - renderContext.View.Pass = DrawPass::GlobalSDF; - uint32 viewMask = renderContext.View.RenderLayersMask; - const bool useCache = !updated && !renderContext.Task->IsCameraCut; - static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); - const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); - auto& chunks = ChunksCache; - chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false); - bool anyDraw = false; - const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; - //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; - for (int32 cascade = 0; cascade < 4; cascade++) + // Send models data to the GPU { - // Reduce frequency of the updates - if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) - continue; - const float distance = cascadesDistances[cascade]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; - const float snapping = voxelSize * mipFactor; - const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; - // TODO: cascade scrolling on movement to reduce dirty chunks? - //const Vector3 center = Vector3::Zero; - sdfData.Positions[cascade] = center; - BoundingBox cascadeBounds(center - distance, center + distance); - // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) - const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade - const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); - GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + PROFILE_GPU_CPU("Update Models"); + _modelsBuffer->Flush(context); + } - // Clear cascade before rasterization + // Perform batched chunks rasterization + if (!anyDraw) + { + anyDraw = true; + context->ResetSR(); + tmpMip = RenderTargetPool::Get(desc); + if (!tmpMip) + return true; + } + ModelsRasterizeData data; + data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; + data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; + data.MaxDistance = maxDistance; + data.CascadeResolution = resolution; + data.CascadeMipResolution = resolutionMip; + data.CascadeMipFactor = mipFactor; + context->BindUA(0, cascadeView); + context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); + if (_cb1) + context->BindCB(1, _cb1); + const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; + auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; + { + PROFILE_GPU_CPU("Clear Chunks"); + for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { - PROFILE_CPU_NAMED("Clear"); - chunks.Clear(); - _modelsBuffer->Clear(); - _modelsTextures.Clear(); - } + auto& key = it->Item; + if (chunks.ContainsKey(key)) + continue; - // Draw all objects from all scenes into the cascade - _modelsBufferCount = 0; - _voxelSize = voxelSize; - _cascadeBounds = cascadeBounds; - for (auto* scene : renderContext.List->Scenes) - { - // TODO: optimize for moving camera (copy sdf) - // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) - for (auto& e : scene->Actors) - { - if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) - { - e.Actor->Draw(renderContext); - } - } - } - - // Send models data to the GPU - { - PROFILE_GPU_CPU("Update Models"); - _modelsBuffer->Flush(context); - } - - // Perform batched chunks rasterization - if (!anyDraw) - { - anyDraw = true; - context->ResetSR(); - tmpMip = RenderTargetPool::Get(desc); - if (!tmpMip) - return true; - } - ModelsRasterizeData data; - data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; - data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; - data.MaxDistance = maxDistance; - data.CascadeResolution = resolution; - data.CascadeMipResolution = resolutionMip; - data.CascadeMipFactor = mipFactor; - context->BindUA(0, cascadeView); - context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); - if (_cb1) - context->BindCB(1, _cb1); - const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; - auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; - { - PROFILE_GPU_CPU("Clear Chunks"); - for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) - { - auto& key = it->Item; - if (chunks.ContainsKey(key)) - continue; - - // Clear empty chunk - nonEmptyChunks.Remove(it); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - if (_cb1) - context->UpdateCB(_cb1, &data); - context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - } - } - // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds - { - PROFILE_GPU_CPU("Rasterize Chunks"); - for (auto& e : chunks) - { - // Rasterize non-empty chunk - auto& key = e.Key; - auto& chunk = e.Value; - for (int32 i = 0; i < chunk.ModelsCount; i++) - { - int32 model = chunk.Models[i]; - data.Models[i] = model; - context->BindSR(i + 1, _modelsTextures[model]); - } - ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); - GPUShaderProgramCS* cs; - if (key.Layer == 0) - { - // First layer so can override existing chunk data - cs = _csRasterizeModel0; - nonEmptyChunks.Add(key); - } - else - { - // Another layer so need combine with existing chunk data - cs = _csRasterizeModel1; - } - context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) - -#if GLOBAL_SDF_DEBUG_CHUNKS - // Debug draw chunk bounds in world space with number of models in it - if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) - { - Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; - BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); - DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); - DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); - } -#endif - } - } - - // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) - { - PROFILE_GPU_CPU("Generate Mip"); + // Clear empty chunk + nonEmptyChunks.Remove(it); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; if (_cb1) context->UpdateCB(_cb1, &data); - context->ResetUA(); - context->BindSR(0, cascadeView); - context->BindUA(0, cascadeMipView); - const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); - int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; - context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); - context->UnBindSR(0); - GPUTextureView* tmpMipView = tmpMip->ViewVolume(); - for (int32 i = 1; i < floodFillIterations; i++) + context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches + } + } + // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds + { + PROFILE_GPU_CPU("Rasterize Chunks"); + for (auto& e : chunks) + { + // Rasterize non-empty chunk + auto& key = e.Key; + auto& chunk = e.Value; + for (int32 i = 0; i < chunk.ModelsCount; i++) { - context->ResetUA(); - context->BindSR(0, cascadeMipView); - context->BindUA(0, tmpMipView); - context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); - Swap(tmpMipView, cascadeMipView); + int32 model = chunk.Models[i]; + data.Models[i] = model; + context->BindSR(i + 1, _modelsTextures[model]); } - if (floodFillIterations % 2 == 0) - Swap(tmpMipView, cascadeMipView); + ASSERT_LOW_LAYER(chunk.ModelsCount != 0); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ModelsCount = chunk.ModelsCount; + if (_cb1) + context->UpdateCB(_cb1, &data); + GPUShaderProgramCS* cs; + if (key.Layer == 0) + { + // First layer so can override existing chunk data + cs = _csRasterizeModel0; + nonEmptyChunks.Add(key); + } + else + { + // Another layer so need combine with existing chunk data + cs = _csRasterizeModel1; + } + context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) + +#if GLOBAL_SDF_DEBUG_CHUNKS + // Debug draw chunk bounds in world space with number of models in it + if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) + { + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; + BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); + DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); + DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); + } +#endif } } - RenderTargetPool::Release(tmpMip); - if (anyDraw) + // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) { - context->UnBindCB(1); + PROFILE_GPU_CPU("Generate Mip"); + if (_cb1) + context->UpdateCB(_cb1, &data); context->ResetUA(); - context->FlushState(); - context->ResetSR(); - context->FlushState(); + context->BindSR(0, cascadeView); + context->BindUA(0, cascadeMipView); + const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE); + int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS; + context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + context->UnBindSR(0); + GPUTextureView* tmpMipView = tmpMip->ViewVolume(); + for (int32 i = 1; i < floodFillIterations; i++) + { + context->ResetUA(); + context->BindSR(0, cascadeMipView); + context->BindUA(0, tmpMipView); + context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); + Swap(tmpMipView, cascadeMipView); + } + if (floodFillIterations % 2 == 0) + Swap(tmpMipView, cascadeMipView); } } + RenderTargetPool::Release(tmpMip); + if (anyDraw) + { + context->UnBindCB(1); + context->ResetUA(); + context->FlushState(); + context->ResetSR(); + context->FlushState(); + } + // Copy results static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.CascadeMips), "Invalid cascades count."); @@ -476,6 +491,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize; } result.GlobalSDF.Resolution = (float)resolution; + sdfData.Result = result; return false; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index b4a9cf984..f4c503222 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -46,6 +46,14 @@ private: BoundingBox _cascadeBounds; public: + /// + /// Gets the Global SDF (only if enabled in Graphics Settings). + /// + /// The rendering context buffers. + /// The result Global SDF data for binding to the shaders. + /// True if there is no valid Global SDF rendered during this frame, otherwise false. + bool Get(const RenderBuffers* buffers, BindingData& result); + /// /// Renders the Global SDF. /// diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 8174ebeb5..d6d21c1be 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -29,6 +29,7 @@ #include "AntiAliasing/SMAA.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Level.h" +#include "Engine/Core/Config/GraphicsSettings.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/QuadOverdrawPass.h" @@ -289,6 +290,7 @@ void Renderer::DrawPostFxMaterial(GPUContext* context, const RenderContext& rend void RenderInner(SceneRenderTask* task, RenderContext& renderContext) { auto context = GPUDevice::Instance->GetMainContext(); + auto* graphicsSettings = GraphicsSettings::Get(); auto& view = renderContext.View; ASSERT(renderContext.Buffers && renderContext.Buffers->GetWidth() > 0); @@ -338,6 +340,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) } #endif + // Global SDF rendering (can be used by materials later on) + if (graphicsSettings->EnableGlobalSDF) + { + GlobalSignDistanceFieldPass::BindingData bindingData; + GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingData); + } + // Fill GBuffer GBufferPass::Instance()->Fill(renderContext, lightBuffer->View()); From 80d7c854abea607fe55c2350940fb63af308e814 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 24 Mar 2022 15:24:36 +0100 Subject: [PATCH 047/144] Add SDF for plane model --- Content/Editor/Primitives/Plane.flax | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/Editor/Primitives/Plane.flax b/Content/Editor/Primitives/Plane.flax index 1e5eaf76b..57fc0959a 100644 --- a/Content/Editor/Primitives/Plane.flax +++ b/Content/Editor/Primitives/Plane.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5683a761f198d01d7189490d526b55e393eb01fffbd6761fb969d11067b3db73 -size 2277 +oid sha256:9a82456ad825b8bca32b4eec7e33ced41029551183ebad712462e73d006b887c +size 3321 From dff1b37a3be6f8bd308f9ef30a87bb025b1cddc7 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 09:50:18 +0100 Subject: [PATCH 048/144] Bump up build number --- Flax.flaxproj | 2 +- Source/FlaxEngine.Gen.cs | 4 ++-- Source/FlaxEngine.Gen.h | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index d2b063043..4f24f4c52 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 4, - "Build": 6330 + "Build": 6331 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.", diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 697452ffa..dbde71661 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857cc7990797")] -[assembly: AssemblyVersion("1.4.6330")] -[assembly: AssemblyFileVersion("1.4.6330")] +[assembly: AssemblyVersion("1.4.6331")] +[assembly: AssemblyFileVersion("1.4.6331")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index dc340ad54..fc0450288 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 4, 6330) -#define FLAXENGINE_VERSION_TEXT "1.4.6330" +#define FLAXENGINE_VERSION Version(1, 4, 6331) +#define FLAXENGINE_VERSION_TEXT "1.4.6331" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 4 -#define FLAXENGINE_VERSION_BUILD 6330 +#define FLAXENGINE_VERSION_BUILD 6331 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved." From b847b9ccba52baee611f301bb25f4b8b2e8a107a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 10:09:52 +0100 Subject: [PATCH 049/144] Add **Sample Global SDF** node to materials --- Source/Editor/Surface/Archetypes/Textures.cs | 13 ++++++++ .../Materials/DecalMaterialShader.cpp | 2 +- .../Materials/DeferredMaterialShader.cpp | 2 +- .../Materials/DeformableMaterialShader.cpp | 2 +- .../Graphics/Materials/MaterialParams.cpp | 18 ++++++++++- .../Graphics/Materials/MaterialParams.h | 5 ++++ .../Materials/TerrainMaterialShader.cpp | 2 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- .../MaterialGenerator.Textures.cpp | 10 +++++++ Source/Engine/Visject/ShaderGraph.cpp | 21 +++++++++++++ Source/Engine/Visject/ShaderGraph.h | 1 + .../Engine/Visject/ShaderGraphUtilities.cpp | 30 ++++++++++++------- Source/Shaders/GlobalSignDistanceField.hlsl | 6 ++-- 13 files changed, 94 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index a3974c740..b1a39bcb6 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -358,6 +358,19 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(1, "Location", true, null, 2), } }, + new NodeArchetype + { + TypeID = 14, + Title = "Sample Global SDF", + Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(200, 20), + Elements = new[] + { + NodeElementArchetype.Factory.Output(0, "Distance", typeof(float), 0), + NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1), + } + }, }; } } diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index f0ac5300b..a8fefdbca 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -48,7 +48,7 @@ void DecalMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 29ef818e0..50780743b 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -74,7 +74,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 61d6fb42a..f491ee700 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -62,7 +62,7 @@ void DeformableMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 7386343b5..e7dfe88e0 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -11,6 +11,8 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Streaming/Streaming.h" bool MaterialInfo8::operator==(const MaterialInfo8& other) const @@ -157,6 +159,9 @@ const Char* ToString(MaterialParameterType value) case MaterialParameterType::TextureGroupSampler: result = TEXT("TextureGroupSampler"); break; + case MaterialParameterType::GlobalSDF: + result = TEXT("GlobalSDF"); + break; default: result = TEXT(""); break; @@ -198,7 +203,6 @@ Variant MaterialParameter::GetValue() const case MaterialParameterType::GPUTexture: return _asGPUTexture.Get(); default: - CRASH; return Variant::Zero; } } @@ -303,6 +307,8 @@ void MaterialParameter::SetValue(const Variant& value) invalidType = true; } break; + case MaterialParameterType::GlobalSDF: + break; default: invalidType = true; } @@ -475,6 +481,16 @@ void MaterialParameter::Bind(BindMeta& meta) const case MaterialParameterType::TextureGroupSampler: meta.Context->BindSampler(_registerIndex, Streaming::GetTextureGroupSampler(_asInteger)); break; + case MaterialParameterType::GlobalSDF: + { + GlobalSignDistanceFieldPass::BindingData bindingData; + if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData)) + Platform::MemoryClear(&bindingData, sizeof(bindingData)); + for (int32 i = 0; i < 4; i++) + meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); + *((GlobalSignDistanceFieldPass::GlobalSDFData*)(meta.Constants.Get() + _offset)) = bindingData.GlobalSDF; + break; + } default: break; } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 664da2b19..a367114c2 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -128,6 +128,11 @@ enum class MaterialParameterType : byte /// The texture sampler derived from texture group settings. /// TextureGroupSampler = 19, + + /// + /// The Global SDF (textures and constants). + /// + GlobalSDF = 20, }; const Char* ToString(MaterialParameterType value); diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 808278c04..b93298d7a 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -67,7 +67,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) bindMeta.Context = context; bindMeta.Constants = cb; bindMeta.Input = nullptr; - bindMeta.Buffers = nullptr; + bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 2fe47bc48..e5b39d685 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -215,7 +215,7 @@ void GlobalSignDistanceFieldPass::Dispose() bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) { - auto* sdfData = buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")); + auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; if (sdfData && sdfData->LastFrameUsed == Engine::FrameCount) { result = sdfData->Result; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index bb940b610..a5e03f124 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -420,6 +420,16 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = writeLocal(VariantType::Vector2, String::Format(TEXT("({3} + float2({0}, {1})) * {2}"), frameX.Value, frameY.Value, framesXYInv.Value, uv.Value), node); break; } + // Sample Global SDF + case 14: + { + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + value = writeLocal(VariantType::Float, String::Format(TEXT("SampleGlobalSDF({0}, {0}_Tex, {1})"), param.ShaderName, worldPosition.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + //float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) + break; + } default: break; } diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 7cf2f01b5..d06a00977 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -1298,6 +1298,27 @@ SerializedMaterialParam& ShaderGenerator::findOrAddTextureGroupSampler(int32 ind return param; } +SerializedMaterialParam& ShaderGenerator::findOrAddGlobalSDF() +{ + // Find + for (int32 i = 0; i < _parameters.Count(); i++) + { + SerializedMaterialParam& param = _parameters[i]; + if (!param.IsPublic && param.Type == MaterialParameterType::GlobalSDF) + return param; + } + + // Create + SerializedMaterialParam& param = _parameters.AddOne(); + param.Type = MaterialParameterType::GlobalSDF; + param.IsPublic = false; + param.Override = true; + param.Name = TEXT("Global SDF"); + param.ShaderName = getParamName(_parameters.Count()); + param.ID = Guid(_parameters.Count(), 0, 0, 3); // Assign temporary id + return param; +} + String ShaderGenerator::getLocalName(int32 index) { return TEXT("local") + StringUtils::ToString(index); diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 1ae7cab7f..38205f743 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -286,6 +286,7 @@ protected: SerializedMaterialParam findOrAddCubeTexture(const Guid& id); SerializedMaterialParam findOrAddSceneTexture(MaterialSceneTextures type); SerializedMaterialParam& findOrAddTextureGroupSampler(int32 index); + SerializedMaterialParam& findOrAddGlobalSDF(); static String getLocalName(int32 index); static String getParamName(int32 index); diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index 0944685e9..bfe86fcef 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -9,19 +9,19 @@ #include "Engine/Content/Content.h" #include "Engine/Engine/GameplayGlobals.h" #include "Engine/Graphics/Config.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& writer, Array& parameters) { int32 constantsOffset = 0; int32 paddingIndex = 0; - for (int32 i = 0; i < parameters.Count(); i++) { auto& param = parameters[i]; - const Char* format = nullptr; int32 size; int32 alignment; + bool zeroRegister = true; switch (param.Type) { case MaterialParameterType::Bool: @@ -107,11 +107,15 @@ void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& write alignment = 16; format = TEXT("float4 {0};"); break; - default: ; } break; } - default: ; + case MaterialParameterType::GlobalSDF: + zeroRegister = false; + size = sizeof(GlobalSignDistanceFieldPass::GlobalSDFData); + alignment = 16; + format = TEXT("GlobalSDFData {0};"); + break; } if (format) { @@ -126,7 +130,8 @@ void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& write } } - param.RegisterIndex = 0; + if (zeroRegister) + param.RegisterIndex = 0; param.Offset = constantsOffset; writer.WriteLine(format, param.ShaderName); constantsOffset += size; @@ -139,7 +144,9 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri for (int32 i = 0; i < parameters.Count(); i++) { auto& param = parameters[i]; - const Char* format; + const Char* format = nullptr; + bool zeroOffset = true; + int32 registers = 1; switch (param.Type) { case MaterialParameterType::NormalMap: @@ -158,16 +165,19 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri case MaterialParameterType::GPUTextureVolume: format = TEXT("Texture3D {0} : register(t{1});"); break; - default: - format = nullptr; + case MaterialParameterType::GlobalSDF: + format = TEXT("Texture3D {0}_Tex[4] : register(t{1});"); + registers = 4; + zeroOffset = false; break; } if (format) { - param.Offset = 0; + if (zeroOffset) + param.Offset = 0; param.RegisterIndex = (byte)startRegister; writer.WriteLine(format, param.ShaderName, startRegister); - startRegister++; + startRegister += registers; if (param.RegisterIndex >= GPU_MAX_SR_BINDED) { return TEXT("Too many textures used. The maximum supported amount is " MACRO_TO_STR(GPU_MAX_SR_BINDED) " (including lightmaps and utility textures for lighting)."); diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 83a98ae00..2e3f5ddd0 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -57,17 +57,15 @@ struct GlobalSDFHit float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) { float distance = data.CascadePosDistance[3].w * 2.0f; + UNROLL for (uint cascade = minCascade; cascade < 4; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; float3 posInCascade = worldPosition - cascadePosDistance.xyz; float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; - if (any(cascadeUV < 0) || any(cascadeUV > 1)) - continue; - // TODO: sample mip first float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); - if (cascadeDistance < 1.0f) + if (cascadeDistance < 1.0f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) { distance = cascadeDistance * cascadeMaxDistance; break; From f8670a497e5a0b5524e0bfe159e4c31a8dc5961d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 10:10:23 +0100 Subject: [PATCH 050/144] Fix crash when loading model SDF on older GPUs (d3d10) --- Source/Engine/Content/Assets/Model.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 79a7d869c..507360611 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -13,6 +13,7 @@ #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" @@ -121,9 +122,16 @@ protected: REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true); +static byte EnableModelSDF = 0; + Model::Model(const SpawnParams& params, const AssetInfo* info) : ModelBase(params, info, StreamingGroups::Instance()->Models()) { + if (EnableModelSDF == 0 && GPUDevice::Instance) + { + const bool enable = GPUDevice::Instance->GetFeatureLevel() >= FeatureLevel::SM5; + EnableModelSDF = enable ? 1 : 2; + } } Model::~Model() @@ -598,6 +606,8 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData) { + if (EnableModelSDF == 2) + return true; // Not supported ScopeLock lock(Locker); if (!HasAnyLODInitialized()) return true; @@ -885,7 +895,7 @@ Asset::LoadResult Model::load() // Load SDF auto chunk15 = GetChunk(15); - if (chunk15 && chunk15->IsLoaded()) + if (chunk15 && chunk15->IsLoaded() && EnableModelSDF == 1) { MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size()); int32 version; From 4a18185e8155184ee8a287e643c048521bb48c83 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 11:41:17 +0100 Subject: [PATCH 051/144] Add **Sample Global SDF** node to particles --- Source/Editor/Surface/Archetypes/Textures.cs | 2 +- .../Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp | 7 +++++++ .../Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp | 9 +++++++++ Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- .../MaterialGenerator/MaterialGenerator.Textures.cpp | 1 - 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index b1a39bcb6..beed72475 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -363,7 +363,7 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 14, Title = "Sample Global SDF", Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.", - Flags = NodeFlags.MaterialGraph, + Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, Size = new Vector2(200, 20), Elements = new[] { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index f21777389..9f887503d 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -124,6 +124,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, value = Value::Zero; break; } + // Sample Global SDF + case 14: + { + // Not supported + value = Value::Zero; + break; + } default: break; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index 8f2c76b64..1f1166418 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -303,6 +303,15 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val loadTexture(node, box, copy, value); break; } + // Sample Global SDF + case 14: + { + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + value = writeLocal(VariantType::Float, String::Format(TEXT("SampleGlobalSDF({0}, {0}_Tex, {1})"), param.ShaderName, worldPosition.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + break; + } default: break; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index e5b39d685..e9024ee9b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -216,7 +216,7 @@ void GlobalSignDistanceFieldPass::Dispose() bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) { auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; - if (sdfData && sdfData->LastFrameUsed == Engine::FrameCount) + if (sdfData && sdfData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use SDF from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) { result = sdfData->Result; return false; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index a5e03f124..bff013718 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -427,7 +427,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); value = writeLocal(VariantType::Float, String::Format(TEXT("SampleGlobalSDF({0}, {0}_Tex, {1})"), param.ShaderName, worldPosition.Value), node); _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); - //float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) break; } default: From a5af0a1c819121b81c550e9fc9e1e9cf3ac4ab97 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 11:42:09 +0100 Subject: [PATCH 052/144] Fix game build --- Source/Engine/Content/Assets/Model.cpp | 11 +++++++++-- Source/Engine/Content/Assets/Model.h | 2 +- Source/Engine/Graphics/Models/ModelData.cpp | 11 +++++++++++ Source/Engine/Graphics/Models/ModelData.h | 12 +----------- .../Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 ++ Source/Engine/Tools/ModelTool/ModelTool.h | 3 +-- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 507360611..5b7ebac9b 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -617,17 +617,24 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData) LOG(Warning, "Cannot generate SDF for virtual models on a main thread."); return true; } - cacheData &= Storage != nullptr; // Cache only if has storage linked lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1); // Generate SDF +#if USE_EDITOR + cacheData &= Storage != nullptr; // Cache only if has storage linked MemoryWriteStream sdfStream; - if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, cacheData ? &sdfStream : nullptr, GetPath())) + MemoryWriteStream* outputStream = cacheData ? &sdfStream : nullptr; +#else + class MemoryWriteStream* outputStream = nullptr; +#endif + if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath())) return true; +#if USE_EDITOR // Set asset data if (cacheData) GetOrCreateChunk(15)->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); +#endif return false; } diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index d58fb469b..15744148f 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -212,7 +212,7 @@ public: /// Can be called in async in case of SDF generation on a CPU (assuming model is not during rendering). /// The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead. /// The index of the LOD to use for the SDF building. - /// If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets. + /// If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets or in build. /// True if failed, otherwise false. API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true); diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index cf7657039..f5f6ce94f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -531,6 +531,17 @@ void MeshData::TransformBuffer(const Matrix& matrix) } } +void MeshData::NormalizeBlendWeights() +{ + ASSERT(Positions.Count() == BlendWeights.Count()); + for (int32 i = 0; i < Positions.Count(); i++) + { + const float sum = BlendWeights[i].SumValues(); + const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; + BlendWeights[i] *= invSum; + } +} + void MeshData::Merge(MeshData& other) { // Merge index buffer (and remap indices) diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 2a7164123..364400384 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Common.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/Int4.h" @@ -281,16 +280,7 @@ public: /// /// Normalizes the blend weights. Requires to have vertices with positions and blend weights setup. /// - void NormalizeBlendWeights() - { - ASSERT(Positions.Count() == BlendWeights.Count()); - for (int32 i = 0; i < Positions.Count(); i++) - { - const float sum = BlendWeights[i].SumValues(); - const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; - BlendWeights[i] *= invSum; - } - } + void NormalizeBlendWeights(); /// /// Merges this mesh data with the specified other mesh. diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index e9024ee9b..5a64860b9 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -183,7 +183,7 @@ bool GlobalSignDistanceFieldPass::setupResources() return false; } -#if USE_EDITOR +#if COMPILE_WITH_DEV_ENV void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 85776e5d6..36aaf0b15 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -12,7 +12,9 @@ #include "Engine/Threading/JobSystem.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 8173df383..15a7d4def 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -12,7 +12,6 @@ #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" -class MemoryWriteStream; class JsonWriter; /// @@ -186,7 +185,7 @@ public: // Optional: inputModel or modelData // Optional: outputSDF or null, outputStream or null - static bool GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName); + static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName); #if USE_EDITOR public: From 1271a337c5b9d8f15787409bfde7c38c61c429f6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 11:42:39 +0100 Subject: [PATCH 053/144] Fix sampling Global SDF if unsupported (return large distance) --- Source/Shaders/GlobalSignDistanceField.hlsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 2e3f5ddd0..5bd516f92 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -57,6 +57,8 @@ struct GlobalSDFHit float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) { float distance = data.CascadePosDistance[3].w * 2.0f; + if (distance <= 0.0f) + return 60000; UNROLL for (uint cascade = minCascade; cascade < 4; cascade++) { From 6c4e61a924df2053b6eccb37661f5db867e9f388 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 12:53:30 +0100 Subject: [PATCH 054/144] Add `SampleGlobalSDFGradient` to get normal vector of Global SDF --- Content/Shaders/GlobalSignDistanceField.flax | 4 +-- Source/Shaders/GlobalSignDistanceField.hlsl | 31 +++++++++++++++++++ Source/Shaders/GlobalSignDistanceField.shader | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 88e8ebcc6..671925ceb 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b4917350728a690a7b14ddd2c78f2b18a009c5a93663cf7b8853913951b58a6 -size 8265 +oid sha256:ddf6d7c9411162a9e6ba9a762d8c95374a3c78b2ca9ee34c5695869eb56cbf6b +size 8375 diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 5bd516f92..26bbc7957 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -76,6 +76,37 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 return distance; } +// Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) +{ + float3 gradient = float3(0, 0.00001f, 0); + float distance = data.CascadePosDistance[3].w * 2.0f; + if (distance <= 0.0f) + return gradient; + UNROLL + for (uint cascade = minCascade; cascade < 4; cascade++) + { + float4 cascadePosDistance = data.CascadePosDistance[cascade]; + float cascadeMaxDistance = cascadePosDistance.w * 2; + float3 posInCascade = worldPosition - cascadePosDistance.xyz; + float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f; + float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); + if (cascadeDistance < 0.9f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) + { + float texelOffset = 0.5f / data.Resolution; + float xp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x + texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float xn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x - texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float yp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y + texelOffset, cascadeUV.z), 0).x; + float yn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y - texelOffset, cascadeUV.z), 0).x; + float zp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z + texelOffset), 0).x; + float zn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z - texelOffset), 0).x; + gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeMaxDistance; + break; + } + } + return gradient; +} + // Ray traces the Global SDF. GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4], Texture3D mips[4], const GlobalSDFTrace trace, uint minCascade = 0) { diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index 8b4d4d69f..dac95b00c 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -210,6 +210,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target float3 color = saturate(hit.StepsCount / 80.0f).xxx; if (!hit.IsHit()) color.rg *= 0.4f; + //else color.rgb = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, hit.GetHitPosition(trace))); return float4(color, 1); } From a917397090262ca843a8c1601b20fe1f9edcbbf3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 15:36:00 +0100 Subject: [PATCH 055/144] Add `Conform to Global SDF` to GPU particles --- Content/Shaders/GlobalSignDistanceField.flax | 4 +-- .../Surface/Archetypes/ParticleModules.cs | 25 +++++++++++++++ ...rticleEmitterGraph.CPU.ParticleModules.cpp | 6 ++++ ...rticleEmitterGraph.GPU.ParticleModules.cpp | 32 +++++++++++++++++++ .../Particles/Graph/ParticleEmitterGraph.h | 1 + Source/Shaders/GlobalSignDistanceField.hlsl | 9 +++--- Source/Shaders/GlobalSignDistanceField.shader | 9 +++++- 7 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 671925ceb..54dd366ac 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddf6d7c9411162a9e6ba9a762d8c95374a3c78b2ca9ee34c5695869eb56cbf6b -size 8375 +oid sha256:c283024b5b907b9ee8eeb43ee95f8f825fe3cca959461b920d93f2978fdfd3bf +size 8460 diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index 916ed76ea..498d83c68 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -1362,6 +1362,31 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(-0.5f + 0, "Surface Thickness", true, typeof(float), 5, 8), }, }, + new NodeArchetype + { + TypeID = 335, + Create = CreateParticleModuleNode, + Title = "Conform to Global SDF", + Description = "Applies the force vector to particles to conform around Global SDF", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 4 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Update, + 5.0f, + 2000.0f, + 1.0f, + 5000.0f, + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(-0.5f, "Attraction Speed", true, typeof(float), 0, 2), + NodeElementArchetype.Factory.Input(-0.5f + 1.0f, "Attraction Force", true, typeof(float), 1, 3), + NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Stick Distance", true, typeof(float), 2, 4), + NodeElementArchetype.Factory.Input(-0.5f + 3.0f, "Stick Force", true, typeof(float), 3, 5), + }, + }, GetParticleAttribute(ModuleType.Update, 350, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero), GetParticleAttribute(ModuleType.Update, 351, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Update, 352, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 39b1f8b9b..dd5fb6ea5 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -1463,6 +1463,12 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Not supported break; } + // Conform to Global SDF + case 335: + { + // Not supported + break; + } #undef COLLISION_BEGIN #undef COLLISION_INPUTS_FETCH diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index addce771b..fd1c000fe 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -877,6 +877,38 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } + // Conform to Global SDF + case 335: + { + auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); + auto velocity = AccessParticleAttribute(node, nodeGpu->Attributes[1], AccessMode::ReadWrite); + auto mass = AccessParticleAttribute(node, nodeGpu->Attributes[2], AccessMode::Read); + + const Value attractionSpeed = GetValue(node->GetBox(0), 2).AsFloat(); + const Value attractionForce = GetValue(node->GetBox(1), 3).AsFloat(); + const Value stickDistance = GetValue(node->GetBox(2), 4).AsFloat(); + const Value stickForce = GetValue(node->GetBox(3), 5).AsFloat(); + + auto param = findOrAddGlobalSDF(); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + _writer.Write( + TEXT( + " {{\n" + " // Conform to Global SDF\n" + " float dist;\n" + " float3 dir = normalize(SampleGlobalSDFGradient({3}, {3}_Tex, {0}, dist));\n" + " if (dist > 0) dir *= -1;\n" + " float distToSurface = abs(dist);\n" + " float spdNormal = dot(dir, {1});\n" + " float ratio = smoothstep(0.0f, {6} * 2.0f, distToSurface);\n" + " float tgtSpeed = {4} * ratio;\n" + " float deltaSpeed = tgtSpeed - spdNormal;\n" + " float3 deltaVelocity = dir * (sign(deltaSpeed) * min(abs(deltaSpeed), DeltaTime * lerp({7}, {5}, ratio)) / max({2}, PARTICLE_THRESHOLD));\n" + " if (dist < 500) {1} += deltaVelocity;\n" + " }}\n" + ), position.Value, velocity.Value, mass.Value, param.ShaderName, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); + break; + } #undef COLLISION_BEGIN #undef COLLISION_LOGIC diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 8c803726e..3ab77c0b9 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -415,6 +415,7 @@ public: #undef CASE_SET_PARTICLE_ATTRIBUTE // Conform to Sphere case GRAPH_NODE_MAKE_TYPE(15, 305): + case GRAPH_NODE_MAKE_TYPE(15, 335): // Conform to Global SDF { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 26bbc7957..bb33316bc 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -77,11 +77,11 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 } // Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. -float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, out float distance, uint minCascade = 0) { float3 gradient = float3(0, 0.00001f, 0); - float distance = data.CascadePosDistance[3].w * 2.0f; - if (distance <= 0.0f) + distance = 60000; + if (data.CascadePosDistance[3].w <= 0.0f) return gradient; UNROLL for (uint cascade = minCascade; cascade < 4; cascade++) @@ -93,7 +93,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4] float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0); if (cascadeDistance < 0.9f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) { - float texelOffset = 0.5f / data.Resolution; + float texelOffset = 1.0f / data.Resolution; float xp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x + texelOffset, cascadeUV.y, cascadeUV.z), 0).x; float xn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x - texelOffset, cascadeUV.y, cascadeUV.z), 0).x; float yp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y + texelOffset, cascadeUV.z), 0).x; @@ -101,6 +101,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4] float zp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z + texelOffset), 0).x; float zn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z - texelOffset), 0).x; gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeMaxDistance; + distance = cascadeDistance * cascadeMaxDistance; break; } } diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index dac95b00c..82cccbd65 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -210,7 +210,14 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target float3 color = saturate(hit.StepsCount / 80.0f).xxx; if (!hit.IsHit()) color.rg *= 0.4f; - //else color.rgb = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, hit.GetHitPosition(trace))); +#if 0 + else + { + // Debug draw SDF normals + float dst; + color.rgb = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, hit.GetHitPosition(trace), dst)) * 0.5f + 0.5f; + } +#endif return float4(color, 1); } From 3e9b6caa1cacfccde746ada5f43a4555b668487d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 15:36:30 +0100 Subject: [PATCH 056/144] Fix sorting items in various contextual list popups in Editor --- .../Editor/CustomEditors/Dedicated/RagdollEditor.cs | 2 +- .../Editor/CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../CustomEditors/Dedicated/UIControlEditor.cs | 2 +- Source/Editor/GUI/ItemsListContextMenu.cs | 13 +++++++++++++ Source/Editor/GUI/Popups/ActorSearchPopup.cs | 2 +- Source/Editor/GUI/Popups/AssetSearchPopup.cs | 2 +- Source/Editor/GUI/Popups/ScriptSearchPopup.cs | 2 +- Source/Editor/GUI/Popups/TypeSearchPopup.cs | 2 +- Source/Editor/Surface/Archetypes/Particles.cs | 2 +- Source/Editor/Surface/VisjectSurfaceWindow.cs | 2 +- Source/Editor/Windows/Assets/VisualScriptWindow.cs | 2 +- 11 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index fa72e06c0..2c3aebcc1 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -273,7 +273,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(item); } cm.ItemClicked += item => action((string)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d101d2ff2..8febb3958 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } cm.ItemClicked += item => AddScript((ScriptType)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(this, button.BottomLeft); } diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index ac26de204..55473d42c 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -642,7 +642,7 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.AddItem(new TypeSearchPopup.TypeItemView(controlTypes[i])); } cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 1c62a6556..3bfe912c4 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -263,6 +263,19 @@ namespace FlaxEditor.GUI PerformLayout(true); _searchBox.Focus(); } + + /// + /// Sorts the items list (by item name by default). + /// + public void SortItems() + { + ItemsPanel.SortChildren(); + if (_categoryPanels != null) + { + for (int i = 0; i < _categoryPanels.Count; i++) + _categoryPanels[i].SortChildren(); + } + } /// /// Adds the item to the view and registers for the click event. diff --git a/Source/Editor/GUI/Popups/ActorSearchPopup.cs b/Source/Editor/GUI/Popups/ActorSearchPopup.cs index b5859a52b..92482c18e 100644 --- a/Source/Editor/GUI/Popups/ActorSearchPopup.cs +++ b/Source/Editor/GUI/Popups/ActorSearchPopup.cs @@ -67,7 +67,7 @@ namespace FlaxEditor.GUI { Find(Level.GetScene(i)); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/AssetSearchPopup.cs b/Source/Editor/GUI/Popups/AssetSearchPopup.cs index 3f81c2e2a..8f5e007a0 100644 --- a/Source/Editor/GUI/Popups/AssetSearchPopup.cs +++ b/Source/Editor/GUI/Popups/AssetSearchPopup.cs @@ -122,7 +122,7 @@ namespace FlaxEditor.GUI if (project.Content != null) FindAssets(project.Content.Folder); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs index d8c1e9ee1..ece346895 100644 --- a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs +++ b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs @@ -78,7 +78,7 @@ namespace FlaxEditor.GUI { Find(Level.GetScene(i)); } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index 9ab0d5b54..40396652b 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -95,7 +95,7 @@ namespace FlaxEditor.GUI } } } - SortChildren(); + SortItems(); } private void OnItemClicked(Item item) diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 17f96228f..9585dff88 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes }); } cm.ItemClicked += item => AddModule((ushort)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(this, button.BottomLeft); } diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 6ae949000..08640be17 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -461,7 +461,7 @@ namespace FlaxEditor.Surface cm.AddItem(item); } cm.ItemClicked += OnAddParameterItemClicked; - cm.SortChildren(); + cm.SortItems(); cm.Show(button.Parent, button.BottomLeft); } diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 2fab58b9f..bb3844eab 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -188,7 +188,7 @@ namespace FlaxEditor.Windows.Assets cm.AddItem(item); } cm.ItemClicked += (ItemsListContextMenu.Item item) => window.SetParamType(index, (ScriptType)item.Tag); - cm.SortChildren(); + cm.SortItems(); cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition)); }); b.Enabled = window._canEdit; From 5a0d8f44ffa92386512ff9fcc03972ed470b18fd Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 15:36:47 +0100 Subject: [PATCH 057/144] Fix crash on Editor closing --- Source/Engine/UI/GUI/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 0031e07bd..8cd9c932b 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -313,7 +313,7 @@ namespace FlaxEngine.GUI /// /// Gets the control DPI scale factor (1 is default). Includes custom DPI scale. /// - public float DpiScale => RootWindow?.Window.DpiScale ?? Platform.DpiScale; + public float DpiScale => RootWindow?.Window?.DpiScale ?? Platform.DpiScale; /// /// Gets screen position of the control (upper left corner). From 2bf0a7af8ee1016af3d58bce610510b2c60f5e99 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 15:37:01 +0100 Subject: [PATCH 058/144] Fix opening particle emitter editor window if shader compilation fails --- Source/Editor/Windows/Assets/ParticleEmitterWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 226cea0b0..1c62cc712 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -261,6 +261,9 @@ namespace FlaxEditor.Windows.Assets base.OnSurfaceEditingStart(); } + /// + protected override bool CanEditSurfaceOnAssetLoadError => true; + /// protected override bool SaveToOriginal() { From f608d2537f90058a5b548fb1104cf21683523122 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 25 Mar 2022 15:45:49 +0100 Subject: [PATCH 059/144] Fix --- .../Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index fd1c000fe..1a944ba7d 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -904,7 +904,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " float tgtSpeed = {4} * ratio;\n" " float deltaSpeed = tgtSpeed - spdNormal;\n" " float3 deltaVelocity = dir * (sign(deltaSpeed) * min(abs(deltaSpeed), DeltaTime * lerp({7}, {5}, ratio)) / max({2}, PARTICLE_THRESHOLD));\n" - " if (dist < 500) {1} += deltaVelocity;\n" + " {1} += deltaVelocity;\n" " }}\n" ), position.Value, velocity.Value, mass.Value, param.ShaderName, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); break; From bcc4a2c0a47f39c607d52a23099406ca25f4da49 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 10:27:33 +0200 Subject: [PATCH 060/144] Add `Position (Global SDF)` particle module --- .../Surface/Archetypes/ParticleModules.cs | 15 ++++++++++++- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 6 ++++++ ...rticleEmitterGraph.GPU.ParticleModules.cpp | 21 +++++++++++++++++++ .../Particles/Graph/ParticleEmitterGraph.h | 3 ++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index 498d83c68..a9f4f5f5e 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -888,7 +888,6 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.ComboBox(0, -10.0f, 160, 2, typeof(ParticleModelFacingMode)), }, }, - new NodeArchetype { TypeID = 214, @@ -912,6 +911,20 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Velocity Scale", true, typeof(float), 2, 4), }, }, + new NodeArchetype + { + TypeID = 215, + Create = CreateParticleModuleNode, + Title = "Position (Global SDF)", + Description = "Places the particles on Global SDF surface (uses current particle position to snap it to SDF)", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 0 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Initialize, + }, + }, GetParticleAttribute(ModuleType.Initialize, 250, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero), GetParticleAttribute(ModuleType.Initialize, 251, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Initialize, 252, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index dd5fb6ea5..4b6b36b55 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -1204,6 +1204,12 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } + // Position (Global SDF) + case 215: + { + // Not supported + break; + } // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index 1a944ba7d..10f3b4bd9 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -610,6 +610,27 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, velocityAttr.Value, center.Value, rotationSpeed.Value, velocityScale.Value, customDataOffset); break; } + // Position (Global SDF) + case 215: + { + auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); + auto param = findOrAddGlobalSDF(); + String wsPos = position.Value; + if (((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local) + wsPos = String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), wsPos); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + _writer.Write( + TEXT( + " {{\n" + " // Position (Global SDF)\n" + " float3 wsPos = {2};\n" + " float dist;\n" + " float3 dir = -normalize(SampleGlobalSDFGradient({1}, {1}_Tex, wsPos, dist));\n" + " {0} += dir * dist;\n" + " }}\n" + ), position.Value, param.ShaderName, wsPos); + break; + } // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 3ab77c0b9..566fe5b51 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -355,7 +355,7 @@ public: USE_ATTRIBUTE(Mass, Float, 2); break; } - // Position (plane/box surface/box volume/cylinder/line/sphere/circle/disc/torus) + // Position (plane/box surface/box volume/cylinder/line/sphere/circle/disc/torus/Global SDF) case GRAPH_NODE_MAKE_TYPE(15, 202): case GRAPH_NODE_MAKE_TYPE(15, 203): case GRAPH_NODE_MAKE_TYPE(15, 204): @@ -366,6 +366,7 @@ public: case GRAPH_NODE_MAKE_TYPE(15, 209): case GRAPH_NODE_MAKE_TYPE(15, 210): case GRAPH_NODE_MAKE_TYPE(15, 211): + case GRAPH_NODE_MAKE_TYPE(15, 215): { USE_ATTRIBUTE(Position, Vector3, 0); break; From 3a9edabd032f914ad20f8aaaee5f0fcdc1467ba1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 13:39:20 +0200 Subject: [PATCH 061/144] Add `Collision (Global SDF)` particle module --- Content/Editor/Primitives/Sphere.flax | 4 +- .../Surface/Archetypes/ParticleModules.cs | 30 +++++- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 6 ++ ...rticleEmitterGraph.GPU.ParticleModules.cpp | 94 +++++++++++-------- .../ParticleEmitterGraph.GPU.Particles.cpp | 6 +- .../Graph/GPU/ParticleEmitterGraph.GPU.h | 10 ++ .../Particles/Graph/ParticleEmitterGraph.h | 3 +- 7 files changed, 106 insertions(+), 47 deletions(-) diff --git a/Content/Editor/Primitives/Sphere.flax b/Content/Editor/Primitives/Sphere.flax index 9ec03d998..651bc7afc 100644 --- a/Content/Editor/Primitives/Sphere.flax +++ b/Content/Editor/Primitives/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:215797e4f6486d74eef5fcdc17bd574d4da3d32fb363e796c5ca04d9b93c6c6c -size 24621 +oid sha256:0bd78fb9c7b970d7661cff626568cd5fada5a5071740b7771241288d9bcb7995 +size 40605 diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index a9f4f5f5e..30f902a57 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -1357,7 +1357,7 @@ namespace FlaxEditor.Surface.Archetypes true, (int)ModuleType.Update, false, // Invert - 0.0f, // Radius + 5.0f, // Radius 0.0f, // Roughness 0.1f, // Elasticity 0.0f, // Friction @@ -1400,6 +1400,34 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(-0.5f + 3.0f, "Stick Force", true, typeof(float), 3, 5), }, }, + new NodeArchetype + { + TypeID = 336, + Create = CreateParticleModuleNode, + Title = "Collision (Global SDF)", + Description = "Collides particles with the scene Global SDF", + Flags = DefaultModuleFlags, + Size = new Vector2(200, 5 * Surface.Constants.LayoutOffsetY), + DefaultValues = new object[] + { + true, + (int)ModuleType.Update, + false, // Invert + 5.0f, // Radius + 0.4f, // Roughness + 0.1f, // Elasticity + 0.0f, // Friction + 0.0f, // Lifetime Loss + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(-0.5f + 0, "Radius", true, typeof(float), 0, 3), + NodeElementArchetype.Factory.Input(-0.5f + 1, "Roughness", true, typeof(float), 1, 4), + NodeElementArchetype.Factory.Input(-0.5f + 2, "Elasticity", true, typeof(float), 2, 5), + NodeElementArchetype.Factory.Input(-0.5f + 3, "Friction", true, typeof(float), 3, 6), + NodeElementArchetype.Factory.Input(-0.5f + 4, "Lifetime Loss", true, typeof(float), 4, 7), + }, + }, GetParticleAttribute(ModuleType.Update, 350, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero), GetParticleAttribute(ModuleType.Update, 351, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Update, 352, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 4b6b36b55..e37378a3a 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -1475,6 +1475,12 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Not supported break; } + // Collision (Global SDF) + case 336: + { + // Not supported + break; + } #undef COLLISION_BEGIN #undef COLLISION_INPUTS_FETCH diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index 10f3b4bd9..36a10a522 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -616,7 +616,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); auto param = findOrAddGlobalSDF(); String wsPos = position.Value; - if (((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local) + if (IsLocalSimulationSpace()) wsPos = String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), wsPos); _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); _writer.Write( @@ -666,13 +666,8 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) case 330: { COLLISION_BEGIN(); - - auto planePositionBox = node->GetBox(5); - auto planeNormalBox = node->GetBox(6); - - const Value planePosition = GetValue(planePositionBox, 8).AsVector3(); - const Value planeNormal = GetValue(planeNormalBox, 9).AsVector3(); - + const Value planePosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value planeNormal = GetValue(node->GetBox(6), 9).AsVector3(); _writer.Write( TEXT( " {{\n" @@ -699,13 +694,8 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) case 331: { COLLISION_BEGIN(); - - auto spherePositionBox = node->GetBox(5); - auto sphereRadiusBox = node->GetBox(6); - - const Value spherePosition = GetValue(spherePositionBox, 8).AsVector3(); - const Value sphereRadius = GetValue(sphereRadiusBox, 9).AsFloat(); - + const Value spherePosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value sphereRadius = GetValue(node->GetBox(6), 9).AsFloat(); _writer.Write( TEXT( " {{\n" @@ -735,13 +725,8 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) case 332: { COLLISION_BEGIN(); - - auto boxPositionBox = node->GetBox(5); - auto boxSizeBox = node->GetBox(6); - - const Value boxPosition = GetValue(boxPositionBox, 8).AsVector3(); - const Value boxSize = GetValue(boxSizeBox, 9).AsVector3(); - + const Value boxPosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value boxSize = GetValue(node->GetBox(6), 9).AsVector3(); _writer.Write( TEXT( " {{\n" @@ -786,15 +771,9 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) case 333: { COLLISION_BEGIN(); - - auto cylinderPositionBox = node->GetBox(5); - auto cylinderHeightBox = node->GetBox(6); - auto cylinderRadiusBox = node->GetBox(7); - - const Value cylinderPosition = GetValue(cylinderPositionBox, 8).AsVector3(); - const Value cylinderHeight = GetValue(cylinderHeightBox, 9).AsFloat(); - const Value cylinderRadius = GetValue(cylinderRadiusBox, 10).AsFloat(); - + const Value cylinderPosition = GetValue(node->GetBox(5), 8).AsVector3(); + const Value cylinderHeight = GetValue(node->GetBox(6), 9).AsFloat(); + const Value cylinderRadius = GetValue(node->GetBox(7), 10).AsFloat(); _writer.Write( TEXT( " {{\n" @@ -842,13 +821,8 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) case 334: { COLLISION_BEGIN(); - - auto surfaceThicknessBox = node->GetBox(5); - - const Value surfaceThickness = GetValue(surfaceThicknessBox, 8).AsFloat(); - + const Value surfaceThickness = GetValue(node->GetBox(5), 8).AsFloat(); const auto sceneDepthTexture = findOrAddSceneTexture(MaterialSceneTextures::SceneDepth); - _writer.Write( TEXT( " {{\n" @@ -929,7 +903,51 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " }}\n" ), position.Value, velocity.Value, mass.Value, param.ShaderName, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); break; - } + } + // Collision (Global SDF) + case 336: + { + COLLISION_BEGIN(); + auto param = findOrAddGlobalSDF(); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + const Char* format = IsLocalSimulationSpace() + ? TEXT( + " {{\n" + " // Collision (Global SDF)\n" + " float3 nextPos = {0} + {1} * DeltaTime;\n" + " nextPos = mul(float4(nextPos, 1), WorldMatrix).xyz;\n" + " float dist = SampleGlobalSDF({10}, {10}_Tex, nextPos);\n" + " if (dist < {5})\n" + " {{\n" + " {0} = mul(float4({0}, 1), WorldMatrix).xyz;\n" + " float3 n = normalize(SampleGlobalSDFGradient({10}, {10}_Tex, {0}, dist));\n" + " {0} += n * -dist;\n" + " {0} = mul(float4({0}, 1), InvWorldMatrix).xyz;\n" + COLLISION_LOGIC() + " }}\n" + ) + : TEXT( + " {{\n" + " // Collision (Global SDF)\n" + " float3 nextPos = {0} + {1} * DeltaTime;\n" + " float dist = SampleGlobalSDF({10}, {10}_Tex, nextPos);\n" + " if (dist < {5})\n" + " {{\n" + " float3 n = normalize(SampleGlobalSDFGradient({10}, {10}_Tex, {0}, dist));\n" + " {0} += n * -dist;\n" + COLLISION_LOGIC() + " }}\n" + ); + _writer.Write(format, + // 0-4 + positionAttr.Value, velocityAttr.Value, ageAttr.Value, invert, sign, + // 5-9 + radius.Value, roughness.Value, elasticity.Value, friction.Value, lifetimeLoss.Value, + // 10 + param.ShaderName + ); + break; + } #undef COLLISION_BEGIN #undef COLLISION_LOGIC diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 65935f381..7b4dfefb1 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -427,14 +427,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va } // Particle Position (world space) case 212: - { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); - if (((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local) - { + if (IsLocalSimulationSpace()) value = writeLocal(VariantType::Vector3, String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), value.Value), node); - } break; - } // Random Float Range case 213: { diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h index 6e35f96af..988883965 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h @@ -156,6 +156,16 @@ private: { return box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : Value::Zero; } + + bool IsLocalSimulationSpace() const + { + return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local; + } + + bool IsWorldSimulationSpace() const + { + return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::World; + } }; #endif diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 566fe5b51..d03d7ebea 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -430,12 +430,13 @@ public: USE_ATTRIBUTE(Position, Vector3, 0); break; } - // Collision (plane/sphere/box/cylinder/depth) + // Collision (plane/sphere/box/cylinder/depth/Global SDF) case GRAPH_NODE_MAKE_TYPE(15, 330): case GRAPH_NODE_MAKE_TYPE(15, 331): case GRAPH_NODE_MAKE_TYPE(15, 332): case GRAPH_NODE_MAKE_TYPE(15, 333): case GRAPH_NODE_MAKE_TYPE(15, 334): + case GRAPH_NODE_MAKE_TYPE(15, 336): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); From 4938c7e64ba0f9c8dca59373894bdd56af4f4b1b Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 13:39:45 +0200 Subject: [PATCH 062/144] Fix ParticleEffectEditor issues after emitter editing if selected --- .../Dedicated/ParticleEffectEditor.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index bfdcc57f6..664721258 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -26,6 +26,26 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + private object ParameterGet(object instance, GraphParameter parameter, object tag) + { + if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter) + return particleEffect.GetParameterValue(effectParameter.TrackName, parameter.Name); + return null; + } + + private void ParameterSet(object instance, object value, GraphParameter parameter, object tag) + { + if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter) + particleEffect.SetParameterValue(effectParameter.TrackName, parameter.Name, value); + } + + private object ParameterDefaultValue(object instance, GraphParameter parameter, object tag) + { + if (tag is ParticleEffectParameter effectParameter) + return effectParameter.DefaultValue; + return null; + } + /// public override void Initialize(LayoutElementsContainer layout) { @@ -48,11 +68,7 @@ namespace FlaxEditor.CustomEditors.Dedicated group.Panel.Open(false); var data = SurfaceUtils.InitGraphParameters(parametersGroup); - SurfaceUtils.DisplayGraphParameters(group, data, - (instance, parameter, tag) => ((ParticleEffect)instance).GetParameterValue(trackName, parameter.Name), - (instance, value, parameter, tag) => ((ParticleEffect)instance).SetParameterValue(trackName, parameter.Name, value), - Values, - (instance, parameter, tag) => ((ParticleEffectParameter)tag).DefaultValue); + SurfaceUtils.DisplayGraphParameters(group, data, ParameterGet, ParameterSet, Values, ParameterDefaultValue); } } From a685962d3206d95181d63642a365fa393e4d8ea5 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 13:58:27 +0200 Subject: [PATCH 063/144] Add `Sample Global SDF Gradient` node to materials and particles --- Source/Editor/Surface/Archetypes/Textures.cs | 14 ++++++++++++++ .../MaterialGenerator.Textures.cpp | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index beed72475..044ee6430 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -371,6 +371,20 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1), } }, + new NodeArchetype + { + TypeID = 15, + Title = "Sample Global SDF Gradient", + Description = "Samples the Global SDF to get the gradient and distance to the closest surface (in world-space). Normalize gradient to get SDF surface normal vector. Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.", + Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, + Size = new Vector2(260, 40), + Elements = new[] + { + NodeElementArchetype.Factory.Output(0, "Gradient", typeof(Vector3), 0), + NodeElementArchetype.Factory.Output(1, "Distance", typeof(float), 2), + NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1), + } + }, }; } } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index bff013718..7ed372a32 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -429,6 +429,21 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); break; } + // Sample Global SDF Gradient + case 15: + { + auto gradientBox = node->GetBox(0); + auto distanceBox = node->GetBox(2); + auto param = findOrAddGlobalSDF(); + Value worldPosition = tryGetValue(node->GetBox(1), Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz"))).Cast(VariantType::Vector3); + auto distance = writeLocal(VariantType::Float, node); + auto gradient = writeLocal(VariantType::Vector3, String::Format(TEXT("SampleGlobalSDFGradient({0}, {0}_Tex, {1}, {2})"), param.ShaderName, worldPosition.Value, distance.Value), node); + _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); + gradientBox->Cache = gradient; + distanceBox->Cache = distance; + value = box == gradientBox ? gradient : distance; + break; + } default: break; } From e56b280f5b4d10028d340e2bf8a3178f73a6ef9e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 14:01:29 +0200 Subject: [PATCH 064/144] Format code for shader generators --- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 74 +++++------ .../ParticleEmitterGraph.CPU.Particles.cpp | 68 +++++----- .../Graph/CPU/ParticleEmitterGraph.CPU.cpp | 12 +- ...rticleEmitterGraph.GPU.ParticleModules.cpp | 68 +++++----- .../ParticleEmitterGraph.GPU.Particles.cpp | 125 ++++++------------ .../GPU/ParticleEmitterGraph.GPU.Textures.cpp | 10 +- .../Graph/GPU/ParticleEmitterGraph.GPU.cpp | 2 +- .../MaterialGenerator.Layers.cpp | 14 +- .../MaterialGenerator.Material.cpp | 84 ++++++------ .../MaterialGenerator.Parameters.cpp | 2 +- .../MaterialGenerator.Particles.cpp | 26 ++-- .../MaterialGenerator.Textures.cpp | 20 +-- .../MaterialGenerator.Tools.cpp | 10 +- 13 files changed, 234 insertions(+), 281 deletions(-) diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index e37378a3a..986072fa3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -121,14 +121,14 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) // Calculate particles to spawn during this frame switch (node->TypeID) { - // Constant Spawn Rate + // Constant Spawn Rate case 100: { const float rate = Math::Max((float)TryGetValue(node->GetBox(0), node->Values[2]), 0.0f); spawnCount += rate * context.DeltaTime; break; } - // Single Burst + // Single Burst case 101: { const bool isFirstUpdate = (context.Data->Time - context.DeltaTime) <= 0.0f; @@ -139,7 +139,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) } break; } - // Periodic + // Periodic case 102: { float& nextSpawnTime = data.NextSpawnTime; @@ -152,7 +152,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) } break; } - // Periodic Burst (range) + // Periodic Burst (range) case 103: { float& nextSpawnTime = data.NextSpawnTime; @@ -186,7 +186,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* switch (node->TypeID) { - // Orient Sprite + // Orient Sprite case 201: case 303: { @@ -229,7 +229,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Orient Model + // Orient Model case 213: case 309: { @@ -246,7 +246,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Update Age + // Update Age case 300: { PARTICLE_EMITTER_MODULE("Update Age"); @@ -259,7 +259,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Gravity/Force + // Gravity/Force case 301: case 304: { @@ -288,7 +288,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Conform to Sphere + // Conform to Sphere case 305: { PARTICLE_EMITTER_MODULE("Conform to Sphere"); @@ -351,7 +351,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (sphere) + // Kill (sphere) case 306: { PARTICLE_EMITTER_MODULE("Kill"); @@ -400,7 +400,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (box) + // Kill (box) case 307: { PARTICLE_EMITTER_MODULE("Kill"); @@ -454,7 +454,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Kill (custom) + // Kill (custom) case 308: { PARTICLE_EMITTER_MODULE("Kill (custom)"); @@ -492,7 +492,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Linear Drag + // Linear Drag case 310: { PARTICLE_EMITTER_MODULE("Linear Drag"); @@ -538,7 +538,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Turbulence + // Turbulence case 311: { PARTICLE_EMITTER_MODULE("Turbulence"); @@ -598,7 +598,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Set Attribute + // Set Attribute case 200: case 302: { @@ -630,7 +630,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Set Position/Lifetime/Age/.. + // Set Position/Lifetime/Age/.. case 250: case 251: case 252: @@ -688,7 +688,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* } break; } - // Position (sphere surface) + // Position (sphere surface) case 202: { PARTICLE_EMITTER_MODULE("Position"); @@ -734,7 +734,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (plane) + // Position (plane) case 203: { PARTICLE_EMITTER_MODULE("Position"); @@ -773,7 +773,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (circle) + // Position (circle) case 204: { PARTICLE_EMITTER_MODULE("Position"); @@ -817,7 +817,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (disc) + // Position (disc) case 205: { PARTICLE_EMITTER_MODULE("Position"); @@ -861,7 +861,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (box surface) + // Position (box surface) case 206: { PARTICLE_EMITTER_MODULE("Position"); @@ -912,7 +912,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (box volume) + // Position (box volume) case 207: { PARTICLE_EMITTER_MODULE("Position"); @@ -951,7 +951,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (cylinder) + // Position (cylinder) case 208: { PARTICLE_EMITTER_MODULE("Position"); @@ -997,7 +997,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (line) + // Position (line) case 209: { PARTICLE_EMITTER_MODULE("Position"); @@ -1036,7 +1036,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (torus) + // Position (torus) case 210: { PARTICLE_EMITTER_MODULE("Position"); @@ -1101,7 +1101,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (sphere volume) + // Position (sphere volume) case 211: { PARTICLE_EMITTER_MODULE("Position"); @@ -1147,13 +1147,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (depth) + // Position (depth) case 212: { // Not supported break; } - // Position (spiral) + // Position (spiral) case 214: { PARTICLE_EMITTER_MODULE("Position"); @@ -1204,14 +1204,14 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Position (Global SDF) + // Position (Global SDF) case 215: { // Not supported break; } - // Helper macros for collision modules to share the code + // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ PARTICLE_EMITTER_MODULE("Collision"); \ auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; \ @@ -1251,7 +1251,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* velocityPtr += stride; \ agePtr += stride - // Collision (plane) + // Collision (plane) case 330: { COLLISION_BEGIN(); @@ -1293,7 +1293,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (sphere) + // Collision (sphere) case 331: { COLLISION_BEGIN(); @@ -1338,7 +1338,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (box) + // Collision (box) case 332: { COLLISION_BEGIN(); @@ -1398,7 +1398,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (cylinder) + // Collision (cylinder) case 333: { COLLISION_BEGIN(); @@ -1463,19 +1463,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #undef LOGIC break; } - // Collision (depth) + // Collision (depth) case 334: { // Not supported break; } - // Conform to Global SDF + // Conform to Global SDF case 335: { // Not supported break; } - // Collision (Global SDF) + // Collision (Global SDF) case 336: { // Not supported diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 9f887503d..0e7eec882 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -15,7 +15,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod auto& context = Context.Get(); switch (node->TypeID) { - // Get + // Get case 2: { int32 paramIndex; @@ -96,28 +96,28 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, { switch (node->TypeID) { - // Scene Texture + // Scene Texture case 6: { // Not supported value = Value::Zero; break; } - // Scene Depth + // Scene Depth case 8: { // Not supported value = Value::Zero; break; } - // Texture + // Texture case 11: { // TODO: support sampling textures in CPU particles value = Value::Zero; break; } - // Load Texture + // Load Texture case 13: { // TODO: support sampling textures in CPU particles @@ -141,18 +141,18 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va auto& context = Context.Get(); switch (node->TypeID) { - // Linearize Depth + // Linearize Depth case 7: { // TODO: support Linearize Depth in CPU particles value = Value::Zero; break; } - // Time + // Time case 8: value = box->ID == 0 ? context.Data->Time : context.DeltaTime; break; - // Transform Position To Screen UV + // Transform Position To Screen UV case 9: { GET_VIEW(); @@ -176,7 +176,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: { byte* ptr = ACCESS_PARTICLE_ATTRIBUTE(0); @@ -204,7 +204,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node } break; } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const auto particleIndex = tryGetValue(node->GetBox(1), context.ParticleIndex); @@ -233,61 +233,61 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node } break; } - // Particle Position + // Particle Position case 101: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Lifetime + // Particle Lifetime case 102: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Age + // Particle Age case 103: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Color + // Particle Color case 104: { value = GET_PARTICLE_ATTRIBUTE(0, Vector4); break; } - // Particle Velocity + // Particle Velocity case 105: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Sprite Size + // Particle Sprite Size case 106: { value = GET_PARTICLE_ATTRIBUTE(0, Vector2); break; } - // Particle Mass + // Particle Mass case 107: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Particle Rotation + // Particle Rotation case 108: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Angular Velocity + // Particle Angular Velocity case 109: { value = GET_PARTICLE_ATTRIBUTE(0, Vector3); break; } - // Particle Normalized Age + // Particle Normalized Age case 110: { const float age = GET_PARTICLE_ATTRIBUTE(0, float); @@ -295,55 +295,55 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node value = age / Math::Max(lifetime, ZeroTolerance); break; } - // Particle Radius + // Particle Radius case 111: { value = GET_PARTICLE_ATTRIBUTE(0, float); break; } - // Effect Position + // Effect Position case 200: { value = context.Effect->GetPosition(); break; } - // Effect Rotation + // Effect Rotation case 201: { value = context.Effect->GetOrientation(); break; } - // Effect Scale + // Effect Scale case 202: { value = context.Effect->GetScale(); break; } - // Simulation Mode + // Simulation Mode case 203: { value = box->ID == 0; break; } - // View Position + // View Position case 204: { value = context.ViewTask ? context.ViewTask->View.Position : Vector3::Zero; break; } - // View Direction + // View Direction case 205: { value = context.ViewTask ? context.ViewTask->View.Direction : Vector3::Forward; break; } - // View Far Plane + // View Far Plane case 206: { value = context.ViewTask ? context.ViewTask->View.Far : 0.0f; break; } - // Screen Size + // Screen Size case 207: { const Vector4 size = context.ViewTask ? context.ViewTask->View.ScreenSize : Vector4::Zero; @@ -353,13 +353,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node value = Vector2(size.Z, size.W); break; } - // Particle Position (world space) + // Particle Position (world space) case 212: value = GET_PARTICLE_ATTRIBUTE(0, Vector3); if (context.Emitter->SimulationSpace == ParticlesSimulationSpace::Local) value.AsVector3() = context.Effect->GetTransform().LocalToWorld(value.AsVector3()); break; - // Particle Emitter Function + // Particle Emitter Function case 300: { // Load function asset @@ -406,11 +406,11 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node context.GraphStack.Pop(); break; } - // Particle Index + // Particle Index case 301: value = context.ParticleIndex; break; - // Particles Count + // Particles Count case 302: value = (uint32)context.Data->Buffer->CPU.Count; break; @@ -425,7 +425,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, auto& context = Context.Get(); switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index 1338ee64a..12211de41 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -102,7 +102,7 @@ void ParticleEmitterGraphCPU::InitializeNode(Node* node) switch (node->Type) { - // Position (spiral) + // Position (spiral) case GRAPH_NODE_MAKE_TYPE(15, 214): node->CustomDataOffset = CustomDataSize; CustomDataSize += sizeof(float); @@ -226,7 +226,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa const auto module = emitter->Graph.RenderModules[moduleIndex]; switch (module->TypeID) { - // Sprite Rendering + // Sprite Rendering case 400: { if (_graph._attrSpriteSize != -1) @@ -246,7 +246,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Light Rendering + // Light Rendering case 401: { // Prepare graph data @@ -269,7 +269,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa break; } - // Model Rendering + // Model Rendering case 403: { const auto modelAsset = (Model*)module->Assets[0].Get(); @@ -297,7 +297,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Ribbon Rendering + // Ribbon Rendering case 404: { if (_graph._attrRibbonWidth != -1) @@ -317,7 +317,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa } break; } - // Volumetric Fog Rendering + // Volumetric Fog Rendering case 405: { // Find the maximum radius of the particle diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index 36a10a522..c267eea05 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -10,7 +10,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) auto nodeGpu = (ParticleEmitterGraphGPUNode*)node; switch (node->TypeID) { - // Orient Sprite + // Orient Sprite case 201: case 303: { @@ -28,7 +28,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Orient Model + // Orient Model case 213: case 309: { @@ -39,14 +39,14 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Update Age + // Update Age case 300: { auto attribute = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); _writer.Write(TEXT("\t{0} += DeltaTime;\n"), attribute.Value); break; } - // Gravity/Force + // Gravity/Force case 301: case 304: { @@ -55,7 +55,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) _writer.Write(TEXT("\t{0} += {1} * DeltaTime;\n"), attribute.Value, force.Value); break; } - // Conform to Sphere + // Conform to Sphere case 305: { auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); @@ -94,7 +94,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, velocity.Value, mass.Value, sphereCenter.Value, sphereRadius.Value, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); break; } - // Kill (sphere) + // Kill (sphere) case 306: { UseKill(); @@ -119,7 +119,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, sphereCenter.Value, sphereRadius.Value, sign); break; } - // Kill (box) + // Kill (box) case 307: { UseKill(); @@ -149,7 +149,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, boxCenter.Value, boxSize.Value, invert); break; } - // Kill (custom) + // Kill (custom) case 308: { UseKill(); @@ -162,7 +162,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), kill.Value); break; } - // Linear Drag + // Linear Drag case 310: { const bool useSpriteSize = node->Values[3].AsBool; @@ -195,7 +195,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) } break; } - // Turbulence + // Turbulence case 311: { auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); @@ -225,7 +225,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, velocity.Value, mass.Value, fieldPosition.Value, fieldRotation.Value, fieldScale.Value, roughness.Value, intensity.Value, octavesCount.Value); break; } - // Set Attribute + // Set Attribute case 200: case 302: { @@ -235,7 +235,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) SET_ATTRIBUTE(attribute, value.Value); break; } - // Set Position/Lifetime/Age/.. + // Set Position/Lifetime/Age/.. case 250: case 251: case 252: @@ -271,7 +271,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) SET_ATTRIBUTE(attribute, value.Value); break; } - // Position (sphere surface) + // Position (sphere surface) case 202: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -298,7 +298,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (plane) + // Position (plane) case 203: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -318,7 +318,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (circle) + // Position (circle) case 204: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -343,7 +343,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (disc) + // Position (disc) case 205: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -368,7 +368,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (box surface) + // Position (box surface) case 206: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -400,7 +400,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (box volume) + // Position (box volume) case 207: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -420,7 +420,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, size.Value); break; } - // Position (cylinder) + // Position (cylinder) case 208: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -447,7 +447,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, center.Value, radius.Value, height.Value, arc.Value); break; } - // Position (line) + // Position (line) case 209: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -467,7 +467,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, start.Value, end.Value); break; } - // Position (torus) + // Position (torus) case 210: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -513,7 +513,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, thickness.Value, arc.Value); break; } - // Position (sphere volume) + // Position (sphere volume) case 211: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -540,7 +540,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, 0, center.Value, radius.Value, arc.Value); break; } - // Position (depth) + // Position (depth) case 212: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -574,7 +574,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), positionAttr.Value, uv.Value, depthCullRange.Value, depthOffset.Value, linearDepth.Value, lifetimeAttr.Value); break; } - // Position (spiral) + // Position (spiral) case 214: { auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Write); @@ -632,7 +632,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) break; } - // Helper macros for collision modules to share the code + // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ auto positionAttr = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::ReadWrite); \ auto velocityAttr = AccessParticleAttribute(node, nodeGpu->Attributes[1], AccessMode::ReadWrite); \ @@ -662,7 +662,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " {2} += {9};\n" \ " }}\n" - // Collision (plane) + // Collision (plane) case 330: { COLLISION_BEGIN(); @@ -690,7 +690,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (sphere) + // Collision (sphere) case 331: { COLLISION_BEGIN(); @@ -721,7 +721,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (box) + // Collision (box) case 332: { COLLISION_BEGIN(); @@ -767,7 +767,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (cylinder) + // Collision (cylinder) case 333: { COLLISION_BEGIN(); @@ -817,7 +817,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Collision (depth) + // Collision (depth) case 334: { COLLISION_BEGIN(); @@ -872,18 +872,18 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ); break; } - // Conform to Global SDF + // Conform to Global SDF case 335: { auto position = AccessParticleAttribute(node, nodeGpu->Attributes[0], AccessMode::Read); auto velocity = AccessParticleAttribute(node, nodeGpu->Attributes[1], AccessMode::ReadWrite); auto mass = AccessParticleAttribute(node, nodeGpu->Attributes[2], AccessMode::Read); - + const Value attractionSpeed = GetValue(node->GetBox(0), 2).AsFloat(); const Value attractionForce = GetValue(node->GetBox(1), 3).AsFloat(); const Value stickDistance = GetValue(node->GetBox(2), 4).AsFloat(); const Value stickForce = GetValue(node->GetBox(3), 5).AsFloat(); - + auto param = findOrAddGlobalSDF(); _includes.Add(TEXT("./Flax/GlobalSignDistanceField.hlsl")); _writer.Write( @@ -904,7 +904,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) ), position.Value, velocity.Value, mass.Value, param.ShaderName, attractionSpeed.Value, attractionForce.Value, stickDistance.Value, stickForce.Value); break; } - // Collision (Global SDF) + // Collision (Global SDF) case 336: { COLLISION_BEGIN(); diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 7b4dfefb1..7e6b081ed 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -117,7 +117,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, V { switch (node->TypeID) { - // Get + // Get case 1: case 2: { @@ -188,7 +188,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, V value = Value(VariantType::Object, param->ShaderName); break; default: - CRASH; + CRASH; break; } } @@ -208,7 +208,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& { switch (node->TypeID) { - // Linearize Depth + // Linearize Depth case 7: { // Get input @@ -218,11 +218,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& linearizeSceneDepth(node, depth, value); break; } - // Time + // Time case 8: value = box->ID == 0 ? Value(VariantType::Float, TEXT("Time")) : Value(VariantType::Float, TEXT("DeltaTime")); break; - // Transform Position To Screen UV + // Transform Position To Screen UV case 9: { const Value position = tryGetValue(node->GetBox(0), Value::Zero).AsVector3(); @@ -242,13 +242,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va { switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: - { value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt), AccessMode::Read); break; - } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const Char* format; @@ -285,61 +283,43 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(type, String::Format(format, attribute.Offset, particleIndex.Value), node); break; } - // Particle Position + // Particle Position case 101: - { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Lifetime + // Particle Lifetime case 102: - { value = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Age + // Particle Age case 103: - { value = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Color + // Particle Color case 104: - { value = AccessParticleAttribute(node, TEXT("Color"), ParticleAttribute::ValueTypes::Vector4, AccessMode::Read); break; - } - // Particle Velocity + // Particle Velocity case 105: - { value = AccessParticleAttribute(node, TEXT("Velocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Sprite Size + // Particle Sprite Size case 106: - { value = AccessParticleAttribute(node, TEXT("SpriteSize"), ParticleAttribute::ValueTypes::Vector2, AccessMode::Read); break; - } - // Particle Mass + // Particle Mass case 107: - { value = AccessParticleAttribute(node, TEXT("Mass"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Particle Rotation + // Particle Rotation case 108: - { value = AccessParticleAttribute(node, TEXT("Rotation"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Angular Velocity + // Particle Angular Velocity case 109: - { value = AccessParticleAttribute(node, TEXT("AngularVelocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); break; - } - // Particle Normalized Age + // Particle Normalized Age case 110: { const auto age = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); @@ -347,91 +327,65 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeOperation2(node, age, lifetime, '/'); break; } - // Particle Radius + // Particle Radius case 111: - { value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttribute::ValueTypes::Float, AccessMode::Read); break; - } - // Effect Position + // Effect Position case 200: - { value = Value(VariantType::Vector3, TEXT("EffectPosition")); break; - } - // Effect Rotation + // Effect Rotation case 201: - { value = Value(VariantType::Quaternion, TEXT("EffectRotation")); break; - } - // Effect Scale + // Effect Scale case 202: - { value = Value(VariantType::Vector3, TEXT("EffectScale")); break; - } - // Simulation Mode + // Simulation Mode case 203: - { value = Value(box->ID == 1); break; - } - // View Position + // View Position case 204: - { value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - } - // View Direction + // View Direction case 205: - { value = Value(VariantType::Vector3, TEXT("ViewDir")); break; - } - // View Far Plane + // View Far Plane case 206: - { value = Value(VariantType::Float, TEXT("ViewFar")); break; - } - // Screen Size + // Screen Size case 207: - { value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw")); break; - } - // Random Float + // Random Float case 208: - { value = writeLocal(VariantType::Float, TEXT("RAND"), node); break; - } - // Random Vector2 + // Random Vector2 case 209: - { value = writeLocal(VariantType::Vector2, TEXT("RAND2"), node); break; - } - // Random Vector3 + // Random Vector3 case 210: - { value = writeLocal(VariantType::Vector3, TEXT("RAND3"), node); break; - } - // Random Vector4 + // Random Vector4 case 211: - { value = writeLocal(VariantType::Vector4, TEXT("RAND4"), node); break; - } - // Particle Position (world space) + // Particle Position (world space) case 212: value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read); if (IsLocalSimulationSpace()) value = writeLocal(VariantType::Vector3, String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), value.Value), node); break; - // Random Float Range + // Random Float Range case 213: { auto& a = node->Values[0].AsFloat; @@ -439,7 +393,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node); break; } - // Random Vector2 Range + // Random Vector2 Range case 214: { auto& a = node->Values[0].AsVector2(); @@ -447,7 +401,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector2, String::Format(TEXT("float2(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND))"), a.X, b.X, a.Y, b.Y), node); break; } - // Random Vector3 Range + // Random Vector3 Range case 215: { auto& a = node->Values[0].AsVector3(); @@ -455,7 +409,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector3, String::Format(TEXT("float3(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z), node); break; } - // Random Vector4 Range + // Random Vector4 Range case 216: { auto& a = node->Values[0].AsVector4(); @@ -463,7 +417,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va value = writeLocal(VariantType::Vector4, String::Format(TEXT("float4(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND), lerp({6}, {7}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z, a.W, b.W), node); break; } - // Particle Emitter Function + // Particle Emitter Function case 300: { // Load function asset @@ -518,11 +472,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va _graphStack.Pop(); break; } - // Particle Index + // Particle Index case 301: value = Value(VariantType::Uint, TEXT("context.ParticleIndex")); break; - // Particles Count + // Particles Count case 302: value = Value(VariantType::Uint, TEXT("context.ParticlesCount")); break; @@ -535,7 +489,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val { switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call @@ -595,7 +549,6 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val } break; } - default: break; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index 1f1166418..46a25560f 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -120,7 +120,7 @@ bool ParticleEmitterGPUGenerator::sampleSceneTexture(Node* caller, Box* box, con result = Value(VariantType::Float, valueBox->Cache.Value + _subs[3]); break; default: - CRASH; + CRASH; break; } @@ -151,7 +151,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val { switch (node->TypeID) { - // Scene Texture + // Scene Texture case 6: { // Get texture type @@ -255,11 +255,11 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val } break; } - // Scene Depth + // Scene Depth case 8: sampleSceneDepth(node, value, box); break; - // Texture + // Texture case 11: { // Check if texture has been selected @@ -276,7 +276,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val } break; } - // Load Texture + // Load Texture case 13: { // Get input texture diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp index d1c55021b..2b0e5b8e6 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp @@ -208,7 +208,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer& typeName = TEXT("uint"); break; default: - CRASH; + CRASH; } _writer.Write(TEXT("// {0:^6} | {1:^6} | {2}\n"), a.Offset, typeName, a.Name); } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp index d36168d1a..def7d7ee8 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Sample Layer + // Sample Layer case 1: { Guid id = (Guid)node->Values[0]; @@ -141,7 +141,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) value = MaterialValue(VariantType::Void, varName); break; } - // Blend Linear + // Blend Linear case 2: case 5: case 8: @@ -215,8 +215,8 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) #undef EAT_BOX break; } - // Pack Material Layer (old: without TessellationMultiplier, SubsurfaceColor and WorldDisplacement support) - // [Deprecated on 2018.10.01, expires on 2019.10.01] + // Pack Material Layer (old: without TessellationMultiplier, SubsurfaceColor and WorldDisplacement support) + // [Deprecated on 2018.10.01, expires on 2019.10.01] case 3: { // Create new layer @@ -263,8 +263,8 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) break; } - // Unpack Material Layer - // Node type 4 -> [Deprecated on 2018.10.01, expires on 2019.10.01] + // Unpack Material Layer + // Node type 4 -> [Deprecated on 2018.10.01, expires on 2019.10.01] case 4: case 7: { @@ -284,7 +284,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) } break; } - // Pack Material Layer + // Pack Material Layer case 6: { // Create new layer diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 5809cb267..65718c4b3 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -9,24 +9,24 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // World Position + // World Position case 2: value = Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz")); break; - // View + // View case 3: { switch (box->ID) { - // Position + // Position case 0: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - // Direction + // Direction case 1: value = Value(VariantType::Vector3, TEXT("ViewDir")); break; - // Far Plane + // Far Plane case 2: value = Value(VariantType::Float, TEXT("ViewFar")); break; @@ -34,15 +34,15 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) } break; } - // Normal + // Normal case 4: value = getNormal; break; - // Camera Vector + // Camera Vector case 5: value = getCameraVector(node); break; - // Screen Position + // Screen Position case 6: { // Position @@ -54,11 +54,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) break; } - // Screen Size + // Screen Size case 7: value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw")); break; - // Custom code + // Custom code case 8: { // Skip if has no code @@ -127,15 +127,15 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = box->Cache; break; } - // Object Position + // Object Position case 9: value = Value(VariantType::Vector3, TEXT("GetObjectPosition(input)")); break; - // Two Sided Sign + // Two Sided Sign case 10: value = Value(VariantType::Float, TEXT("input.TwoSidedSign")); break; - // Camera Depth Fade + // Camera Depth Fade case 11: { auto faeLength = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat(); @@ -152,40 +152,40 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = x5; break; } - // Vertex Color + // Vertex Color case 12: value = getVertexColor; _treeLayer->UsageFlags |= MaterialUsageFlags::UseVertexColor; break; - // Pre-skinned Local Position + // Pre-skinned Local Position case 13: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedPosition")) : Value::Zero; break; - // Pre-skinned Local Normal + // Pre-skinned Local Normal case 14: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedNormal")) : Value::Zero; break; - // Depth + // Depth case 15: value = writeLocal(VariantType::Float, TEXT("distance(ViewPos, input.WorldPosition)"), node); break; - // Tangent + // Tangent case 16: value = Value(VariantType::Vector3, TEXT("input.TBN[0]")); break; - // Bitangent + // Bitangent case 17: value = Value(VariantType::Vector3, TEXT("input.TBN[1]")); break; - // Camera Position + // Camera Position case 18: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; - // Per Instance Random + // Per Instance Random case 19: value = Value(VariantType::Float, TEXT("GetPerInstanceRandom(input)")); break; - // Interpolate VS To PS + // Interpolate VS To PS case 20: { const auto input = node->GetBox(0); @@ -219,7 +219,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) _vsToPsInterpolants.Add(input); break; } - // Terrain Holes Mask + // Terrain Holes Mask case 21: { MaterialLayer* baseLayer = GetRootLayer(); @@ -229,7 +229,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = Value::One; break; } - // Terrain Layer Weight + // Terrain Layer Weight case 22: { MaterialLayer* baseLayer = GetRootLayer(); @@ -252,7 +252,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = Value(VariantType::Float, String::Format(TEXT("input.Layers[{0}][{1}]"), slotIndex, componentIndex)); break; } - // Depth Fade + // Depth Fade case 23: { // Calculate screen-space UVs @@ -278,7 +278,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, fadeDistance.Value), node); break; } - // Material Function + // Material Function case 24: { // Load function asset @@ -333,11 +333,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) _graphStack.Pop(); break; } - // Object Size + // Object Size case 25: value = Value(VariantType::Vector3, TEXT("GetObjectSize(input)")); break; - // Blend Normals + // Blend Normals case 26: { const auto baseNormal = tryGetValue(node->GetBox(0), getNormalZero).AsVector3(); @@ -350,7 +350,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, text2, node); break; } - // Rotator + // Rotator case 27: { const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); @@ -366,7 +366,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector2, String::Format(TEXT("{3} + float2(dot({0},{1}), dot({0},{2}))"), x1.Value, dotB1.Value, dotB2.Value, center.Value), node); break; } - // Sphere Mask + // Sphere Mask case 28: { const auto a = tryGetValue(node->GetBox(0), 0, Value::Zero); @@ -384,7 +384,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Float, String::Format(TEXT("{0} ? (1 - {1}) : {1}"), invert.Value, x2.Value), node); break; } - // Tiling & Offset + // Tiling & Offset case 29: { const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); @@ -394,42 +394,42 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector2, String::Format(TEXT("{0} * {1} + {2}"), uv.Value, tiling.Value, offset.Value), node); break; } - // DDX + // DDX case 30: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(inValue.Type, String::Format(TEXT("ddx({0})"), inValue.Value), node); break; } - // DDY + // DDY case 31: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(inValue.Type, String::Format(TEXT("ddy({0})"), inValue.Value), node); break; } - // Sign + // Sign case 32: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Float, String::Format(TEXT("sign({0})"), inValue.Value), node); break; } - // Any + // Any case 33: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Bool, String::Format(TEXT("any({0})"), inValue.Value), node); break; } - // All + // All case 34: { const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); value = writeLocal(ValueType::Bool, String::Format(TEXT("all({0})"), inValue.Value), node); break; } - // Blackbody + // Blackbody case 35: { // Reference: Mitchell Charity, http://www.vendian.org/mncharity/dir3/blackbody/ @@ -451,7 +451,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, String::Format(TEXT("{1} < 1000.0f ? {0} * {1}/1000.0f : {0}"), color.Value, temperature.Value), node); break; } - // HSVToRGB + // HSVToRGB case 36: { const auto hsv = tryGetValue(node->GetBox(0), node->Values[0]).AsVector3(); @@ -463,7 +463,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Vector3, String::Format(TEXT("{1}.z * lerp(float3(1.0, 1.0, 1.0), {0}, {1}.y)"), x1.Value, color.Value), node); break; } - // RGBToHSV + // RGBToHSV case 37: { // Reference: Ian Taylor, https://www.chilliant.com/rgb2hsv.html @@ -474,8 +474,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) auto p = writeLocal(ValueType::Vector4, String::Format(TEXT("({0}.g < {0}.b) ? float4({0}.bg, -1.0f, 2.0f/3.0f) : float4({0}.gb, 0.0f, -1.0f/3.0f)"), rgb.Value), node); auto q = writeLocal(ValueType::Vector4, String::Format(TEXT("({0}.r < {1}.x) ? float4({1}.xyw, {0}.r) : float4({0}.r, {1}.yzx)"), rgb.Value, p.Value), node); auto c = writeLocal(ValueType::Float, String::Format(TEXT("{0}.x - min({0}.w, {0}.y)"), q.Value), node); - auto h = writeLocal(ValueType::Float , String::Format(TEXT("abs(({0}.w - {0}.y) / (6 * {1} + {2}) + {0}.z)"), q.Value, c.Value, epsilon.Value), node); - + auto h = writeLocal(ValueType::Float, String::Format(TEXT("abs(({0}.w - {0}.y) / (6 * {1} + {2}) + {0}.z)"), q.Value, c.Value, epsilon.Value), node); + auto hcv = writeLocal(ValueType::Vector3, String::Format(TEXT("float3({0}, {1}, {2}.x)"), h.Value, c.Value, q.Value), node); value = writeLocal(ValueType::Vector3, String::Format(TEXT("float3({0}.x * 360.0f, {0}.y / ({0}.z + {1}), {0}.z)"), hcv.Value, epsilon.Value), node); break; @@ -489,7 +489,7 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Function Input + // Function Input case 1: { // Find the function call diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp index a19b9ea42..67bb3d84d 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupParameters(Box* box, Node* node, Value& valu { switch (node->TypeID) { - // Get + // Get case 1: { // Get parameter diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp index 76c0d9fa9..ec3ea3777 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp @@ -99,74 +99,74 @@ void MaterialGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value switch (node->TypeID) { - // Particle Attribute + // Particle Attribute case 100: { value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt)); break; } - // Particle Attribute (by index) + // Particle Attribute (by index) case 303: { const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("input.ParticleIndex"))), VariantType::Uint); value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast(node->Values[1].AsInt), particleIndex.Value.Get()); break; } - // Particle Position + // Particle Position case 101: { value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttributeValueTypes::Vector3, nullptr, ParticleAttributeSpace::LocalPosition); break; } - // Particle Lifetime + // Particle Lifetime case 102: { value = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttributeValueTypes::Float); break; } - // Particle Age + // Particle Age case 103: { value = AccessParticleAttribute(node, TEXT("Age"), ParticleAttributeValueTypes::Float); break; } - // Particle Color + // Particle Color case 104: { value = AccessParticleAttribute(node, TEXT("Color"), ParticleAttributeValueTypes::Vector4); break; } - // Particle Velocity + // Particle Velocity case 105: { value = AccessParticleAttribute(node, TEXT("Velocity"), ParticleAttributeValueTypes::Vector3, nullptr, ParticleAttributeSpace::LocalDirection); break; } - // Particle Sprite Size + // Particle Sprite Size case 106: { value = AccessParticleAttribute(node, TEXT("SpriteSize"), ParticleAttributeValueTypes::Vector2); break; } - // Particle Mass + // Particle Mass case 107: { value = AccessParticleAttribute(node, TEXT("Mass"), ParticleAttributeValueTypes::Float); break; } - // Particle Rotation + // Particle Rotation case 108: { value = AccessParticleAttribute(node, TEXT("Rotation"), ParticleAttributeValueTypes::Vector3); break; } - // Particle Angular Velocity + // Particle Angular Velocity case 109: { value = AccessParticleAttribute(node, TEXT("AngularVelocity"), ParticleAttributeValueTypes::Vector3); break; } - // Particle Normalized Age + // Particle Normalized Age case 110: { const auto age = AccessParticleAttribute(node, TEXT("Age"), ParticleAttributeValueTypes::Float); @@ -174,7 +174,7 @@ void MaterialGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value value = writeOperation2(node, age, lifetime, '/'); break; } - // Particle Radius + // Particle Radius case 111: { value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttributeValueTypes::Float); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 7ed372a32..44b8805d0 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Texture + // Texture case 1: { // Check if texture has been selected @@ -28,11 +28,11 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // TexCoord + // TexCoord case 2: value = getUVs; break; - // Cube Texture + // Cube Texture case 3: { // Check if texture has been selected @@ -52,7 +52,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Normal Map + // Normal Map case 4: { // Check if texture has been selected @@ -72,7 +72,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Parallax Occlusion Mapping + // Parallax Occlusion Mapping case 5: { auto heightTextureBox = node->GetBox(4); @@ -163,7 +163,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = result; break; } - // Scene Texture + // Scene Texture case 6: { // Get texture type @@ -267,7 +267,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } break; } - // Scene Color + // Scene Color case 7: { // Sample scene color texture @@ -275,13 +275,13 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) sampleTexture(node, value, box, ¶m); break; } - // Scene Depth + // Scene Depth case 8: { sampleSceneDepth(node, value, box); break; } - // Sample Texture + // Sample Texture case 9: { enum CommonSamplerType @@ -401,7 +401,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = textureBox->Cache; break; } - // Flipbook + // Flipbook case 10: { // Get input values diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp index 1c5160593..21a9e6ec0 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Tools.cpp @@ -8,7 +8,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) { switch (node->TypeID) { - // Fresnel + // Fresnel case 1: case 4: { @@ -33,7 +33,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = local6; break; } - // Desaturation + // Desaturation case 2: { // Get inputs @@ -46,13 +46,13 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = writeFunction3(node, input, dot, scale, TEXT("lerp"), VariantType::Vector3); break; } - // Time + // Time case 3: { value = getTime; break; } - // Panner + // Panner case 6: { // Get inputs @@ -68,7 +68,7 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) value = writeOperation2(node, uv, local1, '+'); break; } - // Linearize Depth + // Linearize Depth case 7: { // Get input From fc138bbbbbe5e163c8fed97fc288ca0b69f320eb Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 14:01:51 +0200 Subject: [PATCH 065/144] Fix GPU context error during lights rendering --- Source/Engine/Renderer/LightPass.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 373a3228c..1e54f1edf 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -281,6 +281,8 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer light.SetupLightData(&perLight.Light, view, renderShadow); @@ -336,6 +338,8 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer light.SetupLightData(&perLight.Light, view, renderShadow); @@ -377,6 +381,8 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Set shadow mask context->BindSR(5, shadowMaskView); } + else + context->UnBindSR(5); // Pack light properties buffer light.SetupLightData(&perLight.Light, view, renderShadow); From 2b83975ea2540b93e7f1de07d9db478a084998bf Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 14:48:21 +0200 Subject: [PATCH 066/144] Add support for implicit casting from Quaternion to other types in shaders --- Source/Engine/Visject/ShaderGraphValue.cpp | 34 ++++++++++++++++++++-- Source/Engine/Visject/ShaderGraphValue.h | 11 +++++++ Source/Shaders/Common.hlsl | 13 +++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index cd3c1354a..828f9ac0f 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -2,6 +2,7 @@ #include "ShaderGraphValue.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Quaternion.h" #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" @@ -54,6 +55,10 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v) Type = VariantType::Types::Vector4; Value = String::Format(TEXT("float4({0}, {1}, {2}, {3})"), (*(Vector4*)v.AsData).X, (*(Vector4*)v.AsData).Y, (*(Vector4*)v.AsData).Z, (*(Vector4*)v.AsData).W); break; + case VariantType::Quaternion: + Type = VariantType::Types::Quaternion; + Value = String::Format(TEXT("float4({0}, {1}, {2}, {3})"), (*(Quaternion*)v.AsData).X, (*(Quaternion*)v.AsData).Y, (*(Quaternion*)v.AsData).Z, (*(Quaternion*)v.AsData).W); + break; case VariantType::String: Type = VariantType::Types::String; Value = (StringView)v; @@ -115,11 +120,14 @@ ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type) case VariantType::Types::Color: v = TEXT("float4(0, 0, 0, 0)"); break; + case VariantType::Types::Quaternion: + v = TEXT("float4(0, 0, 0, 1)"); + break; case VariantType::Types::Void: v = TEXT("((Material)0)"); break; default: - CRASH; + CRASH; v = nullptr; } return ShaderGraphValue(type, String(v)); @@ -145,11 +153,12 @@ ShaderGraphValue ShaderGraphValue::InitForHalf(VariantType::Types type) v = TEXT("float3(0.5, 0.5, 0.5)"); break; case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: v = TEXT("float4(0.5, 0.5, 0.5, 0.5)"); break; default: - CRASH; + CRASH; v = nullptr; } return ShaderGraphValue(type, String(v)); @@ -175,11 +184,12 @@ ShaderGraphValue ShaderGraphValue::InitForOne(VariantType::Types type) v = TEXT("float3(1, 1, 1)"); break; case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: v = TEXT("float4(1, 1, 1, 1)"); break; default: - CRASH; + CRASH; v = nullptr; } return ShaderGraphValue(type, String(v)); @@ -208,6 +218,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((bool){0}.x)"); break; @@ -224,6 +235,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((int){0}.x)"); break; @@ -240,6 +252,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((uint){0}.x)"); break; @@ -256,6 +269,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Vector2: case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("((float){0}.x)"); break; @@ -272,6 +286,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: break; case VariantType::Types::Vector3: case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: case VariantType::Types::Color: format = TEXT("{0}.xy"); break; @@ -293,6 +308,9 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: case VariantType::Types::Color: format = TEXT("{0}.xyz"); break; + case VariantType::Types::Quaternion: + format = TEXT("QuatRotateVector(float3(0, 0, 1), {0})"); + break; } break; case VariantType::Types::Vector4: @@ -312,6 +330,16 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: format = TEXT("float4({0}.xyz, 0)"); break; case VariantType::Types::Color: + case VariantType::Types::Vector4: + case VariantType::Types::Quaternion: + format = TEXT("{0}"); + break; + } + break; + case VariantType::Types::Quaternion: + switch (v.Type) + { + case VariantType::Types::Color: case VariantType::Types::Vector4: format = TEXT("{0}"); break; diff --git a/Source/Engine/Visject/ShaderGraphValue.h b/Source/Engine/Visject/ShaderGraphValue.h index 913fdcf41..6c9a846fe 100644 --- a/Source/Engine/Visject/ShaderGraphValue.h +++ b/Source/Engine/Visject/ShaderGraphValue.h @@ -75,6 +75,17 @@ public: { } + /// + /// Initializes a new instance of the struct. + /// + /// The type. + /// The value. + ShaderGraphValue(VariantType::Types type, const String&& value) + : Type(type) + , Value(MoveTemp(value)) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index ed2951c7e..8e5a682c6 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -204,6 +204,19 @@ float Luminance(float3 color) return dot(color, float3(0.299f, 0.587f, 0.114f)); } +// Quaternion multiplication (http://mathworld.wolfram.com/Quaternion.html) +float4 QuatMultiply(float4 q1, float4 q2) +{ + return float4(q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz), q1.w * q2.w - dot(q1.xyz, q2.xyz)); +} + +// Vector rotation with a quaternion (http://mathworld.wolfram.com/Quaternion.html) +float3 QuatRotateVector(float3 v, float4 q) +{ + float4 nq = q * float4(-1, -1, -1, 1); + return QuatMultiply(q, QuatMultiply(float4(v, 0), nq)).xyz; +} + // Samples the unwrapped 3D texture (eg. volume texture of size 16x16x16 would be unwrapped to 256x16) float4 SampleUnwrappedTexture3D(Texture2D tex, SamplerState s, float3 uvw, float size) { From 446a6f22fa7515172addf6f822048bc93b2006d4 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 14:48:45 +0200 Subject: [PATCH 067/144] Fix using `double` value with `float` values to be casted in Editor --- Source/Editor/CustomEditors/Editors/DoubleEditor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs index 06dee3d1b..859b70f4e 100644 --- a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; using System.Linq; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; @@ -66,7 +67,13 @@ namespace FlaxEditor.CustomEditors.Editors } else { - _element.Value = (double)Values[0]; + var value = Values[0]; + if (value is double asDouble) + _element.Value = (float)asDouble; + else if (value is float asFloat) + _element.Value = asFloat; + else + throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "")); } } } From c41a446ae96bc1b74b9f8660c2859a0e1ab696c1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 28 Mar 2022 15:02:32 +0200 Subject: [PATCH 068/144] Add `Rotate Vector` node to Visject Surface graphs --- Source/Editor/Surface/Archetypes/Math.cs | 14 ++++++++++++++ Source/Engine/Visject/ShaderGraph.cpp | 9 ++++++++- Source/Engine/Visject/ShaderGraphValue.cpp | 4 ++-- Source/Engine/Visject/VisjectGraph.cpp | 10 ++++++++-- Source/Shaders/Common.hlsl | 2 +- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 2bfb3e816..b9d4098bc 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -427,6 +427,20 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 4), } }, + new NodeArchetype + { + TypeID = 49, + Title = "Rotate Vector", + Description = "Rotates given vector using the Quaternion", + Flags = NodeFlags.AllGraphs, + Size = new Vector2(200, 40), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Quaternion", true, typeof(Quaternion), 0), + NodeElementArchetype.Factory.Input(1, "Vector", true, typeof(Vector3), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector3), 2), + } + }, }; } } diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index d06a00977..11adc31cd 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -409,10 +409,17 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) const auto rangeA = tryGetValue(node->GetBox(1), node->Values[1].AsVector2()); const auto rangeB = tryGetValue(node->GetBox(2), node->Values[2].AsVector2()); const auto clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool(); - const auto mapFunc = String::Format(TEXT("{2}.x + ({0} - {1}.x) * ({2}.y - {2}.x) / ({1}.y - {1}.x)"), inVal.Value, rangeA.Value, rangeB.Value); value = writeLocal(ValueType::Float, String::Format(TEXT("{2} ? clamp({0}, {1}.x, {1}.y) : {0}"), mapFunc, rangeB.Value, clamp.Value), node); break; + } + // Rotate Vector + case 49: + { + const Value quaternion = tryGetValue(node->GetBox(0), Value::InitForZero(VariantType::Quaternion)).Cast(VariantType::Quaternion); + const Value vector = tryGetValue(node->GetBox(1), Vector3::Forward).Cast(VariantType::Vector3); + value = writeLocal(ValueType::Vector3, String::Format(TEXT("QuatRotateVector({0}, {1})"), quaternion.Value, vector.Value), node); + break; } default: break; diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index 828f9ac0f..0b4178ca3 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -130,7 +130,7 @@ ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type) CRASH; v = nullptr; } - return ShaderGraphValue(type, String(v)); + return ShaderGraphValue(type, v); } ShaderGraphValue ShaderGraphValue::InitForHalf(VariantType::Types type) @@ -309,7 +309,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType:: format = TEXT("{0}.xyz"); break; case VariantType::Types::Quaternion: - format = TEXT("QuatRotateVector(float3(0, 0, 1), {0})"); + format = TEXT("QuatRotateVector({0}, float3(0, 0, 1))"); // Returns direction vector break; } break; diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index e2091e749..658121b20 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -405,11 +405,17 @@ void VisjectExecutor::ProcessGroupMath(Box* box, Node* node, Value& value) const Vector2 rangeA = tryGetValue(node->GetBox(1), node->Values[1]).AsVector2(); const Vector2 rangeB = tryGetValue(node->GetBox(2), node->Values[2]).AsVector2(); const bool clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool; - auto mapFunc = Math::Remap(inVal, rangeA.X, rangeA.Y, rangeB.X, rangeB.Y); - value = clamp ? Math::Clamp(mapFunc, rangeB.X, rangeB.Y) : mapFunc; break; + } + // Rotate Vector + case 49: + { + const Quaternion quaternion = (Quaternion)tryGetValue(node->GetBox(0), Quaternion::Identity); + const Vector3 vector = (Vector3)tryGetValue(node->GetBox(1), Vector3::Forward); + value = quaternion * vector; + break; } default: break; diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 8e5a682c6..fd5fc19f4 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -211,7 +211,7 @@ float4 QuatMultiply(float4 q1, float4 q2) } // Vector rotation with a quaternion (http://mathworld.wolfram.com/Quaternion.html) -float3 QuatRotateVector(float3 v, float4 q) +float3 QuatRotateVector(float4 q, float3 v) { float4 nq = q * float4(-1, -1, -1, 1); return QuatMultiply(q, QuatMultiply(float4(v, 0), nq)).xyz; From 26b2cb291eff6d3f7e523e2ddb0737fd18b38f55 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 29 Mar 2022 14:59:40 +0200 Subject: [PATCH 069/144] Expose GlobalSignDistanceFieldPass to user scripts --- Source/Engine/Renderer/GlobalSignDistanceFieldPass.h | 2 +- Source/Engine/Renderer/RendererPass.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index f4c503222..abc0a877a 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -7,7 +7,7 @@ /// /// Global Sign Distance Field (SDF) rendering pass. Composites scene geometry into series of 3D volume textures that cover the world around the camera for global distance field sampling. /// -class GlobalSignDistanceFieldPass : public RendererPass +class FLAXENGINE_API GlobalSignDistanceFieldPass : public RendererPass { public: // Constant buffer data for Global SDF access on a GPU. diff --git a/Source/Engine/Renderer/RendererPass.h b/Source/Engine/Renderer/RendererPass.h index a40e273d6..3a4146ed1 100644 --- a/Source/Engine/Renderer/RendererPass.h +++ b/Source/Engine/Renderer/RendererPass.h @@ -37,7 +37,7 @@ public: /// Each render pass supports proper resources initialization and disposing. /// /// -class RendererPassBase : public Object +class FLAXENGINE_API RendererPassBase : public Object { protected: From 9d56093381039c25d16a842f4d414aa500512e5f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 29 Mar 2022 15:00:13 +0200 Subject: [PATCH 070/144] Add lazy init for Global SDF shader --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 49 +++++++++---------- .../Renderer/GlobalSignDistanceFieldPass.h | 1 + 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 5a64860b9..c3257b622 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "GlobalSignDistanceFieldPass.h" -#include "GBufferPass.h" #include "RenderList.h" #include "Engine/Core/Math/Int3.h" #include "Engine/Core/Collections/HashSet.h" @@ -134,35 +133,31 @@ String GlobalSignDistanceFieldPass::ToString() const bool GlobalSignDistanceFieldPass::Init() { // Check platform support - auto device = GPUDevice::Instance; - if (device->GetFeatureLevel() < FeatureLevel::SM5 || !device->Limits.HasCompute || !device->Limits.HasTypedUAVLoad) - return false; - if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D)) - return false; - - // Create pipeline states - _psDebug = device->CreatePipelineState(); - - // Load shader - _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSignDistanceField")); - if (_shader == nullptr) - return false; -#if COMPILE_WITH_DEV_ENV - _shader.Get()->OnReloading.Bind(this); -#endif - - // Init buffer - _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); - + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad + && FORMAT_FEATURES_ARE_NOT_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D); return false; } bool GlobalSignDistanceFieldPass::setupResources() { - // Check shader - if (!_shader || !_shader->IsLoaded()) + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSignDistanceField")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) return true; + + const auto device = GPUDevice::Instance; const auto shader = _shader->GetShader(); + + // Check shader _cb0 = shader->GetCB(0); _cb1 = shader->GetCB(1); _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); @@ -171,10 +166,14 @@ bool GlobalSignDistanceFieldPass::setupResources() _csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0); _csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1); + // Init buffer + _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); + // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; - if (!_psDebug->IsValid()) + if (!_psDebug) { + _psDebug = device->CreatePipelineState(); psDesc.PS = shader->GetPS("PS_Debug"); if (_psDebug->Init(psDesc)) return true; @@ -187,7 +186,7 @@ bool GlobalSignDistanceFieldPass::setupResources() void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) { - _psDebug->ReleaseGPU(); + SAFE_DELETE_GPU_RESOURCE(_psDebug); _csRasterizeModel0 = nullptr; _csRasterizeModel1 = nullptr; _csClearChunk = nullptr; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index abc0a877a..3ca871708 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -28,6 +28,7 @@ public: }; private: + bool _supported = false; AssetReference _shader; GPUPipelineState* _psDebug = nullptr; GPUShaderProgramCS* _csRasterizeModel0 = nullptr; From 787e7b423ce3d2832a4fdfaeed22bb4e4ab886e5 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 29 Mar 2022 15:02:27 +0200 Subject: [PATCH 071/144] Add additional HitNormal feature to Global SDF trace output --- Source/Shaders/GlobalSignDistanceField.hlsl | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index bb33316bc..4f02acff2 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -24,6 +24,7 @@ struct GlobalSDFTrace float3 WorldDirection; float MaxDistance; float StepScale; + bool NeedsHitNormal; void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance, float stepScale = 1.0f) { @@ -32,12 +33,14 @@ struct GlobalSDFTrace MinDistance = minDistance; MaxDistance = maxDistance; StepScale = stepScale; + NeedsHitNormal = false; } }; // Global SDF ray trace hit information. struct GlobalSDFHit { + float3 HitNormal; float HitTime; uint HitCascade; uint StepsCount; @@ -54,13 +57,13 @@ struct GlobalSDFHit }; // Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. -float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, uint minCascade = 0) +float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition) { float distance = data.CascadePosDistance[3].w * 2.0f; if (distance <= 0.0f) return 60000; UNROLL - for (uint cascade = minCascade; cascade < 4; cascade++) + for (uint cascade = 0; cascade < 4; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -77,14 +80,14 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 } // Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. -float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, out float distance, uint minCascade = 0) +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, out float distance) { float3 gradient = float3(0, 0.00001f, 0); distance = 60000; if (data.CascadePosDistance[3].w <= 0.0f) return gradient; UNROLL - for (uint cascade = minCascade; cascade < 4; cascade++) + for (uint cascade = 0; cascade < 4; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -109,19 +112,17 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4] } // Ray traces the Global SDF. -GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4], Texture3D mips[4], const GlobalSDFTrace trace, uint minCascade = 0) +GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4], Texture3D mips[4], const GlobalSDFTrace trace) { - GlobalSDFHit hit; + GlobalSDFHit hit = (GlobalSDFHit)0; hit.HitTime = -1.0f; - hit.HitCascade = 0; - hit.StepsCount = 0; float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) float chunkMarginDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) float nextIntersectionStart = 0.0f; float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; UNROLL - for (uint cascade = minCascade; cascade < 4 && hit.HitTime < 0.0f; cascade++) + for (uint cascade = 0; cascade < 4 && hit.HitTime < 0.0f; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -173,6 +174,18 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] // Surface hit hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f); hit.HitCascade = cascade; + if (trace.NeedsHitNormal) + { + // Calculate hit normal from SDF gradient + float texelOffset = 1.0f / data.Resolution; + float xp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x + texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float xn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x - texelOffset, cascadeUV.y, cascadeUV.z), 0).x; + float yp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y + texelOffset, cascadeUV.z), 0).x; + float yn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y - texelOffset, cascadeUV.z), 0).x; + float zp = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z + texelOffset), 0).x; + float zn = tex[cascade].SampleLevel(SamplerLinearClamp, float3(cascadeUV.x, cascadeUV.y, cascadeUV.z - texelOffset), 0).x; + hit.HitNormal = normalize(float3(xp - xn, yp - yn, zp - zn)); + } break; } From 63b8b0cb509f0a432d1d973337ff06742bee5f14 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 29 Mar 2022 15:04:15 +0200 Subject: [PATCH 072/144] Add `GlobalSurfaceAtlas` pass to Renderer (wip) --- Content/Shaders/GlobalSurfaceAtlas.flax | 3 + Source/Editor/Cooker/Steps/DeployDataStep.cpp | 1 + Source/Editor/Viewport/EditorViewport.cs | 1 + Source/Engine/Graphics/Enums.h | 7 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 170 ++++++++++++++++++ .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 57 ++++++ Source/Engine/Renderer/Renderer.cpp | 11 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 5 + Source/Shaders/GlobalSurfaceAtlas.shader | 43 +++++ 9 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 Content/Shaders/GlobalSurfaceAtlas.flax create mode 100644 Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp create mode 100644 Source/Engine/Renderer/GlobalSurfaceAtlasPass.h create mode 100644 Source/Shaders/GlobalSurfaceAtlas.hlsl create mode 100644 Source/Shaders/GlobalSurfaceAtlas.shader diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax new file mode 100644 index 000000000..378f723e5 --- /dev/null +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1b0c45d96dff29c3fceed3eede0b671110d99b4a5774f9e487760b4a0fd9563 +size 1878 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 8ff70bb4e..b9c092059 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -69,6 +69,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/BitonicSort")); data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); + data.AddRootEngineAsset(TEXT("Shaders/GlobalSurfaceAtlas")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index c618dd568..bf78dea5b 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1408,6 +1408,7 @@ namespace FlaxEditor.Viewport new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"), new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"), new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"), + new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas"), }; private void WidgetCamSpeedShowHide(Control cm) diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index c1fca1224..44e080035 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -854,9 +854,14 @@ API_ENUM() enum class ViewMode QuadOverdraw = 23, /// - /// Draw global Sign Distant Field (SDF) preview. + /// Draw Global Sign Distant Field (SDF) preview. /// GlobalSDF = 24, + + /// + /// Draw Global Surface Atlas preview. + /// + GlobalSurfaceAtlas = 25, }; /// diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp new file mode 100644 index 000000000..aac122295 --- /dev/null +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -0,0 +1,170 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "GlobalSurfaceAtlasPass.h" +#include "GlobalSignDistanceFieldPass.h" +#include "RenderList.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" + +PACK_STRUCT(struct Data0 + { + Vector3 ViewWorldPos; + float ViewNearPlane; + Vector3 Padding00; + float ViewFarPlane; + Vector4 ViewFrustumWorldRays[4]; + GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; + }); + +class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + GPUTexture* Dummy = nullptr; // TODO use some actual atlas textures + GlobalSurfaceAtlasPass::BindingData Result; + + ~GlobalSurfaceAtlasCustomBuffer() + { + RenderTargetPool::Release(Dummy); + } +}; + +String GlobalSurfaceAtlasPass::ToString() const +{ + return TEXT("GlobalSurfaceAtlasPass"); +} + +bool GlobalSurfaceAtlasPass::Init() +{ + // Check platform support + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad; + return false; +} + +bool GlobalSurfaceAtlasPass::setupResources() +{ + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSurfaceAtlas")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + + const auto device = GPUDevice::Instance; + const auto shader = _shader->GetShader(); + _cb0 = shader->GetCB(0); + + // Create pipeline state + GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psDebug) + { + _psDebug = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_Debug"); + if (_psDebug->Init(psDesc)) + return true; + } + + return false; +} + +#if COMPILE_WITH_DEV_ENV + +void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) +{ + SAFE_DELETE_GPU_RESOURCE(_psDebug); + invalidateResources(); +} + +#endif + +void GlobalSurfaceAtlasPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + SAFE_DELETE_GPU_RESOURCE(_psDebug); + _cb0 = nullptr; + _shader = nullptr; +} + +bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) +{ + // Skip if not supported + if (setupResources()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& surfaceAtlasData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSurfaceAtlas")); + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (surfaceAtlasData.LastFrameUsed == currentFrame) + { + result = surfaceAtlasData.Result; + return false; + } + + PROFILE_GPU_CPU("Global Surface Atlas"); + + return false; + // TODO: configurable via graphics settings + const int32 resolution = 4096; + // TODO: configurable via postFx settings (maybe use Global SDF distance?) + const float distance = 20000; + + // TODO: Initialize buffers + surfaceAtlasData.LastFrameUsed = currentFrame; + + // TODO: Rasterize world geometry into Global Surface Atlas + + // Copy results + result.Dummy = surfaceAtlasData.Dummy; + surfaceAtlasData.Result = result; + return false; +} + +void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) +{ + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + BindingData bindingData; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF) || Render(renderContext, context, bindingData)) + { + context->Draw(output, renderContext.Buffers->GBuffer0); + return; + } + + PROFILE_GPU_CPU("Global Surface Atlas Debug"); + const Vector2 outputSize(output->Size()); + if (_cb0) + { + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + for (int32 i = 0; i < 4; i++) + data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); + data.GlobalSDF = bindingDataSDF.GlobalSDF; + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + context->SetViewportAndScissors(outputSize.X, outputSize.Y); + context->DrawFullscreenTriangle(); +} diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h new file mode 100644 index 000000000..a759fd37b --- /dev/null +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "RendererPass.h" + +/// +/// Global Surface Atlas rendering pass. Captures scene geometry into a single atlas texture which contains surface diffuse color, normal vector, emission light, and calculates direct+indirect lighting. Used by Global Illumination and Reflections. +/// +class FLAXENGINE_API GlobalSurfaceAtlasPass : public RendererPass +{ +public: + // Binding data for the GPU. + struct BindingData + { + GPUTexture* Dummy; // TODO: add textures + }; + +private: + bool _supported = false; + AssetReference _shader; + GPUPipelineState* _psDebug = nullptr; + GPUConstantBuffer* _cb0 = nullptr; + +public: + /// + /// Renders the Global Surface Atlas. + /// + /// The rendering context. + /// The GPU context. + /// The result Global Surface Atlas data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, BindingData& result); + + /// + /// Renders the debug view. + /// + /// The rendering context. + /// The GPU context. + /// The output buffer. + void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + +private: +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index d6d21c1be..b2753e2b5 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -22,6 +22,7 @@ #include "HistogramPass.h" #include "AtmospherePreCompute.h" #include "GlobalSignDistanceFieldPass.h" +#include "GlobalSurfaceAtlasPass.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" @@ -83,6 +84,7 @@ bool RendererService::Init() PassList.Add(SMAA::Instance()); PassList.Add(HistogramPass::Instance()); PassList.Add(GlobalSignDistanceFieldPass::Instance()); + PassList.Add(GlobalSurfaceAtlasPass::Instance()); #if USE_EDITOR PassList.Add(QuadOverdrawPass::Instance()); #endif @@ -352,11 +354,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) // Debug drawing if (renderContext.View.Mode == ViewMode::GlobalSDF) - { GlobalSignDistanceFieldPass::Instance()->RenderDebug(renderContext, context, lightBuffer); - } - if (renderContext.View.Mode == ViewMode::Emissive || - renderContext.View.Mode == ViewMode::LightmapUVsDensity || + else if (renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas) + GlobalSurfaceAtlasPass::Instance()->RenderDebug(renderContext, context, lightBuffer); + if (renderContext.View.Mode == ViewMode::Emissive || + renderContext.View.Mode == ViewMode::LightmapUVsDensity || + renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas || renderContext.View.Mode == ViewMode::GlobalSDF) { context->ResetRenderTarget(); diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl new file mode 100644 index 000000000..bbad3d4e1 --- /dev/null +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -0,0 +1,5 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" + +// TODO: implement Global Surface Atlas sampling diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader new file mode 100644 index 000000000..4600982ce --- /dev/null +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -0,0 +1,43 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/GlobalSurfaceAtlas.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" + +META_CB_BEGIN(0, Data) +float3 ViewWorldPos; +float ViewNearPlane; +float3 Padding00; +float ViewFarPlane; +float4 ViewFrustumWorldRays[4]; +GlobalSDFData GlobalSDF; +META_CB_END + +#ifdef _PS_Debug + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); + +// Pixel shader for Global Surface Atlas debug drawing +META_PS(true, FEATURE_LEVEL_SM5) +float4 PS_Debug(Quad_VS2PS input) : SV_Target +{ + // Shot a ray from camera into the Global SDF + GlobalSDFTrace trace; + float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; + viewRay = normalize(viewRay - ViewWorldPos); + trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); + trace.NeedsHitNormal = true; + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + if (!hit.IsHit()) + return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); + + // TODO: debug draw Surface Cache + + // Debug draw SDF normals + float3 color = hit.HitNormal * 0.5f + 0.5f; + return float4(color, 1); +} + +#endif From ceb64afd4a8f951cef01e7236534ae9199c9b125 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 1 Apr 2022 12:38:46 +0200 Subject: [PATCH 073/144] Add `DynamicTypedBuffer` utility --- Source/Engine/Graphics/DynamicBuffer.cpp | 16 +++++++++++++++ Source/Engine/Graphics/DynamicBuffer.h | 26 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index dd96d7d2c..29882aef5 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "DynamicBuffer.h" +#include "PixelFormatExtensions.h" #include "GPUDevice.h" #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" @@ -87,3 +88,18 @@ void DynamicBuffer::Dispose() SAFE_DELETE_GPU_RESOURCE(_buffer); Data.Resize(0); } + +DynamicTypedBuffer::DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess, const String& name) + : DynamicBuffer(initialCapacity, PixelFormatExtensions::SizeInBytes(format), name) + , _format(format) + , _isUnorderedAccess(isUnorderedAccess) +{ +} + +void DynamicTypedBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + auto bufferFlags = GPUBufferFlags::ShaderResource; + if (_isUnorderedAccess) + bufferFlags |= GPUBufferFlags::UnorderedAccess; + desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride); +} diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index aeab4105e..507c284de 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -202,3 +202,29 @@ protected: desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); } }; + +/// +/// Dynamic Typed buffer that allows to upload data to the GPU from CPU (supports dynamic resizing). +/// +class FLAXENGINE_API DynamicTypedBuffer : public DynamicBuffer +{ +private: + PixelFormat _format; + bool _isUnorderedAccess; + +public: + + /// + /// Init + /// + /// Initial capacity of the buffer (in bytes). + /// Format of the data. + /// True if unordered access usage. + /// Buffer name. + DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess = false, const String& name = String::Empty); + +protected: + + // [DynamicBuffer] + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; +}; From 94799a9e28ca4231e3f39415472d5ae2c7136e8c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 1 Apr 2022 12:39:46 +0200 Subject: [PATCH 074/144] Fixes and tweaks for rendering --- Source/Engine/Graphics/GPUContext.h | 6 +++--- Source/Engine/Graphics/RenderBuffers.h | 3 +++ Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 8 +++++--- Source/Engine/Renderer/GlobalSignDistanceFieldPass.h | 1 + Source/Engine/Renderer/Renderer.cpp | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 59699417e..b87faf6a2 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -553,7 +553,7 @@ public: /// /// Sets the rendering viewport and scissor rectangle. /// - /// The viewport. + /// The viewport (in pixels). API_FUNCTION() FORCE_INLINE void SetViewportAndScissors(const Viewport& viewport) { SetViewport(viewport); @@ -575,13 +575,13 @@ public: /// /// Sets the rendering viewport. /// - /// The viewport. + /// The viewport (in pixels). API_FUNCTION() virtual void SetViewport(API_PARAM(Ref) const Viewport& viewport) = 0; /// /// Sets the scissor rectangle. /// - /// The scissor rectangle. + /// The scissor rectangle (in pixels). API_FUNCTION() virtual void SetScissor(API_PARAM(Ref) const Rectangle& scissorRect) = 0; public: diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index b0cbcd321..c0fc51808 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -16,6 +16,9 @@ #define GBUFFER2_FORMAT PixelFormat::R8G8B8A8_UNorm #define GBUFFER3_FORMAT PixelFormat::R8G8B8A8_UNorm +// Light accumulation buffer format (direct+indirect light, materials emissive) +#define LIGHT_BUFFER_FORMAT PixelFormat::R11G11B10_Float + /// /// The scene rendering buffers container. /// diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index c3257b622..eb3c57664 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -135,12 +135,15 @@ bool GlobalSignDistanceFieldPass::Init() // Check platform support const auto device = GPUDevice::Instance; _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad - && FORMAT_FEATURES_ARE_NOT_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D); + && FORMAT_FEATURES_ARE_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D); return false; } bool GlobalSignDistanceFieldPass::setupResources() { + if (!_supported) + return true; + // Load shader if (!_shader) { @@ -239,7 +242,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex result = sdfData.Result; return false; } - + sdfData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global SDF"); // TODO: configurable via graphics settings @@ -251,7 +254,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; // Initialize buffers - sdfData.LastFrameUsed = currentFrame; auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; for (GPUTexture*& cascade : sdfData.Cascades) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 3ca871708..1e48ee25b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -72,6 +72,7 @@ public: /// The output buffer. void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); + // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. void RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); private: diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index b2753e2b5..3cb7d5f0a 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -326,7 +326,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::Distortion); // Get the light accumulation buffer - auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), PixelFormat::R11G11B10_Float); + auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), LIGHT_BUFFER_FORMAT); auto lightBuffer = RenderTargetPool::Get(tempDesc); #if USE_EDITOR From de8a6bea58ac1c4fa1996b2751f4c3c09e7c6fcd Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 1 Apr 2022 12:40:26 +0200 Subject: [PATCH 075/144] Add drawing RenderList if it was not batched/sorted --- Source/Engine/Renderer/RenderList.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 43dcfd82e..e5f353770 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -783,6 +783,28 @@ DRAW: context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } } + if (list.Batches.IsEmpty() && list.Indices.Count() != 0) + { + // Draw calls list has nto been batched so execute draw calls separately + for (int32 j = 0; j < list.Indices.Count(); j++) + { + auto& drawCall = DrawCalls[list.Indices[j]]; + bindParams.FirstDrawCall = &drawCall; + drawCall.Material->Bind(bindParams); + + context->BindIB(drawCall.Geometry.IndexBuffer); + context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); + + if (drawCall.InstanceCount == 0) + { + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); + } + else + { + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); + } + } + } } } From 251de1b643ba0de2ee20b0190b8ea15db52cba2d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 1 Apr 2022 12:41:09 +0200 Subject: [PATCH 076/144] Add support for freeing slots in `RectPack` --- Source/Engine/Utilities/RectPack.h | 37 ++++++++++++------------------ 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Utilities/RectPack.h b/Source/Engine/Utilities/RectPack.h index 1662b4fec..1811684ea 100644 --- a/Source/Engine/Utilities/RectPack.h +++ b/Source/Engine/Utilities/RectPack.h @@ -25,9 +25,6 @@ struct RectPack SizeType Width; SizeType Height; - // The remaining space amount inside this slot (updated on every insertion, initial it equal to width*height). - SizeType SpaceLeft; - // True, if slot has been allocated, otherwise it's free. bool IsUsed; @@ -45,7 +42,6 @@ struct RectPack , Y(y) , Width(width) , Height(height) - , SpaceLeft(width * height) , IsUsed(false) { } @@ -73,16 +69,6 @@ struct RectPack NodeType* Insert(SizeType itemWidth, SizeType itemHeight, SizeType itemPadding, Args&&...args) { NodeType* result; - const SizeType paddedWidth = itemWidth + itemPadding; - const SizeType paddedHeight = itemHeight + itemPadding; - const SizeType paddedSize = paddedWidth * paddedHeight; - - // Check if there is enough space to fix that item within this slot - if (SpaceLeft < paddedSize) - { - // Not enough space - return nullptr; - } // If there are left and right slots there are empty regions around this slot (it also means this slot is occupied) if (Left || Right) @@ -91,25 +77,22 @@ struct RectPack { result = Left->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...); if (result) - { - SpaceLeft -= paddedSize; return result; - } } if (Right) { result = Right->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...); if (result) - { - SpaceLeft -= paddedSize; return result; - } } // Not enough space return nullptr; } + const SizeType paddedWidth = itemWidth + itemPadding; + const SizeType paddedHeight = itemHeight + itemPadding; + // This slot can't fit or has been already occupied if (IsUsed || paddedWidth > Width || paddedHeight > Height) { @@ -122,7 +105,6 @@ struct RectPack { // Insert into this slot IsUsed = true; - SpaceLeft -= paddedSize; result = (NodeType*)this; result->OnInsert(Forward(args)...); return result; @@ -152,9 +134,20 @@ struct RectPack // Insert into this slot IsUsed = true; - SpaceLeft -= paddedSize; result = (NodeType*)this; result->OnInsert(Forward(args)...); return result; } + + /// + /// Frees the node. + /// + /// The node that contains inserted an item or null if failed to find a free space. + template + void Free(Args&&...args) + { + ASSERT(IsUsed); + IsUsed = false; + ((NodeType*)this)->OnFree(Forward(args)...); + } }; From cff57e56977466a5e5240cb7781f74a3f6a2ec75 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 1 Apr 2022 14:15:09 +0200 Subject: [PATCH 077/144] Fix inserting to RectPack after freeing node --- Source/Engine/Utilities/RectPack.h | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Utilities/RectPack.h b/Source/Engine/Utilities/RectPack.h index 1811684ea..b7528834c 100644 --- a/Source/Engine/Utilities/RectPack.h +++ b/Source/Engine/Utilities/RectPack.h @@ -69,6 +69,18 @@ struct RectPack NodeType* Insert(SizeType itemWidth, SizeType itemHeight, SizeType itemPadding, Args&&...args) { NodeType* result; + const SizeType paddedWidth = itemWidth + itemPadding; + const SizeType paddedHeight = itemHeight + itemPadding; + + // Check if we're free and just the right size + if (!IsUsed && Width == paddedWidth && Height == paddedHeight) + { + // Insert into this slot + IsUsed = true; + result = (NodeType*)this; + result->OnInsert(Forward(args)...); + return result; + } // If there are left and right slots there are empty regions around this slot (it also means this slot is occupied) if (Left || Right) @@ -85,14 +97,8 @@ struct RectPack if (result) return result; } - - // Not enough space - return nullptr; } - const SizeType paddedWidth = itemWidth + itemPadding; - const SizeType paddedHeight = itemHeight + itemPadding; - // This slot can't fit or has been already occupied if (IsUsed || paddedWidth > Width || paddedHeight > Height) { @@ -100,16 +106,6 @@ struct RectPack return nullptr; } - // Check if we're just right size - if (Width == paddedWidth && Height == paddedHeight) - { - // Insert into this slot - IsUsed = true; - result = (NodeType*)this; - result->OnInsert(Forward(args)...); - return result; - } - // The width and height of the new child node const SizeType remainingWidth = Math::Max(0, Width - paddedWidth); const SizeType remainingHeight = Math::Max(0, Height - paddedHeight); From 52bb5803b33659b83dd5a6b19017ea06a3cf4122 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:09:13 +0200 Subject: [PATCH 078/144] Cleanup asset previews setup for Editors thumbnails rendering --- Source/Editor/Content/Proxy/AssetProxy.cs | 20 +++++++++++++++++++ .../Editor/Content/Proxy/CubeTextureProxy.cs | 15 ++------------ .../Content/Proxy/MaterialInstanceProxy.cs | 15 ++------------ Source/Editor/Content/Proxy/MaterialProxy.cs | 15 ++------------ Source/Editor/Content/Proxy/ModelProxy.cs | 15 ++------------ .../Content/Proxy/ParticleEmitterProxy.cs | 15 ++------------ .../Content/Proxy/ParticleSystemProxy.cs | 15 ++------------ Source/Editor/Content/Proxy/PrefabProxy.cs | 15 ++------------ .../Editor/Content/Proxy/SkinnedModelProxy.cs | 15 ++------------ 9 files changed, 36 insertions(+), 104 deletions(-) diff --git a/Source/Editor/Content/Proxy/AssetProxy.cs b/Source/Editor/Content/Proxy/AssetProxy.cs index 6f081c050..67a0704c0 100644 --- a/Source/Editor/Content/Proxy/AssetProxy.cs +++ b/Source/Editor/Content/Proxy/AssetProxy.cs @@ -93,5 +93,25 @@ namespace FlaxEditor.Content public virtual void OnThumbnailDrawCleanup(ThumbnailRequest request) { } + + /// + /// Initializes rendering settings for asset preview drawing for a thumbnail. + /// + /// The asset preview. + protected void InitAssetPreview(Viewport.Previews.AssetPreview preview) + { + preview.RenderOnlyWithWindow = false; + preview.UseAutomaticTaskManagement = false; + preview.AnchorPreset = AnchorPresets.StretchAll; + preview.Offsets = Margin.Zero; + + var task = preview.Task; + task.Enabled = false; + + var eyeAdaptation = preview.PostFxVolume.EyeAdaptation; + eyeAdaptation.Mode = EyeAdaptationMode.None; + eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; + preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + } } } diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs index fbf75d9f5..d89771b13 100644 --- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs +++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs @@ -44,19 +44,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new CubeTexturePreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new CubeTexturePreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 38faf5b71..663c191ef 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -51,19 +51,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new MaterialPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new MaterialPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index 8c31bd4c8..e2e74eb14 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -97,19 +97,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new MaterialPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new MaterialPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index 7122bee77..b99d15134 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -58,19 +58,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ModelPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ModelPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs index f10c6cfe1..089c614da 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs @@ -52,19 +52,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ParticleEmitterPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ParticleEmitterPreview(false); + InitAssetPreview(_preview); } // Mark for initial warmup diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index 4d1261232..c19e84a78 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -83,19 +83,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new ParticleEmitterPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new ParticleEmitterPreview(false); + InitAssetPreview(_preview); } // Mark for initial warmup diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index edc4ebba0..9ec143368 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -94,19 +94,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new PrefabPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new PrefabPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs index 2666b836c..597c69a1d 100644 --- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs +++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs @@ -44,19 +44,8 @@ namespace FlaxEditor.Content { if (_preview == null) { - _preview = new AnimatedModelPreview(false) - { - RenderOnlyWithWindow = false, - UseAutomaticTaskManagement = false, - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - }; - _preview.Task.Enabled = false; - - var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation; - eyeAdaptation.Mode = EyeAdaptationMode.None; - eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; - _preview.PostFxVolume.EyeAdaptation = eyeAdaptation; + _preview = new AnimatedModelPreview(false); + InitAssetPreview(_preview); } // TODO: disable streaming for asset during thumbnail rendering (and restore it after) From 3ded5326a2e36357261269232d5ca9d650dea4b3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:09:47 +0200 Subject: [PATCH 079/144] Add more utilities for using `Matrix3x3` --- Source/Engine/Core/Math/Matrix.cpp | 85 +++++-------------- Source/Engine/Core/Math/Matrix.h | 2 + Source/Engine/Core/Math/Matrix3x3.cpp | 29 ++++++- Source/Engine/Core/Math/Matrix3x3.h | 11 +++ .../Engine/Core/Math/OrientedBoundingBox.cpp | 15 +++- Source/Engine/Core/Math/OrientedBoundingBox.h | 7 +- Source/Engine/Core/Math/Vector3.cpp | 11 ++- Source/Engine/Core/Math/Vector3.h | 7 ++ 8 files changed, 95 insertions(+), 72 deletions(-) diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp index d2e87ead2..039ab201b 100644 --- a/Source/Engine/Core/Math/Matrix.cpp +++ b/Source/Engine/Core/Math/Matrix.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "Matrix.h" +#include "Matrix3x3.h" #include "Vector2.h" #include "Quaternion.h" #include "Transform.h" @@ -15,6 +16,17 @@ const Matrix Matrix::Identity( 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); +Matrix::Matrix(const Matrix3x3& matrix) +{ + Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3)); + Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3)); + Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3)); + M14 = 0.0f; + M24 = 0.0f; + M34 = 0.0f; + M44 = 1.0f; +} + String Matrix::ToString() const { return String::Format(TEXT("{}"), *this); @@ -284,8 +296,6 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up xaxis.Normalize(); Vector3::Cross(zaxis, xaxis, yaxis); - result = Identity; - result.M11 = xaxis.X; result.M21 = xaxis.Y; result.M31 = xaxis.Z; @@ -298,9 +308,14 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up result.M23 = zaxis.Y; result.M33 = zaxis.Z; + result.M14 = 0.0f; + result.M24 = 0.0f; + result.M34 = 0.0f; + result.M41 = -Vector3::Dot(xaxis, eye); result.M42 = -Vector3::Dot(yaxis, eye); result.M43 = -Vector3::Dot(zaxis, eye); + result.M44 = 1.0f; } void Matrix::OrthoOffCenter(float left, float right, float bottom, float top, float zNear, float zFar, Matrix& result) @@ -587,33 +602,7 @@ void Matrix::Transformation2D(Vector2& scalingCenter, float scalingRotation, con Matrix Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const Vector3& up) { Matrix result; - Vector3 vector3, vector31, vector32; - - Vector3::Normalize(forward, vector3); - vector3.Negate(); - Vector3::Normalize(Vector3::Cross(up, vector3), vector31); - Vector3::Cross(vector3, vector31, vector32); - - result.M11 = vector31.X; - result.M12 = vector31.Y; - result.M13 = vector31.Z; - result.M14 = 0.0f; - - result.M21 = vector32.X; - result.M22 = vector32.Y; - result.M23 = vector32.Z; - result.M24 = 0.0f; - - result.M31 = vector3.X; - result.M32 = vector3.Y; - result.M33 = vector3.Z; - result.M34 = 0.0f; - - result.M41 = position.X; - result.M42 = position.Y; - result.M43 = position.Z; - result.M44 = 1.0f; - + CreateWorld(position, forward, up, result); return result; } @@ -649,41 +638,9 @@ void Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const Matrix Matrix::CreateFromAxisAngle(const Vector3& axis, float angle) { - Matrix matrix; - - const float x = axis.X; - const float y = axis.Y; - const float z = axis.Z; - const float single = Math::Sin(angle); - const float single1 = Math::Cos(angle); - const float single2 = x * x; - const float single3 = y * y; - const float single4 = z * z; - const float single5 = x * y; - const float single6 = x * z; - const float single7 = y * z; - - matrix.M11 = single2 + single1 * (1.0f - single2); - matrix.M12 = single5 - single1 * single5 + single * z; - matrix.M13 = single6 - single1 * single6 - single * y; - matrix.M14 = 0.0f; - - matrix.M21 = single5 - single1 * single5 - single * z; - matrix.M22 = single3 + single1 * (1.0f - single3); - matrix.M23 = single7 - single1 * single7 + single * x; - matrix.M24 = 0.0f; - - matrix.M31 = single6 - single1 * single6 + single * y; - matrix.M32 = single7 - single1 * single7 - single * x; - matrix.M33 = single4 + single1 * (1.0f - single4); - matrix.M34 = 0.0f; - - matrix.M41 = 0.0f; - matrix.M42 = 0.0f; - matrix.M43 = 0.0f; - matrix.M44 = 1.0f; - - return matrix; + Matrix result; + CreateFromAxisAngle(axis, angle, result); + return result; } void Matrix::CreateFromAxisAngle(const Vector3& axis, float angle, Matrix& result) diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h index 6d680e292..feb7c8160 100644 --- a/Source/Engine/Core/Math/Matrix.h +++ b/Source/Engine/Core/Math/Matrix.h @@ -150,6 +150,8 @@ public: Platform::MemoryCopy(Raw, values, sizeof(float) * 16); } + explicit Matrix(const Matrix3x3& matrix); + public: String ToString() const; diff --git a/Source/Engine/Core/Math/Matrix3x3.cpp b/Source/Engine/Core/Math/Matrix3x3.cpp index c443582a2..c208b4f98 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cpp +++ b/Source/Engine/Core/Math/Matrix3x3.cpp @@ -1,8 +1,9 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "Matrix3x3.h" -#include "../Types/String.h" +#include "Matrix.h" #include "Quaternion.h" +#include "../Types/String.h" const Matrix3x3 Matrix3x3::Zero(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); const Matrix3x3 Matrix3x3::Identity( @@ -10,11 +11,37 @@ const Matrix3x3 Matrix3x3::Identity( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); +Matrix3x3::Matrix3x3(const Matrix& matrix) +{ + Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3)); + Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3)); + Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3)); +} + String Matrix3x3::ToString() const { return String::Format(TEXT("{}"), *this); } +void Matrix3x3::NormalizeScale() +{ + const float scaleX = 1.0f / Vector3(M11, M21, M31).Length(); + const float scaleY = 1.0f / Vector3(M12, M22, M32).Length(); + const float scaleZ = 1.0f / Vector3(M13, M23, M33).Length(); + + M11 *= scaleX; + M21 *= scaleX; + M31 *= scaleX; + + M12 *= scaleY; + M22 *= scaleY; + M32 *= scaleY; + + M13 *= scaleZ; + M23 *= scaleZ; + M33 *= scaleZ; +} + void Matrix3x3::Invert(const Matrix3x3& value, Matrix3x3& result) { const float d11 = value.M22 * value.M33 + value.M23 * -value.M32; diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h index b8227776c..f005abc3a 100644 --- a/Source/Engine/Core/Math/Matrix3x3.h +++ b/Source/Engine/Core/Math/Matrix3x3.h @@ -113,6 +113,12 @@ public: Platform::MemoryCopy(Raw, values, sizeof(float) * 9); } + /// + /// Initializes a new instance of the struct. + /// + /// The 4 by 4 matrix to initialize from with rotation and scale (translation is skipped). + explicit Matrix3x3(const Matrix& matrix); + public: String ToString() const; @@ -255,6 +261,11 @@ public: Transpose(*this, *this); } + /// + /// Removes any scaling from the matrix by performing the normalization (each row magnitude is 1). + /// + void NormalizeScale(); + public: /// diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.cpp b/Source/Engine/Core/Math/OrientedBoundingBox.cpp index 7aaeabb18..593c8cb46 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.cpp +++ b/Source/Engine/Core/Math/OrientedBoundingBox.cpp @@ -8,11 +8,18 @@ OrientedBoundingBox::OrientedBoundingBox(const BoundingBox& bb) { - const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) / 2.0f; + const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) * 0.5f; Extents = bb.Maximum - center; Matrix::Translation(center, Transformation); } +OrientedBoundingBox::OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation) + : Extents(extents) + , Transformation(rotationScale) +{ + Transformation.SetTranslation(translation); +} + OrientedBoundingBox::OrientedBoundingBox(Vector3 points[], int32 pointCount) { ASSERT(points && pointCount > 0); @@ -104,6 +111,12 @@ void OrientedBoundingBox::GetBoundingBox(BoundingBox& result) const BoundingBox::FromPoints(corners, 8, result); } +void OrientedBoundingBox::Transform(const Matrix& matrix) +{ + const Matrix tmp = Transformation; + Matrix::Multiply(tmp, matrix, Transformation); +} + ContainmentType OrientedBoundingBox::Contains(const Vector3& point, float* distance) const { // Transform the point into the obb coordinates diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.h b/Source/Engine/Core/Math/OrientedBoundingBox.h index b76791ed7..a1e8236ab 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.h +++ b/Source/Engine/Core/Math/OrientedBoundingBox.h @@ -40,6 +40,8 @@ public: Transformation = transformation; } + OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation); + // Init // @param minimum The minimum vertex of the bounding box. // @param maximum The maximum vertex of the bounding box. @@ -99,10 +101,7 @@ public: // Transforms this box using a transformation matrix. // @param mat The transformation matrix. - void Transform(const Matrix& mat) - { - Transformation *= mat; - } + void Transform(const Matrix& matrix); // Scales the OBB by scaling its Extents without affecting the Transformation matrix. // By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate. diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 83faa28a3..567e107f6 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -9,6 +9,7 @@ #include "Color.h" #include "Quaternion.h" #include "Matrix.h" +#include "Matrix3x3.h" #include "Int2.h" #include "Int3.h" #include "Int4.h" @@ -215,9 +216,15 @@ void Vector3::Transform(const Vector3& vector, const Matrix& transform, Vector3& void Vector3::Transform(const Vector3* vectors, const Matrix& transform, Vector3* results, int32 vectorsCount) { for (int32 i = 0; i < vectorsCount; i++) - { Transform(vectors[i], transform, results[i]); - } +} + +void Vector3::Transform(const Vector3& vector, const Matrix3x3& transform, Vector3& result) +{ + result = Vector3( + vector.X * transform.M11 + vector.Y * transform.M21 + vector.Z * transform.M31, + vector.X * transform.M12 + vector.Y * transform.M22 + vector.Z * transform.M32, + vector.X * transform.M13 + vector.Y * transform.M23 + vector.Z * transform.M33); } Vector3 Vector3::Transform(const Vector3& vector, const Matrix& transform) diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index b6c1ce7fb..2c0ddc68d 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -11,6 +11,7 @@ struct Double3; struct Double4; struct Quaternion; struct Matrix; +struct Matrix3x3; struct Vector2; struct Vector4; struct Color; @@ -767,6 +768,12 @@ public: // @param vectorsCount Amount of vectors to transform static void Transform(const Vector3* vectors, const Matrix& transform, Vector3* results, int32 vectorsCount); + // Transforms a 3D vector by the given matrix + // @param vector The source vector + // @param transform The transformation matrix + // @param result When the method completes, contains the transformed Vector3 + static void Transform(const Vector3& vector, const Matrix3x3& transform, Vector3& result); + // Transforms a 3D vector by the given matrix // @param vector The source vector // @param transform The transformation matrix From 4c98e0a335843ffc6610d0ae47d7126ddba173c8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:11:01 +0200 Subject: [PATCH 080/144] Add `IsSingleFrame` to `RenderView` for thumbnails/pre-render views drawing without temporal effects and LOD transitions --- Source/Editor/Content/Proxy/AssetProxy.cs | 4 ++++ Source/Engine/Content/Assets/Model.cpp | 9 ++++++--- Source/Engine/Content/Assets/SkinnedModel.cpp | 15 +++++++-------- Source/Engine/Graphics/RenderView.h | 5 +++++ Source/Engine/Renderer/DrawCall.h | 4 ++-- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Content/Proxy/AssetProxy.cs b/Source/Editor/Content/Proxy/AssetProxy.cs index 67a0704c0..6516dbc37 100644 --- a/Source/Editor/Content/Proxy/AssetProxy.cs +++ b/Source/Editor/Content/Proxy/AssetProxy.cs @@ -108,6 +108,10 @@ namespace FlaxEditor.Content var task = preview.Task; task.Enabled = false; + var view = task.View; + view.IsSingleFrame = true; // Disable LOD transitions + task.View = view; + var eyeAdaptation = preview.PostFxVolume.EyeAdaptation; eyeAdaptation.Mode = EyeAdaptationMode.None; eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode; diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 5b7ebac9b..e96592043 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -194,7 +194,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) if (lodIndex == -1) { // Handling model fade-out transition - if (modelFrame == frame && info.DrawState->PrevLOD != -1) + if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame) { // Check if start transition if (info.DrawState->LODTransition == 255) @@ -223,8 +223,11 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) lodIndex += info.LODBias + renderContext.View.ModelLODBias; lodIndex = ClampLODIndex(lodIndex); + if (renderContext.View.IsSingleFrame) + { + } // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports) - if (modelFrame == frame) + else if (modelFrame == frame) { // Check if start transition if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255) @@ -249,7 +252,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info) } // Draw - if (info.DrawState->PrevLOD == lodIndex) + if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame) { LODs[lodIndex].Draw(renderContext, info, 0.0f); } diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 12e940382..e3e1339a1 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -30,13 +30,11 @@ class StreamSkinnedModelLODTask : public ThreadPoolTask { private: - WeakAssetReference _asset; int32 _lodIndex; FlaxStorage::LockData _dataLock; public: - /// /// Init /// @@ -50,7 +48,6 @@ public: } public: - // [ThreadPoolTask] bool HasReference(Object* resource) const override { @@ -58,7 +55,6 @@ public: } protected: - // [ThreadPoolTask] bool Run() override { @@ -191,7 +187,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf if (lodIndex == -1) { // Handling model fade-out transition - if (modelFrame == frame && info.DrawState->PrevLOD != -1) + if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame) { // Check if start transition if (info.DrawState->LODTransition == 255) @@ -220,8 +216,11 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf lodIndex += info.LODBias + renderContext.View.ModelLODBias; lodIndex = ClampLODIndex(lodIndex); + if (renderContext.View.IsSingleFrame) + { + } // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports) - if (modelFrame == frame) + else if (modelFrame == frame) { // Check if start transition if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255) @@ -237,7 +236,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf info.DrawState->PrevLOD = lodIndex; } } - // Check if there was a gap between frames in drawing this model instance + // Check if there was a gap between frames in drawing this model instance else if (modelFrame < frame || info.DrawState->PrevLOD == -1) { // Reset state @@ -246,7 +245,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf } // Draw - if (info.DrawState->PrevLOD == lodIndex) + if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame) { LODs[lodIndex].Draw(renderContext, info, 0.0f); } diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 5a4825a3d..597e436a3 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -97,6 +97,11 @@ public: /// API_FIELD() bool IsOfflinePass = false; + /// + /// Flag used by single-frame rendering passes (eg. thumbnail rendering, model view caching) to reject LOD transitions animations and other temporal draw effects. + /// + API_FIELD() bool IsSingleFrame = false; + /// /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index e3b49b41f..16f2dd590 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -322,13 +322,13 @@ struct TIsPODType #define GEOMETRY_DRAW_STATE_EVENT_BEGIN(drawState, worldMatrix) \ const auto frame = Engine::FrameCount; \ - if (drawState.PrevFrame + 1 < frame) \ + if (drawState.PrevFrame + 1 < frame && !renderContext.View.IsSingleFrame) \ { \ drawState.PrevWorld = worldMatrix; \ } #define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \ - if (drawState.PrevFrame != frame) \ + if (drawState.PrevFrame != frame && !renderContext.View.IsSingleFrame) \ { \ drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ From 8bf01146f1cee74bfa8c48f1f4836aa4597ff051 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:11:26 +0200 Subject: [PATCH 081/144] Add profile event to Global SDF objects drawing loop --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index eb3c57664..23786ea69 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -336,15 +336,18 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex _modelsBufferCount = 0; _voxelSize = voxelSize; _cascadeBounds = cascadeBounds; - for (auto* scene : renderContext.List->Scenes) { - // TODO: optimize for moving camera (copy sdf) - // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) - for (auto& e : scene->Actors) + PROFILE_CPU_NAMED("Draw"); + for (auto* scene : renderContext.List->Scenes) { - if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + // TODO: optimize for moving camera (copy sdf) + // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) + for (auto& e : scene->Actors) { - e.Actor->Draw(renderContext); + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) + { + e.Actor->Draw(renderContext); + } } } } From 016b96e9f09856dfbac1c1c3402d4a3ce862b000 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:13:31 +0200 Subject: [PATCH 082/144] Add objects rasterization to Global Surface Atlas --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 367 +++++++++++++++++- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 14 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 74 +++- Source/Shaders/GlobalSurfaceAtlas.shader | 10 + 5 files changed, 457 insertions(+), 12 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 378f723e5..a78bf85ac 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1b0c45d96dff29c3fceed3eede0b671110d99b4a5774f9e487760b4a0fd9563 -size 1878 +oid sha256:ac008a80c1862ce851e6dee12c32d45477f37b406033182449cd3f6f63e69e1f +size 2377 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index aac122295..fbdf85a02 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -3,6 +3,8 @@ #include "GlobalSurfaceAtlasPass.h" #include "GlobalSignDistanceFieldPass.h" #include "RenderList.h" +#include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUDevice.h" @@ -10,6 +12,20 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Utilities/RectPack.h" + +// This must match HLSL +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (1) +#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE) +#define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 1 // Forces to redraw all object tiles every frame +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) + +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#include "Engine/Debug/DebugDraw.h" +#endif PACK_STRUCT(struct Data0 { @@ -19,20 +35,65 @@ PACK_STRUCT(struct Data0 float ViewFarPlane; Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; + GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; }); +struct GlobalSurfaceAtlasTile : RectPack +{ + GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) + : RectPack(x, y, width, height) + { + } + + void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex); + + void OnFree() + { + } +}; + +struct GlobalSurfaceAtlasObject +{ + uint64 LastFrameUsed; + GlobalSurfaceAtlasTile* Tiles[6] = {}; + OrientedBoundingBox Bounds; +}; + class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer { public: - GPUTexture* Dummy = nullptr; // TODO use some actual atlas textures + int32 Resolution = 0; + GPUTexture* AtlasDepth = nullptr; + GPUTexture* AtlasGBuffer0 = nullptr; + GPUTexture* AtlasGBuffer1 = nullptr; + GPUTexture* AtlasGBuffer2 = nullptr; + GPUTexture* AtlasDirectLight = nullptr; GlobalSurfaceAtlasPass::BindingData Result; + GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles + Dictionary Objects; + + FORCE_INLINE void Clear() + { + RenderTargetPool::Release(AtlasDepth); + RenderTargetPool::Release(AtlasGBuffer0); + RenderTargetPool::Release(AtlasGBuffer1); + RenderTargetPool::Release(AtlasGBuffer2); + RenderTargetPool::Release(AtlasDirectLight); + SAFE_DELETE(AtlasTiles); + Objects.Clear(); + } ~GlobalSurfaceAtlasCustomBuffer() { - RenderTargetPool::Release(Dummy); + Clear(); } }; +void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex) +{ + buffer->Objects[actor].Tiles[tileIndex] = this; +} + String GlobalSurfaceAtlasPass::ToString() const { return TEXT("GlobalSurfaceAtlasPass"); @@ -48,6 +109,9 @@ bool GlobalSurfaceAtlasPass::Init() bool GlobalSurfaceAtlasPass::setupResources() { + if (!_supported) + return true; + // Load shader if (!_shader) { @@ -93,6 +157,7 @@ void GlobalSurfaceAtlasPass::Dispose() RendererPass::Dispose(); // Cleanup + SAFE_DELETE(_objectsBuffer); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -114,22 +179,306 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result = surfaceAtlasData.Result; return false; } - + surfaceAtlasData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global Surface Atlas"); - return false; // TODO: configurable via graphics settings const int32 resolution = 4096; // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float distance = 20000; - // TODO: Initialize buffers - surfaceAtlasData.LastFrameUsed = currentFrame; + // Initialize buffers + bool noCache = surfaceAtlasData.Resolution != resolution; + if (noCache) + { + surfaceAtlasData.Clear(); + surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); - // TODO: Rasterize world geometry into Global Surface Atlas + auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); + uint64 memUsage = 0; + // TODO: try using BC4/BC5/BC7 block compression for Surface Atlas (eg. for Tiles material properties) +#define INIT_ATLAS_TEXTURE(texture, format) desc.Format = format; surfaceAtlasData.texture = RenderTargetPool::Get(desc); if (!surfaceAtlasData.texture) return true; memUsage += surfaceAtlasData.texture->GetMemoryUsage() + INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); + INIT_ATLAS_TEXTURE(AtlasDirectLight, LIGHT_BUFFER_FORMAT); + desc.Flags = GPUTextureFlags::DepthStencil | GPUTextureFlags::ShaderResource; + INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); +#undef INIT_ATLAS_TEXTURE + surfaceAtlasData.Resolution = resolution; + LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); + } + + // Add objects into the atlas + if (_objectsBuffer) + _objectsBuffer->Clear(); + else + _objectsBuffer = New(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")); + _dirtyObjectsBuffer.Clear(); + { + PROFILE_CPU_NAMED("Draw"); + const uint32 viewMask = renderContext.View.RenderLayersMask; + const Vector3 viewPosition = renderContext.View.Position; + const uint16 minTileResolution = 8; // Minimum size (in texels) of the tile in atlas + const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas + const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object + const float minObjectRadius = 20.0f; // Skip too small objects + const float tileTexelsPerWorldUnit = 1.0f / 4.0f; // Scales the tiles resolution + const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down + const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down + const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < minTileResolution, "Invalid tile size configuration."); + for (auto* scene : renderContext.List->Scenes) + { + // TODO: optimize for static objects (SceneRendering could have separate and optimized caching for static actors) + for (auto& e : scene->Actors) + { + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) + { + // TODO: move into actor-specific Draw() impl (eg. via GlobalSurfaceAtlas pass) + auto* staticModel = ScriptingObject::Cast(e.Actor); + if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered()) + { + const bool staticLight = staticModel->HasStaticFlag(StaticFlags::Lightmap); + Matrix localToWorld; + staticModel->GetWorld(&localToWorld); + bool anyTile = false, dirty = false; + GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(e.Actor); + auto& lod = staticModel->Model->LODs.Last(); + BoundingBox localBounds = lod.GetBox(); + Vector3 boundsSize = localBounds.GetSize() * staticModel->GetScale(); + const float distanceScale = Math::Lerp(1.0f, distanceScaling, Math::InverseLerp(distanceScalingStart, distanceScalingEnd, CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition))); + const float tilesScale = tileTexelsPerWorldUnit * distanceScale; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + // Calculate optimal tile resolution for the object side + Vector3 boundsSizeTile = boundsSize; + boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size + boundsSizeTile.Absolute(); + uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); + if (tileResolution < minTileResolution) + { + // Skip too small surfaces + if (object && object->Tiles[tileIndex]) + { + object->Tiles[tileIndex]->Free(); + object->Tiles[tileIndex] = nullptr; + } + continue; + } + + // Clamp and snap to reduce atlas fragmentation + tileResolution = Math::Clamp(tileResolution, minTileResolution, maxTileResolution); + tileResolution = Math::AlignDown(tileResolution, tileResolutionAlignment); + + // Reuse current tile (refit only on a significant resolution change) + if (object && object->Tiles[tileIndex]) + { + const uint16 tileRefitResolutionStep = 32; + const uint16 currentSize = object->Tiles[tileIndex]->Width; + if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) + { + if (!staticLight) + { + // TODO: collect dirty tile to be rasterized once every X frames + } + anyTile = true; + continue; + } + object->Tiles[tileIndex]->Free(); + } + + // Insert tile into atlas + auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, e.Actor, tileIndex); + // TODO: try to perform atlas defragmentation if it's full (eg. max once per ~10s) + if (tile) + { + if (!object) + object = &surfaceAtlasData.Objects[e.Actor]; + object->Tiles[tileIndex] = tile; + anyTile = true; + dirty = true; + } + else if (object) + { + object->Tiles[tileIndex] = nullptr; + } + } + if (anyTile) + { + // Mark object as used + object->LastFrameUsed = currentFrame; + object->Bounds = OrientedBoundingBox(localBounds); + object->Bounds.Transform(localToWorld); + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); + // TODO: populate ObjectsBuffer with objects tiles data + + // Write to objects buffer (this must match unpacking logic in HLSL) + // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) + Vector4 objectData[GLOBAL_SURFACE_ATLAS_OBJECT_SIZE]; + objectData[0] = *(Vector4*)&e.Bounds; + _objectsBuffer->Write(objectData); + } + } + } + } + } + } + + // Remove unused objects + for (auto it = surfaceAtlasData.Objects.Begin(); it.IsNotEnd(); ++it) + { + if (it->Value.LastFrameUsed != currentFrame) + { + for (auto& tile : it->Value.Tiles) + { + if (tile) + tile->Free(); + } + surfaceAtlasData.Objects.Remove(it); + } + } + // TODO: perform atlas defragmentation after certain amount of tiles removal + + // Send objects data to the GPU + { + PROFILE_GPU_CPU("Update Objects"); + // TODO: cache objects data in surfaceAtlasData to reduce memory transfer + _objectsBuffer->Flush(context); + } + + // Rasterize world geometry material properties into Global Surface Atlas + if (_dirtyObjectsBuffer.Count() != 0) + { + PROFILE_GPU_CPU("Rasterize Tiles"); + + RenderContext renderContextTiles = renderContext; + renderContextTiles.List = RenderList::GetFromPool(); + renderContextTiles.View.Pass = DrawPass::GBuffer; + renderContextTiles.View.Mode = ViewMode::Default; + renderContextTiles.View.ModelLODBias += 100000; + renderContextTiles.View.ShadowModelLODBias += 100000; + renderContextTiles.View.IsSingleFrame = true; + renderContextTiles.View.Near = 0.0f; + renderContextTiles.View.Prepare(renderContextTiles); + + GPUTextureView* depthBuffer = surfaceAtlasData.AtlasDepth->View(); + GPUTextureView* targetBuffers[4] = + { + surfaceAtlasData.AtlasDirectLight->View(), + surfaceAtlasData.AtlasGBuffer0->View(), + surfaceAtlasData.AtlasGBuffer1->View(), + surfaceAtlasData.AtlasGBuffer2->View(), + }; + context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); + { + PROFILE_GPU_CPU("Clear"); + if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + { + /// Full-atlas hardware clear + context->ClearDepth(depthBuffer); + context->Clear(targetBuffers[0], Color::Transparent); + context->Clear(targetBuffers[1], Color::Transparent); + context->Clear(targetBuffers[2], Color::Transparent); + context->Clear(targetBuffers[3], Color(1, 0, 0, 0)); + } + else + { + // TODO: clear all dirt tiles in a single draw call (software) + } + } + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].CanUseInstancing = false; + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].CanUseInstancing = false; + for (const auto& e : _dirtyObjectsBuffer) + { + renderContextTiles.List->Clear(); + renderContextTiles.List->DrawCalls.Clear(); + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].Indices.Clear(); + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].Indices.Clear(); + + // Fake projection matrix to disable Screen Size culling based on RenderTools::ComputeBoundsScreenRadiusSquared + renderContextTiles.View.Projection.Values[0][0] = 10000.0f; + + // Collect draw calls for the object + e.First->Draw(renderContextTiles); + + // Render all tiles into the atlas + GlobalSurfaceAtlasObject& object = *e.Second; +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS + DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); +#endif + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + + // Setup view to render object from the side + Vector3 xAxis, yAxis, zAxis = Vector3::Zero; + zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; + yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; + Vector3::Cross(yAxis, zAxis, xAxis); + Vector3 localSpaceOffset = -zAxis * object.Bounds.Extents; + Vector3::TransformNormal(xAxis, object.Bounds.Transformation, xAxis); + Vector3::TransformNormal(yAxis, object.Bounds.Transformation, yAxis); + Vector3::TransformNormal(zAxis, object.Bounds.Transformation, zAxis); + xAxis.NormalizeFast(); + yAxis.NormalizeFast(); + zAxis.NormalizeFast(); + Vector3::Transform(localSpaceOffset, object.Bounds.Transformation, renderContextTiles.View.Position); + renderContextTiles.View.Direction = zAxis; + + // Create view matrix + Matrix viewMatrix; + viewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object.Bounds); + viewBounds.Transform(viewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + Vector3 viewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // Setup projection to capture object from the side + renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles + renderContextTiles.View.Far = viewBoundsSize.Z + 0.2f; + Matrix projectionMatrix; + Matrix::Ortho(viewBoundsSize.X, viewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); + renderContextTiles.View.SetUp(viewMatrix, projectionMatrix); +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS + DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); + DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); +#endif + + // Draw + context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tileWidth, tileHeight)); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBuffer); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBufferNoDecals); + } + } + context->ResetRenderTarget(); + RenderList::ReturnToPool(renderContextTiles.List); + } + + // TODO: update direct lighting atlas (for modified tiles and lights) + // TODO: update static lights only for dirty tiles (dynamic lights every X frames) + // TODO: use custom dynamic vertex buffer to decide which atlas tiles to shade with a light + + // TODO: indirect lighting apply to get infinite bounces for GI // Copy results - result.Dummy = surfaceAtlasData.Dummy; + result.Atlas[0] = surfaceAtlasData.AtlasDepth; + result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; + result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; + result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; + result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; return false; } @@ -155,6 +504,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex for (int32 i = 0; i < 4; i++) data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); data.GlobalSDF = bindingDataSDF.GlobalSDF; + data.GlobalSurfaceAtlas = bindingData.GlobalSurfaceAtlas; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -163,6 +513,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } + context->BindSR(8, bindingData.Atlas[1]->View()); // TODO: pass Atlas[4]=AtlasDirectLight context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index a759fd37b..45db66b2b 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -10,10 +10,18 @@ class FLAXENGINE_API GlobalSurfaceAtlasPass : public RendererPass { public: + // Constant buffer data for Global Surface Atlas access on a GPU. + PACK_STRUCT(struct GlobalSurfaceAtlasData + { + Vector3 Padding; + uint32 ObjectsCount; + }); + // Binding data for the GPU. struct BindingData { - GPUTexture* Dummy; // TODO: add textures + GPUTexture* Atlas[5]; + GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; private: @@ -22,6 +30,10 @@ private: GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; + // Rasterization cache + class DynamicTypedBuffer* _objectsBuffer = nullptr; + Array> _dirtyObjectsBuffer; + public: /// /// Renders the Global Surface Atlas. diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index bbad3d4e1..7ed0a239d 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -1,5 +1,77 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "./Flax/Common.hlsl" +#include "./Flax/Collisions.hlsl" -// TODO: implement Global Surface Atlas sampling +// This must match C++ +#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * 1) + +struct GlobalSurfaceTile +{ + uint Index; + uint2 AtlasCoord; + bool Enabled; +}; + +struct GlobalSurfaceObject +{ + float3 BoundsPosition; + float BoundsRadius; + float3x3 InvRotation; + float3 BoundsMin; + float3 BoundsMax; + GlobalSurfaceTile Tiles[6]; +}; + +float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + return objects.Load(objectStart); +} + +GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + float4 vector0 = objects.Load(objectStart + 0); + GlobalSurfaceObject object = (GlobalSurfaceObject)0; + object.BoundsPosition = vector0.xyz; + object.BoundsRadius = vector0.w; + // TODO: InvRotation + // TODO: BoundsMin + // TODO: BoundsMax + // TODO: Tiles + return object; +} + +// Global Surface Atlas data for a constant buffer +struct GlobalSurfaceAtlasData +{ + float3 Padding; + uint ObjectsCount; +}; + +// Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture3D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) +{ + float4 result = float4(0, 0, 0, 0); + // TODO: add grid culling to object for faster lookup + LOOP + for (uint objectIndex = 0; objectIndex < data.ObjectsCount && result.a <= 0.0f; objectIndex++) + { + // Cull point vs sphere + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(objects, objectIndex); + if (distance(objectBounds.xyz, worldPosition) > objectBounds.w) + continue; + GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); + + // TODO: project worldPosition and worldNormal into object-space + // TODO: select 1, 2 or 3 tiles from object that match normal vector + // TODO: sample tiles with weight based on sample normal (reject tile if projected UVs are outside 0-1 range) + + // TODO: implement Global Surface Atlas sampling + result = float4((objectIndex + 1) / data.ObjectsCount, 0, 0, 1); + } + return result; +} diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 4600982ce..4b0f00b16 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -12,17 +12,26 @@ float3 Padding00; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; +GlobalSurfaceAtlasData GlobalSurfaceAtlas; META_CB_END #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); +Texture2D GlobalSurfaceAtlasTex : register(t8); +//Buffer GlobalSurfaceAtlasObjects : register(t9); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) float4 PS_Debug(Quad_VS2PS input) : SV_Target { +#if 1 + // Preview Global Surface Atlas texture + float4 texSample = GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); + return float4(texSample.rgb, 1); +#endif + // Shot a ray from camera into the Global SDF GlobalSDFTrace trace; float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; @@ -34,6 +43,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); // TODO: debug draw Surface Cache + //float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); // Debug draw SDF normals float3 color = hit.HitNormal * 0.5f + 0.5f; From aba0e46073d55851f6d286b0b03f2c10172aaea2 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 5 Apr 2022 17:21:55 +0200 Subject: [PATCH 083/144] Add utility ctors to Half vectors --- Source/Engine/Core/Math/Half.cpp | 6 +-- Source/Engine/Core/Math/Half.h | 39 ++++++++++++------- Source/Engine/Graphics/Models/Mesh.cpp | 4 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 2 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Core/Math/Half.cpp b/Source/Engine/Core/Math/Half.cpp index 40ab1144c..b17721cd6 100644 --- a/Source/Engine/Core/Math/Half.cpp +++ b/Source/Engine/Core/Math/Half.cpp @@ -12,9 +12,9 @@ static_assert(sizeof(Half2) == 4, "Invalid Half2 type size."); static_assert(sizeof(Half3) == 6, "Invalid Half3 type size."); static_assert(sizeof(Half4) == 8, "Invalid Half4 type size."); -Half2 Half2::Zero(0, 0); -Half3 Half3::Zero(0, 0, 0); -Half4 Half4::Zero(0, 0, 0, 0); +Half2 Half2::Zero(0.0f, 0.0f); +Half3 Half3::Zero(0.0f, 0.0f, 0.0f); +Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f); Half2::Half2(const Vector2& v) { diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h index dcdd6beee..f6fa3d4c5 100644 --- a/Source/Engine/Core/Math/Half.h +++ b/Source/Engine/Core/Math/Half.h @@ -45,7 +45,6 @@ class FLAXENGINE_API Float16Compressor static const int32 minD = minC - subC - 1; public: - static Half Compress(const float value) { #if USE_SSE_HALF_CONVERSION @@ -102,14 +101,12 @@ public: struct FLAXENGINE_API Half2 { public: - /// /// Zero vector /// static Half2 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -121,7 +118,6 @@ public: Half Y; public: - /// /// Default constructor /// @@ -129,6 +125,17 @@ public: { } + /// + /// Init + /// + /// X component + /// Y component + Half2(Half x, Half y) + : X(x) + , Y(y) + { + } + /// /// Init /// @@ -147,7 +154,6 @@ public: Half2(const Vector2& v); public: - /// /// Convert to Vector2 /// @@ -161,14 +167,12 @@ public: struct FLAXENGINE_API Half3 { public: - /// /// Zero vector /// static Half3 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -185,11 +189,17 @@ public: Half Z; public: - Half3() { } + Half3(Half x, Half y, Half z) + : X(x) + , Y(y) + , Z(z) + { + } + Half3(const float x, const float y, const float z) { X = Float16Compressor::Compress(x); @@ -200,7 +210,6 @@ public: Half3(const Vector3& v); public: - Vector3 ToVector3() const; }; @@ -210,14 +219,12 @@ public: struct FLAXENGINE_API Half4 { public: - /// /// Zero vector /// static Half4 Zero; public: - /// /// Gets or sets the X component of the vector. /// @@ -239,11 +246,18 @@ public: Half W; public: - Half4() { } + Half4(Half x, Half y, Half z, Half w) + : X(x) + , Y(y) + , Z(z) + , W(w) + { + } + Half4(const float x, const float y, const float z) { X = Float16Compressor::Compress(x); @@ -265,7 +279,6 @@ public: explicit Half4(const Rectangle& rect); public: - Vector2 ToVector2() const; Vector3 ToVector3() const; Vector4 ToVector4() const; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e7078543f..01a61d5eb 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -89,12 +89,12 @@ namespace } else { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1[i].TexCoord = v; } { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1[i].LightmapUVs = v; } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 67f3683bd..062f7eaea 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -404,7 +404,7 @@ bool UpdateMesh(SkinnedMesh* mesh, MonoArray* verticesObj, MonoArray* trianglesO } else { - auto v = Half2(0, 0); + auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb[i].TexCoord = v; } From 5fc9cb154bd28476039c19774eadc29845506a51 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 5 Apr 2022 17:22:03 +0200 Subject: [PATCH 084/144] Fix typo --- Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index ead6ca9c3..b0cfb30f8 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -515,12 +515,12 @@ bool GPUDeviceDX11::Init() dsDesc.FrontFace = defaultStencilOp; dsDesc.BackFace = defaultStencilOp; int32 index; -#define CREATE_DEPTH_STENCIL_STATE(depthTextEnable, depthWrite) \ - dsDesc.DepthEnable = depthTextEnable; \ +#define CREATE_DEPTH_STENCIL_STATE(depthEnable, depthWrite) \ + dsDesc.DepthEnable = depthEnable; \ dsDesc.DepthWriteMask = depthWrite ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; \ for(int32 depthFunc = 1; depthFunc <= 8; depthFunc++) { \ dsDesc.DepthFunc = (D3D11_COMPARISON_FUNC)depthFunc; \ - index = (int32)depthFunc + (depthTextEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ + index = (int32)depthFunc + (depthEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ HRESULT result = _device->CreateDepthStencilState(&dsDesc, &DepthStencilStates[index]); \ LOG_DIRECTX_RESULT_WITH_RETURN(result); } CREATE_DEPTH_STENCIL_STATE(false, false); From 60d6e6b9cebaf5b9e1c5e099c2bd5cb6352be9c9 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 5 Apr 2022 17:22:45 +0200 Subject: [PATCH 085/144] Add dirty tiles clearing before mesh rasterization to Global Surface Atlas --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 58 +++++++++++++++++-- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 2 + Source/Shaders/GlobalSurfaceAtlas.shader | 18 ++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index fbdf85a02..c718abfe6 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -20,7 +20,7 @@ #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (1) #define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE) #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles -#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 1 // Forces to redraw all object tiles every frame +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // Forces to redraw all object tiles every frame #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) #if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS @@ -38,6 +38,11 @@ PACK_STRUCT(struct Data0 GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; }); +PACK_STRUCT(struct AtlasTileVertex + { + Half2 Position; + }); + struct GlobalSurfaceAtlasTile : RectPack { GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) @@ -138,6 +143,17 @@ bool GlobalSurfaceAtlasPass::setupResources() if (_psDebug->Init(psDesc)) return true; } + if (!_psClear) + { + _psClear = device->CreatePipelineState(); + psDesc.DepthTestEnable = true; + psDesc.DepthWriteEnable = true; + psDesc.DepthFunc = ComparisonFunc::Always; + psDesc.VS = shader->GetVS("VS_Clear"); + psDesc.PS = shader->GetPS("PS_Clear"); + if (_psClear->Init(psDesc)) + return true; + } return false; } @@ -146,6 +162,7 @@ bool GlobalSurfaceAtlasPass::setupResources() void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) { + SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDebug); invalidateResources(); } @@ -157,7 +174,9 @@ void GlobalSurfaceAtlasPass::Dispose() RendererPass::Dispose(); // Cleanup + SAFE_DELETE(_vertexBuffer); SAFE_DELETE(_objectsBuffer); + SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -208,6 +227,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.Resolution = resolution; LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); } + if (!_vertexBuffer) + _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); // Add objects into the atlas if (_objectsBuffer) @@ -223,7 +244,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object const float minObjectRadius = 20.0f; // Skip too small objects - const float tileTexelsPerWorldUnit = 1.0f / 4.0f; // Scales the tiles resolution + const float tileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away @@ -376,7 +397,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co PROFILE_GPU_CPU("Clear"); if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) { - /// Full-atlas hardware clear + // Full-atlas hardware clear context->ClearDepth(depthBuffer); context->Clear(targetBuffers[0], Color::Transparent); context->Clear(targetBuffers[1], Color::Transparent); @@ -385,7 +406,36 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } else { - // TODO: clear all dirt tiles in a single draw call (software) + // Per-tile clear (with a single draw call) + _vertexBuffer->Clear(); + _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); + const Vector2 posToClipMul(2.0f / resolution, -2.0f / resolution); + const Vector2 posToClipAdd(-1.0f, 1.0f); + for (const auto& e : _dirtyObjectsBuffer) + { + const auto& object = *e.Second; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); + auto* quad = _vertexBuffer->WriteReserve(6); + quad[0] = { { max } }; + quad[1] = { { min.X, max.Y } }; + quad[2] = { { min } }; + quad[3] = quad[2]; + quad[4] = { { max.X, min.Y } }; + quad[5] = quad[0]; + } + } + _vertexBuffer->Flush(context); + auto vb = _vertexBuffer->GetBuffer(); + context->SetState(_psClear); + context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); + context->BindVB(ToSpan(&vb, 1)); + context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); } } renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].CanUseInstancing = false; diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 45db66b2b..4966913d9 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -27,10 +27,12 @@ public: private: bool _supported = false; AssetReference _shader; + GPUPipelineState* _psClear = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; // Rasterization cache + class DynamicVertexBuffer* _vertexBuffer = nullptr; class DynamicTypedBuffer* _objectsBuffer = nullptr; Array> _dirtyObjectsBuffer; diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 4b0f00b16..1f8101f6c 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -15,6 +15,24 @@ GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; META_CB_END +// Vertex shader for Global Surface Atlas software clearing +META_VS(true, FEATURE_LEVEL_SM5) +META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +float4 VS_Clear(float2 Position : POSITION0) : SV_Position +{ + return float4(Position, 1, 1); +} + +// Pixel shader for Global Surface Atlas software clearing +META_PS(true, FEATURE_LEVEL_SM5) +void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out float4 RT1 : SV_Target2, out float4 RT2 : SV_Target3) +{ + Light = float4(0, 0, 0, 0); + RT0 = float4(0, 0, 0, 0); + RT1 = float4(0, 0, 0, 0); + RT2 = float4(1, 0, 0, 0); +} + #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); From 34c0d6c44204cf860153b82a0304f76725ebc1c6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 6 Apr 2022 13:15:45 +0200 Subject: [PATCH 086/144] Add Global Surface Atlas objects redrawing --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 ++-- .../Engine/Renderer/GlobalSurfaceAtlasPass.cpp | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index a78bf85ac..1b733c177 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac008a80c1862ce851e6dee12c32d45477f37b406033182449cd3f6f63e69e1f -size 2377 +oid sha256:6be2f094757f54ce8080ceb7e53834cd4b0082b8b0ce83184ac3163518311715 +size 3000 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index c718abfe6..b97e7bb66 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -60,6 +60,7 @@ struct GlobalSurfaceAtlasTile : RectPack struct GlobalSurfaceAtlasObject { uint64 LastFrameUsed; + uint64 LastFrameDirty; GlobalSurfaceAtlasTile* Tiles[6] = {}; OrientedBoundingBox Bounds; }; @@ -244,7 +245,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object const float minObjectRadius = 20.0f; // Skip too small objects - const float tileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution + const float tileTexelsPerWorldUnit = 1.0f / 20.0f; // Scales the tiles resolution const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away @@ -260,7 +261,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co auto* staticModel = ScriptingObject::Cast(e.Actor); if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered()) { - const bool staticLight = staticModel->HasStaticFlag(StaticFlags::Lightmap); Matrix localToWorld; staticModel->GetWorld(&localToWorld); bool anyTile = false, dirty = false; @@ -299,10 +299,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const uint16 currentSize = object->Tiles[tileIndex]->Width; if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) { - if (!staticLight) - { - // TODO: collect dirty tile to be rasterized once every X frames - } anyTile = true; continue; } @@ -327,12 +323,20 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } if (anyTile) { + // Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed) + uint32 redrawFramesCount = staticModel->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4; + if (currentFrame - object->LastFrameDirty >= (redrawFramesCount + (e.Actor->GetID().D & redrawFramesCount))) + dirty = true; + // Mark object as used object->LastFrameUsed = currentFrame; object->Bounds = OrientedBoundingBox(localBounds); object->Bounds.Transform(localToWorld); if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + { + object->LastFrameDirty = currentFrame; _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); + } // TODO: populate ObjectsBuffer with objects tiles data // Write to objects buffer (this must match unpacking logic in HLSL) From 430ef09ab1da8228d2a187f2f1fbe5f5174e9b6a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 6 Apr 2022 13:16:12 +0200 Subject: [PATCH 087/144] Fix render pass resource checking --- Source/Engine/Renderer/GBufferPass.cpp | 2 +- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 5 +++-- Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp | 2 +- Source/Engine/Renderer/MotionBlurPass.cpp | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 682dd9b6a..e8b753b09 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -249,7 +249,7 @@ bool SortDecal(Decal* const& a, Decal* const& b) void GBufferPass::RenderDebug(RenderContext& renderContext) { // Check if has resources loaded - if (setupResources()) + if (checkIfSkipPass()) return; // Cache data diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 23786ea69..7e4846b7f 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -170,7 +170,8 @@ bool GlobalSignDistanceFieldPass::setupResources() _csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1); // Init buffer - _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); + if (!_modelsBuffer) + _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; @@ -229,7 +230,7 @@ bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) { // Skip if not supported - if (setupResources()) + if (checkIfSkipPass()) return true; if (renderContext.List->Scenes.Count() == 0) return true; diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index b97e7bb66..3a99cc01b 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -186,7 +186,7 @@ void GlobalSurfaceAtlasPass::Dispose() bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) { // Skip if not supported - if (setupResources()) + if (checkIfSkipPass()) return true; if (renderContext.List->Scenes.Count() == 0) return true; diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index d0ad68691..e00225225 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -245,7 +245,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f { auto context = GPUDevice::Instance->GetMainContext(); const auto motionVectors = renderContext.Buffers->MotionVectors; - if (!motionVectors->IsAllocated() || setupResources()) + if (!motionVectors->IsAllocated() || checkIfSkipPass()) { context->Draw(frame); return; From 49a67be419c7c2949364e12b801c4c9ef3aeca03 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 6 Apr 2022 16:56:04 +0200 Subject: [PATCH 088/144] Add objects culling in Global Surface Atlas --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +-- .../Renderer/GlobalSurfaceAtlasPass.cpp | 33 +++++++++++-------- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 2 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 33 ++++++++++++------- Source/Shaders/GlobalSurfaceAtlas.shader | 17 ++++------ 5 files changed, 51 insertions(+), 38 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 1b733c177..24d65714f 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6be2f094757f54ce8080ceb7e53834cd4b0082b8b0ce83184ac3163518311715 -size 3000 +oid sha256:6519ab7a3915ab48b643fa5b8b7a3076cfad0e356dae8158e5194499c87dec32 +size 2969 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 3a99cc01b..7777608ef 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -17,8 +17,7 @@ #include "Engine/Utilities/RectPack.h" // This must match HLSL -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (1) -#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE) +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of Vector4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // Forces to redraw all object tiles every frame #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) @@ -74,10 +73,16 @@ public: GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; + DynamicTypedBuffer ObjectsBuffer; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; + GlobalSurfaceAtlasCustomBuffer() + : ObjectsBuffer(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")) + { + } + FORCE_INLINE void Clear() { RenderTargetPool::Release(AtlasDepth); @@ -86,6 +91,7 @@ public: RenderTargetPool::Release(AtlasGBuffer2); RenderTargetPool::Release(AtlasDirectLight); SAFE_DELETE(AtlasTiles); + ObjectsBuffer.Clear(); Objects.Clear(); } @@ -176,7 +182,6 @@ void GlobalSurfaceAtlasPass::Dispose() // Cleanup SAFE_DELETE(_vertexBuffer); - SAFE_DELETE(_objectsBuffer); SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; @@ -232,10 +237,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); // Add objects into the atlas - if (_objectsBuffer) - _objectsBuffer->Clear(); - else - _objectsBuffer = New(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")); + surfaceAtlasData.ObjectsBuffer.Clear(); _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -245,7 +247,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object const float minObjectRadius = 20.0f; // Skip too small objects - const float tileTexelsPerWorldUnit = 1.0f / 20.0f; // Scales the tiles resolution + const float tileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away @@ -337,13 +339,17 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co object->LastFrameDirty = currentFrame; _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); } - // TODO: populate ObjectsBuffer with objects tiles data // Write to objects buffer (this must match unpacking logic in HLSL) + Matrix worldToLocalBounds; + Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) - Vector4 objectData[GLOBAL_SURFACE_ATLAS_OBJECT_SIZE]; + auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_SIZE); objectData[0] = *(Vector4*)&e.Bounds; - _objectsBuffer->Write(objectData); + objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); + objectData[2] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); + objectData[3] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); + objectData[4] = Vector4(object->Bounds.Extents, 0.0f); } } } @@ -369,8 +375,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Send objects data to the GPU { PROFILE_GPU_CPU("Update Objects"); - // TODO: cache objects data in surfaceAtlasData to reduce memory transfer - _objectsBuffer->Flush(context); + surfaceAtlasData.ObjectsBuffer.Flush(context); } // Rasterize world geometry material properties into Global Surface Atlas @@ -532,6 +537,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer(); result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; return false; @@ -568,6 +574,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } context->BindSR(8, bindingData.Atlas[1]->View()); // TODO: pass Atlas[4]=AtlasDirectLight + context->BindSR(9, bindingData.Objects ? bindingData.Objects->View() : nullptr); context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 4966913d9..b39862614 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -21,6 +21,7 @@ public: struct BindingData { GPUTexture* Atlas[5]; + GPUBuffer* Objects; GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; @@ -33,7 +34,6 @@ private: // Rasterization cache class DynamicVertexBuffer* _vertexBuffer = nullptr; - class DynamicTypedBuffer* _objectsBuffer = nullptr; Array> _dirtyObjectsBuffer; public: diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 7ed0a239d..8d0c07010 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,7 +4,7 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * 1) +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of float4s per-object struct GlobalSurfaceTile { @@ -17,30 +17,35 @@ struct GlobalSurfaceObject { float3 BoundsPosition; float BoundsRadius; - float3x3 InvRotation; - float3 BoundsMin; - float3 BoundsMax; + float4x4 WorldToLocal; + float3 Extent; GlobalSurfaceTile Tiles[6]; }; float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; return objects.Load(objectStart); } GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; float4 vector0 = objects.Load(objectStart + 0); + float4 vector1 = objects.Load(objectStart + 1); + float4 vector2 = objects.Load(objectStart + 2); + float4 vector3 = objects.Load(objectStart + 3); + float4 vector4 = objects.Load(objectStart + 4); GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; - // TODO: InvRotation - // TODO: BoundsMin - // TODO: BoundsMax + object.WorldToLocal[0] = float4(vector1.xyz, 0.0f); + object.WorldToLocal[1] = float4(vector2.xyz, 0.0f); + object.WorldToLocal[2] = float4(vector3.xyz, 0.0f); + object.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); + object.Extent = vector4.xyz; // TODO: Tiles return object; } @@ -53,7 +58,7 @@ struct GlobalSurfaceAtlasData }; // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). -float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture3D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture2D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); // TODO: add grid culling to object for faster lookup @@ -65,13 +70,17 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture3D objectBounds.w) continue; GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); + float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz; + object.Extent += 10.0f; // TODO: why SDF is so enlarged compared to actual bounds? + if (any(localPosition > object.Extent) || any(localPosition < -object.Extent)) + continue; + float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); - // TODO: project worldPosition and worldNormal into object-space // TODO: select 1, 2 or 3 tiles from object that match normal vector // TODO: sample tiles with weight based on sample normal (reject tile if projected UVs are outside 0-1 range) // TODO: implement Global Surface Atlas sampling - result = float4((objectIndex + 1) / data.ObjectsCount, 0, 0, 1); + result = float4((float)(objectIndex + 1) / (float)data.ObjectsCount, 0, 0, 1); } return result; } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 1f8101f6c..9003a52df 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -38,16 +38,15 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); Texture2D GlobalSurfaceAtlasTex : register(t8); -//Buffer GlobalSurfaceAtlasObjects : register(t9); +Buffer GlobalSurfaceAtlasObjects : register(t9); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) float4 PS_Debug(Quad_VS2PS input) : SV_Target { -#if 1 +#if 0 // Preview Global Surface Atlas texture - float4 texSample = GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); - return float4(texSample.rgb, 1); + return float4(GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 1); #endif // Shot a ray from camera into the Global SDF @@ -59,13 +58,11 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); if (!hit.IsHit()) return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); + //return float4(hit.HitNormal * 0.5f + 0.5f, 1); - // TODO: debug draw Surface Cache - //float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); - - // Debug draw SDF normals - float3 color = hit.HitNormal * 0.5f + 0.5f; - return float4(color, 1); + // Sample Global Surface Atlas at the hit location + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasTex, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); + return float4(surfaceColor.rgb, 1); } #endif From da4008575bc5ba583a01d1a34a5f3bbcb4f02f48 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 7 Apr 2022 17:16:06 +0200 Subject: [PATCH 089/144] Progress on surface atlas sampling --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 107 ++++++++++++------ Source/Shaders/GlobalSurfaceAtlas.hlsl | 75 ++++++++++-- Source/Shaders/GlobalSurfaceAtlas.shader | 7 +- 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 24d65714f..8b8cbd705 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6519ab7a3915ab48b643fa5b8b7a3076cfad0e356dae8158e5194499c87dec32 -size 2969 +oid sha256:13070c079b7a2e5530be1b9d9392b9cc36f41bc566bf86595fe0460a7be4f536 +size 3046 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 7777608ef..f757a55b9 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -17,7 +17,7 @@ #include "Engine/Utilities/RectPack.h" // This must match HLSL -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of Vector4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // Forces to redraw all object tiles every frame #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) @@ -44,6 +44,11 @@ PACK_STRUCT(struct AtlasTileVertex struct GlobalSurfaceAtlasTile : RectPack { + Vector3 ViewDirection; + Vector3 ViewPosition; // TODO: use from ViewMatrix + Vector3 ViewBoundsSize; + Matrix ViewMatrix; + GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) : RectPack(x, y, width, height) { @@ -209,6 +214,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // TODO: configurable via graphics settings const int32 resolution = 4096; + const float resolutionInv = 1.0f / resolution; // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float distance = 20000; @@ -350,6 +356,55 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co objectData[2] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); objectData[3] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); objectData[4] = Vector4(object->Bounds.Extents, 0.0f); + // TODO: try to optimize memory footprint (eg. merge scale into extents and use rotation+offset but reconstruct rotation from two axes with sign) + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object->Tiles[tileIndex]; + const int32 tileStart = 5 + tileIndex * 5; + if (!tile) + { + // Disable tile + objectData[tileStart + 4] = Vector4::Zero; + continue; + } + + // Setup view to render object from the side + Vector3 xAxis, yAxis, zAxis = Vector3::Zero; + zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; + yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; + Vector3::Cross(yAxis, zAxis, xAxis); + Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents; + Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis); + Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis); + Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis); + xAxis.NormalizeFast(); + yAxis.NormalizeFast(); + zAxis.NormalizeFast(); + Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition); + tile->ViewDirection = zAxis; + + // Create view matrix + tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object->Bounds); + viewBounds.Transform(tile->ViewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // 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; + objectData[tileStart + 0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight); + objectData[tileStart + 1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); + objectData[tileStart + 2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); + objectData[tileStart + 3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); + objectData[tileStart + 4] = Vector4(tile->ViewBoundsSize, 1.0f); + } } } } @@ -418,7 +473,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Per-tile clear (with a single draw call) _vertexBuffer->Clear(); _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); - const Vector2 posToClipMul(2.0f / resolution, -2.0f / resolution); + const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); const Vector2 posToClipAdd(-1.0f, 1.0f); for (const auto& e : _dirtyObjectsBuffer) { @@ -475,41 +530,14 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; - // Setup view to render object from the side - Vector3 xAxis, yAxis, zAxis = Vector3::Zero; - zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; - yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; - Vector3::Cross(yAxis, zAxis, xAxis); - Vector3 localSpaceOffset = -zAxis * object.Bounds.Extents; - Vector3::TransformNormal(xAxis, object.Bounds.Transformation, xAxis); - Vector3::TransformNormal(yAxis, object.Bounds.Transformation, yAxis); - Vector3::TransformNormal(zAxis, object.Bounds.Transformation, zAxis); - xAxis.NormalizeFast(); - yAxis.NormalizeFast(); - zAxis.NormalizeFast(); - Vector3::Transform(localSpaceOffset, object.Bounds.Transformation, renderContextTiles.View.Position); - renderContextTiles.View.Direction = zAxis; - - // Create view matrix - Matrix viewMatrix; - viewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, renderContextTiles.View.Position))); - viewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, renderContextTiles.View.Position))); - viewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, renderContextTiles.View.Position))); - viewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); - - // Calculate object bounds size in the view - OrientedBoundingBox viewBounds(object.Bounds); - viewBounds.Transform(viewMatrix); - Vector3 viewExtent; - Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); - Vector3 viewBoundsSize = viewExtent.GetAbsolute() * 2.0f; - // Setup projection to capture object from the side + renderContextTiles.View.Position = tile->ViewPosition; + renderContextTiles.View.Direction = tile->ViewDirection; renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles - renderContextTiles.View.Far = viewBoundsSize.Z + 0.2f; + renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 0.2f; Matrix projectionMatrix; - Matrix::Ortho(viewBoundsSize.X, viewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); - renderContextTiles.View.SetUp(viewMatrix, projectionMatrix); + Matrix::Ortho(tile->ViewBoundsSize.X, tile->ViewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); + renderContextTiles.View.SetUp(tile->ViewMatrix, projectionMatrix); #if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); @@ -573,8 +601,15 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } - context->BindSR(8, bindingData.Atlas[1]->View()); // TODO: pass Atlas[4]=AtlasDirectLight - context->BindSR(9, bindingData.Objects ? bindingData.Objects->View() : nullptr); + context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); + context->BindSR(9, bindingData.Atlas[0]->View()); + { + GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse + //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals + //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao + //GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light + context->BindSR(10, tex->View()); + } context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 8d0c07010..b0bcf3a6d 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,12 +4,14 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.3f // Cut-off value for tiles transitions blending during sampling struct GlobalSurfaceTile { - uint Index; - uint2 AtlasCoord; + float4 AtlasRect; + float4x4 WorldToLocal; + float3 ViewBoundsSize; bool Enabled; }; @@ -19,7 +21,6 @@ struct GlobalSurfaceObject float BoundsRadius; float4x4 WorldToLocal; float3 Extent; - GlobalSurfaceTile Tiles[6]; }; float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) @@ -37,7 +38,7 @@ GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint ob float4 vector1 = objects.Load(objectStart + 1); float4 vector2 = objects.Load(objectStart + 2); float4 vector3 = objects.Load(objectStart + 3); - float4 vector4 = objects.Load(objectStart + 4); + float4 vector4 = objects.Load(objectStart + 4); // w unused GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; @@ -46,10 +47,30 @@ GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint ob object.WorldToLocal[2] = float4(vector3.xyz, 0.0f); object.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); object.Extent = vector4.xyz; - // TODO: Tiles return object; } +GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint objectIndex, uint tileIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; + const uint tileStart = objectStart + 5 + tileIndex * 5; + float4 vector0 = objects.Load(tileStart + 0); + float4 vector1 = objects.Load(tileStart + 1); + float4 vector2 = objects.Load(tileStart + 2); + float4 vector3 = objects.Load(tileStart + 3); + float4 vector4 = objects.Load(tileStart + 4); + GlobalSurfaceTile tile = (GlobalSurfaceTile)0; + tile.AtlasRect = vector0.xyzw; + tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); + tile.WorldToLocal[1] = float4(vector2.xyz, 0.0f); + tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f); + tile.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); + tile.ViewBoundsSize = vector4.xyz; + tile.Enabled = vector4.w > 0; + return tile; +} + // Global Surface Atlas data for a constant buffer struct GlobalSurfaceAtlasData { @@ -58,9 +79,10 @@ struct GlobalSurfaceAtlasData }; // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). -float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture2D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); + float surfaceThreshold = 10.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) // TODO: add grid culling to object for faster lookup LOOP for (uint objectIndex = 0; objectIndex < data.ObjectsCount && result.a <= 0.0f; objectIndex++) @@ -71,16 +93,45 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture2D atl continue; GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz; - object.Extent += 10.0f; // TODO: why SDF is so enlarged compared to actual bounds? - if (any(localPosition > object.Extent) || any(localPosition < -object.Extent)) + float3 localExtent = object.Extent + surfaceThreshold; + if (any(localPosition > localExtent) || any(localPosition < -localExtent)) continue; float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); - // TODO: select 1, 2 or 3 tiles from object that match normal vector - // TODO: sample tiles with weight based on sample normal (reject tile if projected UVs are outside 0-1 range) + // Pick tiles to sample based on the directionality + // TODO: sample 1/2/3 tiles with weight based on sample normal + uint tileIndex = 2; + + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + + // Tile normal weight based on the sampling angle + float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); + float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); + normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); + if (normalWeight <= 0.0f) + continue; + + // Get tile UV and depth at the world position + float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; + float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; + float2 tileUV = (tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f; + tileUV.y = 1.0 - tileUV.y; + float2 atlasCoord = tileUV * tile.AtlasRect.zw + tile.AtlasRect.xy; + + // Tile depth weight based on sample position occlusion + // TODO: gather 4 depth samples to smooth weight (depth weight per-sample used late for bilinear weights) + float tileZ = depth.Load(int3(atlasCoord, 0)).x; + float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z; + float depthWeight = 1.0f - saturate((abs(tileDepth - tileZ) - depthThreshold) / (0.5f * depthThreshold)); + if (depthWeight <= 0.0f) + continue; + + // Sample atlas texture + // TODO: separate GatherRed/Blue/Green with bilinear weights + float4 color = atlas.Load(int3(atlasCoord, 0)); // TODO: implement Global Surface Atlas sampling - result = float4((float)(objectIndex + 1) / (float)data.ObjectsCount, 0, 0, 1); + result = float4(color.rgb, 1); } return result; } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 9003a52df..91f7bb0f0 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -37,8 +37,9 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); -Texture2D GlobalSurfaceAtlasTex : register(t8); -Buffer GlobalSurfaceAtlasObjects : register(t9); +Buffer GlobalSurfaceAtlasObjects : register(t8); +Texture2D GlobalSurfaceAtlasDepth : register(t9); +Texture2D GlobalSurfaceAtlasTex : register(t10); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -61,7 +62,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasTex, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); return float4(surfaceColor.rgb, 1); } From 598876d8e3dfa4facc10132da07e234f151c08e6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 8 Apr 2022 13:53:23 +0200 Subject: [PATCH 090/144] Add shader reloading on header file edit for shaders with compilation errors --- Source/Engine/Content/Asset.cpp | 13 ++++++------- .../ShadersCompilation/ShadersCompilation.cpp | 11 ++++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 8d520a62d..380cef278 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -324,15 +324,14 @@ void Asset::Reload() Content::AssetReloading(this); OnReloading(this); - // Fire unload event - // TODO: maybe just call release storage ref or sth? we cannot call onUnload because managed asset objects gets invalidated - //onUnload_MainThread(); - ScopeLock lock(Locker); - // Unload current data - unload(true); - _isLoaded = false; + if (IsLoaded()) + { + // Unload current data + unload(true); + _isLoaded = false; + } // Start reloading process startLoading(); diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index 21d564668..9e56367a6 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -19,6 +19,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/Asset.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/Assets/Shader.h" #if USE_EDITOR #define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module #include "Engine/ContentImporters/AssetsImportingManager.h" @@ -56,7 +58,6 @@ using namespace ShadersCompilationImpl; class ShadersCompilationService : public EngineService { public: - ShadersCompilationService() : EngineService(TEXT("Shaders Compilation Service"), -100) { @@ -250,6 +251,7 @@ namespace if (action == FileSystemAction::Delete) return; + // Get list of assets using this shader file Array toReload; { ScopeLock lock(ShaderIncludesMapLocker); @@ -260,6 +262,13 @@ namespace toReload = file->Value; } + // Add any shaders that failed to load (eg. due to error in included header) + for (Asset* asset : Content::GetAssets()) + { + if (asset->LastLoadFailed() && asset->Is() && !toReload.Contains(asset)) + toReload.Add(asset); + } + LOG(Info, "Shader include \'{0}\' has been modified.", path); // Wait a little so app that was editing the file (e.g. Visual Studio, Notepad++) has enough time to flush whole file change From fbc1e6c8615765f7fdffff8d1a048dc0f84812f7 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 8 Apr 2022 13:53:59 +0200 Subject: [PATCH 091/144] Fix shader source code encoding error on compilation error --- .../Graphics/Shaders/Cache/ShaderAssetBase.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index d84231406..efaed6d8f 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -282,16 +282,18 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) editorDefine.Definition = "1"; #endif InitCompilationOptions(options); - if (ShadersCompilation::Compile(options)) + const bool failed = ShadersCompilation::Compile(options); + + // Encrypt source code + Encryption::EncryptBytes(reinterpret_cast(source), sourceLength); + + if (failed) { LOG(Error, "Failed to compile shader '{0}'", parent->ToString()); return true; } LOG(Info, "Shader '{0}' compiled! Cache size: {1} bytes", parent->ToString(), cacheStream.GetPosition()); - // Encrypt source code - Encryption::EncryptBytes(reinterpret_cast(source), sourceLength); - // Save compilation result (based on current caching policy) if (cachingMode == ShaderStorage::CachingMode::AssetInternal) { @@ -345,7 +347,7 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) result.Data.Link(cacheChunk->Data); } #if COMPILE_WITH_SHADER_CACHE_MANAGER - // Check if has cached shader + // Check if has cached shader else if (cachedEntry.IsValid() || ShaderCacheManager::TryGetEntry(shaderProfile, parent->GetID(), cachedEntry)) { // Load results from cache From 7735849b7fd3b1d0864425a641d58c85344ffe82 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 8 Apr 2022 13:54:31 +0200 Subject: [PATCH 092/144] Add Global Surface Atlas sampling --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 6 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 121 ++++++++++++------ 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index f757a55b9..5ed5c1027 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -145,6 +145,8 @@ bool GlobalSurfaceAtlasPass::setupResources() const auto device = GPUDevice::Instance; const auto shader = _shader->GetShader(); _cb0 = shader->GetCB(0); + if (!_cb0) + return true; // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; @@ -285,7 +287,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size boundsSizeTile.Absolute(); uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); - if (tileResolution < minTileResolution) + if (tileResolution < 4) { // Skip too small surfaces if (object && object->Tiles[tileIndex]) @@ -399,7 +401,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // 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; - objectData[tileStart + 0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight); + objectData[tileStart + 0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv; objectData[tileStart + 1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); objectData[tileStart + 2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); objectData[tileStart + 3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index b0bcf3a6d..eefa09bfe 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -5,11 +5,11 @@ // This must match C++ #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object -#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.3f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.25f // Cut-off value for tiles transitions blending during sampling struct GlobalSurfaceTile { - float4 AtlasRect; + float4 AtlasRectUV; float4x4 WorldToLocal; float3 ViewBoundsSize; bool Enabled; @@ -61,7 +61,7 @@ GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint object float4 vector3 = objects.Load(tileStart + 3); float4 vector4 = objects.Load(tileStart + 4); GlobalSurfaceTile tile = (GlobalSurfaceTile)0; - tile.AtlasRect = vector0.xyzw; + tile.AtlasRectUV = vector0.xyzw; tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); tile.WorldToLocal[1] = float4(vector2.xyz, 0.0f); tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f); @@ -78,11 +78,64 @@ struct GlobalSurfaceAtlasData uint ObjectsCount; }; +float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilinearWeights) +{ + float4 sampleX = atlas.GatherRed(SamplerLinearClamp, atlasUV); + float4 sampleY = atlas.GatherGreen(SamplerLinearClamp, atlasUV); + float4 sampleZ = atlas.GatherBlue(SamplerLinearClamp, atlasUV); + return float3(dot(sampleX, bilinearWeights), dot(sampleY, bilinearWeights), dot(sampleZ, bilinearWeights)); +} + +float4 SampleGlobalSurfaceAtlasTile(GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) +{ + // Tile normal weight based on the sampling angle + float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); + float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); + normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); + if (normalWeight <= 0.0f) + return 0; + + // Get tile UV and depth at the world position + float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; + float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; + float2 tileUV = (tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f; + tileUV.y = 1.0 - tileUV.y; + float2 atlasUV = tileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; + + // Calculate bilinear weights + float4 bilinearWeights; + bilinearWeights.x = (1.0 - tileUV.x) * (tileUV.y); + bilinearWeights.y = (tileUV.x) * (tileUV.y); + bilinearWeights.z = (tileUV.x) * (1 - tileUV.y); + bilinearWeights.w = (1 - tileUV.x) * (1 - tileUV.y); + bilinearWeights = saturate(bilinearWeights); + + // Tile depth weight based on sample position occlusion + float4 tileZ = depth.Gather(SamplerLinearClamp, atlasUV, 0.0f); + float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z; + float4 depthVisibility = 1.0f; + UNROLL + for (uint i = 0; i < 4; i++) + depthVisibility[i] = 1.0f - saturate((abs(tileDepth - tileZ[i]) - depthThreshold) / (0.5f * depthThreshold)); + float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); + if (sampleWeight <= 0.0f) + return 0; + bilinearWeights = depthVisibility * bilinearWeights; + //bilinearWeights = normalize(bilinearWeights); + + // Sample atlas texture + float3 sampleColor = SampleGlobalSurfaceAtlasTex(atlas, atlasUV, bilinearWeights); + + //return float4(sampleWeight.xxx, sampleWeight); + return float4(sampleColor.rgb * sampleWeight, sampleWeight); + //return float4(normalWeight.xxx, sampleWeight); +} + // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); - float surfaceThreshold = 10.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) + float surfaceThreshold = 20.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) // TODO: add grid culling to object for faster lookup LOOP for (uint objectIndex = 0; objectIndex < data.ObjectsCount && result.a <= 0.0f; objectIndex++) @@ -96,42 +149,34 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer localExtent) || any(localPosition < -localExtent)) continue; - float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); - // Pick tiles to sample based on the directionality + // Sample tiles based on the directionality + // TODO: place enabled tiles mask in object data to skip reading disabled tiles // TODO: sample 1/2/3 tiles with weight based on sample normal - uint tileIndex = 2; - - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); - - // Tile normal weight based on the sampling angle - float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); - float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); - normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); - if (normalWeight <= 0.0f) - continue; - - // Get tile UV and depth at the world position - float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; - float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; - float2 tileUV = (tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f; - tileUV.y = 1.0 - tileUV.y; - float2 atlasCoord = tileUV * tile.AtlasRect.zw + tile.AtlasRect.xy; - - // Tile depth weight based on sample position occlusion - // TODO: gather 4 depth samples to smooth weight (depth weight per-sample used late for bilinear weights) - float tileZ = depth.Load(int3(atlasCoord, 0)).x; - float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z; - float depthWeight = 1.0f - saturate((abs(tileDepth - tileZ) - depthThreshold) / (0.5f * depthThreshold)); - if (depthWeight <= 0.0f) - continue; - - // Sample atlas texture - // TODO: separate GatherRed/Blue/Green with bilinear weights - float4 color = atlas.Load(int3(atlasCoord, 0)); - - // TODO: implement Global Surface Atlas sampling - result = float4(color.rgb, 1); + float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); + float3 localNormalSq = localNormal * localNormal; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + { + uint tileIndex = localNormal.x > 0.0f ? 0 : 1; + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + { + uint tileIndex = localNormal.y > 0.0f ? 2 : 3; + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + { + uint tileIndex = localNormal.z > 0.0f ? 4 : 5; + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } } + + // Normalize result + result.rgb /= max(result.a, 0.0001f); + return result; } From 52245d36d6ae495bbb33f50e96872cdf3422559b Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 11 Apr 2022 10:36:19 +0200 Subject: [PATCH 093/144] Fixes for global atlas cache rendering --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 61 ++++++++++++++----- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 2 +- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 5ed5c1027..cb22e9737 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -65,8 +65,35 @@ struct GlobalSurfaceAtlasObject { uint64 LastFrameUsed; uint64 LastFrameDirty; - GlobalSurfaceAtlasTile* Tiles[6] = {}; + GlobalSurfaceAtlasTile* Tiles[6]; OrientedBoundingBox Bounds; + + GlobalSurfaceAtlasObject() + { + Platform::MemoryClear(this, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject(const GlobalSurfaceAtlasObject& other) + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject(GlobalSurfaceAtlasObject&& other) noexcept + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + } + + GlobalSurfaceAtlasObject& operator=(const GlobalSurfaceAtlasObject& other) + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + return *this; + } + + GlobalSurfaceAtlasObject& operator=(GlobalSurfaceAtlasObject&& other) noexcept + { + Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); + return *this; + } }; class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer @@ -345,7 +372,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) { object->LastFrameDirty = currentFrame; - _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); + _dirtyObjectsBuffer.Add(e.Actor); } // Write to objects buffer (this must match unpacking logic in HLSL) @@ -477,9 +504,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); const Vector2 posToClipAdd(-1.0f, 1.0f); - for (const auto& e : _dirtyObjectsBuffer) + for (Actor* actor : _dirtyObjectsBuffer) { - const auto& object = *e.Second; + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) { auto* tile = object.Tiles[tileIndex]; @@ -504,23 +531,29 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); } } - renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].CanUseInstancing = false; - renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].CanUseInstancing = false; - for (const auto& e : _dirtyObjectsBuffer) + // TODO: limit dirty objects count on a first frame (eg. collect overflown objects to be redirty next frame) + auto& drawCallsListGBuffer = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer]; + auto& drawCallsListGBufferNoDecals = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals]; + drawCallsListGBuffer.CanUseInstancing = false; + drawCallsListGBufferNoDecals.CanUseInstancing = false; + for (Actor* actor : _dirtyObjectsBuffer) { - renderContextTiles.List->Clear(); + // Clear draw calls list renderContextTiles.List->DrawCalls.Clear(); - renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].Indices.Clear(); - renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].Indices.Clear(); + renderContextTiles.List->BatchedDrawCalls.Clear(); + drawCallsListGBuffer.Indices.Clear(); + drawCallsListGBuffer.PreBatchedDrawCalls.Clear(); + drawCallsListGBufferNoDecals.Indices.Clear(); + drawCallsListGBufferNoDecals.PreBatchedDrawCalls.Clear(); // Fake projection matrix to disable Screen Size culling based on RenderTools::ComputeBoundsScreenRadiusSquared renderContextTiles.View.Projection.Values[0][0] = 10000.0f; // Collect draw calls for the object - e.First->Draw(renderContextTiles); + actor->Draw(renderContextTiles); // Render all tiles into the atlas - GlobalSurfaceAtlasObject& object = *e.Second; + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; #if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); #endif @@ -547,8 +580,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tileWidth, tileHeight)); - renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBuffer); - renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBufferNoDecals); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, drawCallsListGBuffer); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, drawCallsListGBufferNoDecals); } } context->ResetRenderTarget(); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index b39862614..3bb204f5c 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -34,7 +34,7 @@ private: // Rasterization cache class DynamicVertexBuffer* _vertexBuffer = nullptr; - Array> _dirtyObjectsBuffer; + Array _dirtyObjectsBuffer; public: /// From bd48829a09e8792a242ced4f468dbb08f049fce1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 11 Apr 2022 10:36:39 +0200 Subject: [PATCH 094/144] Improve quality for atlas sampling --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 3 +- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 3 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 28 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index cb22e9737..6d3ddcdf5 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -242,7 +242,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co PROFILE_GPU_CPU("Global Surface Atlas"); // TODO: configurable via graphics settings - const int32 resolution = 4096; + const int32 resolution = 2048; const float resolutionInv = 1.0f / resolution; // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float distance = 20000; @@ -601,6 +601,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer(); + result.GlobalSurfaceAtlas.Resolution = (float)resolution; result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; return false; diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 3bb204f5c..c577732c7 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -13,7 +13,8 @@ public: // Constant buffer data for Global Surface Atlas access on a GPU. PACK_STRUCT(struct GlobalSurfaceAtlasData { - Vector3 Padding; + Vector2 Padding; + float Resolution; uint32 ObjectsCount; }); diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index eefa09bfe..ef64ac1f4 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -5,7 +5,7 @@ // This must match C++ #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object -#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.25f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling struct GlobalSurfaceTile { @@ -74,7 +74,8 @@ GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint object // Global Surface Atlas data for a constant buffer struct GlobalSurfaceAtlasData { - float3 Padding; + float2 Padding; + float Resolution; uint ObjectsCount; }; @@ -86,7 +87,7 @@ float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilin return float3(dot(sampleX, bilinearWeights), dot(sampleY, bilinearWeights), dot(sampleZ, bilinearWeights)); } -float4 SampleGlobalSurfaceAtlasTile(GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) +float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) { // Tile normal weight based on the sampling angle float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); @@ -103,12 +104,12 @@ float4 SampleGlobalSurfaceAtlasTile(GlobalSurfaceTile tile, Texture2D depth, Tex float2 atlasUV = tileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; // Calculate bilinear weights + float2 bilinearWeightsUV = frac(atlasUV * data.Resolution + 0.5f); float4 bilinearWeights; - bilinearWeights.x = (1.0 - tileUV.x) * (tileUV.y); - bilinearWeights.y = (tileUV.x) * (tileUV.y); - bilinearWeights.z = (tileUV.x) * (1 - tileUV.y); - bilinearWeights.w = (1 - tileUV.x) * (1 - tileUV.y); - bilinearWeights = saturate(bilinearWeights); + bilinearWeights.x = (1.0 - bilinearWeightsUV.x) * (bilinearWeightsUV.y); + bilinearWeights.y = (bilinearWeightsUV.x) * (bilinearWeightsUV.y); + bilinearWeights.z = (bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y); + bilinearWeights.w = (1 - bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y); // Tile depth weight based on sample position occlusion float4 tileZ = depth.Gather(SamplerLinearClamp, atlasUV, 0.0f); @@ -116,7 +117,11 @@ float4 SampleGlobalSurfaceAtlasTile(GlobalSurfaceTile tile, Texture2D depth, Tex float4 depthVisibility = 1.0f; UNROLL for (uint i = 0; i < 4; i++) + { depthVisibility[i] = 1.0f - saturate((abs(tileDepth - tileZ[i]) - depthThreshold) / (0.5f * depthThreshold)); + if (tileZ[i] >= 1.0f) + depthVisibility[i] = 0.0f; + } float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); if (sampleWeight <= 0.0f) return 0; @@ -152,26 +157,25 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) { uint tileIndex = localNormal.x > 0.0f ? 0 : 1; GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); - result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) { uint tileIndex = localNormal.y > 0.0f ? 2 : 3; GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); - result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) { uint tileIndex = localNormal.z > 0.0f ? 4 : 5; GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); - result += SampleGlobalSurfaceAtlasTile(tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } } From e8774ebd29c8f84fccd7b021dae1950a9e5ad5d3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 11 Apr 2022 16:15:28 +0200 Subject: [PATCH 095/144] Fixes and improvements for global sdf and global surface atlas --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 43 ++++++++++++------- Source/Shaders/GlobalSurfaceAtlas.hlsl | 4 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 7e4846b7f..60bcde40b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -251,7 +251,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const int32 mipFactor = 4; const int32 resolutionMip = Math::DivideAndRoundUp(resolution, mipFactor); // TODO: configurable via postFx settings - const float distanceExtent = 2500.0f; + const float distanceExtent = 2000.0f; const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; // Initialize buffers @@ -403,8 +403,10 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex PROFILE_GPU_CPU("Rasterize Chunks"); for (auto& e : chunks) { - // Rasterize non-empty chunk + // Rasterize non-empty chunk (first layer so can override existing chunk data) auto& key = e.Key; + if (key.Layer != 0) + continue; auto& chunk = e.Value; for (int32 i = 0; i < chunk.ModelsCount; i++) { @@ -417,20 +419,9 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex data.ModelsCount = chunk.ModelsCount; if (_cb1) context->UpdateCB(_cb1, &data); - GPUShaderProgramCS* cs; - if (key.Layer == 0) - { - // First layer so can override existing chunk data - cs = _csRasterizeModel0; - nonEmptyChunks.Add(key); - } - else - { - // Another layer so need combine with existing chunk data - cs = _csRasterizeModel1; - } - context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); - // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?) + nonEmptyChunks.Add(key); + context->Dispatch(_csRasterizeModel0, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches (maybe cache per-shader write/read flags for all UAVs?) #if GLOBAL_SDF_DEBUG_CHUNKS // Debug draw chunk bounds in world space with number of models in it @@ -443,6 +434,26 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } #endif } + for (auto& e : chunks) + { + // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) + auto& key = e.Key; + if (key.Layer == 0) + continue; + auto& chunk = e.Value; + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + int32 model = chunk.Models[i]; + data.Models[i] = model; + context->BindSR(i + 1, _modelsTextures[model]); + } + ASSERT_LOW_LAYER(chunk.ModelsCount != 0); + data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ModelsCount = chunk.ModelsCount; + if (_cb1) + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } } // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index ef64ac1f4..dab389ab9 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -99,7 +99,7 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur // Get tile UV and depth at the world position float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; - float2 tileUV = (tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f; + float2 tileUV = saturate((tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f); tileUV.y = 1.0 - tileUV.y; float2 atlasUV = tileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; @@ -143,7 +143,7 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer Date: Tue, 12 Apr 2022 11:09:53 +0200 Subject: [PATCH 096/144] Add sdf to engine primitives --- Content/Editor/Primitives/Cone.flax | 4 ++-- Content/Editor/Primitives/Cylinder.flax | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content/Editor/Primitives/Cone.flax b/Content/Editor/Primitives/Cone.flax index fa0123178..adf83905b 100644 --- a/Content/Editor/Primitives/Cone.flax +++ b/Content/Editor/Primitives/Cone.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ac606bb7dc790affc7b8ca01077597902e7a29e2ae9ea10c60d949adc953839 -size 8815 +oid sha256:5f4e87190b79f532ca04327064cc5bf05312fb77ba267d6a3df213252983f23e +size 11217 diff --git a/Content/Editor/Primitives/Cylinder.flax b/Content/Editor/Primitives/Cylinder.flax index 310b426aa..224177090 100644 --- a/Content/Editor/Primitives/Cylinder.flax +++ b/Content/Editor/Primitives/Cylinder.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d487edcc44d3d27275f180e00a99753dae244b2ba74f7c1a016775d64d72929 -size 13207 +oid sha256:d744a628567cf3ca1598183a3d7fbf2b23c3b23816ca80103cd8026638f7eddc +size 15609 From 74b95a6ebbd41730e9b392b544286cfeb2d5c7d3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 12 Apr 2022 11:10:38 +0200 Subject: [PATCH 097/144] Add Global Surface Atlas automatic defragmentation on insertion failure --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 6d3ddcdf5..9e863d75d 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -100,6 +100,8 @@ class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer { public: int32 Resolution = 0; + uint64 LastFrameAtlasInsertFail = 0; + uint64 LastFrameAtlasDefragmentation = 0; GPUTexture* AtlasDepth = nullptr; GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer1 = nullptr; @@ -115,6 +117,14 @@ public: { } + FORCE_INLINE void ClearObjects() + { + LastFrameAtlasDefragmentation = Engine::FrameCount; + SAFE_DELETE(AtlasTiles); + ObjectsBuffer.Clear(); + Objects.Clear(); + } + FORCE_INLINE void Clear() { RenderTargetPool::Release(AtlasDepth); @@ -122,9 +132,7 @@ public: RenderTargetPool::Release(AtlasGBuffer1); RenderTargetPool::Release(AtlasGBuffer2); RenderTargetPool::Release(AtlasDirectLight); - SAFE_DELETE(AtlasTiles); - ObjectsBuffer.Clear(); - Objects.Clear(); + ClearObjects(); } ~GlobalSurfaceAtlasCustomBuffer() @@ -252,7 +260,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (noCache) { surfaceAtlasData.Clear(); - surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); uint64 memUsage = 0; @@ -268,6 +275,18 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.Resolution = resolution; LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); } + else + { + // Perform atlas defragmentation if needed + // TODO: track atlas used vs free ratio to skip defragmentation if it's nearly full (then maybe auto resize up?) + if (currentFrame - surfaceAtlasData.LastFrameAtlasInsertFail < 10 && + currentFrame - surfaceAtlasData.LastFrameAtlasDefragmentation > 60) + { + surfaceAtlasData.ClearObjects(); + } + } + if (!surfaceAtlasData.AtlasTiles) + surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); @@ -353,9 +372,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co anyTile = true; dirty = true; } - else if (object) + else { - object->Tiles[tileIndex] = nullptr; + if (object) + object->Tiles[tileIndex] = nullptr; + surfaceAtlasData.LastFrameAtlasInsertFail = currentFrame; } } if (anyTile) @@ -454,7 +475,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.Objects.Remove(it); } } - // TODO: perform atlas defragmentation after certain amount of tiles removal // Send objects data to the GPU { From 0a27d277a04a955ded876fa3a09de56e22f4af01 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 12 Apr 2022 14:27:06 +0200 Subject: [PATCH 098/144] Adjustments --- .../Engine/Graphics/Materials/MaterialShaderFeatures.cpp | 8 ++++---- Source/Engine/Renderer/LightPass.cpp | 8 ++++---- Source/Engine/Renderer/RenderList.cpp | 8 ++++---- Source/Engine/Renderer/RenderList.h | 8 ++++---- Source/Engine/Renderer/ShadowsPass.cpp | 6 +++--- Source/Engine/Renderer/VolumetricFogPass.cpp | 6 +++--- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 6b043fbb2..c9f65e3fb 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -50,7 +50,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanUnBindSR(dirLightShaderRegisterIndex); } - dirLight.SetupLightData(&data.DirectionalLight, view, useShadow); + dirLight.SetupLightData(&data.DirectionalLight, useShadow); } else { @@ -63,7 +63,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSkyLights.HasItems()) { auto& skyLight = cache->SkyLights.First(); - skyLight.SetupLightData(&data.SkyLight, view, false); + skyLight.SetupLightData(&data.SkyLight, false); const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); } @@ -104,7 +104,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanPointLights[i]; if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) { - light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; } } @@ -113,7 +113,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSpotLights[i]; if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) { - light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; } } diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 1e54f1edf..2beeac5d8 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -285,7 +285,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -342,7 +342,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -385,7 +385,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB context->UnBindSR(5); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, renderShadow); + light.SetupLightData(&perLight.Light, renderShadow); // Calculate lighting context->UpdateCB(cb0, &perLight); @@ -419,7 +419,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB Matrix::Multiply(world, view.ViewProjection(), wvp); // Pack light properties buffer - light.SetupLightData(&perLight.Light, view, false); + light.SetupLightData(&perLight.Light, false); Matrix::Transpose(wvp, perLight.WVP); // Bind source image diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index e5f353770..1f51951af 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -41,7 +41,7 @@ namespace Array MemPool; } -void RendererDirectionalLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererDirectionalLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = -2.0f; data->SpotAngles.Y = 1.0f; @@ -58,7 +58,7 @@ void RendererDirectionalLightData::SetupLightData(LightData* data, const RenderV data->RadiusInv = 0; } -void RendererSpotLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererSpotLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = CosOuterCone; data->SpotAngles.Y = InvCosConeDifference; @@ -75,7 +75,7 @@ void RendererSpotLightData::SetupLightData(LightData* data, const RenderView& vi data->RadiusInv = 1.0f / Radius; } -void RendererPointLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererPointLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = -2.0f; data->SpotAngles.Y = 1.0f; @@ -92,7 +92,7 @@ void RendererPointLightData::SetupLightData(LightData* data, const RenderView& v data->RadiusInv = 1.0f / Radius; } -void RendererSkyLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const +void RendererSkyLightData::SetupLightData(LightData* data, bool useShadow) const { data->SpotAngles.X = AdditiveColor.X; data->SpotAngles.Y = AdditiveColor.Y; diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 1a83065ac..c6e2d16af 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -42,7 +42,7 @@ struct RendererDirectionalLightData float ContactShadowsLength; ShadowsCastingMode ShadowsMode; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererSpotLightData @@ -80,7 +80,7 @@ struct RendererSpotLightData GPUTexture* IESTexture; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererPointLightData @@ -114,7 +114,7 @@ struct RendererPointLightData GPUTexture* IESTexture; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; struct RendererSkyLightData @@ -132,7 +132,7 @@ struct RendererSkyLightData CubeTexture* Image; - void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const; + void SetupLightData(LightData* data, bool useShadow) const; }; /// diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 05f8659c4..a90708b10 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -325,7 +325,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererPointLightD // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade); @@ -427,7 +427,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererSpotLightDa // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade); @@ -719,7 +719,7 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererDirectional // Setup shader data GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetupLightData(&sperLight.Light, view, true); + light.SetupLightData(&sperLight.Light, true); sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCSM; sperLight.LightShadow.Sharpness = light.ShadowsSharpness; sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength); diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 8ebce3600..61b544985 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -306,7 +306,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); Matrix::Transpose(view.Projection, perLight.ViewToVolumeClip); - light.SetupLightData(&perLight.LocalLight, view, true); + light.SetupLightData(&perLight.LocalLight, true); perLight.LocalLightShadow = shadow; // Upload data @@ -365,7 +365,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); - light.SetupLightData(&perLight.LocalLight, renderContext.View, withShadow); + light.SetupLightData(&perLight.LocalLight, withShadow); // Upload data context->UpdateCB(cb1, &perLight); @@ -441,7 +441,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) { const auto shadowPass = ShadowsPass::Instance(); const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex; - dirLight.SetupLightData(&_cache.Data.DirectionalLight, view, useShadow); + dirLight.SetupLightData(&_cache.Data.DirectionalLight, useShadow); _cache.Data.DirectionalLight.Color *= brightness; if (useShadow) { From 0668a2316708634ab8a4a6d492ef8dec004d831f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 13 Apr 2022 16:26:19 +0200 Subject: [PATCH 099/144] Add direct lighting rendering into Global Surface Atlas --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- Flax.sln.DotSettings | 1 + .../Renderer/GlobalSurfaceAtlasPass.cpp | 227 +++++++++++++++--- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 2 + Source/Engine/Renderer/LightPass.cpp | 21 -- Source/Engine/Renderer/ShadowsPass.h | 21 ++ Source/Shaders/GBuffer.hlsl | 18 -- Source/Shaders/GlobalSurfaceAtlas.hlsl | 1 + Source/Shaders/GlobalSurfaceAtlas.shader | 95 +++++++- Source/Shaders/Lighting.hlsl | 29 +-- 10 files changed, 322 insertions(+), 97 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 8b8cbd705..f08b3e829 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13070c079b7a2e5530be1b9d9392b9cc36f41bc566bf86595fe0460a7be4f536 -size 3046 +oid sha256:4f6d1ff40e046e496fe4a550cabf61e180cc46d5045798463d11aa1863578539 +size 5969 diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 007b8e5a2..ec2e8d359 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -254,6 +254,7 @@ True True True + True True True True diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 9e863d75d..5f6d72036 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -3,6 +3,7 @@ #include "GlobalSurfaceAtlasPass.h" #include "GlobalSignDistanceFieldPass.h" #include "RenderList.h" +#include "ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" #include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Engine.h" @@ -19,10 +20,11 @@ // This must match HLSL #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles -#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // Forces to redraw all object tiles every frame -#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) +#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) -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS #include "Engine/Debug/DebugDraw.h" #endif @@ -35,11 +37,15 @@ PACK_STRUCT(struct Data0 Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; + LightData Light; }); PACK_STRUCT(struct AtlasTileVertex { Half2 Position; + Half2 TileUV; + uint16 ObjectIndex; + uint16 TileIndex; }); struct GlobalSurfaceAtlasTile : RectPack @@ -66,6 +72,8 @@ struct GlobalSurfaceAtlasObject uint64 LastFrameUsed; uint64 LastFrameDirty; GlobalSurfaceAtlasTile* Tiles[6]; + uint32 Index; + float Radius; OrientedBoundingBox Bounds; GlobalSurfaceAtlasObject() @@ -103,11 +111,13 @@ public: uint64 LastFrameAtlasInsertFail = 0; uint64 LastFrameAtlasDefragmentation = 0; GPUTexture* AtlasDepth = nullptr; + GPUTexture* AtlasEmissive = nullptr; GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; DynamicTypedBuffer ObjectsBuffer; + uint32 ObjectIndexCounter; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; @@ -128,6 +138,7 @@ public: FORCE_INLINE void Clear() { RenderTargetPool::Release(AtlasDepth); + RenderTargetPool::Release(AtlasEmissive); RenderTargetPool::Release(AtlasGBuffer0); RenderTargetPool::Release(AtlasGBuffer1); RenderTargetPool::Release(AtlasGBuffer2); @@ -198,11 +209,27 @@ bool GlobalSurfaceAtlasPass::setupResources() psDesc.DepthTestEnable = true; psDesc.DepthWriteEnable = true; psDesc.DepthFunc = ComparisonFunc::Always; - psDesc.VS = shader->GetVS("VS_Clear"); + psDesc.VS = shader->GetVS("VS_Atlas"); psDesc.PS = shader->GetPS("PS_Clear"); if (_psClear->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_DirectLighting", 0); + if (_psDirectLighting0->Init(psDesc)) + return true; + _psDirectLighting1 = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_DirectLighting", 1); + if (_psDirectLighting1->Init(psDesc)) + return true; + } return false; } @@ -212,6 +239,8 @@ bool GlobalSurfaceAtlasPass::setupResources() void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) { SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); SAFE_DELETE_GPU_RESOURCE(_psDebug); invalidateResources(); } @@ -225,6 +254,8 @@ void GlobalSurfaceAtlasPass::Dispose() // Cleanup SAFE_DELETE(_vertexBuffer); SAFE_DELETE_GPU_RESOURCE(_psClear); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); + SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -239,6 +270,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co return true; auto& surfaceAtlasData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSurfaceAtlas")); + // Render Global SDF (used for direct shadowing) + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF)) + return true; + // Skip if already done in the current frame const auto currentFrame = Engine::FrameCount; if (surfaceAtlasData.LastFrameUsed == currentFrame) @@ -265,6 +301,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co uint64 memUsage = 0; // TODO: try using BC4/BC5/BC7 block compression for Surface Atlas (eg. for Tiles material properties) #define INIT_ATLAS_TEXTURE(texture, format) desc.Format = format; surfaceAtlasData.texture = RenderTargetPool::Get(desc); if (!surfaceAtlasData.texture) return true; memUsage += surfaceAtlasData.texture->GetMemoryUsage() + INIT_ATLAS_TEXTURE(AtlasEmissive, LIGHT_BUFFER_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); @@ -289,9 +326,37 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); - + const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); + const Vector2 posToClipAdd(-1.0f, 1.0f); +#define VB_WRITE_TILE_POS_ONLY(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0].Position = max; \ + quad[1].Position = { min.X, max.Y }; \ + quad[2].Position = min; \ + quad[3].Position = quad[2].Position; \ + quad[4].Position = { max.X, min.Y }; \ + quad[5].Position = quad[0].Position +#define VB_WRITE_TILE(tile) \ + Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); \ + Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ + Vector2 minUV(0, 0), maxUV(1, 1); \ + auto* quad = _vertexBuffer->WriteReserve(6); \ + quad[0] = { { max }, { maxUV }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[2] = { { min }, { minUV }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[3] = quad[2]; \ + quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[5] = quad[0] +#define VB_DRAW() \ + _vertexBuffer->Flush(context); \ + auto vb = _vertexBuffer->GetBuffer(); \ + context->BindVB(ToSpan(&vb, 1)); \ + context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); // Add objects into the atlas surfaceAtlasData.ObjectsBuffer.Clear(); + surfaceAtlasData.ObjectIndexCounter = 0; _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -390,7 +455,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co object->LastFrameUsed = currentFrame; object->Bounds = OrientedBoundingBox(localBounds); object->Bounds.Transform(localToWorld); - if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + object->Radius = e.Bounds.Radius; + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) { object->LastFrameDirty = currentFrame; _dirtyObjectsBuffer.Add(e.Actor); @@ -400,6 +466,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co Matrix worldToLocalBounds; Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) + object->Index = surfaceAtlasData.ObjectIndexCounter++; auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_SIZE); objectData[0] = *(Vector4*)&e.Bounds; objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); @@ -500,7 +567,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co GPUTextureView* depthBuffer = surfaceAtlasData.AtlasDepth->View(); GPUTextureView* targetBuffers[4] = { - surfaceAtlasData.AtlasDirectLight->View(), + surfaceAtlasData.AtlasEmissive->View(), surfaceAtlasData.AtlasGBuffer0->View(), surfaceAtlasData.AtlasGBuffer1->View(), surfaceAtlasData.AtlasGBuffer2->View(), @@ -508,7 +575,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); { PROFILE_GPU_CPU("Clear"); - if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) { // Full-atlas hardware clear context->ClearDepth(depthBuffer); @@ -522,8 +589,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Per-tile clear (with a single draw call) _vertexBuffer->Clear(); _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); - const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); - const Vector2 posToClipAdd(-1.0f, 1.0f); for (Actor* actor : _dirtyObjectsBuffer) { const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; @@ -532,23 +597,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co auto* tile = object.Tiles[tileIndex]; if (!tile) continue; - Vector2 minPos((float)tile->X, (float)tile->Y), maxPos((float)(tile->X + tile->Width), (float)(tile->Y + tile->Height)); - Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); - auto* quad = _vertexBuffer->WriteReserve(6); - quad[0] = { { max } }; - quad[1] = { { min.X, max.Y } }; - quad[2] = { { min } }; - quad[3] = quad[2]; - quad[4] = { { max.X, min.Y } }; - quad[5] = quad[0]; + VB_WRITE_TILE_POS_ONLY(tile); } } - _vertexBuffer->Flush(context); - auto vb = _vertexBuffer->GetBuffer(); context->SetState(_psClear); context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); - context->BindVB(ToSpan(&vb, 1)); - context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); + VB_DRAW(); } } // TODO: limit dirty objects count on a first frame (eg. collect overflown objects to be redirty next frame) @@ -574,7 +628,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Render all tiles into the atlas const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); #endif for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) @@ -588,12 +642,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Setup projection to capture object from the side renderContextTiles.View.Position = tile->ViewPosition; renderContextTiles.View.Direction = tile->ViewDirection; - renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles - renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 0.2f; + renderContextTiles.View.Near = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; Matrix projectionMatrix; Matrix::Ortho(tile->ViewBoundsSize.X, tile->ViewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); renderContextTiles.View.SetUp(tile->ViewMatrix, projectionMatrix); -#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); #endif @@ -608,12 +662,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co RenderList::ReturnToPool(renderContextTiles.List); } - // TODO: update direct lighting atlas (for modified tiles and lights) - // TODO: update static lights only for dirty tiles (dynamic lights every X frames) - // TODO: use custom dynamic vertex buffer to decide which atlas tiles to shade with a light - - // TODO: indirect lighting apply to get infinite bounces for GI - // Copy results result.Atlas[0] = surfaceAtlasData.AtlasDepth; result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; @@ -624,6 +672,114 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.GlobalSurfaceAtlas.Resolution = (float)resolution; result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); 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 + context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + + context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); + context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->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, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View()); + context->BindCB(0, _cb0); + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.GlobalSDF = bindingDataSDF.GlobalSDF; + data.GlobalSurfaceAtlas = result.GlobalSurfaceAtlas; + + // 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 + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw 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); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting0); + VB_DRAW(); + } + for (auto& light : renderContext.List->PointLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + for (auto& light : renderContext.List->SpotLights) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + Vector3 lightToObject = object.Bounds.GetCenter() - light.Position; + if (lightToObject.LengthSquared() >= Math::Square(object.Radius + light.Radius)) + continue; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile || Vector3::Dot(tile->ViewDirection, light.Direction) < ZeroTolerance) + continue; + VB_WRITE_TILE(tile); + } + } + + // Draw draw light + const bool useShadow = CanRenderShadow(renderContext.View, light); + light.SetupLightData(&data.Light, useShadow); + context->UpdateCB(_cb0, &data); + context->SetState(_psDirectLighting1); + VB_DRAW(); + } + + context->ResetRenderTarget(); + } + + // TODO: indirect lighting apply to get infinite bounces for GI + +#undef WRITE_TILE return false; } @@ -639,7 +795,6 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex PROFILE_GPU_CPU("Global Surface Atlas Debug"); const Vector2 outputSize(output->Size()); - if (_cb0) { Data0 data; data.ViewWorldPos = renderContext.View.Position; @@ -660,10 +815,10 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); context->BindSR(9, bindingData.Atlas[0]->View()); { - GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse + //GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao - //GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light + GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light context->BindSR(10, tex->View()); } context->SetState(_psDebug); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index c577732c7..2b225bc46 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -30,6 +30,8 @@ private: bool _supported = false; AssetReference _shader; GPUPipelineState* _psClear = nullptr; + GPUPipelineState* _psDirectLighting0 = nullptr; + GPUPipelineState* _psDirectLighting1 = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 2beeac5d8..c6ef07f51 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -141,27 +141,6 @@ bool LightPass::setupResources() return false; } -template -bool CanRenderShadow(RenderView& view, const T& light) -{ - bool result = false; - switch ((ShadowsCastingMode)light.ShadowsMode) - { - case ShadowsCastingMode::StaticOnly: - result = view.IsOfflinePass; - break; - case ShadowsCastingMode::DynamicOnly: - result = !view.IsOfflinePass; - break; - case ShadowsCastingMode::All: - result = true; - break; - default: - break; - } - return result && light.ShadowsStrength > ZeroTolerance; -} - void LightPass::Dispose() { // Base diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 1f9a0db5f..c17757fb4 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -14,6 +14,27 @@ /// #define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float +template +bool CanRenderShadow(RenderView& view, const T& light) +{ + bool result = false; + switch ((ShadowsCastingMode)light.ShadowsMode) + { + case ShadowsCastingMode::StaticOnly: + result = view.IsOfflinePass; + break; + case ShadowsCastingMode::DynamicOnly: + result = !view.IsOfflinePass; + break; + case ShadowsCastingMode::All: + result = true; + break; + default: + break; + } + return result && light.ShadowsStrength > ZeroTolerance; +} + /// /// Shadows rendering service. /// diff --git a/Source/Shaders/GBuffer.hlsl b/Source/Shaders/GBuffer.hlsl index 0edcbef27..9ec390135 100644 --- a/Source/Shaders/GBuffer.hlsl +++ b/Source/Shaders/GBuffer.hlsl @@ -144,24 +144,6 @@ GBufferSample SampleGBufferFast(GBufferData gBuffer, float2 uv) return result; } -// Sample GBuffer normal vector, shading model and view space position -GBufferSample SampleGBufferNormalVPos(GBufferData gBuffer, float2 uv) -{ - GBufferSample result; - - // Sample GBuffer - float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv); - - // Decode normal and shading model - result.Normal = DecodeNormal(gBuffer1.rgb); - result.ShadingModel = (int)(gBuffer1.a * 3.999); - - // Calculate view space position - result.ViewPos = GetViewPos(gBuffer, uv); - - return result; -} - #if defined(USE_GBUFFER_CUSTOM_DATA) // Sample GBuffer custom data only diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index dab389ab9..540425a8b 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -6,6 +6,7 @@ // This must match C++ #define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) struct GlobalSurfaceTile { diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 91f7bb0f0..8d1286b18 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -1,9 +1,13 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +// Diffuse-only lighting +#define NO_SPECULAR + #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" #include "./Flax/GlobalSurfaceAtlas.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/LightingCommon.hlsl" META_CB_BEGIN(0, Data) float3 ViewWorldPos; @@ -13,14 +17,35 @@ float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; +LightData Light; META_CB_END -// Vertex shader for Global Surface Atlas software clearing +struct AtlasVertexIput +{ + float2 Position : POSITION0; + float2 TileUV : TEXCOORD0; + uint2 Index : TEXCOORD1; +}; + +struct AtlasVertexOutput +{ + float4 Position : SV_Position; + float2 TileUV : TEXCOORD0; + nointerpolation uint2 Index : TEXCOORD1; +}; + +// Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile) META_VS(true, FEATURE_LEVEL_SM5) META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -float4 VS_Clear(float2 Position : POSITION0) : SV_Position +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_UINT, 0, ALIGN, PER_VERTEX, 0, true) +AtlasVertexOutput VS_Atlas(AtlasVertexIput input) { - return float4(Position, 1, 1); + AtlasVertexOutput output; + output.Position = float4(input.Position, 1, 1); + output.TileUV = input.TileUV; + output.Index = input.Index; + return output; } // Pixel shader for Global Surface Atlas software clearing @@ -33,6 +58,70 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl RT2 = float4(1, 0, 0, 0); } +#ifdef _PS_DirectLighting + +#include "./Flax/GBuffer.hlsl" +#include "./Flax/Matrix.hlsl" +#include "./Flax/Lighting.hlsl" + +// GBuffer+Depth at 0-3 slots +Buffer GlobalSurfaceAtlasObjects : register(t4); + +// Pixel shader for Global Surface Atlas shading with direct light contribution +META_PS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(RADIAL_LIGHT=0) +META_PERMUTATION_1(RADIAL_LIGHT=1) +float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target +{ + // Load current tile info + //GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(GlobalSurfaceAtlasObjects, input.Index.x); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.Index.x, input.Index.y); + float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; + + // Load GBuffer sample from atlas + GBufferData gBufferData = (GBufferData)0; + GBufferSample gBuffer = SampleGBuffer(gBufferData, atlasUV); + + // Skip unlit pixels + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + { + discard; + return 0; + } + + // Reconstruct world-space position manually (from uv+depth within a tile) + float tileDepth = SampleZ(atlasUV); + //float tileNear = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //float tileFar = tile.ViewBoundsSize.z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET; + //gBufferData.ViewInfo.zw = float2(tileFar / (tileFar - tileNear), (-tileFar * tileNear) / (tileFar - tileNear) / tileFar); + //gBufferData.ViewInfo.zw = float2(1, 0); + //float tileLinearDepth = LinearizeZ(gBufferData, tileDepth); + float3 tileSpacePos = float3(input.TileUV.x - 0.5f, 0.5f - input.TileUV.y, tileDepth); + float3 gBufferTilePos = tileSpacePos * tile.ViewBoundsSize; + float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); + gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + + float4 shadowMask = 1; + BRANCH + if (Light.CastShadows > 0) + { + // TODO: calculate shadow for the light (use Global SDF) + } + + // Calculate lighting +#if RADIAL_LIGHT + bool isSpotLight = Light.SpotAngles.x > -2.0f; +#else + bool isSpotLight = false; +#endif + float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight); + + return light; +} + +#endif + #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 9b81504a6..4a1cabac6 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -3,10 +3,6 @@ #ifndef __LIGHTING__ #define __LIGHTING__ -#if !defined(USE_GBUFFER_CUSTOM_DATA) -#error "Cannot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." -#endif - #include "./Flax/LightingCommon.hlsl" ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) @@ -20,21 +16,23 @@ ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMa LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { float3 diffuseColor = GetDiffuseColor(gBuffer); - float3 specularColor = GetSpecularColor(gBuffer); - float3 H = normalize(V + L); float NoL = saturate(dot(N, L)); float NoV = max(dot(N, V), 1e-5); float NoH = saturate(dot(N, H)); float VoH = saturate(dot(V, H)); - float D = D_GGX(gBuffer.Roughness, NoH) * energy; - float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); - float3 F = F_Schlick(specularColor, VoH); - LightingData lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); +#if defined(NO_SPECULAR) + lighting.Specular = 0; +#else + float3 specularColor = GetSpecularColor(gBuffer); + float3 F = F_Schlick(specularColor, VoH); + float D = D_GGX(gBuffer.Roughness, NoH) * energy; + float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL); lighting.Specular = (D * Vis) * F; +#endif lighting.Transmission = 0; return lighting; } @@ -42,7 +40,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the material float3 subsurfaceColor = gBuffer.CustomData.rgb; float opacity = gBuffer.CustomData.a; @@ -51,21 +49,21 @@ LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, fl float normalContribution = saturate(dot(N, H) * opacity + 1.0f - opacity); float backScatter = gBuffer.AO * normalContribution / (PI * 2.0f); lighting.Transmission = lerp(backScatter, 1, inscatter) * subsurfaceColor; - +#endif return lighting; } LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { LightingData lighting = StandardShading(gBuffer, energy, L, V, N); - +#if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the thin foliage float3 subsurfaceColor = gBuffer.CustomData.rgb; float wrapNoL = saturate((-dot(N, L) + 0.5f) / 2.25); float VoL = dot(V, L); float scatter = D_GGX(0.36, saturate(-VoL)); lighting.Transmission = subsurfaceColor * (wrapNoL * scatter); - +#endif return lighting; } @@ -144,9 +142,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f // Calculate direct lighting LightingData lighting = SurfaceShading(gBuffer, energy, L, V, N); -#if NO_SPECULAR - lighting.Specular = float3(0, 0, 0); -#endif // Calculate final light color float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * (NoL * attenuation * shadow.SurfaceShadow); From d52e70af6cdb9dc07e00267b5627224114715b15 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 14 Apr 2022 16:26:45 +0200 Subject: [PATCH 100/144] Implement shadows rendering to direct light atlas (with Glboal SDF tracing) --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 21 +++++++-- Source/Shaders/GlobalSignDistanceField.hlsl | 5 ++- Source/Shaders/GlobalSurfaceAtlas.shader | 44 ++++++++++++++++--- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index f08b3e829..038465b36 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f6d1ff40e046e496fe4a550cabf61e180cc46d5045798463d11aa1863578539 -size 5969 +oid sha256:da4ac86323889084590fe12325b00acae1294dbdf2e28077f3af5a93ea7a7162 +size 7125 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 5f6d72036..b7a8148de 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -32,7 +32,8 @@ PACK_STRUCT(struct Data0 { Vector3 ViewWorldPos; float ViewNearPlane; - Vector3 Padding00; + Vector2 Padding00; + float LightShadowsStrength; float ViewFarPlane; Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; @@ -51,7 +52,7 @@ PACK_STRUCT(struct AtlasTileVertex struct GlobalSurfaceAtlasTile : RectPack { Vector3 ViewDirection; - Vector3 ViewPosition; // TODO: use from ViewMatrix + Vector3 ViewPosition; Vector3 ViewBoundsSize; Matrix ViewMatrix; @@ -428,7 +429,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Insert tile into atlas auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, e.Actor, tileIndex); - // TODO: try to perform atlas defragmentation if it's full (eg. max once per ~10s) if (tile) { if (!object) @@ -680,7 +680,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Copy emissive light into the final direct lighting atlas // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles - context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + { + PROFILE_GPU_CPU("Copy Emissive"); + context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + } context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->View()); @@ -689,6 +692,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); context->BindSR(4, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View()); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } context->BindCB(0, _cb0); Data0 data; data.ViewWorldPos = renderContext.View.Position; @@ -717,6 +725,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co 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); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting0); VB_DRAW(); @@ -743,6 +752,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); VB_DRAW(); @@ -769,6 +779,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); VB_DRAW(); @@ -779,6 +790,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // TODO: indirect lighting apply to get infinite bounces for GI + // TODO: explore atlas tiles optimization with feedback from renderer (eg. when tile is sampled by GI/Reflections mark it as used, then sort tiles by importance and prioritize updates for ones frequently used) + #undef WRITE_TILE return false; } diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 4f02acff2..b27938951 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -6,6 +6,7 @@ #define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 #define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 #define GLOBAL_SDF_MIP_FLOODS 5 +#define GLOBAL_SDF_WORLD_SIZE 60000.0f // Global SDF data for a constant buffer struct GlobalSDFData @@ -61,7 +62,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 { float distance = data.CascadePosDistance[3].w * 2.0f; if (distance <= 0.0f) - return 60000; + return GLOBAL_SDF_WORLD_SIZE; UNROLL for (uint cascade = 0; cascade < 4; cascade++) { @@ -83,7 +84,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4], float3 worldPosition, out float distance) { float3 gradient = float3(0, 0.00001f, 0); - distance = 60000; + distance = GLOBAL_SDF_WORLD_SIZE; if (data.CascadePosDistance[3].w <= 0.0f) return gradient; UNROLL diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 8d1286b18..e866e1a3a 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -12,7 +12,8 @@ META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; -float3 Padding00; +float2 Padding00; +float LightShadowsStrengthOneMinus; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; @@ -66,6 +67,8 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl // GBuffer+Depth at 0-3 slots Buffer GlobalSurfaceAtlasObjects : register(t4); +Texture3D GlobalSDFTex[4] : register(t5); +Texture3D GlobalSDFMip[4] : register(t9); // Pixel shader for Global Surface Atlas shading with direct light contribution META_PS(true, FEATURE_LEVEL_SM5) @@ -81,11 +84,10 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target // Load GBuffer sample from atlas GBufferData gBufferData = (GBufferData)0; GBufferSample gBuffer = SampleGBuffer(gBufferData, atlasUV); - - // Skip unlit pixels BRANCH if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) { + // Skip unlit pixels discard; return 0; } @@ -102,11 +104,43 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + // Calculate shadowing + float3 L = Light.Direction; +#if RADIAL_LIGHT + float3 toLight = Light.Position - gBuffer.WorldPos; + float toLightDst = length(toLight); + if (toLightDst >= Light.Radius) + { + // Skip texels outside the light influence range + discard; + return 0; + } + L = toLight / toLightDst; +#else + float toLightDst = GLOBAL_SDF_WORLD_SIZE; +#endif float4 shadowMask = 1; - BRANCH if (Light.CastShadows > 0) { - // TODO: calculate shadow for the light (use Global SDF) + float NoL = dot(gBuffer.Normal, L); + float shadowBias = 10.0f; + float bias = 2 * shadowBias * saturate(1 - NoL) + shadowBias; + BRANCH + if (NoL > 0) + { + // TODO: try using shadow map for on-screen pixels + // TODO: try using cone trace with Global SDF for smoother shadow (eg. for sun shadows or for area lights) + + // Shot a ray from light into texel to see if there is any occluder + GlobalSDFTrace trace; + trace.Init(gBuffer.WorldPos + gBuffer.Normal * shadowBias, L, bias, toLightDst - bias); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + shadowMask = hit.IsHit() ? LightShadowsStrengthOneMinus : 1; + } + else + { + shadowMask = 0; + } } // Calculate lighting From 2b9aa0cf713483e32da8e3bc092bf5186763601b Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 15 Apr 2022 09:59:37 +0200 Subject: [PATCH 101/144] Fix typos --- Source/Shaders/GlobalSurfaceAtlas.shader | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index e866e1a3a..cd9f1cba7 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -13,7 +13,7 @@ META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; float2 Padding00; -float LightShadowsStrengthOneMinus; +float LightShadowsStrength; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; @@ -131,11 +131,11 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target // TODO: try using shadow map for on-screen pixels // TODO: try using cone trace with Global SDF for smoother shadow (eg. for sun shadows or for area lights) - // Shot a ray from light into texel to see if there is any occluder + // Shot a ray from texel into the light to see if there is any occluder GlobalSDFTrace trace; trace.Init(gBuffer.WorldPos + gBuffer.Normal * shadowBias, L, bias, toLightDst - bias); GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); - shadowMask = hit.IsHit() ? LightShadowsStrengthOneMinus : 1; + shadowMask = hit.IsHit() ? LightShadowsStrength : 1; } else { From 997d2a4db9836f69d952cf3ec57411a5c2e82ae8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 15 Apr 2022 10:12:54 +0200 Subject: [PATCH 102/144] Fix Model SDF uploading with async task that locks asset file data to prevent release --- Source/Engine/Content/Assets/Model.cpp | 54 +++++++++++++++++++------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index e96592043..8ee204e11 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -15,6 +15,7 @@ #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -50,11 +51,6 @@ private: FlaxStorage::LockData _dataLock; public: - /// - /// Init - /// - /// Parent model - /// LOD to stream index StreamModelLODTask(Model* model, int32 lodIndex) : _asset(model) , _lodIndex(lodIndex) @@ -63,21 +59,16 @@ public: } public: - // [ThreadPoolTask] bool HasReference(Object* resource) const override { return _asset == resource; } -protected: - // [ThreadPoolTask] bool Run() override { AssetReference model = _asset.Get(); if (model == nullptr) - { return true; - } // Get data BytesContainer data; @@ -120,6 +111,42 @@ protected: } }; +class StreamModelSDFTask : public GPUUploadTextureMipTask +{ +private: + WeakAssetReference _asset; + FlaxStorage::LockData _dataLock; + +public: + StreamModelSDFTask(Model* model, GPUTexture* texture, const Span& data, int32 mipIndex, int32 rowPitch, int32 slicePitch) + : GPUUploadTextureMipTask(texture, mipIndex, data, rowPitch, slicePitch, false) + , _asset(model) + , _dataLock(model->Storage->Lock()) + { + } + + bool HasReference(Object* resource) const override + { + return _asset == resource; + } + + Result run(GPUTasksContext* context) override + { + AssetReference model = _asset.Get(); + if (model == nullptr) + return Result::MissingResources; + return GPUUploadTextureMipTask::run(context); + } + + void OnEnd() override + { + _dataLock.Release(); + + // Base + GPUUploadTextureMipTask::OnEnd(); + } +}; + REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true); static byte EnableModelSDF = 0; @@ -933,11 +960,8 @@ Asset::LoadResult Model::load() ModelSDFMip mipData; sdfStream.Read(&mipData); void* mipBytes = sdfStream.Read(mipData.SlicePitch); - BytesContainer mipBytesData; - mipBytesData.Link((byte*)mipBytes, mipData.SlicePitch); - auto task = SDF.Texture->UploadMipMapAsync(mipBytesData, mipData.MipIndex, mipData.RowPitch, mipData.SlicePitch, false); - if (task) - task->Start(); + auto task = ::New(this, SDF.Texture, Span((byte*)mipBytes, mipData.SlicePitch), mipData.MipIndex, mipData.RowPitch, mipData.SlicePitch); + task->Start(); } break; } From a6c14bd98607a941f28f788144711f34c03c43b8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Sat, 16 Apr 2022 22:49:03 +0200 Subject: [PATCH 103/144] Fix `Dictionary::Remove` return value if empty --- Source/Engine/Core/Collections/Dictionary.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 6e2d0e85d..ae88d378b 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -653,11 +653,9 @@ public: bool Remove(const KeyComparableType& key) { if (IsEmpty()) - return true; - + return false; FindPositionResult pos; FindPosition(key, pos); - if (pos.ObjectIndex != -1) { _allocation.Get()[pos.ObjectIndex].Delete(); From c53a463bb12db99cbedf42189096e344ebe22414 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 19 Apr 2022 17:14:29 +0200 Subject: [PATCH 104/144] Refactor `HashSet` to support custom allocator --- Source/Engine/Content/Assets/Animation.cpp | 2 +- Source/Engine/Core/Collections/Dictionary.h | 43 +-- Source/Engine/Core/Collections/HashSet.h | 361 +++++++++----------- Source/Engine/Particles/ParticleSystem.cpp | 2 +- Source/flax.natvis | 34 +- 5 files changed, 204 insertions(+), 238 deletions(-) diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index b2de85bee..226074a72 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -83,7 +83,7 @@ void Animation::ClearCache() // Free memory MappingCache.Clear(); - MappingCache.Cleanup(); + MappingCache.SetCapacity(0); } const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index ae88d378b..2de3cecc9 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -378,10 +378,10 @@ public: // Insert ASSERT(pos.FreeSlotIndex != -1); - auto bucket = &_allocation.Get()[pos.FreeSlotIndex]; - bucket->Occupy(key); + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket.Occupy(key); _elementsCount++; - return bucket->Value; + return bucket.Value; } /// @@ -481,7 +481,7 @@ public: #endif void ClearDelete() { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value) Delete(i->Value); @@ -555,14 +555,6 @@ public: SetCapacity(capacity, preserveContents); } - /// - /// Cleanup collection data (changes size to 0 without data preserving). - /// - FORCE_INLINE void Cleanup() - { - SetCapacity(0, false); - } - /// /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. /// @@ -640,7 +632,7 @@ public: void Add(const Iterator& i) { ASSERT(&i._collection != this && i); - Bucket& bucket = *i; + const Bucket& bucket = *i; Add(bucket.Key, bucket.Value); } @@ -693,7 +685,7 @@ public: int32 RemoveValue(const ValueType& value) { int32 result = 0; - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value == value) { @@ -714,16 +706,11 @@ public: template Iterator Find(const KeyComparableType& key) const { - if (HasItems()) - { - const Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - { - if (data[i].IsOccupied() && data[i].Key == key) - return Iterator(*this, i); - } - } - return End(); + if (IsEmpty()) + return End(); + FindPositionResult pos; + FindPosition(key, pos); + return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); } /// @@ -794,7 +781,7 @@ public: { Clear(); SetCapacity(other.Capacity(), false); - for (auto i = other.Begin(); i != other.End(); ++i) + for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); @@ -807,7 +794,7 @@ public: template void GetKeys(Array& result) const { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Key); } @@ -818,7 +805,7 @@ public: template void GetValues(Array& result) const { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Value); } @@ -893,7 +880,7 @@ protected: while (checksCount < _size) { // Empty bucket - auto& bucket = data[bucketIndex]; + const Bucket& bucket = data[bucketIndex]; if (bucket.IsEmpty()) { // Found place to insert diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 5d105bcae..eb10a5015 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -2,78 +2,62 @@ #pragma once -#include "Engine/Core/Core.h" -#include "Engine/Core/Math/Math.h" -#include "Engine/Platform/Platform.h" -#include "HashFunctions.h" -#include "Config.h" +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Collections/Config.h" /// /// Template for unordered set of values (without duplicates with O(1) lookup access). /// /// The type of elements in the set. -template +/// The type of memory allocator. +template API_CLASS(InBuild) class HashSet { friend HashSet; - public: /// - /// Describes single portion of space for the item in a hash map + /// Describes single portion of space for the item in a hash map. /// struct Bucket { friend HashSet; - public: - enum State : byte { Empty, Deleted, Occupied, }; - - public: - + + /// The item. T Item; private: - State _state; - public: - - Bucket() - : _state(Empty) - { - } - - ~Bucket() - { - } - - public: - void Free() { + if (_state == Occupied) + Memory::DestructItem(&Item); _state = Empty; } void Delete() { _state = Deleted; + Memory::DestructItem(&Item); } - void Occupy(const T& item) + template + void Occupy(const ItemType& item) { - Item = item; + Memory::ConstructItems(&Item, &item, 1); _state = Occupied; } - public: - FORCE_INLINE bool IsEmpty() const { return _state == Empty; @@ -94,13 +78,15 @@ public: return _state != Occupied; } }; + + typedef typename AllocationType::template Data AllocationData; private: int32 _elementsCount = 0; int32 _deletedCount = 0; - int32 _tableSize = 0; - Bucket* _table = nullptr; + int32 _size = 0; + AllocationData _allocation; public: @@ -117,10 +103,27 @@ public: /// The initial capacity. HashSet(int32 capacity) { - ASSERT(capacity >= 0); SetCapacity(capacity); } + /// + /// Initializes a new instance of the class. + /// + /// The other collection to move. + HashSet(HashSet&& other) noexcept + : _elementsCount(other._elementsCount) + , _deletedCount(other._deletedCount) + , _size(other._size) + { + _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; + _size = other._size; + other._elementsCount = 0; + other._deletedCount = 0; + other._size = 0; + _allocation.Swap(other._allocation); + } + /// /// Initializes a new instance of the class. /// @@ -137,18 +140,39 @@ public: /// The reference to this. HashSet& operator=(const HashSet& other) { - // Ensure we're not trying to set to itself if (this != &other) Clone(other); return *this; } + /// + /// Moves the data from the other collection. + /// + /// The other collection to move. + /// The reference to this. + HashSet& operator=(HashSet&& other) noexcept + { + if (this != &other) + { + Clear(); + _allocation.Free(); + _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; + _size = other._size; + other._elementsCount = 0; + other._deletedCount = 0; + other._size = 0; + _allocation.Swap(other._allocation); + } + return *this; + } + /// /// Finalizes an instance of the class. /// ~HashSet() { - Cleanup(); + SetCapacity(0, false); } public: @@ -156,7 +180,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of elements in the collection. FORCE_INLINE int32 Count() const { return _elementsCount; @@ -165,16 +188,14 @@ public: /// /// Gets the amount of the elements that can be contained by the collection. /// - /// The capacity of the collection. FORCE_INLINE int32 Capacity() const { - return _tableSize; + return _size; } /// /// Returns true if collection is empty. /// - /// True if is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _elementsCount == 0; @@ -183,7 +204,6 @@ public: /// /// Returns true if collection has one or more elements. /// - /// True if isn't empty, otherwise false. FORCE_INLINE bool HasItems() const { return _elementsCount != 0; @@ -197,9 +217,7 @@ public: struct Iterator { friend HashSet; - private: - HashSet& _collection; int32 _index; @@ -223,41 +241,37 @@ public: { } + Iterator(Iterator&& i) + : _collection(i._collection) + , _index(i._index) + { + } + public: - /// - /// Checks if iterator is in the end of the collection - /// - /// True if is in the end, otherwise false FORCE_INLINE bool IsEnd() const { return _index == _collection.Capacity(); } - /// - /// Checks if iterator is not in the end of the collection - /// - /// True if is not in the end, otherwise false FORCE_INLINE bool IsNotEnd() const { return _index != _collection.Capacity(); } - public: - FORCE_INLINE Bucket& operator*() const { - return _collection._table[_index]; + return _collection._allocation.Get()[_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection._table[_index]; + return &_collection._allocation.Get()[_index]; } FORCE_INLINE explicit operator bool() const { - return _index >= 0 && _index < _collection._tableSize; + return _index >= 0 && _index < _collection._size; } FORCE_INLINE bool operator !() const @@ -275,17 +289,16 @@ public: return _index != v._index || &_collection != &v._collection; } - public: - Iterator& operator++() { const int32 capacity = _collection.Capacity(); if (_index != capacity) { + const Bucket* data = _collection._allocation.Get(); do { _index++; - } while (_index != capacity && _collection._table[_index].IsNotOccupied()); + } while (_index != capacity && data[_index].IsNotOccupied()); } return *this; } @@ -301,10 +314,11 @@ public: { if (_index > 0) { + const Bucket* data = _collection._allocation.Get(); do { _index--; - } while (_index > 0 && _collection._table[_index].IsNotOccupied()); + } while (_index > 0 && data[_index].IsNotOccupied()); } return *this; } @@ -324,22 +338,25 @@ public: /// void Clear() { - if (_table) + if (_elementsCount + _deletedCount != 0) { - // Free all buckets - // Note: this will not clear allocated objects space! - for (int32 i = 0; i < _tableSize; i++) - _table[i].Free(); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i].Free(); _elementsCount = _deletedCount = 0; } } /// - /// Clear the collection and delete value objects. + /// Clears the collection and delete value objects. + /// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method. /// +#if defined(_MSC_VER) + template::Value>::Type> +#endif void ClearDelete() { - for (auto i = Begin(); i.IsNotEnd(); ++i) + for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value) ::Delete(i->Value); @@ -354,86 +371,63 @@ public: /// Enable/disable preserving collection contents during resizing void SetCapacity(int32 capacity, bool preserveContents = true) { - // Validate input - ASSERT(capacity >= 0); - - // Check if capacity won't change if (capacity == Capacity()) return; - - // Cache previous state - auto oldTable = _table; - auto oldTableSize = _tableSize; - - // Clear elements counters - const auto oldElementsCount = _elementsCount; + ASSERT(capacity >= 0); + AllocationData oldAllocation; + oldAllocation.Swap(_allocation); + const int32 oldSize = _size; + const int32 oldElementsCount = _elementsCount; _deletedCount = _elementsCount = 0; - - // Check if need to create a new table - if (capacity > 0) + if (capacity != 0 && (capacity & (capacity - 1)) != 0) { - // Align capacity value - if (Math::IsPowerOfTwo(capacity) == false) - capacity = Math::RoundUpToPowerOf2(capacity); - - // Allocate new table - _table = NewArray(capacity); - _tableSize = capacity; - - // Check if preserve content - if (oldElementsCount != 0 && preserveContents) + // Align capacity value to the next power of two (if it's not) + capacity++; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + capacity = capacity + 1; + } + if (capacity) + { + _allocation.Allocate(capacity); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < capacity; i++) + data[i]._state = Bucket::Empty; + } + _size = capacity; + Bucket* oldData = oldAllocation.Get(); + if (oldElementsCount != 0 && preserveContents) + { + // TODO; move keys and values on realloc + for (int32 i = 0; i < oldSize; i++) { - // Try to preserve all values in the collection - for (int32 i = 0; i < oldTableSize; i++) - { - if (oldTable[i].IsOccupied()) - Add(oldTable[i].Item); - } + if (oldData[i].IsOccupied()) + Add(oldData[i].Item); } } - else + if (oldElementsCount != 0) { - // Clear data - _table = nullptr; - _tableSize = 0; - } - ASSERT(preserveContents == false || _elementsCount == oldElementsCount); - - // Delete old table - if (oldTable) - { - DeleteArray(oldTable, oldTableSize); + for (int32 i = 0; i < oldSize; i++) + oldData[i].Free(); } } /// - /// Increases collection capacity by given extra size (content will be preserved) + /// Ensures that collection has given capacity. /// - /// Extra size to enlarge collection - FORCE_INLINE void IncreaseCapacity(int32 extraSize) - { - ASSERT(extraSize >= 0); - SetCapacity(Capacity() + extraSize); - } - - /// - /// Ensures that collection has given capacity - /// - /// Minimum required capacity - void EnsureCapacity(int32 minCapacity) + /// The minimum required capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, bool preserveContents = true) { if (Capacity() >= minCapacity) return; - int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; - SetCapacity(Math::Clamp(num, minCapacity, MAX_int32 - 1410)); - } - - /// - /// Cleanup collection data (changes size to 0 without data preserving) - /// - FORCE_INLINE void Cleanup() - { - SetCapacity(0, false); + if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) + minCapacity = DICTIONARY_DEFAULT_CAPACITY; + const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + SetCapacity(capacity, preserveContents); } public: @@ -443,7 +437,8 @@ public: /// /// The element to add to the set. /// True if element has been added to the collection, otherwise false if the element is already present. - bool Add(const T& item) + template + bool Add(const ItemType& item) { // Ensure to have enough memory for the next item (in case of new element insertion) EnsureCapacity(_elementsCount + _deletedCount + 1); @@ -453,12 +448,12 @@ public: FindPosition(item, pos); // Check if object has been already added - if (pos.ObjectIndex != INVALID_INDEX) + if (pos.ObjectIndex != -1) return false; // Insert - ASSERT(pos.FreeSlotIndex != INVALID_INDEX); - auto bucket = &_table[pos.FreeSlotIndex]; + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; bucket->Occupy(item); _elementsCount++; @@ -472,7 +467,7 @@ public: void Add(const Iterator& i) { ASSERT(&i._collection != this && i); - Bucket& bucket = *i; + const Bucket& bucket = *i; Add(bucket.Item); } @@ -481,22 +476,20 @@ public: /// /// The element to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. - bool Remove(const T& item) + template + bool Remove(const ItemType& item) { if (IsEmpty()) return true; - FindPositionResult pos; FindPosition(item, pos); - - if (pos.ObjectIndex != INVALID_INDEX) + if (pos.ObjectIndex != -1) { - _table[pos.ObjectIndex].Delete(); + _allocation.Get()[pos.ObjectIndex].Delete(); _elementsCount--; _deletedCount++; return true; } - return false; } @@ -510,8 +503,8 @@ public: ASSERT(&i._collection == this); if (i) { - ASSERT(_table[i._index].IsOccupied()); - _table[i._index].Delete(); + ASSERT(_allocation.Get()[i._index].IsOccupied()); + _allocation.Get()[i._index].Delete(); _elementsCount--; _deletedCount++; return true; @@ -526,15 +519,14 @@ public: /// /// Item to find /// Iterator for the found element or End if cannot find it - Iterator Find(const T& item) const + template + Iterator Find(const ItemType& item) const { if (IsEmpty()) return End(); - FindPositionResult pos; FindPosition(item, pos); - - return pos.ObjectIndex != INVALID_INDEX ? Iterator(*this, pos.ObjectIndex) : End(); + return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); } /// @@ -542,15 +534,14 @@ public: /// /// The item to locate. /// True if value has been found in a collection, otherwise false - bool Contains(const T& item) const + template + bool Contains(const ItemType& item) const { if (IsEmpty()) return false; - FindPositionResult pos; FindPosition(item, pos); - - return pos.ObjectIndex != INVALID_INDEX; + return pos.ObjectIndex != -1; } public: @@ -561,41 +552,26 @@ public: /// Other collection to clone void Clone(const HashSet& other) { - // Clear previous data Clear(); - - // Update capacity SetCapacity(other.Capacity(), false); - - // Clone items - for (auto i = other.Begin(); i != other.End(); ++i) + for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); - - // Check ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); } public: - /// - /// Gets iterator for beginning of the collection. - /// - /// Iterator for beginning of the collection. Iterator Begin() const { - Iterator i(*this, INVALID_INDEX); + Iterator i(*this, -1); ++i; return i; } - /// - /// Gets iterator for ending of the collection. - /// - /// Iterator for ending of the collection. Iterator End() const { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } Iterator begin() @@ -607,7 +583,7 @@ public: FORCE_INLINE Iterator end() { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } const Iterator begin() const @@ -619,11 +595,14 @@ public: FORCE_INLINE const Iterator end() const { - return Iterator(*this, _tableSize); + return Iterator(*this, _size); } protected: + /// + /// The result container of the set item lookup searching. + /// struct FindPositionResult { int32 ObjectIndex; @@ -632,42 +611,43 @@ protected: /// /// Returns a pair of positions: 1st where the object is, 2nd where - /// it would go if you wanted to insert it. 1st is INVALID_INDEX - /// if object is not found; 2nd is INVALID_INDEX if it is. + /// it would go if you wanted to insert it. 1st is -1 + /// if object is not found; 2nd is -1 if it is. /// Note: because of deletions where-to-insert is not trivial: it's the /// first deleted bucket we see, as long as we don't find the item later /// /// The item to find /// Pair of values: where the object is and where it would go if you wanted to insert it - void FindPosition(const T& item, FindPositionResult& result) const + template + void FindPosition(const ItemType& item, FindPositionResult& result) const { - ASSERT(_table); - - const int32 tableSizeMinusOne = _tableSize - 1; + ASSERT(_size); + const int32 tableSizeMinusOne = _size - 1; int32 bucketIndex = GetHash(item) & tableSizeMinusOne; - int32 insertPos = INVALID_INDEX; + int32 insertPos = -1; int32 numChecks = 0; - result.FreeSlotIndex = INVALID_INDEX; - - while (numChecks < _tableSize) + const Bucket* data = _allocation.Get(); + result.FreeSlotIndex = -1; + while (numChecks < _size) { // Empty bucket - if (_table[bucketIndex].IsEmpty()) + const Bucket& bucket = data[bucketIndex]; + if (bucket.IsEmpty()) { // Found place to insert - result.ObjectIndex = INVALID_INDEX; - result.FreeSlotIndex = insertPos == INVALID_INDEX ? bucketIndex : insertPos; + result.ObjectIndex = -1; + result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos; return; } // Deleted bucket - if (_table[bucketIndex].IsDeleted()) + if (bucket.IsDeleted()) { // Keep searching but mark to insert - if (insertPos == INVALID_INDEX) + if (insertPos == -1) insertPos = bucketIndex; } // Occupied bucket by target item - else if (_table[bucketIndex].Item == item) + else if (bucket.Item == item) { // Found item result.ObjectIndex = bucketIndex; @@ -675,10 +655,9 @@ protected: } numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, numChecks)) & tableSizeMinusOne; } - - result.ObjectIndex = INVALID_INDEX; + result.ObjectIndex = -1; result.FreeSlotIndex = insertPos; } }; diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 6a1f32327..745bca545 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -461,7 +461,7 @@ void ParticleSystem::unload(bool isReloading) FramesPerSecond = 0.0f; DurationFrames = 0; Emitters.Resize(0); - EmittersParametersOverrides.Cleanup(); + EmittersParametersOverrides.SetCapacity(0); Tracks.Resize(0); } diff --git a/Source/flax.natvis b/Source/flax.natvis index 4cc1397d8..a38626357 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -61,24 +61,24 @@ - + - {{ Size={_elementsCount} Capacity={_tableSize} }} - - _elementsCount - _tableSize - - - _elementsCount - - - - _table[i] - - i++ - - - + {{ Size={_elementsCount} Capacity={_size} }} + + _elementsCount + _size + + + _elementsCount + + + + _allocation._data[i].Item + + i++ + + + From b9652949b0c70b4254f6e7aba0b0d360f4ce5fed Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 19 Apr 2022 17:15:54 +0200 Subject: [PATCH 105/144] Minor improvements --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 ++-- Source/Engine/Graphics/RenderBuffers.h | 3 +++ Source/Engine/Graphics/RenderTask.cpp | 5 +++-- Source/Engine/Level/Scene/SceneRendering.cpp | 11 +++-------- Source/Engine/Profiler/ProfilerCPU.h | 3 +++ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 038465b36..900052f79 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da4ac86323889084590fe12325b00acae1294dbdf2e28077f3af5a93ea7a7162 -size 7125 +oid sha256:7444088469d72ca5bd6f3507726e4ba68a2f550fcd78cfbd5c7a7b2fbee5e982 +size 7113 diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index c0fc51808..149b4c12b 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -26,6 +26,9 @@ API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject { DECLARE_SCRIPTING_TYPE(RenderBuffers); + /// + /// The custom rendering state. + /// class CustomBuffer : public Object { public: diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 23ac08591..5d4b09b9e 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -289,7 +289,8 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContext& renderContext) { if (_customActorsScene == nullptr) _customActorsScene = New(); - _customActorsScene->Clear(); + else + _customActorsScene->Clear(); for (Actor* a : CustomActors) AddActorToSceneRendering(_customActorsScene, a); _customActorsScene->Draw(renderContext); @@ -414,7 +415,7 @@ void SceneRenderTask::OnEnd(GPUContext* context) // Swap matrices View.PrevView = View.View; View.PrevProjection = View.Projection; - Matrix::Multiply(View.PrevView, View.PrevProjection, View.PrevViewProjection); + View.PrevViewProjection = View.ViewProjection(); } bool SceneRenderTask::Resize(int32 width, int32 height) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 72acf14e9..740a87a91 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -25,10 +25,7 @@ void SceneRendering::Draw(RenderContext& renderContext) if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)) && e.Actor->GetStaticFlags() & view.StaticFlagsMask) { #if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif + PROFILE_CPU_ACTOR(e.Actor); #endif e.Actor->Draw(renderContext); } @@ -42,10 +39,7 @@ void SceneRendering::Draw(RenderContext& renderContext) if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds))) { #if SCENE_RENDERING_USE_PROFILER - PROFILE_CPU(); -#if TRACY_ENABLE - ___tracy_scoped_zone.Name(*e.Actor->GetName(), e.Actor->GetName().Length()); -#endif + PROFILE_CPU_ACTOR(e.Actor); #endif e.Actor->Draw(renderContext); } @@ -88,6 +82,7 @@ void SceneRendering::Clear() int32 SceneRendering::AddActor(Actor* a) { int32 key = 0; + // TODO: track removedCount and skip searching for free entry if there is none for (; key < Actors.Count(); key++) { if (Actors[key].Actor == nullptr) diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index b9e070bb0..e4d40a444 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -403,9 +403,11 @@ struct TIsPODType #ifdef TRACY_ENABLE #define PROFILE_CPU_SRC_LOC(srcLoc) tracy::ScopedZone ___tracy_scoped_zone( (tracy::SourceLocationData*)&(srcLoc) ); ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #define PROFILE_CPU_ASSET(asset) ZoneScoped; const StringView __tracy_asset_name((asset)->GetPath()); ZoneName(*__tracy_asset_name, __tracy_asset_name.Length()) +#define PROFILE_CPU_ACTOR(actor) ZoneScoped; const StringView __tracy_actor_name((actor)->GetName()); ZoneName(*__tracy_actor_name, __tracy_actor_name.Length()) #else #define PROFILE_CPU_SRC_LOC(srcLoc) ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #define PROFILE_CPU_ASSET(asset) +#define PROFILE_CPU_ACTOR(actor) #endif #else @@ -415,5 +417,6 @@ struct TIsPODType #define PROFILE_CPU_NAMED(name) #define PROFILE_CPU_SRC_LOC(srcLoc) #define PROFILE_CPU_ASSET(asset) +#define PROFILE_CPU_ACTOR(actor) #endif From fe430e81ad345b85467eeb4eba589a8013f8658d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 19 Apr 2022 17:16:33 +0200 Subject: [PATCH 106/144] Add `ISceneRenderingListener` for using scene information in renderer cache --- Source/Engine/Level/Scene/SceneRendering.cpp | 29 ++++++++++++++++++++ Source/Engine/Level/Scene/SceneRendering.h | 27 ++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 740a87a91..c29db6bee 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -10,6 +10,23 @@ #include "Engine/Profiler/ProfilerCPU.h" #endif +ISceneRenderingListener::~ISceneRenderingListener() +{ + for (SceneRendering* scene : _scenes) + { + scene->_listeners.Remove(this); + } +} + +void ISceneRenderingListener::ListenSceneRendering(SceneRendering* scene) +{ + if (!_scenes.Contains(scene)) + { + _scenes.Add(scene); + scene->_listeners.Add(this); + } +} + void SceneRendering::Draw(RenderContext& renderContext) { auto& view = renderContext.View; @@ -73,6 +90,12 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { + for (auto* listener : _listeners) + { + listener->OnSceneRenderingClear(this); + listener->_scenes.Remove(this); + } + _listeners.Clear(); Actors.Clear(); #if USE_EDITOR PhysicsDebug.Clear(); @@ -95,6 +118,8 @@ int32 SceneRendering::AddActor(Actor* a) e.LayerMask = a->GetLayerMask(); e.Bounds = a->GetSphere(); e.NoCulling = a->_drawNoCulling; + for (auto* listener : _listeners) + listener->OnSceneRenderingAddActor(a); return key; } @@ -104,6 +129,8 @@ void SceneRendering::UpdateActor(Actor* a, int32 key) return; auto& e = Actors[key]; ASSERT_LOW_LAYER(a == e.Actor); + for (auto* listener : _listeners) + listener->OnSceneRenderingUpdateActor(a, e.Bounds); e.LayerMask = a->GetLayerMask(); e.Bounds = a->GetSphere(); } @@ -114,6 +141,8 @@ void SceneRendering::RemoveActor(Actor* a, int32& key) { auto& e = Actors[key]; ASSERT_LOW_LAYER(a == e.Actor); + for (auto* listener : _listeners) + listener->OnSceneRenderingRemoveActor(a); e.Actor = nullptr; e.LayerMask = 0; } diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index afcb078ea..160a89510 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -8,6 +8,7 @@ #include "Engine/Level/Actor.h" class SceneRenderTask; +class SceneRendering; struct PostProcessSettings; struct RenderContext; struct RenderView; @@ -32,6 +33,28 @@ public: virtual void Blend(PostProcessSettings& other, float weight) = 0; }; +/// +/// Interface for objects to plug into Scene Rendering and listen for its evens such as static actors changes which are relevant for drawing cache. +/// +/// +class FLAXENGINE_API ISceneRenderingListener +{ + friend SceneRendering; +private: + Array> _scenes; +public: + ~ISceneRenderingListener(); + + // Starts listening to the scene rendering events. + void ListenSceneRendering(SceneRendering* scene); + + // Events called by Scene Rendering + virtual void OnSceneRenderingAddActor(Actor* a) = 0; + virtual void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) = 0; + virtual void OnSceneRenderingRemoveActor(Actor* a) = 0; + virtual void OnSceneRenderingClear(SceneRendering* scene) = 0; +}; + /// /// Scene rendering helper subsystem that boosts the level rendering by providing efficient objects cache and culling implementation. /// @@ -59,6 +82,10 @@ private: Array ViewportIcons; #endif + // Listener - some rendering systems cache state of the scene (eg. in RenderBuffers::CustomBuffer), this extensions allows those systems to invalidate cache and handle scene changes + friend ISceneRenderingListener; + Array> _listeners; + public: /// /// Draws the scene. Performs the optimized actors culling and draw calls submission for the current render pass (defined by the render view). From 1523fa98ecd86089ad1b142308d7e633a15bdbf9 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 19 Apr 2022 17:17:27 +0200 Subject: [PATCH 107/144] Add static chunks caching to Global SDF --- Source/Engine/Level/Actors/StaticModel.cpp | 2 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 316 +++++++++++++----- .../Renderer/GlobalSignDistanceFieldPass.h | 3 +- 3 files changed, 232 insertions(+), 89 deletions(-) diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 132202dac..a32512b1b 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -215,7 +215,7 @@ void StaticModel::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) { - GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(Model->SDF, _world, _box); + GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _world, _box); return; } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 60bcde40b..e287ba6f0 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -17,13 +17,15 @@ // Some of those constants must match in shader // TODO: try using R8 format for Global SDF #define GLOBAL_SDF_FORMAT PixelFormat::R16_Float -#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 +#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. #define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 -#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 -#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 // Global SDF chunk size in voxels. +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 // The margin in voxels around objects for culling. Reduces artifacts but reduces performance. +#define GLOBAL_SDF_RASTERIZE_MIP_FACTOR 4 // Global SDF mip resolution downscale factor. #define GLOBAL_SDF_MIP_GROUP_SIZE 4 -#define GLOBAL_SDF_MIP_FLOODS 5 +#define GLOBAL_SDF_MIP_FLOODS 5 // Amount of flood fill passes for mip. #define GLOBAL_SDF_DEBUG_CHUNKS 0 +#define GLOBAL_SDF_ACTOR_IS_STATIC(actor) ((actor->GetStaticFlags() & (StaticFlags::Lightmap | StaticFlags::Transform)) == (int32)(StaticFlags::Lightmap | StaticFlags::Transform)) static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple of 4 due to data packing for GPU constant buffer."); #if GLOBAL_SDF_DEBUG_CHUNKS @@ -79,6 +81,7 @@ struct RasterizeModel struct RasterizeChunk { + bool Dynamic = false; int32 ModelsCount = 0; int32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; }; @@ -91,6 +94,12 @@ struct RasterizeChunkKey int32 Layer; Int3 Coord; + FORCE_INLINE void NextLayer() + { + Layer++; + Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + } + friend bool operator==(const RasterizeChunkKey& a, const RasterizeChunkKey& b) { return a.Hash == b.Hash && a.Coord == b.Coord && a.Layer == b.Layer; @@ -102,21 +111,97 @@ uint32 GetHash(const RasterizeChunkKey& key) return key.Hash; } -class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer +struct CascadeData +{ + GPUTexture* Texture = nullptr; + GPUTexture* Mip = nullptr; + Vector3 Position; + float VoxelSize; + BoundingBox Bounds; + HashSet NonEmptyChunks; + HashSet StaticChunks; + + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) + { + if (StaticChunks.IsEmpty() || !Bounds.Intersects(objectBounds)) + return; + + BoundingBox objectBoundsCascade; + const float objectMargin = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, Bounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, Bounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + + // Invalidate static chunks intersecting with dirty bounds + RasterizeChunkKey key; + key.Layer = 0; + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + StaticChunks.Remove(key); + } + } + } + } + + ~CascadeData() + { + RenderTargetPool::Release(Texture); + RenderTargetPool::Release(Mip); + } +}; + +class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: - GPUTexture* Cascades[4] = {}; - GPUTexture* CascadeMips[4] = {}; - Vector3 Positions[4]; - HashSet NonEmptyChunks[4]; + CascadeData Cascades[4]; + HashSet ObjectTypes; GlobalSignDistanceFieldPass::BindingData Result; - ~GlobalSignDistanceFieldCustomBuffer() + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) { - for (GPUTexture* cascade : Cascades) - RenderTargetPool::Release(cascade); - for (GPUTexture* mip : CascadeMips) - RenderTargetPool::Release(mip); + for (auto& cascade : Cascades) + cascade.OnSceneRenderingDirty(objectBounds); + } + + // [ISceneRenderingListener] + void OnSceneRenderingAddActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingRemoveActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingClear(SceneRendering* scene) override + { + for (auto& cascade : Cascades) + cascade.StaticChunks.Clear(); } }; @@ -163,6 +248,8 @@ bool GlobalSignDistanceFieldPass::setupResources() // Check shader _cb0 = shader->GetCB(0); _cb1 = shader->GetCB(1); + if (!_cb0 || !_cb1) + return true; _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); _csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1); _csClearChunk = shader->GetCS("CS_ClearChunk"); @@ -248,8 +335,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // TODO: configurable via graphics settings const int32 resolution = 256; - const int32 mipFactor = 4; - const int32 resolutionMip = Math::DivideAndRoundUp(resolution, mipFactor); + const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); // TODO: configurable via postFx settings const float distanceExtent = 2000.0f; const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; @@ -257,47 +343,59 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Initialize buffers auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; - for (GPUTexture*& cascade : sdfData.Cascades) + for (auto& cascade : sdfData.Cascades) { - if (cascade && cascade->Width() != desc.Width) + GPUTexture*& texture = cascade.Texture; + if (texture && texture->Width() != desc.Width) { - RenderTargetPool::Release(cascade); - cascade = nullptr; + RenderTargetPool::Release(texture); + texture = nullptr; } - if (!cascade) + if (!texture) { - cascade = RenderTargetPool::Get(desc); - if (!cascade) + texture = RenderTargetPool::Get(desc); + if (!texture) return true; updated = true; - PROFILE_GPU_CPU("Init"); - context->ClearUA(cascade, Vector4::One); } } desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - for (GPUTexture*& cascadeMip : sdfData.CascadeMips) + for (auto& cascade : sdfData.Cascades) { - if (cascadeMip && cascadeMip->Width() != desc.Width) + GPUTexture*& texture = cascade.Mip; + if (texture && texture->Width() != desc.Width) { - RenderTargetPool::Release(cascadeMip); - cascadeMip = nullptr; + RenderTargetPool::Release(texture); + texture = nullptr; } - if (!cascadeMip) + if (!texture) { - cascadeMip = RenderTargetPool::Get(desc); - if (!cascadeMip) + texture = RenderTargetPool::Get(desc); + if (!texture) return true; updated = true; } } GPUTexture* tmpMip = nullptr; if (updated) - LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + { + PROFILE_GPU_CPU("Init"); + for (auto& cascade : sdfData.Cascades) + { + cascade.NonEmptyChunks.Clear(); + cascade.StaticChunks.Clear(); + context->ClearUA(cascade.Texture, Vector4::One); + context->ClearUA(cascade.Mip, Vector4::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); + } + for (SceneRendering* scene : renderContext.List->Scenes) + sdfData.ListenSceneRendering(scene); // Rasterize world geometry into Global SDF renderContext.View.Pass = DrawPass::GlobalSDF; uint32 viewMask = renderContext.View.RenderLayersMask; - const bool useCache = !updated && !renderContext.Task->IsCameraCut; + const bool useCache = !updated; static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size."); const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE); auto& chunks = ChunksCache; @@ -306,24 +404,24 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; for (int32 cascade = 0; cascade < 4; cascade++) + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { // Reduce frequency of the updates - if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) continue; - const float distance = cascadesDistances[cascade]; + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; const float maxDistance = distance * 2; const float voxelSize = maxDistance / resolution; - const float snapping = voxelSize * mipFactor; - const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping; - // TODO: cascade scrolling on movement to reduce dirty chunks? + const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); + const Vector3 center = Vector3::Floor(renderContext.View.Position / chunkSize) * chunkSize; //const Vector3 center = Vector3::Zero; - sdfData.Positions[cascade] = center; BoundingBox cascadeBounds(center - distance, center + distance); // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade - const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume(); - GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume(); + GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); + GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); // Clear cascade before rasterization { @@ -333,17 +431,26 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex _modelsTextures.Clear(); } + // Check if cascade center has been moved + if (!(useCache && Vector3::NearEqual(cascade.Position, center, voxelSize))) + { + // TODO: optimize for moving camera (copy sdf for cached chunks) + cascade.StaticChunks.Clear(); + } + cascade.Position = center; + cascade.VoxelSize = voxelSize; + cascade.Bounds = cascadeBounds; + // Draw all objects from all scenes into the cascade _modelsBufferCount = 0; _voxelSize = voxelSize; _cascadeBounds = cascadeBounds; + _sdfData = &sdfData; { PROFILE_CPU_NAMED("Draw"); - for (auto* scene : renderContext.List->Scenes) + for (SceneRendering* scene : renderContext.List->Scenes) { - // TODO: optimize for moving camera (copy sdf) - // TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering) - for (auto& e : scene->Actors) + for (const auto& e : scene->Actors) { if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) { @@ -374,38 +481,62 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex data.MaxDistance = maxDistance; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; - data.CascadeMipFactor = mipFactor; + data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; context->BindUA(0, cascadeView); context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); - if (_cb1) - context->BindCB(1, _cb1); + context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; - auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; + bool anyChunkDispatch = false; { PROFILE_GPU_CPU("Clear Chunks"); - for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) + for (auto it = cascade.NonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { auto& key = it->Item; if (chunks.ContainsKey(key)) continue; // Clear empty chunk - nonEmptyChunks.Remove(it); + cascade.NonEmptyChunks.Remove(it); data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches } } // TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds { PROFILE_GPU_CPU("Rasterize Chunks"); - for (auto& e : chunks) + + // Update static chunks + for (auto it = chunks.Begin(); it.IsNotEnd(); ++it) { - // Rasterize non-empty chunk (first layer so can override existing chunk data) - auto& key = e.Key; - if (key.Layer != 0) + auto& e = *it; + if (e.Key.Layer != 0) + continue; + if (e.Value.Dynamic) + { + // Remove static chunk with dynamic objects + cascade.StaticChunks.Remove(e.Key); + } + else if (cascade.StaticChunks.Contains(e.Key)) + { + // Skip updating static chunk + auto key = e.Key; + while (chunks.Remove(key)) + key.NextLayer(); + } + else + { + // Add to cache (render now but skip next frame) + cascade.StaticChunks.Add(e.Key); + } + } + + // Rasterize non-empty chunk (first layer so can override existing chunk data) + for (const auto& e : chunks) + { + if (e.Key.Layer != 0) continue; auto& chunk = e.Value; for (int32 i = 0; i < chunk.ModelsCount; i++) @@ -415,30 +546,38 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); - nonEmptyChunks.Add(key); + context->UpdateCB(_cb1, &data); + cascade.NonEmptyChunks.Add(e.Key); context->Dispatch(_csRasterizeModel0, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches (maybe cache per-shader write/read flags for all UAVs?) #if GLOBAL_SDF_DEBUG_CHUNKS // Debug draw chunk bounds in world space with number of models in it - if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS) + if (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS) { - Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; + int32 count = chunk.ModelsCount; + RasterizeChunkKey tmp = e.Key; + tmp.NextLayer(); + while (chunks.ContainsKey(tmp)) + { + count += chunks[tmp].ModelsCount; + tmp.NextLayer(); + } + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(e.Key.Coord) * chunkSize; BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize); DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false); - DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red); + DebugDraw::DrawText(StringUtils::ToString(count), chunkBounds.GetCenter(), Color::Red); } #endif } - for (auto& e : chunks) + + // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) + for (const auto& e : chunks) { - // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) - auto& key = e.Key; - if (key.Layer == 0) + if (e.Key.Layer == 0) continue; auto& chunk = e.Value; for (int32 i = 0; i < chunk.ModelsCount; i++) @@ -448,19 +587,19 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; } } // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) + if (updated || anyChunkDispatch) { PROFILE_GPU_CPU("Generate Mip"); - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->ResetUA(); context->BindSR(0, cascadeView); context->BindUA(0, cascadeMipView); @@ -494,17 +633,19 @@ 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.CascadeMips), "Invalid cascades count."); - Platform::MemoryCopy(result.Cascades, sdfData.Cascades, sizeof(result.Cascades)); - Platform::MemoryCopy(result.CascadeMips, sdfData.CascadeMips, sizeof(result.CascadeMips)); - for (int32 cascade = 0; cascade < 4; cascade++) + static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); + static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { - const float distance = cascadesDistances[cascade]; + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; const float maxDistance = distance * 2; const float voxelSize = maxDistance / resolution; - const Vector3 center = sdfData.Positions[cascade]; - result.GlobalSDF.CascadePosDistance[cascade] = Vector4(center, distance); - result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize; + const Vector3 center = cascade.Position; + result.GlobalSDF.CascadePosDistance[cascadeIndex] = Vector4(center, distance); + result.GlobalSDF.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Cascades[cascadeIndex] = cascade.Texture; + result.CascadeMips[cascadeIndex] = cascade.Mip; } result.GlobalSDF.Resolution = (float)resolution; sdfData.Result = result; @@ -522,7 +663,6 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC PROFILE_GPU_CPU("Global SDF Debug"); const Vector2 outputSize(output->Size()); - if (_cb0) { Data data; data.ViewWorldPos = renderContext.View.Position; @@ -545,7 +685,7 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->DrawFullscreenTriangle(); } -void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) +void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) { if (!sdf.Texture) return; @@ -596,8 +736,10 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sd _modelsTextures.Add(sdf.Texture->ViewVolume()); // Inject object into the intersecting cascade chunks + _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); RasterizeChunkKey key; auto& chunks = ChunksCache; + const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) { for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) @@ -607,12 +749,12 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sd key.Layer = 0; key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; RasterizeChunk* chunk = &chunks[key]; + chunk->Dynamic |= dynamic; // Move to the next layer if chunk has overflown while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) { - key.Layer++; - key.Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + key.NextLayer(); chunk = &chunks[key]; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 1e48ee25b..6243d00cb 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -45,6 +45,7 @@ private: int32 _modelsBufferCount; float _voxelSize; BoundingBox _cascadeBounds; + class GlobalSignDistanceFieldCustomBuffer* _sdfData; public: /// @@ -73,7 +74,7 @@ public: void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. - void RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); + void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); private: #if COMPILE_WITH_DEV_ENV From 8ed2bb41798d3edf68d7612937fc445756627cb0 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 11:37:50 +0200 Subject: [PATCH 108/144] Fix `HashSet::Remove` return value if empty --- Source/Engine/Core/Collections/HashSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index eb10a5015..22bdfe65a 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -480,7 +480,7 @@ public: bool Remove(const ItemType& item) { if (IsEmpty()) - return true; + return false; FindPositionResult pos; FindPosition(item, pos); if (pos.ObjectIndex != -1) From 0b6d3a313ea78041ba39cac17d0b4e9c2685c693 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:08:23 +0200 Subject: [PATCH 109/144] Fix new dynamic buffers to be actually dynamic --- Source/Engine/Graphics/DynamicBuffer.cpp | 8 +++++++- Source/Engine/Graphics/DynamicBuffer.h | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 29882aef5..d4db1b957 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -89,6 +89,12 @@ void DynamicBuffer::Dispose() Data.Resize(0); } +void DynamicStructuredBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); + desc.Usage = GPUResourceUsage::Dynamic; +} + DynamicTypedBuffer::DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess, const String& name) : DynamicBuffer(initialCapacity, PixelFormatExtensions::SizeInBytes(format), name) , _format(format) @@ -101,5 +107,5 @@ void DynamicTypedBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) auto bufferFlags = GPUBufferFlags::ShaderResource; if (_isUnorderedAccess) bufferFlags |= GPUBufferFlags::UnorderedAccess; - desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride); + desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride, GPUResourceUsage::Dynamic); } diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 507c284de..9f251ff14 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -197,10 +197,7 @@ public: protected: // [DynamicBuffer] - void InitDesc(GPUBufferDescription& desc, int32 numElements) override - { - desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); - } + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; }; /// From e44c09aa6efcca95865c10ae7ee125a4445c4b73 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:10:22 +0200 Subject: [PATCH 110/144] Optimize SDF model buffer flushing to be used only if needed --- .../Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index e287ba6f0..71c73c7d1 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -460,12 +460,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } } - // Send models data to the GPU - { - PROFILE_GPU_CPU("Update Models"); - _modelsBuffer->Flush(context); - } - // Perform batched chunks rasterization if (!anyDraw) { @@ -533,6 +527,13 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } } + // Send models data to the GPU + if (chunks.Count() != 0) + { + PROFILE_GPU_CPU("Update Models"); + _modelsBuffer->Flush(context); + } + // Rasterize non-empty chunk (first layer so can override existing chunk data) for (const auto& e : chunks) { From 60d2670e2e472fe4ee5d589609a21f1c0ecfd4b3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:29:09 +0200 Subject: [PATCH 111/144] Fixes --- Source/Engine/Level/Actors/ModelInstanceActor.cpp | 4 ++-- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index e71d66f05..a6470b98a 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -47,8 +47,8 @@ void ModelInstanceActor::OnEnable() void ModelInstanceActor::OnDisable() { - GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); - // Base Actor::OnDisable(); + + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 71c73c7d1..0557421e5 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -477,7 +477,6 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex data.CascadeMipResolution = resolutionMip; data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; context->BindUA(0, cascadeView); - context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; bool anyChunkDispatch = false; @@ -533,6 +532,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex PROFILE_GPU_CPU("Update Models"); _modelsBuffer->Flush(context); } + context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); // Rasterize non-empty chunk (first layer so can override existing chunk data) for (const auto& e : chunks) From 0a458d94c49df83fae376a94b238973485f3cb6c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:36:16 +0200 Subject: [PATCH 112/144] Add support for Volume textures to have residency changed as regular textures --- .../Engine/Graphics/Textures/GPUTexture.cpp | 2 +- Source/Engine/Graphics/Textures/GPUTexture.h | 4 +-- .../DirectX/DX11/GPUTextureDX11.cpp | 34 +++++++++---------- .../DirectX/DX11/GPUTextureDX11.h | 2 +- .../DirectX/DX12/GPUTextureDX12.cpp | 33 ++++++++++-------- .../DirectX/DX12/GPUTextureDX12.h | 2 +- .../GraphicsDevice/Null/GPUTextureNull.h | 2 +- .../Vulkan/GPUTextureVulkan.cpp | 22 ++++-------- .../GraphicsDevice/Vulkan/GPUTextureVulkan.h | 2 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 +- 11 files changed, 50 insertions(+), 57 deletions(-) diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index a49b97070..bc5424223 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -813,5 +813,5 @@ void GPUTexture::SetResidentMipLevels(int32 count) if (_residentMipLevels == count || !IsRegularTexture()) return; _residentMipLevels = count; - onResidentMipsChanged(); + OnResidentMipsChanged(); } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index 28007196d..a51d4418b 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -567,12 +567,12 @@ public: /// Sets the number of resident mipmap levels in the texture (already uploaded to the GPU). /// API_PROPERTY() void SetResidentMipLevels(int32 count); - + protected: virtual bool OnInit() = 0; uint64 calculateMemoryUsage() const; - virtual void onResidentMipsChanged() = 0; + virtual void OnResidentMipsChanged() = 0; public: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp index f5e071f68..8927f9435 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp @@ -109,37 +109,37 @@ bool GPUTextureDX11::OnInit() return false; } -void GPUTextureDX11::onResidentMipsChanged() +void GPUTextureDX11::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Fill description + const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); + const int32 mipLevels = ResidentMipLevels(); D3D11_SHADER_RESOURCE_VIEW_DESC srDesc; srDesc.Format = _dxgiFormatSRV; if (IsCubeMap()) { srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; - srDesc.TextureCube.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.TextureCube.MipLevels = ResidentMipLevels(); + srDesc.TextureCube.MostDetailedMip = firstMipIndex; + srDesc.TextureCube.MipLevels = mipLevels; + } + else if (IsVolume()) + { + srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + srDesc.Texture3D.MostDetailedMip = firstMipIndex; + srDesc.Texture3D.MipLevels = mipLevels; } else { srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srDesc.Texture2D.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.Texture2D.MipLevels = ResidentMipLevels(); + srDesc.Texture2D.MostDetailedMip = firstMipIndex; + srDesc.Texture2D.MipLevels = mipLevels; } - - // Create new view ID3D11ShaderResourceView* srView; VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); - - // Change view - if (_handlesPerSlice[0].GetParent() == nullptr) - _handlesPerSlice[0].Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); + GPUTextureViewDX11& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + if (view.GetParent() == nullptr) + view.Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); else - _handlesPerSlice[0].SetSRV(srView); + view.SetSRV(srView); } void GPUTextureDX11::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h index 51bdff1d4..b40bee48d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h @@ -277,7 +277,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index 1bba5880d..5945257e8 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -208,36 +208,39 @@ bool GPUTextureDX12::OnInit() return false; } -void GPUTextureDX12::onResidentMipsChanged() +void GPUTextureDX12::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Fill description + const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); + const int32 mipLevels = ResidentMipLevels(); D3D12_SHADER_RESOURCE_VIEW_DESC srDesc; srDesc.Format = _dxgiFormatSRV; srDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; if (IsCubeMap()) { srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; - srDesc.TextureCube.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.TextureCube.MipLevels = ResidentMipLevels(); + srDesc.TextureCube.MostDetailedMip = firstMipIndex; + srDesc.TextureCube.MipLevels = mipLevels; srDesc.TextureCube.ResourceMinLODClamp = 0; } + else if (IsVolume()) + { + srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + srDesc.Texture3D.MostDetailedMip = firstMipIndex; + srDesc.Texture3D.MipLevels = mipLevels; + srDesc.Texture3D.ResourceMinLODClamp = 0; + } else { srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srDesc.Texture2D.MostDetailedMip = MipLevels() - ResidentMipLevels(); - srDesc.Texture2D.MipLevels = ResidentMipLevels(); + srDesc.Texture2D.MostDetailedMip = firstMipIndex; + srDesc.Texture2D.MipLevels = mipLevels; srDesc.Texture2D.PlaneSlice = 0; srDesc.Texture2D.ResourceMinLODClamp = 0; } - - // Change view - if (_handlesPerSlice[0].GetParent() == nullptr) - _handlesPerSlice[0].Init(this, _device, this, Format(), MultiSampleLevel()); - _handlesPerSlice[0].SetSRV(srDesc); + GPUTextureViewDX12& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + if (view.GetParent() == nullptr) + view.Init(this, _device, this, Format(), MultiSampleLevel()); + view.SetSRV(srDesc); } void GPUTextureDX12::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h index c91471846..db274c91a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h @@ -216,7 +216,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h b/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h index abfd5aaaf..81b3c22d7 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUTextureNull.h @@ -57,7 +57,7 @@ protected: return false; } - void onResidentMipsChanged() override + void OnResidentMipsChanged() override { } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index be8f26255..eb4466ab8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -425,29 +425,19 @@ void GPUTextureVulkan::initHandles() } } -void GPUTextureVulkan::onResidentMipsChanged() +void GPUTextureVulkan::OnResidentMipsChanged() { - // We support changing resident mip maps only for regular textures (render targets and depth buffers don't use that feature at all) - ASSERT(IsRegularTexture() && _handlesPerSlice.Count() == 1); - ASSERT(!IsVolume()); - - // Change view - auto& handle = _handlesPerSlice[0]; - handle.Release(); + // Update view VkExtent3D extent; extent.width = Width(); extent.height = Height(); extent.depth = Depth(); const int32 firstMipIndex = MipLevels() - ResidentMipLevels(); const int32 mipLevels = ResidentMipLevels(); - if (IsCubeMap()) - { - handle.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, VK_IMAGE_VIEW_TYPE_CUBE, mipLevels, firstMipIndex, ArraySize()); - } - else - { - handle.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, VK_IMAGE_VIEW_TYPE_2D, mipLevels, firstMipIndex, ArraySize()); - } + const VkImageViewType viewType = IsVolume() ? VK_IMAGE_VIEW_TYPE_3D : (IsCubeMap() ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D); + GPUTextureViewVulkan& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; + view.Release(); + view.Init(_device, this, _image, mipLevels, Format(), MultiSampleLevel(), extent, viewType, mipLevels, firstMipIndex, ArraySize()); } void GPUTextureVulkan::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h index aec0ed428..fcbbd2c01 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h @@ -182,7 +182,7 @@ protected: // [GPUTexture] bool OnInit() override; - void onResidentMipsChanged() override; + void OnResidentMipsChanged() override; void OnReleaseGPU() override; }; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 0557421e5..c4df8e611 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -688,7 +688,7 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) { - if (!sdf.Texture) + if (!sdf.Texture || sdf.Texture->ResidentMipLevels() == 0) return; // Setup object data diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 36aaf0b15..8a4cd9a91 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -123,7 +123,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float *(uint8*)ptr = (uint8)v; }; } - GPUTextureDescription textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, mipCount); + GPUTextureDescription textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource, mipCount); if (outputSDF) { *outputSDF = sdf; From 5345d1f6856b2c0590b3ffd8d0a3d3dd9ed75fb8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:37:39 +0200 Subject: [PATCH 113/144] Add events for streamable resources residency changes tracking --- Source/Engine/Content/Assets/Model.cpp | 11 +++++++---- Source/Engine/Content/Assets/SkinnedModel.cpp | 9 ++++++--- Source/Engine/Graphics/Textures/GPUTexture.cpp | 1 + Source/Engine/Graphics/Textures/GPUTexture.h | 5 +++++ Source/Engine/Graphics/Textures/StreamingTexture.cpp | 2 ++ Source/Engine/Streaming/StreamableResource.h | 6 ++++++ 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 8ee204e11..4512172f8 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -91,6 +91,7 @@ public: // Update residency level model->_loadedLODs++; + model->ResidencyChanged(); return false; } @@ -697,11 +698,8 @@ bool Model::Init(const Span& meshesCountPerLod) // Setup LODs for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { LODs[lodIndex].Dispose(); - } LODs.Resize(meshesCountPerLod.Length()); - _loadedLODs = meshesCountPerLod.Length(); // Setup meshes for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++) @@ -720,6 +718,10 @@ bool Model::Init(const Span& meshesCountPerLod) } } + // Update resource residency + _loadedLODs = meshesCountPerLod.Length(); + ResidencyChanged(); + return false; } @@ -836,6 +838,7 @@ Task* Model::CreateStreamingTask(int32 residency) for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) LODs[i].Unload(); _loadedLODs = residency; + ResidencyChanged(); } return result; @@ -945,7 +948,7 @@ Asset::LoadResult Model::load() sdfStream.Read(&data); if (!SDF.Texture) SDF.Texture = GPUTexture::New(); - if (SDF.Texture->Init(GPUTextureDescription::New3D(data.Width, data.Height, data.Depth, data.Format, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, data.MipLevels))) + if (SDF.Texture->Init(GPUTextureDescription::New3D(data.Width, data.Height, data.Depth, data.Format, GPUTextureFlags::ShaderResource, data.MipLevels))) return LoadResult::Failed; SDF.LocalToUVWMul = data.LocalToUVWMul; SDF.LocalToUVWAdd = data.LocalToUVWAdd; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index e3e1339a1..f0cff64aa 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -85,6 +85,7 @@ protected: // Update residency level model->_loadedLODs++; + model->ResidencyChanged(); return false; } @@ -676,11 +677,8 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) // Setup LODs for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { LODs[lodIndex].Dispose(); - } LODs.Resize(meshesCountPerLod.Length()); - _loadedLODs = meshesCountPerLod.Length(); // Setup meshes for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++) @@ -699,6 +697,10 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) } } + // Update resource residency + _loadedLODs = meshesCountPerLod.Length(); + ResidencyChanged(); + return false; } @@ -827,6 +829,7 @@ Task* SkinnedModel::CreateStreamingTask(int32 residency) for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) LODs[i].Unload(); _loadedLODs = residency; + ResidencyChanged(); } return result; diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index bc5424223..69131efa7 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -814,4 +814,5 @@ void GPUTexture::SetResidentMipLevels(int32 count) return; _residentMipLevels = count; OnResidentMipsChanged(); + ResidentMipsChanged(this); } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index a51d4418b..3e6893008 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -568,6 +568,11 @@ public: /// API_PROPERTY() void SetResidentMipLevels(int32 count); + /// + /// Event called when texture residency gets changed. Texture Mip gets loaded into GPU memory and is ready to use. + /// + Delegate ResidentMipsChanged; + protected: virtual bool OnInit() = 0; diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 0290048fc..96a023c3b 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -233,6 +233,7 @@ protected: { Swap(_streamingTexture->_texture, _newTexture); _streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount); + _streamingTexture->ResidencyChanged(); SAFE_DELETE_GPU_RESOURCE(_newTexture); // Base @@ -447,6 +448,7 @@ Task* StreamingTexture::CreateStreamingTask(int32 residency) { // Do the quick data release _texture->ReleaseGPU(); + ResidencyChanged(); } else { diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index 233663a66..b63a65356 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Delegate.h" #include "Engine/Core/Collections/SamplesBuffer.h" class StreamingGroup; @@ -111,6 +112,11 @@ public: }; StreamingCache Streaming; + + /// + /// Event called when current resource residency gets changed (eg. model LOD or texture MIP gets loaded). Usually called from async thread. + /// + Action ResidencyChanged; /// /// Requests the streaming update for this resource during next streaming manager update. From b3d18f3b0e4722f1deb894bf67191d439fa32d4e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:39:27 +0200 Subject: [PATCH 114/144] Improve `StaticModel` to register for Scene Rendering once the model has any LOD streamed-in --- Source/Engine/Level/Actors/StaticModel.cpp | 59 ++++++++++++++++++-- Source/Engine/Level/Actors/StaticModel.h | 4 ++ Source/Engine/Level/Scene/SceneRendering.cpp | 6 ++ Source/Engine/Level/Scene/SceneRendering.h | 2 + 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index a32512b1b..41cb38515 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -163,20 +163,46 @@ void StaticModel::RemoveVertexColors() void StaticModel::OnModelChanged() { + if (_residencyChangedModel) + { + _residencyChangedModel = nullptr; + Model->ResidencyChanged.Unbind(this); + } RemoveVertexColors(); Entries.Release(); - if (Model && !Model->IsLoaded()) - { UpdateBounds(); - } + else if (!Model && _sceneRenderingKey != -1) + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } void StaticModel::OnModelLoaded() { Entries.SetupIfInvalid(Model); - UpdateBounds(); + if (_sceneRenderingKey == -1 && _scene) + { + // Register for rendering but once the model has any LOD loaded + if (Model->GetLoadedLODs() == 0) + { + _residencyChangedModel = Model; + Model->ResidencyChanged.Bind(this); + } + else + { + _sceneRenderingKey = GetSceneRendering()->AddActor(this); + } + } +} + +void StaticModel::OnModelResidencyChanged() +{ + if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0) + { + _sceneRenderingKey = GetSceneRendering()->AddActor(this); + _residencyChangedModel = nullptr; + Model->ResidencyChanged.Unbind(this); + } } void StaticModel::UpdateBounds() @@ -488,3 +514,28 @@ void StaticModel::OnTransformChanged() _transform.GetWorld(_world); UpdateBounds(); } + +void StaticModel::OnEnable() +{ + if (_scene && Model && Model->IsLoaded() && Model->GetLoadedLODs() > 0 && _sceneRenderingKey == -1) + _sceneRenderingKey = GetSceneRendering()->AddActor(this); + + // Skip ModelInstanceActor (add to SceneRendering manually) + Actor::OnEnable(); +} + +void StaticModel::OnDisable() +{ + // Skip ModelInstanceActor (add to SceneRendering manually) + Actor::OnDisable(); + + if (_sceneRenderingKey != -1) + { + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); + } + else if (_residencyChangedModel) + { + _residencyChangedModel = nullptr; + Model->ResidencyChanged.Unbind(this); + } +} diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 60f0355bf..db0ece1a6 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -25,6 +25,7 @@ private: byte _vertexColorsCount; Array _vertexColorsData[MODEL_MAX_LODS]; GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS]; + Model* _residencyChangedModel = nullptr; public: @@ -182,6 +183,7 @@ private: void OnModelChanged(); void OnModelLoaded(); + void OnModelResidencyChanged(); void UpdateBounds(); public: @@ -199,4 +201,6 @@ protected: // [ModelInstanceActor] void OnTransformChanged() override; + void OnEnable() override; + void OnDisable() override; }; diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index c29db6bee..3090fd798 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -6,6 +6,7 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" +#include "Engine/Threading/Threading.h" #if SCENE_RENDERING_USE_PROFILER #include "Engine/Profiler/ProfilerCPU.h" #endif @@ -29,6 +30,7 @@ void ISceneRenderingListener::ListenSceneRendering(SceneRendering* scene) void SceneRendering::Draw(RenderContext& renderContext) { + ScopeLock lock(Locker); auto& view = renderContext.View; const BoundingFrustum frustum = view.CullingFrustum; renderContext.List->Scenes.Add(this); @@ -90,6 +92,7 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { + ScopeLock lock(Locker); for (auto* listener : _listeners) { listener->OnSceneRenderingClear(this); @@ -104,6 +107,7 @@ void SceneRendering::Clear() int32 SceneRendering::AddActor(Actor* a) { + ScopeLock lock(Locker); int32 key = 0; // TODO: track removedCount and skip searching for free entry if there is none for (; key < Actors.Count(); key++) @@ -125,6 +129,7 @@ int32 SceneRendering::AddActor(Actor* a) void SceneRendering::UpdateActor(Actor* a, int32 key) { + ScopeLock lock(Locker); if (Actors.IsEmpty()) return; auto& e = Actors[key]; @@ -137,6 +142,7 @@ void SceneRendering::UpdateActor(Actor* a, int32 key) void SceneRendering::RemoveActor(Actor* a, int32& key) { + ScopeLock lock(Locker); if (Actors.HasItems()) { auto& e = Actors[key]; diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 160a89510..9aed3e3b7 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -6,6 +6,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Level/Actor.h" +#include "Engine/Platform/CriticalSection.h" class SceneRenderTask; class SceneRendering; @@ -75,6 +76,7 @@ public: Array Actors; Array PostFxProviders; + CriticalSection Locker; private: #if USE_EDITOR From ca935f4f08e53bb8ecbf5008f8408f751ce4eb4e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 12:40:23 +0200 Subject: [PATCH 115/144] Improve Global SDF static chunks cache to track SDF textures streaming --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 32 +++++++++++++++++++ .../Renderer/GlobalSurfaceAtlasPass.cpp | 1 + 2 files changed, 33 insertions(+) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index c4df8e611..f53e10511 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -164,8 +164,32 @@ class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public: CascadeData Cascades[4]; HashSet ObjectTypes; + HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; + void OnSDFTextureDeleted(ScriptingObject* object) + { + auto* texture = (GPUTexture*)object; + if (SDFTextures.Remove(texture)) + { + texture->Deleted.Unbind(this); + texture->ResidentMipsChanged.Unbind(this); + } + } + + void OnSDFTextureResidentMipsChanged(GPUTexture* texture) + { + // Stop tracking texture streaming once it gets fully loaded + if (texture->ResidentMipLevels() == texture->MipLevels()) + { + OnSDFTextureDeleted(texture); + + // Clear static chunks cache + for (auto& cascade : Cascades) + cascade.StaticChunks.Clear(); + } + } + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) { for (auto& cascade : Cascades) @@ -763,4 +787,12 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas } } } + + // Track streaming for SDF textures used in static chunks to invalidate cache + if (!dynamic && sdf.Texture->ResidentMipLevels() != sdf.Texture->MipLevels() && !_sdfData->SDFTextures.Contains(sdf.Texture)) + { + sdf.Texture->Deleted.Bind(_sdfData); + sdf.Texture->ResidentMipsChanged.Bind(_sdfData); + _sdfData->SDFTextures.Add(sdf.Texture); + } } diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index b7a8148de..755fb6660 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -371,6 +371,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < minTileResolution, "Invalid tile size configuration."); for (auto* scene : renderContext.List->Scenes) { From 9d205cbb7dc54c5e447171050b05ca6b7cace5a0 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 21 Apr 2022 13:08:28 +0200 Subject: [PATCH 116/144] Fix --- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index f53e10511..e13bf3f61 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -167,6 +167,15 @@ public: HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; + ~GlobalSignDistanceFieldCustomBuffer() + { + for (const auto& e : SDFTextures) + { + e.Item->Deleted.Unbind(this); + e.Item->ResidentMipsChanged.Unbind(this); + } + } + void OnSDFTextureDeleted(ScriptingObject* object) { auto* texture = (GPUTexture*)object; From 77dcc9b7a37d86f6c005eaae9af3c8f01e453458 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 22 Apr 2022 14:24:06 +0200 Subject: [PATCH 117/144] Refactor Global Surface Atlas objects buffer into separate tiles buffer to reduce memory usage and increase cache hit ratio --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 62 ++++++++++++------- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 1 + Source/Shaders/GlobalSurfaceAtlas.hlsl | 48 ++++++++------ Source/Shaders/GlobalSurfaceAtlas.shader | 14 +++-- 5 files changed, 79 insertions(+), 50 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 900052f79..a4cb58313 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7444088469d72ca5bd6f3507726e4ba68a2f550fcd78cfbd5c7a7b2fbee5e982 -size 7113 +oid sha256:adb567f9a7774b558e1cd4ce3e3c69e15b5a1904bb85b2d29527c3ecdc2635db +size 7235 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 755fb6660..5f2fe6dd9 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -18,7 +18,8 @@ #include "Engine/Utilities/RectPack.h" // This must match HLSL -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE 6 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles #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 @@ -55,6 +56,7 @@ struct GlobalSurfaceAtlasTile : RectPack Vector3 ViewPosition; Vector3 ViewBoundsSize; Matrix ViewMatrix; + uint16 TileIndex; GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) : RectPack(x, y, width, height) @@ -118,13 +120,16 @@ public: GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; DynamicTypedBuffer ObjectsBuffer; + DynamicTypedBuffer TilesBuffer; uint32 ObjectIndexCounter; + uint16 TileIndexCounter; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; GlobalSurfaceAtlasCustomBuffer() - : ObjectsBuffer(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")) + : ObjectsBuffer(256 * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")) + , TilesBuffer(256 * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE * 3 / 4, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.TilesBuffer")) { } @@ -133,6 +138,7 @@ public: LastFrameAtlasDefragmentation = Engine::FrameCount; SAFE_DELETE(AtlasTiles); ObjectsBuffer.Clear(); + TilesBuffer.Clear(); Objects.Clear(); } @@ -344,20 +350,28 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ Vector2 minUV(0, 0), maxUV(1, 1); \ auto* quad = _vertexBuffer->WriteReserve(6); \ - quad[0] = { { max }, { maxUV }, (uint16)object.Index, (uint16)tileIndex }; \ - quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ - quad[2] = { { min }, { minUV }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[0] = { { max }, { maxUV }, (uint16)object.Index, tile->TileIndex }; \ + quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, (uint16)object.Index, tile->TileIndex }; \ + quad[2] = { { min }, { minUV }, (uint16)object.Index, tile->TileIndex }; \ quad[3] = quad[2]; \ - quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, (uint16)object.Index, (uint16)tileIndex }; \ + quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, (uint16)object.Index, tile->TileIndex }; \ quad[5] = quad[0] #define VB_DRAW() \ _vertexBuffer->Flush(context); \ auto vb = _vertexBuffer->GetBuffer(); \ context->BindVB(ToSpan(&vb, 1)); \ context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); + // Add objects into the atlas surfaceAtlasData.ObjectsBuffer.Clear(); + surfaceAtlasData.TilesBuffer.Clear(); surfaceAtlasData.ObjectIndexCounter = 0; + { + // Tile at index 0 is invalid + surfaceAtlasData.TileIndexCounter = 1; + auto* tileData = surfaceAtlasData.TilesBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); + Platform::MemoryClear(tileData, sizeof(Vector4) * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); + } _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -468,23 +482,22 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) object->Index = surfaceAtlasData.ObjectIndexCounter++; - auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_SIZE); + auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE); objectData[0] = *(Vector4*)&e.Bounds; objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); objectData[2] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); objectData[3] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); - objectData[4] = Vector4(object->Bounds.Extents, 0.0f); + objectData[4] = Vector4(object->Bounds.Extents, 0.0f); // w unused + objectData[5] = Vector4::Zero; // w unused + auto tileIndices = reinterpret_cast(&objectData[5]); // xyz used for tile indices packed into uint16 // TODO: try to optimize memory footprint (eg. merge scale into extents and use rotation+offset but reconstruct rotation from two axes with sign) for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) { auto* tile = object->Tiles[tileIndex]; - const int32 tileStart = 5 + tileIndex * 5; if (!tile) - { - // Disable tile - objectData[tileStart + 4] = Vector4::Zero; continue; - } + tile->TileIndex = surfaceAtlasData.TileIndexCounter++; + tileIndices[tileIndex] = tile->TileIndex; // Setup view to render object from the side Vector3 xAxis, yAxis, zAxis = Vector3::Zero; @@ -517,11 +530,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // 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; - objectData[tileStart + 0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv; - objectData[tileStart + 1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); - objectData[tileStart + 2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); - objectData[tileStart + 3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); - objectData[tileStart + 4] = Vector4(tile->ViewBoundsSize, 1.0f); + auto* tileData = surfaceAtlasData.TilesBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); + tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv; + tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); + tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); + tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); + tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused } } } @@ -548,6 +562,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co { PROFILE_GPU_CPU("Update Objects"); surfaceAtlasData.ObjectsBuffer.Flush(context); + surfaceAtlasData.TilesBuffer.Flush(context); } // Rasterize world geometry material properties into Global Surface Atlas @@ -670,6 +685,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer(); + result.Tiles = surfaceAtlasData.TilesBuffer.GetBuffer(); result.GlobalSurfaceAtlas.Resolution = (float)resolution; result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; @@ -693,10 +709,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); context->BindSR(4, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View()); + context->BindSR(5, surfaceAtlasData.TilesBuffer.GetBuffer()->View()); for (int32 i = 0; i < 4; i++) { - context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); + context->BindSR(i + 6, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 10, bindingDataSDF.CascadeMips[i]->ViewVolume()); } context->BindCB(0, _cb0); Data0 data; @@ -827,13 +844,14 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); - context->BindSR(9, bindingData.Atlas[0]->View()); + context->BindSR(9, bindingData.Tiles ? bindingData.Tiles->View() : nullptr); + context->BindSR(10, bindingData.Atlas[0]->View()); { //GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light - context->BindSR(10, tex->View()); + context->BindSR(11, tex->View()); } context->SetState(_psDebug); context->SetRenderTarget(output->View()); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 2b225bc46..68a4da65d 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -23,6 +23,7 @@ public: { GPUTexture* Atlas[5]; GPUBuffer* Objects; + GPUBuffer* Tiles; GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 540425a8b..0fbec4531 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,7 +4,8 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE 6 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) @@ -13,7 +14,6 @@ struct GlobalSurfaceTile float4 AtlasRectUV; float4x4 WorldToLocal; float3 ViewBoundsSize; - bool Enabled; }; struct GlobalSurfaceObject @@ -22,24 +22,26 @@ struct GlobalSurfaceObject float BoundsRadius; float4x4 WorldToLocal; float3 Extent; + uint TileIndices[6]; }; float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE; return objects.Load(objectStart); } GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE; float4 vector0 = objects.Load(objectStart + 0); float4 vector1 = objects.Load(objectStart + 1); float4 vector2 = objects.Load(objectStart + 2); float4 vector3 = objects.Load(objectStart + 3); float4 vector4 = objects.Load(objectStart + 4); // w unused + float4 vector5 = objects.Load(objectStart + 5); // w unused GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; @@ -48,19 +50,27 @@ GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint ob object.WorldToLocal[2] = float4(vector3.xyz, 0.0f); object.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); object.Extent = vector4.xyz; + uint vector5x = asuint(vector5.x); + uint vector5y = asuint(vector5.y); + uint vector5z = asuint(vector5.z); + object.TileIndices[0] = vector5x & 0xffff; // Limitation on max 65k active tiles + object.TileIndices[1] = vector5x >> 16; + object.TileIndices[2] = vector5y & 0xffff; + object.TileIndices[3] = vector5y >> 16; + object.TileIndices[4] = vector5z & 0xffff; + object.TileIndices[5] = vector5z >> 16; return object; } -GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint objectIndex, uint tileIndex) +GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint tileIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; - const uint tileStart = objectStart + 5 + tileIndex * 5; + const uint tileStart = tileIndex * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE; float4 vector0 = objects.Load(tileStart + 0); float4 vector1 = objects.Load(tileStart + 1); float4 vector2 = objects.Load(tileStart + 2); float4 vector3 = objects.Load(tileStart + 3); - float4 vector4 = objects.Load(tileStart + 4); + float4 vector4 = objects.Load(tileStart + 4); // w unused GlobalSurfaceTile tile = (GlobalSurfaceTile)0; tile.AtlasRectUV = vector0.xyzw; tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); @@ -68,7 +78,6 @@ GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint object tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f); tile.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); tile.ViewBoundsSize = vector4.xyz; - tile.Enabled = vector4.w > 0; return tile; } @@ -138,7 +147,7 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur } // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). -float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Buffer tiles, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); float surfaceThreshold = 20.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) @@ -157,25 +166,24 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + uint tileIndex = object.TileIndices[localNormal.x > 0.0f ? 0 : 1]; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) { - uint tileIndex = localNormal.x > 0.0f ? 0 : 1; - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + tileIndex = object.TileIndices[localNormal.y > 0.0f ? 2 : 3]; + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) { - uint tileIndex = localNormal.y > 0.0f ? 2 : 3; - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) + tileIndex = object.TileIndices[localNormal.z > 0.0f ? 4 : 5]; + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) { - uint tileIndex = localNormal.z > 0.0f ? 4 : 5; - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index cd9f1cba7..4e19ef772 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -67,8 +67,9 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl // GBuffer+Depth at 0-3 slots Buffer GlobalSurfaceAtlasObjects : register(t4); -Texture3D GlobalSDFTex[4] : register(t5); -Texture3D GlobalSDFMip[4] : register(t9); +Buffer GlobalSurfaceAtlasTiles : register(t5); +Texture3D GlobalSDFTex[4] : register(t6); +Texture3D GlobalSDFMip[4] : register(t10); // Pixel shader for Global Surface Atlas shading with direct light contribution META_PS(true, FEATURE_LEVEL_SM5) @@ -78,7 +79,7 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target { // Load current tile info //GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(GlobalSurfaceAtlasObjects, input.Index.x); - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.Index.x, input.Index.y); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasTiles, input.Index.y); float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; // Load GBuffer sample from atlas @@ -161,8 +162,9 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); Buffer GlobalSurfaceAtlasObjects : register(t8); -Texture2D GlobalSurfaceAtlasDepth : register(t9); -Texture2D GlobalSurfaceAtlasTex : register(t10); +Buffer GlobalSurfaceAtlasTiles : register(t9); +Texture2D GlobalSurfaceAtlasDepth : register(t10); +Texture2D GlobalSurfaceAtlasTex : register(t11); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -185,7 +187,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasTiles, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); return float4(surfaceColor.rgb, 1); } From 42bb4483b3c8d7506151ad7c418de2959453c8e8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 22 Apr 2022 15:15:33 +0200 Subject: [PATCH 118/144] Minro tweaks to comments --- Source/Engine/Core/Math/Vector3.h | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 2c0ddc68d..b98e7c233 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -229,18 +229,16 @@ public: } /// - /// Calculates a vector with values being absolute values of that vector + /// Calculates a vector with values being absolute values of that vector. /// - /// Absolute vector Vector3 GetAbsolute() const { return Vector3(Math::Abs(X), Math::Abs(Y), Math::Abs(Z)); } /// - /// Calculates a vector with values being opposite to values of that vector + /// Calculates a vector with values being opposite to values of that vector. /// - /// Negative vector Vector3 GetNegative() const { return Vector3(-X, -Y, -Z); @@ -249,7 +247,6 @@ public: /// /// Calculates a normalized vector that has length equal to 1. /// - /// The normalized vector. Vector3 GetNormalized() const { const float rcp = 1.0f / Length(); @@ -257,63 +254,56 @@ public: } /// - /// Returns average arithmetic of all the components + /// Returns the average arithmetic of all the components. /// - /// Average arithmetic of all the components float AverageArithmetic() const { return (X + Y + Z) * 0.333333334f; } /// - /// Gets sum of all vector components values + /// Gets the sum of all vector components values. /// - /// Sum of X,Y and Z float SumValues() const { return X + Y + Z; } /// - /// Returns minimum value of all the components + /// Returns the minimum value of all the components. /// - /// Minimum value float MinValue() const { return Math::Min(X, Y, Z); } /// - /// Returns maximum value of all the components + /// Returns the maximum value of all the components. /// - /// Maximum value float MaxValue() const { return Math::Max(X, Y, Z); } /// - /// Returns true if vector has one or more components is not a number (NaN) + /// Returns true if vector has one or more components is not a number (NaN). /// - /// True if one or more components is not a number (NaN) bool IsNaN() const { return isnan(X) || isnan(Y) || isnan(Z); } /// - /// Returns true if vector has one or more components equal to +/- infinity + /// Returns true if vector has one or more components equal to +/- infinity. /// - /// True if one or more components equal to +/- infinity bool IsInfinity() const { return isinf(X) || isinf(Y) || isinf(Z); } /// - /// Returns true if vector has one or more components equal to +/- infinity or NaN + /// Returns true if vector has one or more components equal to +/- infinity or NaN. /// - /// True if one or more components equal to +/- infinity or NaN bool IsNanOrInfinity() const { return IsInfinity() || IsNaN(); From 4524edb899894d61d965dc600b9dfebed39c6f7e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 22 Apr 2022 18:33:22 +0200 Subject: [PATCH 119/144] Fix static model registering for rendering after streaming in even if disabled --- Source/Engine/Foliage/Foliage.cpp | 2 +- Source/Engine/Graphics/RenderTask.cpp | 3 +- Source/Engine/Level/Actors/Camera.cpp | 2 +- Source/Engine/Level/Actors/Decal.cpp | 2 +- .../Engine/Level/Actors/DirectionalLight.cpp | 2 +- .../Engine/Level/Actors/EnvironmentProbe.cpp | 2 +- .../Level/Actors/ExponentialHeightFog.cpp | 2 +- .../Level/Actors/ModelInstanceActor.cpp | 2 +- Source/Engine/Level/Actors/PointLight.cpp | 2 +- Source/Engine/Level/Actors/Sky.cpp | 2 +- Source/Engine/Level/Actors/SkyLight.cpp | 2 +- Source/Engine/Level/Actors/Skybox.cpp | 2 +- Source/Engine/Level/Actors/SpotLight.cpp | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 22 ++++++------ Source/Engine/Level/Scene/SceneRendering.cpp | 34 ++++++++++--------- Source/Engine/Level/Scene/SceneRendering.h | 2 +- Source/Engine/Particles/ParticleEffect.cpp | 2 +- Source/Engine/Terrain/Terrain.cpp | 2 +- Source/Engine/UI/SpriteRender.cpp | 2 +- Source/Engine/UI/TextRender.cpp | 2 +- 20 files changed, 49 insertions(+), 44 deletions(-) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index c784178c6..9a855eba2 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -1229,7 +1229,7 @@ void Foliage::OnLayerChanged() void Foliage::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 5d4b09b9e..62ee24b2b 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -276,7 +276,8 @@ void AddActorToSceneRendering(SceneRendering* s, Actor* a) { if (a && a->IsActiveInHierarchy()) { - s->AddActor(a); + int32 key = -1; + s->AddActor(a, key); for (Actor* child : a->Children) AddActorToSceneRendering(s, child); } diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index 76cb7d667..8ca4d675a 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -354,7 +354,7 @@ void Camera::OnEnable() { Cameras.Add(this); #if USE_EDITOR - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #endif // Base diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index ebf9f7361..20df5e21b 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -122,7 +122,7 @@ bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) void Decal::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index e8bcfbba4..7391691b3 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -68,7 +68,7 @@ bool DirectionalLight::IntersectsItself(const Ray& ray, float& distance, Vector3 void DirectionalLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 6ad0c6091..51bab7cf4 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -202,7 +202,7 @@ bool EnvironmentProbe::IntersectsItself(const Ray& ray, float& distance, Vector3 void EnvironmentProbe::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 5521b888d..712051250 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -205,7 +205,7 @@ void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderCon void ExponentialHeightFog::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index a6470b98a..8cf3a3620 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -39,7 +39,7 @@ void ModelInstanceActor::OnLayerChanged() void ModelInstanceActor::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 9c827a877..04024e480 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -71,7 +71,7 @@ void PointLight::UpdateBounds() void PointLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index c5107ae0c..59d95d3f8 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -239,7 +239,7 @@ void Sky::EndPlay() void Sky::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index a5d4fb0a3..d7bfa4c7b 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -171,7 +171,7 @@ bool SkyLight::HasContentLoaded() const void SkyLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 580145da3..f76beff7e 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -122,7 +122,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M void Skybox::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 8dbea083c..234786e3c 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -119,7 +119,7 @@ void SpotLight::UpdateBounds() void SpotLight::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); #endif diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 41cb38515..58328d223 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -180,28 +180,28 @@ void StaticModel::OnModelLoaded() { Entries.SetupIfInvalid(Model); UpdateBounds(); - if (_sceneRenderingKey == -1 && _scene) + if (_sceneRenderingKey == -1 && _scene && _isActiveInHierarchy && _isEnabled && !_residencyChangedModel) { // Register for rendering but once the model has any LOD loaded if (Model->GetLoadedLODs() == 0) { _residencyChangedModel = Model; - Model->ResidencyChanged.Bind(this); + _residencyChangedModel->ResidencyChanged.Bind(this); } else { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); } } } void StaticModel::OnModelResidencyChanged() { - if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0) + if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0 && _residencyChangedModel) { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); + _residencyChangedModel->ResidencyChanged.Unbind(this); _residencyChangedModel = nullptr; - Model->ResidencyChanged.Unbind(this); } } @@ -517,8 +517,10 @@ void StaticModel::OnTransformChanged() void StaticModel::OnEnable() { - if (_scene && Model && Model->IsLoaded() && Model->GetLoadedLODs() > 0 && _sceneRenderingKey == -1) - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + if (_scene && _sceneRenderingKey == -1 && !_residencyChangedModel && Model && Model->IsLoaded() && Model->GetLoadedLODs() != 0) + { + GetSceneRendering()->AddActor(this, _sceneRenderingKey); + } // Skip ModelInstanceActor (add to SceneRendering manually) Actor::OnEnable(); @@ -533,9 +535,9 @@ void StaticModel::OnDisable() { GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } - else if (_residencyChangedModel) + if (_residencyChangedModel) { + _residencyChangedModel->ResidencyChanged.Unbind(this); _residencyChangedModel = nullptr; - Model->ResidencyChanged.Unbind(this); } } diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 3090fd798..affd8e576 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -105,26 +105,28 @@ void SceneRendering::Clear() #endif } -int32 SceneRendering::AddActor(Actor* a) +void SceneRendering::AddActor(Actor* a, int32& key) { ScopeLock lock(Locker); - int32 key = 0; - // TODO: track removedCount and skip searching for free entry if there is none - for (; key < Actors.Count(); key++) + if (key == -1) { - if (Actors[key].Actor == nullptr) - break; + // TODO: track removedCount and skip searching for free entry if there is none + key = 0; + for (; key < Actors.Count(); key++) + { + if (Actors[key].Actor == nullptr) + break; + } + if (key == Actors.Count()) + Actors.AddOne(); + auto& e = Actors[key]; + e.Actor = a; + e.LayerMask = a->GetLayerMask(); + e.Bounds = a->GetSphere(); + e.NoCulling = a->_drawNoCulling; + for (auto* listener : _listeners) + listener->OnSceneRenderingAddActor(a); } - if (key == Actors.Count()) - Actors.AddOne(); - auto& e = Actors[key]; - e.Actor = a; - e.LayerMask = a->GetLayerMask(); - e.Bounds = a->GetSphere(); - e.NoCulling = a->_drawNoCulling; - for (auto* listener : _listeners) - listener->OnSceneRenderingAddActor(a); - return key; } void SceneRendering::UpdateActor(Actor* a, int32 key) diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 9aed3e3b7..9faa8d6f5 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -107,7 +107,7 @@ public: void Clear(); public: - int32 AddActor(Actor* a); + void AddActor(Actor* a, int32& key); void UpdateActor(Actor* a, int32 key); void RemoveActor(Actor* a, int32& key); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 6275509e5..078690337 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -700,7 +700,7 @@ void ParticleEffect::EndPlay() void ParticleEffect::OnEnable() { GetScene()->Ticking.Update.AddTick(this); - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR GetSceneRendering()->AddViewportIcon(this); GetScene()->Ticking.Update.AddTickExecuteInEditor(this); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 40916a095..baba38123 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -743,7 +743,7 @@ RigidBody* Terrain::GetAttachedRigidBody() const void Terrain::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->AddPhysicsDebug(this); #endif diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index 3b4f9d3b8..4b4402bbc 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -190,7 +190,7 @@ void SpriteRender::OnEndPlay() void SpriteRender::OnEnable() { - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); // Base Actor::OnEnable(); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 93051d500..ccce0530a 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -496,7 +496,7 @@ void TextRender::OnEnable() { UpdateLayout(); } - _sceneRenderingKey = GetSceneRendering()->AddActor(this); + GetSceneRendering()->AddActor(this, _sceneRenderingKey); } void TextRender::OnDisable() From 49aa4abc200a8e085fcb20d10461560c2b3cf92a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 27 Apr 2022 12:47:11 +0200 Subject: [PATCH 120/144] Optimize Global Surface Atlas sampling with 3d-grid culling into chunks --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 249 +++++++++++++----- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 15 +- Source/Shaders/Collisions.hlsl | 7 + Source/Shaders/GlobalSurfaceAtlas.hlsl | 128 +++++---- Source/Shaders/GlobalSurfaceAtlas.shader | 106 +++++++- 6 files changed, 377 insertions(+), 132 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index a4cb58313..abfd4b5ae 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adb567f9a7774b558e1cd4ce3e3c69e15b5a1904bb85b2d29527c3ecdc2635db -size 7235 +oid sha256:1ea0db65392ada8819237b2f109b8549cf372e1ab96e17073b0dd24638cf8eb7 +size 10523 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 5f2fe6dd9..dde8878b7 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -18,14 +18,17 @@ #include "Engine/Utilities/RectPack.h" // This must match HLSL -#define GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE 6 // Amount of float4s per-object -#define GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE 5 // Amount of float4s per-tile +#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling +#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 +#define GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE 6 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles #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 -#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS || GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS #include "Engine/Debug/DebugDraw.h" #endif @@ -33,7 +36,8 @@ PACK_STRUCT(struct Data0 { Vector3 ViewWorldPos; float ViewNearPlane; - Vector2 Padding00; + float Padding00; + uint32 CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; Vector4 ViewFrustumWorldRays[4]; @@ -46,8 +50,7 @@ PACK_STRUCT(struct AtlasTileVertex { Half2 Position; Half2 TileUV; - uint16 ObjectIndex; - uint16 TileIndex; + uint32 TileAddress; }); struct GlobalSurfaceAtlasTile : RectPack @@ -56,7 +59,8 @@ struct GlobalSurfaceAtlasTile : RectPack Vector3 ViewPosition; Vector3 ViewBoundsSize; Matrix ViewMatrix; - uint16 TileIndex; + uint32 Address; + uint32 ObjectAddressOffset; GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) : RectPack(x, y, width, height) @@ -75,7 +79,6 @@ struct GlobalSurfaceAtlasObject uint64 LastFrameUsed; uint64 LastFrameDirty; GlobalSurfaceAtlasTile* Tiles[6]; - uint32 Index; float Radius; OrientedBoundingBox Bounds; @@ -119,26 +122,18 @@ public: GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; - DynamicTypedBuffer ObjectsBuffer; - DynamicTypedBuffer TilesBuffer; - uint32 ObjectIndexCounter; - uint16 TileIndexCounter; + GPUBuffer* ChunksBuffer = nullptr; + GPUBuffer* CulledObjectsBuffer = nullptr; + int32 CulledObjectsCounterIndex = -1; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; - GlobalSurfaceAtlasCustomBuffer() - : ObjectsBuffer(256 * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")) - , TilesBuffer(256 * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE * 3 / 4, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.TilesBuffer")) - { - } - FORCE_INLINE void ClearObjects() { + CulledObjectsCounterIndex = -1; LastFrameAtlasDefragmentation = Engine::FrameCount; SAFE_DELETE(AtlasTiles); - ObjectsBuffer.Clear(); - TilesBuffer.Clear(); Objects.Clear(); } @@ -155,6 +150,8 @@ public: ~GlobalSurfaceAtlasCustomBuffer() { + SAFE_DELETE_GPU_RESOURCE(ChunksBuffer); + SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); Clear(); } }; @@ -200,6 +197,7 @@ bool GlobalSurfaceAtlasPass::setupResources() _cb0 = shader->GetCB(0); if (!_cb0) return true; + _csCullObjects = shader->GetCS("CS_CullObjects"); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; @@ -260,6 +258,8 @@ void GlobalSurfaceAtlasPass::Dispose() // Cleanup SAFE_DELETE(_vertexBuffer); + SAFE_DELETE(_objectsBuffer); + SAFE_DELETE_GPU_RESOURCE(_culledObjectsSizeBuffer); SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); @@ -317,6 +317,13 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); #undef INIT_ATLAS_TEXTURE surfaceAtlasData.Resolution = resolution; + if (!surfaceAtlasData.ChunksBuffer) + { + surfaceAtlasData.ChunksBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.ChunksBuffer")); + if (surfaceAtlasData.ChunksBuffer->Init(GPUBufferDescription::Raw(sizeof(uint32) * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess))) + return true; + memUsage += surfaceAtlasData.ChunksBuffer->GetMemoryUsage(); + } LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); } else @@ -333,6 +340,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); + if (!_objectsBuffer) + _objectsBuffer = New(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 Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); const Vector2 posToClipAdd(-1.0f, 1.0f); #define VB_WRITE_TILE_POS_ONLY(tile) \ @@ -350,11 +361,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co Half2 min(minPos * posToClipMul + posToClipAdd), max(maxPos * posToClipMul + posToClipAdd); \ Vector2 minUV(0, 0), maxUV(1, 1); \ auto* quad = _vertexBuffer->WriteReserve(6); \ - quad[0] = { { max }, { maxUV }, (uint16)object.Index, tile->TileIndex }; \ - quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, (uint16)object.Index, tile->TileIndex }; \ - quad[2] = { { min }, { minUV }, (uint16)object.Index, tile->TileIndex }; \ + quad[0] = { { max }, { maxUV }, tile->Address }; \ + quad[1] = { { min.X, max.Y }, { minUV.X, maxUV.Y }, tile->Address }; \ + quad[2] = { { min }, { minUV }, tile->Address }; \ quad[3] = quad[2]; \ - quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, (uint16)object.Index, tile->TileIndex }; \ + quad[4] = { { max.X, min.Y }, { maxUV.X, minUV.Y }, tile->Address }; \ quad[5] = quad[0] #define VB_DRAW() \ _vertexBuffer->Flush(context); \ @@ -363,15 +374,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); // Add objects into the atlas - surfaceAtlasData.ObjectsBuffer.Clear(); - surfaceAtlasData.TilesBuffer.Clear(); - surfaceAtlasData.ObjectIndexCounter = 0; - { - // Tile at index 0 is invalid - surfaceAtlasData.TileIndexCounter = 1; - auto* tileData = surfaceAtlasData.TilesBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); - Platform::MemoryClear(tileData, sizeof(Vector4) * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); - } + _objectsBuffer->Clear(); _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -480,24 +483,26 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Write to objects buffer (this must match unpacking logic in HLSL) Matrix worldToLocalBounds; Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); - // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) - object->Index = surfaceAtlasData.ObjectIndexCounter++; - auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE); + uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4); + auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE); objectData[0] = *(Vector4*)&e.Bounds; - objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); - objectData[2] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); - objectData[3] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); - objectData[4] = Vector4(object->Bounds.Extents, 0.0f); // w unused - objectData[5] = Vector4::Zero; // w unused - auto tileIndices = reinterpret_cast(&objectData[5]); // xyz used for tile indices packed into uint16 - // TODO: try to optimize memory footprint (eg. merge scale into extents and use rotation+offset but reconstruct rotation from two axes with sign) + objectData[1] = Vector4::Zero; // w unused + objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); + objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); + objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); + objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused + auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16 + auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles) + *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE; for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) { auto* tile = object->Tiles[tileIndex]; if (!tile) continue; - tile->TileIndex = surfaceAtlasData.TileIndexCounter++; - tileIndices[tileIndex] = tile->TileIndex; + tile->ObjectAddressOffset = *objectDataSize; + tile->Address = objectAddress + tile->ObjectAddressOffset; + tileOffsets[tileIndex] = tile->ObjectAddressOffset; + *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE; // Setup view to render object from the side Vector3 xAxis, yAxis, zAxis = Vector3::Zero; @@ -530,7 +535,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // 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 = surfaceAtlasData.TilesBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE); + auto* tileData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE); tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv; tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); @@ -558,13 +563,6 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } } - // Send objects data to the GPU - { - PROFILE_GPU_CPU("Update Objects"); - surfaceAtlasData.ObjectsBuffer.Flush(context); - surfaceAtlasData.TilesBuffer.Flush(context); - } - // Rasterize world geometry material properties into Global Surface Atlas if (_dirtyObjectsBuffer.Count() != 0) { @@ -678,16 +676,142 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co RenderList::ReturnToPool(renderContextTiles.List); } + // Send objects data to the GPU + { + PROFILE_GPU_CPU("Update Objects"); + _objectsBuffer->Flush(context); + } + + // Init constants + result.GlobalSurfaceAtlas.ViewPos = renderContext.View.Position; + result.GlobalSurfaceAtlas.Resolution = (float)resolution; + result.GlobalSurfaceAtlas.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; + result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); + + // Cull objects into chunks (for faster Atlas sampling) + if (surfaceAtlasData.Objects.Count() != 0) + { + // 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). + PROFILE_GPU_CPU("Cull Objects"); + uint32 objectsBufferCapacity = (uint32)((float)_objectsBuffer->Data.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) + { + if (!_culledObjectsSizeBuffer) + { + Platform::MemoryClear(_culledObjectsSizeFrames, sizeof(_culledObjectsSizeFrames)); + _culledObjectsSizeBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.CulledObjectsSizeBuffer")); + const GPUBufferDescription desc = GPUBufferDescription::Buffer(ARRAY_COUNT(_culledObjectsSizeFrames) * sizeof(uint32), GPUBufferFlags::None, PixelFormat::R32_UInt, _culledObjectsSizeFrames, sizeof(uint32), GPUResourceUsage::StagingReadback); + if (_culledObjectsSizeBuffer->Init(desc)) + return true; + } + if (surfaceAtlasData.CulledObjectsCounterIndex != -1) + { + // Get the last counter value (accept staging readback delay) + auto data = (uint32*)_culledObjectsSizeBuffer->Map(GPUResourceMapMode::Read); + uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; + _culledObjectsSizeBuffer->Unmap(); + if (counter > 0) + objectsBufferCapacity = counter * sizeof(Vector4); + } + if (surfaceAtlasData.CulledObjectsCounterIndex == -1) + { + // Find a free timer slot + for (int32 i = 0; i < ARRAY_COUNT(_culledObjectsSizeFrames); i++) + { + if (currentFrame - _culledObjectsSizeFrames[i] > GPU_ASYNC_LATENCY) + { + surfaceAtlasData.CulledObjectsCounterIndex = i; + break; + } + } + } + if (surfaceAtlasData.CulledObjectsCounterIndex != -1) + { + // Copy current counter value + _culledObjectsSizeFrames[surfaceAtlasData.CulledObjectsCounterIndex] = currentFrame; + context->CopyBuffer(_culledObjectsSizeBuffer, surfaceAtlasData.ChunksBuffer, sizeof(uint32), surfaceAtlasData.CulledObjectsCounterIndex * sizeof(uint32), 0); + } + } + + // Allocate buffer for culled objects (estimated size) + objectsBufferCapacity = Math::AlignUp(objectsBufferCapacity, 4096u); + 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(Vector4)); + 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); + + // Cull objects into chunks (1 thread per chunk) + Data0 data; + data.ViewWorldPos = renderContext.View.Position; + data.ViewNearPlane = renderContext.View.Near; + data.ViewFarPlane = renderContext.View.Far; + data.CulledObjectsCapacity = objectsBufferCapacity; + data.GlobalSurfaceAtlas = result.GlobalSurfaceAtlas; + context->UpdateCB(_cb0, &data); + 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->BindUA(0, surfaceAtlasData.ChunksBuffer->View()); + context->BindUA(1, surfaceAtlasData.CulledObjectsBuffer->View()); + context->Dispatch(_csCullObjects, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + context->ResetUA(); + +#if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS + // Debug draw tiles that have any objects inside + for (int32 z = 0; z < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; z++) + { + for (int32 y = 0; y < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; y++) + { + for (int32 x = 0; x < GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; x++) + { + Vector3 chunkCoord(x, y, z); + Vector3 chunkMin = result.GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * result.GlobalSurfaceAtlas.ChunkSize; + Vector3 chunkMax = chunkMin + result.GlobalSurfaceAtlas.ChunkSize; + BoundingBox chunkBounds(chunkMin, chunkMax); + if (Vector3::Distance(chunkBounds.GetCenter(), result.GlobalSurfaceAtlas.ViewPos) >= 2000.0f) + continue; + + int32 count = 0; + for (auto& e : surfaceAtlasData.Objects) + { + BoundingSphere objectBounds(e.Value.Bounds.GetCenter(), e.Value.Radius); + if (chunkBounds.Intersects(objectBounds)) + count++; + } + if (count != 0) + { + DebugDraw::DrawText(String::Format(TEXT("{} Objects"), count), chunkBounds.GetCenter(), Color::Green); + DebugDraw::DrawWireBox(chunkBounds, Color::Green); + } + } + } + } +#endif + } + // Copy results result.Atlas[0] = surfaceAtlasData.AtlasDepth; result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; - result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer(); - result.Tiles = surfaceAtlasData.TilesBuffer.GetBuffer(); - result.GlobalSurfaceAtlas.Resolution = (float)resolution; - result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); + result.Chunks = surfaceAtlasData.ChunksBuffer; + result.CulledObjects = surfaceAtlasData.CulledObjectsBuffer; surfaceAtlasData.Result = result; // Render direct lighting into atlas @@ -708,12 +832,11 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View()); context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); - context->BindSR(4, surfaceAtlasData.ObjectsBuffer.GetBuffer()->View()); - context->BindSR(5, surfaceAtlasData.TilesBuffer.GetBuffer()->View()); + context->BindSR(4, _objectsBuffer->GetBuffer()->View()); for (int32 i = 0; i < 4; i++) { - context->BindSR(i + 6, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 10, bindingDataSDF.CascadeMips[i]->ViewVolume()); + context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); } context->BindCB(0, _cb0); Data0 data; @@ -843,8 +966,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } - context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); - context->BindSR(9, bindingData.Tiles ? bindingData.Tiles->View() : nullptr); + context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); + context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.Atlas[0]->View()); { //GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 68a4da65d..a59631c74 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -13,8 +13,11 @@ public: // Constant buffer data for Global Surface Atlas access on a GPU. PACK_STRUCT(struct GlobalSurfaceAtlasData { - Vector2 Padding; + Vector3 ViewPos; + float Padding0; + float Padding1; float Resolution; + float ChunkSize; uint32 ObjectsCount; }); @@ -22,8 +25,8 @@ public: struct BindingData { GPUTexture* Atlas[5]; - GPUBuffer* Objects; - GPUBuffer* Tiles; + GPUBuffer* Chunks; + GPUBuffer* CulledObjects; GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; @@ -35,10 +38,14 @@ private: GPUPipelineState* _psDirectLighting1 = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; + GPUShaderProgramCS* _csCullObjects; - // Rasterization cache + // Cache + class GPUBuffer* _culledObjectsSizeBuffer = nullptr; + class DynamicTypedBuffer* _objectsBuffer = nullptr; class DynamicVertexBuffer* _vertexBuffer = nullptr; Array _dirtyObjectsBuffer; + uint64 _culledObjectsSizeFrames[8]; public: /// diff --git a/Source/Shaders/Collisions.hlsl b/Source/Shaders/Collisions.hlsl index 4ac81b4aa..4a62aebc4 100644 --- a/Source/Shaders/Collisions.hlsl +++ b/Source/Shaders/Collisions.hlsl @@ -35,4 +35,11 @@ float2 LineHitBox(float3 lineStart, float3 lineEnd, float3 boxMin, float3 boxMax return saturate(intersections); } +// Determines whether there is an intersection between a box and a sphere. +bool BoxIntersectsSphere(float3 boxMin, float3 boxMax, float3 sphereCenter, float sphereRadius) +{ + const float3 clampedCenter = clamp(sphereCenter, boxMin, boxMax); + return distance(sphereCenter, clampedCenter) <= sphereRadius; +} + #endif diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 0fbec4531..7e14b4403 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,8 +4,9 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE 6 // Amount of float4s per-object -#define GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE 5 // Amount of float4s per-tile +#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling +#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 +#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) @@ -22,55 +23,60 @@ struct GlobalSurfaceObject float BoundsRadius; float4x4 WorldToLocal; float3 Extent; - uint TileIndices[6]; + uint TileOffsets[6]; + uint DataSize; // count of float4s for object+tiles }; -float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) +float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectAddress) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE; - return objects.Load(objectStart); + return objects.Load(objectAddress + 0); } -GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) +uint LoadGlobalSurfaceAtlasObjectDataSize(Buffer objects, uint objectAddress) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_BUFFER_STRIDE; - float4 vector0 = objects.Load(objectStart + 0); - float4 vector1 = objects.Load(objectStart + 1); - float4 vector2 = objects.Load(objectStart + 2); - float4 vector3 = objects.Load(objectStart + 3); - float4 vector4 = objects.Load(objectStart + 4); // w unused - float4 vector5 = objects.Load(objectStart + 5); // w unused + return asuint(objects.Load(objectAddress + 1).w); +} + +GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectAddress) +{ + // This must match C++ + float4 vector0 = objects.Load(objectAddress + 0); + float4 vector1 = objects.Load(objectAddress + 1); + float4 vector2 = objects.Load(objectAddress + 2); + float4 vector3 = objects.Load(objectAddress + 3); + float4 vector4 = objects.Load(objectAddress + 4); + float4 vector5 = objects.Load(objectAddress + 5); // w unused GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; - object.WorldToLocal[0] = float4(vector1.xyz, 0.0f); - object.WorldToLocal[1] = float4(vector2.xyz, 0.0f); - object.WorldToLocal[2] = float4(vector3.xyz, 0.0f); - object.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); - object.Extent = vector4.xyz; - uint vector5x = asuint(vector5.x); - uint vector5y = asuint(vector5.y); - uint vector5z = asuint(vector5.z); - object.TileIndices[0] = vector5x & 0xffff; // Limitation on max 65k active tiles - object.TileIndices[1] = vector5x >> 16; - object.TileIndices[2] = vector5y & 0xffff; - object.TileIndices[3] = vector5y >> 16; - object.TileIndices[4] = vector5z & 0xffff; - object.TileIndices[5] = vector5z >> 16; + object.WorldToLocal[0] = float4(vector2.xyz, 0.0f); + object.WorldToLocal[1] = float4(vector3.xyz, 0.0f); + object.WorldToLocal[2] = float4(vector4.xyz, 0.0f); + object.WorldToLocal[3] = float4(vector2.w, vector3.w, vector4.w, 1.0f); + object.Extent = vector5.xyz; + uint vector1x = asuint(vector1.x); + uint vector1y = asuint(vector1.y); + uint vector1z = asuint(vector1.z); + object.DataSize = asuint(vector1.w); + object.TileOffsets[0] = vector1x & 0xffff; + object.TileOffsets[1] = vector1x >> 16; + object.TileOffsets[2] = vector1y & 0xffff; + object.TileOffsets[3] = vector1y >> 16; + object.TileOffsets[4] = vector1z & 0xffff; + object.TileOffsets[5] = vector1z >> 16; return object; } -GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint tileIndex) +GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint tileAddress) { // This must match C++ - const uint tileStart = tileIndex * GLOBAL_SURFACE_ATLAS_TILE_BUFFER_STRIDE; - float4 vector0 = objects.Load(tileStart + 0); - float4 vector1 = objects.Load(tileStart + 1); - float4 vector2 = objects.Load(tileStart + 2); - float4 vector3 = objects.Load(tileStart + 3); - float4 vector4 = objects.Load(tileStart + 4); // w unused + float4 vector0 = objects.Load(tileAddress + 0); + float4 vector1 = objects.Load(tileAddress + 1); + float4 vector2 = objects.Load(tileAddress + 2); + float4 vector3 = objects.Load(tileAddress + 3); + float4 vector4 = objects.Load(tileAddress + 4); // w unused GlobalSurfaceTile tile = (GlobalSurfaceTile)0; tile.AtlasRectUV = vector0.xyzw; tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); @@ -84,8 +90,11 @@ GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint tileIn // Global Surface Atlas data for a constant buffer struct GlobalSurfaceAtlasData { - float2 Padding; + float3 ViewPos; + float Padding0; + float Padding1; float Resolution; + float ChunkSize; uint ObjectsCount; }; @@ -147,19 +156,38 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur } // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). -float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Buffer tiles, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBuffer chunks, Buffer culledObjects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); float surfaceThreshold = 20.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) - // TODO: add grid culling to object for faster lookup + + // Snap to the closest chunk to get culled objects + uint3 chunkCoord = (uint3)clamp(floor((worldPosition - data.ViewPos) / data.ChunkSize + (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)), 0, GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION - 1); + uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4; + uint objectsStart = chunks.Load(chunkAddress); + if (objectsStart == 0) + { + // Empty chunk + return result; + } + + // Read objects counter + float4 chunkHeader = culledObjects[objectsStart]; + objectsStart++; + uint objectsCount = asuint(chunkHeader.x); + + // Loop over culled objects inside the chunk LOOP - for (uint objectIndex = 0; objectIndex < data.ObjectsCount; objectIndex++) + for (uint objectIndex = 0; objectIndex < objectsCount; objectIndex++) { // Cull point vs sphere - float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(objects, objectIndex); + uint objectAddress = objectsStart; + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(culledObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(culledObjects, objectAddress); + objectsStart += objectSize; if (distance(objectBounds.xyz, worldPosition) > objectBounds.w) continue; - GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); + GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(culledObjects, objectAddress); float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz; float3 localExtent = object.Extent + surfaceThreshold; if (any(localPosition > localExtent) || any(localPosition < -localExtent)) @@ -168,22 +196,22 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer 0.0f ? 0 : 1]; - if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) + uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) { - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileIndex = object.TileIndices[localNormal.y > 0.0f ? 2 : 3]; - if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) + tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) { - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileIndex = object.TileIndices[localNormal.z > 0.0f ? 4 : 5]; - if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileIndex != 0) + tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) { - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(tiles, tileIndex); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 4e19ef772..0ae63e05b 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -12,7 +12,8 @@ META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; -float2 Padding00; +float Padding00; +uint CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; @@ -25,27 +26,27 @@ struct AtlasVertexIput { float2 Position : POSITION0; float2 TileUV : TEXCOORD0; - uint2 Index : TEXCOORD1; + uint TileAddress : TEXCOORD1; }; struct AtlasVertexOutput { float4 Position : SV_Position; float2 TileUV : TEXCOORD0; - nointerpolation uint2 Index : TEXCOORD1; + nointerpolation uint TileAddress : TEXCOORD1; }; // Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile) META_VS(true, FEATURE_LEVEL_SM5) META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_UINT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) AtlasVertexOutput VS_Atlas(AtlasVertexIput input) { AtlasVertexOutput output; output.Position = float4(input.Position, 1, 1); output.TileUV = input.TileUV; - output.Index = input.Index; + output.TileAddress = input.TileAddress; return output; } @@ -67,9 +68,8 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl // GBuffer+Depth at 0-3 slots Buffer GlobalSurfaceAtlasObjects : register(t4); -Buffer GlobalSurfaceAtlasTiles : register(t5); -Texture3D GlobalSDFTex[4] : register(t6); -Texture3D GlobalSDFMip[4] : register(t10); +Texture3D GlobalSDFTex[4] : register(t5); +Texture3D GlobalSDFMip[4] : register(t9); // Pixel shader for Global Surface Atlas shading with direct light contribution META_PS(true, FEATURE_LEVEL_SM5) @@ -78,8 +78,7 @@ META_PERMUTATION_1(RADIAL_LIGHT=1) float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target { // Load current tile info - //GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(GlobalSurfaceAtlasObjects, input.Index.x); - GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasTiles, input.Index.y); + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.TileAddress); float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy; // Load GBuffer sample from atlas @@ -157,12 +156,93 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target #endif +#if defined(_CS_CullObjects) + +#include "./Flax/Collisions.hlsl" + +RWByteAddressBuffer RWGlobalSurfaceAtlasChunks : register(u0); +RWBuffer RWGlobalSurfaceAtlasCulledObjects : register(u1); +Buffer GlobalSurfaceAtlasObjects : register(t0); + +// Compute shader for culling objects into chunks +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE)] +void CS_CullObjects(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 chunkCoord = DispatchThreadId; + uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4; + if (chunkAddress == 0) + return; // Skip chunk at 0,0,0 (used for counter) + float3 chunkMin = GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * GlobalSurfaceAtlas.ChunkSize; + float3 chunkMax = chunkMin + GlobalSurfaceAtlas.ChunkSize; + + // Count objects data size in this chunk (amount of float4s) + uint objectsSize = 0, objectAddress = 0, objectsCount = 0; + // TODO: maybe cache 20-30 culled object indices in thread memory to skip culling them again when copying data (maybe reude chunk size to get smaller objects count per chunk)? + LOOP + for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++) + { + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress); + if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w)) + { + objectsSize += objectSize; + objectsCount++; + } + objectAddress += objectSize; + } + if (objectsSize == 0) + { + // Empty chunk + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0); + return; + } + objectsSize++; // Include objects count before actual objects data + + // Allocate object data size in the buffer + uint objectsStart; + RWGlobalSurfaceAtlasChunks.InterlockedAdd(0, objectsSize, objectsStart); + if (objectsStart + objectsSize > CulledObjectsCapacity) + { + // Not enough space in the buffer + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0); + return; + } + + // Write object data start + RWGlobalSurfaceAtlasChunks.Store(chunkAddress, objectsStart); + + // Write objects count before actual objects data + RWGlobalSurfaceAtlasCulledObjects[objectsStart] = float4(asfloat(objectsCount), 0, 0, 0); + objectsStart++; + + // Copy objects data in this chunk + objectAddress = 0; + LOOP + for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++) + { + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress); + uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress); + if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w)) + { + for (uint i = 0; i < objectSize; i++) + { + RWGlobalSurfaceAtlasCulledObjects[objectsStart + i] = GlobalSurfaceAtlasObjects[objectAddress + i]; + } + objectsStart += objectSize; + } + objectAddress += objectSize; + } +} + +#endif + #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); -Buffer GlobalSurfaceAtlasObjects : register(t8); -Buffer GlobalSurfaceAtlasTiles : register(t9); +ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); +Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); @@ -187,7 +267,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasTiles, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); return float4(surfaceColor.rgb, 1); } From 27a1dc8966b7e109293ceba277d37d32086e4043 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 27 Apr 2022 14:12:27 +0200 Subject: [PATCH 121/144] Improve property names displaying in UI and add Unit Test for it --- Source/Editor/Content/Proxy/JsonAssetProxy.cs | 2 +- Source/Editor/Content/Proxy/SettingsProxy.cs | 2 +- .../Editor/CustomEditors/CustomEditorsUtil.cs | 48 --------------- .../CustomEditors/Dedicated/ActorEditor.cs | 6 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 2 +- .../CustomEditors/Editors/GenericEditor.cs | 2 +- Source/Editor/GUI/EnumComboBox.cs | 2 +- .../Editor/GUI/Timeline/Tracks/ActorTrack.cs | 2 +- Source/Editor/Surface/AttributesEditor.cs | 2 +- Source/Editor/Surface/NodeElementArchetype.cs | 2 +- Source/Editor/Utilities/Utils.cs | 59 +++++++++++++++++++ Source/Editor/Windows/GameCookerWindow.cs | 2 +- Source/Editor/Windows/ToolboxWindow.cs | 2 +- Source/Engine/Core/Config/GameSettings.cs | 2 +- .../FlaxEngine.Tests/FlaxEngine.Tests.csproj | 1 + .../FlaxEngine.Tests/TestPropertyNameUI.cs | 30 ++++++++++ 17 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 15d5e89f1..3f2863f74 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -163,7 +163,7 @@ namespace FlaxEditor.Content public sealed class SpawnableJsonAssetProxy : JsonAssetProxy where T : new() { /// - public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name); + public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(typeof(T).Name); /// public override bool CanCreate(ContentFolder targetLocation) diff --git a/Source/Editor/Content/Proxy/SettingsProxy.cs b/Source/Editor/Content/Proxy/SettingsProxy.cs index 6b1b5a6cb..0bbd2830f 100644 --- a/Source/Editor/Content/Proxy/SettingsProxy.cs +++ b/Source/Editor/Content/Proxy/SettingsProxy.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Content /// public override string Name => "Settings"; - //public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(_type.Name); + //public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(_type.Name); /// public override bool CanCreate(ContentFolder targetLocation) diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 27e9896ee..211e69d3a 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.Scripting; using FlaxEngine; @@ -13,8 +12,6 @@ namespace FlaxEditor.CustomEditors { internal static class CustomEditorsUtil { - private static readonly StringBuilder CachedSb = new StringBuilder(256); - internal static readonly Dictionary InBuildTypeNames = new Dictionary() { { typeof(bool), "bool" }, @@ -46,51 +43,6 @@ namespace FlaxEditor.CustomEditors return result; } - /// - /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. - /// - /// The name. - /// The result. - public static string GetPropertyNameUI(string name) - { - int length = name.Length; - StringBuilder sb = CachedSb; - sb.Clear(); - sb.EnsureCapacity(length + 8); - int startIndex = 0; - - // Skip some prefixes - if (name.StartsWith("g_") || name.StartsWith("m_")) - startIndex = 2; - - // Filter text - for (int i = startIndex; i < length; i++) - { - var c = name[i]; - - // Space before word starting with uppercase letter - if (char.IsUpper(c) && i > 0) - { - if (i + 1 < length && !char.IsUpper(name[i + 1])) - sb.Append(' '); - } - // Space instead of underscore - else if (c == '_') - { - if (sb.Length > 0) - sb.Append(' '); - continue; - } - // Space before digits sequence - else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1])) - sb.Append(' '); - - sb.Append(c); - } - - return sb.ToString(); - } - internal static CustomEditor CreateEditor(ValueContainer values, CustomEditor overrideEditor, bool canUseRefPicker = true) { // Check if use provided editor diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index 4a46c32c7..49cc67868 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -209,13 +209,13 @@ namespace FlaxEditor.CustomEditors.Dedicated if (editor is RemovedScriptDummy removed) { node.TextColor = Color.OrangeRed; - node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name); + node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } // Actor or Script else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; - node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name); + node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name); } // Array Item else if (editor.ParentEditor?.Values?.Type.IsArray ?? false) @@ -225,7 +225,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Common type else if (editor.Values.Info != ScriptMemberInfo.Null) { - node.Text = CustomEditorsUtil.GetPropertyNameUI(editor.Values.Info.Name); + node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name); } // Custom type else if (editor.Values[0] != null) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index a85770fbb..c165824eb 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -611,7 +611,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var editor = CustomEditorsUtil.CreateEditor(scriptType, false); // Create group - var title = CustomEditorsUtil.GetPropertyNameUI(scriptType.Name); + var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 55473d42c..60915ff8e 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -32,7 +32,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_presets != value) { _presets = value; - TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString()); + TooltipText = Utilities.Utils.GetPropertyNameUI(_presets.ToString()); } } } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 79ae1dd94..a69ae7ba4 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.CustomEditors.Editors ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null; IsReadOnly |= !info.HasSet; - DisplayName = Display?.Name ?? CustomEditorsUtil.GetPropertyNameUI(info.Name); + DisplayName = Display?.Name ?? Utilities.Utils.GetPropertyNameUI(info.Name); var editor = Editor.Instance; TooltipText = editor.CodeDocs.GetTooltip(info, attributes); _membersOrder = editor.Options.Options.General.ScriptMembersOrder; diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs index 27563bd7a..24f818d0d 100644 --- a/Source/Editor/GUI/EnumComboBox.cs +++ b/Source/Editor/GUI/EnumComboBox.cs @@ -261,7 +261,7 @@ namespace FlaxEditor.GUI switch (formatMode) { case EnumDisplayAttribute.FormatMode.Default: - name = CustomEditorsUtil.GetPropertyNameUI(field.Name); + name = Utilities.Utils.GetPropertyNameUI(field.Name); break; case EnumDisplayAttribute.FormatMode.None: name = field.Name; diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 8758c7622..9a7f4dfed 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -177,7 +177,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (SubTracks.Any(x => x is IObjectTrack y && y.Object == script)) continue; - var name = CustomEditorsUtil.GetPropertyNameUI(script.GetType().Name); + var name = Utilities.Utils.GetPropertyNameUI(script.GetType().Name); menu.AddButton(name, OnAddScriptTrack).Tag = script; } } diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 74bfe2e25..6de6d60da 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Surface for (int i = 0; i < options.Count; i++) { var type = options[i]; - _options[i] = new OptionType(CustomEditorsUtil.GetPropertyNameUI(type.Name), type, Creator); + _options[i] = new OptionType(Utilities.Utils.GetPropertyNameUI(type.Name), type, Creator); } base.Initialize(layout); diff --git a/Source/Editor/Surface/NodeElementArchetype.cs b/Source/Editor/Surface/NodeElementArchetype.cs index d5a8ea122..d365668ea 100644 --- a/Source/Editor/Surface/NodeElementArchetype.cs +++ b/Source/Editor/Surface/NodeElementArchetype.cs @@ -457,7 +457,7 @@ namespace FlaxEditor.Surface if (field.Name.Equals("value__")) continue; - var name = CustomEditorsUtil.GetPropertyNameUI(field.Name); + var name = Utilities.Utils.GetPropertyNameUI(field.Name); values.Add(name); } return ComboBox(x, y, width, valueIndex, values.ToArray()); diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 9ec08aacc..183442542 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; @@ -27,6 +28,8 @@ namespace FlaxEditor.Utilities /// public static class Utils { + private static readonly StringBuilder CachedSb = new StringBuilder(256); + private static readonly string[] MemorySizePostfixes = { "B", @@ -862,6 +865,62 @@ namespace FlaxEditor.Utilities } } + /// + /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. + /// + /// The name. + /// The result. + public static string GetPropertyNameUI(string name) + { + int length = name.Length; + StringBuilder sb = CachedSb; + sb.Clear(); + sb.EnsureCapacity(length + 8); + int startIndex = 0; + + // Skip some prefixes + if (name.StartsWith("g_") || name.StartsWith("m_")) + startIndex = 2; + + // Filter text + var lastChar = '\0'; + for (int i = startIndex; i < length; i++) + { + var c = name[i]; + + // Space before word starting with uppercase letter + if (char.IsUpper(c) && i > 0) + { + if (lastChar != ' ' && (!char.IsUpper(name[i - 1]) || (i + 1 != length && !char.IsUpper(name[i + 1])))) + { + lastChar = ' '; + sb.Append(lastChar); + } + } + // Space instead of underscore + else if (c == '_') + { + if (sb.Length > 0 && lastChar != ' ') + { + lastChar = ' '; + sb.Append(lastChar); + } + continue; + } + // Space before digits sequence + else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1]) && lastChar != ' ') + { + lastChar = ' '; + sb.Append(lastChar); + } + + lastChar = c; + sb.Append(lastChar); + } + + return sb.ToString(); + } + /// /// Creates the search popup with a tree. /// diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 01cd8efae..74ca3ea9b 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -278,7 +278,7 @@ namespace FlaxEditor.Windows name = "Mac"; break; default: - name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString()); + name = Utilities.Utils.GetPropertyNameUI(_platform.ToString()); break; } var group = layout.Group(name); diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 0d47a92b3..2925df386 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -223,7 +223,7 @@ namespace FlaxEditor.Windows if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges)) continue; - var item = _groupSearch.AddChild(CreateActorItem(CustomEditors.CustomEditorsUtil.GetPropertyNameUI(text), actorType)); + var item = _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType)); var highlights = new List(ranges.Length); var style = Style.Current; diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index a8519fe4c..7e70f29bd 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -334,7 +334,7 @@ namespace FlaxEditor.Content.Settings } // Create new settings asset and link it to the game settings - var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json"); + var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", Utilities.Utils.GetPropertyNameUI(typeof(T).Name) + ".json"); if (Editor.SaveJsonAsset(path, obj)) return true; asset = FlaxEngine.Content.LoadAsync(path); diff --git a/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj b/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj index 9ee085dc4..89e433e28 100644 --- a/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj +++ b/Source/Tools/FlaxEngine.Tests/FlaxEngine.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs new file mode 100644 index 000000000..0b7644190 --- /dev/null +++ b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using FlaxEditor.Utilities; +using NUnit.Framework; + +namespace FlaxEditor.Tests +{ + [TestFixture] + public class TestPropertyNameUI + { + [Test] + public void TestFormatting() + { + Assert.AreEqual("property", Utils.GetPropertyNameUI("property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("_property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("m_property")); + Assert.AreEqual("property", Utils.GetPropertyNameUI("g_property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("Property")); + Assert.AreEqual("Property 1", Utils.GetPropertyNameUI("Property1")); + Assert.AreEqual("Property Name", Utils.GetPropertyNameUI("PropertyName")); + Assert.AreEqual("Property Name", Utils.GetPropertyNameUI("Property_Name")); + Assert.AreEqual("Property 123", Utils.GetPropertyNameUI("Property_123")); + Assert.AreEqual("Property TMP", Utils.GetPropertyNameUI("Property_TMP")); + Assert.AreEqual("Property TMP", Utils.GetPropertyNameUI("PropertyTMP")); + Assert.AreEqual("Property T", Utils.GetPropertyNameUI("PropertyT")); + Assert.AreEqual("Property TMP Word", Utils.GetPropertyNameUI("PropertyTMPWord")); + Assert.AreEqual("Generate SDF On Model Import", Utils.GetPropertyNameUI("GenerateSDFOnModelImport")); + } + } +} From 37a49c0d2e0cbc44bc423938967c88d1fc4120f4 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 27 Apr 2022 14:25:48 +0200 Subject: [PATCH 122/144] Reduce artifacts on Global Surface Atlas sampling due to SDF precision loss on far cascades --- Source/Shaders/GlobalSurfaceAtlas.hlsl | 4 ++-- Source/Shaders/GlobalSurfaceAtlas.shader | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 7e14b4403..16c6078bb 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -156,10 +156,10 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur } // Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). -float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBuffer chunks, Buffer culledObjects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) +// surfaceThreshold - Additional threshold (in world-units) between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBuffer chunks, Buffer culledObjects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold = 20.0f) { float4 result = float4(0, 0, 0, 0); - float surfaceThreshold = 20.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) // Snap to the closest chunk to get culled objects uint3 chunkCoord = (uint3)clamp(floor((worldPosition - data.ViewPos) / data.ChunkSize + (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)), 0, GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION - 1); diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 0ae63e05b..d6e198f05 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -267,7 +267,8 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); + float surfaceThreshold = hit.HitCascade * 10.0f + 20.0f; // Scale the threshold based on the hit cascade (less precision) + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); return float4(surfaceColor.rgb, 1); } From a7e512bd50c726f13a88e538805f671a04506732 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 27 Apr 2022 16:41:40 +0200 Subject: [PATCH 123/144] Add `GlobalSurfaceAtlas` draw pass --- Flax.flaxproj | 2 +- Source/Engine/Foliage/Foliage.cpp | 2 + Source/Engine/Foliage/FoliageType.cpp | 3 + Source/Engine/Graphics/Enums.h | 11 +- Source/Engine/Level/Actors/AnimatedModel.cpp | 5 + Source/Engine/Level/Actors/Camera.cpp | 12 +- .../Engine/Level/Actors/DirectionalLight.cpp | 1 + .../Level/Actors/ExponentialHeightFog.cpp | 6 +- Source/Engine/Level/Actors/SkyLight.cpp | 1 + Source/Engine/Level/Actors/SplineModel.cpp | 5 + Source/Engine/Level/Actors/StaticModel.cpp | 9 + Source/Engine/Particles/ParticleEffect.cpp | 6 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 328 +++++++++--------- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 4 + Source/Engine/Terrain/Terrain.cpp | 5 + Source/Engine/UI/SpriteRender.cpp | 2 +- Source/Engine/UI/TextRender.cpp | 5 + Source/FlaxEngine.Gen.cs | 4 +- Source/FlaxEngine.Gen.h | 6 +- 19 files changed, 238 insertions(+), 179 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 4f24f4c52..d3c343532 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 4, - "Build": 6331 + "Build": 6332 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.", diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 9a855eba2..a471b41c4 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -847,6 +847,8 @@ void Foliage::Draw(RenderContext& renderContext) { if (renderContext.View.Pass == DrawPass::GlobalSDF) return; // TODO: Foliage rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // Not supported if (Instances.IsEmpty()) return; auto& view = renderContext.View; diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index bdb57b922..1aa14bacb 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -212,4 +212,7 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index 44e080035..0125fb94c 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -703,10 +703,15 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 MotionVectors = 1 << 4, /// - /// The Global Sign Distance Field (SDF) rendering pass. + /// The Global Sign Distance Field (SDF) rendering pass. Used for software raytracing though the scene on a GPU. /// GlobalSDF = 1 << 5, + /// + /// The Global Surface Atlas rendering pass. Used for software raytracing though the scene on a GPU to evaluate the object surface material properties. + /// + GlobalSurfaceAtlas = 1 << 6, + /// /// The debug quad overdraw rendering (editor-only). /// @@ -717,13 +722,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32 /// The default set of draw passes for the scene objects. /// API_ENUM(Attributes="HideInEditor") - Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF, + Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas, /// /// The all draw passes combined into a single mask. /// API_ENUM(Attributes="HideInEditor") - All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF, + All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF | GlobalSurfaceAtlas, }; DECLARE_ENUM_OPERATORS(DrawPass); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 0303d3e04..eb385ce8b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -694,6 +694,8 @@ void AnimatedModel::Draw(RenderContext& renderContext) { if (renderContext.View.Pass == DrawPass::GlobalSDF) return; // TODO: Animated Model rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // No supported GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode)); @@ -815,6 +817,9 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index 8ca4d675a..288a060c3 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -249,7 +249,9 @@ bool Camera::HasContentLoaded() const void Camera::Draw(RenderContext& renderContext) { - if (renderContext.View.Flags & ViewFlags::EditorSprites && _previewModel && _previewModel->IsLoaded()) + if (renderContext.View.Flags & ViewFlags::EditorSprites + && _previewModel + && _previewModel->IsLoaded()) { GeometryDrawStateData drawState; Mesh::DrawInfo draw; @@ -259,14 +261,16 @@ void Camera::Draw(RenderContext& renderContext) draw.Lightmap = nullptr; draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; - draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass); + draw.DrawModes = (DrawPass)((DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward) & renderContext.View.Pass); BoundingSphere::FromBox(_previewModelBox, draw.Bounds); draw.PerInstanceRandom = GetPerInstanceRandom(); draw.LODBias = 0; draw.ForcedLOD = -1; draw.VertexColors = nullptr; - - _previewModel->Draw(renderContext, draw); + if (draw.DrawModes != DrawPass::None) + { + _previewModel->Draw(renderContext, draw); + } } } diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 7391691b3..38ac0407d 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -20,6 +20,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) AdjustBrightness(renderContext.View, brightness); if (Brightness > ZeroTolerance && (renderContext.View.Flags & ViewFlags::DirectionalLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { RendererDirectionalLightData data; diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 712051250..82d5dd7bb 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -33,7 +33,11 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext) { // Render only when shader is valid and fog can be rendered // Do not render exponential fog in orthographic views - if ((renderContext.View.Flags & ViewFlags::Fog) != 0 && _shader && _shader->IsLoaded() && renderContext.View.IsPerspectiveProjection()) + if ((renderContext.View.Flags & ViewFlags::Fog) != 0 + && renderContext.View.Pass & DrawPass::GBuffer + && _shader + && _shader->IsLoaded() + && renderContext.View.IsPerspectiveProjection()) { // Prepare if (_psFog.States[0] == nullptr) diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index d7bfa4c7b..6bab5440b 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -101,6 +101,7 @@ void SkyLight::Draw(RenderContext& renderContext) float brightness = Brightness; AdjustBrightness(renderContext.View, brightness); if ((renderContext.View.Flags & ViewFlags::SkyLights) != 0 + && renderContext.View.Pass & DrawPass::GBuffer && brightness > ZeroTolerance && (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, GetPosition()) < ViewDistance * ViewDistance)) { diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index c84226928..e7d47b97d 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -352,6 +352,8 @@ void SplineModel::Draw(RenderContext& renderContext) auto model = Model.Get(); if (renderContext.View.Pass == DrawPass::GlobalSDF) return; // TODO: Spline Model rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // TODO: Spline Model rendering to Global Surface Atlas if (!Entries.IsValidFor(model)) Entries.Setup(model); @@ -475,6 +477,9 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } void SplineModel::OnTransformChanged() diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 58328d223..2019ce3af 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -10,6 +10,7 @@ #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" +#include "Engine/Renderer/GlobalSurfaceAtlasPass.h" #include "Engine/Utilities/Encryption.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -244,6 +245,11 @@ void StaticModel::Draw(RenderContext& renderContext) GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _world, _box); return; } + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + { + GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, _world, Model->LODs.Last().GetBox()); + return; + } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); // Flush vertex colors if need to @@ -443,6 +449,9 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; { const auto member = stream.FindMember("RenderPasses"); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 078690337..5d32aceff 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -494,7 +494,7 @@ bool ParticleEffect::HasContentLoaded() const void ParticleEffect::Draw(RenderContext& renderContext) { - if (renderContext.View.Pass == DrawPass::GlobalSDF) + if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position)); Particles::DrawParticles(renderContext, this); @@ -679,10 +679,6 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* { ApplyModifiedParameters(); } - - // [Deprecated on 07.02.2022, expires on 07.02.2024] - if (modifier->EngineBuild <= 6330) - DrawModes |= DrawPass::GlobalSDF; } void ParticleEffect::EndPlay() diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index dde8878b7..4473e9101 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -129,6 +129,15 @@ public: GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; + // Cached data to be reused during RasterizeActor + uint64 CurrentFrame; + float ResolutionInv; + Vector3 ViewPosition; + float TileTexelsPerWorldUnit; + float DistanceScalingStart; + float DistanceScalingEnd; + float DistanceScaling; + FORCE_INLINE void ClearObjects() { CulledObjectsCounterIndex = -1; @@ -374,176 +383,30 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->DrawInstanced(_vertexBuffer->Data.Count() / sizeof(AtlasTileVertex), 1); // Add objects into the atlas - _objectsBuffer->Clear(); - _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); + _objectsBuffer->Clear(); + _dirtyObjectsBuffer.Clear(); + _surfaceAtlasData = &surfaceAtlasData; + renderContext.View.Pass = DrawPass::GlobalSurfaceAtlas; + surfaceAtlasData.CurrentFrame = currentFrame; + surfaceAtlasData.ResolutionInv = resolutionInv; + surfaceAtlasData.ViewPosition = renderContext.View.Position; + surfaceAtlasData.TileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution + surfaceAtlasData.DistanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down + surfaceAtlasData.DistanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down + surfaceAtlasData.DistanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas const uint32 viewMask = renderContext.View.RenderLayersMask; const Vector3 viewPosition = renderContext.View.Position; - const uint16 minTileResolution = 8; // Minimum size (in texels) of the tile in atlas - const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas - const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object const float minObjectRadius = 20.0f; // Skip too small objects - const float tileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution - const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down - const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down - const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away - // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas - static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < minTileResolution, "Invalid tile size configuration."); for (auto* scene : renderContext.List->Scenes) { - // TODO: optimize for static objects (SceneRendering could have separate and optimized caching for static actors) for (auto& e : scene->Actors) { if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) { - // TODO: move into actor-specific Draw() impl (eg. via GlobalSurfaceAtlas pass) - auto* staticModel = ScriptingObject::Cast(e.Actor); - if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered()) - { - Matrix localToWorld; - staticModel->GetWorld(&localToWorld); - bool anyTile = false, dirty = false; - GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(e.Actor); - auto& lod = staticModel->Model->LODs.Last(); - BoundingBox localBounds = lod.GetBox(); - Vector3 boundsSize = localBounds.GetSize() * staticModel->GetScale(); - const float distanceScale = Math::Lerp(1.0f, distanceScaling, Math::InverseLerp(distanceScalingStart, distanceScalingEnd, CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition))); - const float tilesScale = tileTexelsPerWorldUnit * distanceScale; - for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) - { - // Calculate optimal tile resolution for the object side - Vector3 boundsSizeTile = boundsSize; - boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size - boundsSizeTile.Absolute(); - uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); - if (tileResolution < 4) - { - // Skip too small surfaces - if (object && object->Tiles[tileIndex]) - { - object->Tiles[tileIndex]->Free(); - object->Tiles[tileIndex] = nullptr; - } - continue; - } - - // Clamp and snap to reduce atlas fragmentation - tileResolution = Math::Clamp(tileResolution, minTileResolution, maxTileResolution); - tileResolution = Math::AlignDown(tileResolution, tileResolutionAlignment); - - // Reuse current tile (refit only on a significant resolution change) - if (object && object->Tiles[tileIndex]) - { - const uint16 tileRefitResolutionStep = 32; - const uint16 currentSize = object->Tiles[tileIndex]->Width; - if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) - { - anyTile = true; - continue; - } - object->Tiles[tileIndex]->Free(); - } - - // Insert tile into atlas - auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, e.Actor, tileIndex); - if (tile) - { - if (!object) - object = &surfaceAtlasData.Objects[e.Actor]; - object->Tiles[tileIndex] = tile; - anyTile = true; - dirty = true; - } - else - { - if (object) - object->Tiles[tileIndex] = nullptr; - surfaceAtlasData.LastFrameAtlasInsertFail = currentFrame; - } - } - if (anyTile) - { - // Redraw objects from time-to-time (dynamic objects can be animated, static objects can have textures streamed) - uint32 redrawFramesCount = staticModel->HasStaticFlag(StaticFlags::Lightmap) ? 120 : 4; - if (currentFrame - object->LastFrameDirty >= (redrawFramesCount + (e.Actor->GetID().D & redrawFramesCount))) - dirty = true; - - // Mark object as used - object->LastFrameUsed = currentFrame; - object->Bounds = OrientedBoundingBox(localBounds); - object->Bounds.Transform(localToWorld); - object->Radius = e.Bounds.Radius; - if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) - { - object->LastFrameDirty = currentFrame; - _dirtyObjectsBuffer.Add(e.Actor); - } - - // Write to objects buffer (this must match unpacking logic in HLSL) - Matrix worldToLocalBounds; - Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); - uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4); - auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE); - objectData[0] = *(Vector4*)&e.Bounds; - objectData[1] = Vector4::Zero; // w unused - objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); - objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); - objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); - objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused - auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16 - auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles) - *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE; - for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) - { - auto* tile = object->Tiles[tileIndex]; - if (!tile) - continue; - tile->ObjectAddressOffset = *objectDataSize; - tile->Address = objectAddress + tile->ObjectAddressOffset; - tileOffsets[tileIndex] = tile->ObjectAddressOffset; - *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE; - - // Setup view to render object from the side - Vector3 xAxis, yAxis, zAxis = Vector3::Zero; - zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; - yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; - Vector3::Cross(yAxis, zAxis, xAxis); - Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents; - Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis); - Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis); - Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis); - xAxis.NormalizeFast(); - yAxis.NormalizeFast(); - zAxis.NormalizeFast(); - Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition); - tile->ViewDirection = zAxis; - - // Create view matrix - tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition))); - tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition))); - tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition))); - tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); - - // Calculate object bounds size in the view - OrientedBoundingBox viewBounds(object->Bounds); - viewBounds.Transform(tile->ViewMatrix); - Vector3 viewExtent; - Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); - tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f; - - // 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(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE); - tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * resolutionInv; - tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); - tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); - tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); - tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused - } - } - } + e.Actor->Draw(renderContext); } } } @@ -981,3 +844,150 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->SetViewportAndScissors(outputSize.X, outputSize.Y); context->DrawFullscreenTriangle(); } + +void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds) +{ + GlobalSurfaceAtlasCustomBuffer& surfaceAtlasData = *_surfaceAtlasData; + BoundingSphere actorBounds = actor->GetSphere(); + Vector3 boundsSize = localBounds.GetSize() * actor->GetScale(); + const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorBounds, surfaceAtlasData.ViewPosition))); + const float tilesScale = surfaceAtlasData.TileTexelsPerWorldUnit * distanceScale; + GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actor); + bool anyTile = false, dirty = false; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + // Calculate optimal tile resolution for the object side + Vector3 boundsSizeTile = boundsSize; + boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size + boundsSizeTile.Absolute(); + uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); + if (tileResolution < 4) + { + // Skip too small surfaces + if (object && object->Tiles[tileIndex]) + { + object->Tiles[tileIndex]->Free(); + object->Tiles[tileIndex] = nullptr; + } + continue; + } + + // Clamp tile resolution (in pixels) + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < 8, "Invalid tile size configuration. Minimum tile size must be larger than padding."); + tileResolution = Math::Clamp(tileResolution, 8, 128); + + // Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object + tileResolution = Math::AlignDown(tileResolution, 8); + + // Reuse current tile (refit only on a significant resolution change) + if (object && object->Tiles[tileIndex]) + { + const uint16 tileRefitResolutionStep = 32; + const uint16 currentSize = object->Tiles[tileIndex]->Width; + if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) + { + anyTile = true; + continue; + } + object->Tiles[tileIndex]->Free(); + } + + // Insert tile into atlas + auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actor, tileIndex); + if (tile) + { + if (!object) + object = &surfaceAtlasData.Objects[actor]; + object->Tiles[tileIndex] = tile; + anyTile = true; + dirty = true; + } + else + { + if (object) + object->Tiles[tileIndex] = nullptr; + surfaceAtlasData.LastFrameAtlasInsertFail = surfaceAtlasData.CurrentFrame; + } + } + if (!anyTile) + return; + + // 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))) + dirty = true; + + // Mark object as used + object->LastFrameUsed = surfaceAtlasData.CurrentFrame; + object->Bounds = OrientedBoundingBox(localBounds); + object->Bounds.Transform(localToWorld); + object->Radius = actorBounds.Radius; + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) + { + object->LastFrameDirty = surfaceAtlasData.CurrentFrame; + _dirtyObjectsBuffer.Add(actor); + } + + // Write to objects buffer (this must match unpacking logic in HLSL) + Matrix worldToLocalBounds; + Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); + uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4); + auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE); + objectData[0] = *(Vector4*)&actorBounds; + objectData[1] = Vector4::Zero; // w unused + objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); + objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); + objectData[4] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); + objectData[5] = Vector4(object->Bounds.Extents, 0.0f); // w unused + auto tileOffsets = reinterpret_cast(&objectData[1]); // xyz used for tile offsets packed into uint16 + auto objectDataSize = reinterpret_cast(&objectData[1].W); // w used for object size (count of Vector4s for object+tiles) + *objectDataSize = GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object->Tiles[tileIndex]; + if (!tile) + continue; + tile->ObjectAddressOffset = *objectDataSize; + tile->Address = objectAddress + tile->ObjectAddressOffset; + tileOffsets[tileIndex] = tile->ObjectAddressOffset; + *objectDataSize += GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE; + + // Setup view to render object from the side + Vector3 xAxis, yAxis, zAxis = Vector3::Zero; + zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; + yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; + Vector3::Cross(yAxis, zAxis, xAxis); + Vector3 localSpaceOffset = -zAxis * object->Bounds.Extents; + Vector3::TransformNormal(xAxis, object->Bounds.Transformation, xAxis); + Vector3::TransformNormal(yAxis, object->Bounds.Transformation, yAxis); + Vector3::TransformNormal(zAxis, object->Bounds.Transformation, zAxis); + xAxis.NormalizeFast(); + yAxis.NormalizeFast(); + zAxis.NormalizeFast(); + Vector3::Transform(localSpaceOffset, object->Bounds.Transformation, tile->ViewPosition); + tile->ViewDirection = zAxis; + + // Create view matrix + tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object->Bounds); + viewBounds.Transform(tile->ViewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // 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(GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE); + tileData[0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight) * surfaceAtlasData.ResolutionInv; + tileData[1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); + tileData[2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); + tileData[3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); + tileData[4] = Vector4(tile->ViewBoundsSize, 0.0f); // w unused + } +} diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index a59631c74..70363caad 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -44,6 +44,7 @@ private: class GPUBuffer* _culledObjectsSizeBuffer = nullptr; class DynamicTypedBuffer* _objectsBuffer = nullptr; class DynamicVertexBuffer* _vertexBuffer = nullptr; + class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData; Array _dirtyObjectsBuffer; uint64 _culledObjectsSizeFrames[8]; @@ -65,6 +66,9 @@ public: /// The output buffer. 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, const Matrix& localToWorld, const BoundingBox& localBounds); + private: #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index baba38123..d029410c3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -507,6 +507,8 @@ void Terrain::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) return; // TODO: Terrain rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // TODO: Terrain rendering to Global Surface Atlas PROFILE_CPU(); @@ -733,6 +735,9 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; } RigidBody* Terrain::GetAttachedRigidBody() const diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index 4b4402bbc..0ced24966 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -106,7 +106,7 @@ bool SpriteRender::HasContentLoaded() const void SpriteRender::Draw(RenderContext& renderContext) { - if (renderContext.View.Pass == DrawPass::GlobalSDF) + if (renderContext.View.Pass == DrawPass::GlobalSDF || renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded()) return; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index ccce0530a..cf206f4bc 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -342,6 +342,8 @@ void TextRender::Draw(RenderContext& renderContext) { if (renderContext.View.Pass == DrawPass::GlobalSDF) return; // TODO: Text rendering to Global SDF + if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + return; // TODO: Text rendering to Global Surface Atlas if (_isDirty) { UpdateLayout(); @@ -483,6 +485,9 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) DrawModes |= DrawPass::GlobalSDF; + // [Deprecated on 27.04.2022, expires on 27.04.2024] + if (modifier->EngineBuild <= 6331) + DrawModes |= DrawPass::GlobalSurfaceAtlas; _isDirty = true; } diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index dbde71661..403e7e965 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857cc7990797")] -[assembly: AssemblyVersion("1.4.6331")] -[assembly: AssemblyFileVersion("1.4.6331")] +[assembly: AssemblyVersion("1.4.6332")] +[assembly: AssemblyFileVersion("1.4.6332")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index fc0450288..fae7510a9 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 4, 6331) -#define FLAXENGINE_VERSION_TEXT "1.4.6331" +#define FLAXENGINE_VERSION Version(1, 4, 6332) +#define FLAXENGINE_VERSION_TEXT "1.4.6332" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 4 -#define FLAXENGINE_VERSION_BUILD 6331 +#define FLAXENGINE_VERSION_BUILD 6332 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved." From 3ec856778c2afeb7e2c27b16e76ecb22e3521f4f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 28 Apr 2022 10:42:58 +0200 Subject: [PATCH 124/144] Optimize Global SDF chunk data --- .../Renderer/GlobalSignDistanceFieldPass.cpp | 17 ++++++++++++----- .../Renderer/GlobalSignDistanceFieldPass.h | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index e13bf3f61..2c7c907e0 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -81,9 +81,16 @@ struct RasterizeModel struct RasterizeChunk { - bool Dynamic = false; - int32 ModelsCount = 0; - int32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + uint16 ModelsCount; + uint16 Dynamic : 1; + uint16 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + uint16 Heightfields[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT]; + + RasterizeChunk() + { + ModelsCount = 0; + Dynamic = false; + } }; constexpr int32 RasterizeChunkKeyHashResolution = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; @@ -756,7 +763,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; // Add model data for the GPU buffer - int32 modelIndex = _modelsBufferCount++; + uint16 modelIndex = _modelsBufferCount++; ModelRasterizeData modelData; Matrix::Transpose(worldToVolume, modelData.WorldToVolume); Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld); @@ -797,7 +804,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas } } - // Track streaming for SDF textures used in static chunks to invalidate cache + // Track streaming for textures used in static chunks to invalidate cache if (!dynamic && sdf.Texture->ResidentMipLevels() != sdf.Texture->MipLevels() && !_sdfData->SDFTextures.Contains(sdf.Texture)) { sdf.Texture->Deleted.Bind(_sdfData); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 6243d00cb..88c0f2cb2 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -42,7 +42,7 @@ private: // Rasterization cache class DynamicStructuredBuffer* _modelsBuffer = nullptr; Array _modelsTextures; - int32 _modelsBufferCount; + uint16 _modelsBufferCount; float _voxelSize; BoundingBox _cascadeBounds; class GlobalSignDistanceFieldCustomBuffer* _sdfData; From 70b9db7598e66cad10466fff32b4010b13365c39 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 28 Apr 2022 14:17:10 +0200 Subject: [PATCH 125/144] Add support for rasterizing terrain into Global SDF as heightfield --- Content/Shaders/GlobalSignDistanceField.flax | 4 +- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 213 ++++++++++++++---- .../Renderer/GlobalSignDistanceFieldPass.h | 10 +- Source/Engine/Terrain/Terrain.cpp | 21 +- Source/Shaders/GlobalSignDistanceField.shader | 75 +++++- 6 files changed, 258 insertions(+), 69 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 54dd366ac..2fc860f85 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c283024b5b907b9ee8eeb43ee95f8f825fe3cca959461b920d93f2978fdfd3bf -size 8460 +oid sha256:46a726df8a90ca98b038e6f7d278b0d77e2e18e89859d1bf490b880acd6333b3 +size 10943 diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index abfd4b5ae..3191dd042 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ea0db65392ada8819237b2f109b8549cf372e1ab96e17073b0dd24638cf8eb7 -size 10523 +oid sha256:fe3a26590d86b5da94b82dbd508e5f05c83b74d333d5be4c7f102897b611ead9 +size 10665 diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 2c7c907e0..b681aef60 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -18,6 +18,7 @@ // TODO: try using R8 format for Global SDF #define GLOBAL_SDF_FORMAT PixelFormat::R16_Float #define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. +#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 // The maximum amount of heightfields to store in a single chunk. #define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 #define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 // Global SDF chunk size in voxels. #define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 // The margin in voxels around objects for culling. Reduces artifacts but reduces performance. @@ -32,7 +33,7 @@ static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple o #include "Engine/Debug/DebugDraw.h" #endif -PACK_STRUCT(struct ModelRasterizeData +PACK_STRUCT(struct ObjectRasterizeData { Matrix WorldToVolume; // TODO: use 3x4 matrix Matrix VolumeToWorld; // TODO: use 3x4 matrix @@ -59,13 +60,14 @@ PACK_STRUCT(struct ModelsRasterizeData Int3 ChunkCoord; float MaxDistance; Vector3 CascadeCoordToPosMul; - int ModelsCount; + int ObjectsCount; Vector3 CascadeCoordToPosAdd; int32 CascadeResolution; - Vector2 Padding0; + float Padding0; + float CascadeVoxelSize; int32 CascadeMipResolution; int32 CascadeMipFactor; - uint32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; + uint32 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; }); struct RasterizeModel @@ -82,6 +84,7 @@ struct RasterizeModel struct RasterizeChunk { uint16 ModelsCount; + uint16 HeightfieldsCount : 15; uint16 Dynamic : 1; uint16 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; uint16 Heightfields[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT]; @@ -89,6 +92,7 @@ struct RasterizeChunk RasterizeChunk() { ModelsCount = 0; + HeightfieldsCount = 0; Dynamic = false; } }; @@ -292,13 +296,14 @@ bool GlobalSignDistanceFieldPass::setupResources() return true; _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); _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); // Init buffer - if (!_modelsBuffer) - _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); + if (!_objectsBuffer) + _objectsBuffer = New(64u * (uint32)sizeof(ObjectRasterizeData), (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer")); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; @@ -320,6 +325,7 @@ void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) SAFE_DELETE_GPU_RESOURCE(_psDebug); _csRasterizeModel0 = nullptr; _csRasterizeModel1 = nullptr; + _csRasterizeHeightfield = nullptr; _csClearChunk = nullptr; _csGenerateMip0 = nullptr; _csGenerateMip1 = nullptr; @@ -335,8 +341,8 @@ void GlobalSignDistanceFieldPass::Dispose() RendererPass::Dispose(); // Cleanup - SAFE_DELETE(_modelsBuffer); - _modelsTextures.Resize(0); + SAFE_DELETE(_objectsBuffer); + _objectsTextures.Resize(0); SAFE_DELETE_GPU_RESOURCE(_psDebug); _shader = nullptr; ChunksCache.Clear(); @@ -467,8 +473,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { PROFILE_CPU_NAMED("Clear"); chunks.Clear(); - _modelsBuffer->Clear(); - _modelsTextures.Clear(); + _objectsBuffer->Clear(); + _objectsTextures.Clear(); } // Check if cascade center has been moved @@ -482,9 +488,10 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex cascade.Bounds = cascadeBounds; // Draw all objects from all scenes into the cascade - _modelsBufferCount = 0; + _objectsBufferCount = 0; _voxelSize = voxelSize; _cascadeBounds = cascadeBounds; + _cascadeIndex = cascadeIndex; _sdfData = &sdfData; { PROFILE_CPU_NAMED("Draw"); @@ -516,6 +523,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; + data.CascadeVoxelSize = voxelSize; context->BindUA(0, cascadeView); context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; @@ -569,42 +577,61 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Send models data to the GPU if (chunks.Count() != 0) { - PROFILE_GPU_CPU("Update Models"); - _modelsBuffer->Flush(context); + PROFILE_GPU_CPU("Update Objects"); + _objectsBuffer->Flush(context); } - context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); + context->BindSR(0, _objectsBuffer->GetBuffer() ? _objectsBuffer->GetBuffer()->View() : nullptr); - // Rasterize non-empty chunk (first layer so can override existing chunk data) + // Rasterize non-empty chunks (first layer so can override existing chunk data) for (const auto& e : chunks) { if (e.Key.Layer != 0) continue; auto& chunk = e.Value; + cascade.NonEmptyChunks.Add(e.Key); + for (int32 i = 0; i < chunk.ModelsCount; i++) { - int32 model = chunk.Models[i]; - data.Models[i] = model; - context->BindSR(i + 1, _modelsTextures[model]); + auto objectIndex = chunk.Models[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); } - ASSERT_LOW_LAYER(chunk.ModelsCount != 0); + for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - data.ModelsCount = chunk.ModelsCount; + data.ObjectsCount = chunk.ModelsCount; context->UpdateCB(_cb1, &data); - cascade.NonEmptyChunks.Add(e.Key); - context->Dispatch(_csRasterizeModel0, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + auto cs = data.ObjectsCount != 0 ? _csRasterizeModel0 : _csClearChunk; // Terrain-only chunk can be quickly cleared + context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); anyChunkDispatch = true; // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches (maybe cache per-shader write/read flags for all UAVs?) + if (chunk.HeightfieldsCount != 0) + { + // Inject heightfield (additive) + for (int32 i = 0; i < chunk.HeightfieldsCount; i++) + { + auto objectIndex = chunk.Heightfields[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.HeightfieldsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } + #if GLOBAL_SDF_DEBUG_CHUNKS // Debug draw chunk bounds in world space with number of models in it if (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS) { - int32 count = chunk.ModelsCount; + int32 count = chunk.ModelsCount + chunk.HeightfieldsCount; RasterizeChunkKey tmp = e.Key; tmp.NextLayer(); while (chunks.ContainsKey(tmp)) { - count += chunks[tmp].ModelsCount; + count += chunks[tmp].ModelsCount + chunks[tmp].HeightfieldsCount; tmp.NextLayer(); } Vector3 chunkMin = cascadeBounds.Minimum + Vector3(e.Key.Coord) * chunkSize; @@ -615,23 +642,45 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex #endif } - // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) + // Rasterize non-empty chunks (additive layers so so need combine with existing chunk data) for (const auto& e : chunks) { if (e.Key.Layer == 0) continue; auto& chunk = e.Value; - for (int32 i = 0; i < chunk.ModelsCount; i++) - { - int32 model = chunk.Models[i]; - data.Models[i] = model; - context->BindSR(i + 1, _modelsTextures[model]); - } - ASSERT_LOW_LAYER(chunk.ModelsCount != 0); data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - data.ModelsCount = chunk.ModelsCount; - context->UpdateCB(_cb1, &data); - context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + + if (chunk.ModelsCount != 0) + { + // Inject models (additive) + for (int32 i = 0; i < chunk.ModelsCount; i++) + { + auto objectIndex = chunk.Models[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.ModelsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } + + if (chunk.HeightfieldsCount != 0) + { + // Inject heightfields (additive) + for (int32 i = 0; i < chunk.HeightfieldsCount; i++) + { + auto objectIndex = chunk.Heightfields[i]; + data.Objects[i] = objectIndex; + context->BindSR(i + 1, _objectsTextures[objectIndex]); + } + for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++) + context->UnBindSR(i + 1); + data.ObjectsCount = chunk.HeightfieldsCount; + context->UpdateCB(_cb1, &data); + context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + } anyChunkDispatch = true; } } @@ -762,19 +811,19 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas Vector3 volumeToUVWMul = sdf.LocalToUVWMul; Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul; - // Add model data for the GPU buffer - uint16 modelIndex = _modelsBufferCount++; - ModelRasterizeData modelData; - Matrix::Transpose(worldToVolume, modelData.WorldToVolume); - Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld); - modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; - modelData.VolumeToUVWMul = volumeToUVWMul; - modelData.VolumeToUVWAdd = volumeToUVWAdd; - modelData.MipOffset = (float)mipLevelIndex; - modelData.DecodeMul = 2.0f * sdf.MaxDistance; - modelData.DecodeAdd = -sdf.MaxDistance; - _modelsBuffer->Write(modelData); - _modelsTextures.Add(sdf.Texture->ViewVolume()); + // Add object data for the GPU buffer + uint16 objectIndex = _objectsBufferCount++; + ObjectRasterizeData objectData; + Matrix::Transpose(worldToVolume, objectData.WorldToVolume); + Matrix::Transpose(volumeToWorld, objectData.VolumeToWorld); + objectData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent; + objectData.VolumeToUVWMul = volumeToUVWMul; + objectData.VolumeToUVWAdd = volumeToUVWAdd; + objectData.MipOffset = (float)mipLevelIndex; + objectData.DecodeMul = 2.0f * sdf.MaxDistance; + objectData.DecodeAdd = -sdf.MaxDistance; + _objectsBuffer->Write(objectData); + _objectsTextures.Add(sdf.Texture->ViewVolume()); // Inject object into the intersecting cascade chunks _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); @@ -799,7 +848,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas chunk = &chunks[key]; } - chunk->Models[chunk->ModelsCount++] = modelIndex; + chunk->Models[chunk->ModelsCount++] = objectIndex; } } } @@ -812,3 +861,69 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas _sdfData->SDFTextures.Add(sdf.Texture); } } + +void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV) +{ + if (!heightfield || heightfield->ResidentMipLevels() == 0) + return; + + // Setup object data + BoundingBox objectBoundsCascade; + const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + + // Add object data for the GPU buffer + uint16 objectIndex = _objectsBufferCount++; + ObjectRasterizeData objectData; + Matrix worldToLocal; + Matrix::Invert(localToWorld, worldToLocal); + Matrix::Transpose(worldToLocal, objectData.WorldToVolume); + Matrix::Transpose(localToWorld, objectData.VolumeToWorld); + objectData.VolumeToUVWMul = Vector3(localToUV.X, 1.0f, localToUV.Y); + objectData.VolumeToUVWAdd = Vector3(localToUV.Z, 0.0f, localToUV.W); + objectData.MipOffset = (float)_cascadeIndex * 0.5f; // Use lower-quality mip for far cascades + _objectsBuffer->Write(objectData); + _objectsTextures.Add(heightfield->View()); + + // Inject object into the intersecting cascade chunks + _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); + RasterizeChunkKey key; + auto& chunks = ChunksCache; + const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); + for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++) + { + for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++) + { + for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++) + { + key.Layer = 0; + key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + RasterizeChunk* chunk = &chunks[key]; + chunk->Dynamic |= dynamic; + + // Move to the next layer if chunk has overflown + while (chunk->HeightfieldsCount == GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT) + { + key.NextLayer(); + chunk = &chunks[key]; + } + + chunk->Heightfields[chunk->HeightfieldsCount++] = objectIndex; + } + } + } + + // Track streaming for textures used in static chunks to invalidate cache + if (!dynamic && heightfield->ResidentMipLevels() != heightfield->MipLevels() && !_sdfData->SDFTextures.Contains(heightfield)) + { + heightfield->Deleted.Bind(_sdfData); + heightfield->ResidentMipsChanged.Bind(_sdfData); + _sdfData->SDFTextures.Add(heightfield); + } +} diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 88c0f2cb2..9ca01a547 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -33,6 +33,7 @@ private: GPUPipelineState* _psDebug = nullptr; GPUShaderProgramCS* _csRasterizeModel0 = nullptr; GPUShaderProgramCS* _csRasterizeModel1 = nullptr; + GPUShaderProgramCS* _csRasterizeHeightfield = nullptr; GPUShaderProgramCS* _csClearChunk = nullptr; GPUShaderProgramCS* _csGenerateMip0 = nullptr; GPUShaderProgramCS* _csGenerateMip1 = nullptr; @@ -40,9 +41,10 @@ private: GPUConstantBuffer* _cb1 = nullptr; // Rasterization cache - class DynamicStructuredBuffer* _modelsBuffer = nullptr; - Array _modelsTextures; - uint16 _modelsBufferCount; + class DynamicStructuredBuffer* _objectsBuffer = nullptr; + Array _objectsTextures; + uint16 _objectsBufferCount; + int32 _cascadeIndex; float _voxelSize; BoundingBox _cascadeBounds; class GlobalSignDistanceFieldCustomBuffer* _sdfData; @@ -76,6 +78,8 @@ public: // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); + void RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV); + private: #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index d029410c3..5515ce707 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -13,6 +13,7 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Renderer/GlobalSignDistanceFieldPass.h" Terrain::Terrain(const SpawnParams& params) : PhysicsColliderActor(params) @@ -506,7 +507,25 @@ void Terrain::Draw(RenderContext& renderContext) if (drawModes == DrawPass::None) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) - return; // TODO: Terrain rendering to Global SDF + { + const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize; + const float posToUV = 0.25f / chunkSize; + Vector4 localToUV(posToUV, posToUV, 0.0f, 0.0f); + Matrix localToWorld; + for (const TerrainPatch* patch : _patches) + { + if (!patch->Heightmap) + continue; + Transform patchTransform; + patchTransform.Translation = patch->_offset + Vector3(0, patch->_yOffset, 0); + patchTransform.Orientation = Quaternion::Identity; + patchTransform.Scale = Vector3(1.0f, patch->_yHeight, 1.0f); + patchTransform = _transform.LocalToWorld(patchTransform); + patchTransform.GetWorld(localToWorld); + GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), localToWorld, patch->_bounds, localToUV); + } + return; + } if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; // TODO: Terrain rendering to Global Surface Atlas diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index 82cccbd65..d07e5f38d 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -5,10 +5,11 @@ #include "./Flax/GlobalSignDistanceField.hlsl" #define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 +#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 #define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 #define GLOBAL_SDF_MIP_GROUP_SIZE 4 -struct ModelRasterizeData +struct ObjectRasterizeData { float4x4 WorldToVolume; // TODO: use 3x4 matrix float4x4 VolumeToWorld; // TODO: use 3x4 matrix @@ -33,13 +34,14 @@ META_CB_BEGIN(1, ModelsRasterizeData) int3 ChunkCoord; float MaxDistance; float3 CascadeCoordToPosMul; -int ModelsCount; +int ObjectsCount; float3 CascadeCoordToPosAdd; int CascadeResolution; -float2 Padding0; +float Padding0; +float CascadeVoxelSize; int CascadeMipResolution; int CascadeMipFactor; -uint4 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4]; +uint4 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4]; META_CB_END float CombineDistanceToSDF(float sdf, float distanceToSDF) @@ -54,13 +56,18 @@ float CombineDistanceToSDF(float sdf, float distanceToSDF) return sqrt(Square(max(sdf, 0)) + Square(distanceToSDF)); } -#if defined(_CS_RasterizeModel) +#if defined(_CS_RasterizeModel) || defined(_CS_RasterizeHeightfield) RWTexture3D GlobalSDFTex : register(u0); -StructuredBuffer ModelsBuffer : register(t0); -Texture3D ModelSDFTex[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1); +StructuredBuffer ObjectsBuffer : register(t0); -float DistanceToModelSDF(float minDistance, ModelRasterizeData modelData, Texture3D modelSDFTex, float3 worldPos) +#endif + +#if defined(_CS_RasterizeModel) + +Texture3D ObjectsTextures[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1); + +float DistanceToModelSDF(float minDistance, ObjectRasterizeData modelData, Texture3D modelSDFTex, float3 worldPos) { // Compute SDF volume UVs and distance in world-space to the volume bounds float3 volumePos = mul(float4(worldPos, 1), modelData.WorldToVolume).xyz; @@ -98,11 +105,55 @@ void CS_RasterizeModel(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_D #if READ_SDF minDistance *= GlobalSDFTex[voxelCoord]; #endif - for (int i = 0; i < ModelsCount; i++) + for (uint i = 0; i < ObjectsCount; i++) { - ModelRasterizeData modelData = ModelsBuffer[Models[i / 4][i % 4]]; - float modelDistance = DistanceToModelSDF(minDistance, modelData, ModelSDFTex[i], voxelWorldPos); - minDistance = min(minDistance, modelDistance); + ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; + float objectDistance = DistanceToModelSDF(minDistance, objectData, ObjectsTextures[i], voxelWorldPos); + minDistance = min(minDistance, objectDistance); + } + GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance); +} + +#endif + +#if defined(_CS_RasterizeHeightfield) + +Texture2D ObjectsTextures[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT] : register(t1); + +// Compute shader for rasterizing heightfield into Global SDF +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] +void CS_RasterizeHeightfield(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint3 voxelCoord = ChunkCoord + DispatchThreadId; + float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; + float minDistance = MaxDistance * GlobalSDFTex[voxelCoord]; + float thickness = CascadeVoxelSize * -4; + for (uint i = 0; i < ObjectsCount; i++) + { + ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; + + // Convert voxel world-space position into heightfield local-space position and get heightfield UV + float3 volumePos = mul(float4(voxelWorldPos, 1), objectData.WorldToVolume).xyz; + float3 volumeUV = volumePos * objectData.VolumeToUVWMul + objectData.VolumeToUVWAdd; + float2 heightfieldUV = float2(volumeUV.x, volumeUV.z); + + // Sample the heightfield + float4 heightmapValue = ObjectsTextures[i].SampleLevel(SamplerLinearClamp, heightfieldUV, objectData.MipOffset); + bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f; + if (isHole || any(heightfieldUV < 0.0f) || any(heightfieldUV > 1.0f)) + continue; + float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0; + float2 positionXZ = volumePos.xz; + float3 position = float3(positionXZ.x, height, positionXZ.y); + float3 heightfieldPosition = mul(float4(position, 1), objectData.VolumeToWorld).xyz; + float3 heightfieldNormal = normalize(float3(objectData.VolumeToWorld[0].y, objectData.VolumeToWorld[1].y, objectData.VolumeToWorld[2].y)); + + // Calculate distance from voxel center to the heightfield + float objectDistance = dot(heightfieldNormal, voxelWorldPos - heightfieldPosition); + if (objectDistance < thickness) + objectDistance = thickness - objectDistance; + minDistance = min(minDistance, objectDistance); } GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance); } From 1a711ea14fcbe97f3ace5f5bbb6aec8c9a1c791a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 28 Apr 2022 14:17:30 +0200 Subject: [PATCH 126/144] Improve Global Surface Atlas debug view to display multiple view types --- .../Renderer/GlobalSurfaceAtlasPass.cpp | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 4473e9101..fa18b6b6e 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -832,17 +832,33 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.Atlas[0]->View()); - { - //GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse - //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals - //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao - GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light - context->BindSR(11, tex->View()); - } context->SetState(_psDebug); context->SetRenderTarget(output->View()); - context->SetViewportAndScissors(outputSize.X, outputSize.Y); - context->DrawFullscreenTriangle(); + { + Vector2 outputSizeThird = outputSize * 0.333f; + Vector2 outputSizeTwoThird = outputSize * 0.666f; + + // Full screen - diffuse + context->BindSR(11, bindingData.Atlas[1]->View()); + context->SetViewport(outputSize.X, outputSize.Y); + context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); + context->DrawFullscreenTriangle(); + + // Bottom left - normals + context->BindSR(11, bindingData.Atlas[2]->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + + // Bottom middle - direct light + context->BindSR(11, bindingData.Atlas[4]->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeThird.Y, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + + // Bottom right - roughness/metalness/ao + context->BindSR(11, bindingData.Atlas[3]->View()); + context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeTwoThird.Y, outputSizeThird.X, outputSizeThird.Y)); + context->DrawFullscreenTriangle(); + } } void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds) From 5465652466fc58dee8ce4a3fa8267a6e4fb47c1c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 29 Apr 2022 14:05:03 +0200 Subject: [PATCH 127/144] Add terrain rendering support for Global Surface Atlas --- Content/Shaders/GlobalSignDistanceField.flax | 2 +- Source/Engine/Graphics/RenderView.h | 5 ++ Source/Engine/Level/Actors/SplineModel.cpp | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 2 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 54 ++++++++++--------- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 4 +- .../Engine/ShadowsOfMordor/Builder.Jobs.cpp | 3 +- Source/Engine/Terrain/Terrain.cpp | 25 +++++++-- Source/Engine/Terrain/TerrainChunk.h | 12 ++--- Source/Shaders/GlobalSurfaceAtlas.hlsl | 15 +++++- 10 files changed, 80 insertions(+), 44 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 2fc860f85..b07e84262 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46a726df8a90ca98b038e6f7d278b0d77e2e18e89859d1bf490b880acd6333b3 +oid sha256:618c12f75584c82a5e64a0161ef94b02373af3d5497d9f292dcdbd90d716b62a size 10943 diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 597e436a3..8ba994767 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -102,6 +102,11 @@ public: /// API_FIELD() bool IsSingleFrame = false; + /// + /// Flag used by custom passes to skip any object culling when drawing. + /// + API_FIELD() bool IsCullingDisabled = false; + /// /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index e7d47b97d..f2443570e 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -378,7 +378,7 @@ void SplineModel::Draw(RenderContext& renderContext) for (int32 segment = 0; segment < _instances.Count(); segment++) { auto& instance = _instances[segment]; - if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + if (!(renderContext.View.IsCullingDisabled || renderContext.View.CullingFrustum.Intersects(instance.Sphere))) continue; drawCall.Deformable.Segment = (float)segment; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 2019ce3af..ccf355195 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -247,7 +247,7 @@ void StaticModel::Draw(RenderContext& renderContext) } if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) { - GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, _world, Model->LODs.Last().GetBox()); + GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _world, Model->LODs.Last().GetBox()); return; } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index fa18b6b6e..07aa96eaa 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -67,7 +67,7 @@ struct GlobalSurfaceAtlasTile : RectPack { } - void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex); + void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex); void OnFree() { @@ -78,6 +78,7 @@ struct GlobalSurfaceAtlasObject { uint64 LastFrameUsed; uint64 LastFrameDirty; + Actor* Actor; GlobalSurfaceAtlasTile* Tiles[6]; float Radius; OrientedBoundingBox Bounds; @@ -127,7 +128,7 @@ public: int32 CulledObjectsCounterIndex = -1; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles - Dictionary Objects; + Dictionary Objects; // Cached data to be reused during RasterizeActor uint64 CurrentFrame; @@ -165,9 +166,9 @@ public: } }; -void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex) +void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex) { - buffer->Objects[actor].Tiles[tileIndex] = this; + buffer->Objects[actorObject].Tiles[tileIndex] = this; } String GlobalSurfaceAtlasPass::ToString() const @@ -438,6 +439,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co renderContextTiles.View.ModelLODBias += 100000; renderContextTiles.View.ShadowModelLODBias += 100000; renderContextTiles.View.IsSingleFrame = true; + renderContextTiles.View.IsCullingDisabled = true; renderContextTiles.View.Near = 0.0f; renderContextTiles.View.Prepare(renderContextTiles); @@ -466,9 +468,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Per-tile clear (with a single draw call) _vertexBuffer->Clear(); _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); - for (Actor* actor : _dirtyObjectsBuffer) + for (void* actorObject : _dirtyObjectsBuffer) { - const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject]; for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) { auto* tile = object.Tiles[tileIndex]; @@ -487,8 +489,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co auto& drawCallsListGBufferNoDecals = renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals]; drawCallsListGBuffer.CanUseInstancing = false; drawCallsListGBufferNoDecals.CanUseInstancing = false; - for (Actor* actor : _dirtyObjectsBuffer) + for (void* actorObject : _dirtyObjectsBuffer) { + const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actorObject]; + // Clear draw calls list renderContextTiles.List->DrawCalls.Clear(); renderContextTiles.List->BatchedDrawCalls.Clear(); @@ -501,10 +505,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co renderContextTiles.View.Projection.Values[0][0] = 10000.0f; // Collect draw calls for the object - actor->Draw(renderContextTiles); + object.Actor->Draw(renderContextTiles); // Render all tiles into the atlas - const auto& object = ((const Dictionary&)surfaceAtlasData.Objects)[actor]; #if GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); #endif @@ -838,19 +841,19 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex Vector2 outputSizeThird = outputSize * 0.333f; Vector2 outputSizeTwoThird = outputSize * 0.666f; - // Full screen - diffuse - context->BindSR(11, bindingData.Atlas[1]->View()); + // Full screen - direct light + context->BindSR(11, bindingData.Atlas[4]->View()); context->SetViewport(outputSize.X, outputSize.Y); context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); - // Bottom left - normals - context->BindSR(11, bindingData.Atlas[2]->View()); + // Bottom left - diffuse + context->BindSR(11, bindingData.Atlas[1]->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); context->DrawFullscreenTriangle(); - // Bottom middle - direct light - context->BindSR(11, bindingData.Atlas[4]->View()); + // Bottom middle - normals + context->BindSR(11, bindingData.Atlas[2]->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeThird.Y, outputSizeThird.X, outputSizeThird.Y)); context->DrawFullscreenTriangle(); @@ -861,17 +864,19 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex } } -void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToWorld, const BoundingBox& localBounds) +void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask) { GlobalSurfaceAtlasCustomBuffer& surfaceAtlasData = *_surfaceAtlasData; - BoundingSphere actorBounds = actor->GetSphere(); Vector3 boundsSize = localBounds.GetSize() * actor->GetScale(); - const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorBounds, surfaceAtlasData.ViewPosition))); + const float distanceScale = Math::Lerp(1.0f, surfaceAtlasData.DistanceScaling, Math::InverseLerp(surfaceAtlasData.DistanceScalingStart, surfaceAtlasData.DistanceScalingEnd, CollisionsHelper::DistanceSpherePoint(actorObjectBounds, surfaceAtlasData.ViewPosition))); const float tilesScale = surfaceAtlasData.TileTexelsPerWorldUnit * distanceScale; - GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actor); + GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(actorObject); bool anyTile = false, dirty = false; for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) { + if (((1 << tileIndex) & tilesMask) == 0) + continue; + // Calculate optimal tile resolution for the object side Vector3 boundsSizeTile = boundsSize; boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size @@ -909,11 +914,11 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW } // Insert tile into atlas - auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actor, tileIndex); + auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actorObject, tileIndex); if (tile) { if (!object) - object = &surfaceAtlasData.Objects[actor]; + object = &surfaceAtlasData.Objects[actorObject]; object->Tiles[tileIndex] = tile; anyTile = true; dirty = true; @@ -934,14 +939,15 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW dirty = true; // Mark object as used + object->Actor = actor; object->LastFrameUsed = surfaceAtlasData.CurrentFrame; object->Bounds = OrientedBoundingBox(localBounds); object->Bounds.Transform(localToWorld); - object->Radius = actorBounds.Radius; + object->Radius = actorObjectBounds.Radius; if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES) { object->LastFrameDirty = surfaceAtlasData.CurrentFrame; - _dirtyObjectsBuffer.Add(actor); + _dirtyObjectsBuffer.Add(actorObject); } // Write to objects buffer (this must match unpacking logic in HLSL) @@ -949,7 +955,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, const Matrix& localToW Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); uint32 objectAddress = _objectsBuffer->Data.Count() / sizeof(Vector4); auto* objectData = _objectsBuffer->WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE); - objectData[0] = *(Vector4*)&actorBounds; + objectData[0] = *(Vector4*)&actorObjectBounds; objectData[1] = Vector4::Zero; // w unused objectData[2] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); objectData[3] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index 70363caad..f245ac7f0 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -45,7 +45,7 @@ private: class DynamicTypedBuffer* _objectsBuffer = nullptr; class DynamicVertexBuffer* _vertexBuffer = nullptr; class GlobalSurfaceAtlasCustomBuffer* _surfaceAtlasData; - Array _dirtyObjectsBuffer; + Array _dirtyObjectsBuffer; uint64 _culledObjectsSizeFrames[8]; public: @@ -67,7 +67,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, const Matrix& localToWorld, const BoundingBox& localBounds); + void RasterizeActor(Actor* actor, void* actorObject, const BoundingSphere& actorObjectBounds, const Matrix& localToWorld, const BoundingBox& localBounds, uint32 tilesMask = MAX_uint32); private: #if COMPILE_WITH_DEV_ENV diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 169036aa9..4293c267e 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -152,8 +152,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) auto chunkSize = terrain->GetChunkSize(); const auto heightmap = patch->Heightmap.Get()->GetTexture(); - Matrix world; - chunk->GetWorld(&world); + const Matrix& world = chunk->GetWorld(); Matrix::Transpose(world, shaderData.WorldMatrix); shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 5515ce707..021384376 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -14,6 +14,7 @@ #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" +#include "Engine/Renderer/GlobalSurfaceAtlasPass.h" Terrain::Terrain(const SpawnParams& params) : PhysicsColliderActor(params) @@ -527,7 +528,25 @@ void Terrain::Draw(RenderContext& renderContext) return; } if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) - return; // TODO: Terrain rendering to Global Surface Atlas + { + for (TerrainPatch* patch : _patches) + { + if (!patch->Heightmap) + continue; + Matrix worldToLocal; + BoundingSphere chunkSphere; + BoundingBox localBounds; + for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + { + TerrainChunk* chunk = &patch->Chunks[chunkIndex]; + 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); + } + } + return; + } PROFILE_CPU(); @@ -541,7 +560,7 @@ void Terrain::Draw(RenderContext& renderContext) for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++) { const auto patch = _patches[patchIndex]; - if (frustum.Intersects(patch->_bounds)) + if (renderContext.View.IsCullingDisabled || frustum.Intersects(patch->_bounds)) { // Skip if has no heightmap or it's not loaded if (patch->Heightmap == nullptr || patch->Heightmap->GetTexture()->ResidentMipLevels() == 0) @@ -552,7 +571,7 @@ void Terrain::Draw(RenderContext& renderContext) { auto chunk = &patch->Chunks[chunkIndex]; chunk->_cachedDrawLOD = 0; - if (frustum.Intersects(chunk->_bounds)) + if (renderContext.View.IsCullingDisabled || frustum.Intersects(chunk->_bounds)) { if (chunk->PrepareDraw(renderContext)) { diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index 611ff46a4..5764844fb 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -55,7 +55,6 @@ public: /// /// Gets the x coordinate. /// - /// The x position. FORCE_INLINE int32 GetX() const { return _x; @@ -64,7 +63,6 @@ public: /// /// Gets the z coordinate. /// - /// The z position. FORCE_INLINE int32 GetZ() const { return _z; @@ -73,7 +71,6 @@ public: /// /// Gets the patch. /// - /// The terrain patch, FORCE_INLINE TerrainPatch* GetPatch() const { return _patch; @@ -82,19 +79,17 @@ public: /// /// Gets the chunk world bounds. /// - /// The bounding box. FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } /// - /// Gets the model world matrix transform. + /// Gets the chunk world matrix transform. /// - /// The result world matrix. - FORCE_INLINE void GetWorld(Matrix* world) const + FORCE_INLINE const Matrix& GetWorld() const { - *world = _world; + return _world; } /// @@ -109,7 +104,6 @@ public: /// /// Determines whether this chunk has valid lightmap data. /// - /// true if this chunk has valid lightmap data; otherwise, false. FORCE_INLINE bool HasLightmap() const { return Lightmap.TextureIndex != INVALID_INDEX; diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 16c6078bb..b90488cd8 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -193,8 +193,21 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu if (any(localPosition > localExtent) || any(localPosition < -localExtent)) continue; + // Remove the scale vector from the transformation matrix + float3x3 worldToLocal = object.WorldToLocal; + float scaleX = length(worldToLocal[0]); + float scaleY = length(worldToLocal[1]); + float scaleZ = length(worldToLocal[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + worldToLocal[0] *= invScale.x; + worldToLocal[1] *= invScale.y; + worldToLocal[2] *= invScale.z; + // Sample tiles based on the directionality - float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); + float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormalSq = localNormal * localNormal; uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) From 8428f2823999a53a44507b326eb382810dc8e46a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 29 Apr 2022 17:11:39 +0200 Subject: [PATCH 128/144] Various improvements for SDF rendering --- Content/Shaders/GlobalSignDistanceField.flax | 2 +- Source/Editor/Windows/Assets/ModelWindow.cs | 10 +++++++++- Source/Engine/Content/Assets/Model.cpp | 4 ++-- Source/Engine/Content/Assets/Model.h | 3 ++- Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp | 11 +++++++---- Source/Engine/Tools/ModelTool/ModelTool.cpp | 8 ++++---- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- Source/Shaders/GlobalSignDistanceField.shader | 2 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 2 +- 9 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index b07e84262..f30c01874 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:618c12f75584c82a5e64a0161ef94b02373af3d5497d9f292dcdbd90d716b62a +oid sha256:ce0651af73a6bc605cb88d6a747f0d241d15062c19c8359c783138e476c9b0d3 size 10943 diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index d493cd0e6..f3197660c 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -201,6 +201,13 @@ namespace FlaxEditor.Windows.Assets resolution.FloatValue.MaxValue = 100.0f; resolution.FloatValue.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; resolution.FloatValue.BoxValueChanged += b => { proxy.Window._importSettings.SDFResolution = b.Value; }; + proxy.Window._importSettings.SDFResolution = sdf.ResolutionScale; + + var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh."); + backfacesThreshold.FloatValue.MinValue = 0.001f; + backfacesThreshold.FloatValue.MaxValue = 1.0f; + backfacesThreshold.FloatValue.Value = proxy.Window._backfacesThreshold; + backfacesThreshold.FloatValue.BoxValueChanged += b => { proxy.Window._backfacesThreshold = b.Value; }; var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); lodIndex.IntValue.MinValue = 0; @@ -286,7 +293,7 @@ namespace FlaxEditor.Windows.Assets private void OnRebuildSDF() { var proxy = (MeshesPropertiesProxy)Values[0]; - proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value); + proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold); proxy.Window.MarkAsEdited(); Presenter.BuildLayoutOnUpdate(); } @@ -770,6 +777,7 @@ namespace FlaxEditor.Windows.Assets private StaticModel _highlightActor; private MeshDataCache _meshData; private ModelImportSettings _importSettings = new ModelImportSettings(); + private float _backfacesThreshold = 0.6f; /// public ModelWindow(Editor editor, AssetItem item) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 4512172f8..a0e994702 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -635,7 +635,7 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) #endif -bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData) +bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold) { if (EnableModelSDF == 2) return true; // Not supported @@ -658,7 +658,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData) #else class MemoryWriteStream* outputStream = nullptr; #endif - if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath())) + if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath(), backfacesThreshold)) return true; #if USE_EDITOR diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index 15744148f..6fde4bab3 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -213,8 +213,9 @@ public: /// The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead. /// The index of the LOD to use for the SDF building. /// If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets or in build. + /// Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh. /// True if failed, otherwise false. - API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true); + API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true, float backfacesThreshold = 0.6f); /// /// Sets set SDF data (releases the current one). diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 07aa96eaa..52fa4c9dc 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -580,10 +580,13 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co { // Get the last counter value (accept staging readback delay) auto data = (uint32*)_culledObjectsSizeBuffer->Map(GPUResourceMapMode::Read); - uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; - _culledObjectsSizeBuffer->Unmap(); - if (counter > 0) - objectsBufferCapacity = counter * sizeof(Vector4); + if (data) + { + uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; + _culledObjectsSizeBuffer->Unmap(); + if (counter > 0) + objectsBufferCapacity = counter * sizeof(Vector4); + } } if (surfaceAtlasData.CulledObjectsCounterIndex == -1) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 8a4cd9a91..fd8575612 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -66,7 +66,7 @@ ModelSDFMip::ModelSDFMip(int32 mipIndex, const TextureMipData& mip) { } -bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName) +bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold) { PROFILE_CPU(); auto startTime = Platform::GetTimeSeconds(); @@ -176,7 +176,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float for (int32 i = 6; i < sampleCount; i++) sampleDirections.Get()[i] = rand.GetUnitVector(); } - Function sdfJob = [&sdf, &resolution, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) + Function sdfJob = [&sdf, &resolution, &backfacesThreshold, &sampleDirections, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z) { PROFILE_CPU_NAMED("Model SDF Job"); float hitDistance; @@ -210,8 +210,8 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float float distance = minDistance; // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry - //if ((float)hitBackCount > )hitCount * 0.3f && hitCount != 0) - if ((float)hitBackCount > (float)sampleDirections.Count() * 0.6f && hitCount != 0) + // if ((float)hitBackCount > (float)hitCount * 0.3f && hitCount != 0) + if ((float)hitBackCount > (float)sampleDirections.Count() * backfacesThreshold && hitCount != 0) { // Voxel is inside the geometry so turn it into negative distance to the surface distance *= -1; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 15a7d4def..5af8a84eb 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -185,7 +185,7 @@ public: // Optional: inputModel or modelData // Optional: outputSDF or null, outputStream or null - static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName); + static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold = 0.6f); #if USE_EDITOR public: diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index d07e5f38d..4c2ad666e 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -128,7 +128,7 @@ void CS_RasterizeHeightfield(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId uint3 voxelCoord = ChunkCoord + DispatchThreadId; float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; float minDistance = MaxDistance * GlobalSDFTex[voxelCoord]; - float thickness = CascadeVoxelSize * -4; + float thickness = CascadeVoxelSize * -8; for (uint i = 0; i < ObjectsCount; i++) { ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index b90488cd8..9220f6c26 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -194,7 +194,7 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu continue; // Remove the scale vector from the transformation matrix - float3x3 worldToLocal = object.WorldToLocal; + float3x3 worldToLocal = (float3x3)object.WorldToLocal; float scaleX = length(worldToLocal[0]); float scaleY = length(worldToLocal[1]); float scaleZ = length(worldToLocal[2]); From 7731db638f725de83a76b7ce98257d16df24fbc1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Sat, 30 Apr 2022 15:22:54 +0200 Subject: [PATCH 129/144] Fix raycasting Global SDF from far cascade --- Source/Shaders/GlobalSignDistanceField.hlsl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index b27938951..f851fc192 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -45,7 +45,7 @@ struct GlobalSDFHit float HitTime; uint HitCascade; uint StepsCount; - + bool IsHit() { return HitTime >= 0.0f; @@ -135,15 +135,20 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] float2 intersections = LineHitBox(trace.WorldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www); intersections.xy *= traceMaxDistance; intersections.x = max(intersections.x, nextIntersectionStart); + float stepTime = intersections.x; if (intersections.x >= intersections.y) - break; - - // Skip the current cascade tracing on the next cascade - nextIntersectionStart = intersections.y; + { + // Skip the current cascade if the ray starts outside it + stepTime = intersections.y; + } + else + { + // Skip the current cascade tracing on the next cascade + nextIntersectionStart = intersections.y; + } // Walk over the cascade SDF uint step = 0; - float stepTime = intersections.x; LOOP for (; step < 250 && stepTime < intersections.y; step++) { From 8e02d449fb783b329a08937ee5ee2ba8f085c9a3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 2 May 2022 10:13:20 +0200 Subject: [PATCH 130/144] Fix rare crash on terrain in Global Surface Atlas sampling --- Source/Shaders/GlobalSurfaceAtlas.hlsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 9220f6c26..b1ea4d5d5 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -175,6 +175,8 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu float4 chunkHeader = culledObjects[objectsStart]; objectsStart++; uint objectsCount = asuint(chunkHeader.x); + if (objectsCount > data.ObjectsCount) // Prevents crashing - don't know why the data is invalid here (rare issue when moving fast though scene with terrain) + return result; // Loop over culled objects inside the chunk LOOP From 04a7dab7e7e63bf51b464d08832fe2521e20edec Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 2 May 2022 10:36:31 +0200 Subject: [PATCH 131/144] Minor fixes --- Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp | 2 +- Source/Engine/ShadersCompilation/ShaderCompiler.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 52fa4c9dc..8268f5994 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -609,7 +609,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } // Allocate buffer for culled objects (estimated size) - objectsBufferCapacity = Math::AlignUp(objectsBufferCapacity, 4096u); + objectsBufferCapacity = Math::Min(Math::AlignUp(objectsBufferCapacity, 4096u), (uint32)MAX_int32); if (!surfaceAtlasData.CulledObjectsBuffer) surfaceAtlasData.CulledObjectsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("GlobalSurfaceAtlas.CulledObjectsBuffer")); if (surfaceAtlasData.CulledObjectsBuffer->GetSize() < objectsBufferCapacity) diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index eba91df76..afca3c4b5 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -25,7 +25,7 @@ namespace IncludedFiles { String Path; DateTime LastEditTime; - Array Source; + StringAnsi Source; }; CriticalSection Locker; @@ -222,7 +222,7 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co result = New(); result->Path = path; result->LastEditTime = FileSystem::GetFileLastEditTime(path); - if (File::ReadAllBytes(result->Path, result->Source)) + if (File::ReadAllText(result->Path, result->Source)) { LOG(Error, "Failed to load shader source file '{0}' included in '{1}' (path: '{2}')", String(includedFile), String(sourceFile), path); Delete(result); @@ -234,8 +234,8 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co context->Includes.Add(path); // Copy to output - source = (const char*)result->Source.Get(); - sourceLength = result->Source.Count() - 1; + source = result->Source.Get(); + sourceLength = result->Source.Length(); return false; } From 4b6fce7e5d2ecc9b19a942308301d0675f289bab Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 2 May 2022 10:57:54 +0200 Subject: [PATCH 132/144] Ignore Rider configs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 603f6d435..274105a24 100644 --- a/.gitignore +++ b/.gitignore @@ -148,5 +148,6 @@ bin/ obj/ *.vcxproj.filters .vscode/ +.idea/ *.code-workspace From acd064c364c1b4d91a23493cb92e505593b4571c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 2 May 2022 12:09:53 +0200 Subject: [PATCH 133/144] Introduce separate GI directory --- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 3 +++ Content/Shaders/GlobalSurfaceAtlas.flax | 3 --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 2 +- .../Engine/Renderer/{ => GI}/GlobalSurfaceAtlasPass.cpp | 8 ++++---- Source/Engine/Renderer/{ => GI}/GlobalSurfaceAtlasPass.h | 2 +- Source/Engine/Renderer/Renderer.cpp | 2 +- Source/Engine/Terrain/Terrain.cpp | 2 +- Source/Shaders/{ => GI}/GlobalSurfaceAtlas.hlsl | 0 Source/Shaders/{ => GI}/GlobalSurfaceAtlas.shader | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 Content/Shaders/GI/GlobalSurfaceAtlas.flax delete mode 100644 Content/Shaders/GlobalSurfaceAtlas.flax rename Source/Engine/Renderer/{ => GI}/GlobalSurfaceAtlasPass.cpp (99%) rename Source/Engine/Renderer/{ => GI}/GlobalSurfaceAtlasPass.h (99%) rename Source/Shaders/{ => GI}/GlobalSurfaceAtlas.hlsl (100%) rename Source/Shaders/{ => GI}/GlobalSurfaceAtlas.shader (99%) diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax new file mode 100644 index 000000000..2376e451e --- /dev/null +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ec7fc26caf2d2c9c216cc47684ac2adc6b872a5a61cdd7028a59e9230eae0f4 +size 10668 diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax deleted file mode 100644 index 3191dd042..000000000 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe3a26590d86b5da94b82dbd508e5f05c83b74d333d5be4c7f102897b611ead9 -size 10665 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b9c092059..b41b28e43 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -69,7 +69,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/BitonicSort")); data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); - data.AddRootEngineAsset(TEXT("Shaders/GlobalSurfaceAtlas")); + data.AddRootEngineAsset(TEXT("Shaders/GI/GlobalSurfaceAtlas")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index ccf355195..cdfd2a2e1 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -10,7 +10,7 @@ #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" -#include "Engine/Renderer/GlobalSurfaceAtlasPass.h" +#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" #include "Engine/Utilities/Encryption.h" #if USE_EDITOR #include "Editor/Editor.h" diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp similarity index 99% rename from Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp rename to Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 8268f5994..e26c4efda 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -1,9 +1,9 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "GlobalSurfaceAtlasPass.h" -#include "GlobalSignDistanceFieldPass.h" -#include "RenderList.h" -#include "ShadowsPass.h" +#include "../GlobalSignDistanceFieldPass.h" +#include "../RenderList.h" +#include "../ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" #include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Engine.h" @@ -192,7 +192,7 @@ bool GlobalSurfaceAtlasPass::setupResources() // Load shader if (!_shader) { - _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSurfaceAtlas")); + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GI/GlobalSurfaceAtlas")); if (_shader == nullptr) return true; #if COMPILE_WITH_DEV_ENV diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h similarity index 99% rename from Source/Engine/Renderer/GlobalSurfaceAtlasPass.h rename to Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index f245ac7f0..19fa65c1c 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -2,7 +2,7 @@ #pragma once -#include "RendererPass.h" +#include "../RendererPass.h" /// /// Global Surface Atlas rendering pass. Captures scene geometry into a single atlas texture which contains surface diffuse color, normal vector, emission light, and calculates direct+indirect lighting. Used by Global Illumination and Reflections. diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index ad50ff6e2..056870571 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -22,7 +22,7 @@ #include "HistogramPass.h" #include "AtmospherePreCompute.h" #include "GlobalSignDistanceFieldPass.h" -#include "GlobalSurfaceAtlasPass.h" +#include "GI/GlobalSurfaceAtlasPass.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 021384376..452b4ca56 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -14,7 +14,7 @@ #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" -#include "Engine/Renderer/GlobalSurfaceAtlasPass.h" +#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" Terrain::Terrain(const SpawnParams& params) : PhysicsColliderActor(params) diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl similarity index 100% rename from Source/Shaders/GlobalSurfaceAtlas.hlsl rename to Source/Shaders/GI/GlobalSurfaceAtlas.hlsl diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader similarity index 99% rename from Source/Shaders/GlobalSurfaceAtlas.shader rename to Source/Shaders/GI/GlobalSurfaceAtlas.shader index d6e198f05..16d569158 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -5,9 +5,9 @@ #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" -#include "./Flax/GlobalSurfaceAtlas.hlsl" -#include "./Flax/GlobalSignDistanceField.hlsl" #include "./Flax/LightingCommon.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/GI/GlobalSurfaceAtlas.hlsl" META_CB_BEGIN(0, Data) float3 ViewWorldPos; From ab37cc72a9db8af26988b889e9b8cb81ffa42fa0 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 2 May 2022 12:52:38 +0200 Subject: [PATCH 134/144] Minor renaming --- Source/Engine/Graphics/Materials/MaterialParams.cpp | 2 +- Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp | 4 ++-- Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h | 4 ++-- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 2 +- Source/Engine/Renderer/GlobalSignDistanceFieldPass.h | 4 ++-- Source/Engine/Visject/ShaderGraphUtilities.cpp | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 213e4e0c0..1e0da6213 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -490,7 +490,7 @@ void MaterialParameter::Bind(BindMeta& meta) const Platform::MemoryClear(&bindingData, sizeof(bindingData)); for (int32 i = 0; i < 4; i++) meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); - *((GlobalSignDistanceFieldPass::GlobalSDFData*)(meta.Constants.Get() + _offset)) = bindingData.GlobalSDF; + *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.GlobalSDF; break; } default: diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index e26c4efda..87d979d62 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -41,8 +41,8 @@ PACK_STRUCT(struct Data0 float LightShadowsStrength; float ViewFarPlane; Vector4 ViewFrustumWorldRays[4]; - GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; - GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; + GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; + GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; LightData Light; }); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index 19fa65c1c..81eba1bcb 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -11,7 +11,7 @@ class FLAXENGINE_API GlobalSurfaceAtlasPass : public RendererPass Date: Thu, 12 May 2022 13:30:59 +0200 Subject: [PATCH 135/144] Fix `InstanceOrigin`, `PerInstanceRandom` and `LODDitherFactor` to not use interpolation between shader stages --- Content/Editor/MaterialTemplates/Particle.shader | 8 ++++---- Content/Editor/MaterialTemplates/Surface.shader | 4 ++-- Source/Shaders/MaterialCommon.hlsl | 9 +++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index a3736a53e..ec73cd072 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -76,8 +76,8 @@ struct VertexOutput #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - float3 InstanceOrigin : TEXCOORD6; - float InstanceParams : TEXCOORD7; // x-PerInstanceRandom + nointerpolation float3 InstanceOrigin : TEXCOORD6; + nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom }; // Interpolants passed to the pixel shader @@ -94,8 +94,8 @@ struct PixelInput #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - float3 InstanceOrigin : TEXCOORD6; - float InstanceParams : TEXCOORD7; // x-PerInstanceRandom + nointerpolation float3 InstanceOrigin : TEXCOORD6; + nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom bool IsFrontFace : SV_IsFrontFace; }; diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index cf6e00e36..ff88f1b40 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -43,8 +43,8 @@ struct GeometryData #endif float3 WorldNormal : TEXCOORD3; float4 WorldTangent : TEXCOORD4; - float3 InstanceOrigin : TEXCOORD5; - float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor + nointerpolation float3 InstanceOrigin : TEXCOORD5; + nointerpolation float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor float3 PrevWorldPosition : TEXCOORD7; }; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 40a83ca5c..a18e29f81 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -98,26 +98,24 @@ struct ModelInput #if USE_VERTEX_COLOR half4 Color : COLOR; #endif - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; struct ModelInput_PosOnly { float3 Position : POSITION; - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; @@ -129,13 +127,12 @@ struct ModelInput_Skinned float4 Tangent : TANGENT; uint4 BlendIndices : BLENDINDICES; float4 BlendWeights : BLENDWEIGHT; - #if USE_INSTANCING float4 InstanceOrigin : ATTRIBUTE0; // .w contains PerInstanceRandom float4 InstanceTransform1 : ATTRIBUTE1; // .w contains LODDitherFactor float3 InstanceTransform2 : ATTRIBUTE2; float3 InstanceTransform3 : ATTRIBUTE3; - half4 InstanceLightmapArea : ATTRIBUTE4; + half4 InstanceLightmapArea : ATTRIBUTE4; #endif }; From f7955a5c4e15b7c32633d7d6b52520ffb7c55a66 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 12 May 2022 13:44:57 +0200 Subject: [PATCH 136/144] Add support for custom location of Custom Global Code in generated material source code --- Source/Editor/Surface/Archetypes/Material.cs | 40 ++++++++++++++- .../MaterialGenerator/MaterialGenerator.cpp | 49 +++++++++++++------ .../MaterialGenerator/MaterialGenerator.h | 1 + 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 424511cce..f7ce33c92 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -236,6 +236,39 @@ namespace FlaxEditor.Surface.Archetypes } } + internal enum MaterialTemplateInputsMapping + { + /// + /// Constant buffers. + /// + Constants = 1, + + /// + /// Shader resources such as textures and buffers. + /// + ShaderResources = 2, + + /// + /// Pre-processor definitions. + /// + Defines = 3, + + /// + /// Included files. + /// + Includes = 7, + + /// + /// Default location after all shader resources and methods but before actual material code. + /// + Utilities = 8, + + /// + /// Shader functions location after all material shaders. + /// + Shaders = 9, + } + /// /// The nodes for that group. /// @@ -814,17 +847,20 @@ namespace FlaxEditor.Surface.Archetypes Title = "Custom Global Code", Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.", Flags = NodeFlags.MaterialGraph, - Size = new Vector2(300, 220), + Size = new Vector2(300, 240), DefaultValues = new object[] { "// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}", true, + (int)MaterialTemplateInputsMapping.Utilities, }, Elements = new[] { NodeElementArchetype.Factory.Bool(0, 0, 1), NodeElementArchetype.Factory.Text(20, 0, "Enabled"), - NodeElementArchetype.Factory.TextBox(0, 20, 300, 200, 0), + NodeElementArchetype.Factory.Text(0, 20, "Location"), + NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)), + NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0), } }, }; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index f1d5b5840..42b57c341 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -392,6 +392,21 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; + // Find all Custom Global Code nodes + Array> customGlobalCodeNodes; + Array> graphs; + _functions.GetValues(graphs); + for (MaterialLayer* layer : _layers) + graphs.Add(&layer->Graph); + for (Graph* graph : graphs) + { + for (const MaterialGraph::Node& node : graph->Nodes) + { + if (node.Type == GRAPH_NODE_MAKE_TYPE(1, 38) && (bool)node.Values[1]) + customGlobalCodeNodes.Add(&node); + } + } + #define WRITE_FEATURES(input) FeaturesLock.Lock(); for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); FeaturesLock.Unlock(); // Defines { @@ -408,6 +423,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo } WRITE_FEATURES(Defines); inputs[In_Defines] = _writer.ToString(); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Defines); _writer.Clear(); } @@ -416,6 +432,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo for (auto& include : _includes) _writer.Write(TEXT("#include \"{0}\"\n"), include.Item); WRITE_FEATURES(Includes); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Includes); inputs[In_Includes] = _writer.ToString(); _writer.Clear(); } @@ -425,6 +442,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo WRITE_FEATURES(Constants); if (_parameters.HasItems()) ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Constants); inputs[In_Constants] = _writer.ToString(); _writer.Clear(); } @@ -485,6 +503,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo return true; } } + WriteCustomGlobalCode(customGlobalCodeNodes, In_ShaderResources); inputs[In_ShaderResources] = _writer.ToString(); _writer.Clear(); } @@ -492,21 +511,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Utilities { WRITE_FEATURES(Utilities); - Array> graphs; - _functions.GetValues(graphs); - for (MaterialLayer* layer : _layers) - graphs.Add(&layer->Graph); - for (Graph* graph : graphs) - { - for (const MaterialGraph::Node& node : graph->Nodes) - { - if (node.Type == GRAPH_NODE_MAKE_TYPE(1, 38) && (bool)node.Values[1]) - { - // Custom Global Code - _writer.Write((StringView)node.Values[0]); - } - } - } + WriteCustomGlobalCode(customGlobalCodeNodes, In_Utilities); inputs[In_Utilities] = _writer.ToString(); _writer.Clear(); } @@ -514,6 +519,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Shaders { WRITE_FEATURES(Shaders); + WriteCustomGlobalCode(customGlobalCodeNodes, In_Shaders); inputs[In_Shaders] = _writer.ToString(); _writer.Clear(); } @@ -799,4 +805,17 @@ void MaterialGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) } } +void MaterialGenerator::WriteCustomGlobalCode(const Array>& nodes, int32 templateInputsMapping) +{ + for (const MaterialGraph::Node* node : nodes) + { + if ((int32)node->Values[2] == templateInputsMapping) + { + _writer.Write(TEXT("\n")); + _writer.Write((StringView)node->Values[0]); + _writer.Write(TEXT("\n")); + } + } +} + #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 48cd21cb4..9a8aa5290 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -205,6 +205,7 @@ private: MaterialValue AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttributeValueTypes valueType, const Char* index = nullptr, ParticleAttributeSpace space = ParticleAttributeSpace::AsIs); void prepareLayer(MaterialLayer* layer, bool allowVisibleParams); + void WriteCustomGlobalCode(const Array>& nodes, int32 templateInputsMapping); public: From abcc319168ce5423b549c3badc1760359b9782cd Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 12 May 2022 13:46:05 +0200 Subject: [PATCH 137/144] Minor rename fixes --- .../Graphics/Materials/MaterialParams.cpp | 2 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 28 +++++++++---------- .../Renderer/GI/GlobalSurfaceAtlasPass.h | 15 ++++++++-- .../Renderer/GlobalSignDistanceFieldPass.cpp | 8 +++--- .../Renderer/GlobalSignDistanceFieldPass.h | 2 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 4 +-- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 1e0da6213..87d4193ef 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -490,7 +490,7 @@ void MaterialParameter::Bind(BindMeta& meta) const Platform::MemoryClear(&bindingData, sizeof(bindingData)); for (int32 i = 0; i < 4; i++) meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); - *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.GlobalSDF; + *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants; break; } default: diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 87d979d62..8803c7e5b 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -549,10 +549,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } // Init constants - result.GlobalSurfaceAtlas.ViewPos = renderContext.View.Position; - result.GlobalSurfaceAtlas.Resolution = (float)resolution; - result.GlobalSurfaceAtlas.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; - result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); + result.Constants.ViewPos = renderContext.View.Position; + result.Constants.Resolution = (float)resolution; + result.Constants.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; + result.Constants.ObjectsCount = surfaceAtlasData.Objects.Count(); // Cull objects into chunks (for faster Atlas sampling) if (surfaceAtlasData.Objects.Count() != 0) @@ -629,7 +629,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co data.ViewNearPlane = renderContext.View.Near; data.ViewFarPlane = renderContext.View.Far; data.CulledObjectsCapacity = objectsBufferCapacity; - data.GlobalSurfaceAtlas = result.GlobalSurfaceAtlas; + data.GlobalSurfaceAtlas = result.Constants; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); static_assert(GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION % GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE == 0, "Invalid chunks resolution/groups setting."); @@ -710,8 +710,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->BindCB(0, _cb0); Data0 data; data.ViewWorldPos = renderContext.View.Position; - data.GlobalSDF = bindingDataSDF.GlobalSDF; - data.GlobalSurfaceAtlas = result.GlobalSurfaceAtlas; + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = result.Constants; // Shade object tiles influenced by lights to calculate direct lighting // TODO: reduce redraw frequency for static lights (StaticFlags::Lightmap) @@ -825,8 +825,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex data.ViewFarPlane = renderContext.View.Far; for (int32 i = 0; i < 4; i++) data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); - data.GlobalSDF = bindingDataSDF.GlobalSDF; - data.GlobalSurfaceAtlas = bindingData.GlobalSurfaceAtlas; + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = bindingData.Constants; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -837,7 +837,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex } context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); - context->BindSR(10, bindingData.Atlas[0]->View()); + context->BindSR(10, bindingData.AtlasDepth->View()); context->SetState(_psDebug); context->SetRenderTarget(output->View()); { @@ -845,23 +845,23 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex Vector2 outputSizeTwoThird = outputSize * 0.666f; // Full screen - direct light - context->BindSR(11, bindingData.Atlas[4]->View()); + context->BindSR(11, bindingData.AtlasLighting->View()); context->SetViewport(outputSize.X, outputSize.Y); context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); // Bottom left - diffuse - context->BindSR(11, bindingData.Atlas[1]->View()); + context->BindSR(11, bindingData.AtlasGBuffer0->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); context->DrawFullscreenTriangle(); // Bottom middle - normals - context->BindSR(11, bindingData.Atlas[2]->View()); + context->BindSR(11, 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.Atlas[3]->View()); + context->BindSR(11, bindingData.AtlasGBuffer2->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, outputSizeTwoThird.Y, outputSizeThird.X, outputSizeThird.Y)); context->DrawFullscreenTriangle(); } diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index 81eba1bcb..ca33ddc68 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -24,10 +24,21 @@ public: // Binding data for the GPU. struct BindingData { - GPUTexture* Atlas[5]; + union + { + struct + { + GPUTexture* AtlasDepth; + GPUTexture* AtlasGBuffer0; + GPUTexture* AtlasGBuffer1; + GPUTexture* AtlasGBuffer2; + GPUTexture* AtlasLighting; + }; + GPUTexture* Atlas[5]; + }; GPUBuffer* Chunks; GPUBuffer* CulledObjects; - ConstantsData GlobalSurfaceAtlas; + ConstantsData Constants; }; private: diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index a36d780e6..c94b41b6f 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -732,12 +732,12 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const float maxDistance = distance * 2; const float voxelSize = maxDistance / resolution; const Vector3 center = cascade.Position; - result.GlobalSDF.CascadePosDistance[cascadeIndex] = Vector4(center, distance); - result.GlobalSDF.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, distance); + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; result.Cascades[cascadeIndex] = cascade.Texture; result.CascadeMips[cascadeIndex] = cascade.Mip; } - result.GlobalSDF.Resolution = (float)resolution; + result.Constants.Resolution = (float)resolution; sdfData.Result = result; return false; } @@ -760,7 +760,7 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC data.ViewFarPlane = renderContext.View.Far; for (int32 i = 0; i < 4; i++) data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); - data.GlobalSDF = bindingData.GlobalSDF; + data.GlobalSDF = bindingData.Constants; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 6460980af..5e1f0461a 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -24,7 +24,7 @@ public: { GPUTexture* Cascades[4]; GPUTexture* CascadeMips[4]; - ConstantsData GlobalSDF; + ConstantsData Constants; }; private: diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 16d569158..b9e805e80 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -22,7 +22,7 @@ GlobalSurfaceAtlasData GlobalSurfaceAtlas; LightData Light; META_CB_END -struct AtlasVertexIput +struct AtlasVertexInput { float2 Position : POSITION0; float2 TileUV : TEXCOORD0; @@ -41,7 +41,7 @@ META_VS(true, FEATURE_LEVEL_SM5) META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) -AtlasVertexOutput VS_Atlas(AtlasVertexIput input) +AtlasVertexOutput VS_Atlas(AtlasVertexInput input) { AtlasVertexOutput output; output.Position = float4(input.Position, 1, 1); From 69b9c1b9ee1887cc81ced4b3a6cb0294905d03d4 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 12 May 2022 13:46:17 +0200 Subject: [PATCH 138/144] Add editorconfig for shader files --- Source/.editorconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/.editorconfig b/Source/.editorconfig index 06e4f1454..8f9d7f7b0 100644 --- a/Source/.editorconfig +++ b/Source/.editorconfig @@ -18,6 +18,11 @@ indent_size = 4 indent_style = space indent_size = 4 +# Shader files +[*.{hlsl,shader,glsl}] +indent_style = space +indent_size = 4 + # XAML files [*.xaml] indent_style = space From f7e48d9b23b2dfbc4fa330b2883e1c91c2bff3b8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 12 May 2022 13:47:19 +0200 Subject: [PATCH 139/144] Add shader getter to `IMaterial` interface --- Source/Engine/Content/Assets/Material.cpp | 7 ++++++- Source/Engine/Content/Assets/Material.h | 1 + Source/Engine/Content/Assets/MaterialInstance.cpp | 5 +++++ Source/Engine/Content/Assets/MaterialInstance.h | 1 + Source/Engine/Graphics/Materials/IMaterial.h | 7 +++++++ Source/Engine/Graphics/Materials/MaterialShader.h | 3 +-- Source/Engine/Renderer/Editor/LODPreview.cpp | 5 +++++ Source/Engine/Renderer/Editor/LODPreview.h | 1 + Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp | 5 +++++ Source/Engine/Renderer/Editor/LightmapUVsDensity.h | 1 + Source/Engine/Renderer/Editor/MaterialComplexity.cpp | 5 +++++ Source/Engine/Renderer/Editor/MaterialComplexity.h | 1 + Source/Engine/Renderer/Editor/VertexColors.cpp | 5 +++++ Source/Engine/Renderer/Editor/VertexColors.h | 1 + 14 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 2b715da0c..baef3adce 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -48,6 +48,11 @@ const MaterialInfo& Material::GetInfo() const return EmptyInfo; } +GPUShader* Material::GetShader() const +{ + return _materialShader ? _materialShader->GetShader() : nullptr; +} + bool Material::IsReady() const { return _materialShader && _materialShader->IsReady(); @@ -431,7 +436,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "USE_GBUFFER_CUSTOM_DATA", Numbers[useCustomData ? 1 : 0] }); options.Macros.Add({ "USE_REFLECTIONS", Numbers[info.FeaturesFlags & MaterialFeaturesFlags::DisableReflections ? 0 : 1] }); if (!(info.FeaturesFlags & MaterialFeaturesFlags::DisableReflections) && info.FeaturesFlags & MaterialFeaturesFlags::ScreenSpaceReflections) - options.Macros.Add({ "MATERIAL_REFLECTIONS", Numbers[1]}); + options.Macros.Add({ "MATERIAL_REFLECTIONS", Numbers[1] }); options.Macros.Add({ "USE_FOG", Numbers[info.FeaturesFlags & MaterialFeaturesFlags::DisableFog ? 0 : 1] }); if (useForward) options.Macros.Add({ "USE_PIXEL_NORMAL_OFFSET_REFRACTION", Numbers[info.FeaturesFlags & MaterialFeaturesFlags::PixelNormalOffsetRefraction ? 1 : 0] }); diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index a8bf9a629..676680968 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -45,6 +45,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index e0e57c6cb..9d94aeed4 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -143,6 +143,11 @@ const MaterialInfo& MaterialInstance::GetInfo() const return EmptyInfo; } +GPUShader* MaterialInstance::GetShader() const +{ + return _baseMaterial ? _baseMaterial->GetShader() : nullptr; +} + bool MaterialInstance::IsReady() const { return IsLoaded() && _baseMaterial && _baseMaterial->IsReady(); diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index f5143aadd..d5df025be 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -59,6 +59,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index f22feda91..c59aebb58 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -5,6 +5,7 @@ #include "MaterialInfo.h" struct MaterialParamsLink; +class GPUShader; class GPUContext; class GPUTextureView; class RenderBuffers; @@ -26,6 +27,12 @@ public: /// The constant reference to the material descriptor. virtual const MaterialInfo& GetInfo() const = 0; + /// + /// Gets the shader resource. + /// + /// The material shader resource. + virtual GPUShader* GetShader() const = 0; + /// /// Determines whether material is a surface shader. /// diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 4c789b2ea..8257147c8 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -98,8 +98,6 @@ public: /// The created and loaded material or null if failed. static MaterialShader* CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info); - GPUShader* GetShader() const; - /// /// Clears the loaded data. /// @@ -114,5 +112,6 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; }; diff --git a/Source/Engine/Renderer/Editor/LODPreview.cpp b/Source/Engine/Renderer/Editor/LODPreview.cpp index 57b79083d..612de21a8 100644 --- a/Source/Engine/Renderer/Editor/LODPreview.cpp +++ b/Source/Engine/Renderer/Editor/LODPreview.cpp @@ -22,6 +22,11 @@ const MaterialInfo& LODPreviewMaterialShader::GetInfo() const return _material->GetInfo(); } +GPUShader* LODPreviewMaterialShader::GetShader() const +{ + return _material->GetShader(); +} + bool LODPreviewMaterialShader::IsReady() const { return _material && _material->IsReady(); diff --git a/Source/Engine/Renderer/Editor/LODPreview.h b/Source/Engine/Renderer/Editor/LODPreview.h index c8df9cadd..8de84a7cf 100644 --- a/Source/Engine/Renderer/Editor/LODPreview.h +++ b/Source/Engine/Renderer/Editor/LODPreview.h @@ -30,6 +30,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; bool CanUseInstancing(InstancingHandler& handler) const override; DrawPass GetDrawModes() const override; diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 6b7ac9b5f..d5a6d11e8 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -53,6 +53,11 @@ const MaterialInfo& LightmapUVsDensityMaterialShader::GetInfo() const return _info; } +GPUShader* LightmapUVsDensityMaterialShader::GetShader() const +{ + return _shader->GetShader(); +} + bool LightmapUVsDensityMaterialShader::IsReady() const { return _shader && _shader->IsLoaded(); diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h index a8905094a..9c293ca57 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h @@ -40,6 +40,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; void Bind(BindParameters& params) override; diff --git a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp index 81c125d43..e8e1feb97 100644 --- a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp +++ b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp @@ -29,6 +29,11 @@ const MaterialInfo& MaterialComplexityMaterialShader::WrapperShader::GetInfo() c return Info; } +GPUShader* MaterialComplexityMaterialShader::WrapperShader::GetShader() const +{ + return MaterialAsset->GetShader(); +} + bool MaterialComplexityMaterialShader::WrapperShader::IsReady() const { return MaterialAsset && MaterialAsset->IsReady(); diff --git a/Source/Engine/Renderer/Editor/MaterialComplexity.h b/Source/Engine/Renderer/Editor/MaterialComplexity.h index 292f736a5..716964f0d 100644 --- a/Source/Engine/Renderer/Editor/MaterialComplexity.h +++ b/Source/Engine/Renderer/Editor/MaterialComplexity.h @@ -25,6 +25,7 @@ private: MaterialDomain Domain; AssetReference MaterialAsset; const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; bool CanUseInstancing(InstancingHandler& handler) const override; DrawPass GetDrawModes() const override; diff --git a/Source/Engine/Renderer/Editor/VertexColors.cpp b/Source/Engine/Renderer/Editor/VertexColors.cpp index 965ba3fd9..4b4a8fc3c 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.cpp +++ b/Source/Engine/Renderer/Editor/VertexColors.cpp @@ -41,6 +41,11 @@ const MaterialInfo& VertexColorsMaterialShader::GetInfo() const return _info; } +GPUShader* VertexColorsMaterialShader::GetShader() const +{ + return _shader->GetShader(); +} + bool VertexColorsMaterialShader::IsReady() const { return _shader && _shader->IsLoaded(); diff --git a/Source/Engine/Renderer/Editor/VertexColors.h b/Source/Engine/Renderer/Editor/VertexColors.h index ffeca3245..79ea22ae0 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.h +++ b/Source/Engine/Renderer/Editor/VertexColors.h @@ -38,6 +38,7 @@ public: // [IMaterial] const MaterialInfo& GetInfo() const override; + GPUShader* GetShader() const override; bool IsReady() const override; DrawPass GetDrawModes() const override; void Bind(BindParameters& params) override; From c74b66f728732d2e7d2c75756085606f6c762c6e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 16 May 2022 10:05:15 +0200 Subject: [PATCH 140/144] Add `Quaternion::RotationMatrix` from `Matrix3x3` rotation --- Source/Engine/Core/Math/Quaternion.cpp | 54 ++++++++++++++++++++++++++ Source/Engine/Core/Math/Quaternion.h | 6 +++ 2 files changed, 60 insertions(+) diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp index df2ae7da3..b03ab8133 100644 --- a/Source/Engine/Core/Math/Quaternion.cpp +++ b/Source/Engine/Core/Math/Quaternion.cpp @@ -4,6 +4,7 @@ #include "Vector3.h" #include "Vector4.h" #include "Matrix.h" +#include "Matrix3x3.h" #include "Math.h" #include "../Types/String.h" @@ -208,6 +209,59 @@ void Quaternion::RotationMatrix(const Matrix& matrix, Quaternion& result) result.Normalize(); } +void Quaternion::RotationMatrix(const Matrix3x3& matrix, Quaternion& result) +{ + float sqrtV; + float half; + const float scale = matrix.M11 + matrix.M22 + matrix.M33; + + if (scale > 0.0f) + { + sqrtV = Math::Sqrt(scale + 1.0f); + result.W = sqrtV * 0.5f; + sqrtV = 0.5f / sqrtV; + + result.X = (matrix.M23 - matrix.M32) * sqrtV; + result.Y = (matrix.M31 - matrix.M13) * sqrtV; + result.Z = (matrix.M12 - matrix.M21) * sqrtV; + } + else if (matrix.M11 >= matrix.M22 && matrix.M11 >= matrix.M33) + { + sqrtV = Math::Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33); + half = 0.5f / sqrtV; + + result = Quaternion( + 0.5f * sqrtV, + (matrix.M12 + matrix.M21) * half, + (matrix.M13 + matrix.M31) * half, + (matrix.M23 - matrix.M32) * half); + } + else if (matrix.M22 > matrix.M33) + { + sqrtV = Math::Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33); + half = 0.5f / sqrtV; + + result = Quaternion( + (matrix.M21 + matrix.M12) * half, + 0.5f * sqrtV, + (matrix.M32 + matrix.M23) * half, + (matrix.M31 - matrix.M13) * half); + } + else + { + sqrtV = Math::Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22); + half = 0.5f / sqrtV; + + result = Quaternion( + (matrix.M31 + matrix.M13) * half, + (matrix.M32 + matrix.M23) * half, + 0.5f * sqrtV, + (matrix.M12 - matrix.M21) * half); + } + + result.Normalize(); +} + void Quaternion::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up, Quaternion& result) { Matrix matrix; diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 4fea7e69c..77e3a51fe 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -10,6 +10,7 @@ struct Vector2; struct Vector3; struct Vector4; struct Matrix; +struct Matrix3x3; /// /// Represents a four dimensional mathematical quaternion. Euler angles are stored in: pitch, yaw, roll order (x, y, z). @@ -566,6 +567,11 @@ public: // @param result When the method completes, contains the newly created quaternion static void RotationMatrix(const Matrix& matrix, Quaternion& result); + // Creates a quaternion given a rotation matrix + // @param matrix The rotation matrix + // @param result When the method completes, contains the newly created quaternion + static void RotationMatrix(const Matrix3x3& matrix, Quaternion& result); + // Creates a left-handed, look-at quaternion // @param eye The position of the viewer's eye // @param target The camera look-at target From 8125e406bb139a31543f4cb0002764c0f9772b37 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 16 May 2022 10:06:12 +0200 Subject: [PATCH 141/144] Add new math utilities to shaders library --- Source/Shaders/Math.hlsl | 15 +++++++++++ Source/Shaders/Octahedral.hlsl | 47 ++++++++++++++++++++++++++++++++++ Source/Shaders/Quaternion.hlsl | 18 +++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 Source/Shaders/Octahedral.hlsl create mode 100644 Source/Shaders/Quaternion.hlsl diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index d211779d3..0299ebf6d 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -54,6 +54,21 @@ float4 Square(float4 x) return x * x; } +float Max2(float2 x) +{ + return max(x.x, x.y); +} + +float Max3(float3 x) +{ + return max(x.x, max(x.y, x.z)); +} + +float Max4(float4 x) +{ + return max(x.x, max(x.y, max(x.z, x.w))); +} + float Pow2(float x) { return x * x; diff --git a/Source/Shaders/Octahedral.hlsl b/Source/Shaders/Octahedral.hlsl new file mode 100644 index 000000000..7cc1092a4 --- /dev/null +++ b/Source/Shaders/Octahedral.hlsl @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#ifndef __OCTAHEDRAL__ +#define __OCTAHEDRAL__ + +// Implementation based on: +// "A Survey of Efficient Representations for Independent Unit Vectors", Journal of Computer Graphics Tools (JCGT), vol. 3, no. 2, 1-30, 2014 +// Zina H. Cigolle, Sam Donow, Daniel Evangelakos, Michael Mara, Morgan McGuire, and Quirin Meyer +// http://jcgt.org/published/0003/02/01/ + +float GetSignNotZero(float v) +{ + return v >= 0.0f ? 1.0f : -1.0f; +} + +float2 GetSignNotZero(float2 v) +{ + return float2(GetSignNotZero(v.x), GetSignNotZero(v.y)); +} + +// Calculates octahedral coordinates (in range [-1; 1]) for direction vector +float2 GetOctahedralCoords(float3 direction) +{ + float2 uv = direction.xy * (1.0f / (abs(direction.x) + abs(direction.y) + abs(direction.z))); + if (direction.z < 0.0f) + uv = (1.0f - abs(uv.yx)) * GetSignNotZero(uv.xy); + return uv; +} + +// Calculates octahedral coordinates (in range [-1; 1]) for 2D texture (assuming 1 pixel border around) +float2 GetOctahedralCoords(uint2 texCoords, uint resolution) +{ + float2 uv = float2(texCoords.x % resolution, texCoords.y % resolution) + 0.5f; + uv.xy /= float(resolution); + return uv * 2.0f - 1.0f; +} + +// Gets the direction vector from octahedral coordinates +float3 GetOctahedralDirection(float2 coords) +{ + float3 direction = float3(coords.x, coords.y, 1.0f - abs(coords.x) - abs(coords.y)); + if (direction.z < 0.0f) + direction.xy = (1.0f - abs(direction.yx)) * GetSignNotZero(direction.xy); + return normalize(direction); +} + +#endif diff --git a/Source/Shaders/Quaternion.hlsl b/Source/Shaders/Quaternion.hlsl new file mode 100644 index 000000000..09b5c49b3 --- /dev/null +++ b/Source/Shaders/Quaternion.hlsl @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#ifndef __QUATERNION__ +#define __QUATERNION__ + +float4 QuaternionMultiply(float4 q1, float4 q2) +{ + return float4(q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz), q1.w * q2.w - dot(q1.xyz, q2.xyz)); +} + +float3 QuaternionRotate(float4 q, float3 v) +{ + float3 b = q.xyz; + float b2 = dot(b, b); + return (v * (q.w * q.w - b2) + b * (dot(v, b) * 2.f) + cross(b, v) * (q.w * 2.f)); +} + +#endif From f9d5c745079b4b9991f72c357994a6779635188c Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 17 May 2022 14:08:48 +0200 Subject: [PATCH 142/144] Disable GI in editor asset viewports by default --- Source/Engine/Graphics/Enums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index 0125fb94c..55398accb 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -1017,7 +1017,7 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : int64 /// /// Default flags for materials/models previews generating. /// - DefaultAssetPreview = Reflections | Decals | GI | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows, + DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows, }; DECLARE_ENUM_OPERATORS(ViewFlags); From c3b2c55d5153e165f04e9c1923371f218c8d1c3d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 19 May 2022 12:00:12 +0200 Subject: [PATCH 143/144] Fix normal map when importing materials for model files --- Source/Engine/ContentImporters/CreateMaterial.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/ContentImporters/CreateMaterial.cpp b/Source/Engine/ContentImporters/CreateMaterial.cpp index 36caacb23..1dd5e7f05 100644 --- a/Source/Engine/ContentImporters/CreateMaterial.cpp +++ b/Source/Engine/ContentImporters/CreateMaterial.cpp @@ -61,13 +61,13 @@ namespace return &node; } - ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId) + ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId, bool normalMap = false) { if (!textureId.IsValid()) return nullptr; auto& node = layer->Graph.Nodes.AddOne(); node.ID = layer->Graph.Nodes.Count(); - node.Type = GRAPH_NODE_MAKE_TYPE(5, 1); + node.Type = GRAPH_NODE_MAKE_TYPE(5, normalMap ? 4 : 1); node.Boxes.Resize(7); node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector2); // UVs node.Boxes[6] = MaterialGraphBox(&node, 6, VariantType::Object); // Texture Reference @@ -178,7 +178,7 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context) CONNECT(layer->Root->Boxes[static_cast(MaterialGraphBoxes::Emissive)], emissiveColor->Boxes[0]); SET_POS(emissiveColor, Vector2(-493.5272f, -2.926111f)); } - auto normalMap = AddTextureNode(layer, options.Normals.Texture); + auto normalMap = AddTextureNode(layer, options.Normals.Texture, true); if (normalMap) { CONNECT(layer->Root->Boxes[static_cast(MaterialGraphBoxes::Normal)], normalMap->Boxes[1]); From 1a64df9116d7e7deb4d711cba7040dcf663899f0 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 19 May 2022 16:03:54 +0200 Subject: [PATCH 144/144] Add more math utilities --- Source/Engine/Core/Math/CollisionsHelper.cpp | 10 ++++++++++ Source/Engine/Core/Math/CollisionsHelper.h | 6 ++++++ Source/Shaders/Math.hlsl | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index f778fa4ec..48e262286 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -1437,6 +1437,16 @@ bool CollisionsHelper::LineIntersectsRect(const Vector2& p1, const Vector2& p2, return (topoverlap < botoverlap) && (!((botoverlap < t) || (topoverlap > b)));*/ } +Vector2 CollisionsHelper::LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax) +{ + const Vector3 invDirection = 1.0f / (lineEnd - lineStart); + const Vector3 enterIntersection = (boxMin - lineStart) * invDirection; + const Vector3 exitIntersection = (boxMax - lineStart) * invDirection; + const Vector3 minIntersections = Vector3::Min(enterIntersection, exitIntersection); + const Vector3 maxIntersections = Vector3::Max(enterIntersection, exitIntersection); + return Vector2(Math::Saturate(minIntersections.MaxValue()), Math::Saturate(maxIntersections.MinValue())); +} + bool CollisionsHelper::IsPointInTriangle(const Vector2& point, const Vector2& a, const Vector2& b, const Vector2& c) { const Vector2 an = a - point; diff --git a/Source/Engine/Core/Math/CollisionsHelper.h b/Source/Engine/Core/Math/CollisionsHelper.h index 9720369c1..ef1791ec1 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.h +++ b/Source/Engine/Core/Math/CollisionsHelper.h @@ -569,6 +569,12 @@ public: /// True if line intersects with the rectangle static bool LineIntersectsRect(const Vector2& p1, const Vector2& p2, const Rectangle& rect); + // Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd). + // Returns the intersections on the line (x - closest, y - furthest). + // Line hits the box if: intersections.x < intersections.y. + // Hit point is: hitPoint = lineStart + (lineEnd - lineStart) * intersections.x/y. + static Vector2 LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax); + /// /// Determines whether the given 2D point is inside the specified triangle. /// diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index 0299ebf6d..037efccba 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -54,6 +54,21 @@ float4 Square(float4 x) return x * x; } +float Min2(float2 x) +{ + return min(x.x, x.y); +} + +float Min3(float3 x) +{ + return min(x.x, min(x.y, x.z)); +} + +float Min4(float4 x) +{ + return min(x.x, min(x.y, min(x.z, x.w))); +} + float Max2(float2 x) { return max(x.x, x.y);