feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend

Vulkan is the sole, user-signed-off backend (V10 landed) and step 1
already removed ImGui/Studio/DevTools. This step deletes the GL
rendering backend itself: every Gpu/Gl/** implementation, the Wb
ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/
BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache,
RenderBootstrap, and RenderFrameGlStateController.

GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/
OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses
its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and
RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone —
there is nothing left to select between. The five world-draw dual-arm
renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer,
ParticleRenderer, SkyRenderer) and the composition roots
(WorldRenderComposition, HostInputCameraComposition,
LivePresentationComposition, FrameRootComposition) collapse to their
RHI-only arm. GL-only diagnostic properties with a live external reader
(DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op
rather than disappearing, since the reader is out of this commit's
scope.

A few GL-flavored mechanisms turned out to be backend-neutral once
isolated: GlConstructionCleanupLedger is renamed
ResourceConstructionCleanupLedger (exception-chain walking has nothing
to do with GL), and GlfwNativePlatformProbe moved out of the otherwise
GL-only GraphicalCapabilityRecord.cs into
GraphicalWindowBackendSelection.cs before the rest of that file was
deleted.

Test files with no surviving subject are deleted outright
(GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests,
PortalDepthShaderParityTests, TextureCacheBindlessTests,
TextRendererFailureSafetyTests, ClipFrameUploadTests, every
Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests);
others get their dead GL-only members trimmed while their live
assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now
reads GpuBindingModel.StorageClipRegions, the same binding index under
its new backend-neutral name; GpuResourceRetirementTransactionTests
drops its OpenGLGraphicsDevice-subclassing test double and the two GL
queue tests it existed for). EnvCellRendererTests' construction helper
now builds a real ObjectMeshManager via VulkanMeshPipelineDevice
instead of passing null through a null-forgiving operator, since the
RHI constructor never tolerated a null mesh manager and the old GL
constructor (which did) is gone.

Deferred to the next two steps, deliberately not touched here: the
Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl
(WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale
csproj comment (the package itself is still load-bearing —
TextureFormat and friends are used well beyond the deleted
ManagedGLUniformBuffer), and the CI/gate scripts.

Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors.
Tests: full-solution `dotnet test` green across every project
(App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all
others 100%); the 2 App.Tests names that flake under full-suite
parallel execution (#250-family, documented pre-existing) pass in
isolation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-29 02:19:53 +02:00
parent b70b9832ff
commit 8a7a0837e1
121 changed files with 1243 additions and 19840 deletions

View file

@ -14,7 +14,6 @@ using DatReaderWriter.DBObjs;
using Microsoft.Extensions.Logging.Abstractions;
using Silk.NET.Input;
using Silk.NET.OpenGL;
using Shader = AcDream.App.Rendering.Shader;
namespace AcDream.App.Composition;
@ -31,27 +30,21 @@ internal sealed record WorldTerrainBuildContext(
/// <summary>
/// The render foundation the later phases build on.
///
/// <para>Campaign V slice V6h made every raw-GL member nullable. They are all
/// present on GL and all absent on Vulkan, because the world renderers that own
/// them are still raw GL until slices V4t/V4c/V4d land the Vulkan world arm.
/// What survives on both backends is exactly the RHI-ported set — the texture
/// cache's UI path, the debug font, the text renderer and the debug lines — plus
/// the backend-neutral residency ledger and shader directory.</para>
/// <para>Every member here is backend-neutral. The raw-GL-only members this
/// record used to carry (<c>Bindless</c>, <c>TerrainShader</c>, <c>MeshShader</c>,
/// <c>Samplers</c>) were deleted at Campaign V slice V11 along with the GL arm
/// that was their only producer.</para>
/// </summary>
internal sealed record WorldRenderFoundation(
string ShadersDirectory,
BindlessSupport? Bindless,
TerrainAtlas? TerrainAtlas,
Shader? TerrainShader,
SceneLightingUboBinding? SceneLighting,
DebugLineRenderer DebugLines,
BitmapFont? DebugFont,
TextRenderer? TextRenderer,
TerrainModernRenderer? Terrain,
Shader? MeshShader,
WbMeshAdapter? MeshAdapter,
TextureCache TextureCache,
SamplerCache? Samplers,
ResidencyManager Residency);
internal sealed record WorldRenderResult(
@ -71,8 +64,6 @@ internal sealed record WorldRenderDependencies(
internal interface IGameWindowWorldRenderPublication
{
void PublishBindlessSupport(BindlessSupport value);
void PublishTerrainShader(Shader value);
void PublishSceneLighting(SceneLightingUboBinding value);
void PublishDebugLines(DebugLineRenderer value);
void PublishHudResources(BitmapFont font, TextRenderer text);
@ -81,27 +72,20 @@ internal interface IGameWindowWorldRenderPublication
float[] heightTable,
TerrainBlendingContext blending,
ConcurrentDictionary<uint, SurfaceInfo> surfaceCache);
void PublishMeshShader(Shader value);
void PublishWbMeshAdapter(WbMeshAdapter value);
void PublishTextureCache(TextureCache value);
void PublishSamplerCache(SamplerCache value);
}
internal interface IWorldRenderCompositionFactory
{
void InitializeGlState(GL gl);
WorldRegionData LoadRegion(IDatReaderWriter dats);
void InitializeEnvironment(WorldEnvironmentController environment, Region region);
BindlessSupport RequireBindless(GL gl, Action<string> log);
TerrainAtlas AcquireTerrainAtlas(
IGameRenderResourceLifetime lifetime,
GL gl,
IDatReaderWriter dats,
BindlessSupport bindless);
/// <summary>
/// Campaign V slice V6i-2: the terrain atlas built through
/// <see cref="AcDream.App.Rendering.Gpu.IGpuDevice"/> rather than raw GL.
/// Same DATs, same decode, same layer ordering — only the upload differs.
/// <see cref="AcDream.App.Rendering.Gpu.IGpuDevice"/>. The raw-GL arm this
/// used to fork from (<c>AcquireTerrainAtlas</c>) was deleted at slice V11;
/// the name keeps its "BackendNeutral" suffix rather than being renamed, to
/// avoid touching every call site for a purely cosmetic change.
/// </summary>
TerrainAtlas AcquireBackendNeutralTerrainAtlas(
IGameRenderResourceLifetime lifetime,
@ -120,12 +104,11 @@ internal interface IWorldRenderCompositionFactory
Action<string> log);
void SetTerrainAnisotropic(TerrainAtlas atlas, int level);
Shader CreateTerrainShader(GL gl, string shadersDirectory);
SceneLightingUboBinding CreateSceneLighting(GL gl);
/// <summary>
/// Campaign V slice V6j: scene lighting on a backend with no global uniform
/// binding point. It publishes a ring section on the world pass scope and
/// each renderer binds it inside the pass.
/// each renderer binds it inside the pass. The raw-GL global-uniform-binding
/// arm (<c>CreateSceneLighting</c>) was deleted at slice V11.
/// </summary>
SceneLightingUboBinding CreateBackendNeutralSceneLighting(
ICurrentGpuFrameSource frameSource,
@ -140,17 +123,11 @@ internal interface IWorldRenderCompositionFactory
AcDream.App.Rendering.Gpu.IGpuDevice device,
ICurrentGpuFrameSource frameSource,
string shadersDirectory);
TerrainModernRenderer CreateTerrain(
GL gl,
BindlessSupport bindless,
Shader shader,
TerrainAtlas atlas,
AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice,
IGpuResourceRetirementQueue retirement);
/// <summary>
/// Campaign V slice V6j: terrain's RHI arm. No GL context, no linked
/// program — the pipeline compiles <c>terrain_modern</c> from the committed
/// SPIR-V and records into the world pass the scope publishes.
/// SPIR-V and records into the world pass the scope publishes. The raw-GL
/// arm (<c>CreateTerrain</c>) was deleted at slice V11.
/// </summary>
TerrainModernRenderer CreateBackendNeutralTerrain(
AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice,
@ -170,7 +147,6 @@ internal interface IWorldRenderCompositionFactory
uint initialCenterLandblockId,
float[] heightTable,
TerrainAtlas? atlas);
Shader CreateMeshShader(GL gl, string shadersDirectory);
WbMeshAdapter CreateMeshAdapter(
GL? gl,
AcDream.App.Rendering.Gpu.IGpuDevice device,
@ -179,10 +155,8 @@ internal interface IWorldRenderCompositionFactory
IGpuResourceRetirementQueue retirement,
ResidencyBudgetOptions budgets);
TextureCache CreateTextureCache(
GL? gl,
AcDream.App.Rendering.Gpu.IGpuDevice device,
IDatReaderWriter dats,
BindlessSupport? bindless,
IGpuResourceRetirementQueue retirement,
string diagnosticsDirectory,
ResidencyBudgetOptions budgets);
@ -193,20 +167,12 @@ internal interface IWorldRenderCompositionFactory
IPreparedAssetSource preparedAssets,
IAnimationLoader animations,
DatSoundCache? audio);
SamplerCache CreateSamplerCache(GL gl);
void Release(IDisposable resource);
}
internal sealed class RetailWorldRenderCompositionFactory
: IWorldRenderCompositionFactory
{
public void InitializeGlState(GL gl)
{
ArgumentNullException.ThrowIfNull(gl);
gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
gl.Enable(EnableCap.DepthTest);
}
public WorldRegionData LoadRegion(IDatReaderWriter dats)
{
ArgumentNullException.ThrowIfNull(dats);
@ -231,46 +197,6 @@ internal sealed class RetailWorldRenderCompositionFactory
environment.Initialize(region);
}
public BindlessSupport RequireBindless(GL gl, Action<string> log)
{
ArgumentNullException.ThrowIfNull(gl);
ArgumentNullException.ThrowIfNull(log);
if (BindlessSupport.TryCreate(gl, out BindlessSupport? bindless))
{
if (bindless!.HasShaderDrawParameters(gl))
{
log("[N.5] modern path capabilities present " +
"(bindless + ARB_shader_draw_parameters)");
return bindless;
}
log("[N.5] GL_ARB_shader_draw_parameters not present — " +
"modern path not available");
}
else
{
log("[N.5] GL_ARB_bindless_texture not present — " +
"modern path not available");
}
throw new NotSupportedException(
"acdream requires GL_ARB_bindless_texture + " +
"GL_ARB_shader_draw_parameters (GL 4.3+ with bindless support). " +
"Your GPU/driver does not expose these extensions. If this is " +
"unexpected, please file a bug report with your GPU vendor + " +
"driver version.");
}
public TerrainAtlas AcquireTerrainAtlas(
IGameRenderResourceLifetime lifetime,
GL gl,
IDatReaderWriter dats,
BindlessSupport bindless)
{
ArgumentNullException.ThrowIfNull(lifetime);
return lifetime.AcquireTerrainAtlas(
() => TerrainAtlas.Build(gl, dats, bindless));
}
public TerrainAtlas AcquireBackendNeutralTerrainAtlas(
IGameRenderResourceLifetime lifetime,
AcDream.App.Rendering.Gpu.IGpuDevice device,
@ -289,15 +215,6 @@ internal sealed class RetailWorldRenderCompositionFactory
public void SetTerrainAnisotropic(TerrainAtlas atlas, int level) =>
atlas.SetAnisotropic(level);
public Shader CreateTerrainShader(GL gl, string shadersDirectory) =>
new(
gl,
Path.Combine(shadersDirectory, "terrain_modern.vert"),
Path.Combine(shadersDirectory, "terrain_modern.frag"),
includeCommonPreamble: true);
public SceneLightingUboBinding CreateSceneLighting(GL gl) => new(gl);
public SceneLightingUboBinding CreateBackendNeutralSceneLighting(
ICurrentGpuFrameSource frameSource,
IWorldPassScope scope) =>
@ -321,25 +238,6 @@ internal sealed class RetailWorldRenderCompositionFactory
string shadersDirectory) =>
new(device, frameSource, shadersDirectory);
public TerrainModernRenderer CreateTerrain(
GL gl,
BindlessSupport bindless,
Shader shader,
TerrainAtlas atlas,
AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice,
IGpuResourceRetirementQueue retirement) =>
// Campaign V slice V4t: terrain's atlas slots live in the device's one
// texture table. This renderer only exists on GL — the `gl is not null`
// gate at its call site is the same one — so the backend cast is a
// statement of that fact rather than a narrowing.
new(
gl,
bindless,
shader,
atlas,
(AcDream.App.Rendering.Gpu.Gl.GlGpuDevice)gpuDevice,
retirement);
public TerrainModernRenderer CreateBackendNeutralTerrain(
AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice,
ICurrentGpuFrameSource frameSource,
@ -400,13 +298,6 @@ internal sealed class RetailWorldRenderCompositionFactory
new ConcurrentDictionary<uint, SurfaceInfo>());
}
public Shader CreateMeshShader(GL gl, string shadersDirectory) =>
new(
gl,
Path.Combine(shadersDirectory, "mesh_modern.vert"),
Path.Combine(shadersDirectory, "mesh_modern.frag"),
includeCommonPreamble: true);
public WbMeshAdapter CreateMeshAdapter(
GL? gl,
AcDream.App.Rendering.Gpu.IGpuDevice device,
@ -424,18 +315,14 @@ internal sealed class RetailWorldRenderCompositionFactory
budgets);
public TextureCache CreateTextureCache(
GL? gl,
AcDream.App.Rendering.Gpu.IGpuDevice device,
IDatReaderWriter dats,
BindlessSupport? bindless,
IGpuResourceRetirementQueue retirement,
string diagnosticsDirectory,
ResidencyBudgetOptions budgets) =>
new(
gl,
device,
dats,
bindless,
retirement,
diagnosticsDirectory,
budgets);
@ -501,19 +388,14 @@ internal sealed class RetailWorldRenderCompositionFactory
}
}
public SamplerCache CreateSamplerCache(GL gl) => new(gl);
public void Release(IDisposable resource) => resource.Dispose();
}
internal enum WorldRenderCompositionPoint
{
GlStateInitialized,
RegionLoaded,
EnvironmentInitialized,
BindlessPublished,
TerrainAtlasAcquired,
TerrainShaderPublished,
SceneLightingPublished,
DebugLinesPublished,
DebugFontCreated,
@ -522,10 +404,8 @@ internal enum WorldRenderCompositionPoint
HudResourcesCompleted,
TerrainPublished,
TerrainBuildStatePublished,
MeshShaderPublished,
MeshAdapterPublished,
TextureCachePublished,
SamplerCachePublished,
}
/// <summary>
@ -571,91 +451,53 @@ internal sealed class WorldRenderCompositionPhase
var scope = new CompositionAcquisitionScope();
try
{
// Campaign V slice V6h: null on a backend with no GL context. Every
// world renderer below is still raw GL, so the Vulkan arm composes
// the RHI-ported subset only — the texture cache's UI path, the
// debug font, the text renderer, the debug lines — and leaves the
// world absent until slices V4t/V4c/V4d land it.
GL? gl = platform.Graphics.Gl;
var residency = new ResidencyManager(
_dependencies.ResidencyBudgets);
if (gl is not null)
_factory.InitializeGlState(gl);
Fault(WorldRenderCompositionPoint.GlStateInitialized);
WorldRegionData region = _factory.LoadRegion(content.Dats);
Fault(WorldRenderCompositionPoint.RegionLoaded);
_factory.InitializeEnvironment(_dependencies.Environment, region.Region);
Fault(WorldRenderCompositionPoint.EnvironmentInitialized);
BindlessSupport? bindless = gl is null
? null
: _factory.RequireBindless(gl, _dependencies.Log);
if (bindless is not null)
_publication.PublishBindlessSupport(bindless);
Fault(WorldRenderCompositionPoint.BindlessPublished);
// Campaign V slice V6i-2: the atlas exists on BOTH arms now. GL
// builds it from raw texture names as before; a backend with no GL
// context builds the same layers through IGpuDevice.CreateTexture.
// That is what makes the blending/T-code tables below real on
// Vulkan — and, more to the point, it is what puts the new creation
// path under the validation layer instead of leaving it a claim.
TerrainAtlas? terrainAtlas = gl is not null && bindless is not null
? _factory.AcquireTerrainAtlas(
_dependencies.RenderResources,
gl,
content.Dats,
bindless)
: _factory.AcquireBackendNeutralTerrainAtlas(
_dependencies.RenderResources,
_dependencies.GpuDevice,
content.Dats);
// Campaign V slice V6i-2: the atlas builds through IGpuDevice on
// every remaining backend. The raw-GL arm that built it from raw
// texture names was deleted at slice V11.
TerrainAtlas terrainAtlas = _factory.AcquireBackendNeutralTerrainAtlas(
_dependencies.RenderResources,
_dependencies.GpuDevice,
content.Dats);
_factory.SetTerrainAnisotropic(
terrainAtlas,
settings.ResolvedQuality.AnisotropicLevel);
Fault(WorldRenderCompositionPoint.TerrainAtlasAcquired);
// The rest of the world texture stack's creation path, exercised on
// the arm that has no GL: one shared array of each format family and
// one composite array, created and released here. Nothing draws
// them; see the type's own documentation for why they are built
// anyway. It claims no composition point and enters no acquisition
// scope — the frozen publication order is a pinned assertion, and an
// exercise that owns nothing past its own statement is not a
// published owner.
if (gl is null)
{
_factory.ExerciseBackendNeutralWorldTextures(
_dependencies.GpuDevice,
_dependencies.Log);
}
// The rest of the world texture stack's creation path: one shared
// array of each format family and one composite array, created and
// released here. Nothing draws them; see the type's own
// documentation for why they are built anyway. It claims no
// composition point and enters no acquisition scope — the frozen
// publication order is a pinned assertion, and an exercise that
// owns nothing past its own statement is not a published owner.
_factory.ExerciseBackendNeutralWorldTextures(
_dependencies.GpuDevice,
_dependencies.Log);
string shadersDirectory = Path.Combine(
AppContext.BaseDirectory,
"Rendering",
"Shaders");
Shader? terrainShader = AcquireAndPublishIf(
gl is not null,
scope,
"terrain shader",
() => _factory.CreateTerrainShader(gl!, shadersDirectory),
_publication.PublishTerrainShader,
WorldRenderCompositionPoint.TerrainShaderPublished);
// Campaign V slice V6j: scene lighting exists on BOTH arms — the
// world renderers read it on both, and on the RHI arm it publishes a
// ring section instead of holding a global binding point.
IWorldPassScope? worldPassScope = platform.Graphics.WorldPassScope;
SceneLightingUboBinding? sceneLighting = AcquireAndPublish(
// Campaign V slice V6j: scene lighting publishes a ring section on
// the world pass scope; the raw-GL global-uniform-binding arm was
// deleted at slice V11.
IWorldPassScope worldPassScope = platform.Graphics.WorldPassScope
?? throw new InvalidOperationException(
"The graphics backend must publish a world pass scope.");
SceneLightingUboBinding sceneLighting = AcquireAndPublish(
scope,
"scene lighting",
() => gl is not null
? _factory.CreateSceneLighting(gl)
: _factory.CreateBackendNeutralSceneLighting(
_dependencies.GpuFrameSource,
worldPassScope
?? throw new InvalidOperationException(
"A backend without a GL context must publish a world pass scope.")),
() => _factory.CreateBackendNeutralSceneLighting(
_dependencies.GpuFrameSource,
worldPassScope),
_publication.PublishSceneLighting,
WorldRenderCompositionPoint.SceneLightingPublished);
DebugLineRenderer debugLines = AcquireAndPublish(
@ -671,28 +513,18 @@ internal sealed class WorldRenderCompositionPhase
(BitmapFont? debugFont, TextRenderer? textRenderer) =
ComposeOptionalHudResources(scope, shadersDirectory);
// Campaign V slice V6j: terrain exists on BOTH arms. The GL arm is
// unchanged; the RHI arm records into the world pass and reads the
// same backend-neutral atlas built above.
TerrainModernRenderer? terrain = AcquireAndPublish(
// Campaign V slice V6j: terrain records into the world pass and
// reads the same backend-neutral atlas built above. The raw-GL arm
// was deleted at slice V11.
TerrainModernRenderer terrain = AcquireAndPublish(
scope,
"terrain renderer",
() => gl is not null
? _factory.CreateTerrain(
gl,
bindless!,
terrainShader!,
terrainAtlas!,
_dependencies.GpuDevice,
_dependencies.ResourceRetirement)
: _factory.CreateBackendNeutralTerrain(
_dependencies.GpuDevice,
_dependencies.GpuFrameSource,
worldPassScope
?? throw new InvalidOperationException(
"A backend without a GL context must publish a world pass scope."),
terrainAtlas!,
_dependencies.ResourceRetirement),
() => _factory.CreateBackendNeutralTerrain(
_dependencies.GpuDevice,
_dependencies.GpuFrameSource,
worldPassScope,
terrainAtlas,
_dependencies.ResourceRetirement),
_publication.PublishTerrain,
WorldRenderCompositionPoint.TerrainPublished);
@ -707,25 +539,17 @@ internal sealed class WorldRenderCompositionPhase
terrainBuild.SurfaceCache);
Fault(WorldRenderCompositionPoint.TerrainBuildStatePublished);
Shader? meshShader = AcquireAndPublishIf(
gl is not null,
scope,
"mesh shader",
() => _factory.CreateMeshShader(gl!, shadersDirectory),
_publication.PublishMeshShader,
WorldRenderCompositionPoint.MeshShaderPublished);
if (meshShader is not null)
_dependencies.Log("[N.5] mesh_modern shader loaded");
// Campaign V slice V6i-3: the mesh pipeline exists on BOTH arms. Its
// upload bodies reached IGpuBuffer, so a backend with no GL context
// builds the same arena, the same atlases and the same render data —
// which is what makes streaming's publication into GPU state real
// there rather than a no-op.
// Campaign V slice V6i-3: the mesh pipeline builds the same arena,
// atlases and render data on every remaining backend, which is what
// makes streaming's publication into GPU state real rather than a
// no-op. gl is always null here — the raw-GL arm was deleted at
// slice V11, and the constructor's GL? gl parameter is Commit 3's
// (IMeshPipelineDevice.Gl) to remove.
WbMeshAdapter meshAdapter = AcquireAndPublish(
scope,
"WB mesh adapter",
() => _factory.CreateMeshAdapter(
gl,
gl: null,
_dependencies.GpuDevice,
content.Dats,
content.PreparedAssets,
@ -737,22 +561,13 @@ internal sealed class WorldRenderCompositionPhase
scope,
"texture cache",
() => _factory.CreateTextureCache(
gl,
_dependencies.GpuDevice,
content.Dats,
bindless,
_dependencies.ResourceRetirement,
_dependencies.DiagnosticsDirectory,
residency.Budgets),
_publication.PublishTextureCache,
WorldRenderCompositionPoint.TextureCachePublished);
SamplerCache? samplers = AcquireAndPublishIf(
gl is not null,
scope,
"sampler cache",
() => _factory.CreateSamplerCache(gl!),
_publication.PublishSamplerCache,
WorldRenderCompositionPoint.SamplerCachePublished);
_factory.RegisterResidencySources(
residency,
meshAdapter,
@ -769,18 +584,14 @@ internal sealed class WorldRenderCompositionPhase
terrainBuild,
new WorldRenderFoundation(
shadersDirectory,
bindless,
terrainAtlas,
terrainShader,
sceneLighting,
debugLines,
debugFont,
textRenderer,
terrain,
meshShader,
meshAdapter,
textureCache,
samplers,
residency));
}
catch (Exception failure)
@ -843,27 +654,6 @@ internal sealed class WorldRenderCompositionPhase
return value;
}
/// <summary>
/// Campaign V slice V6h: acquires an owner the active backend may not have.
/// The fault point still fires on both arms so a failure-injection test
/// covers the same ordered sequence whichever backend composed it.
/// </summary>
private T? AcquireAndPublishIf<T>(
bool supported,
CompositionAcquisitionScope scope,
string name,
Func<T> factory,
Action<T> publish,
WorldRenderCompositionPoint point)
where T : class, IDisposable
{
T? value = supported
? scope.Acquire(name, factory, _factory.Release).Publish(publish)
: null;
Fault(point);
return value;
}
private void Fault(WorldRenderCompositionPoint point) =>
_faultInjection?.Invoke(point);
}