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}