Skip to content

Commit efe3e13

Browse files
committed
Added vello render implementation
1 parent 0ca573b commit efe3e13

4 files changed

Lines changed: 193 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gosub_vello/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ description = "Vello rendering backend for Gosub"
99
[dependencies]
1010
gosub_shared = { version = "0.1.1", registry = "gosub", path = "../gosub_shared" }
1111
gosub_interface = { version = "0.1.1", registry = "gosub", path = "../gosub_interface", features = [] }
12+
gosub_engine_api = { version = "0.1.0", path = "../gosub_engine_api" }
1213
gosub_html5 = { version = "0.1.1", registry = "gosub", path = "../gosub_html5", optional = true }
1314
gosub_svg = { version = "0.1.1", registry = "gosub", path = "../gosub_svg" }
1415
gosub_fontmanager = { version = "0.1.0", path = "../gosub_fontmanager", registry = "gosub" }
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use std::any::Any;
2+
use std::collections::HashMap;
3+
use std::sync::atomic::{AtomicU64, Ordering};
4+
use std::sync::{Arc, Mutex};
5+
6+
use anyhow::anyhow;
7+
use vello::kurbo::Affine;
8+
use vello::peniko::{Color as VelloColor, Fill};
9+
use vello::wgpu;
10+
use vello::{AaConfig, RenderParams, Renderer as VelloRenderer, Scene};
11+
12+
use gosub_engine_api::render::backend::{
13+
ErasedSurface, ExternalHandle, GpuPixelFormat, PresentMode, RenderBackend as EngineRenderBackend, RgbaImage,
14+
SurfaceSize,
15+
};
16+
use gosub_engine_api::render::DisplayItem;
17+
use gosub_engine_api::BrowsingContext;
18+
19+
use crate::render::InstanceAdapter;
20+
21+
/// Off-screen Vello/wgpu surface used by the `gosub_engine_api::RenderBackend` path.
22+
struct VelloEngineSurface {
23+
id: u64,
24+
size: SurfaceSize,
25+
frame_id: u64,
26+
}
27+
28+
impl ErasedSurface for VelloEngineSurface {
29+
fn as_any(&self) -> &dyn Any {
30+
self
31+
}
32+
fn as_any_mut(&mut self) -> &mut dyn Any {
33+
self
34+
}
35+
fn size(&self) -> SurfaceSize {
36+
self.size
37+
}
38+
}
39+
40+
/// Vello rendering backend for the new `gosub_engine_api::RenderBackend` path.
41+
///
42+
/// Owns its own wgpu device/queue and Vello renderer. Distinct from [`crate::VelloBackend`]
43+
/// which implements the old `gosub_interface::RenderBackend` and obtains its wgpu context
44+
/// from the windowing system via `WindowData`.
45+
pub struct VelloEngineBackend {
46+
instance_adapter: Arc<InstanceAdapter>,
47+
renderer: Mutex<VelloRenderer>,
48+
textures: Mutex<HashMap<u64, (wgpu::Texture, wgpu::TextureView)>>,
49+
next_id: AtomicU64,
50+
}
51+
52+
impl VelloEngineBackend {
53+
/// Creates a new backend, initialising the wgpu device/queue synchronously.
54+
pub fn new() -> anyhow::Result<Self> {
55+
let r = futures::executor::block_on(crate::render::Renderer::new())?;
56+
let ia = r.instance_adapter;
57+
let renderer = ia.create_renderer(None)?;
58+
59+
Ok(Self {
60+
instance_adapter: ia,
61+
renderer: Mutex::new(renderer),
62+
textures: Mutex::new(HashMap::new()),
63+
next_id: AtomicU64::new(1),
64+
})
65+
}
66+
}
67+
68+
impl EngineRenderBackend for VelloEngineBackend {
69+
fn name(&self) -> &'static str {
70+
"vello"
71+
}
72+
73+
fn create_surface(
74+
&self,
75+
size: SurfaceSize,
76+
_present: PresentMode,
77+
) -> anyhow::Result<Box<dyn ErasedSurface + Send>> {
78+
let texture = self.instance_adapter.device.create_texture(&wgpu::TextureDescriptor {
79+
label: Some("vello-engine-surface"),
80+
size: wgpu::Extent3d {
81+
width: size.width,
82+
height: size.height,
83+
depth_or_array_layers: 1,
84+
},
85+
mip_level_count: 1,
86+
sample_count: 1,
87+
dimension: wgpu::TextureDimension::D2,
88+
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
89+
format: wgpu::TextureFormat::Rgba8Unorm,
90+
view_formats: &[],
91+
});
92+
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
93+
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
94+
self.textures.lock().unwrap().insert(id, (texture, view));
95+
96+
Ok(Box::new(VelloEngineSurface { id, size, frame_id: 0 }))
97+
}
98+
99+
fn render(&self, ctx: &mut BrowsingContext, surface: &mut dyn ErasedSurface) -> anyhow::Result<()> {
100+
let s = surface
101+
.as_any_mut()
102+
.downcast_mut::<VelloEngineSurface>()
103+
.ok_or_else(|| anyhow!("VelloEngineBackend: wrong surface type"))?;
104+
105+
let vp = ctx.viewport();
106+
let offset_x = vp.x as f32;
107+
let offset_y = vp.y as f32;
108+
109+
let mut scene = Scene::new();
110+
for item in ctx.render_list().items.iter() {
111+
match item {
112+
DisplayItem::Clear { color } => {
113+
scene.fill(
114+
Fill::NonZero,
115+
Affine::IDENTITY,
116+
VelloColor::new([color.r, color.g, color.b, color.a]),
117+
None,
118+
&vello::kurbo::Rect::new(0.0, 0.0, vp.width as f64, vp.height as f64),
119+
);
120+
}
121+
DisplayItem::Rect { x, y, w, h, color } => {
122+
let rx = (*x - offset_x) as f64;
123+
let ry = (*y - offset_y) as f64;
124+
scene.fill(
125+
Fill::NonZero,
126+
Affine::IDENTITY,
127+
VelloColor::new([color.r, color.g, color.b, color.a]),
128+
None,
129+
&vello::kurbo::Rect::new(rx, ry, rx + *w as f64, ry + *h as f64),
130+
);
131+
}
132+
DisplayItem::TextRun { .. } => {
133+
// Text via the new engine API requires font data in DisplayItem.
134+
// Will be addressed when the pipeline provides richer paint commands.
135+
log::warn!("VelloEngineBackend: TextRun not yet implemented in new backend path");
136+
}
137+
}
138+
}
139+
140+
let textures = self.textures.lock().unwrap();
141+
let (_, view) = textures
142+
.get(&s.id)
143+
.ok_or_else(|| anyhow!("VelloEngineBackend: invalid surface id {}", s.id))?;
144+
145+
self.renderer
146+
.lock()
147+
.unwrap()
148+
.render_to_texture(
149+
&self.instance_adapter.device,
150+
&self.instance_adapter.queue,
151+
&scene,
152+
view,
153+
&RenderParams {
154+
base_color: VelloColor::WHITE,
155+
width: s.size.width,
156+
height: s.size.height,
157+
antialiasing_method: AaConfig::Msaa16,
158+
},
159+
)
160+
.map_err(|e| anyhow!(e.to_string()))?;
161+
162+
s.frame_id = s.frame_id.wrapping_add(1);
163+
Ok(())
164+
}
165+
166+
fn snapshot(&self, _surface: &mut dyn ErasedSurface, _max_dim: u32) -> anyhow::Result<RgbaImage> {
167+
Err(anyhow!("VelloEngineBackend: GPU readback snapshot not yet implemented"))
168+
}
169+
170+
fn external_handle(&self, surface: &mut dyn ErasedSurface) -> anyhow::Result<ExternalHandle> {
171+
let s = surface
172+
.as_any_mut()
173+
.downcast_mut::<VelloEngineSurface>()
174+
.ok_or_else(|| anyhow!("VelloEngineBackend: wrong surface type in external_handle"))?;
175+
176+
Ok(ExternalHandle::WgpuTextureId {
177+
id: s.id,
178+
width: s.size.width,
179+
height: s.size.height,
180+
format: GpuPixelFormat::Rgba8UnormSrgb,
181+
frame_id: s.frame_id,
182+
})
183+
}
184+
}
185+
186+
impl Drop for VelloEngineBackend {
187+
fn drop(&mut self) {
188+
self.textures.lock().unwrap().clear();
189+
}
190+
}

crates/gosub_vello/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod text;
3636
mod transform;
3737

3838
mod debug;
39+
pub mod engine_backend;
3940
#[cfg(feature = "vello_svg")]
4041
mod vello_svg;
4142

0 commit comments

Comments
 (0)