feat(session): Skia console UI — overlay contract + stats OSD/capture HUD (phase 4a)
The §6.1 presenter↔console-UI contract lands: pf-presenter exposes its device (SharedDevice) and composites at most one premultiplied-alpha quad per frame (new overlay.frag + LOAD render pass over the swapchain; zero cost while the overlay returns None). pf-console-ui implements it with skia-safe on the shared VkDevice: DirectContext via the ash dispatch chain, a ring of two offscreen render targets (one-frame-in- flight safe), damage-driven redraws — the OSD re-renders at 1 Hz, the hint on capture toggles, nothing per-frame. Skia never touches the swapchain. The session binary carries it behind the default ui feature: 4.9 MB stripped without, 10 MB with (measured). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
//! The presenter↔console-UI contract (punktfunk-planning
|
||||
//! `linux-client-rearchitecture.md` §6.1): the presenter exposes its device and
|
||||
//! composites at most ONE sampled RGBA quad per frame; the overlay implementation
|
||||
//! (pf-console-ui, Skia) fills offscreen images on its own damage-driven schedule. No
|
||||
//! Skia type crosses this line — everything here is ash — and a `frame()` returning
|
||||
//! `None` costs the hot path nothing (the quad isn't even recorded).
|
||||
|
||||
use ash::vk;
|
||||
|
||||
/// The presenter's device, shared with the overlay so its renderer (Skia's
|
||||
/// `DirectContext`) creates resources on the same VkDevice/queue. Handles stay valid for
|
||||
/// the presenter's lifetime — the overlay must be dropped before it (the run loop owns
|
||||
/// both and drops the overlay first).
|
||||
pub struct SharedDevice {
|
||||
pub entry: ash::Entry,
|
||||
pub instance: ash::Instance,
|
||||
pub physical_device: vk::PhysicalDevice,
|
||||
pub device: ash::Device,
|
||||
pub queue: vk::Queue,
|
||||
pub queue_family_index: u32,
|
||||
}
|
||||
|
||||
/// What the overlay may draw this frame — composed by the run loop from session state.
|
||||
/// Milestone 1 (OSD/HUD) is text-shaped; the console library replaces this with a
|
||||
/// richer scene enum when it moves in.
|
||||
pub struct FrameCtx<'a> {
|
||||
/// Swapchain size in pixels — the overlay renders 1:1.
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
/// Multi-line stats OSD (top-left panel); `None` = hidden.
|
||||
pub stats: Option<&'a str>,
|
||||
/// The capture hint (bottom-center pill, "click to capture…"); `None` = hidden.
|
||||
pub hint: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// One overlay image ready to composite: RGBA, PREMULTIPLIED alpha, already in
|
||||
/// `SHADER_READ_ONLY_OPTIMAL`, sized `width`×`height` (normally the `FrameCtx` size; a
|
||||
/// stale size during a resize just stretches for a frame).
|
||||
pub struct OverlayFrame {
|
||||
pub image: vk::Image,
|
||||
pub view: vk::ImageView,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// The console-UI side. Object-safe; the session binary passes
|
||||
/// `Option<Box<dyn Overlay>>` (None = the Skia-free power-user build).
|
||||
pub trait Overlay {
|
||||
/// One-time setup on the presenter's device.
|
||||
fn init(&mut self, shared: &SharedDevice) -> anyhow::Result<()>;
|
||||
|
||||
/// Input routing, before capture sees the event. `true` = consumed (a menu is up) —
|
||||
/// the event must not reach capture/forwarding. The OSD/HUD milestone consumes
|
||||
/// nothing; the console library will.
|
||||
fn handle_event(&mut self, event: &sdl3::event::Event) -> bool;
|
||||
|
||||
/// Once per presenter iteration. Damage-driven: re-render (flush + transition to
|
||||
/// SHADER_READ_ONLY) only when the content or size changed, else return the previous
|
||||
/// image. `None` = nothing to composite. The returned image must stay untouched
|
||||
/// until `frame()` runs again (the presenter runs one frame in flight and the
|
||||
/// implementation keeps a ring of two, so alternating satisfies this).
|
||||
fn frame(&mut self, ctx: &FrameCtx) -> anyhow::Result<Option<OverlayFrame>>;
|
||||
}
|
||||
Reference in New Issue
Block a user