From 0f08e693cb1ba16c0d7bc05628a447ed16b118f1 Mon Sep 17 00:00:00 2001 From: HMasataka Date: Sat, 7 Mar 2026 15:10:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?IBuffer=20=E3=81=AB=20Update=20=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HOST_VISIBLE バッファの内容を毎フレーム更新できるようにする。 VulkanBuffer では vkMapMemory/NativeMemory.Copy/vkUnmapMemory で実装。 Co-Authored-By: Claude Opus 4.6 --- .../Platform/Xr/BoundarySystemTests.cs | 1 + .../Rendering/PipelineSelectionTests.cs | 2 ++ .../StereoDeferredRenderSystemExecuteTests.cs | 3 +++ .../StereoForwardPlusRenderSystemTests.cs | 1 + src/Seed.Engine/Graphics/IBuffer.cs | 6 +++++ .../Graphics/Vulkan/VulkanBuffer.cs | 23 +++++++++++++++++++ 6 files changed, 36 insertions(+) diff --git a/src/Seed.Engine.Tests/Platform/Xr/BoundarySystemTests.cs b/src/Seed.Engine.Tests/Platform/Xr/BoundarySystemTests.cs index e27561e..95e73a8 100644 --- a/src/Seed.Engine.Tests/Platform/Xr/BoundarySystemTests.cs +++ b/src/Seed.Engine.Tests/Platform/Xr/BoundarySystemTests.cs @@ -454,6 +454,7 @@ public StubBuffer(BufferUsage usage, ulong size) public BufferUsage Usage { get; } public ulong SizeInBytes { get; } + public void Update(ReadOnlySpan data) { } public void Dispose() { } } diff --git a/src/Seed.Engine.Tests/Rendering/PipelineSelectionTests.cs b/src/Seed.Engine.Tests/Rendering/PipelineSelectionTests.cs index 0b3a039..daee852 100644 --- a/src/Seed.Engine.Tests/Rendering/PipelineSelectionTests.cs +++ b/src/Seed.Engine.Tests/Rendering/PipelineSelectionTests.cs @@ -1,3 +1,4 @@ +using System; using Xunit; using Seed.Engine.Ecs; @@ -364,6 +365,7 @@ public StubBuffer(BufferUsage usage, ulong size) public BufferUsage Usage { get; } public ulong SizeInBytes { get; } + public void Update(ReadOnlySpan data) { } public void Dispose() { } } } diff --git a/src/Seed.Engine.Tests/Rendering/StereoDeferredRenderSystemExecuteTests.cs b/src/Seed.Engine.Tests/Rendering/StereoDeferredRenderSystemExecuteTests.cs index 15c37e8..2ff286a 100644 --- a/src/Seed.Engine.Tests/Rendering/StereoDeferredRenderSystemExecuteTests.cs +++ b/src/Seed.Engine.Tests/Rendering/StereoDeferredRenderSystemExecuteTests.cs @@ -142,6 +142,8 @@ private static StereoDeferredRenderSystem CreateSystem( null, null, null, + null, + null, -1, null, null, @@ -393,6 +395,7 @@ public StubBuffer(BufferUsage usage, ulong size) public BufferUsage Usage { get; } public ulong SizeInBytes { get; } + public void Update(ReadOnlySpan data) { } public void Dispose() { } } diff --git a/src/Seed.Engine.Tests/Rendering/StereoForwardPlusRenderSystemTests.cs b/src/Seed.Engine.Tests/Rendering/StereoForwardPlusRenderSystemTests.cs index 579d1c1..e0e5947 100644 --- a/src/Seed.Engine.Tests/Rendering/StereoForwardPlusRenderSystemTests.cs +++ b/src/Seed.Engine.Tests/Rendering/StereoForwardPlusRenderSystemTests.cs @@ -550,6 +550,7 @@ public StubBuffer(BufferUsage usage, ulong size) public BufferUsage Usage { get; } public ulong SizeInBytes { get; } + public void Update(ReadOnlySpan data) { } public void Dispose() { } } diff --git a/src/Seed.Engine/Graphics/IBuffer.cs b/src/Seed.Engine/Graphics/IBuffer.cs index a74cf15..9b93b09 100644 --- a/src/Seed.Engine/Graphics/IBuffer.cs +++ b/src/Seed.Engine/Graphics/IBuffer.cs @@ -16,4 +16,10 @@ public interface IBuffer : IDisposable /// Gets the size in bytes of this buffer. /// ulong SizeInBytes { get; } + + /// + /// Updates the buffer contents with new data. + /// The buffer must have been created with host-visible memory. + /// + void Update(ReadOnlySpan data); } diff --git a/src/Seed.Engine/Graphics/Vulkan/VulkanBuffer.cs b/src/Seed.Engine/Graphics/Vulkan/VulkanBuffer.cs index 78968df..6e6c51a 100644 --- a/src/Seed.Engine/Graphics/Vulkan/VulkanBuffer.cs +++ b/src/Seed.Engine/Graphics/Vulkan/VulkanBuffer.cs @@ -125,6 +125,29 @@ private Result CreateBuffer(ReadOnlySpan data) } } + /// + public void Update(ReadOnlySpan data) + { + unsafe + { + void* mappedData; + VkResult result = VulkanNative.vkMapMemory( + _vulkanDevice.Device, _memory, 0, SizeInBytes, 0, &mappedData); + if (result != VkResult.VK_SUCCESS) + { + return; + } + + fixed (byte* pData = data) + { + System.Runtime.InteropServices.NativeMemory.Copy( + pData, mappedData, (nuint)data.Length); + } + + VulkanNative.vkUnmapMemory(_vulkanDevice.Device, _memory); + } + } + /// public void Dispose() { From d10d6e5fc85b68cb9ae08cc47c04a1b8d3b37268 Mon Sep 17 00:00:00 2001 From: HMasataka Date: Sat, 7 Mar 2026 15:11:04 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=89=E3=82=A6=20unif?= =?UTF-8?q?orm=20=E3=83=90=E3=83=83=E3=83=95=E3=82=A1=E3=82=92=E6=AF=8E?= =?UTF-8?q?=E3=83=95=E3=83=AC=E3=83=BC=E3=83=A0=E6=9B=B4=E6=96=B0=E3=81=97?= =?UTF-8?q?=E3=81=A6=E5=BD=B1=E3=82=BA=E3=83=AC=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ShadowData (lightSpaceVP + inverseVP) が初期化時に一度だけ書き込まれ 毎フレーム更新されていなかったため、カメラや物体が動くと ライティングシェーダのワールド座標復元がずれて影が元の位置に残っていた。 DeferredRenderSystem: 現在のカメラ VP の逆行列でバッファを更新 StereoDeferredRenderSystem: 左右の目ごとに inverseVP を更新 Co-Authored-By: Claude Opus 4.6 --- src/Seed.Engine.App/Program.cs | 2 +- .../Rendering/DeferredRenderSystem.cs | 16 ++++++++++ .../Rendering/StereoDeferredRenderSystem.cs | 30 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Seed.Engine.App/Program.cs b/src/Seed.Engine.App/Program.cs index 8e3fda9..390ee59 100644 --- a/src/Seed.Engine.App/Program.cs +++ b/src/Seed.Engine.App/Program.cs @@ -1176,7 +1176,7 @@ private static void Main() gBufferPipeline, lightingPipeline, lightingDescriptorSet, resources, imageAvailableSemaphore, renderFinishedSemaphore, inFlightFence, - shadowMap, shadowPipeline, + shadowMap, shadowPipeline, shadowBuffer, pointLightPipeline, pointLightDescriptorSet, sphereMeshId, spotLightPipeline, spotLightDescriptorSet, coneMeshId)); diff --git a/src/Seed.Engine/Rendering/DeferredRenderSystem.cs b/src/Seed.Engine/Rendering/DeferredRenderSystem.cs index 3553b0e..86a4eec 100644 --- a/src/Seed.Engine/Rendering/DeferredRenderSystem.cs +++ b/src/Seed.Engine/Rendering/DeferredRenderSystem.cs @@ -29,6 +29,7 @@ public sealed class DeferredRenderSystem : ISystem private readonly IRenderTarget? _shadowMap; private readonly IPipeline? _shadowPipeline; + private readonly IBuffer? _shadowBuffer; private readonly IPipeline? _pointLightPipeline; private readonly IDescriptorSet? _pointLightDescriptorSet; @@ -62,6 +63,7 @@ public DeferredRenderSystem( IntPtr inFlightFence, IRenderTarget? shadowMap = null, IPipeline? shadowPipeline = null, + IBuffer? shadowBuffer = null, IPipeline? pointLightPipeline = null, IDescriptorSet? pointLightDescriptorSet = null, int sphereMeshId = -1, @@ -82,6 +84,7 @@ public DeferredRenderSystem( _inFlightFence = inFlightFence; _shadowMap = shadowMap; _shadowPipeline = shadowPipeline; + _shadowBuffer = shadowBuffer; _pointLightPipeline = pointLightPipeline; _pointLightDescriptorSet = pointLightDescriptorSet; _sphereMeshId = sphereMeshId; @@ -154,6 +157,19 @@ public void Execute(World world) Matrix4x4 lightSpaceVP = ComputeLightSpaceVP(world); ExecuteShadowPass(world, lightSpaceVP); _commandBuffer.PipelineBarrier(); + + if (_shadowBuffer != null) + { + var invertResult = viewProjection.Invert(); + Matrix4x4 inverseVP = invertResult.IsSuccess + ? invertResult.Value + : Matrix4x4.Identity; + + Span shadowData = stackalloc byte[128]; + MemoryMarshal.Write(shadowData, in lightSpaceVP); + MemoryMarshal.Write(shadowData.Slice(64), in inverseVP); + _shadowBuffer.Update(shadowData); + } } ExecuteGBufferPass(world, viewProjection); diff --git a/src/Seed.Engine/Rendering/StereoDeferredRenderSystem.cs b/src/Seed.Engine/Rendering/StereoDeferredRenderSystem.cs index 36442de..c1d1111 100644 --- a/src/Seed.Engine/Rendering/StereoDeferredRenderSystem.cs +++ b/src/Seed.Engine/Rendering/StereoDeferredRenderSystem.cs @@ -33,6 +33,8 @@ public sealed class StereoDeferredRenderSystem : ISystem private readonly IRenderTarget? _shadowMap; private readonly IPipeline? _shadowPipeline; + private readonly IBuffer? _leftShadowBuffer; + private readonly IBuffer? _rightShadowBuffer; private readonly IPipeline? _pointLightPipeline; private readonly IDescriptorSet? _leftPointLightDescriptorSet; @@ -76,6 +78,8 @@ public StereoDeferredRenderSystem( float farPlane, IRenderTarget? shadowMap, IPipeline? shadowPipeline, + IBuffer? leftShadowBuffer, + IBuffer? rightShadowBuffer, IPipeline? pointLightPipeline, IDescriptorSet? leftPointLightDescriptorSet, IDescriptorSet? rightPointLightDescriptorSet, @@ -103,6 +107,8 @@ public StereoDeferredRenderSystem( _farPlane = farPlane; _shadowMap = shadowMap; _shadowPipeline = shadowPipeline; + _leftShadowBuffer = leftShadowBuffer; + _rightShadowBuffer = rightShadowBuffer; _pointLightPipeline = pointLightPipeline; _leftPointLightDescriptorSet = leftPointLightDescriptorSet; _rightPointLightDescriptorSet = rightPointLightDescriptorSet; @@ -208,9 +214,10 @@ public void Execute(World world) } // Shadow pass is view-independent, execute once + Matrix4x4 lightSpaceVP = Matrix4x4.Identity; if (_shadowMap != null && _shadowPipeline != null) { - Matrix4x4 lightSpaceVP = ComputeLightSpaceVP(world); + lightSpaceVP = ComputeLightSpaceVP(world); ExecuteShadowPass(world, lightSpaceVP); _commandBuffer.PipelineBarrier(); } @@ -219,6 +226,7 @@ public void Execute(World world) var leftAcquire = _leftSwapChain.AcquireNextImage(IntPtr.Zero); if (leftAcquire.IsSuccess) { + UpdateShadowBuffer(_leftShadowBuffer, lightSpaceVP, leftVP); ExecuteGBufferPass(world, leftVP, _leftGBuffer); _commandBuffer.PipelineBarrier(); ExecuteLightingPass( @@ -232,6 +240,7 @@ public void Execute(World world) var rightAcquire = _rightSwapChain.AcquireNextImage(IntPtr.Zero); if (rightAcquire.IsSuccess) { + UpdateShadowBuffer(_rightShadowBuffer, lightSpaceVP, rightVP); ExecuteGBufferPass(world, rightVP, _rightGBuffer); _commandBuffer.PipelineBarrier(); ExecuteLightingPass( @@ -598,4 +607,23 @@ private Matrix4x4 ComputeLightSpaceVP(World world) return Matrix4x4.Identity; } + + private static void UpdateShadowBuffer( + IBuffer? shadowBuffer, Matrix4x4 lightSpaceVP, Matrix4x4 viewProjection) + { + if (shadowBuffer == null) + { + return; + } + + var invertResult = viewProjection.Invert(); + Matrix4x4 inverseVP = invertResult.IsSuccess + ? invertResult.Value + : Matrix4x4.Identity; + + Span data = stackalloc byte[128]; + MemoryMarshal.Write(data, in lightSpaceVP); + MemoryMarshal.Write(data.Slice(64), in inverseVP); + shadowBuffer.Update(data); + } }