using AcDream.App.Input; using Silk.NET.Maths; namespace AcDream.App.Rendering; internal interface IFramebufferViewportTarget { void ResizeViewport(int width, int height); } // Campaign V slice V11 deleted SilkFramebufferViewportTarget, the GL // implementation of IFramebufferViewportTarget: VulkanHostInputCameraComposition // Factory's NullFramebufferViewportTarget is the sole surviving implementation // (Vulkan's swapchain recreation owns the actual viewport-equivalent resize, // which this seam never drove). internal interface IFramebufferCameraTarget { void SetAspect(float aspect); } internal sealed class CameraFramebufferTarget(CameraController camera) : IFramebufferCameraTarget { private readonly CameraController _camera = camera ?? throw new ArgumentNullException(nameof(camera)); public void SetAspect(float aspect) => _camera.SetAspect(aspect); } internal interface IFramebufferDevToolsTarget { void ResetLayout(int width, int height); } /// Expected-owner lease for the optional Phase-3 resize edge. internal sealed class FramebufferDevToolsBinding : IDisposable { private readonly FramebufferResizeController _owner; private readonly IFramebufferDevToolsTarget _target; private bool _disposed; public FramebufferDevToolsBinding( FramebufferResizeController owner, IFramebufferDevToolsTarget target) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _target = target ?? throw new ArgumentNullException(nameof(target)); _owner.BindDevTools(_target); } public void Dispose() { if (_disposed) return; _owner.UnbindDevTools(_target); _disposed = true; } } /// /// The one framebuffer-size target. Logical gameplay/UI dimensions continue /// to come from Window.Size; this owner only applies physical framebuffer /// viewport/aspect changes in the frozen acdream host order. /// internal sealed class FramebufferResizeController { private readonly ViewportAspectState _viewportAspect; private IFramebufferViewportTarget? _viewport; private IFramebufferCameraTarget? _camera; private IFramebufferDevToolsTarget? _devTools; public FramebufferResizeController(ViewportAspectState viewportAspect) => _viewportAspect = viewportAspect ?? throw new ArgumentNullException(nameof(viewportAspect)); public void BindViewport(IFramebufferViewportTarget viewport) { ArgumentNullException.ThrowIfNull(viewport); BindOnce(ref _viewport, viewport, "viewport"); } public void BindCamera(IFramebufferCameraTarget camera) { ArgumentNullException.ThrowIfNull(camera); BindOnce(ref _camera, camera, "camera"); } public void BindDevTools(IFramebufferDevToolsTarget devTools) { ArgumentNullException.ThrowIfNull(devTools); BindOnce(ref _devTools, devTools, "developer tools"); } public void UnbindDevTools(IFramebufferDevToolsTarget devTools) { ArgumentNullException.ThrowIfNull(devTools); if (ReferenceEquals(_devTools, devTools)) _devTools = null; } public void Resize(Vector2D newSize) => Resize(newSize.X, newSize.Y); public void Resize(int width, int height) { if (width <= 0 || height <= 0) return; // Frozen order: GL viewport, shared aspect publication, camera, then // the optional developer-tools layout reset (unbound since Campaign V // slice V11 removed the ImGui frontend). Late binding never replays an // earlier resize transition. _viewport?.ResizeViewport(width, height); _viewportAspect.Update(width, height); _camera?.SetAspect(width / (float)height); _devTools?.ResetLayout(width, height); } private static void BindOnce(ref T? slot, T value, string name) where T : class { if (slot is not null && !ReferenceEquals(slot, value)) throw new InvalidOperationException( $"The framebuffer {name} target is already bound."); slot = value; } }