use crate::{PlatformAtlas, Scene};
use gpui_types::{DevicePixels, Size};
use std::any::Any;
use std::sync::Arc;
impl dyn SceneRenderer + '_ {
pub fn as_renderer<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref::<T>()
}
pub fn as_renderer_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut::<T>()
}
}
pub trait GpuRenderer: SceneRenderer {
type Device;
fn device(&self) -> Option<Self::Device>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PixelBuffer {
width: u32,
height: u32,
data: Vec<u8>,
}
impl PixelBuffer {
pub fn new(width: u32, height: u32, data: Vec<u8>) -> anyhow::Result<Self> {
let expected = width as usize * height as usize * 4;
anyhow::ensure!(
data.len() == expected,
"a {width}x{height} RGBA8 image is {expected} bytes, but the buffer is {}",
data.len()
);
Ok(Self {
width,
height,
data,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn into_data(self) -> Vec<u8> {
self.data
}
}
pub trait SceneRenderer: Any {
fn draw(&mut self, scene: &Scene) -> bool;
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
fn set_viewport_size(&mut self, _size: Size<DevicePixels>) {}
fn render_scene(&mut self, _scene: &Scene, _size: Size<DevicePixels>) -> anyhow::Result<()> {
anyhow::bail!("offscreen rendering is not supported by this renderer")
}
fn read_pixels(&mut self) -> anyhow::Result<PixelBuffer> {
anyhow::bail!("pixel readback is not supported by this renderer")
}
fn offscreen_surface_handle(&self) -> Option<Arc<dyn Any + Send + Sync>> {
None
}
fn render_scene_to_image(
&mut self,
scene: &Scene,
size: Size<DevicePixels>,
) -> anyhow::Result<PixelBuffer> {
self.render_scene(scene, size)?;
self.read_pixels()
}
}
pub struct ObservingRenderer<R, F> {
inner: R,
on_scene: F,
}
impl<R, F> ObservingRenderer<R, F> {
pub fn new(inner: R, on_scene: F) -> Self {
Self { inner, on_scene }
}
pub fn inner(&self) -> &R {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut R {
&mut self.inner
}
pub fn into_inner(self) -> R {
self.inner
}
}
impl<R, F> SceneRenderer for ObservingRenderer<R, F>
where
R: SceneRenderer,
F: FnMut(&Scene) + 'static,
{
fn draw(&mut self, scene: &Scene) -> bool {
(self.on_scene)(scene);
self.inner.draw(scene)
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.inner.sprite_atlas()
}
fn set_viewport_size(&mut self, size: Size<DevicePixels>) {
self.inner.set_viewport_size(size)
}
fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
(self.on_scene)(scene);
self.inner.render_scene(scene, size)
}
fn read_pixels(&mut self) -> anyhow::Result<PixelBuffer> {
self.inner.read_pixels()
}
fn offscreen_surface_handle(&self) -> Option<Arc<dyn Any + Send + Sync>> {
self.inner.offscreen_surface_handle()
}
}
#[cfg(test)]
mod tests {
use std::{borrow::Cow, cell::RefCell, rc::Rc};
use super::*;
use crate::{AtlasKey, AtlasTile, CustomRenderPrimitive, ImportedTextureHandle};
use gpui_types::{Bounds, ContentMask, Corners};
struct StubAtlas;
impl PlatformAtlas for StubAtlas {
fn get_or_insert_with<'a>(
&self,
_key: AtlasKey,
_build: &mut dyn FnMut() -> anyhow::Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
) -> anyhow::Result<Option<AtlasTile>> {
Ok(None)
}
fn remove(&self, _key: &AtlasKey) {}
}
struct RecordingRenderer {
draws: Rc<RefCell<usize>>,
}
impl SceneRenderer for RecordingRenderer {
fn draw(&mut self, _scene: &Scene) -> bool {
*self.draws.borrow_mut() += 1;
true
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
Arc::new(StubAtlas)
}
}
#[test]
fn an_observing_renderer_observes_the_scene_and_forwards_it() {
let draws = Rc::new(RefCell::new(0));
let observed = Rc::new(RefCell::new(Vec::new()));
let mut renderer = ObservingRenderer::new(
RecordingRenderer {
draws: Rc::clone(&draws),
},
{
let observed = Rc::clone(&observed);
move |scene: &Scene| observed.borrow_mut().push(scene.custom.len())
},
);
let mut scene = Scene::default();
scene.custom.push(CustomRenderPrimitive::Texture {
order: 0,
handle: ImportedTextureHandle::new(()),
bounds: Bounds::default(),
content_mask: ContentMask::default(),
radii: Corners::default(),
opacity: 1.0,
flip_v: false,
});
assert!(renderer.draw(&scene));
assert_eq!(*observed.borrow(), [1]);
assert_eq!(*draws.borrow(), 1);
}
}