Skip to main content

gpui_platform/
platform_renderer.rs

1//! The renderer seam: the trait a window holds, the target a renderer is built against, and
2//! the factory the window consults.
3//!
4//! A window holds a `Box<dyn PlatformRenderer>` rather than a concrete renderer, so an
5//! application can install its own through the renderer factory. The lifecycle methods keep
6//! the names the backends already call them by, and each platform's native hooks — the macOS
7//! layer, the Windows background appearance — are supertraits rather than methods, so a
8//! backend reaches its own without a downcast and neither widens `SceneRenderer`.
9//!
10//! A trait's supertraits cannot be cfg-selected, so [`NativeSceneHooks`] is there to be the one
11//! that can: it is this platform's hooks, and nothing implements it by hand.
12
13use std::any::Any;
14use std::fmt;
15use std::rc::Rc;
16
17use gpui_engine::SceneRenderer;
18use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
19
20use crate::{DevicePixels, GpuSpecs, Pixels, Size};
21
22/// The macOS hooks a window needs and no other platform does.
23#[cfg(target_os = "macos")]
24pub trait MacSceneRenderer: SceneRenderer {
25    /// The `CAMetalLayer` this renderer draws through, which the window installs as the
26    /// view's backing layer from `-[NSView makeBackingLayer]`.
27    fn layer_ptr(&self) -> *mut std::ffi::c_void;
28
29    /// Whether presenting waits for the Core Animation transaction, which a resized window
30    /// needs.
31    fn set_presents_with_transaction(&mut self, value: bool);
32}
33
34/// The Windows hooks a window needs and no other platform does.
35#[cfg(target_os = "windows")]
36pub trait WinSceneRenderer: SceneRenderer {
37    /// The window's background appearance changed.
38    fn set_background_appearance(&mut self, appearance: crate::WindowBackgroundAppearance);
39
40    /// The drawable size changed. Windows resizes a swap chain here rather than in
41    /// `PlatformRenderer::update_drawable_size`, because the failure is one the window has to act
42    /// on: a drawable that cannot be resized means the devices are invalid.
43    fn resize(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()>;
44
45    /// The renderer may draw again. A renderer that discards the frames between a device loss
46    /// and the forced render that follows it clears that state here; only Direct3D has any.
47    fn mark_drawable(&mut self) {}
48}
49
50/// The platform's native hooks, as a supertrait every [`PlatformRenderer`] has.
51#[cfg(target_os = "macos")]
52pub trait NativeSceneHooks: MacSceneRenderer {}
53/// The platform's native hooks, as a supertrait every [`PlatformRenderer`] has.
54#[cfg(target_os = "windows")]
55pub trait NativeSceneHooks: WinSceneRenderer {}
56/// The platform's native hooks, as a supertrait every [`PlatformRenderer`] has.
57#[cfg(not(any(target_os = "macos", target_os = "windows")))]
58pub trait NativeSceneHooks: SceneRenderer {}
59
60#[cfg(target_os = "macos")]
61impl<T: MacSceneRenderer + ?Sized> NativeSceneHooks for T {}
62#[cfg(target_os = "windows")]
63impl<T: WinSceneRenderer + ?Sized> NativeSceneHooks for T {}
64#[cfg(not(any(target_os = "macos", target_os = "windows")))]
65impl<T: SceneRenderer + ?Sized> NativeSceneHooks for T {}
66
67/// What a window asks of whatever renders into it.
68///
69/// `draw` and `sprite_atlas` come from [`SceneRenderer`]; this adds the lifecycle a window
70/// drives, which an offscreen renderer has no reason to implement. `update_drawable_size` and
71/// `max_texture_size` are the two with no default, because a renderer that cannot answer them
72/// cannot be a window's.
73pub trait PlatformRenderer: NativeSceneHooks {
74    /// The target's size changed, in device pixels.
75    fn update_drawable_size(&mut self, size: Size<DevicePixels>);
76
77    /// The largest texture this renderer can allocate, in pixels.
78    fn max_texture_size(&self) -> u32;
79
80    /// What GPU this renderer is on, if it can tell.
81    fn gpu_specs(&self) -> Option<GpuSpecs>;
82
83    /// Whether glyphs are rasterized with subpixel antialiasing in BGR order.
84    fn set_subpixel_layout(&mut self, _is_bgr: bool) {}
85
86    /// The window's transparency changed.
87    fn update_transparency(&mut self, _transparent: bool) {}
88
89    /// Release the renderer's resources, keeping it reconstructible.
90    fn destroy(&mut self) {}
91
92    /// Whether the GPU device or its surface has been lost.
93    fn device_lost(&self) -> bool {
94        false
95    }
96
97    /// Whether the renderer has work the frame loop should not wait on.
98    fn needs_redraw(&mut self) -> bool {
99        false
100    }
101
102    /// Rebuild from the target the renderer was built against, after a lost device or
103    /// surface.
104    #[cfg(not(target_family = "wasm"))]
105    fn recover(&mut self, _target: RendererTarget<'_>) -> anyhow::Result<()> {
106        anyhow::bail!("renderer does not support recovery")
107    }
108}
109
110/// What a renderer is built against: the window's handles, its geometry, and whatever the
111/// backend needs to add.
112///
113/// The handles are typed rather than erased, because erasing them would make any renderer
114/// that is not the backend's own downcast to the backend's target type — which means
115/// depending on the backend crate. The backend's own extras are the one erased field.
116pub struct RendererTarget<'a> {
117    /// The window's raw handle, which the renderer builds its surface from.
118    pub window_handle: Option<RawWindowHandle>,
119    /// The display the window is on, for the same.
120    pub display_handle: Option<RawDisplayHandle>,
121    /// The window's size, in logical pixels.
122    pub size: Size<Pixels>,
123    /// The ratio between logical pixels and device pixels.
124    pub scale_factor: f32,
125    /// Whether the window is transparent.
126    pub transparent: bool,
127    /// Backend-specific extras, for that backend's own renderer only. `Any` is `'static`, so
128    /// this must point at owned data; a backend with nothing to add passes `None`.
129    pub backend: Option<&'a dyn Any>,
130}
131
132/// Builds a window's renderer from the surface the window has already made.
133///
134/// It runs exactly once per window, after the native window exists and before the first
135/// frame, because a renderer binds to a surface and the surface comes from the window. On the
136/// recovery path the renderer is handed a [`RendererTarget`] built from the surface it already
137/// has, not a new one from here.
138pub trait RendererFactory: 'static {
139    /// Build the renderer this window will draw through.
140    fn create(&self, target: RendererTarget<'_>) -> anyhow::Result<Box<dyn PlatformRenderer>>;
141}
142
143/// A shared [`RendererFactory`], which is also `Debug` because the window options it lives in
144/// are.
145#[derive(Clone)]
146pub struct DynRendererFactory(pub Rc<dyn RendererFactory>);
147
148impl fmt::Debug for DynRendererFactory {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        f.write_str("DynRendererFactory(..)")
151    }
152}
153
154/// The spelling for the common case: a closure is a factory.
155///
156/// It is a newtype rather than a blanket `impl RendererFactory for F where F: Fn(..)`, which
157/// would collide with any other blanket implementation the moment one appeared.
158pub struct FnRendererFactory<F>(pub F);
159
160impl<F> RendererFactory for FnRendererFactory<F>
161where
162    F: Fn(RendererTarget<'_>) -> anyhow::Result<Box<dyn PlatformRenderer>> + 'static,
163{
164    fn create(&self, target: RendererTarget<'_>) -> anyhow::Result<Box<dyn PlatformRenderer>> {
165        (self.0)(target)
166    }
167}