acdream/src/AcDream.App/Rendering/RenderFrameResourceController.cs
Erik 7a0227c12e feat(render): Vulkan campaign V11 step 3 — drop the GL packages and shaders
Commit 2 deleted the GL rendering backend's implementations; this step
removes the package references and shader vocabulary they leave behind,
so nothing in the App project still spells Silk.NET.OpenGL.

Silk.NET.OpenGL and Silk.NET.OpenGL.Extensions.ARB are dropped from
AcDream.App.csproj. Chorizite.Core stays — the audit is NOT clean: its
Render.Enums (TextureFormat, BufferUsage) and Lib.BoundingBox types are
used directly and extensively across the Wb texture/mesh pipeline,
independent of the deleted GL IUniformBuffer implementers the package
comment used to cite. The stale comment is corrected in place.

IMeshPipelineDevice.Gl is removed along with the GL? gl parameter
threaded through WbMeshAdapter's four constructors, WorldRenderComposition's
CreateMeshAdapter, and VulkanMeshPipelineDevice's Gl => null
implementation — nothing read any of them once the legacy per-mesh
upload bodies were gone (confirmed by grep: the sole non-doc-comment hit
was a test assertion). While in WbMeshAdapter.Dispose(), found and fixed
a real bug along the way: its teardown still pattern-matched the deleted
GL GpuFrameFlightController to decide whether to wait for submitted work,
which VulkanFrameFlightController replaced at slice V6a without this site
being updated — so the wait had been silently dead on every Vulkan run
since then. Retargeted to VulkanFrameFlightController, which carries the
same WaitForSubmittedWork().

The GL pixel-format vocabulary (Silk.NET.OpenGL.PixelFormat/PixelType) that
WorldTextureArray/TextureFormatExtensions/TextureAtlasManager used for
upload validation is replaced by AcDream.Content's existing Silk.NET-free
UploadPixelFormat/UploadPixelType enums (added at MP1a to keep the bake
tool GL-free); two new members (Rgb, Red, Float) extend that enum with
their GL ABI constants to cover the full vocabulary WorldTextureArray
needs, since MP1a's original set only covered what the extractor itself
emits. ObjectMeshManager's App-boundary cast
`(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat` becomes a direct
pass-through now that both sides share the type.

GpuBindingModel.StorageTextureTable (the GL-only binding=9 emulation of
the Vulkan texture table) is deleted and StorageBindingCount drops from
10 to 9; the descriptor-set-layout code that builds from that count
(VulkanPipelineLayouts, VulkanFrameBindings) is untouched and just
allocates one fewer always-dummy-seeded, always-unused binding.

