Move reservoir history update to compute
This commit is contained in:
parent
3cb352d2e4
commit
4d0fcba656
6 changed files with 192 additions and 79 deletions
|
|
@ -576,10 +576,12 @@ w: invalidation code
|
|||
```
|
||||
|
||||
Current state: SDF, temporal Gaussian, and TubeField producers write current
|
||||
scene reservoir guide data and shared candidate rows. Reservoir presentation
|
||||
resolve reads current candidates plus ping-ponged structured reservoir-history
|
||||
rows, folds confidence and domain validity into validation, and writes the next
|
||||
structured history rows. Pixel-history MRTs are no longer the temporal owner.
|
||||
scene reservoir guide data and shared candidate rows. The shared reservoir
|
||||
history update compute pass reads current candidates plus ping-ponged structured
|
||||
reservoir-history rows, folds confidence and domain validity into validation,
|
||||
writes the next structured history rows, and emits the resolved HDR field
|
||||
texture consumed by bloom and presentation. Pixel-history MRTs and the
|
||||
presentation shader are no longer temporal owners.
|
||||
|
||||
### Phase C: Fractal Probe Pipeline
|
||||
|
||||
|
|
|
|||
|
|
@ -292,13 +292,14 @@ w: invalidation code
|
|||
```
|
||||
|
||||
The first live producers are SDF surfaces, temporal Gaussian splats, and
|
||||
TubeField candidates, but the schema is not surface-only. Resolve reads current
|
||||
candidate rows and previous structured reservoir-history rows, folds confidence
|
||||
and domain validity into validation, then writes the next structured reservoir
|
||||
history. This resolve is the first reservoir resolve implementation, not an
|
||||
independent TAA owner. Future ReSTIR/GRIS passes should extend the producer side
|
||||
of this schema with explicit Form/Appearance/Transport fields rather than
|
||||
packing more reservoir folklore into scene-control channels.
|
||||
TubeField candidates, but the schema is not surface-only. The shared reservoir
|
||||
history update compute pass reads current candidate rows and previous structured
|
||||
reservoir-history rows, folds confidence and domain validity into validation,
|
||||
writes the next structured reservoir history, and emits the resolved HDR field
|
||||
texture. Presentation samples that resolved texture; it does not write reservoir
|
||||
history. Future ReSTIR/GRIS passes should extend the producer side of this
|
||||
schema with explicit Form/Appearance/Transport fields rather than packing more
|
||||
reservoir folklore into scene-control channels.
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
|
|
@ -318,8 +319,9 @@ packing more reservoir folklore into scene-control channels.
|
|||
7. Weave reservoir confidence and temporal detail into structured reservoir
|
||||
history rows so reservoir resolve can distinguish stable reused evidence
|
||||
from fresh stochastic noise. The current implementation keeps four
|
||||
resolver-owned rows per pixel and no longer stores previous reservoir
|
||||
validity in pixel-history MRTs.
|
||||
resolver-owned rows per pixel, updates them in a shared compute pass, emits
|
||||
a resolved HDR field texture for bloom/presentation, and no longer stores
|
||||
previous reservoir validity in pixel-history MRTs.
|
||||
8. Add Mimir-facing candidate adapters only after the fractal path proves the
|
||||
contract: modality features are candidates, not a second reservoir system.
|
||||
9. Port the pure core to HLSL and add CPU/GPU parity fixtures.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
private const int RootFractalRadiosityReservoirSrv = 17;
|
||||
private const int RootBlueNoise = 18;
|
||||
private const int RootFieldReservoirCandidates = 19;
|
||||
private const int RootReservoirResolvedOutput = 20;
|
||||
private const int RootFusionFrameConstants = 0;
|
||||
private const int RootFusionSeeds = 1;
|
||||
private const int RootFusionSensorCameras = 2;
|
||||
|
|
@ -204,6 +205,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
private ID3D12PipelineState? bloomDownsamplePipelineState;
|
||||
private ID3D12PipelineState? bloomBlurHorizontalPipelineState;
|
||||
private ID3D12PipelineState? bloomBlurVerticalPipelineState;
|
||||
private ID3D12PipelineState? reservoirHistoryUpdatePipelineState;
|
||||
private ID3D12PipelineState? resolvePipelineState;
|
||||
private readonly ID3D12Fence fence;
|
||||
private readonly AutoResetEvent fenceEvent = new(false);
|
||||
|
|
@ -231,6 +233,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
private D3D12RenderTarget sceneCandidateMetadataRenderTarget;
|
||||
private D3D12RenderTarget sceneCandidateControlRenderTarget;
|
||||
private D3D12RenderTarget sceneCandidateReservoirGuideRenderTarget;
|
||||
private D3D12RenderTarget reservoirResolvedRenderTarget;
|
||||
private readonly Dictionary<string, D3D12RenderTarget> graphRenderTargets = new(StringComparer.Ordinal);
|
||||
private D3D12TrackedResource sceneDepthTarget;
|
||||
private D3D12DescriptorSlot sceneDepthStencilView;
|
||||
|
|
@ -433,6 +436,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
sceneCandidateMetadataRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-metadata-target", "Aquarium D3D12 Scene Candidate Metadata Target");
|
||||
sceneCandidateControlRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-control-target", "Aquarium D3D12 Scene Candidate Control Target");
|
||||
sceneCandidateReservoirGuideRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-reservoir-guide-target", "Aquarium D3D12 Scene Candidate Reservoir Guide Target");
|
||||
reservoirResolvedRenderTarget = CreateReservoirResolvedRenderTarget();
|
||||
sceneDepthStencilView = depthStencilViewArena.Allocate();
|
||||
sceneDepthTarget = CreateSceneDepthTarget(sceneDepthStencilView);
|
||||
CreateBloomRenderTargets();
|
||||
|
|
@ -981,6 +985,10 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
sceneCandidateControlRenderTarget.CreateShaderResourceView(device, frameResources.SceneCandidateControlDescriptor);
|
||||
frameResources.SceneCandidateReservoirGuideDescriptor = frameResources.TransientShaderDescriptors.Allocate();
|
||||
sceneCandidateReservoirGuideRenderTarget.CreateShaderResourceView(device, frameResources.SceneCandidateReservoirGuideDescriptor);
|
||||
frameResources.ReservoirResolvedDescriptor = frameResources.TransientShaderDescriptors.Allocate();
|
||||
reservoirResolvedRenderTarget.CreateShaderResourceView(device, frameResources.ReservoirResolvedDescriptor);
|
||||
frameResources.ReservoirResolvedUnorderedAccessDescriptor = frameResources.TransientShaderDescriptors.Allocate();
|
||||
reservoirResolvedRenderTarget.CreateUnorderedAccessView(device, frameResources.ReservoirResolvedUnorderedAccessDescriptor);
|
||||
frameResources.FieldReservoirCandidateUnorderedAccessDescriptor = frameResources.TransientShaderDescriptors.Allocate();
|
||||
fieldReservoirCandidateBuffer.CreateUnorderedAccessView(device, frameResources.FieldReservoirCandidateUnorderedAccessDescriptor);
|
||||
frameResources.FieldReservoirLockUnorderedAccessDescriptor = frameResources.TransientShaderDescriptors.Allocate();
|
||||
|
|
@ -1244,6 +1252,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
sceneCandidateControlRenderTarget.Dispose();
|
||||
sceneCandidateMetadataRenderTarget.Dispose();
|
||||
sceneCandidateRenderTarget.Dispose();
|
||||
reservoirResolvedRenderTarget.Dispose();
|
||||
sceneDepthTarget.Dispose();
|
||||
heightFieldRenderTarget.Dispose();
|
||||
for (var index = 0; index < frames.Length; index++)
|
||||
|
|
@ -1871,6 +1880,8 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
bloomBlurHorizontal.Name = "Aquarium D3D12 Bloom Blur Horizontal Pipeline";
|
||||
var bloomBlurVertical = CreateBloomBlurVerticalPipelineState(paths.Post);
|
||||
bloomBlurVertical.Name = "Aquarium D3D12 Bloom Blur Vertical Pipeline";
|
||||
var reservoirHistoryUpdate = CreateReservoirHistoryUpdatePipelineState(paths.Post);
|
||||
reservoirHistoryUpdate.Name = "Aquarium D3D12 Reservoir History Update Pipeline";
|
||||
var resolve = CreateResolvePipelineState(paths.Post);
|
||||
resolve.Name = "Aquarium D3D12 Resolve Pipeline";
|
||||
|
||||
|
|
@ -1901,6 +1912,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
bloomDownsample,
|
||||
bloomBlurHorizontal,
|
||||
bloomBlurVertical,
|
||||
reservoirHistoryUpdate,
|
||||
resolve);
|
||||
}
|
||||
|
||||
|
|
@ -1925,6 +1937,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
resourceRegistry.RemoveRenderTarget("scene-candidate-metadata-target");
|
||||
resourceRegistry.RemoveRenderTarget("scene-candidate-control-target");
|
||||
resourceRegistry.RemoveRenderTarget("scene-candidate-reservoir-guide-target");
|
||||
resourceRegistry.RemoveRenderTarget("reservoir-resolved-target");
|
||||
resourceRegistry.RemoveResource("scene-depth-target");
|
||||
resourceRegistry.RemoveRenderTarget("height-field-target");
|
||||
DisposeBloomRenderTargets();
|
||||
|
|
@ -1937,6 +1950,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
sceneCandidateMetadataRenderTarget.Dispose();
|
||||
sceneCandidateControlRenderTarget.Dispose();
|
||||
sceneCandidateReservoirGuideRenderTarget.Dispose();
|
||||
reservoirResolvedRenderTarget.Dispose();
|
||||
fieldReservoirCandidateBuffer.Dispose();
|
||||
fieldReservoirLockBuffer.Dispose();
|
||||
DisposeFieldReservoirHistoryBuffers();
|
||||
|
|
@ -1971,6 +1985,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
sceneCandidateMetadataRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-metadata-target", "Aquarium D3D12 Scene Candidate Metadata Target");
|
||||
sceneCandidateControlRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-control-target", "Aquarium D3D12 Scene Candidate Control Target");
|
||||
sceneCandidateReservoirGuideRenderTarget = CreateSceneAuxiliaryRenderTarget("scene-candidate-reservoir-guide-target", "Aquarium D3D12 Scene Candidate Reservoir Guide Target");
|
||||
reservoirResolvedRenderTarget = CreateReservoirResolvedRenderTarget();
|
||||
DisposeTubeFieldRestirBuffers();
|
||||
CreateTubeFieldRestirBuffers();
|
||||
CreateFieldReservoirBuffers();
|
||||
|
|
@ -2017,6 +2032,23 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
return target;
|
||||
}
|
||||
|
||||
private D3D12RenderTarget CreateReservoirResolvedRenderTarget()
|
||||
{
|
||||
var target = new D3D12RenderTarget(
|
||||
device,
|
||||
width,
|
||||
height,
|
||||
SceneHdrFormat,
|
||||
renderTargetViewArena.Allocate(),
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
new Color4(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
"Aquarium D3D12 Reservoir Resolved Target");
|
||||
resourceRegistry.Add("reservoir-resolved-target", target);
|
||||
return target;
|
||||
}
|
||||
|
||||
private void CreateFieldReservoirBuffers()
|
||||
{
|
||||
fieldReservoirCandidateBuffer = CreateFieldReservoirCandidateBuffer();
|
||||
|
|
@ -2520,7 +2552,8 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
}
|
||||
|
||||
ResolveFieldReservoir(context.CommandList, frameResources);
|
||||
RenderBloom(context.CommandList, frameResources);
|
||||
UpdateReservoirHistory(context.CommandList, frameResources);
|
||||
RenderBloom(context.CommandList, frameResources, reservoirResolvedRenderTarget, frameResources.ReservoirResolvedDescriptor);
|
||||
PresentBackBuffer(context, frameResources);
|
||||
}
|
||||
|
||||
|
|
@ -2625,6 +2658,52 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateReservoirHistory(ID3D12GraphicsCommandList activeCommandList, FrameResources frameResources)
|
||||
{
|
||||
if (reservoirHistoryUpdatePipelineState is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeCommandList.BeginEvent("Reservoir History Update");
|
||||
try
|
||||
{
|
||||
var historyReadIndex = temporalFrameIndex & 1;
|
||||
var historyWriteIndex = 1 - historyReadIndex;
|
||||
var historyReadBuffer = fieldReservoirHistoryBuffers[historyReadIndex];
|
||||
var historyWriteBuffer = fieldReservoirHistoryBuffers[historyWriteIndex];
|
||||
|
||||
sceneRenderTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
sceneMetadataRenderTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
sceneControlRenderTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
sceneReservoirGuideRenderTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
fieldReservoirCandidateBuffer.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
historyReadBuffer.Transition(activeCommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
historyWriteBuffer.Transition(activeCommandList, ResourceStates.UnorderedAccess);
|
||||
reservoirResolvedRenderTarget.Transition(activeCommandList, ResourceStates.UnorderedAccess);
|
||||
|
||||
activeCommandList.SetDescriptorHeaps(frameResources.TransientShaderDescriptors.Heap);
|
||||
activeCommandList.SetPipelineState(reservoirHistoryUpdatePipelineState);
|
||||
activeCommandList.SetComputeRootSignature(fullscreenRootSignature);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootFrameConstants, frameResources.FrameConstantsDescriptor.Gpu);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootSourceTexture, frameResources.SceneDescriptor.Gpu);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootCurrentSceneMetadata, frameResources.SceneMetadataDescriptor.Gpu);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootCurrentSceneControl, frameResources.SceneControlDescriptor.Gpu);
|
||||
activeCommandList.SetComputeRootShaderResourceView(RootReservoirHistoryRead, historyReadBuffer.Resource.GPUVirtualAddress);
|
||||
activeCommandList.SetComputeRootUnorderedAccessView(RootReservoirHistoryWrite, historyWriteBuffer.Resource.GPUVirtualAddress);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootCurrentReservoirGuide, frameResources.SceneReservoirGuideDescriptor.Gpu);
|
||||
activeCommandList.SetComputeRootShaderResourceView(RootFieldReservoirCandidates, fieldReservoirCandidateBuffer.Resource.GPUVirtualAddress);
|
||||
activeCommandList.SetComputeRootDescriptorTable(RootReservoirResolvedOutput, frameResources.ReservoirResolvedUnorderedAccessDescriptor.Gpu);
|
||||
activeCommandList.Dispatch((uint)((width + 7) / 8), (uint)((height + 7) / 8), 1);
|
||||
activeCommandList.ResourceBarrier(ResourceBarrier.BarrierUnorderedAccessView(historyWriteBuffer.Resource));
|
||||
activeCommandList.ResourceBarrier(ResourceBarrier.BarrierUnorderedAccessView(reservoirResolvedRenderTarget.Resource));
|
||||
}
|
||||
finally
|
||||
{
|
||||
activeCommandList.EndEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderTubeFields(ID3D12GraphicsCommandList activeCommandList, FrameResources frameResources)
|
||||
{
|
||||
if (activeTubeFieldDrawIndexCount <= 0 || tubeFieldRenderPipelineState is null || tubeFieldDrawBatches.Count == 0)
|
||||
|
|
@ -2967,7 +3046,11 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
private static bool IsFinite(Vector3 value) =>
|
||||
float.IsFinite(value.X) && float.IsFinite(value.Y) && float.IsFinite(value.Z);
|
||||
|
||||
private void RenderBloom(ID3D12GraphicsCommandList activeCommandList, FrameResources frameResources)
|
||||
private void RenderBloom(
|
||||
ID3D12GraphicsCommandList activeCommandList,
|
||||
FrameResources frameResources,
|
||||
D3D12RenderTarget sourceTarget,
|
||||
D3D12DescriptorSlot initialSourceDescriptor)
|
||||
{
|
||||
activeCommandList.BeginEvent("Bloom Pyramid");
|
||||
try
|
||||
|
|
@ -2975,12 +3058,12 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
activeCommandList.SetDescriptorHeaps(frameResources.TransientShaderDescriptors.Heap);
|
||||
activeCommandList.SetGraphicsRootSignature(fullscreenRootSignature);
|
||||
activeCommandList.SetGraphicsRootDescriptorTable(RootFrameConstants, frameResources.FrameConstantsDescriptor.Gpu);
|
||||
sceneRenderTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource);
|
||||
sourceTarget.Transition(activeCommandList, ResourceStates.PixelShaderResource);
|
||||
|
||||
for (var level = 0; level < BloomLevelCount; level++)
|
||||
{
|
||||
var sourceDescriptor = level == 0
|
||||
? frameResources.SceneDescriptor
|
||||
? initialSourceDescriptor
|
||||
: frameResources.BloomDescriptors[level - 1];
|
||||
var pipelineState = level == 0
|
||||
? bloomPrefilterPipelineState!
|
||||
|
|
@ -3035,38 +3118,27 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
|
||||
private void PresentBackBuffer(D3D12PassContext context, FrameResources frameResources)
|
||||
{
|
||||
context.CommandList.BeginEvent("Reservoir Presentation Resolve");
|
||||
context.CommandList.BeginEvent("Reservoir Presentation Resolve");
|
||||
try
|
||||
{
|
||||
var historyReadIndex = temporalFrameIndex & 1;
|
||||
var historyWriteIndex = 1 - historyReadIndex;
|
||||
var historyReadBuffer = fieldReservoirHistoryBuffers[historyReadIndex];
|
||||
var historyWriteBuffer = fieldReservoirHistoryBuffers[historyWriteIndex];
|
||||
|
||||
sceneRenderTarget.Transition(context.CommandList, ResourceStates.PixelShaderResource);
|
||||
reservoirResolvedRenderTarget.Transition(context.CommandList, ResourceStates.PixelShaderResource);
|
||||
sceneMetadataRenderTarget.Transition(context.CommandList, ResourceStates.PixelShaderResource);
|
||||
sceneControlRenderTarget.Transition(context.CommandList, ResourceStates.PixelShaderResource);
|
||||
sceneReservoirGuideRenderTarget.Transition(context.CommandList, ResourceStates.PixelShaderResource);
|
||||
fieldReservoirCandidateBuffer.Transition(context.CommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
historyReadBuffer.Transition(context.CommandList, ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource);
|
||||
historyWriteBuffer.Transition(context.CommandList, ResourceStates.UnorderedAccess);
|
||||
|
||||
context.BackBuffer.Transition(context.CommandList, ResourceStates.RenderTarget);
|
||||
context.CommandList.SetDescriptorHeaps(frameResources.TransientShaderDescriptors.Heap);
|
||||
context.CommandList.SetPipelineState(resolvePipelineState!);
|
||||
context.CommandList.SetGraphicsRootSignature(fullscreenRootSignature);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootFrameConstants, frameResources.FrameConstantsDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootSourceTexture, frameResources.SceneDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootSourceTexture, frameResources.ReservoirResolvedDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootBloom, frameResources.BloomPresentationDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootCurrentSceneMetadata, frameResources.SceneMetadataDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootCurrentSceneControl, frameResources.SceneControlDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootShaderResourceView(RootReservoirHistoryRead, historyReadBuffer.Resource.GPUVirtualAddress);
|
||||
context.CommandList.SetGraphicsRootUnorderedAccessView(RootReservoirHistoryWrite, historyWriteBuffer.Resource.GPUVirtualAddress);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootSdfObjects, frameResources.SdfObjectDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootTemporalGaussians, frameResources.TemporalGaussianDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootCurrentReservoirGuide, frameResources.SceneReservoirGuideDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootDescriptorTable(RootBlueNoise, frameResources.BlueNoiseDescriptor.Gpu);
|
||||
context.CommandList.SetGraphicsRootShaderResourceView(RootFieldReservoirCandidates, fieldReservoirCandidateBuffer.Resource.GPUVirtualAddress);
|
||||
|
||||
context.CommandList.RSSetViewports(viewport);
|
||||
context.CommandList.RSSetScissorRects(scissorRect);
|
||||
|
|
@ -3077,7 +3149,6 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
null);
|
||||
context.CommandList.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
|
||||
context.CommandList.DrawInstanced(3, 1, 0, 0);
|
||||
context.CommandList.ResourceBarrier(ResourceBarrier.BarrierUnorderedAccessView(historyWriteBuffer.Resource));
|
||||
CopyProgramOutputBackBuffer(context.CommandList, context.BackBuffer);
|
||||
}
|
||||
finally
|
||||
|
|
@ -4281,6 +4352,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
bloomDownsamplePipelineState!,
|
||||
bloomBlurHorizontalPipelineState!,
|
||||
bloomBlurVerticalPipelineState!,
|
||||
reservoirHistoryUpdatePipelineState!,
|
||||
resolvePipelineState!)
|
||||
: null;
|
||||
}
|
||||
|
|
@ -4316,6 +4388,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
bloomDownsamplePipelineState = pipelines.BloomDownsample;
|
||||
bloomBlurHorizontalPipelineState = pipelines.BloomBlurHorizontal;
|
||||
bloomBlurVerticalPipelineState = pipelines.BloomBlurVertical;
|
||||
reservoirHistoryUpdatePipelineState = pipelines.ReservoirHistoryUpdate;
|
||||
resolvePipelineState = pipelines.Resolve;
|
||||
}
|
||||
|
||||
|
|
@ -4346,6 +4419,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
bloomDownsamplePipelineState = null;
|
||||
bloomBlurHorizontalPipelineState = null;
|
||||
bloomBlurVerticalPipelineState = null;
|
||||
reservoirHistoryUpdatePipelineState = null;
|
||||
resolvePipelineState = null;
|
||||
}
|
||||
|
||||
|
|
@ -4497,28 +4571,35 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
28,
|
||||
0,
|
||||
D3D12.DescriptorRangeOffsetAppend);
|
||||
var reservoirResolvedOutputRange = new DescriptorRange(
|
||||
DescriptorRangeType.UnorderedAccessView,
|
||||
1,
|
||||
23,
|
||||
0,
|
||||
D3D12.DescriptorRangeOffsetAppend);
|
||||
var rootParameters = new[]
|
||||
{
|
||||
new RootParameter(new RootDescriptorTable([constantBufferRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([sourceTextureRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([sourceTextureRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([heightFieldBrushRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([sdfLightRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([bloomRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([currentSceneMetadataRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([currentSceneControlRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(51, 0), ShaderVisibility.Pixel),
|
||||
new RootParameter(RootParameterType.UnorderedAccessView, new RootDescriptor(22, 0), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([currentSceneMetadataRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([currentSceneControlRange]), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(51, 0), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.UnorderedAccessView, new RootDescriptor(22, 0), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([studioPmremRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([studioIrradianceRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([sdfObjectRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([temporalGaussianRange]), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([currentReservoirGuideRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(new RootDescriptorTable([currentReservoirGuideRange]), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(38, 0), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(39, 0), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(40, 0), ShaderVisibility.All),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(41, 0), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([blueNoiseRange]), ShaderVisibility.Pixel),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(45, 0), ShaderVisibility.Pixel),
|
||||
new RootParameter(RootParameterType.ShaderResourceView, new RootDescriptor(45, 0), ShaderVisibility.All),
|
||||
new RootParameter(new RootDescriptorTable([reservoirResolvedOutputRange]), ShaderVisibility.All),
|
||||
};
|
||||
var staticSamplers = new[]
|
||||
{
|
||||
|
|
@ -4534,7 +4615,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
StaticBorderColor.TransparentBlack,
|
||||
0.0f,
|
||||
float.MaxValue,
|
||||
ShaderVisibility.Pixel,
|
||||
ShaderVisibility.All,
|
||||
0),
|
||||
};
|
||||
var description = new RootSignatureDescription(
|
||||
|
|
@ -5010,6 +5091,17 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
return CreateFullscreenPipelineState(path, "FullscreenTriangleVS", "D3D12BloomBlurVerticalPS", SceneHdrFormat);
|
||||
}
|
||||
|
||||
private ID3D12PipelineState CreateReservoirHistoryUpdatePipelineState(string path)
|
||||
{
|
||||
var computeShader = CompileShader(path, "D3D12ReservoirHistoryUpdateCS", "cs_5_0");
|
||||
var description = new ComputePipelineStateDescription
|
||||
{
|
||||
RootSignature = fullscreenRootSignature,
|
||||
ComputeShader = computeShader,
|
||||
};
|
||||
return device.CreateComputePipelineState(description);
|
||||
}
|
||||
|
||||
private ID3D12PipelineState CreateResolvePipelineState(string path)
|
||||
{
|
||||
return CreateFullscreenPipelineState(
|
||||
|
|
@ -5354,11 +5446,13 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
ID3D12PipelineState BloomDownsample,
|
||||
ID3D12PipelineState BloomBlurHorizontal,
|
||||
ID3D12PipelineState BloomBlurVertical,
|
||||
ID3D12PipelineState ReservoirHistoryUpdate,
|
||||
ID3D12PipelineState Resolve) : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
Resolve.Dispose();
|
||||
ReservoirHistoryUpdate.Dispose();
|
||||
BloomBlurVertical.Dispose();
|
||||
BloomBlurHorizontal.Dispose();
|
||||
BloomDownsample.Dispose();
|
||||
|
|
@ -5495,6 +5589,10 @@ public sealed class D3D12Renderer : IAquariumRenderer
|
|||
|
||||
public D3D12DescriptorSlot SceneCandidateReservoirGuideDescriptor { get; set; }
|
||||
|
||||
public D3D12DescriptorSlot ReservoirResolvedDescriptor { get; set; }
|
||||
|
||||
public D3D12DescriptorSlot ReservoirResolvedUnorderedAccessDescriptor { get; set; }
|
||||
|
||||
public D3D12DescriptorSlot FieldReservoirCandidateUnorderedAccessDescriptor { get; set; }
|
||||
|
||||
public D3D12DescriptorSlot FieldReservoirLockUnorderedAccessDescriptor { get; set; }
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ struct FieldReservoirCandidate
|
|||
StructuredBuffer<FieldReservoirCandidate> fieldReservoirCandidates : register(t45);
|
||||
StructuredBuffer<FieldReservoirCandidate> reservoirHistoryRead : register(t51);
|
||||
RWStructuredBuffer<FieldReservoirCandidate> reservoirHistoryWrite : register(u22);
|
||||
RWTexture2D<float4> reservoirResolvedTexture : register(u23);
|
||||
|
||||
static const uint FieldReservoirSlotsPerPixel = 4u;
|
||||
|
||||
|
|
@ -641,22 +642,23 @@ FieldReservoirResolveOut D3D12FieldReservoirResolvePS(VertexOut input)
|
|||
return output;
|
||||
}
|
||||
|
||||
// Reservoir presentation resolve owns temporal reconstruction. The history
|
||||
// textures below are cache lines for validated reservoir evidence, not a
|
||||
// separate final-pixel TAA authority.
|
||||
ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
||||
[numthreads(8, 8, 1)]
|
||||
void D3D12ReservoirHistoryUpdateCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||
{
|
||||
float2 screenUv = float2(input.uv.x, 1.0 - input.uv.y);
|
||||
float2 pixel = screenUv * resolution;
|
||||
uint2 dimensions = (uint2)max(resolution, float2(1.0, 1.0));
|
||||
uint2 currentPixel = min((uint2)max(pixel, float2(0.0, 0.0)), dimensions - 1u);
|
||||
if (dispatchThreadId.x >= dimensions.x || dispatchThreadId.y >= dimensions.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint2 currentPixel = dispatchThreadId.xy;
|
||||
float2 uv = (float2(currentPixel) + 0.5) / float2(dimensions);
|
||||
float2 pixel = float2(currentPixel);
|
||||
uint pixelIndex = currentPixel.y * dimensions.x + currentPixel.x;
|
||||
uint baseIndex = pixelIndex * FieldReservoirSlotsPerPixel;
|
||||
FieldReservoirCandidate sceneCandidate = sceneFieldReservoirCandidate(input.uv);
|
||||
FieldReservoirCandidate sceneCandidate = sceneFieldReservoirCandidate(uv);
|
||||
FieldReservoirCandidate bestResolved = emptyFieldReservoirCandidate();
|
||||
float bestPriority = 1.0e20;
|
||||
float combinedHistoryWeight = 0.0;
|
||||
float combinedHistoryAge = 0.0;
|
||||
bool hasSharedCandidate = false;
|
||||
|
||||
[unroll]
|
||||
|
|
@ -679,7 +681,7 @@ ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
|||
FieldReservoirCandidate resolvedCandidate = resolveReservoirHistoryCandidate(
|
||||
currentCandidate,
|
||||
pixel,
|
||||
input.uv,
|
||||
uv,
|
||||
slotHistoryWeight,
|
||||
slotHistoryAge);
|
||||
reservoirHistoryWrite[baseIndex + slot] = resolvedCandidate;
|
||||
|
|
@ -692,8 +694,6 @@ ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
|||
{
|
||||
bestResolved = resolvedCandidate;
|
||||
bestPriority = priority;
|
||||
combinedHistoryWeight = slotHistoryWeight;
|
||||
combinedHistoryAge = slotHistoryAge;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -702,33 +702,39 @@ ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
|||
bestResolved = sceneCandidate;
|
||||
}
|
||||
|
||||
float3 rawCurrentColor = sceneCandidate.colorTravel.rgb;
|
||||
float3 currentColor = bestResolved.colorTravel.rgb;
|
||||
float currentCoverage = saturate(bestResolved.control.x);
|
||||
float currentStepRatio = saturate(bestResolved.control.y);
|
||||
float currentTemporalDetail = saturate(bestResolved.control.z);
|
||||
float currentFieldId = bestResolved.metadata.x;
|
||||
float currentReservoirConfidence = reservoirCandidateConfidence(bestResolved);
|
||||
float reservoirSampleAge = max(bestResolved.reservoirGuide.y, 0.0);
|
||||
float currentReservoirDomainValidity = reservoirCandidateDomainValidity(bestResolved);
|
||||
float3 resolved = bestResolved.colorTravel.rgb;
|
||||
float bloomStability = saturate((luminance(resolved) + 0.25) / max(luminance(currentColor) + 0.25, 0.0001));
|
||||
float3 finalColor = presentColor(resolved, input.uv, bloomStability);
|
||||
reservoirResolvedTexture[currentPixel] = bestResolved.colorTravel;
|
||||
}
|
||||
|
||||
ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
||||
{
|
||||
float4 resolvedSample = sourceTexture.SampleLevel(sourceSampler, input.uv, 0.0);
|
||||
float4 currentMetadata = loadCurrentMetadata(input.uv);
|
||||
float4 currentControl = loadCurrentControl(input.uv);
|
||||
float4 currentReservoirGuide = loadCurrentReservoirGuide(input.uv);
|
||||
float currentCoverage = saturate(currentControl.x);
|
||||
float currentStepRatio = saturate(currentControl.y);
|
||||
float currentTemporalDetail = saturate(currentControl.z);
|
||||
float currentFieldId = currentMetadata.x;
|
||||
float currentReservoirConfidence = currentReservoirGuide.x > 0.0 ? saturate(currentReservoirGuide.x) : 1.0;
|
||||
float reservoirSampleAge = max(currentReservoirGuide.y, 0.0);
|
||||
float currentReservoirDomainValidity = currentReservoirGuide.z > 0.0 ? saturate(currentReservoirGuide.z) : 1.0;
|
||||
float3 resolved = resolvedSample.rgb;
|
||||
float3 finalColor = presentColor(resolved, input.uv, 1.0);
|
||||
if (renderDebugMode > 0.5 && renderDebugMode < 1.5)
|
||||
{
|
||||
finalColor = aces(rawCurrentColor * max(exposure, 0.001));
|
||||
finalColor = aces(resolved * max(exposure, 0.001));
|
||||
}
|
||||
else if (renderDebugMode >= 1.5 && renderDebugMode < 2.5)
|
||||
{
|
||||
finalColor = aces(bestResolved.colorTravel.rgb * max(exposure, 0.001));
|
||||
finalColor = aces(resolved * max(exposure, 0.001));
|
||||
}
|
||||
else if (renderDebugMode >= 2.5 && renderDebugMode < 3.5)
|
||||
{
|
||||
finalColor = (combinedHistoryAge / MAX_HISTORY_AGE).xxx;
|
||||
finalColor = saturate(reservoirSampleAge / MAX_HISTORY_AGE).xxx;
|
||||
}
|
||||
else if (renderDebugMode >= 3.5 && renderDebugMode < 4.5)
|
||||
{
|
||||
finalColor = combinedHistoryWeight.xxx;
|
||||
finalColor = currentReservoirConfidence.xxx;
|
||||
}
|
||||
else if (renderDebugMode >= 4.5 && renderDebugMode < 5.5)
|
||||
{
|
||||
|
|
@ -745,7 +751,7 @@ ResolveOut D3D12ReservoirPresentationResolvePS(VertexOut input)
|
|||
}
|
||||
else if (renderDebugMode >= 7.5 && renderDebugMode < 8.5)
|
||||
{
|
||||
float luma = luminance(currentColor * max(exposure, 0.001));
|
||||
float luma = luminance(resolved * max(exposure, 0.001));
|
||||
finalColor = luma.xxx;
|
||||
}
|
||||
else if (renderDebugMode >= 8.5 && renderDebugMode < 9.5)
|
||||
|
|
|
|||
|
|
@ -273,3 +273,4 @@
|
|||
{"lesson":"TubeField blackbody ramp sampling was correct on X, but the old single-tube proof material branch treated high emission scale plus material.y near 1 as an exact red/orange proof tube and bypassed the ramp. Spectrum TubeFields now sample the ramp from saturated normalized amplitude on X and use amplitude squared for emission intensity; the exact proof branch is only reachable through an explicit negative tubeData.w sentinel. The high-end blue is expected to survive mostly as a cool fringe after ACES/SE bloom because the blackbody asset's right edge is pale blue and peak emission tonemaps toward white.","timestamp":"2026-05-28T15:06:53+01:00","files":["src/Aquarium.Engine/Render/Shaders/D3D12TubeField.hlsl","state/evidence.jsonl"],"source":"Mimir spectrum dashboard blackbody ramp QA","topic":"tubefield-blackbody-ramp-material"}
|
||||
{"lesson":"TubeField no longer owns final scene contribution through a private blend/K-buffer path. D3D12 now separates scene-candidate MRTs from canonical scene MRTs, clears a shared two-slot-per-pixel field reservoir UAV, lets TubeField pixel evaluation inject opaque SDF candidates with color/travel/metadata/control/reservoir-guide, then runs a fullscreen field-reservoir resolve that writes the canonical scene lanes before temporal resolve, bloom, and presentation. Legacy/direct contributors currently enter that resolve as the scene-candidate input candidate; future cuts should move them to explicit claim injection too.","timestamp":"2026-05-28T15:41:26+01:00","files":["src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12TubeField.hlsl","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","state/map.yaml","state/evidence.jsonl"],"source":"field reservoir ownership cut","topic":"field-reservoir-canonical-scene-resolve"}
|
||||
{"lesson":"The first TubeField ReSTIR cut replaces per-fragment reservoir scatter locks with pixel-owned compute reservoirs. TubeField expansion now emits reusable segment records with baked geometry/material endpoints; D3D12 bins active segments into screen tiles once per frame, streams a small fixed candidate set into a per-pixel initial reservoir, combines previous-frame reservoirs through reprojection, performs spatial neighbor reuse, and exports the selected sample into the shared field-reservoir candidate buffer for canonical scene resolve. Runtime shader compilation and Mimir synthetic spectrum execution passed; frame timing settles around the vsync band after ramp-up, but local window capture was unreliable and should not be treated as visual QA evidence.","timestamp":"2026-05-28T16:17:56+01:00","files":["src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12TubeField.hlsl","state/map.yaml","state/evidence.jsonl"],"source":"TubeField ReSTIR ownership rebuild","topic":"tubefield-restir-pixel-owned-reservoir"}
|
||||
{"lesson":"Reservoir history update and reuse moved out of the presentation pixel shader into a shared D3D12 compute pass. The pass reads canonical scene lanes, shared field candidate rows, and ping-ponged structured history rows; writes the next four history rows per pixel; and emits a resolved HDR field texture consumed by bloom and final presentation. The presentation shader no longer writes reservoir history, so it cannot become a hidden TAA owner. Fensalir and Mimir builds passed, the Mimir spectrum upload smoke passed, and a synthetic spectrum headless capture rendered through the compute-owned reservoir path.","timestamp":"2026-05-28T17:05:00+01:00","files":["src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","docs/temporal-spatial-evidence-reservoir.md","docs/perfect-machine-architecture.md","state/map.yaml"],"source":"user selected full structured-buffer reservoir path","topic":"reservoir-history-compute-owner"}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@ invariants:
|
|||
direct SDF proxies, meshes, volumes, fractal probes, and Mimir sensor
|
||||
observations must publish bounded Form/Appearance/Transport field claims
|
||||
before they can participate in reusable field evidence.
|
||||
- Final composition reads canonical scene targets produced by reservoir
|
||||
resolve. TubeField render lowerings inject opaque SDF candidates into the
|
||||
shared field reservoir; they must not regain private final-visibility
|
||||
authority through blend order, proxy depth, or a TubeField-local K-buffer.
|
||||
- Final composition reads the resolved HDR field texture produced by the
|
||||
shared reservoir history update pass. TubeField render lowerings inject
|
||||
opaque SDF candidates into the shared field reservoir; they must not regain
|
||||
private final-visibility authority through blend order, proxy depth, a
|
||||
presentation-shader history writer, or a TubeField-local K-buffer.
|
||||
- Field packet payload handles are not authority. Resource-backed claims and
|
||||
packets must reference declared AquariumFieldResourceDeclaration rows with
|
||||
kind, GPU residency, shader access, format, shape, validity, version, and
|
||||
|
|
@ -203,11 +204,14 @@ next_actions:
|
|||
distinguish source-lane claims while preserving the TubeField family range.
|
||||
D3D12 now has the first shared field-reservoir resolve cut: legacy/direct
|
||||
scene contributors render into scene-candidate MRTs, TubeField injects
|
||||
two-slot-per-pixel opaque SDF candidates into a shared UAV, and a fullscreen
|
||||
resolve writes canonical scene color/travel/metadata/control/reservoir-guide
|
||||
before temporal resolve, bloom, and presentation. The remaining coherence
|
||||
cut is to make every non-debug contributor inject explicit field candidates
|
||||
instead of entering as the single legacy scene-candidate input.
|
||||
four-slot-per-pixel opaque SDF candidates into a shared UAV, and a
|
||||
fullscreen resolve writes canonical scene color/travel/metadata/control/
|
||||
reservoir-guide lanes. A shared compute pass then updates ping-ponged
|
||||
structured reservoir history, emits the resolved HDR field texture, and
|
||||
bloom/presentation consume that texture without owning history. The
|
||||
remaining coherence cut is to make every non-debug contributor inject
|
||||
explicit field candidates instead of entering as the single legacy
|
||||
scene-candidate input.
|
||||
TubeField reservoir ownership has been rebuilt into the first ReSTIR-shaped
|
||||
compute path: TubeField expansion emits reusable segment records with baked
|
||||
material endpoints, segments are binned into screen tiles once per frame,
|
||||
|
|
|
|||
Loading…
Reference in a new issue