using AcDream.App.Composition; using AcDream.App.Input; using AcDream.App.Rendering; namespace AcDream.App.Tests.Composition; /// /// #387: the V11 null viewport target assumed the driver's OUT_OF_DATE/ /// SUBOPTIMAL results would drive swapchain recreation on window resize; on /// Windows AMD the driver instead presents the stale-extent swapchain scaled /// to the new window (the "resolution pick just stretches" 2026-08-13 user /// gate report). These pin the replacement target's contract: every resize /// event reaching the seam arms the context's frame-boundary recreation. /// public sealed class SwapchainRecreateViewportTargetTests { [Fact] public void ResizeViewport_ArmsRecreation_OncePerEvent() { int armed = 0; IFramebufferViewportTarget target = new VulkanHostInputCameraCompositionFactory.SwapchainRecreateViewportTarget( () => armed++); target.ResizeViewport(1024, 768); Assert.Equal(1, armed); // A second resize arms again — the context collapses bursts itself at // the frame boundary (it re-reads the live FramebufferSize), so the // target must never de-duplicate or latch. target.ResizeViewport(800, 600); Assert.Equal(2, armed); } [Fact] public void ResizeViewport_IgnoresTheEventSize_ContextReadsLiveFramebuffer() { // The width/height are deliberately unused (doc on the target): the // context reads the CURRENT framebuffer size at the frame boundary, // so a stale event cannot install a stale extent. Pin that the arm // fires regardless of the reported size values. int armed = 0; IFramebufferViewportTarget target = new VulkanHostInputCameraCompositionFactory.SwapchainRecreateViewportTarget( () => armed++); target.ResizeViewport(int.MaxValue, 1); Assert.Equal(1, armed); } [Fact] public void Constructor_RejectsNullRecreateHook() { Assert.Throws(() => new VulkanHostInputCameraCompositionFactory.SwapchainRecreateViewportTarget(null!)); } [Fact] public void ResizeController_DeliversResizeEventsToTheBoundTarget() { // The end-to-end seam FramebufferResizeController.Resize → // ResizeViewport → arm: the same event flow GameWindow's // OnFramebufferResize drives in production. Zero-area (minimised) // events are gated by the controller and must NOT arm. int armed = 0; var controller = new FramebufferResizeController(new ViewportAspectState()); controller.BindViewport( new VulkanHostInputCameraCompositionFactory.SwapchainRecreateViewportTarget( () => armed++)); controller.Resize(1280, 720); Assert.Equal(1, armed); controller.Resize(0, 0); // minimised — gated, no arm Assert.Equal(1, armed); controller.Resize(1024, 768); Assert.Equal(2, armed); } }