1use wgpu::util::DeviceExt;
2
3use crate::scene::*;
4
5#[repr(C)]
6#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
7struct QuadVertex {
8 position: [f32; 2],
9 color: [f32; 4],
10 border_color: [f32; 4],
11 rect: [f32; 4], border_radius: [f32; 4],
13 border_width: f32,
14 _padding: [f32; 3],
15}
16
17#[repr(C)]
18#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
19struct Viewport {
20 size: [f32; 2],
21 _pad: [f32; 2],
22}
23
24pub struct Renderer {
25 pub device: wgpu::Device,
26 pub queue: wgpu::Queue,
27 surface: wgpu::Surface<'static>,
28 config: wgpu::SurfaceConfiguration,
29 quad_pipeline: wgpu::RenderPipeline,
30 viewport_buffer: wgpu::Buffer,
31 viewport_bind_group: wgpu::BindGroup,
32 viewport: [f32; 2],
33 pub format: wgpu::TextureFormat,
34}
35
36impl Renderer {
37 pub async fn new(window: std::sync::Arc<winit::window::Window>) -> Self {
38 let size = window.inner_size();
39
40 let instance = wgpu::Instance::default();
41
42 let surface = instance.create_surface(window.clone()).unwrap();
43
44 let adapter = instance
45 .request_adapter(&wgpu::RequestAdapterOptions {
46 power_preference: wgpu::PowerPreference::HighPerformance,
47 compatible_surface: Some(&surface),
48 force_fallback_adapter: false,
49 })
50 .await
51 .unwrap();
52
53 let (device, queue) = adapter
54 .request_device(&wgpu::DeviceDescriptor {
55 label: Some("foundry"),
56 required_features: wgpu::Features::empty(),
57 required_limits: wgpu::Limits::default(),
58 memory_hints: wgpu::MemoryHints::Performance,
59 trace: wgpu::Trace::Off,
60 experimental_features: wgpu::ExperimentalFeatures::default(),
61 })
62 .await
63 .unwrap();
64
65 let surface_caps = surface.get_capabilities(&adapter);
66 let format = surface_caps
67 .formats
68 .iter()
69 .find(|f| f.is_srgb())
70 .copied()
71 .unwrap_or(surface_caps.formats[0]);
72
73 let config = wgpu::SurfaceConfiguration {
74 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
75 format,
76 width: size.width.max(1),
77 height: size.height.max(1),
78 present_mode: wgpu::PresentMode::AutoVsync,
79 alpha_mode: surface_caps.alpha_modes[0],
80 view_formats: vec![],
81 desired_maximum_frame_latency: 2,
82 };
83 surface.configure(&device, &config);
84
85 let viewport_data = Viewport {
87 size: [size.width as f32, size.height as f32],
88 _pad: [0.0; 2],
89 };
90 let viewport_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
91 label: Some("viewport_uniform"),
92 contents: bytemuck::cast_slice(&[viewport_data]),
93 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
94 });
95
96 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
97 label: Some("viewport_layout"),
98 entries: &[wgpu::BindGroupLayoutEntry {
99 binding: 0,
100 visibility: wgpu::ShaderStages::VERTEX,
101 ty: wgpu::BindingType::Buffer {
102 ty: wgpu::BufferBindingType::Uniform,
103 has_dynamic_offset: false,
104 min_binding_size: None,
105 },
106 count: None,
107 }],
108 });
109
110 let viewport_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
111 label: Some("viewport_bind_group"),
112 layout: &bind_group_layout,
113 entries: &[wgpu::BindGroupEntry {
114 binding: 0,
115 resource: viewport_buffer.as_entire_binding(),
116 }],
117 });
118
119 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
120 label: Some("quad_shader"),
121 source: wgpu::ShaderSource::Wgsl(QUAD_SHADER.into()),
122 });
123
124 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
125 label: Some("quad_pipeline_layout"),
126 bind_group_layouts: &[Some(&bind_group_layout)],
127 ..Default::default()
128 });
129
130 let quad_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
131 label: Some("quad_pipeline"),
132 layout: Some(&pipeline_layout),
133 vertex: wgpu::VertexState {
134 module: &shader,
135 entry_point: Some("vs_main"),
136 buffers: &[wgpu::VertexBufferLayout {
137 array_stride: std::mem::size_of::<QuadVertex>() as u64,
138 step_mode: wgpu::VertexStepMode::Vertex,
139 attributes: &wgpu::vertex_attr_array![
140 0 => Float32x2, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4, 4 => Float32x4, 5 => Float32, ],
147 }],
148 compilation_options: Default::default(),
149 },
150 fragment: Some(wgpu::FragmentState {
151 module: &shader,
152 entry_point: Some("fs_main"),
153 targets: &[Some(wgpu::ColorTargetState {
154 format,
155 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
156 write_mask: wgpu::ColorWrites::ALL,
157 })],
158 compilation_options: Default::default(),
159 }),
160 primitive: wgpu::PrimitiveState {
161 topology: wgpu::PrimitiveTopology::TriangleList,
162 ..Default::default()
163 },
164 depth_stencil: None,
165 multisample: wgpu::MultisampleState::default(),
166 multiview_mask: None,
167 cache: None,
168 });
169
170 Self {
171 device,
172 queue,
173 surface,
174 config,
175 quad_pipeline,
176 viewport_buffer,
177 viewport_bind_group,
178 viewport: [size.width as f32, size.height as f32],
179 format,
180 }
181 }
182
183 pub fn resize(&mut self, width: u32, height: u32) {
184 if width > 0 && height > 0 {
185 self.config.width = width;
186 self.config.height = height;
187 self.surface.configure(&self.device, &self.config);
188 self.viewport = [width as f32, height as f32];
189
190 let viewport_data = Viewport {
191 size: self.viewport,
192 _pad: [0.0; 2],
193 };
194 self.queue.write_buffer(
195 &self.viewport_buffer,
196 0,
197 bytemuck::cast_slice(&[viewport_data]),
198 );
199 }
200 }
201
202 pub fn viewport_size(&self) -> (f32, f32) {
203 (self.viewport[0], self.viewport[1])
204 }
205
206 pub fn render(
207 &mut self,
208 scene: &SceneGraph,
209 mut text_engine: Option<&mut crate::text::TextEngine>,
210 ) -> Result<(), Box<dyn std::error::Error>> {
211 let output = match self.surface.get_current_texture() {
212 wgpu::CurrentSurfaceTexture::Success(tex)
213 | wgpu::CurrentSurfaceTexture::Suboptimal(tex) => tex,
214 other => {
215 return Err(format!("surface error: {:?}", other).into());
216 }
217 };
218 let view = output.texture.create_view(&Default::default());
219
220 if let Some(te) = text_engine.as_mut() {
221 te.prepare(
222 scene,
223 &self.device,
224 &self.queue,
225 self.config.width,
226 self.config.height,
227 );
228 }
229
230 let mut vertices = Vec::new();
231 let mut indices: Vec<u32> = Vec::new();
232
233 if let Some(root) = scene.root {
234 self.collect_quads(scene, root, &mut vertices, &mut indices, 0.0, 0.0);
235 }
236
237 let vertex_buffer = self
238 .device
239 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
240 label: Some("quad_vertices"),
241 contents: bytemuck::cast_slice(&vertices),
242 usage: wgpu::BufferUsages::VERTEX,
243 });
244
245 let index_buffer = self
246 .device
247 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
248 label: Some("quad_indices"),
249 contents: bytemuck::cast_slice(&indices),
250 usage: wgpu::BufferUsages::INDEX,
251 });
252
253 let mut encoder = self
254 .device
255 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
256 label: Some("render_encoder"),
257 });
258
259 let clear_color = scene
261 .root
262 .map(|r| {
263 let bg = &scene.get(r).style.background_color;
264 if bg.a > 0.0 {
265 return wgpu::Color {
266 r: bg.r as f64,
267 g: bg.g as f64,
268 b: bg.b as f64,
269 a: bg.a as f64,
270 };
271 }
272 for &child in &scene.get(r).children {
274 let cbg = &scene.get(child).style.background_color;
275 if cbg.a > 0.0 {
276 return wgpu::Color {
277 r: cbg.r as f64,
278 g: cbg.g as f64,
279 b: cbg.b as f64,
280 a: cbg.a as f64,
281 };
282 }
283 }
284 wgpu::Color {
285 r: 1.0,
286 g: 1.0,
287 b: 1.0,
288 a: 1.0,
289 }
290 })
291 .unwrap_or(wgpu::Color {
292 r: 1.0,
293 g: 1.0,
294 b: 1.0,
295 a: 1.0,
296 });
297
298 {
299 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
300 label: Some("main_pass"),
301 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
302 view: &view,
303 resolve_target: None,
304 ops: wgpu::Operations {
305 load: wgpu::LoadOp::Clear(clear_color),
306 store: wgpu::StoreOp::Store,
307 },
308 depth_slice: None,
309 })],
310 depth_stencil_attachment: None,
311 timestamp_writes: None,
312 occlusion_query_set: None,
313 multiview_mask: None,
314 });
315
316 if !indices.is_empty() {
317 pass.set_pipeline(&self.quad_pipeline);
318 pass.set_bind_group(0, &self.viewport_bind_group, &[]);
319 pass.set_vertex_buffer(0, vertex_buffer.slice(..));
320 pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
321 pass.draw_indexed(0..indices.len() as u32, 0, 0..1);
322 }
323
324 if let Some(te) = text_engine.as_ref() {
325 te.render(&mut pass);
326 }
327 }
328
329 self.queue.submit(std::iter::once(encoder.finish()));
330 output.present();
331
332 Ok(())
333 }
334
335 fn collect_quads(
336 &self,
337 scene: &SceneGraph,
338 node_id: NodeId,
339 vertices: &mut Vec<QuadVertex>,
340 indices: &mut Vec<u32>,
341 scroll_x: f32,
342 scroll_y: f32,
343 ) {
344 let node = scene.get(node_id);
345
346 if node.style.display == Display::None {
347 return;
348 }
349
350 let layout = &node.layout;
351 let x = layout.x - scroll_x;
352 let y = layout.y - scroll_y;
353 let w = layout.width;
354 let h = layout.height;
355
356 if w <= 0.0 || h <= 0.0 {
357 let sx = scroll_x + node.scroll_offset.0;
358 let sy = scroll_y + node.scroll_offset.1;
359 for &child_id in &node.children {
360 self.collect_quads(scene, child_id, vertices, indices, sx, sy);
361 }
362 return;
363 }
364
365 let bg = node.style.background_color;
366 let has_bg = bg.a > 0.0;
367 let has_border = node.style.border_width.iter().any(|&w| w > 0.0);
368
369 if has_bg || has_border {
370 let base_idx = vertices.len() as u32;
371
372 let color = [bg.r, bg.g, bg.b, bg.a * node.style.opacity];
373 let bc = node.style.border_color;
374 let border_color = [bc.r, bc.g, bc.b, bc.a];
375 let rect = [x, y, w, h];
376 let border_radius = node.style.border_radius;
377 let border_width = node.style.border_width[0];
378
379 let corners = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
380
381 for pos in corners {
382 vertices.push(QuadVertex {
383 position: pos,
384 color,
385 border_color,
386 rect,
387 border_radius,
388 border_width,
389 _padding: [0.0; 3],
390 });
391 }
392
393 indices.extend_from_slice(&[
394 base_idx,
395 base_idx + 1,
396 base_idx + 2,
397 base_idx,
398 base_idx + 2,
399 base_idx + 3,
400 ]);
401 }
402
403 let sx = scroll_x + node.scroll_offset.0;
404 let sy = scroll_y + node.scroll_offset.1;
405 for &child_id in &node.children {
406 self.collect_quads(scene, child_id, vertices, indices, sx, sy);
407 }
408 }
409}
410
411const QUAD_SHADER: &str = r#"
412struct Viewport {
413 size: vec2<f32>,
414 _pad: vec2<f32>,
415};
416
417@group(0) @binding(0)
418var<uniform> viewport: Viewport;
419
420struct VertexInput {
421 @location(0) position: vec2<f32>,
422 @location(1) color: vec4<f32>,
423 @location(2) border_color: vec4<f32>,
424 @location(3) rect: vec4<f32>,
425 @location(4) border_radius: vec4<f32>,
426 @location(5) border_width: f32,
427};
428
429struct VertexOutput {
430 @builtin(position) clip_position: vec4<f32>,
431 @location(0) color: vec4<f32>,
432 @location(1) border_color: vec4<f32>,
433 @location(2) pixel_pos: vec2<f32>,
434 @location(3) rect: vec4<f32>,
435 @location(4) border_radius: vec4<f32>,
436 @location(5) border_width: f32,
437};
438
439@vertex
440fn vs_main(in: VertexInput) -> VertexOutput {
441 var out: VertexOutput;
442 let x = (in.position.x / viewport.size.x) * 2.0 - 1.0;
443 let y = 1.0 - (in.position.y / viewport.size.y) * 2.0;
444 out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
445 out.color = in.color;
446 out.border_color = in.border_color;
447 out.pixel_pos = in.position;
448 out.rect = in.rect;
449 out.border_radius = in.border_radius;
450 out.border_width = in.border_width;
451 return out;
452}
453
454fn rounded_rect_sdf(pixel: vec2<f32>, rect: vec4<f32>, radius: vec4<f32>) -> f32 {
455 let center = vec2<f32>(rect.x + rect.z * 0.5, rect.y + rect.w * 0.5);
456 let half_size = vec2<f32>(rect.z * 0.5, rect.w * 0.5);
457 let p = pixel - center;
458
459 var r: f32;
460 if p.x < 0.0 {
461 if p.y < 0.0 { r = radius.x; }
462 else { r = radius.w; }
463 } else {
464 if p.y < 0.0 { r = radius.y; }
465 else { r = radius.z; }
466 }
467
468 let q = abs(p) - half_size + vec2<f32>(r, r);
469 return min(max(q.x, q.y), 0.0) + length(max(q, vec2<f32>(0.0, 0.0))) - r;
470}
471
472@fragment
473fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
474 let d = rounded_rect_sdf(in.pixel_pos, in.rect, in.border_radius);
475
476 let aa = 1.0;
477 let alpha = 1.0 - smoothstep(-aa, aa, d);
478
479 if alpha < 0.001 {
480 discard;
481 }
482
483 if in.border_width > 0.0 {
484 let inner_d = rounded_rect_sdf(
485 in.pixel_pos,
486 vec4<f32>(
487 in.rect.x + in.border_width,
488 in.rect.y + in.border_width,
489 in.rect.z - in.border_width * 2.0,
490 in.rect.w - in.border_width * 2.0,
491 ),
492 max(in.border_radius - vec4<f32>(in.border_width), vec4<f32>(0.0)),
493 );
494
495 let border_alpha = smoothstep(-aa, aa, inner_d);
496 let color = mix(in.color, in.border_color, border_alpha);
497 return vec4<f32>(color.rgb, color.a * alpha);
498 }
499
500 return vec4<f32>(in.color.rgb, in.color.a * alpha);
501}
502"#;