using System; using AcDream.App.Rendering.Gpu; using AcDream.Core.Lighting; namespace AcDream.App.Rendering; /// /// Publishes the SceneLighting UBO each frame so every shader sampling /// uLights[] / uFogColor / etc reads consistent data without /// per-shader re-upload. /// /// The raw-GL arm this used to have alongside it โ€” a global uniform /// binding point kept live by a per-flight-slot buffer pool โ€” was deleted at /// Campaign V slice V11. Vulkan has no such global binding point: a /// descriptor set is bound per draw, so the upload is a frame ring slice /// PUBLISHED on , and each world /// renderer binds it inside the pass after its own binds (plan ยง5.5.14 item /// 2). Every allocation within a frame is already distinct memory that lives /// until the frame retires, which is the property the deleted buffer pool /// existed to provide when the world, portal-space and paperdoll draws each /// upload different lighting. /// public sealed unsafe class SceneLightingUboBinding : IDisposable { private readonly ICurrentGpuFrameSource _frames; private readonly WorldFrameSections _sections; private bool _frameStarted; /// /// The GL arm's per-flight-slot buffer pool this used to report on was /// deleted at Campaign V slice V11: every allocation now comes from a ring /// that lives until its frame retires, so there is no persistent /// dynamic-buffer pool left to size. /// internal int DynamicBufferCount => 0; internal SceneLightingUboBinding( ICurrentGpuFrameSource frames, WorldFrameSections sections) { _frames = frames ?? throw new ArgumentNullException(nameof(frames)); _sections = sections ?? throw new ArgumentNullException(nameof(sections)); } /// /// Resets the lighting-submission cursor for a GPU-fenced frame slot. /// World, portal-space, and paperdoll draws can each upload different /// lighting in one frame, so every upload receives distinct storage. /// public void BeginFrame(int frameSlot) { ArgumentOutOfRangeException.ThrowIfNegative(frameSlot); _frameStarted = true; } /// /// Push the current frame's UBO contents to the GPU. Cheap (576 bytes) /// so fine to call unconditionally every frame. /// public void Upload(SceneLightingUbo data) { if (!_frameStarted) throw new InvalidOperationException("BeginFrame must be called before uploading scene lighting."); IGpuFrame frame = _frames.CurrentFrame ?? throw new InvalidOperationException( "Scene lighting requires an open IGpuFrame (see GpuDeviceFrameLifetime)."); GpuRingAllocation allocation = frame.AllocateRing( SceneLightingUbo.SizeInBytes, GpuRingUsage.Uniform); new ReadOnlySpan(&data, SceneLightingUbo.SizeInBytes) .CopyTo(allocation.Data); _sections.SceneLighting = new GpuBufferSection( allocation.Buffer, allocation.OffsetBytes, (uint)SceneLightingUbo.SizeInBytes); } /// /// No-op: this binding owns no GPU resource of its own on the RHI arm โ€” the /// deleted GL arm's per-flight buffer pool was the only thing to release. /// Kept as a method so callers that hold this behind an /// reference (composition's acquisition scope) don't need a special case. /// public void Dispose() { } }