Several fully dead GL-only classes came along for the ride, confirmed by
zero construction sites: SilkFramebufferViewportTarget
(NullFramebufferViewportTarget is the sole production
IFramebufferViewportTarget), SilkRenderGlStateReader
(NullRenderGlStateReader.Instance is the sole IRenderGlStateReader),
RuntimeRenderFrameClearPhase (VulkanRenderFrameClearPhase is the sole
IRenderFrameClearPhase, expressing the same atmosphere-clear logic as a
pass load-op instead), and GpuFrameTimer plus FrameProfiler's
GL-owning FrameBoundary(GL) overload and BeginGpuFrame/EndGpuFrame
bracket (RecordGpuSample is the only GPU-timing path any backend uses
now — the ACDREAM_WB_DIAG nested-query exclusion these existed for no
longer applies, since WbDrawDispatcher's own diagnostic GPU sampling
already moved to the device's Vulkan timer pool). GpuFrameFlightController
itself stays (never constructed with a real fence API in production, but
its retirement-ledger/serial-ring logic is backend-neutral and still
covered by its own unit tests) — only its GL-specific parts (the public
GL constructor overload, SilkGpuFenceApi) are deleted, since removing the
whole class would mean restructuring the frozen Slice-8 composition
shape's GpuFrameFlightController? threading, which is out of this
commit's scope. TextureParameters.cs and BufferUsageExtensions.cs
(zero callers each) are deleted outright.

common.glsl is deleted: nothing in the actual Vulkan .spv build reads
it. tools/ShaderCompiler/Program.cs compiles each .vert/.frag pair
directly and tools/ShaderCompiler/VulkanGlslPreamble.cs injects its own
complete self-contained preamble per file; common.glsl's textual
concatenation was exclusively Shader.cs's GL-only mechanism, deleted at
Commit 2. The five shader files that named it in comments
(mesh_modern.vert, particle.vert, particle.frag, sky.frag,
terrain_modern.frag) are corrected to point at VulkanGlslPreamble.cs
instead. mesh.vert/mesh.frag — the pre-N.5 legacy shader pair the
mandatory modern path already made unreachable, with zero C# consumers
and no compiled .spv — are deleted too. Regenerated via
tools/compile-shaders.ps1: 9/9 remaining shader pairs compile
(previously 9/10, with mesh the sole failure — the VulkanShaderManifestTests
doc comment's "nine of ten are not Vulkan-expressible" was already
stale before this commit).

Test fallout: dead-subject test methods/files are deleted rather than
patched (TextRendererFailureSafetyTests.cs, ClipFrameUploadTests.cs,
GpuResourceRetirementTransactionTests.cs's GL queue tests, one
WorldRenderDiagnosticsTests source-order test, one
RenderFrameResourceControllerTests clear-phase-order test); tests whose
subject moved or was renamed are updated in place rather than deleted
(GpuContractTests, VulkanCapabilityGateTests, MeshPipelineDeviceSeamTests'
pinned seven-member surface now reads six, ParticleBindlessInstanceTests'
cross-dialect check now covers the one surviving dialect,
WbMeshAdapterTests' misleadingly-named null-gl test — gpuDevice was
always the parameter that actually threw).

Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors,
with the Silk.NET.OpenGL/.Extensions.ARB package references physically
removed from the csproj (not just unreferenced in code).
Tests: full-solution `dotnet test` green across every project.
Zero remaining `using Silk.NET.OpenGL` anywhere in src/ or tests/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 02:58:15 +02:00

307 lines
10 KiB
C#

using AcDream.App.Diagnostics;
using AcDream.App.Input;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.World;
namespace AcDream.App.Rendering;
internal readonly record struct RenderFrameFoundation(
bool PortalViewportVisible,
SkyKeyframe Sky,
AtmosphereSnapshot Atmosphere)
{
public bool EnvironOverrideActive =>
Atmosphere.Override != EnvironOverride.None;
}
internal interface IRenderFrameBeginResources
{
void Begin(int gpuSlot);
}
internal interface IRenderFrameSlotSource
{
int CurrentSlot { get; }
}
internal interface IRenderFrameClearPhase
{
RenderFrameFoundation Clear();
}
internal interface IRenderFrameFoundationSource
{
RenderFrameFoundation Foundation { get; }
}
internal interface IRenderFrameLivePreparation
{
void Prepare(int gpuSlot);
}
/// <summary>
/// Owns the pre-world resource transaction after GPU-flight begin. The three
/// typed phases preserve resource begin, clear/state, then live publication
/// order without exposing a renderer service bag.
/// </summary>
internal sealed class RenderFrameResourceController :
IRenderFrameResourcePhase,
IRenderFrameFoundationSource
{
private readonly IRenderFrameSlotSource _frameSlots;
private readonly IRenderFrameBeginResources _resources;
private readonly IRenderFrameClearPhase _clear;
private readonly IRenderFrameLivePreparation _live;
public RenderFrameResourceController(
IRenderFrameSlotSource frameSlots,
IRenderFrameBeginResources resources,
IRenderFrameClearPhase clear,
IRenderFrameLivePreparation live)
{
_frameSlots = frameSlots
?? throw new ArgumentNullException(nameof(frameSlots));
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
_clear = clear ?? throw new ArgumentNullException(nameof(clear));
_live = live ?? throw new ArgumentNullException(nameof(live));
}
public RenderFrameFoundation Foundation { get; private set; }
public void Prepare(RenderFrameInput input)
{
int gpuSlot = _frameSlots.CurrentSlot;
_resources.Begin(gpuSlot);
Foundation = _clear.Clear();
_live.Prepare(gpuSlot);
}
}
/// <summary>Exact ordered begin calls for resources shared by the frame.</summary>
internal sealed class RuntimeRenderFrameBeginResources : IRenderFrameBeginResources
{
private readonly TextureCache? _textures;
private readonly WbDrawDispatcher? _dispatcher;
private readonly EnvCellRenderer? _environmentCells;
private readonly PortalDepthMaskRenderer? _portalDepth;
private readonly ClipFrame? _clip;
private readonly TerrainModernRenderer? _terrain;
private readonly SceneLightingUboBinding? _lighting;
public RuntimeRenderFrameBeginResources(
TextureCache? textures,
WbDrawDispatcher? dispatcher,
EnvCellRenderer? environmentCells,
PortalDepthMaskRenderer? portalDepth,
ClipFrame? clip,
TerrainModernRenderer? terrain,
SceneLightingUboBinding? lighting)
{
_textures = textures;
_dispatcher = dispatcher;
_environmentCells = environmentCells;
_portalDepth = portalDepth;
_clip = clip;
_terrain = terrain;
_lighting = lighting;
}
public void Begin(int gpuSlot)
{
_textures?.BeginCompositeTextureFrame();
_textures?.TickCompositeTextureCache();
_dispatcher?.BeginFrame(gpuSlot);
_environmentCells?.BeginFrame(gpuSlot);
_portalDepth?.BeginFrame(gpuSlot);
// The world-HUD and retained-UI TextRenderers no longer take a
// per-slot begin: Campaign V slice V4a ported them onto the shared
// IGpuFrame ring (see GpuDeviceFrameLifetime), which is reset once
// per frame by IGpuDevice.BeginFrame() rather than per renderer.
_clip?.BeginFrame(gpuSlot);
_terrain?.BeginFrame(gpuSlot);
_lighting?.BeginFrame(gpuSlot);
}
}
internal interface IRenderFramePortalStateSource
{
bool IsPortalViewportVisible { get; }
uint ActiveDestinationCell { get; }
}
internal sealed class LocalPlayerTeleportRenderStateSource
: IRenderFramePortalStateSource
{
private readonly LocalPlayerTeleportController _teleport;
public LocalPlayerTeleportRenderStateSource(LocalPlayerTeleportController teleport)
{
_teleport = teleport ?? throw new ArgumentNullException(nameof(teleport));
}
public bool IsPortalViewportVisible => _teleport.IsPortalViewportVisible;
public uint ActiveDestinationCell => _teleport.ActiveDestinationCell;
}
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase, the raw-GL
// IRenderFrameClearPhase implementation (glClearColor/glClear +
// IRenderFrameGlState.RestoreFrameDefaults): it had zero remaining
// construction sites — VulkanRenderFrameClearPhase (VulkanCompositionFramePhases.cs)
// is the sole production implementer, expressing the same atmosphere-clear
// and portal-viewport-black logic as a Vulkan pass load op instead.
internal interface IRenderLoginStateSource
{
bool IsWaitingForLogin { get; }
}
internal sealed class RenderLoginStateSource : IRenderLoginStateSource
{
private readonly bool _liveMode;
private readonly ILocalPlayerModeSource _mode;
public RenderLoginStateSource(bool liveMode, ILocalPlayerModeSource mode)
{
_liveMode = liveMode;
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
}
public bool IsWaitingForLogin => _liveMode && !_mode.ChaseModeEverEntered;
}
internal interface ILoginRevealCellSource
{
bool TryGet(out uint cellId);
}
internal sealed class LiveLoginRevealCellSource : ILoginRevealCellSource
{
private readonly LiveEntityRuntime _entities;
private readonly ILocalPlayerIdentitySource _identity;
public LiveLoginRevealCellSource(
LiveEntityRuntime entities,
ILocalPlayerIdentitySource identity)
{
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
}
public bool TryGet(out uint cellId)
{
cellId = 0;
if (!_entities.TryGetSnapshot(_identity.ServerGuid, out var playerSpawn)
|| playerSpawn.Position is not { } position)
{
return false;
}
cellId = position.LandblockId;
return cellId != 0;
}
}
/// <summary>Render-thread mesh publication, reveal evaluation, and VFX begin.</summary>
internal sealed class RuntimeRenderFrameLivePreparation
: IRenderFrameLivePreparation
{
private readonly TextureCache? _textures;
private readonly WbMeshAdapter? _meshes;
private readonly WorldRevealCoordinator? _worldReveal;
private readonly IRenderFramePortalStateSource _portal;
private readonly IRenderLoginStateSource _login;
private readonly ILoginRevealCellSource _loginCell;
private readonly ParticleRenderer? _particles;
private readonly FrameProfiler _profiler;
private readonly bool _diagnosticsEnabled;
private readonly RollingTimingSampleWindow _uploadTiming = new(256);
public RuntimeRenderFrameLivePreparation(
TextureCache? textures,
WbMeshAdapter? meshes,
WorldRevealCoordinator? worldReveal,
IRenderFramePortalStateSource portal,
IRenderLoginStateSource login,
ILoginRevealCellSource loginCell,
ParticleRenderer? particles,
FrameProfiler profiler,
bool diagnosticsEnabled)
{
_textures = textures;
_meshes = meshes;
_worldReveal = worldReveal;
_portal = portal ?? throw new ArgumentNullException(nameof(portal));
_login = login ?? throw new ArgumentNullException(nameof(login));
_loginCell = loginCell ?? throw new ArgumentNullException(nameof(loginCell));
_particles = particles;
_profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
_diagnosticsEnabled = diagnosticsEnabled;
}
public RollingTimingPercentiles UploadTiming => _uploadTiming.Snapshot();
public void Prepare(int gpuSlot)
{
_textures?.TickSurfaceHistogramDumpIfEnabled();
long start = _diagnosticsEnabled
? System.Diagnostics.Stopwatch.GetTimestamp()
: 0L;
using (var uploadStage = _profiler.BeginStage(FrameStage.Upload))
{
_meshes?.Tick();
uint revealCell = _portal.ActiveDestinationCell;
if (revealCell == 0
&& _login.IsWaitingForLogin
&& _loginCell.TryGet(out uint loginCell))
{
revealCell = loginCell;
}
if (revealCell != 0)
_worldReveal?.PrepareAndEvaluate(revealCell);
_particles?.BeginFrame(gpuSlot);
}
if (_diagnosticsEnabled)
{
_uploadTiming.PushStopwatchTicks(
System.Diagnostics.Stopwatch.GetTimestamp() - start);
}
}
}
/// <summary>Owns the render-time weather clock and deterministic day index.</summary>
internal sealed class RenderWeatherFrameController : IRenderWeatherFramePhase
{
private readonly WorldTimeService _worldTime;
private readonly WeatherSystem _weather;
private double _elapsedSeconds;
public RenderWeatherFrameController(
WorldTimeService worldTime,
WeatherSystem weather)
{
_worldTime = worldTime ?? throw new ArgumentNullException(nameof(worldTime));
_weather = weather ?? throw new ArgumentNullException(nameof(weather));
}
internal double ElapsedSeconds => _elapsedSeconds;
public void Tick(double deltaSeconds)
{
DerethDateTime.Calendar calendar = _worldTime.CurrentCalendar;
int dayIndex = calendar.Year
* (DerethDateTime.DaysInAMonth * DerethDateTime.MonthsInAYear)
+ (int)calendar.Month * DerethDateTime.DaysInAMonth
+ (calendar.Day - 1);
_weather.Tick(
nowSeconds: _elapsedSeconds,
dayIndex,
dtSeconds: (float)deltaSeconds);
_elapsedSeconds += deltaSeconds;
}
}