Skip to main content

retroglyph_window/
presenter.rs

1//! The [`Presenter`] trait: what a renderer crate implements to rasterize a
2//! grid and present it to a window surface.
3//!
4//! `Presenter` is an [`Output`](retroglyph_core::backend::Output) supertrait plus
5//! window-surface operations, with no input methods: the event loop
6//! owns input, and [`WindowBackend`](crate::WindowBackend) forwards
7//! translated events into its own queue instead.
8//!
9//! | Presenter | `present()` | `init_surface()` |
10//! |---|---|---|
11//! | `SoftwareRenderer` (retroglyph-software) | Copies pixel buffer to softbuffer surface | Creates `softbuffer::Context` + `Surface` |
12//! | `WgpuRenderer` (future) | Submits render pass + presents swap chain | Creates `wgpu::Surface` + `Device` |
13//! | `GlRenderer` (future) | Draws full-screen quad + swaps buffers | Creates GL context from the window |
14//!
15//! See the crate-level docs (`crate` root, "DPI, scale, and the resize contract" and
16//! "Threading model" sections) for the physical-pixel/no-auto-scaling contract on
17//! [`cell_size`](Presenter::cell_size), the sub-cell-remainder behavior on
18//! [`resize_surface`](Presenter::resize_surface), and the single-threaded execution model
19//! every `Presenter` implementation runs under.
20
21use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
22use retroglyph_core::backend::Output;
23use std::sync::Arc;
24
25/// A window/display handle pair, as one trait.
26///
27/// Presenters receive [`raw-window-handle`](raw_window_handle) types, not a
28/// concrete `winit::window::Window`: softbuffer, wgpu, and glutin all accept
29/// these handles directly, so any windowing library that produces them can
30/// drive the same presenter, and only this crate depends on winit itself.
31///
32/// `raw-window-handle` has no combined trait, and surface libraries need to
33/// *own* the handle (softbuffer stores it for the surface's lifetime), so
34/// presenters receive `Arc<dyn WindowHandle>` -- rwh implements the handle
35/// traits for `Arc<H: ?Sized>`, so the trait object passes straight into
36/// `softbuffer::Surface::new` / `wgpu::Instance::create_surface`.
37pub trait WindowHandle: HasWindowHandle + HasDisplayHandle {}
38
39impl<T: HasWindowHandle + HasDisplayHandle + ?Sized> WindowHandle for T {}
40
41/// A surface-lifecycle error that can optionally signal whether it's worth retrying.
42///
43/// [`Presenter::SurfaceError`] is a per-implementation associated type: softbuffer's error enum
44/// has no `Lost`/`Outdated`/`Timeout` discrimination the way `wgpu::SurfaceError` does, so today's
45/// only backend (`SoftwareRenderer`) has no structured way to say "this specific failure is
46/// fatal, don't bother retrying." [`is_recoverable`](Self::is_recoverable) is that hook: a
47/// presenter with real error categories can override it to return `false` for a truly fatal
48/// failure, while every presenter that doesn't need the distinction (including every backend that
49/// exists in this crate today) can implement this trait with an empty body and inherit the
50/// default `true`.
51///
52/// Deliberately not blanket-implemented for every `Debug + Display` type: that would make it
53/// impossible for any concrete error type to override [`is_recoverable`](Self::is_recoverable) at
54/// all (a specific `impl` would conflict with the blanket one), defeating the point of the trait.
55/// Instead, each `SurfaceError` type needs one explicit (and usually empty) `impl
56/// RecoverableError for ...` block -- see `retroglyph_software`'s `SurfaceError` for the minimal
57/// case that just inherits the default.
58pub trait RecoverableError: core::fmt::Debug + core::fmt::Display {
59    /// Whether this error represents a transient failure worth retrying, as opposed to a fatal
60    /// one.
61    ///
62    /// Defaults to `true`: absent any structured error categorization, every failure is treated
63    /// as potentially transient, matching the generic consecutive-failure recovery heuristic
64    /// `winit::run::present_failure_action` already applies. Override to return `false` only for
65    /// an error variant known to be unrecoverable regardless of retries (e.g. a `wgpu::SurfaceError
66    /// ::Lost` variant that persists until the surface is fully rebuilt from a different code
67    /// path than a simple retry).
68    #[must_use]
69    fn is_recoverable(&self) -> bool {
70        true
71    }
72}
73
74// `Infallible` is uninhabited -- no value of it can ever exist, so `is_recoverable` can never
75// actually be called on one -- but a presenter that can't fail (e.g. a test mock) still needs
76// `type SurfaceError = core::convert::Infallible` to satisfy the `RecoverableError` bound, so
77// this impl exists purely for that convenience.
78impl RecoverableError for core::convert::Infallible {}
79
80/// A renderer that rasterizes grid content and presents it to a window
81/// surface.
82///
83/// A supertrait of [`Output`], adding the surface lifecycle (`init_surface`, `resize_surface`,
84/// `present`, `cell_size`) that the event loop drives. Every `Presenter` implementation is an
85/// `Output` implementation for free: [`WindowBackend`](crate::WindowBackend) delegates its own
86/// `Output` impl straight through to `P: Presenter`, with no duplicated method bodies.
87pub trait Presenter: Output {
88    /// Surface lifecycle error (context creation, buffer acquisition,
89    /// present).
90    type SurfaceError: RecoverableError;
91
92    /// Initialize the window surface.
93    ///
94    /// Called once from the loop's `resumed` handler. The presenter creates
95    /// its platform surface (softbuffer surface, wgpu device+surface, GL
96    /// context) from the raw window/display handles.
97    ///
98    /// # Errors
99    ///
100    /// Returns [`Self::SurfaceError`] if surface or context creation fails.
101    fn init_surface(&mut self, window: Arc<dyn WindowHandle>) -> Result<(), Self::SurfaceError>;
102
103    /// Resize the window surface to a new physical pixel size.
104    ///
105    /// Called on every window resize event with `width`/`height` already resolved by the
106    /// caller -- for the `winit` driver (see `winit::run::WindowApp::resize_to`), that means
107    /// `cols * cell_w` x `rows * cell_h`, where `cols`/`rows` are the window's physical size
108    /// divided down to whole cells. Any sub-cell remainder is truncated, not centered or
109    /// cleared: when the window's physical size isn't an exact multiple of the cell size,
110    /// `width`/`height` here are the largest whole-cell-multiple that fits, which can be
111    /// smaller than the window's actual physical size. The OS window itself is never resized
112    /// to compensate, so a non-exact-multiple resize leaves a thin strip at the window's
113    /// trailing edge outside the surface -- retroglyph does not paint or clear that strip;
114    /// whatever the OS/windowing backend leaves there remains visible until a subsequent
115    /// resize covers it.
116    fn resize_surface(&mut self, width: u32, height: u32);
117
118    /// Notify the presenter that the window's scale factor (DPI) changed.
119    ///
120    /// Called when the window moves to a display with a different pixel density, or the
121    /// system DPI setting changes. The event loop follows this with
122    /// [`resize_surface`](Self::resize_surface) for the window's new physical size, so
123    /// this hook only needs to handle DPI-dependent state that isn't a plain buffer
124    /// resize (e.g. regenerating a font atlas rasterized for a particular scale).
125    ///
126    /// Defaults to a no-op: presenters whose rasterization doesn't depend on DPI (like
127    /// `SoftwareRenderer`'s integer `scale` config, set once at construction) need no
128    /// action here.
129    fn scale_factor_changed(&mut self, _scale_factor: f64) {}
130
131    /// Present the rasterized frame to the window surface.
132    ///
133    /// Called after each app tick. A lost frame is not fatal; the caller
134    /// logs the error and continues.
135    ///
136    /// # Errors
137    ///
138    /// Returns [`Self::SurfaceError`] if the surface buffer can't be acquired
139    /// or presented (e.g. context lost on wasm, page flip pending on
140    /// DRI/KMS).
141    fn present(&mut self) -> Result<(), Self::SurfaceError>;
142
143    /// Cell size in physical pixels `(width, height)`.
144    ///
145    /// Physical pixels, not logical/DPI-scaled pixels, and never auto-scaled by this crate for
146    /// display DPI -- see the crate-level "DPI, scale, and the resize contract" docs. A presenter
147    /// whose cells should grow on a `HiDPI` display must change what this returns itself (from
148    /// [`resize`](Output::resize) or [`scale_factor_changed`](Self::scale_factor_changed)); absent
149    /// that, it stays constant for the presenter's lifetime.
150    ///
151    /// `(u32, u32)` rather than [`Size`](retroglyph_core::grid::Size) because grid coordinates
152    /// are `u16` but pixel arithmetic uses `u32` (winit `PhysicalSize`).
153    #[must_use]
154    fn cell_size(&self) -> (u32, u32);
155}