Campaign V migrates the renderer from OpenGL 4.3+extensions to a single
Vulkan 1.3 backend on Windows x64 and Linux x64, then deletes the GL path.
Motivation is compatibility and efficiency, not rescue: mandatory
GL_ARB_bindless_texture is the exact floor that parked Slice L (Mesa
D3D12/llvmpipe lack it) while Vulkan descriptor indexing is core, and
per-frame data can be written straight into mapped memory rather than
copied through BufferSubData.
V0 pins the contract every later slice codes against. Nothing consumes it
yet, so this commit changes no runtime behavior.
The seam is a minimal Vulkan-shaped RHI implemented FIRST on GL. That
ordering is the point: the twelve renderers then port one at a time under a
strict pixel gate on the still-shipping backend, so a divergence is
attributed to one slice instead of surfacing at a big-bang integration.
Duplicating renderers per backend was rejected because WbDrawDispatcher is
4,449 lines holding only ~62 GL call sites — the API surface is small and
the retail-fidelity CPU logic is large, and forking the latter is how subtle
regressions enter.
Contract highlights:
- GpuBindingModel pins set/binding numbers dual-legal for GL and Vulkan
GLSL. Storage bindings 0-8 keep today's shader numbering; UBOs move to
their own set, which resolves the binding=1 collision GL only tolerates
because it keeps SSBO and UBO tables separate.
- GpuRingAllocation is a ref struct replacing every per-frame
BufferSubData; the compiler forbids outliving the owning frame.
- GpuTextureSlot replaces bindless handles. Unassigned is a loud
uint.MaxValue sentinel rather than a silent resolve to slot 0 — the
failure mode behind the magenta 1x1 UI placeholder bug. Renderers
needing a fallback take the device's really-registered default slot.
- Renderers always speak GL winding/viewport conventions; the Vulkan
backend compensates with a negative viewport height in exactly one
mapping function.
Verified while writing the plan: acdream's cameras already build
[0,1]-NDC projections (PortalProjection.cs:12-13), which is Vulkan's
convention. No projection rework is needed and depth precision improves,
at the cost of shifted z-fight patterns — the one pre-approved divergence
class, registered per instance at V7.
Gate: Release build green; App suite 3,785 passed / 3 skipped (3,763
baseline plus 22 new contract tests). Note for later slices, recorded in
the plan: run the suite in Release. LandblockBuildOriginTests'
far-strip test asserts behavior that LandblockStreamer.cs:505 deliberately
turns into a loud Debug.Assert in Debug builds, so a Debug run shows one
pre-existing failure that is not a regression.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
5.4 KiB
C#
116 lines
5.4 KiB
C#
namespace AcDream.App.Rendering.Gpu;
|
|
|
|
/// <summary>
|
|
/// Backend-neutral view of what the device can do. Both backends fill this from
|
|
/// their own probe layer — <c>GraphicalCapabilityRecord</c> for GL, and its
|
|
/// Vulkan sibling added at slice V5 — and the renderers consult only this.
|
|
///
|
|
/// The alignment fields are load-bearing rather than informational: ring
|
|
/// allocations must satisfy them, and getting one wrong produces a driver error
|
|
/// on Vulkan and silently wrong data on some GL implementations.
|
|
/// </summary>
|
|
internal sealed record GpuCapabilityRecord
|
|
{
|
|
public required GpuBackendKind Backend { get; init; }
|
|
|
|
/// <summary>Adapter name, e.g. <c>"AMD Radeon RX 9070 XT"</c>.</summary>
|
|
public required string DeviceName { get; init; }
|
|
|
|
/// <summary>Driver identification string for diagnostics and bug reports.</summary>
|
|
public required string DriverInfo { get; init; }
|
|
|
|
/// <summary>API version actually in use, e.g. <c>"OpenGL 4.6"</c> or <c>"Vulkan 1.3.280"</c>.</summary>
|
|
public required string ApiVersion { get; init; }
|
|
|
|
/// <summary>Simultaneously registerable texture-table slots. Must reach <see cref="GpuBindingModel.TextureTableCapacity"/>.</summary>
|
|
public required uint MaxTextureTableSlots { get; init; }
|
|
|
|
/// <summary>Storage-buffer bindings available. Must reach <see cref="GpuBindingModel.StorageBindingCount"/>.</summary>
|
|
public required uint MaxStorageBufferBindings { get; init; }
|
|
|
|
/// <summary>Push-constant bytes available. Must reach <see cref="GpuBindingModel.PushConstantBytes"/>.</summary>
|
|
public required uint MaxPushConstantBytes { get; init; }
|
|
|
|
/// <summary>Required alignment for a storage-buffer binding offset.</summary>
|
|
public required uint MinStorageBufferOffsetAlignment { get; init; }
|
|
|
|
/// <summary>Required alignment for a uniform-buffer binding offset.</summary>
|
|
public required uint MinUniformBufferOffsetAlignment { get; init; }
|
|
|
|
/// <summary>Clip distances usable by a shader. Phase U.3's per-cell clip gate needs 8.</summary>
|
|
public required uint MaxClipDistances { get; init; }
|
|
|
|
/// <summary>Highest supported multisample count for the backbuffer.</summary>
|
|
public required uint MaxSampleCount { get; init; }
|
|
|
|
/// <summary>Multi-draw-indirect. Mandatory — it is the entire draw architecture.</summary>
|
|
public required bool SupportsMultiDrawIndirect { get; init; }
|
|
|
|
/// <summary>Shader draw parameters (<c>gl_DrawID</c>). Mandatory — batch lookup depends on it.</summary>
|
|
public required bool SupportsDrawParameters { get; init; }
|
|
|
|
/// <summary>BC1/2/3 sampling. Mandatory — DAT surfaces upload as DXT without transcoding.</summary>
|
|
public required bool SupportsTextureCompressionBc { get; init; }
|
|
|
|
/// <summary>GPU timestamps. Optional: absence degrades profiling, not rendering.</summary>
|
|
public required bool SupportsTimestampQueries { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether per-frame data can be written straight into mapped memory the GPU
|
|
/// reads. Both backends report this; only Vulkan currently answers true, and
|
|
/// it is the mechanism behind Campaign V's CPU-cost target.
|
|
/// </summary>
|
|
public required bool SupportsPersistentlyMappedRings { get; init; }
|
|
|
|
/// <summary>
|
|
/// Every mandatory capability this device fails to provide, phrased as
|
|
/// operator-facing sentences. Empty means the device can run acdream.
|
|
/// Startup turns a non-empty list into the same <c>NotSupportedException</c>
|
|
/// and exit-code-4 contract the GL gate already publishes.
|
|
/// </summary>
|
|
public IReadOnlyList<string> SupportFailures
|
|
{
|
|
get
|
|
{
|
|
List<string> failures = [];
|
|
|
|
if (!SupportsMultiDrawIndirect)
|
|
failures.Add("Multi-draw-indirect is required to submit world geometry.");
|
|
if (!SupportsDrawParameters)
|
|
failures.Add("Shader draw parameters (gl_DrawID) are required to select per-draw batch data.");
|
|
if (!SupportsTextureCompressionBc)
|
|
failures.Add("BC (DXT) texture compression is required to upload DAT surfaces.");
|
|
if (MaxTextureTableSlots < GpuBindingModel.TextureTableCapacity)
|
|
{
|
|
failures.Add(
|
|
$"The texture table needs {GpuBindingModel.TextureTableCapacity} slots; " +
|
|
$"this device provides {MaxTextureTableSlots}.");
|
|
}
|
|
|
|
if (MaxStorageBufferBindings < GpuBindingModel.StorageBindingCount)
|
|
{
|
|
failures.Add(
|
|
$"{GpuBindingModel.StorageBindingCount} storage-buffer bindings are required; " +
|
|
$"this device provides {MaxStorageBufferBindings}.");
|
|
}
|
|
|
|
if (MaxPushConstantBytes < GpuBindingModel.PushConstantBytes)
|
|
{
|
|
failures.Add(
|
|
$"{GpuBindingModel.PushConstantBytes} push-constant bytes are required; " +
|
|
$"this device provides {MaxPushConstantBytes}.");
|
|
}
|
|
|
|
if (MaxClipDistances < GpuBindingModel.ClipPlanesPerSlot)
|
|
{
|
|
failures.Add(
|
|
$"{GpuBindingModel.ClipPlanesPerSlot} clip distances are required by the per-cell clip gate; " +
|
|
$"this device provides {MaxClipDistances}.");
|
|
}
|
|
|
|
return failures;
|
|
}
|
|
}
|
|
|
|
public bool IsSupported => SupportFailures.Count == 0;
|
|
}
|