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:
parent
b70b9832ff
commit
8a7a0837e1
121 changed files with 1243 additions and 19840 deletions
|
|
@ -16,7 +16,6 @@ using AcDream.Runtime;
|
|||
using AcDream.Runtime.Gameplay;
|
||||
using DatReaderWriter;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using AcDream.Core.Physics;
|
|||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
|
@ -236,20 +235,15 @@ internal sealed class FrameRootCompositionPhase
|
|||
{
|
||||
FrameRootDependencies d = _dependencies;
|
||||
WorldRenderFoundation foundation = world.Foundation;
|
||||
// Campaign V slice V6h: the frame root is the one seam that genuinely
|
||||
// forks. On GL every world renderer exists and the render graph is
|
||||
// unchanged. On Vulkan none of them does, so the graph is the clear pass,
|
||||
// the private-presentation phase, and the retained UI inside it — the
|
||||
// client's own frame, drawn entirely through the RHI.
|
||||
GL? gl = d.Graphics.Gl;
|
||||
// Campaign V slice V6h: the frame root's raw-GL render graph fork was
|
||||
// deleted at slice V11. The graph is the clear pass, the private-
|
||||
// presentation phase, and the retained UI inside it — the client's
|
||||
// own frame, drawn entirely through the RHI.
|
||||
var teleportRenderState =
|
||||
new LocalPlayerTeleportRenderStateSource(session.LocalTeleport);
|
||||
var renderLoginState = new RenderLoginStateSource(
|
||||
d.Options.LiveMode,
|
||||
d.PlayerMode);
|
||||
RenderFrameGlStateController? renderFrameGlState = gl is null
|
||||
? null
|
||||
: new RenderFrameGlStateController(new SilkRenderFrameGlStateApi(gl));
|
||||
// Campaign V slice V6i-3: on Vulkan the frame's clear is a load op of the
|
||||
// world pass rather than a pass of its own, so the two phases share this
|
||||
// one value. See VulkanWorldScenePhase for why the merge is required
|
||||
|
|
@ -268,18 +262,8 @@ internal sealed class FrameRootCompositionPhase
|
|||
live.ParticleRenderer,
|
||||
d.FrameProfiler,
|
||||
d.FrameDiagnosticsEnabled);
|
||||
IRenderFrameClearPhase clearPhase = gl is not null
|
||||
? new RuntimeRenderFrameClearPhase(
|
||||
gl,
|
||||
d.WorldTime,
|
||||
d.Weather,
|
||||
teleportRenderState,
|
||||
d.ParticleVisibility,
|
||||
host.WorldRenderDiagnostics
|
||||
?? throw new InvalidOperationException(
|
||||
"The GL frame root requires the GL state tripwire."),
|
||||
renderFrameGlState!)
|
||||
: new AcDream.App.Rendering.Gpu.Vk.VulkanRenderFrameClearPhase(
|
||||
IRenderFrameClearPhase clearPhase =
|
||||
new AcDream.App.Rendering.Gpu.Vk.VulkanRenderFrameClearPhase(
|
||||
d.WorldTime,
|
||||
d.Weather,
|
||||
teleportRenderState,
|
||||
|
|
@ -335,27 +319,17 @@ internal sealed class FrameRootCompositionPhase
|
|||
?? new WorldRenderDiagnostics(
|
||||
NullRenderGlStateReader.Instance,
|
||||
d.RenderDiagnosticLog);
|
||||
IRenderFrameGlState worldFrameGlState =
|
||||
(IRenderFrameGlState?)renderFrameGlState
|
||||
?? NullRenderFrameGlState.Instance;
|
||||
IRenderFrameGlState worldFrameGlState = NullRenderFrameGlState.Instance;
|
||||
IWorldPassScope? worldPassScope = d.Graphics.WorldPassScope;
|
||||
var worldFramebufferSource =
|
||||
new SilkRetailPViewFramebufferSource(d.Window);
|
||||
IWorldPassSurface worldPassSurface = gl is not null
|
||||
? new GlWorldPassSurface(
|
||||
gl,
|
||||
live.ClipFrame,
|
||||
worldFramebufferSource,
|
||||
live.DrawDispatcher!,
|
||||
live.EnvCellRenderer!,
|
||||
foundation.Terrain)
|
||||
: new RhiWorldPassSurface(
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope."),
|
||||
host.GpuFrameLifetime,
|
||||
live.ClipFrame,
|
||||
worldFramebufferSource);
|
||||
IWorldPassSurface worldPassSurface = new RhiWorldPassSurface(
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
host.GpuFrameLifetime,
|
||||
live.ClipFrame,
|
||||
worldFramebufferSource);
|
||||
var worldFrameEnvironment =
|
||||
new RuntimeWorldFrameEnvironmentPreparation(
|
||||
d.Options,
|
||||
|
|
@ -438,7 +412,7 @@ internal sealed class FrameRootCompositionPhase
|
|||
// V11, so nothing is lost — composing it would throw on the
|
||||
// first wireframe frame rather than silently misdraw (plan
|
||||
// §5.5.14 item 7).
|
||||
gl is not null ? foundation.DebugLines : null,
|
||||
null,
|
||||
d.PhysicsEngine,
|
||||
d.PlayerMode,
|
||||
d.PlayerController,
|
||||
|
|
@ -488,21 +462,17 @@ internal sealed class FrameRootCompositionPhase
|
|||
d.RenderRange,
|
||||
worldSceneDiagnostics,
|
||||
live.WorldAvailability);
|
||||
if (gl is null)
|
||||
{
|
||||
// On Vulkan the world renderer runs INSIDE the frame's one
|
||||
// backbuffer pass, which this phase opens, publishes on the
|
||||
// scope, and closes.
|
||||
worldSceneRenderer =
|
||||
new AcDream.App.Rendering.Gpu.Vk.VulkanWorldScenePhase(
|
||||
host.GpuFrameLifetime,
|
||||
vulkanClear,
|
||||
() => d.Graphics.Vulkan?.SampleCount ?? 1,
|
||||
(d.Graphics as VulkanGameWindowGraphics)?.WorldPassScopeCore
|
||||
?? throw new InvalidOperationException(
|
||||
"The Vulkan world phase requires the Vulkan graphics handle."),
|
||||
worldSceneRenderer);
|
||||
}
|
||||
// The world renderer runs INSIDE the frame's one backbuffer pass,
|
||||
// which this phase opens, publishes on the scope, and closes.
|
||||
worldSceneRenderer =
|
||||
new AcDream.App.Rendering.Gpu.Vk.VulkanWorldScenePhase(
|
||||
host.GpuFrameLifetime,
|
||||
vulkanClear,
|
||||
() => d.Graphics.Vulkan?.SampleCount ?? 1,
|
||||
(d.Graphics as VulkanGameWindowGraphics)?.WorldPassScopeCore
|
||||
?? throw new InvalidOperationException(
|
||||
"The Vulkan world phase requires the Vulkan graphics handle."),
|
||||
worldSceneRenderer);
|
||||
}
|
||||
Fault(FrameRootCompositionPoint.WorldRendererCreated);
|
||||
bindings = new FrameRootRuntimeBindings();
|
||||
|
|
@ -591,17 +561,15 @@ internal sealed class FrameRootCompositionPhase
|
|||
?? NullRenderFramePostDiagnosticsPhase.Instance;
|
||||
var renderFrame = new RenderFrameOrchestrator(
|
||||
host.GpuFrameLifetime,
|
||||
gl is not null
|
||||
? new FrameProfilerGpuMeasurement(d.FrameProfiler, gl)
|
||||
// Campaign V slice V8: the Vulkan arm measures the same bracket
|
||||
// through its own timestamp scope. It stayed on the null adapter
|
||||
// from V6h until V8, which meant no [frame-prof] line existed on
|
||||
// Vulkan at all — see VulkanFrameGpuMeasurement.
|
||||
: d.Graphics.Vulkan is { } vulkanGraphics
|
||||
? new AcDream.App.Rendering.Gpu.Vk.VulkanFrameGpuMeasurement(
|
||||
d.FrameProfiler,
|
||||
vulkanGraphics.Device)
|
||||
: AcDream.App.Rendering.Gpu.Vk.NullRenderFrameGpuMeasurement.Instance,
|
||||
// Campaign V slice V8: the Vulkan arm measures the frame bracket
|
||||
// through its own timestamp scope — see VulkanFrameGpuMeasurement.
|
||||
// The raw-GL adapter (FrameProfilerGpuMeasurement) was deleted at
|
||||
// slice V11.
|
||||
d.Graphics.Vulkan is { } vulkanGraphics
|
||||
? new AcDream.App.Rendering.Gpu.Vk.VulkanFrameGpuMeasurement(
|
||||
d.FrameProfiler,
|
||||
vulkanGraphics.Device)
|
||||
: AcDream.App.Rendering.Gpu.Vk.NullRenderFrameGpuMeasurement.Instance,
|
||||
framePreparation,
|
||||
worldSceneRenderer,
|
||||
privatePresentation,
|
||||
|
|
|
|||
|
|
@ -1,79 +1,32 @@
|
|||
using AcDream.App.Rendering.Gpu.Vk;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6h: the graphics ownership that platform acquisition
|
||||
/// publishes, once per backend.
|
||||
/// The graphics ownership that platform acquisition publishes.
|
||||
///
|
||||
/// <para><see cref="GameWindowPlatformAcquisition"/> was already generic over its
|
||||
/// graphics type; only the call sites pinned <c>GL</c>. This class is what they
|
||||
/// pin instead, so one composition pipeline drives both backends and the fork
|
||||
/// lives at the handful of construction sites that genuinely differ rather than
|
||||
/// in a second copy of the startup topology.</para>
|
||||
///
|
||||
/// <para>It is deliberately NOT a capability abstraction. Nothing dispatches on
|
||||
/// it per frame; phases that still speak raw GL ask for the context by name and
|
||||
/// take their Vulkan arm when it is absent. The GL arm is deleted at slice V11
|
||||
/// and this class with it.</para>
|
||||
/// <para>Vulkan is the only backend as of Campaign V slice V11: the raw-GL
|
||||
/// implementation (<c>OpenGlGameWindowGraphics</c>) and the members that only
|
||||
/// existed to branch on it (<c>Backend</c>, <c>Gl</c>, <c>RequireGl</c>) were
|
||||
/// deleted along with it. This class stays as the seam
|
||||
/// <see cref="GameWindowPlatformAcquisition"/> and the composition phases are
|
||||
/// already written against, so a future second backend would still add one
|
||||
/// class of this shape rather than reopening every call site.</para>
|
||||
/// </summary>
|
||||
internal abstract class GameWindowGraphics : IDisposable
|
||||
{
|
||||
/// <summary>Which backend this handle owns.</summary>
|
||||
public abstract RenderBackendKind Backend { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The live GL context, or null on any other backend. Phases whose owners
|
||||
/// are still raw GL branch on this; each such branch is a slice of Campaign
|
||||
/// V that has not landed yet, and the null arm names which one.
|
||||
/// </summary>
|
||||
public virtual GL? Gl => null;
|
||||
|
||||
/// <summary>The live Vulkan context, or null on any other backend.</summary>
|
||||
/// <summary>The live Vulkan context.</summary>
|
||||
public virtual VulkanGraphicsContext? Vulkan => null;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6j: the backend's world-pass seam, or null where the
|
||||
/// world renderers open their own passes.
|
||||
///
|
||||
/// <para>It lives here because the three composition phases that need it —
|
||||
/// world render, live presentation and the frame root — already borrow this
|
||||
/// handle, and because whether a backend HAS such a seam is exactly the kind
|
||||
/// of thing this type exists to answer. GL returns null: its renderers
|
||||
/// submit raw, and the frame spine still owns framebuffer management until
|
||||
/// V4h.</para>
|
||||
/// The backend's world-pass seam. Every remaining renderer records into the
|
||||
/// pass this publishes rather than opening its own.
|
||||
/// </summary>
|
||||
public virtual AcDream.App.Rendering.IWorldPassScope? WorldPassScope => null;
|
||||
|
||||
/// <summary>
|
||||
/// The GL context, or a failure naming the caller. Used where the call site
|
||||
/// has already established that the GL arm is running, so a null would be a
|
||||
/// composition bug rather than a backend difference.
|
||||
/// </summary>
|
||||
public GL RequireGl(string owner) =>
|
||||
Gl ?? throw new InvalidOperationException(
|
||||
$"'{owner}' requires the OpenGL context, but the {Backend} backend is active.");
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
|
||||
/// <summary>OpenGL ownership: the Silk.NET <see cref="GL"/> context itself.</summary>
|
||||
internal sealed class OpenGlGameWindowGraphics : GameWindowGraphics
|
||||
{
|
||||
public OpenGlGameWindowGraphics(GL gl) =>
|
||||
Context = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
|
||||
/// <summary>The owned context. <see cref="GameWindowGraphics.Gl"/> is the borrowed view.</summary>
|
||||
public GL Context { get; }
|
||||
|
||||
public override RenderBackendKind Backend => RenderBackendKind.Gl;
|
||||
|
||||
public override GL? Gl => Context;
|
||||
|
||||
public override void Dispose() => Context.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vulkan ownership: the instance, surface, device, swapchain and RHI device
|
||||
/// that <see cref="VulkanGraphicsContext"/> acquired and gated.
|
||||
|
|
@ -91,8 +44,6 @@ internal sealed class VulkanGameWindowGraphics : GameWindowGraphics
|
|||
|
||||
public VulkanGraphicsContext Context { get; }
|
||||
|
||||
public override RenderBackendKind Backend => RenderBackendKind.Vulkan;
|
||||
|
||||
public override VulkanGraphicsContext? Vulkan => Context;
|
||||
|
||||
/// <summary>The concrete scope, for the phase that publishes the encoder on it.</summary>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using AcDream.App.Rendering.Gpu;
|
|||
using AcDream.UI.Abstractions.Input;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.Maths;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
|
|
@ -115,91 +114,6 @@ internal interface IHostInputCameraCompositionFactory
|
|||
PointerPositionState pointer);
|
||||
}
|
||||
|
||||
internal sealed class RetailHostInputCameraCompositionFactory
|
||||
: IHostInputCameraCompositionFactory
|
||||
{
|
||||
public IFramebufferViewportTarget CreateViewportTarget(GameWindowGraphics graphics) =>
|
||||
new SilkFramebufferViewportTarget(graphics.RequireGl("viewport target"));
|
||||
|
||||
public GpuFrameFlightController? CreateGpuFrameFlights(GameWindowGraphics graphics) =>
|
||||
new(graphics.RequireGl("GPU frame flights"));
|
||||
|
||||
public IGpuDevice CreateGpuDevice(
|
||||
GameWindowGraphics graphics,
|
||||
GpuFrameFlightController? frameFlights) =>
|
||||
new AcDream.App.Rendering.Gpu.Gl.GlGpuDevice(
|
||||
graphics.RequireGl("GPU device (RHI)"),
|
||||
frameFlights ?? throw new ArgumentNullException(nameof(frameFlights)),
|
||||
Path.Combine(AppContext.BaseDirectory, "Rendering", "Shaders"));
|
||||
|
||||
public IGpuResourceRetirementQueue CreateRetirement(
|
||||
GameWindowGraphics graphics,
|
||||
GpuFrameFlightController? frameFlights,
|
||||
IGpuDevice device) =>
|
||||
frameFlights ?? throw new ArgumentNullException(nameof(frameFlights));
|
||||
|
||||
public IRenderFrameSlotSource CreateFrameSlots(
|
||||
GameWindowGraphics graphics,
|
||||
GpuFrameFlightController? frameFlights,
|
||||
IGpuDevice device) =>
|
||||
frameFlights ?? throw new ArgumentNullException(nameof(frameFlights));
|
||||
|
||||
public WorldRenderDiagnostics? CreateWorldRenderDiagnostics(
|
||||
GameWindowGraphics graphics,
|
||||
IRenderFrameDiagnosticLog log) =>
|
||||
new(
|
||||
new SilkRenderGlStateReader(
|
||||
graphics.RequireGl("world render diagnostics")),
|
||||
log);
|
||||
|
||||
public SilkKeyboardSource CreateKeyboardSource(
|
||||
IKeyboard keyboard,
|
||||
HostQuiescenceGate quiescence) =>
|
||||
SilkKeyboardSource.CreateDetached(keyboard, quiescence);
|
||||
|
||||
public SilkMouseSource CreateMouseSource(
|
||||
IMouse mouse,
|
||||
IInputCaptureSource capture,
|
||||
IKeyboardSource? keyboard,
|
||||
HostQuiescenceGate quiescence) =>
|
||||
SilkMouseSource.CreateDetached(mouse, capture, keyboard, quiescence);
|
||||
|
||||
public IMouseLookCursor CreateMouseLookCursor(IMouse mouse) =>
|
||||
new SilkMouseLookCursor(mouse);
|
||||
|
||||
public InputDispatcher CreateInputDispatcher(
|
||||
IKeyboardSource keyboard,
|
||||
IMouseSource mouse,
|
||||
KeyBindings bindings) =>
|
||||
InputDispatcher.CreateDetached(keyboard, mouse, bindings);
|
||||
|
||||
public CameraController CreateCameraController() =>
|
||||
new(new OrbitCamera(), new FlyCamera());
|
||||
|
||||
public IFramebufferCameraTarget CreateCameraTarget(CameraController camera) =>
|
||||
new CameraFramebufferTarget(camera);
|
||||
|
||||
public CameraPointerInputController CreateCameraPointerInput(
|
||||
IReadOnlyList<IMouse> mice,
|
||||
HostQuiescenceGate quiescence,
|
||||
IInputCaptureSource capture,
|
||||
LocalPlayerModeState playerMode,
|
||||
CameraController camera,
|
||||
ChaseCameraInputState chase,
|
||||
IMouseSource mouse,
|
||||
PointerPositionState pointer) =>
|
||||
CameraPointerInputController.Create(
|
||||
mice,
|
||||
quiescence,
|
||||
capture,
|
||||
playerMode,
|
||||
camera,
|
||||
chase,
|
||||
mouse,
|
||||
pointer,
|
||||
new EnvironmentInputMonotonicClock());
|
||||
}
|
||||
|
||||
internal enum HostInputCameraCompositionPoint
|
||||
{
|
||||
ViewportBound,
|
||||
|
|
@ -236,7 +150,7 @@ internal sealed class HostInputCameraCompositionPhase :
|
|||
private readonly IHostInputCameraCompositionFactory? _injectedFactory;
|
||||
private readonly Action<HostInputCameraCompositionPoint>? _faultInjection;
|
||||
private IHostInputCameraCompositionFactory _factory =
|
||||
new RetailHostInputCameraCompositionFactory();
|
||||
new VulkanHostInputCameraCompositionFactory();
|
||||
|
||||
public HostInputCameraCompositionPhase(
|
||||
HostInputCameraDependencies dependencies,
|
||||
|
|
@ -253,17 +167,13 @@ internal sealed class HostInputCameraCompositionPhase :
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6h: the default factory is chosen from the platform
|
||||
/// result, not from the host. The backend is a property of the graphics
|
||||
/// ownership that acquisition published, so the phase reads it rather than
|
||||
/// making every call site branch — and an injected factory (composition
|
||||
/// The Vulkan factory is the only one left since the raw-GL arm was
|
||||
/// deleted at Campaign V slice V11; an injected factory (composition
|
||||
/// tests) still wins.
|
||||
/// </summary>
|
||||
private static IHostInputCameraCompositionFactory DefaultFactoryFor(
|
||||
GameWindowGraphics graphics) =>
|
||||
graphics.Backend == RenderBackendKind.Vulkan
|
||||
? new VulkanHostInputCameraCompositionFactory()
|
||||
: new RetailHostInputCameraCompositionFactory();
|
||||
new VulkanHostInputCameraCompositionFactory();
|
||||
|
||||
public HostInputCameraResult Compose(
|
||||
GameWindowPlatformResult<GameWindowGraphics, IInputContext> platform)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ using AcDream.UI.Abstractions.Panels.Chat;
|
|||
using AcDream.UI.Abstractions.Panels.Vitals;
|
||||
using DatReaderWriter;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ using AcDream.Runtime.Entities;
|
|||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
|
@ -678,46 +677,28 @@ internal sealed class LivePresentationCompositionPhase
|
|||
AlphaScratchBudgetProfile.Create(
|
||||
d.Options.ResidencyBudgets.AlphaScratchBytes);
|
||||
|
||||
// Campaign V slice V6h: every renderer below this line is still raw GL.
|
||||
// On a backend without a GL context none of them is constructed, and the
|
||||
// CPU owners this phase also produces — entity lifetime, motion,
|
||||
// selection, effects, lights, the landblock pipeline — run unchanged.
|
||||
GL? gl = d.Graphics.Gl;
|
||||
var selectionScene = new RetailSelectionScene(
|
||||
new RetailSelectionGeometryCache(content.Dats, d.DatLock));
|
||||
// Campaign V slice V6j: the world dispatcher exists on BOTH arms. The GL
|
||||
// arm is unchanged; the RHI arm records into the pass the world scene
|
||||
// phase publishes on this scope.
|
||||
// Campaign V slice V6j: the world dispatcher records into the pass the
|
||||
// world scene phase publishes on this scope. The raw-GL arm was
|
||||
// deleted at slice V11.
|
||||
IWorldPassScope? worldPassScope = d.Graphics.WorldPassScope;
|
||||
var dispatcherLease = scope.Acquire(
|
||||
"WB draw dispatcher",
|
||||
() => gl is not null
|
||||
? new WbDrawDispatcher(
|
||||
gl,
|
||||
foundation.MeshShader!,
|
||||
foundation.TextureCache,
|
||||
foundation.MeshAdapter!,
|
||||
entitySpawnAdapter,
|
||||
foundation.Bindless!,
|
||||
d.ClassificationCache,
|
||||
d.TranslucencyFades,
|
||||
selectionScene,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.DispatcherBytes)
|
||||
: new WbDrawDispatcher(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope."),
|
||||
foundation.TextureCache,
|
||||
foundation.MeshAdapter!,
|
||||
entitySpawnAdapter,
|
||||
d.ClassificationCache,
|
||||
d.TranslucencyFades,
|
||||
selectionScene,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.DispatcherBytes),
|
||||
() => new WbDrawDispatcher(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
foundation.TextureCache,
|
||||
foundation.MeshAdapter!,
|
||||
entitySpawnAdapter,
|
||||
d.ClassificationCache,
|
||||
d.TranslucencyFades,
|
||||
selectionScene,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.DispatcherBytes),
|
||||
static value => value.Dispose());
|
||||
var selectionQuery = new WorldSelectionQuery(
|
||||
liveEntities,
|
||||
|
|
@ -815,8 +796,9 @@ internal sealed class LivePresentationCompositionPhase
|
|||
paperdollLease = scope.Acquire(
|
||||
"paperdoll viewport",
|
||||
() => new PaperdollViewportRenderer(
|
||||
gl,
|
||||
worldPassScope,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
paperdollDispatcher,
|
||||
|
|
@ -861,8 +843,9 @@ internal sealed class LivePresentationCompositionPhase
|
|||
creatureAppraisalLease = scope.Acquire(
|
||||
"creature appraisal viewport",
|
||||
() => new CreatureAppraisalViewportRenderer(
|
||||
gl,
|
||||
worldPassScope,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
appraisalDispatcher,
|
||||
|
|
@ -897,24 +880,17 @@ internal sealed class LivePresentationCompositionPhase
|
|||
var envCellFrustum = new WbFrustum();
|
||||
var envCellLease = scope.Acquire(
|
||||
"environment-cell renderer",
|
||||
() => gl is not null
|
||||
? new EnvCellRenderer(
|
||||
gl,
|
||||
foundation.MeshAdapter!.MeshManager!,
|
||||
envCellFrustum)
|
||||
: new EnvCellRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope."),
|
||||
foundation.MeshAdapter!.MeshManager!,
|
||||
envCellFrustum),
|
||||
() => new EnvCellRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
foundation.MeshAdapter!.MeshManager!,
|
||||
envCellFrustum),
|
||||
static value => value.Dispose());
|
||||
// The RHI arm's three pipelines ARE its program, built at construction,
|
||||
// so only the GL arm has a second initialisation step.
|
||||
if (foundation.MeshShader is { } envCellShader)
|
||||
envCellLease.Resource.Initialize(envCellShader);
|
||||
// The three pipelines ARE its program, built at construction — the
|
||||
// raw-GL arm's separate Initialize(Shader) step was deleted at V11.
|
||||
Fault(LivePresentationCompositionPoint.EnvironmentCellsCreated);
|
||||
|
||||
// The streaming pipeline itself is backend-neutral and runs on both
|
||||
|
|
@ -972,20 +948,18 @@ internal sealed class LivePresentationCompositionPhase
|
|||
"portal clip frame",
|
||||
ClipFrame.NoClip,
|
||||
static value => value.Dispose());
|
||||
// Campaign V slice V6l: the portal depth mask exists on BOTH arms. The
|
||||
// GL arm keeps its inline program and raw draws; the RHI arm compiles
|
||||
// portal_depth from SPIR-V into three pipelines and records into the
|
||||
// pass the world scene phase publishes on this scope.
|
||||
// Campaign V slice V6l: the portal depth mask compiles portal_depth
|
||||
// from SPIR-V into three pipelines and records into the pass the
|
||||
// world scene phase publishes on this scope. The raw-GL inline
|
||||
// program and raw draws were deleted at slice V11.
|
||||
var portalDepthLease = scope.Acquire(
|
||||
"portal depth mask",
|
||||
() => gl is not null
|
||||
? new PortalDepthMaskRenderer(gl)
|
||||
: new PortalDepthMaskRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope.")),
|
||||
() => new PortalDepthMaskRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope.")),
|
||||
static value => value.Dispose());
|
||||
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
||||
PortalWaitNoticeController>? portalWaitNoticeLease = null;
|
||||
|
|
@ -1003,11 +977,9 @@ internal sealed class LivePresentationCompositionPhase
|
|||
: null;
|
||||
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
||||
PortalTunnelPresentation>? portalTunnelLease = null;
|
||||
// Campaign V slice V6m: portal space exists on BOTH arms. The GL arm is
|
||||
// unchanged; the RHI arm opens a backbuffer pass of its own and publishes
|
||||
// it on the scope for the span of the draw, the way the two offscreen
|
||||
// viewports do. It was the last raw-GL world-adjacent renderer, so the
|
||||
// Vulkan arm no longer composes a portal-less teleport presentation.
|
||||
// Campaign V slice V6m: portal space opens a backbuffer pass of its
|
||||
// own and publishes it on the scope for the span of the draw, the way
|
||||
// the two offscreen viewports do. The raw-GL arm was deleted at V11.
|
||||
if (dispatcherLease.Resource is { } portalDispatcher)
|
||||
{
|
||||
PortalTunnelPresentation portalTunnel;
|
||||
|
|
@ -1015,8 +987,9 @@ internal sealed class LivePresentationCompositionPhase
|
|||
{
|
||||
portalTunnel = d.PortalTunnelFallback.AcquirePrepared(
|
||||
() => PortalTunnelPresentation.CreateRequired(
|
||||
gl,
|
||||
worldPassScope,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
host.GpuFrameLifetime,
|
||||
content.Dats,
|
||||
content.AnimationLoader,
|
||||
|
|
@ -1051,87 +1024,45 @@ internal sealed class LivePresentationCompositionPhase
|
|||
}
|
||||
Fault(LivePresentationCompositionPoint.PortalResourcesCreated);
|
||||
|
||||
AcDream.App.Rendering.Shader? skyShader = gl is null
|
||||
? null
|
||||
: d.RenderResourceLifetime.AcquireSkyShader(
|
||||
() => new AcDream.App.Rendering.Shader(
|
||||
gl,
|
||||
Path.Combine(foundation.ShadersDirectory, "sky.vert"),
|
||||
Path.Combine(foundation.ShadersDirectory, "sky.frag"),
|
||||
// Campaign V slice V6e: sky reads the shared texture table and
|
||||
// ACDREAM_UBO_SET now, both of which common.glsl declares.
|
||||
includeCommonPreamble: true));
|
||||
var skyShaderLease = skyShader is null
|
||||
? null
|
||||
: scope.Own(
|
||||
"sky shader lifetime",
|
||||
skyShader,
|
||||
_ => d.RenderResourceLifetime.ReleaseSkyShader());
|
||||
// Campaign V slice V6k: the sky exists on BOTH arms. The GL arm is
|
||||
// unchanged except for where its table slots come from; the RHI arm
|
||||
// compiles the sky pair from SPIR-V and records into the pass the world
|
||||
// scene phase publishes on this scope.
|
||||
// Campaign V slice V6k: the sky compiles its pair from SPIR-V and
|
||||
// records into the pass the world scene phase publishes on this
|
||||
// scope. The raw-GL arm — its own shader pair (acquired through
|
||||
// GameRenderResourceLifetime.AcquireSkyShader, deleted with it) and
|
||||
// its own bindless/device-table wiring — was deleted at slice V11.
|
||||
var skyLease = scope.Acquire(
|
||||
"sky renderer",
|
||||
() => gl is not null
|
||||
? new SkyRenderer(
|
||||
gl,
|
||||
content.Dats,
|
||||
skyShader!,
|
||||
foundation.TextureCache,
|
||||
foundation.Samplers!,
|
||||
// Slice V6e: the sky samples through the shared texture table,
|
||||
// so it needs the same bindless entry point the world path uses.
|
||||
foundation.Bindless!,
|
||||
// Slice V6k: and V4t's world-handle seam on the device, which
|
||||
// retired the last per-renderer GlBindlessHandleTable.
|
||||
(AcDream.App.Rendering.Gpu.Gl.GlGpuDevice)host.GpuDevice)
|
||||
{
|
||||
// Campaign V slice V7: null unless ACDREAM_SKY_PHASE_SECONDS
|
||||
// is set, which is every run but a differential gate's.
|
||||
AnimationPhaseSecondsOverride = d.Options.SkyAnimationPhaseSeconds,
|
||||
}
|
||||
: new SkyRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope."),
|
||||
content.Dats,
|
||||
foundation.TextureCache)
|
||||
{
|
||||
AnimationPhaseSecondsOverride = d.Options.SkyAnimationPhaseSeconds,
|
||||
},
|
||||
() => new SkyRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
content.Dats,
|
||||
foundation.TextureCache)
|
||||
{
|
||||
// Campaign V slice V7: null unless ACDREAM_SKY_PHASE_SECONDS
|
||||
// is set, which is every run but a differential gate's.
|
||||
AnimationPhaseSecondsOverride = d.Options.SkyAnimationPhaseSeconds,
|
||||
},
|
||||
static value => value.Dispose());
|
||||
// Campaign V slice V6l: particles exist on BOTH arms. The GL arm is
|
||||
// unchanged; the RHI arm compiles the two particle pairs from SPIR-V,
|
||||
// draws instances through the vertex binding the V6l contract amendment
|
||||
// added, and records into the pass the world scene phase publishes on
|
||||
// this scope.
|
||||
// Campaign V slice V6l: the RHI arm compiles the two particle pairs
|
||||
// from SPIR-V, draws instances through the vertex binding the V6l
|
||||
// contract amendment added, and records into the pass the world scene
|
||||
// phase publishes on this scope. The raw-GL arm was deleted at V11.
|
||||
var particleLease = scope.AcquireOptional(
|
||||
"particle renderer",
|
||||
() => gl is not null
|
||||
? new ParticleRenderer(
|
||||
gl,
|
||||
foundation.ShadersDirectory,
|
||||
content.ParticleSystem,
|
||||
foundation.TextureCache,
|
||||
content.Dats,
|
||||
foundation.MeshAdapter!,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.ParticleBytes)
|
||||
: new ParticleRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"A backend without a GL context must publish a world pass scope."),
|
||||
content.ParticleSystem,
|
||||
foundation.TextureCache,
|
||||
content.Dats,
|
||||
foundation.MeshAdapter!,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.ParticleBytes),
|
||||
() => new ParticleRenderer(
|
||||
host.GpuDevice,
|
||||
host.GpuFrameLifetime,
|
||||
worldPassScope
|
||||
?? throw new InvalidOperationException(
|
||||
"The graphics backend must publish a world pass scope."),
|
||||
content.ParticleSystem,
|
||||
foundation.TextureCache,
|
||||
content.Dats,
|
||||
foundation.MeshAdapter!,
|
||||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.ParticleBytes),
|
||||
static value => value.Dispose());
|
||||
Fault(LivePresentationCompositionPoint.SkyAndParticlesCreated);
|
||||
|
||||
|
|
@ -1252,7 +1183,6 @@ internal sealed class LivePresentationCompositionPhase
|
|||
portalTunnelLease?.Transfer();
|
||||
portalWaitNoticeLease?.Transfer();
|
||||
skyLease.Transfer();
|
||||
skyShaderLease?.Transfer();
|
||||
particleLease.Transfer();
|
||||
bindingsLease.Transfer();
|
||||
Fault(LivePresentationCompositionPoint.ResultPublished);
|
||||
|
|
|
|||
|
|
@ -8,28 +8,22 @@ using Silk.NET.Input;
|
|||
namespace AcDream.App.Composition;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6h: the Vulkan arm of the host phase.
|
||||
///
|
||||
/// <para>This is the whole of the backend fork at composition Phase 1. Only the
|
||||
/// four graphics members differ from
|
||||
/// <see cref="RetailHostInputCameraCompositionFactory"/>; input, camera and
|
||||
/// pointer construction are platform concerns, not graphics ones, so they
|
||||
/// delegate rather than duplicate. Adding a fifth backend would add one more
|
||||
/// class of this shape and touch nothing else in the composition pipeline —
|
||||
/// which is the property §5.5.9 asked this slice to establish.</para>
|
||||
/// Campaign V slice V6h: the Vulkan arm of the host phase — now the only arm,
|
||||
/// the raw-GL <c>RetailHostInputCameraCompositionFactory</c> it used to fork
|
||||
/// from having been deleted at slice V11. Input, camera and pointer
|
||||
/// construction are inlined directly rather than delegated, since there is no
|
||||
/// longer a second implementation to share them with.
|
||||
///
|
||||
/// <para><b>What is absent, and why.</b> There is no GL fence ring: the RHI
|
||||
/// device owns its own frames-in-flight through a timeline semaphore, so
|
||||
/// retirement and slot indexing come from the device instead. There is no
|
||||
/// <see cref="WorldRenderDiagnostics"/>: it is a raw-GL state tripwire, and
|
||||
/// <see cref="WorldRenderDiagnostics"/>: it was a raw-GL state tripwire, and
|
||||
/// Vulkan has no global state for it to watch. Both nulls are read by the
|
||||
/// phases that would otherwise consume them.</para>
|
||||
/// </summary>
|
||||
internal sealed class VulkanHostInputCameraCompositionFactory
|
||||
: IHostInputCameraCompositionFactory
|
||||
{
|
||||
private readonly RetailHostInputCameraCompositionFactory _platform = new();
|
||||
|
||||
public IFramebufferViewportTarget CreateViewportTarget(
|
||||
GameWindowGraphics graphics) =>
|
||||
// The viewport is a pipeline dynamic state on Vulkan, set per pass by
|
||||
|
|
@ -64,29 +58,29 @@ internal sealed class VulkanHostInputCameraCompositionFactory
|
|||
public SilkKeyboardSource CreateKeyboardSource(
|
||||
IKeyboard keyboard,
|
||||
HostQuiescenceGate quiescence) =>
|
||||
_platform.CreateKeyboardSource(keyboard, quiescence);
|
||||
SilkKeyboardSource.CreateDetached(keyboard, quiescence);
|
||||
|
||||
public SilkMouseSource CreateMouseSource(
|
||||
IMouse mouse,
|
||||
IInputCaptureSource capture,
|
||||
IKeyboardSource? keyboard,
|
||||
HostQuiescenceGate quiescence) =>
|
||||
_platform.CreateMouseSource(mouse, capture, keyboard, quiescence);
|
||||
SilkMouseSource.CreateDetached(mouse, capture, keyboard, quiescence);
|
||||
|
||||
public IMouseLookCursor CreateMouseLookCursor(IMouse mouse) =>
|
||||
_platform.CreateMouseLookCursor(mouse);
|
||||
new SilkMouseLookCursor(mouse);
|
||||
|
||||
public InputDispatcher CreateInputDispatcher(
|
||||
IKeyboardSource keyboard,
|
||||
IMouseSource mouse,
|
||||
KeyBindings bindings) =>
|
||||
_platform.CreateInputDispatcher(keyboard, mouse, bindings);
|
||||
InputDispatcher.CreateDetached(keyboard, mouse, bindings);
|
||||
|
||||
public CameraController CreateCameraController() =>
|
||||
_platform.CreateCameraController();
|
||||
new(new OrbitCamera(), new FlyCamera());
|
||||
|
||||
public IFramebufferCameraTarget CreateCameraTarget(CameraController camera) =>
|
||||
_platform.CreateCameraTarget(camera);
|
||||
new CameraFramebufferTarget(camera);
|
||||
|
||||
public CameraPointerInputController CreateCameraPointerInput(
|
||||
IReadOnlyList<IMouse> mice,
|
||||
|
|
@ -97,7 +91,7 @@ internal sealed class VulkanHostInputCameraCompositionFactory
|
|||
ChaseCameraInputState chase,
|
||||
IMouseSource mouse,
|
||||
PointerPositionState pointer) =>
|
||||
_platform.CreateCameraPointerInput(
|
||||
CameraPointerInputController.Create(
|
||||
mice,
|
||||
quiescence,
|
||||
capture,
|
||||
|
|
@ -105,14 +99,15 @@ internal sealed class VulkanHostInputCameraCompositionFactory
|
|||
camera,
|
||||
chase,
|
||||
mouse,
|
||||
pointer);
|
||||
pointer,
|
||||
new EnvironmentInputMonotonicClock());
|
||||
|
||||
private static VulkanGraphicsContext RequireContext(
|
||||
GameWindowGraphics graphics) =>
|
||||
graphics.Vulkan
|
||||
?? throw new InvalidOperationException(
|
||||
"The Vulkan host factory was composed against the " +
|
||||
$"{graphics.Backend} backend.");
|
||||
"The Vulkan host factory was composed against a backend with no " +
|
||||
"Vulkan context.");
|
||||
|
||||
/// <summary>
|
||||
/// Vulkan sets the viewport per pass from the pass extent, so there is no
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue