Skip to main content

gpui_engine/
renderer.rs

1//! The renderer contract that engines implement.
2
3use crate::{PlatformAtlas, Scene};
4use gpui_types::{DevicePixels, Size};
5use std::any::Any;
6use std::sync::Arc;
7
8/// Downcasting for [`SceneRenderer`] trait objects.
9///
10/// `SceneRenderer` is the GPU-agnostic seam: it presents a [`Scene`] and names no graphics API.
11/// The renderer *behind* it is what owns the device — and the caches, the textures and the other
12/// resources built on it — so a caller that needs those downcasts to the concrete backend renderer
13/// here, rather than having the window lend its guest's internals.
14impl dyn SceneRenderer + '_ {
15    /// `self` as the concrete renderer `T`, if that is what it is.
16    pub fn as_renderer<T: Any>(&self) -> Option<&T> {
17        (self as &dyn Any).downcast_ref::<T>()
18    }
19
20    /// The mutable form of [`as_renderer`](Self::as_renderer).
21    pub fn as_renderer_mut<T: Any>(&mut self) -> Option<&mut T> {
22        (self as &mut dyn Any).downcast_mut::<T>()
23    }
24}
25
26/// A renderer that owns a device a producer can make resources on.
27///
28/// The engine's [`SceneRenderer`] is GPU-agnostic and names no device, but a producer that must make
29/// a texture on the *window's* GPU has to reach the concrete device the renderer drew with — the
30/// same-device rule seen from the renderer's side. A renderer that owns one implements this; a
31/// renderer that has none to lend — a discard or capture-only one — simply does not, and is then
32/// absent from this door rather than answering it with a placeholder.
33///
34/// [`Device`](Self::Device) is the backend's own handle — an `ID3D11Device`, a `metal::Device`, a
35/// wgpu context — so this trait adds no dependency on any GPU driver: only an implementor names one.
36pub trait GpuRenderer: SceneRenderer {
37    /// The device this renderer draws on, and so the one a producer must make its texture on.
38    type Device;
39
40    /// The device, or `None` while there is none to lend: a renderer mid-recovery has taken its
41    /// device apart and has not yet rebuilt it.
42    fn device(&self) -> Option<Self::Device>;
43}
44
45/// A frame read back from a renderer, as raw pixels.
46///
47/// This is the engine's own type rather than an `image::RgbaImage` because the contract is what
48/// every renderer implements, and an image codec crate is not something a renderer needs: the
49/// consumers that want one — a PNG in test support, a fixture diff, an export — sit above the
50/// engine and convert. `image` was a dependency of this crate for this signature alone.
51///
52/// The layout is fixed so that the boundary needs no negotiation: RGBA8, unpremultiplied,
53/// tightly packed, row-major, top-left origin. A backend whose target is BGRA or premultiplied
54/// converts while it copies, which is where the swizzle already happens; a backend with other
55/// pixel formats widens this type rather than smuggling a second layout through it.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct PixelBuffer {
58    width: u32,
59    height: u32,
60    data: Vec<u8>,
61}
62
63impl PixelBuffer {
64    /// Wraps `data` as an image of `width` by `height` pixels.
65    ///
66    /// Fails if `data` is not exactly `width * height * 4` bytes, so that the accessors below
67    /// cannot hand out a buffer shorter than the size they report.
68    pub fn new(width: u32, height: u32, data: Vec<u8>) -> anyhow::Result<Self> {
69        let expected = width as usize * height as usize * 4;
70        anyhow::ensure!(
71            data.len() == expected,
72            "a {width}x{height} RGBA8 image is {expected} bytes, but the buffer is {}",
73            data.len()
74        );
75        Ok(Self {
76            width,
77            height,
78            data,
79        })
80    }
81
82    /// The width, in pixels.
83    pub fn width(&self) -> u32 {
84        self.width
85    }
86
87    /// The height, in pixels.
88    pub fn height(&self) -> u32 {
89        self.height
90    }
91
92    /// The pixels, RGBA8 and tightly packed.
93    pub fn data(&self) -> &[u8] {
94        &self.data
95    }
96
97    /// Consumes the buffer, returning its pixels.
98    pub fn into_data(self) -> Vec<u8> {
99        self.data
100    }
101}
102
103/// A renderer that presents a [`Scene`] to some target.
104///
105/// OS backends and alternative engines implement this; `gpui` produces the
106/// [`Scene`] and submits it through the window's platform implementation.
107///
108/// Offscreen rendering is part of this contract rather than a testing shim: a renderer that is
109/// never given a window is how server-side rendering, headless capture and snapshotting are
110/// expressed, and the window's renderer is the only thing that owns the device. Onscreen
111/// renderers therefore only *need* [`draw`](Self::draw) and [`sprite_atlas`](Self::sprite_atlas);
112/// the rest have defaults that report themselves unsupported.
113///
114/// Rendering and readback are separate steps on purpose. [`render_scene`](Self::render_scene)
115/// fills a target without a CPU round trip, which is what a consumer on the GPU wants;
116/// [`read_pixels`](Self::read_pixels) pays for system memory only when a consumer on the CPU
117/// needs it; [`render_scene_to_image`](Self::render_scene_to_image) is the two together.
118pub trait SceneRenderer: Any {
119    /// Encodes and submits `scene`, returning whether it was presented.
120    ///
121    /// Renderers that cannot observe presentation (or that render offscreen)
122    /// return `true`; window backends that pace themselves on compositor
123    /// callbacks use `false` to retry the frame later.
124    fn draw(&mut self, scene: &Scene) -> bool;
125
126    /// Returns the sprite atlas used by this renderer.
127    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
128
129    /// Sets the size of the target that [`draw`](Self::draw) renders into.
130    ///
131    /// Onscreen renderers derive their target from the window and can ignore
132    /// this; headless renderers use it to size an offscreen texture.
133    fn set_viewport_size(&mut self, _size: Size<DevicePixels>) {}
134
135    /// Renders `scene` into an offscreen target of `size`, without reading it back.
136    ///
137    /// This is the headless analogue of presenting a frame: it performs the same CPU-side
138    /// scene encoding and GPU submission as drawing to a real window, but does not block on
139    /// GPU completion or copy pixels back.
140    fn render_scene(&mut self, _scene: &Scene, _size: Size<DevicePixels>) -> anyhow::Result<()> {
141        anyhow::bail!("offscreen rendering is not supported by this renderer")
142    }
143
144    /// Reads the offscreen target of the last [`render_scene`](Self::render_scene) back.
145    ///
146    /// Only a renderer that has rendered offscreen can answer this; the failure is the same
147    /// "unsupported" one, one step later.
148    fn read_pixels(&mut self) -> anyhow::Result<PixelBuffer> {
149        anyhow::bail!("pixel readback is not supported by this renderer")
150    }
151
152    /// Renders `scene` offscreen and reads the result back.
153    fn render_scene_to_image(
154        &mut self,
155        scene: &Scene,
156        size: Size<DevicePixels>,
157    ) -> anyhow::Result<PixelBuffer> {
158        self.render_scene(scene, size)?;
159        self.read_pixels()
160    }
161}
162
163/// A renderer that hands every scene it is given to a closure before passing it on.
164///
165/// Observation belongs here rather than in a backend. It needs nothing from the platform, so a
166/// backend that learned about inspection would carry it once per API, and the *scene* is already
167/// an engine type: a scene optimiser, a culling pass, an assertion about what primitives a
168/// renderer is handed and in what order, are all statements about this layer.
169///
170/// What a window painted is observable one layer up, where the frame still holds the scene it
171/// built (`Window::painted_quads`), so this is for observing a renderer rather than a frame.
172///
173/// This wraps a renderer in another renderer, and the choices that make it useful are the
174/// forwarding ones: it observes [`draw`](SceneRenderer::draw) and
175/// [`render_scene`](SceneRenderer::render_scene), and it does *not* override
176/// [`render_scene_to_image`](SceneRenderer::render_scene_to_image), so a readback is observed
177/// once through the `render_scene` it composes rather than twice.
178pub struct ObservingRenderer<R, F> {
179    inner: R,
180    on_scene: F,
181}
182
183impl<R, F> ObservingRenderer<R, F> {
184    /// Wraps `inner`, calling `on_scene` with each scene it is asked to draw.
185    pub fn new(inner: R, on_scene: F) -> Self {
186        Self { inner, on_scene }
187    }
188
189    /// The renderer being observed.
190    pub fn inner(&self) -> &R {
191        &self.inner
192    }
193
194    /// The renderer being observed.
195    pub fn inner_mut(&mut self) -> &mut R {
196        &mut self.inner
197    }
198
199    /// Takes back the observed renderer.
200    pub fn into_inner(self) -> R {
201        self.inner
202    }
203}
204
205impl<R, F> SceneRenderer for ObservingRenderer<R, F>
206where
207    R: SceneRenderer,
208    F: FnMut(&Scene) + 'static,
209{
210    fn draw(&mut self, scene: &Scene) -> bool {
211        (self.on_scene)(scene);
212        self.inner.draw(scene)
213    }
214
215    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
216        self.inner.sprite_atlas()
217    }
218
219    fn set_viewport_size(&mut self, size: Size<DevicePixels>) {
220        self.inner.set_viewport_size(size)
221    }
222
223    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
224        (self.on_scene)(scene);
225        self.inner.render_scene(scene, size)
226    }
227
228    fn read_pixels(&mut self) -> anyhow::Result<PixelBuffer> {
229        self.inner.read_pixels()
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use std::{borrow::Cow, cell::RefCell, rc::Rc};
236
237    use super::*;
238    use crate::{AtlasKey, AtlasTile, CustomRenderPrimitive, ImportedTextureHandle};
239    use gpui_types::{Bounds, ContentMask, Corners};
240
241    /// The observer never uploads, so its atlas only has to exist.
242    struct StubAtlas;
243
244    impl PlatformAtlas for StubAtlas {
245        fn get_or_insert_with<'a>(
246            &self,
247            _key: AtlasKey,
248            _build: &mut dyn FnMut() -> anyhow::Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
249        ) -> anyhow::Result<Option<AtlasTile>> {
250            Ok(None)
251        }
252
253        fn remove(&self, _key: &AtlasKey) {}
254    }
255
256    struct RecordingRenderer {
257        draws: Rc<RefCell<usize>>,
258    }
259
260    impl SceneRenderer for RecordingRenderer {
261        fn draw(&mut self, _scene: &Scene) -> bool {
262            *self.draws.borrow_mut() += 1;
263            true
264        }
265
266        fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
267            Arc::new(StubAtlas)
268        }
269    }
270
271    /// What the wrap is for: the closure sees the scene the caller handed over, and the render it
272    /// forwards to still happens. An observer that swallowed the scene would pass the count and
273    /// fail the second assertion, and one that passed the scene on without observing would fail
274    /// the first.
275    #[test]
276    fn an_observing_renderer_observes_the_scene_and_forwards_it() {
277        let draws = Rc::new(RefCell::new(0));
278        let observed = Rc::new(RefCell::new(Vec::new()));
279        let mut renderer = ObservingRenderer::new(
280            RecordingRenderer {
281                draws: Rc::clone(&draws),
282            },
283            {
284                let observed = Rc::clone(&observed);
285                move |scene: &Scene| observed.borrow_mut().push(scene.custom.len())
286            },
287        );
288
289        let mut scene = Scene::default();
290        scene.custom.push(CustomRenderPrimitive::Texture {
291            order: 0,
292            handle: ImportedTextureHandle::new(()),
293            bounds: Bounds::default(),
294            content_mask: ContentMask::default(),
295            radii: Corners::default(),
296            opacity: 1.0,
297            flip_v: false,
298        });
299
300        assert!(renderer.draw(&scene));
301
302        assert_eq!(*observed.borrow(), [1]);
303        assert_eq!(*draws.borrow(), 1);
304    }
305}