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

@ -90,8 +90,30 @@ public sealed class VulkanCapabilityGateTests
Assert.True(record.IsSupported);
}
[Fact]
public void OptionalAtmosphericFormatsDoNotRejectTheRetailRenderer()
{
VulkanCapabilityRecord record = SupportedRecord(
formats: VulkanFormatSupport.Complete with
{
DepthStencilSampled = false,
Rgba16FloatColorAttachment = false,
Rgba16FloatSampled = false,
Rgba16FloatLinearFilter = false,
// A colour-only MSAA fact is not enough when sampling/filtering
// is absent; the neutral projection must still expose zero.
MaxRgba16FloatSampleCount = 8,
});
Assert.Empty(record.SupportFailures);
GpuCapabilityRecord projected = record.ToGpuCapabilityRecord();
Assert.False(projected.SupportsRgba16FloatRenderTargets);
Assert.Equal(0u, projected.MaxRgba16FloatSampleCount);
Assert.False(projected.SupportsSampledDepth);
}
/// <summary>
/// Every field on <see cref="VulkanDeviceFeatureSupport"/> is mandatory, so
/// Every required field on <see cref="VulkanDeviceFeatureSupport"/> is mandatory, so
/// clearing any one of them must produce exactly one new failure. Driving
/// this by reflection rather than by hand means a feature added to the record
/// without a matching Evaluate clause fails here instead of shipping
@ -103,6 +125,7 @@ public sealed class VulkanCapabilityGateTests
IEnumerable<string> featureNames = typeof(VulkanDeviceFeatureSupport)
.GetProperties()
.Where(property => property.PropertyType == typeof(bool))
.Where(property => property.Name != nameof(VulkanDeviceFeatureSupport.Multiview))
.Select(property => property.Name);
foreach (string name in featureNames)
@ -219,10 +242,11 @@ public sealed class VulkanCapabilityGateTests
Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(GpuBindingModel.StorageGlobalLights));
Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(GpuBindingModel.StorageClipRegions));
// Binding 9 (the GL-only uvec2 handle-table emulation, StorageTextureTable)
// is deleted as of Campaign V slice V11 — the Vulkan backend always bound
// set 2 instead and never touched it, so there is no longer a ninth
// binding to assert never spends a scarce dynamic descriptor.
// #226 adds binding 9 for the per-instance detail category. It is a
// plain storage descriptor because the renderer supplies its exact
// ring slice through the descriptor write rather than a dynamic offset.
Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(
GpuBindingModel.StorageInstanceDetailCategory));
}
[Fact]
@ -250,6 +274,41 @@ public sealed class VulkanCapabilityGateTests
Assert.Empty(SupportedRecord().SupportFailures);
}
[Fact]
public void MultiviewIsProjectedButDoesNotRejectTheAuthoritativeRenderer()
{
VulkanDeviceFeatureSupport reduced =
VulkanDeviceFeatureSupport.Complete with { Multiview = false };
VulkanCapabilityRecord record = SupportedRecord(features: reduced);
Assert.True(record.IsSupported);
Assert.False(record.ToGpuCapabilityRecord().SupportsMultiview);
}
[Fact]
public void ADeviceWithTooFewTotalStorageDescriptorsIsRejectedPerSetAndPerStage()
{
VulkanCapabilityRecord perSet = SupportedRecord(
limits: VulkanDeviceLimitSupport.Complete with
{
MaxDescriptorSetStorageBuffers =
GpuBindingModel.StorageBindingCount - 1,
});
VulkanCapabilityRecord perStage = SupportedRecord(
limits: VulkanDeviceLimitSupport.Complete with
{
MaxPerStageDescriptorStorageBuffers =
GpuBindingModel.StorageBindingCount - 1,
});
Assert.Contains(
perSet.SupportFailures,
failure => failure.Contains("total storage bindings", StringComparison.Ordinal));
Assert.Contains(
perStage.SupportFailures,
failure => failure.Contains("each shader stage", StringComparison.Ordinal));
Assert.Empty(SupportedRecord().SupportFailures);
}
[Fact]
public void ATextureTableSmallerThanTheCapacityIsRejectedPerSetAndPerStage()
{
@ -546,8 +605,14 @@ public sealed class VulkanCapabilityGateTests
limits: VulkanDeviceLimitSupport.Complete with
{
MinStorageBufferOffsetAlignment = 16,
MaxStorageBufferRange = 192u * 1024u * 1024u,
MinUniformBufferOffsetAlignment = 64,
MaxColorSampleCount = 4,
MaxImageDimension2D = 8192,
MaxImageArrayLayers = 128,
DeviceLocalHeapBytes = 6UL * 1024 * 1024 * 1024,
MaxDescriptorSetStorageBuffers = 48,
MaxPerStageDescriptorStorageBuffers = 32,
MaxDescriptorSetUpdateAfterBindSampledImages = 500_000,
MaxPerStageDescriptorUpdateAfterBindSampledImages = 16_384,
});
@ -558,11 +623,15 @@ public sealed class VulkanCapabilityGateTests
Assert.Equal("AMD Radeon RX 9070 XT", projected.DeviceName);
Assert.Equal("Vulkan 1.3.280", projected.ApiVersion);
Assert.Equal(16u, projected.MinStorageBufferOffsetAlignment);
Assert.Equal(192u * 1024u * 1024u, projected.MaxStorageBufferRangeBytes);
Assert.Equal(64u, projected.MinUniformBufferOffsetAlignment);
Assert.Equal(4u, projected.MaxSampleCount);
Assert.Equal(8192u, projected.MaxImageDimension2D);
Assert.Equal(128u, projected.MaxImageArrayLayers);
Assert.Equal(6UL * 1024 * 1024 * 1024, projected.DeviceLocalMemoryBytes);
// The table is limited by whichever of the two counts is smaller.
Assert.Equal(16_384u, projected.MaxTextureTableSlots);
Assert.Equal(GpuBindingModel.StorageBindingCount, projected.MaxStorageBufferBindings);
Assert.Equal(32u, projected.MaxStorageBufferBindings);
Assert.True(projected.SupportsMultiDrawIndirect);
Assert.True(projected.SupportsDrawParameters);
Assert.True(projected.SupportsTextureCompressionBc);
@ -570,6 +639,10 @@ public sealed class VulkanCapabilityGateTests
// The whole point of the campaign's CPU target: per-frame data written
// straight into mapped memory rather than copied through BufferSubData.
Assert.True(projected.SupportsPersistentlyMappedRings);
Assert.True(projected.SupportsRgba16FloatRenderTargets);
Assert.Equal(4u, projected.MaxRgba16FloatSampleCount);
Assert.True(projected.SupportsSampledDepth);
Assert.True(projected.SupportsMultiview);
Assert.Empty(projected.SupportFailures);
}