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}