feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -18,10 +18,14 @@ internal sealed record GpuRecordedFrameEnd(long Serial) : GpuRecordedCall;
|
|||
|
||||
internal sealed record GpuRecordedRingAllocation(GpuRingUsage Usage, int ByteCount, uint OffsetBytes) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPassBegin(string Name, int SampleCount) : GpuRecordedCall;
|
||||
internal sealed record GpuRecordedHostStorageVisibility(string BufferName) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPassBegin(string Name, int SampleCount, uint ViewMask = 0) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPassEnd(string Name) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedTimerScope(string Name) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPipelineBind(string PipelineName) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedStorageBind(uint Binding, string BufferName, uint OffsetBytes, uint SizeBytes)
|
||||
|
|
@ -72,13 +76,25 @@ internal sealed record GpuRecordedTextureRegistration(string TextureName, GpuSam
|
|||
|
||||
internal sealed record GpuRecordedTextureRelease(uint Slot) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedRenderTargetCreate(GpuRenderTargetDescription Description)
|
||||
: GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedDirectionalDepthTargetCreate(
|
||||
GpuDirectionalDepthTargetDescription Description) : GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPipelineColorFormatAcquire(GpuTextureFormat Format)
|
||||
: GpuRecordedCall;
|
||||
|
||||
internal sealed record GpuRecordedPipelineColorFormatRelease(GpuTextureFormat Format)
|
||||
: GpuRecordedCall;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory <see cref="IGpuDevice"/> that owns no driver objects. Ring
|
||||
/// allocations are backed by a real byte array, so a test can drive a renderer
|
||||
/// and then read back exactly what it wrote — the same bytes a driver would have
|
||||
/// seen. Everything else is recorded into <see cref="Calls"/> in submission order.
|
||||
/// </summary>
|
||||
internal sealed class RecordingGpuDevice : IGpuDevice
|
||||
internal sealed class RecordingGpuDevice : IGpuDevice, IGpuPipelineFormatVariantHost
|
||||
{
|
||||
private const int DefaultRingCapacityBytes = 8 * 1024 * 1024;
|
||||
|
||||
|
|
@ -87,7 +103,10 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
private readonly List<RecordingGpuBuffer> _createdBuffers = [];
|
||||
private readonly List<RecordingGpuPipeline> _createdPipelines = [];
|
||||
private readonly List<RecordingGpuSampler> _createdSamplers = [];
|
||||
private readonly List<RecordingGpuRenderTarget> _createdRenderTargets = [];
|
||||
private readonly List<RecordingGpuDirectionalDepthTarget> _createdDirectionalDepthTargets = [];
|
||||
private readonly Dictionary<GpuSamplerDescription, RecordingGpuSampler> _samplers = [];
|
||||
private readonly Dictionary<GpuTextureFormat, int> _pipelineFormatLeases = [];
|
||||
private readonly byte[] _ring;
|
||||
private readonly Stack<uint> _freeTextureSlots = new();
|
||||
|
||||
|
|
@ -101,11 +120,13 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(ringCapacityBytes, 1);
|
||||
_ring = new byte[ringCapacityBytes];
|
||||
RingBuffer = new RecordingGpuBuffer(new GpuBufferDescription(
|
||||
"test-ring",
|
||||
ringCapacityBytes,
|
||||
GpuBufferUsage.Storage | GpuBufferUsage.Uniform | GpuBufferUsage.Indirect,
|
||||
GpuMemoryResidency.HostWritable));
|
||||
RingBuffer = new RecordingGpuBuffer(
|
||||
new GpuBufferDescription(
|
||||
"test-ring",
|
||||
ringCapacityBytes,
|
||||
GpuBufferUsage.Storage | GpuBufferUsage.Uniform | GpuBufferUsage.Indirect,
|
||||
GpuMemoryResidency.HostWritable),
|
||||
_ring);
|
||||
|
||||
RecordingGpuTexture placeholder = new("default-white", GpuTextureKind.Texture2D, GpuTextureFormat.Rgba8Unorm, 1, 1, 1, 1);
|
||||
DefaultTextureSlot = RegisterTexture(placeholder, CreateSampler(GpuSamplerDescription.UiNearest));
|
||||
|
|
@ -136,19 +157,29 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
MaxStorageBufferBindings = GpuBindingModel.StorageBindingCount,
|
||||
MaxPushConstantBytes = GpuBindingModel.MaxPushConstantBytes,
|
||||
MinStorageBufferOffsetAlignment = 256,
|
||||
MaxStorageBufferRangeBytes = 128u * 1024u * 1024u,
|
||||
MinUniformBufferOffsetAlignment = 256,
|
||||
MaxClipDistances = GpuBindingModel.ClipPlanesPerSlot,
|
||||
MaxSampleCount = 8,
|
||||
MaxImageDimension2D = 16_384,
|
||||
MaxImageArrayLayers = 2_048,
|
||||
DeviceLocalMemoryBytes = 8UL * 1024 * 1024 * 1024,
|
||||
SupportsMultiDrawIndirect = true,
|
||||
SupportsDrawParameters = true,
|
||||
SupportsTextureCompressionBc = true,
|
||||
SupportsTimestampQueries = true,
|
||||
SupportsPersistentlyMappedRings = true,
|
||||
SupportsRgba16FloatRenderTargets = true,
|
||||
MaxRgba16FloatSampleCount = 8,
|
||||
SupportsSampledDepth = true,
|
||||
SupportsMultiview = true,
|
||||
};
|
||||
|
||||
public IGpuResourceRetirementQueue Retirement => ImmediateGpuResourceRetirementQueue.Instance;
|
||||
|
||||
public IGpuTimerPool Timers { get; } = new RecordingGpuTimerPool();
|
||||
public RecordingGpuTimerPool RecordingTimers { get; } = new();
|
||||
|
||||
public IGpuTimerPool Timers => RecordingTimers;
|
||||
|
||||
public GpuTextureSlot DefaultTextureSlot { get; }
|
||||
|
||||
|
|
@ -160,6 +191,26 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
|
||||
public IReadOnlyList<RecordingGpuSampler> CreatedSamplers => _createdSamplers;
|
||||
|
||||
public IReadOnlyList<RecordingGpuRenderTarget> CreatedRenderTargets => _createdRenderTargets;
|
||||
|
||||
public IReadOnlyList<RecordingGpuDirectionalDepthTarget> CreatedDirectionalDepthTargets =>
|
||||
_createdDirectionalDepthTargets;
|
||||
|
||||
public IReadOnlyDictionary<GpuTextureFormat, int> PipelineFormatLeases =>
|
||||
_pipelineFormatLeases;
|
||||
|
||||
/// <summary>
|
||||
/// Optional deterministic allocation fault used to prove candidate target
|
||||
/// sets roll back atomically. Returning null admits the allocation.
|
||||
/// </summary>
|
||||
public Func<GpuRenderTargetDescription, Exception?>? RenderTargetFailure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional deterministic pipeline-construction fault used to prove that
|
||||
/// renderers retire every partially-created resource.
|
||||
/// </summary>
|
||||
public Func<GpuPipelineDescription, Exception?>? PipelineFailure { get; set; }
|
||||
|
||||
public IGpuBuffer CreateBuffer(in GpuBufferDescription description)
|
||||
{
|
||||
var buffer = new RecordingGpuBuffer(description);
|
||||
|
|
@ -193,11 +244,12 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
|
||||
public IGpuSampler CreateSampler(in GpuSamplerDescription description)
|
||||
{
|
||||
if (_samplers.TryGetValue(description, out RecordingGpuSampler? existing))
|
||||
if (_samplers.TryGetValue(description, out RecordingGpuSampler? existing)
|
||||
&& !existing.IsDisposed)
|
||||
return existing;
|
||||
|
||||
RecordingGpuSampler created = new(description);
|
||||
_samplers.Add(description, created);
|
||||
_samplers[description] = created;
|
||||
_createdSamplers.Add(created);
|
||||
return created;
|
||||
}
|
||||
|
|
@ -205,13 +257,78 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
public IGpuPipeline CreatePipeline(GpuPipelineDescription description)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
if (description.ViewMask != 0 && !Capabilities.SupportsMultiview)
|
||||
throw new NotSupportedException("Multiview pipelines are unsupported.");
|
||||
if (PipelineFailure?.Invoke(description) is { } failure)
|
||||
throw failure;
|
||||
var pipeline = new RecordingGpuPipeline(description);
|
||||
_createdPipelines.Add(pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
public IGpuRenderTarget CreateRenderTarget(in GpuRenderTargetDescription description) =>
|
||||
new RecordingGpuRenderTarget(description);
|
||||
public IGpuRenderTarget CreateRenderTarget(in GpuRenderTargetDescription description)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(description.SampleCount);
|
||||
if (description.SampleableDepth && description.DepthFormat is null)
|
||||
throw new ArgumentException("SampleableDepth requires a depth format.", nameof(description));
|
||||
if ((uint)description.SampleCount > Capabilities.MaxSampleCount)
|
||||
throw new NotSupportedException("The requested sample count is unsupported.");
|
||||
if (description.ColorFormat == GpuTextureFormat.Rgba16FloatRenderTarget
|
||||
&& (!Capabilities.SupportsRgba16FloatRenderTargets
|
||||
|| (uint)description.SampleCount > Capabilities.MaxRgba16FloatSampleCount))
|
||||
{
|
||||
throw new NotSupportedException("RGBA16F render-target capabilities are insufficient.");
|
||||
}
|
||||
if (description.SampleableDepth && !Capabilities.SupportsSampledDepth)
|
||||
throw new NotSupportedException("Sampled depth is unsupported.");
|
||||
if (RenderTargetFailure?.Invoke(description) is { } failure)
|
||||
throw failure;
|
||||
var target = new RecordingGpuRenderTarget(description);
|
||||
_createdRenderTargets.Add(target);
|
||||
_calls.Add(new GpuRecordedRenderTargetCreate(description));
|
||||
return target;
|
||||
}
|
||||
|
||||
public IGpuDirectionalDepthTarget CreateDirectionalDepthTarget(
|
||||
in GpuDirectionalDepthTargetDescription description)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(description.Name);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(description.Resolution);
|
||||
if (description.LayerCount is < 2 or > 4)
|
||||
throw new ArgumentOutOfRangeException(nameof(description));
|
||||
if (description.DepthFormat != GpuTextureFormat.Depth24Stencil8)
|
||||
throw new ArgumentException("Directional depth requires Depth24Stencil8.", nameof(description));
|
||||
if (!Capabilities.SupportsSampledDepth)
|
||||
throw new NotSupportedException("Sampled depth is unsupported.");
|
||||
|
||||
var target = new RecordingGpuDirectionalDepthTarget(description);
|
||||
_createdDirectionalDepthTargets.Add(target);
|
||||
_calls.Add(new GpuRecordedDirectionalDepthTargetCreate(description));
|
||||
return target;
|
||||
}
|
||||
|
||||
public IDisposable AcquirePipelineColorFormat(GpuTextureFormat format)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (format == GpuTextureFormat.Rgba16FloatRenderTarget
|
||||
&& !Capabilities.SupportsRgba16FloatRenderTargets)
|
||||
throw new NotSupportedException("RGBA16F render targets are unsupported.");
|
||||
_pipelineFormatLeases.TryGetValue(format, out int count);
|
||||
_pipelineFormatLeases[format] = checked(count + 1);
|
||||
_calls.Add(new GpuRecordedPipelineColorFormatAcquire(format));
|
||||
return new RecordingPipelineColorFormatLease(this, format);
|
||||
}
|
||||
|
||||
private void ReleasePipelineColorFormat(GpuTextureFormat format)
|
||||
{
|
||||
if (!_pipelineFormatLeases.TryGetValue(format, out int count))
|
||||
return;
|
||||
if (count == 1)
|
||||
_pipelineFormatLeases.Remove(format);
|
||||
else
|
||||
_pipelineFormatLeases[format] = count - 1;
|
||||
_calls.Add(new GpuRecordedPipelineColorFormatRelease(format));
|
||||
}
|
||||
|
||||
public GpuTextureSlot RegisterTexture(IGpuTexture texture, IGpuSampler sampler)
|
||||
{
|
||||
|
|
@ -311,6 +428,16 @@ internal sealed class RecordingGpuDevice : IGpuDevice
|
|||
|
||||
private static uint AlignUp(uint value, uint alignment) =>
|
||||
alignment <= 1 ? value : (value + alignment - 1) / alignment * alignment;
|
||||
|
||||
private sealed class RecordingPipelineColorFormatLease(
|
||||
RecordingGpuDevice device,
|
||||
GpuTextureFormat format) : IDisposable
|
||||
{
|
||||
private RecordingGpuDevice? _device = device;
|
||||
|
||||
public void Dispose() =>
|
||||
Interlocked.Exchange(ref _device, null)?.ReleasePipelineColorFormat(format);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingGpuFrame(RecordingGpuDevice device, long serial, int slotIndex) : IGpuFrame
|
||||
|
|
@ -323,10 +450,77 @@ internal sealed class RecordingGpuFrame(RecordingGpuDevice device, long serial,
|
|||
|
||||
public GpuRingAllocation AllocateRing(int byteCount, GpuRingUsage usage) => device.Allocate(byteCount, usage);
|
||||
|
||||
public void PublishHostStorageWrites(IGpuBuffer buffer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(buffer);
|
||||
if (buffer.Residency != GpuMemoryResidency.HostWritable
|
||||
|| !buffer.Usage.HasFlag(GpuBufferUsage.Storage))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Published host writes require a host-writable storage buffer.",
|
||||
nameof(buffer));
|
||||
}
|
||||
device.Record(new GpuRecordedHostStorageVisibility(buffer.Name));
|
||||
}
|
||||
|
||||
public IGpuPassEncoder BeginPass(GpuPassDescription description)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
device.Record(new GpuRecordedPassBegin(description.Name, description.SampleCount));
|
||||
if (!description.HasColorAttachment)
|
||||
{
|
||||
if (description.SampleCount != 1
|
||||
|| description.Depth is not { DirectionalTarget: RecordingGpuDirectionalDepthTarget directionalTarget } depth)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A colour-less recording pass requires a single-sampled directional-depth target.");
|
||||
}
|
||||
if (depth.Layer < 0 || depth.Layer >= directionalTarget.Description.LayerCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(description));
|
||||
if (description.ViewMask != 0)
|
||||
{
|
||||
uint expected = (1u << directionalTarget.Description.LayerCount) - 1u;
|
||||
if (description.ViewMask != expected || !device.Capabilities.SupportsMultiview)
|
||||
throw new NotSupportedException("Directional multiview requires every target layer and device support.");
|
||||
}
|
||||
if (depth.Store != GpuStoreOp.Store)
|
||||
throw new InvalidOperationException("Directional depth must be stored for sampling.");
|
||||
}
|
||||
if (description.Color.Target is RecordingGpuRenderTarget target)
|
||||
{
|
||||
if (description.SampleCount != target.Description.SampleCount)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Pass and offscreen-target sample counts must match.");
|
||||
}
|
||||
if (target.UsesMultisampleResolve && description.Color.Store != GpuStoreOp.Resolve)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A multisampled offscreen target must resolve into ColorTexture.");
|
||||
}
|
||||
if (target.UsesMultisampleResolve && description.Color.Load == GpuLoadOp.Load)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A transient multisample colour attachment cannot load the prior resolved image.");
|
||||
}
|
||||
if (!target.UsesMultisampleResolve && description.Color.Store == GpuStoreOp.Resolve)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A single-sampled offscreen target cannot use Store=Resolve.");
|
||||
}
|
||||
if (target.UsesMultisampleResolve && description.Depth?.Load == GpuLoadOp.Load)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A transient multisample depth attachment cannot load prior depth.");
|
||||
}
|
||||
if (target.Description.SampleableDepth
|
||||
&& description.Depth is { }
|
||||
&& description.Depth?.Store != GpuStoreOp.Store)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Sampleable depth requires Store=Store.");
|
||||
}
|
||||
}
|
||||
device.Record(new GpuRecordedPassBegin(description.Name, description.SampleCount, description.ViewMask));
|
||||
return new RecordingGpuPassEncoder(device, description);
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +545,10 @@ internal sealed class RecordingGpuPassEncoder(RecordingGpuDevice device, GpuPass
|
|||
public void BindPipeline(IGpuPipeline pipeline)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pipeline);
|
||||
if (pipeline.Description.HasColorAttachment != Pass.HasColorAttachment)
|
||||
throw new InvalidOperationException("Pipeline and pass colour-attachment intents must match.");
|
||||
if (pipeline.Description.ViewMask != Pass.ViewMask)
|
||||
throw new InvalidOperationException("Pipeline and pass view masks must match.");
|
||||
device.Record(new GpuRecordedPipelineBind(pipeline.Description.Name));
|
||||
}
|
||||
|
||||
|
|
@ -407,7 +605,11 @@ internal sealed class RecordingGpuPassEncoder(RecordingGpuDevice device, GpuPass
|
|||
device.Record(new GpuRecordedMultiDrawIndirect(commands.Name, offsetBytes, drawCount, strideBytes));
|
||||
}
|
||||
|
||||
public IDisposable BeginTimerScope(string scopeName) => NullDisposable.Instance;
|
||||
public IDisposable BeginTimerScope(string scopeName)
|
||||
{
|
||||
device.Record(new GpuRecordedTimerScope(scopeName));
|
||||
return NullDisposable.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -428,22 +630,50 @@ internal sealed class RecordingGpuPassEncoder(RecordingGpuDevice device, GpuPass
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingGpuBuffer(GpuBufferDescription description) : IGpuBuffer
|
||||
internal sealed class RecordingGpuBuffer : IGpuBuffer
|
||||
{
|
||||
private readonly byte[] _storage = new byte[description.SizeBytes];
|
||||
private readonly byte[] _storage;
|
||||
|
||||
public string Name { get; } = description.Name;
|
||||
internal RecordingGpuBuffer(
|
||||
GpuBufferDescription description,
|
||||
byte[]? storage = null)
|
||||
{
|
||||
if (storage is not null && storage.Length != description.SizeBytes)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"External recording storage must match the buffer size.",
|
||||
nameof(storage));
|
||||
}
|
||||
_storage = storage ?? new byte[description.SizeBytes];
|
||||
Name = description.Name;
|
||||
SizeBytes = description.SizeBytes;
|
||||
Usage = description.Usage;
|
||||
Residency = description.Residency;
|
||||
}
|
||||
|
||||
public long SizeBytes { get; } = description.SizeBytes;
|
||||
public string Name { get; }
|
||||
|
||||
public GpuBufferUsage Usage { get; } = description.Usage;
|
||||
public long SizeBytes { get; }
|
||||
|
||||
public GpuMemoryResidency Residency { get; } = description.Residency;
|
||||
public GpuBufferUsage Usage { get; }
|
||||
|
||||
public GpuMemoryResidency Residency { get; }
|
||||
|
||||
public bool HostWritesAreCoherent =>
|
||||
Residency == GpuMemoryResidency.HostWritable;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Upload(long offsetBytes, ReadOnlySpan<byte> data) =>
|
||||
public int UploadCount { get; private set; }
|
||||
|
||||
public long UploadedBytes { get; private set; }
|
||||
|
||||
public void Upload(long offsetBytes, ReadOnlySpan<byte> data)
|
||||
{
|
||||
data.CopyTo(_storage.AsSpan((int)offsetBytes, data.Length));
|
||||
UploadCount++;
|
||||
UploadedBytes = checked(UploadedBytes + data.Length);
|
||||
}
|
||||
|
||||
public void CopyTo(IGpuBuffer destination, long sourceOffsetBytes, long destinationOffsetBytes, long byteCount)
|
||||
{
|
||||
|
|
@ -531,25 +761,95 @@ internal sealed class RecordingGpuRenderTarget : IGpuRenderTarget
|
|||
description.Height,
|
||||
layerCount: 1,
|
||||
mipLevelCount: 1);
|
||||
if (description.SampleableDepth && description.DepthFormat is { } depthFormat)
|
||||
{
|
||||
DepthTexture = new RecordingGpuTexture(
|
||||
$"{description.Name}-depth",
|
||||
GpuTextureKind.Texture2D,
|
||||
depthFormat,
|
||||
description.Width,
|
||||
description.Height,
|
||||
layerCount: 1,
|
||||
mipLevelCount: 1);
|
||||
}
|
||||
}
|
||||
|
||||
public GpuRenderTargetDescription Description { get; }
|
||||
|
||||
public IGpuTexture ColorTexture { get; }
|
||||
|
||||
public IGpuTexture? DepthTexture { get; }
|
||||
|
||||
/// <summary>The pass attachment sample count; exposed textures are always single-sampled.</summary>
|
||||
public int AttachmentSampleCount => Description.SampleCount;
|
||||
|
||||
public bool UsesMultisampleResolve => Description.SampleCount > 1;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
IsDisposed = true;
|
||||
ColorTexture.Dispose();
|
||||
DepthTexture?.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RecordingGpuDirectionalDepthTarget : IGpuDirectionalDepthTarget
|
||||
{
|
||||
public RecordingGpuDirectionalDepthTarget(GpuDirectionalDepthTargetDescription description)
|
||||
{
|
||||
Description = description;
|
||||
DepthTexture = new RecordingGpuTexture(
|
||||
$"{description.Name}-depth",
|
||||
GpuTextureKind.Texture2DArray,
|
||||
description.DepthFormat,
|
||||
description.Resolution,
|
||||
description.Resolution,
|
||||
description.LayerCount,
|
||||
mipLevelCount: 1);
|
||||
}
|
||||
|
||||
public GpuDirectionalDepthTargetDescription Description { get; }
|
||||
|
||||
public IGpuTexture DepthTexture { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
IsDisposed = true;
|
||||
DepthTexture.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingGpuTimerPool : IGpuTimerPool
|
||||
{
|
||||
public bool IsSupported => false;
|
||||
private readonly Dictionary<string, double> _resolved = new(StringComparer.Ordinal);
|
||||
|
||||
public bool IsSupported => true;
|
||||
|
||||
internal void SetResolved(string scopeName, double milliseconds) =>
|
||||
_resolved[scopeName] = milliseconds;
|
||||
|
||||
internal void ClearResolved() => _resolved.Clear();
|
||||
|
||||
public bool TryResolve(string scopeName, out double milliseconds)
|
||||
{
|
||||
milliseconds = 0d;
|
||||
return false;
|
||||
return _resolved.TryGetValue(scopeName, out milliseconds);
|
||||
}
|
||||
|
||||
public bool TryTakeResolved(string scopeName, out double milliseconds)
|
||||
{
|
||||
if (!_resolved.TryGetValue(scopeName, out milliseconds))
|
||||
return false;
|
||||
_resolved.Remove(scopeName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue