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 /// The offscreen target of the last [`render_scene`](Self::render_scene), as a handle that may
153 /// cross a thread boundary.
154 ///
155 /// This is the zero-copy half of the offscreen contract: a CPU consumer calls
156 /// [`read_pixels`](Self::read_pixels), while a GPU consumer — a guest runner's host loop —
157 /// receives this and samples the target on the device it was rendered with. The handle is
158 /// `Send + Sync` and opaque; the engine names no graphics API, so a backend downcasts it to its
159 /// own type. `None` when the renderer has no shareable target or has not rendered one yet.
160 fn offscreen_surface_handle(&self) -> Option<Arc<dyn Any + Send + Sync>> {
161 None
162 }
163
164 /// Renders `scene` offscreen and reads the result back.
165 fn render_scene_to_image(
166 &mut self,
167 scene: &Scene,
168 size: Size<DevicePixels>,
169 ) -> anyhow::Result<PixelBuffer> {
170 self.render_scene(scene, size)?;
171 self.read_pixels()
172 }
173}
174
175/// A renderer that hands every scene it is given to a closure before passing it on.
176///
177/// Observation belongs here rather than in a backend. It needs nothing from the platform, so a
178/// backend that learned about inspection would carry it once per API, and the *scene* is already
179/// an engine type: a scene optimiser, a culling pass, an assertion about what primitives a
180/// renderer is handed and in what order, are all statements about this layer.
181///
182/// What a window painted is observable one layer up, where the frame still holds the scene it
183/// built (`Window::painted_quads`), so this is for observing a renderer rather than a frame.
184///
185/// This wraps a renderer in another renderer, and the choices that make it useful are the
186/// forwarding ones: it observes [`draw`](SceneRenderer::draw) and
187/// [`render_scene`](SceneRenderer::render_scene), and it does *not* override
188/// [`render_scene_to_image`](SceneRenderer::render_scene_to_image), so a readback is observed
189/// once through the `render_scene` it composes rather than twice.
190pub struct ObservingRenderer<R, F> {
191 inner: R,
192 on_scene: F,
193}
194
195impl<R, F> ObservingRenderer<R, F> {
196 /// Wraps `inner`, calling `on_scene` with each scene it is asked to draw.
197 pub fn new(inner: R, on_scene: F) -> Self {
198 Self { inner, on_scene }
199 }
200
201 /// The renderer being observed.
202 pub fn inner(&self) -> &R {
203 &self.inner
204 }
205
206 /// The renderer being observed.
207 pub fn inner_mut(&mut self) -> &mut R {
208 &mut self.inner
209 }
210
211 /// Takes back the observed renderer.
212 pub fn into_inner(self) -> R {
213 self.inner
214 }
215}
216
217impl<R, F> SceneRenderer for ObservingRenderer<R, F>
218where
219 R: SceneRenderer,
220 F: FnMut(&Scene) + 'static,
221{
222 fn draw(&mut self, scene: &Scene) -> bool {
223 (self.on_scene)(scene);
224 self.inner.draw(scene)
225 }
226
227 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
228 self.inner.sprite_atlas()
229 }
230
231 fn set_viewport_size(&mut self, size: Size<DevicePixels>) {
232 self.inner.set_viewport_size(size)
233 }
234
235 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
236 (self.on_scene)(scene);
237 self.inner.render_scene(scene, size)
238 }
239
240 fn read_pixels(&mut self) -> anyhow::Result<PixelBuffer> {
241 self.inner.read_pixels()
242 }
243
244 fn offscreen_surface_handle(&self) -> Option<Arc<dyn Any + Send + Sync>> {
245 self.inner.offscreen_surface_handle()
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use std::{borrow::Cow, cell::RefCell, rc::Rc};
252
253 use super::*;
254 use crate::{AtlasKey, AtlasTile, CustomRenderPrimitive, ImportedTextureHandle};
255 use gpui_types::{Bounds, ContentMask, Corners};
256
257 /// The observer never uploads, so its atlas only has to exist.
258 struct StubAtlas;
259
260 impl PlatformAtlas for StubAtlas {
261 fn get_or_insert_with<'a>(
262 &self,
263 _key: AtlasKey,
264 _build: &mut dyn FnMut() -> anyhow::Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
265 ) -> anyhow::Result<Option<AtlasTile>> {
266 Ok(None)
267 }
268
269 fn remove(&self, _key: &AtlasKey) {}
270 }
271
272 struct RecordingRenderer {
273 draws: Rc<RefCell<usize>>,
274 }
275
276 impl SceneRenderer for RecordingRenderer {
277 fn draw(&mut self, _scene: &Scene) -> bool {
278 *self.draws.borrow_mut() += 1;
279 true
280 }
281
282 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
283 Arc::new(StubAtlas)
284 }
285 }
286
287 /// What the wrap is for: the closure sees the scene the caller handed over, and the render it
288 /// forwards to still happens. An observer that swallowed the scene would pass the count and
289 /// fail the second assertion, and one that passed the scene on without observing would fail
290 /// the first.
291 #[test]
292 fn an_observing_renderer_observes_the_scene_and_forwards_it() {
293 let draws = Rc::new(RefCell::new(0));
294 let observed = Rc::new(RefCell::new(Vec::new()));
295 let mut renderer = ObservingRenderer::new(
296 RecordingRenderer {
297 draws: Rc::clone(&draws),
298 },
299 {
300 let observed = Rc::clone(&observed);
301 move |scene: &Scene| observed.borrow_mut().push(scene.custom.len())
302 },
303 );
304
305 let mut scene = Scene::default();
306 scene.custom.push(CustomRenderPrimitive::Texture {
307 order: 0,
308 handle: ImportedTextureHandle::new(()),
309 bounds: Bounds::default(),
310 content_mask: ContentMask::default(),
311 radii: Corners::default(),
312 opacity: 1.0,
313 flip_v: false,
314 });
315
316 assert!(renderer.draw(&scene));
317
318 assert_eq!(*observed.borrow(), [1]);
319 assert_eq!(*draws.borrow(), 1);
320 }
321}