feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -0,0 +1,130 @@
using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Rendering.Wb;
public sealed partial class WbDrawDispatcher
{
internal sealed class DirectionalShadowReceiverPipelineState : IDisposable
{
private readonly MeshPipelineSet _backbuffer;
private readonly MeshPipelineSet _offscreen;
internal DirectionalShadowReceiverPipelineState(
IDirectionalShadowReceiverSource source,
MeshPipelineSet backbuffer,
MeshPipelineSet offscreen)
{
Source = source;
_backbuffer = backbuffer;
_offscreen = offscreen;
}
internal IDirectionalShadowReceiverSource Source { get; }
internal MeshPipelineSet ForSampleCount(int sampleCount) =>
sampleCount > 1 ? _backbuffer : _offscreen;
public void Dispose()
{
DisposeMeshPipelineSet(_backbuffer);
if (!ReferenceEquals(_offscreen, _backbuffer))
DisposeMeshPipelineSet(_offscreen);
}
}
private DirectionalShadowReceiverPipelineState? _directionalShadowReceiver;
/// <summary>
/// Builds both sample-count variants without publishing either. A different
/// pack always gets pipelines compiled from that candidate's shader blobs;
/// no prior pack pipeline is reused by shader name or nullable caching.
/// </summary>
internal DirectionalShadowReceiverPipelineState? PrepareDirectionalShadowReceiver(
IDirectionalShadowReceiverSource? source,
int sampleCount)
{
if (source is null)
return null;
IGpuDevice device = _device
?? throw new InvalidOperationException("Directional receivers require the modern RHI device.");
if (_scope is null || sampleCount != _scope.SampleCount)
throw new InvalidOperationException("Receiver and world-pass sample counts must match.");
MeshPipelineSet? backbuffer = null;
MeshPipelineSet? offscreen = null;
try
{
backbuffer = CreateMeshPipelineSet(
device,
sampleCount,
baseShaders: source.PipelineShaders.WorldReceiver,
namePrefix: "wb-mesh-atmospheric",
usesRenderPackShaderAbi: true);
offscreen = sampleCount == 1
? backbuffer
: CreateMeshPipelineSet(
device,
1,
baseShaders: source.PipelineShaders.WorldReceiver,
namePrefix: "wb-mesh-atmospheric",
usesRenderPackShaderAbi: true);
return new DirectionalShadowReceiverPipelineState(source, backbuffer, offscreen);
}
catch
{
DisposeMeshPipelineSet(backbuffer);
if (!ReferenceEquals(offscreen, backbuffer))
DisposeMeshPipelineSet(offscreen);
throw;
}
}
/// <summary>Assignment-only publication; returned state retires after the coupled swap.</summary>
internal DirectionalShadowReceiverPipelineState? SwapDirectionalShadowReceiver(
DirectionalShadowReceiverPipelineState? candidate)
{
DirectionalShadowReceiverPipelineState? prior = _directionalShadowReceiver;
_directionalShadowReceiver = candidate;
return prior;
}
private MeshPipelineSet PipelinesFor(
IGpuPassEncoder encoder,
IGpuFrame frame,
out DirectionalShadowFrameBinding shadowBinding)
{
DirectionalShadowReceiverPipelineState? receiver = _directionalShadowReceiver;
IDirectionalShadowReceiverSource? source = receiver?.Source;
shadowBinding = DirectionalShadowFrameBinding.Disabled;
bool bindingValid = source is not null
&& source.TryGetCurrentFrameBinding(frame, out shadowBinding);
if (DirectionalShadowReceiverPolicy.ShouldSelectReceiverPipeline(
encoder.Pass.Name,
source is not null,
bindingValid))
{
return receiver!.ForSampleCount(encoder.Pass.SampleCount);
}
return PipelinesFor(encoder);
}
private static void BindDirectionalShadowReceiver(
IGpuPassEncoder encoder,
in DirectionalShadowFrameBinding binding)
{
if (!binding.Enabled || binding.Buffer is null)
return;
encoder.BindUniformBuffer(
GpuBindingModel.UniformDirectionalShadow,
binding.Buffer,
binding.OffsetBytes,
binding.SizeBytes);
}
private void DisposeDirectionalShadowReceiverPipelines()
{
DirectionalShadowReceiverPipelineState? state =
SwapDirectionalShadowReceiver(null);
state?.Dispose();
}
}