using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Rendering.Gpu.Vk;
using AcDream.UI.Abstractions.Input;
using Silk.NET.Input;
namespace AcDream.App.Composition;
///
/// Campaign V slice V6h: the Vulkan arm of the host phase — now the only arm,
/// the raw-GL RetailHostInputCameraCompositionFactory 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.
///
/// What is absent, and why. 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
/// : 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.
///
internal sealed class VulkanHostInputCameraCompositionFactory
: IHostInputCameraCompositionFactory
{
public IFramebufferViewportTarget CreateViewportTarget(
GameWindowGraphics graphics) =>
// The viewport itself is a pipeline dynamic state on Vulkan, set per
// pass by the encoder from the pass extent — but the swapchain still
// has to be told the framebuffer changed. Relying on the driver's
// OUT_OF_DATE/SUBOPTIMAL results alone is spec-insufficient: a
// conformant driver (observed: Windows AMD, 2026-08-13 user gate —
// "just stretching the window, not changing the pixel count") may keep
// presenting the stale-extent swapchain scaled to the new window
// forever. The V11 null target assumed the driver signal; this target
// arms the context's existing frame-boundary recreation instead, so
// the next PrepareFrame recreates at the real FramebufferSize.
new SwapchainRecreateViewportTarget(RequireContext(graphics).RequestRecreate);
public GpuFrameFlightController? CreateGpuFrameFlights(
GameWindowGraphics graphics) => null;
public IGpuDevice CreateGpuDevice(
GameWindowGraphics graphics,
GpuFrameFlightController? frameFlights) =>
RequireContext(graphics).Device;
public IGpuResourceRetirementQueue CreateRetirement(
GameWindowGraphics graphics,
GpuFrameFlightController? frameFlights,
IGpuDevice device) => device.Retirement;
public IRenderFrameSlotSource CreateFrameSlots(
GameWindowGraphics graphics,
GpuFrameFlightController? frameFlights,
IGpuDevice device) =>
new VulkanRenderFrameSlotSource(RequireContext(graphics).Device);
public WorldRenderDiagnostics? CreateWorldRenderDiagnostics(
GameWindowGraphics graphics,
IRenderFrameDiagnosticLog log) => null;
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 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());
private static VulkanGraphicsContext RequireContext(
GameWindowGraphics graphics) =>
graphics.Vulkan
?? throw new InvalidOperationException(
"The Vulkan host factory was composed against a backend with no " +
"Vulkan context.");
///
/// The Vulkan implementation of the resize seam (#387): a window resize
/// arms so the next
/// PrepareFrame rebuilds the swapchain at the current
/// FramebufferSize. The width/height arguments are deliberately unused —
/// the context re-reads the live framebuffer size at the frame boundary,
/// so a burst of resize events collapses into one recreation and a stale
/// event can never install a stale extent. Zero-area (minimised) sizes
/// never reach this target (
/// gates them), and the armed flag survives a minimised skip until a
/// non-zero frame runs, which is the context's own existing behavior.
///
internal sealed class SwapchainRecreateViewportTarget(Action requestRecreate)
: IFramebufferViewportTarget
{
private readonly Action _requestRecreate = requestRecreate
?? throw new ArgumentNullException(nameof(requestRecreate));
public void ResizeViewport(int width, int height) => _requestRecreate();
}
///
/// The flight slot per-frame buffers index by. Identical in role to the GL
/// ring's ; the count comes
/// from the device's timeline flight controller rather than a fence array.
///
private sealed class VulkanRenderFrameSlotSource(VulkanGpuDevice device)
: IRenderFrameSlotSource
{
public int CurrentSlot => device.Flights.CurrentSlot;
}
}