1use std::collections::HashMap;
2use std::sync::Arc;
3use std::{borrow::Cow, sync::Once};
4
5use repose_core::{Brush, GlyphRasterConfig, RenderBackend, Scene, SceneNode, Transform};
6use std::panic::{AssertUnwindSafe, catch_unwind};
7use wgpu::Instance;
8
9static ROT_WARN_ONCE: Once = Once::new();
10
11#[derive(Clone)]
12struct UploadRing {
13 buf: wgpu::Buffer,
14 cap: u64,
15 head: u64,
16}
17
18impl UploadRing {
19 fn new(device: &wgpu::Device, label: &str, cap: u64) -> Self {
20 let buf = device.create_buffer(&wgpu::BufferDescriptor {
21 label: Some(label),
22 size: cap,
23 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
24 mapped_at_creation: false,
25 });
26 Self { buf, cap, head: 0 }
27 }
28
29 fn reset(&mut self) {
30 self.head = 0;
31 }
32
33 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
34 if needed <= self.cap {
35 return;
36 }
37 let new_cap = needed.next_power_of_two();
38 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
39 label: Some("upload ring (grown)"),
40 size: new_cap,
41 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
42 mapped_at_creation: false,
43 });
44 self.cap = new_cap;
45 self.head = 0;
46 }
47
48 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
49 let len = bytes.len() as u64;
50 let start = (self.head + 3) & !3; let end = start + len;
52 assert!(end <= self.cap, "ring overflow - call grow_to_fit first");
53 queue.write_buffer(&self.buf, start, bytes);
54 self.head = end;
55 (start, len)
56 }
57}
58
59struct InstancedPipe<I: bytemuck::Pod> {
60 ring: UploadRing,
61 stride: u64,
62 _marker: std::marker::PhantomData<I>,
63}
64
65impl<I: bytemuck::Pod> InstancedPipe<I> {
66 fn new(ring: UploadRing) -> Self {
67 Self {
68 ring,
69 stride: std::mem::size_of::<I>() as u64,
70 _marker: std::marker::PhantomData,
71 }
72 }
73
74 fn upload(
75 &mut self,
76 device: &wgpu::Device,
77 queue: &wgpu::Queue,
78 data: &[I],
79 ) -> Option<(u64, u32)> {
80 if data.is_empty() {
81 return None;
82 }
83 let bytes = bytemuck::cast_slice(data);
84 self.ring.grow_to_fit(device, bytes.len() as u64);
85 let (off, wrote) = self.ring.alloc_write(queue, bytes);
86 debug_assert_eq!(wrote as usize, bytes.len());
87 Some((off, data.len() as u32))
88 }
89
90 fn reset(&mut self) {
91 self.ring.reset();
92 }
93}
94
95#[repr(C)]
96#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
97struct Globals {
98 ndc_to_px: [f32; 2],
99 _pad: [f32; 2],
100}
101
102pub struct WgpuBackend {
103 surface: wgpu::Surface<'static>,
104 device: wgpu::Device,
105 queue: wgpu::Queue,
106 config: wgpu::SurfaceConfiguration,
107
108 surface_pipes: Pipelines,
111 layer_pipes: Pipelines,
112
113 rects: InstancedPipe<RectInstance>,
115 borders: InstancedPipe<BorderInstance>,
116 ellipses: InstancedPipe<EllipseInstance>,
117 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
118 glyph_mask: InstancedPipe<GlyphInstance>,
119 glyph_color: InstancedPipe<GlyphInstance>,
120
121 image_bind_layout_rgba: wgpu::BindGroupLayout,
123 image_bind_layout_nv12: wgpu::BindGroupLayout,
124 image_sampler: wgpu::Sampler,
125
126 blur_ring: UploadRing,
128
129 text_bind_layout: wgpu::BindGroupLayout,
130
131 clip_ring: UploadRing,
133
134 nv12: InstancedPipe<Nv12Instance>,
136
137 msaa_samples: u32,
138
139 depth_stencil_tex: wgpu::Texture,
141 depth_stencil_view: wgpu::TextureView,
142
143 msaa_tex: Option<wgpu::Texture>,
145 msaa_view: Option<wgpu::TextureView>,
146
147 globals_layout: wgpu::BindGroupLayout,
148 globals_buf: wgpu::Buffer,
149 globals_bind: wgpu::BindGroup,
150
151 atlas_mask: AtlasA8,
153 atlas_color: AtlasRGBA,
154
155 next_image_handle: u64,
157 images: HashMap<u64, ImageTex>,
158
159 frame_index: u64,
161 image_bytes_total: u64,
162 image_evict_after_frames: u64,
163 image_budget_bytes: u64,
164
165 layer_pool: HashMap<u32, LayerTarget>,
168}
169
170#[derive(Clone)]
171struct LayerTarget {
172 texture: wgpu::Texture,
173 view: wgpu::TextureView,
174 bind: wgpu::BindGroup,
175 depth_stencil_tex: wgpu::Texture,
176 depth_stencil_view: wgpu::TextureView,
177 width: u32,
178 height: u32,
179 rect_px: (f32, f32, f32, f32),
180}
181
182#[derive(Clone, Copy)]
184enum PassTarget {
185 Surface,
186 Layer(u32),
187}
188
189struct Pipelines {
194 rects: wgpu::RenderPipeline,
195 borders: wgpu::RenderPipeline,
196 ellipses: wgpu::RenderPipeline,
197 ellipse_borders: wgpu::RenderPipeline,
198 text_mask: wgpu::RenderPipeline,
199 text_color: wgpu::RenderPipeline,
200 image_rgba: wgpu::RenderPipeline,
201 image_nv12: wgpu::RenderPipeline,
202 blur: wgpu::RenderPipeline,
203 clip_a2c: wgpu::RenderPipeline,
204 clip_bin: wgpu::RenderPipeline,
205}
206
207impl Pipelines {
208 fn create(
209 device: &wgpu::Device,
210 format: wgpu::TextureFormat,
211 sample_count: u32,
212 globals_layout: &wgpu::BindGroupLayout,
213 text_bind_layout: &wgpu::BindGroupLayout,
214 image_bind_layout_nv12: &wgpu::BindGroupLayout,
215 clip_pipeline_layout: &wgpu::PipelineLayout,
216 stencil_for_content: &wgpu::DepthStencilState,
217 stencil_for_clip_inc: &wgpu::DepthStencilState,
218 clip_color_target: &wgpu::ColorTargetState,
219 clip_vertex_layout: &wgpu::VertexBufferLayout,
220 ) -> Self {
221 let msaa_state = wgpu::MultisampleState {
222 count: sample_count,
223 mask: !0,
224 alpha_to_coverage_enabled: false,
225 };
226
227 let rect_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
229 label: Some("rect.wgsl"),
230 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/rect.wgsl"))),
231 });
232 let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
233 label: Some("rect pipeline layout"),
234 bind_group_layouts: &[Some(globals_layout)],
235 immediate_size: 0,
236 });
237 let rects = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
238 label: Some("rect pipeline"),
239 layout: Some(&rect_pipeline_layout),
240 vertex: wgpu::VertexState {
241 module: &rect_shader,
242 entry_point: Some("vs_main"),
243 buffers: &[wgpu::VertexBufferLayout {
244 array_stride: std::mem::size_of::<RectInstance>() as u64,
245 step_mode: wgpu::VertexStepMode::Instance,
246 attributes: &[
247 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
248 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32 },
249 wgpu::VertexAttribute { shader_location: 2, offset: 20, format: wgpu::VertexFormat::Uint32 },
250 wgpu::VertexAttribute { shader_location: 3, offset: 24, format: wgpu::VertexFormat::Float32x4 },
251 wgpu::VertexAttribute { shader_location: 4, offset: 40, format: wgpu::VertexFormat::Float32x4 },
252 wgpu::VertexAttribute { shader_location: 5, offset: 56, format: wgpu::VertexFormat::Float32x2 },
253 wgpu::VertexAttribute { shader_location: 6, offset: 64, format: wgpu::VertexFormat::Float32x2 },
254 ],
255 }],
256 compilation_options: wgpu::PipelineCompilationOptions::default(),
257 },
258 fragment: Some(wgpu::FragmentState {
259 module: &rect_shader,
260 entry_point: Some("fs_main"),
261 targets: &[Some(wgpu::ColorTargetState {
262 format,
263 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
264 write_mask: wgpu::ColorWrites::ALL,
265 })],
266 compilation_options: wgpu::PipelineCompilationOptions::default(),
267 }),
268 primitive: wgpu::PrimitiveState::default(),
269 depth_stencil: Some(stencil_for_content.clone()),
270 multisample: msaa_state,
271 multiview_mask: None,
272 cache: None,
273 });
274
275 let border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
277 label: Some("border.wgsl"),
278 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/border.wgsl"))),
279 });
280 let border_pipeline_layout =
281 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
282 label: Some("border pipeline layout"),
283 bind_group_layouts: &[Some(globals_layout)],
284 immediate_size: 0,
285 });
286 let borders = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
287 label: Some("border pipeline"),
288 layout: Some(&border_pipeline_layout),
289 vertex: wgpu::VertexState {
290 module: &border_shader,
291 entry_point: Some("vs_main"),
292 buffers: &[wgpu::VertexBufferLayout {
293 array_stride: std::mem::size_of::<BorderInstance>() as u64,
294 step_mode: wgpu::VertexStepMode::Instance,
295 attributes: &[
296 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
297 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32 },
298 wgpu::VertexAttribute { shader_location: 2, offset: 20, format: wgpu::VertexFormat::Float32 },
299 wgpu::VertexAttribute { shader_location: 3, offset: 24, format: wgpu::VertexFormat::Float32x4 },
300 ],
301 }],
302 compilation_options: wgpu::PipelineCompilationOptions::default(),
303 },
304 fragment: Some(wgpu::FragmentState {
305 module: &border_shader,
306 entry_point: Some("fs_main"),
307 targets: &[Some(wgpu::ColorTargetState {
308 format,
309 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
310 write_mask: wgpu::ColorWrites::ALL,
311 })],
312 compilation_options: wgpu::PipelineCompilationOptions::default(),
313 }),
314 primitive: wgpu::PrimitiveState::default(),
315 depth_stencil: Some(stencil_for_content.clone()),
316 multisample: msaa_state,
317 multiview_mask: None,
318 cache: None,
319 });
320
321 let ellipse_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
323 label: Some("ellipse.wgsl"),
324 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/ellipse.wgsl"))),
325 });
326 let ellipse_pipeline_layout =
327 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
328 label: Some("ellipse pipeline layout"),
329 bind_group_layouts: &[Some(globals_layout)],
330 immediate_size: 0,
331 });
332 let ellipses = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
333 label: Some("ellipse pipeline"),
334 layout: Some(&ellipse_pipeline_layout),
335 vertex: wgpu::VertexState {
336 module: &ellipse_shader,
337 entry_point: Some("vs_main"),
338 buffers: &[wgpu::VertexBufferLayout {
339 array_stride: std::mem::size_of::<EllipseInstance>() as u64,
340 step_mode: wgpu::VertexStepMode::Instance,
341 attributes: &[
342 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
343 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32x4 },
344 ],
345 }],
346 compilation_options: wgpu::PipelineCompilationOptions::default(),
347 },
348 fragment: Some(wgpu::FragmentState {
349 module: &ellipse_shader,
350 entry_point: Some("fs_main"),
351 targets: &[Some(wgpu::ColorTargetState {
352 format,
353 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
354 write_mask: wgpu::ColorWrites::ALL,
355 })],
356 compilation_options: wgpu::PipelineCompilationOptions::default(),
357 }),
358 primitive: wgpu::PrimitiveState::default(),
359 depth_stencil: Some(stencil_for_content.clone()),
360 multisample: msaa_state,
361 multiview_mask: None,
362 cache: None,
363 });
364
365 let ellipse_border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
367 label: Some("ellipse_border.wgsl"),
368 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
369 "shaders/ellipse_border.wgsl"
370 ))),
371 });
372 let ellipse_border_layout =
373 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
374 label: Some("ellipse border layout"),
375 bind_group_layouts: &[Some(globals_layout)],
376 immediate_size: 0,
377 });
378 let ellipse_borders = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
379 label: Some("ellipse border pipeline"),
380 layout: Some(&ellipse_border_layout),
381 vertex: wgpu::VertexState {
382 module: &ellipse_border_shader,
383 entry_point: Some("vs_main"),
384 buffers: &[wgpu::VertexBufferLayout {
385 array_stride: std::mem::size_of::<EllipseBorderInstance>() as u64,
386 step_mode: wgpu::VertexStepMode::Instance,
387 attributes: &[
388 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
389 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32 },
390 wgpu::VertexAttribute { shader_location: 2, offset: 20, format: wgpu::VertexFormat::Float32x4 },
391 ],
392 }],
393 compilation_options: wgpu::PipelineCompilationOptions::default(),
394 },
395 fragment: Some(wgpu::FragmentState {
396 module: &ellipse_border_shader,
397 entry_point: Some("fs_main"),
398 targets: &[Some(wgpu::ColorTargetState {
399 format,
400 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
401 write_mask: wgpu::ColorWrites::ALL,
402 })],
403 compilation_options: wgpu::PipelineCompilationOptions::default(),
404 }),
405 primitive: wgpu::PrimitiveState::default(),
406 depth_stencil: Some(stencil_for_content.clone()),
407 multisample: msaa_state,
408 multiview_mask: None,
409 cache: None,
410 });
411
412 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
414 label: Some("text.wgsl"),
415 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
416 });
417 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
419 label: Some("text_color.wgsl"),
420 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
421 "shaders/text_color.wgsl"
422 ))),
423 });
424 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
425 label: Some("text pipeline layout"),
426 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
427 immediate_size: 0,
428 });
429 let glyph_vertex = wgpu::VertexBufferLayout {
430 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
431 step_mode: wgpu::VertexStepMode::Instance,
432 attributes: &[
433 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
434 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32x4 },
435 wgpu::VertexAttribute { shader_location: 2, offset: 32, format: wgpu::VertexFormat::Float32x4 },
436 ],
437 };
438 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
439 label: Some("text pipeline (mask)"),
440 layout: Some(&text_pipeline_layout),
441 vertex: wgpu::VertexState {
442 module: &text_mask_shader,
443 entry_point: Some("vs_main"),
444 buffers: &[glyph_vertex.clone()],
445 compilation_options: wgpu::PipelineCompilationOptions::default(),
446 },
447 fragment: Some(wgpu::FragmentState {
448 module: &text_mask_shader,
449 entry_point: Some("fs_main"),
450 targets: &[Some(wgpu::ColorTargetState {
451 format,
452 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
453 write_mask: wgpu::ColorWrites::ALL,
454 })],
455 compilation_options: wgpu::PipelineCompilationOptions::default(),
456 }),
457 primitive: wgpu::PrimitiveState::default(),
458 depth_stencil: Some(stencil_for_content.clone()),
459 multisample: msaa_state,
460 multiview_mask: None,
461 cache: None,
462 });
463 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
464 label: Some("text pipeline (color)"),
465 layout: Some(&text_pipeline_layout),
466 vertex: wgpu::VertexState {
467 module: &text_color_shader,
468 entry_point: Some("vs_main"),
469 buffers: &[glyph_vertex],
470 compilation_options: wgpu::PipelineCompilationOptions::default(),
471 },
472 fragment: Some(wgpu::FragmentState {
473 module: &text_color_shader,
474 entry_point: Some("fs_main"),
475 targets: &[Some(wgpu::ColorTargetState {
476 format,
477 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
478 write_mask: wgpu::ColorWrites::ALL,
479 })],
480 compilation_options: wgpu::PipelineCompilationOptions::default(),
481 }),
482 primitive: wgpu::PrimitiveState::default(),
483 depth_stencil: Some(stencil_for_content.clone()),
484 multisample: msaa_state,
485 multiview_mask: None,
486 cache: None,
487 });
488 let image_rgba = text_color.clone();
490
491 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
493 label: Some("blur_shadow.wgsl"),
494 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
495 "shaders/blur_shadow.wgsl"
496 ))),
497 });
498 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
499 label: Some("blur pipeline layout"),
500 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
501 immediate_size: 0,
502 });
503 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
504 label: Some("blur pipeline"),
505 layout: Some(&blur_pipeline_layout),
506 vertex: wgpu::VertexState {
507 module: &blur_shader,
508 entry_point: Some("vs_main"),
509 buffers: &[wgpu::VertexBufferLayout {
510 array_stride: std::mem::size_of::<BlurInstance>() as u64,
511 step_mode: wgpu::VertexStepMode::Instance,
512 attributes: &[
513 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
514 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32x4 },
515 wgpu::VertexAttribute { shader_location: 2, offset: 32, format: wgpu::VertexFormat::Float32x4 },
516 wgpu::VertexAttribute { shader_location: 3, offset: 48, format: wgpu::VertexFormat::Float32x2 },
517 ],
518 }],
519 compilation_options: wgpu::PipelineCompilationOptions::default(),
520 },
521 fragment: Some(wgpu::FragmentState {
522 module: &blur_shader,
523 entry_point: Some("fs_main"),
524 targets: &[Some(wgpu::ColorTargetState {
525 format,
526 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
527 write_mask: wgpu::ColorWrites::ALL,
528 })],
529 compilation_options: wgpu::PipelineCompilationOptions::default(),
530 }),
531 primitive: wgpu::PrimitiveState::default(),
532 depth_stencil: Some(stencil_for_content.clone()),
533 multisample: msaa_state,
534 multiview_mask: None,
535 cache: None,
536 });
537
538 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
540 label: Some("image_nv12.wgsl"),
541 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
542 "shaders/image_nv12.wgsl"
543 ))),
544 });
545 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
546 label: Some("image nv12 pipeline layout"),
547 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
548 immediate_size: 0,
549 });
550 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
551 label: Some("image nv12 pipeline"),
552 layout: Some(&image_nv12_layout),
553 vertex: wgpu::VertexState {
554 module: &image_nv12_shader,
555 entry_point: Some("vs_main"),
556 buffers: &[wgpu::VertexBufferLayout {
557 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
558 step_mode: wgpu::VertexStepMode::Instance,
559 attributes: &[
560 wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x4 },
561 wgpu::VertexAttribute { shader_location: 1, offset: 16, format: wgpu::VertexFormat::Float32x4 },
562 wgpu::VertexAttribute { shader_location: 2, offset: 32, format: wgpu::VertexFormat::Float32x4 },
563 wgpu::VertexAttribute { shader_location: 3, offset: 48, format: wgpu::VertexFormat::Float32 },
564 ],
565 }],
566 compilation_options: wgpu::PipelineCompilationOptions::default(),
567 },
568 fragment: Some(wgpu::FragmentState {
569 module: &image_nv12_shader,
570 entry_point: Some("fs_main"),
571 targets: &[Some(wgpu::ColorTargetState {
572 format,
573 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
574 write_mask: wgpu::ColorWrites::ALL,
575 })],
576 compilation_options: wgpu::PipelineCompilationOptions::default(),
577 }),
578 primitive: wgpu::PrimitiveState::default(),
579 depth_stencil: Some(stencil_for_content.clone()),
580 multisample: msaa_state,
581 multiview_mask: None,
582 cache: None,
583 });
584
585 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
587 label: Some("clip_round_rect_a2c.wgsl"),
588 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
589 "shaders/clip_round_rect_a2c.wgsl"
590 ))),
591 });
592 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
593 label: Some("clip_round_rect_bin.wgsl"),
594 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
595 "shaders/clip_round_rect_bin.wgsl"
596 ))),
597 });
598 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
599 label: Some("clip pipeline (a2c)"),
600 layout: Some(clip_pipeline_layout),
601 vertex: wgpu::VertexState {
602 module: &clip_shader_a2c,
603 entry_point: Some("vs_main"),
604 buffers: &[clip_vertex_layout.clone()],
605 compilation_options: wgpu::PipelineCompilationOptions::default(),
606 },
607 fragment: Some(wgpu::FragmentState {
608 module: &clip_shader_a2c,
609 entry_point: Some("fs_main"),
610 targets: &[Some(clip_color_target.clone())],
611 compilation_options: wgpu::PipelineCompilationOptions::default(),
612 }),
613 primitive: wgpu::PrimitiveState::default(),
614 depth_stencil: Some(stencil_for_clip_inc.clone()),
615 multisample: wgpu::MultisampleState {
616 count: sample_count,
617 mask: !0,
618 alpha_to_coverage_enabled: sample_count > 1,
619 },
620 multiview_mask: None,
621 cache: None,
622 });
623 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
624 label: Some("clip pipeline (bin)"),
625 layout: Some(clip_pipeline_layout),
626 vertex: wgpu::VertexState {
627 module: &clip_shader_bin,
628 entry_point: Some("vs_main"),
629 buffers: &[clip_vertex_layout.clone()],
630 compilation_options: wgpu::PipelineCompilationOptions::default(),
631 },
632 fragment: Some(wgpu::FragmentState {
633 module: &clip_shader_bin,
634 entry_point: Some("fs_main"),
635 targets: &[Some(clip_color_target.clone())],
636 compilation_options: wgpu::PipelineCompilationOptions::default(),
637 }),
638 primitive: wgpu::PrimitiveState::default(),
639 depth_stencil: Some(stencil_for_clip_inc.clone()),
640 multisample: wgpu::MultisampleState {
641 count: sample_count,
642 mask: !0,
643 alpha_to_coverage_enabled: false,
644 },
645 multiview_mask: None,
646 cache: None,
647 });
648
649 Self {
650 rects,
651 borders,
652 ellipses,
653 ellipse_borders,
654 text_mask,
655 text_color,
656 image_rgba,
657 image_nv12,
658 blur,
659 clip_a2c,
660 clip_bin,
661 }
662 }
663}
664
665struct Pass {
667 target: PassTarget,
668 initial_scissor: (u32, u32, u32, u32),
670 clear_color: Option<[f32; 4]>,
673 cmds: Vec<Cmd>,
674}
675
676#[allow(non_snake_case)]
677enum Cmd {
678 ClipPush {
679 off: u64,
680 cnt: u32,
681 scissor: (u32, u32, u32, u32),
682 },
683 ClipPop {
684 scissor: (u32, u32, u32, u32),
685 },
686 Rect {
687 off: u64,
688 cnt: u32,
689 },
690 Border {
691 off: u64,
692 cnt: u32,
693 },
694 Ellipse {
695 off: u64,
696 cnt: u32,
697 },
698 EllipseBorder {
699 off: u64,
700 cnt: u32,
701 },
702 GlyphsMask {
703 off: u64,
704 cnt: u32,
705 },
706 GlyphsColor {
707 off: u64,
708 cnt: u32,
709 },
710 ImageRgba {
711 off: u64,
712 cnt: u32,
713 handle: u64,
714 },
715 ImageNv12 {
716 off: u64,
717 cnt: u32,
718 handle: u64,
719 },
720 PushTransform(Transform),
721 PopTransform,
722 CompositeLayer {
726 off: u64,
727 cnt: u32,
728 layer_id: u32,
729 alpha: f32,
730 },
731 CompositeShadow {
735 off: u64,
736 cnt: u32,
737 layer_id: u32,
738 },
739}
740
741enum ImageTex {
742 Rgba {
743 tex: wgpu::Texture,
744 view: wgpu::TextureView,
745 bind: wgpu::BindGroup,
746 w: u32,
747 h: u32,
748 format: wgpu::TextureFormat,
749 last_used_frame: u64,
750 bytes: u64,
751 },
752 Nv12 {
753 tex_y: wgpu::Texture,
754 view_y: wgpu::TextureView,
755 tex_uv: wgpu::Texture,
756 view_uv: wgpu::TextureView,
757 bind: wgpu::BindGroup,
758 w: u32,
759 h: u32,
760 full_range: bool,
761 last_used_frame: u64,
762 bytes: u64,
763 },
764}
765
766struct AtlasA8 {
767 tex: wgpu::Texture,
768 view: wgpu::TextureView,
769 sampler: wgpu::Sampler,
770 size: u32,
771 next_x: u32,
772 next_y: u32,
773 row_h: u32,
774 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
775}
776
777struct AtlasRGBA {
778 tex: wgpu::Texture,
779 view: wgpu::TextureView,
780 sampler: wgpu::Sampler,
781 size: u32,
782 next_x: u32,
783 next_y: u32,
784 row_h: u32,
785 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
786}
787
788#[derive(Clone, Copy)]
789struct GlyphInfo {
790 u0: f32,
791 v0: f32,
792 u1: f32,
793 v1: f32,
794 w: f32,
795 h: f32,
796 bearing_x: f32,
797 bearing_y: f32,
798 advance: f32,
799}
800
801#[repr(C)]
802#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
803struct RectInstance {
804 xywh: [f32; 4],
805 radius: f32,
806 brush_type: u32,
807 color0: [f32; 4],
808 color1: [f32; 4],
809 grad_start: [f32; 2],
810 grad_end: [f32; 2],
811}
812
813#[repr(C)]
814#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
815struct BorderInstance {
816 xywh: [f32; 4],
817 radius: f32,
818 stroke: f32,
819 color: [f32; 4],
820}
821
822#[repr(C)]
823#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
824struct EllipseInstance {
825 xywh: [f32; 4],
826 color: [f32; 4],
827}
828
829#[repr(C)]
830#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
831struct EllipseBorderInstance {
832 xywh: [f32; 4],
833 stroke: f32,
834 color: [f32; 4],
835}
836
837#[repr(C)]
838#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
839struct GlyphInstance {
840 xywh: [f32; 4],
841 uv: [f32; 4],
842 color: [f32; 4],
843}
844
845#[repr(C)]
846#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
847struct BlurInstance {
848 xywh: [f32; 4],
849 uv: [f32; 4],
850 color: [f32; 4],
851 blur_uv: [f32; 2],
852}
853
854#[repr(C)]
855#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
856struct Nv12Instance {
857 xywh: [f32; 4],
858 uv: [f32; 4],
859 color: [f32; 4], full_range: f32,
861 _pad: [f32; 3],
862}
863
864#[repr(C)]
865#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
866struct ClipInstance {
867 xywh: [f32; 4],
868 radius: f32,
869 _pad: [f32; 3],
870}
871
872fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
873 match content {
874 cosmic_text::SwashContent::Mask => Some(data.to_vec()),
875 cosmic_text::SwashContent::SubpixelMask => {
876 let mut out = Vec::with_capacity(data.len() / 4);
877 for px in data.chunks_exact(4) {
878 let r = px[0];
879 let g = px[1];
880 let b = px[2];
881 out.push(r.max(g).max(b));
882 }
883 Some(out)
884 }
885 cosmic_text::SwashContent::Color => None,
886 }
887}
888
889impl WgpuBackend {
890 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
891 let instance: Instance;
892
893 if cfg!(target_arch = "wasm32") {
894 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
895 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
896 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
897 } else {
898 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
899 };
900
901 let surface = instance.create_surface(window.clone())?;
902
903 let adapter = instance
904 .request_adapter(&wgpu::RequestAdapterOptions {
905 power_preference: wgpu::PowerPreference::HighPerformance,
906 compatible_surface: Some(&surface),
907 force_fallback_adapter: false,
908 })
909 .await
910 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
911
912 let limits = if cfg!(target_arch = "wasm32") {
913 wgpu::Limits::downlevel_webgl2_defaults()
914 } else {
915 wgpu::Limits::default()
916 };
917
918 let (device, queue) = adapter
919 .request_device(&wgpu::DeviceDescriptor {
920 label: Some("repose-rs device"),
921 required_features: wgpu::Features::empty(),
922 required_limits: limits,
923 experimental_features: wgpu::ExperimentalFeatures::disabled(),
924 memory_hints: wgpu::MemoryHints::default(),
925 trace: wgpu::Trace::Off,
926 })
927 .await
928 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
929
930 let size = window.inner_size();
931
932 let caps = surface.get_capabilities(&adapter);
933 let format = caps
934 .formats
935 .iter()
936 .copied()
937 .find(|f| f.is_srgb())
938 .unwrap_or(caps.formats[0]);
939 let present_mode = caps
940 .present_modes
941 .iter()
942 .copied()
943 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
944 .unwrap_or(wgpu::PresentMode::Fifo);
945 let alpha_mode = caps.alpha_modes[0];
946
947 let config = wgpu::SurfaceConfiguration {
948 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
949 format,
950 width: size.width.max(1),
951 height: size.height.max(1),
952 present_mode,
953 alpha_mode,
954 view_formats: vec![],
955 desired_maximum_frame_latency: 2,
956 };
957 surface.configure(&device, &config);
958
959 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
960 label: Some("globals layout"),
961 entries: &[wgpu::BindGroupLayoutEntry {
962 binding: 0,
963 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
964 ty: wgpu::BindingType::Buffer {
965 ty: wgpu::BufferBindingType::Uniform,
966 has_dynamic_offset: false,
967 min_binding_size: None,
968 },
969 count: None,
970 }],
971 });
972
973 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
974 label: Some("globals buf"),
975 size: std::mem::size_of::<Globals>() as u64,
976 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
977 mapped_at_creation: false,
978 });
979
980 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
981 label: Some("globals bind"),
982 layout: &globals_layout,
983 entries: &[wgpu::BindGroupEntry {
984 binding: 0,
985 resource: globals_buf.as_entire_binding(),
986 }],
987 });
988
989 let fmt_features = adapter.get_texture_format_features(format);
991 let msaa_samples = if fmt_features.flags.sample_count_supported(4)
992 && fmt_features
993 .flags
994 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
995 {
996 4
997 } else {
998 1
999 };
1000
1001 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1002
1003 let stencil_for_content = wgpu::DepthStencilState {
1004 format: ds_format,
1005 depth_write_enabled: Some(false),
1006 depth_compare: Some(wgpu::CompareFunction::Always),
1007 stencil: wgpu::StencilState {
1008 front: wgpu::StencilFaceState {
1009 compare: wgpu::CompareFunction::LessEqual,
1010 fail_op: wgpu::StencilOperation::Keep,
1011 depth_fail_op: wgpu::StencilOperation::Keep,
1012 pass_op: wgpu::StencilOperation::Keep,
1013 },
1014 back: wgpu::StencilFaceState {
1015 compare: wgpu::CompareFunction::LessEqual,
1016 fail_op: wgpu::StencilOperation::Keep,
1017 depth_fail_op: wgpu::StencilOperation::Keep,
1018 pass_op: wgpu::StencilOperation::Keep,
1019 },
1020 read_mask: 0xFF,
1021 write_mask: 0x00,
1022 },
1023 bias: wgpu::DepthBiasState::default(),
1024 };
1025
1026 let stencil_for_clip_inc = wgpu::DepthStencilState {
1027 format: ds_format,
1028 depth_write_enabled: Some(false),
1029 depth_compare: Some(wgpu::CompareFunction::Always),
1030 stencil: wgpu::StencilState {
1031 front: wgpu::StencilFaceState {
1032 compare: wgpu::CompareFunction::Equal,
1033 fail_op: wgpu::StencilOperation::Keep,
1034 depth_fail_op: wgpu::StencilOperation::Keep,
1035 pass_op: wgpu::StencilOperation::IncrementClamp,
1036 },
1037 back: wgpu::StencilFaceState {
1038 compare: wgpu::CompareFunction::Equal,
1039 fail_op: wgpu::StencilOperation::Keep,
1040 depth_fail_op: wgpu::StencilOperation::Keep,
1041 pass_op: wgpu::StencilOperation::IncrementClamp,
1042 },
1043 read_mask: 0xFF,
1044 write_mask: 0xFF,
1045 },
1046 bias: wgpu::DepthBiasState::default(),
1047 };
1048
1049 let multisample_state = wgpu::MultisampleState {
1050 count: msaa_samples,
1051 mask: !0,
1052 alpha_to_coverage_enabled: false,
1053 };
1054
1055 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1059 label: Some("image/text sampler"),
1060 address_mode_u: wgpu::AddressMode::ClampToEdge,
1061 address_mode_v: wgpu::AddressMode::ClampToEdge,
1062 mag_filter: wgpu::FilterMode::Linear,
1063 min_filter: wgpu::FilterMode::Linear,
1064 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1065 ..Default::default()
1066 });
1067
1068 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1070 label: Some("text/rgba bind layout"),
1071 entries: &[
1072 wgpu::BindGroupLayoutEntry {
1073 binding: 0,
1074 visibility: wgpu::ShaderStages::FRAGMENT,
1075 ty: wgpu::BindingType::Texture {
1076 multisampled: false,
1077 view_dimension: wgpu::TextureViewDimension::D2,
1078 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1079 },
1080 count: None,
1081 },
1082 wgpu::BindGroupLayoutEntry {
1083 binding: 1,
1084 visibility: wgpu::ShaderStages::FRAGMENT,
1085 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1086 count: None,
1087 },
1088 ],
1089 });
1090 let image_bind_layout_rgba = text_bind_layout.clone();
1092
1093 let image_bind_layout_nv12 =
1095 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1096 label: Some("image bind layout nv12"),
1097 entries: &[
1098 wgpu::BindGroupLayoutEntry {
1100 binding: 0,
1101 visibility: wgpu::ShaderStages::FRAGMENT,
1102 ty: wgpu::BindingType::Texture {
1103 multisampled: false,
1104 view_dimension: wgpu::TextureViewDimension::D2,
1105 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1106 },
1107 count: None,
1108 },
1109 wgpu::BindGroupLayoutEntry {
1111 binding: 1,
1112 visibility: wgpu::ShaderStages::FRAGMENT,
1113 ty: wgpu::BindingType::Texture {
1114 multisampled: false,
1115 view_dimension: wgpu::TextureViewDimension::D2,
1116 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1117 },
1118 count: None,
1119 },
1120 wgpu::BindGroupLayoutEntry {
1122 binding: 2,
1123 visibility: wgpu::ShaderStages::FRAGMENT,
1124 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1125 count: None,
1126 },
1127 ],
1128 });
1129
1130 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1132 label: Some("clip pipeline layout"),
1133 bind_group_layouts: &[Some(&globals_layout)],
1134 immediate_size: 0,
1135 });
1136 let clip_vertex_layout = wgpu::VertexBufferLayout {
1137 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1138 step_mode: wgpu::VertexStepMode::Instance,
1139 attributes: &[
1140 wgpu::VertexAttribute {
1141 shader_location: 0,
1142 offset: 0,
1143 format: wgpu::VertexFormat::Float32x4,
1144 },
1145 wgpu::VertexAttribute {
1146 shader_location: 1,
1147 offset: 16,
1148 format: wgpu::VertexFormat::Float32,
1149 },
1150 ],
1151 };
1152 let clip_color_target = wgpu::ColorTargetState {
1153 format: config.format,
1154 blend: None,
1155 write_mask: wgpu::ColorWrites::empty(),
1156 };
1157
1158 let surface_pipes = Pipelines::create(
1161 &device,
1162 config.format,
1163 msaa_samples,
1164 &globals_layout,
1165 &text_bind_layout,
1166 &image_bind_layout_nv12,
1167 &clip_pipeline_layout,
1168 &stencil_for_content,
1169 &stencil_for_clip_inc,
1170 &clip_color_target,
1171 &clip_vertex_layout,
1172 );
1173 let layer_pipes = Pipelines::create(
1174 &device,
1175 config.format,
1176 1,
1177 &globals_layout,
1178 &text_bind_layout,
1179 &image_bind_layout_nv12,
1180 &clip_pipeline_layout,
1181 &stencil_for_content,
1182 &stencil_for_clip_inc,
1183 &clip_color_target,
1184 &clip_vertex_layout,
1185 );
1186
1187 let blur_ring = UploadRing::new(
1189 &device,
1190 "blur ring",
1191 1024 * 1024,
1192 );
1193
1194
1195 let atlas_mask = Self::init_atlas_mask(&device)?;
1197 let atlas_color = Self::init_atlas_color(&device)?;
1198
1199 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
1201 let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
1202 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
1203 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
1204 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
1205 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
1206 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
1207 let ring_nv12 = UploadRing::new(&device, "ring nv12", 1 << 20);
1208
1209 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1211 label: Some("temp ds"),
1212 size: wgpu::Extent3d {
1213 width: 1,
1214 height: 1,
1215 depth_or_array_layers: 1,
1216 },
1217 mip_level_count: 1,
1218 sample_count: 1,
1219 dimension: wgpu::TextureDimension::D2,
1220 format: wgpu::TextureFormat::Depth24PlusStencil8,
1221 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1222 view_formats: &[],
1223 });
1224 let depth_stencil_view =
1225 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1226
1227 let mut backend = Self {
1228 surface,
1229 device,
1230 queue,
1231 config,
1232
1233 surface_pipes,
1234 layer_pipes,
1235
1236 rects: InstancedPipe::new(ring_rect),
1237 borders: InstancedPipe::new(ring_border),
1238 ellipses: InstancedPipe::new(ring_ellipse),
1239 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1240 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1241 glyph_color: InstancedPipe::new(ring_glyph_color),
1242
1243 text_bind_layout,
1244
1245 image_bind_layout_rgba,
1246 image_bind_layout_nv12,
1247 image_sampler,
1248
1249 blur_ring,
1250
1251 clip_ring: ring_clip,
1252
1253 nv12: InstancedPipe::new(ring_nv12),
1254
1255 msaa_samples,
1256 depth_stencil_tex,
1257 depth_stencil_view,
1258 msaa_tex: None,
1259 msaa_view: None,
1260 globals_bind,
1261 globals_buf,
1262 globals_layout,
1263
1264 atlas_mask,
1265 atlas_color,
1266
1267 next_image_handle: 1,
1268 images: HashMap::new(),
1269
1270 frame_index: 0,
1271 image_bytes_total: 0,
1272 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
1275 };
1276
1277 backend.recreate_msaa_and_depth_stencil();
1278 Ok(backend)
1279 }
1280
1281 #[cfg(not(target_arch = "wasm32"))]
1282 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1283 pollster::block_on(Self::new_async(window))
1284 }
1285
1286 #[cfg(target_arch = "wasm32")]
1287 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1288 anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
1289 }
1290
1291 pub fn set_image_from_bytes(
1294 &mut self,
1295 handle: u64,
1296 data: &[u8],
1297 srgb: bool,
1298 ) -> anyhow::Result<()> {
1299 let img = image::load_from_memory(data)?;
1300 let rgba = img.to_rgba8();
1301 let (w, h) = rgba.dimensions();
1302 self.set_image_rgba8(handle, w, h, &rgba, srgb)
1303 }
1304
1305 pub fn set_image_rgba8(
1306 &mut self,
1307 handle: u64,
1308 w: u32,
1309 h: u32,
1310 rgba: &[u8],
1311 srgb: bool,
1312 ) -> anyhow::Result<()> {
1313 let expected = (w as usize) * (h as usize) * 4;
1314 if rgba.len() < expected {
1315 return Err(anyhow::anyhow!(
1316 "RGBA buffer too small: {} < {}",
1317 rgba.len(),
1318 expected
1319 ));
1320 }
1321
1322 let format = if srgb {
1323 wgpu::TextureFormat::Rgba8UnormSrgb
1324 } else {
1325 wgpu::TextureFormat::Rgba8Unorm
1326 };
1327
1328 let needs_recreate = match self.images.get(&handle) {
1329 Some(ImageTex::Rgba {
1330 w: cw,
1331 h: ch,
1332 format: cf,
1333 ..
1334 }) => *cw != w || *ch != h || *cf != format,
1335 _ => true,
1336 };
1337
1338 if needs_recreate {
1339 self.remove_image(handle);
1341
1342 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1343 label: Some("user image rgba"),
1344 size: wgpu::Extent3d {
1345 width: w,
1346 height: h,
1347 depth_or_array_layers: 1,
1348 },
1349 mip_level_count: 1,
1350 sample_count: 1,
1351 dimension: wgpu::TextureDimension::D2,
1352 format,
1353 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1354 view_formats: &[],
1355 });
1356 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1357
1358 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1359 label: Some("image bind rgba"),
1360 layout: &self.image_bind_layout_rgba,
1361 entries: &[
1362 wgpu::BindGroupEntry {
1363 binding: 0,
1364 resource: wgpu::BindingResource::TextureView(&view),
1365 },
1366 wgpu::BindGroupEntry {
1367 binding: 1,
1368 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1369 },
1370 ],
1371 });
1372
1373 let bytes = (w as u64) * (h as u64) * 4;
1374 self.image_bytes_total += bytes;
1375
1376 self.images.insert(
1377 handle,
1378 ImageTex::Rgba {
1379 tex,
1380 view,
1381 bind,
1382 w,
1383 h,
1384 format,
1385 last_used_frame: self.frame_index,
1386 bytes,
1387 },
1388 );
1389 }
1390
1391 let tex = match self.images.get(&handle) {
1392 Some(ImageTex::Rgba { tex, .. }) => tex,
1393 _ => unreachable!(),
1394 };
1395
1396 self.queue.write_texture(
1397 wgpu::TexelCopyTextureInfo {
1398 texture: tex,
1399 mip_level: 0,
1400 origin: wgpu::Origin3d::ZERO,
1401 aspect: wgpu::TextureAspect::All,
1402 },
1403 &rgba[..expected],
1404 wgpu::TexelCopyBufferLayout {
1405 offset: 0,
1406 bytes_per_row: Some(4 * w),
1407 rows_per_image: Some(h),
1408 },
1409 wgpu::Extent3d {
1410 width: w,
1411 height: h,
1412 depth_or_array_layers: 1,
1413 },
1414 );
1415
1416 self.evict_budget_excess();
1418
1419 Ok(())
1420 }
1421
1422 pub fn set_image_nv12(
1423 &mut self,
1424 handle: u64,
1425 w: u32,
1426 h: u32,
1427 y: &[u8],
1428 uv: &[u8],
1429 full_range: bool,
1430 ) -> anyhow::Result<()> {
1431 let y_expected = (w as usize) * (h as usize);
1432 let uv_w = (w / 2).max(1);
1433 let uv_h = (h / 2).max(1);
1434 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
1435
1436 if y.len() < y_expected {
1437 return Err(anyhow::anyhow!("Y plane too small"));
1438 }
1439 if uv.len() < uv_expected {
1440 return Err(anyhow::anyhow!("UV plane too small"));
1441 }
1442
1443 let needs_recreate = match self.images.get(&handle) {
1444 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
1445 _ => true,
1446 };
1447
1448 if needs_recreate {
1449 self.remove_image(handle);
1450
1451 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
1452 label: Some("nv12 Y"),
1453 size: wgpu::Extent3d {
1454 width: w,
1455 height: h,
1456 depth_or_array_layers: 1,
1457 },
1458 mip_level_count: 1,
1459 sample_count: 1,
1460 dimension: wgpu::TextureDimension::D2,
1461 format: wgpu::TextureFormat::R8Unorm,
1462 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1463 view_formats: &[],
1464 });
1465 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
1466
1467 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
1468 label: Some("nv12 UV"),
1469 size: wgpu::Extent3d {
1470 width: uv_w,
1471 height: uv_h,
1472 depth_or_array_layers: 1,
1473 },
1474 mip_level_count: 1,
1475 sample_count: 1,
1476 dimension: wgpu::TextureDimension::D2,
1477 format: wgpu::TextureFormat::Rg8Unorm,
1478 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1479 view_formats: &[],
1480 });
1481 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
1482
1483 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1484 label: Some("nv12 bind"),
1485 layout: &self.image_bind_layout_nv12,
1486 entries: &[
1487 wgpu::BindGroupEntry {
1488 binding: 0,
1489 resource: wgpu::BindingResource::TextureView(&view_y),
1490 },
1491 wgpu::BindGroupEntry {
1492 binding: 1,
1493 resource: wgpu::BindingResource::TextureView(&view_uv),
1494 },
1495 wgpu::BindGroupEntry {
1496 binding: 2,
1497 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1498 },
1499 ],
1500 });
1501
1502 let bytes = (w as u64) * (h as u64) + (uv_w as u64) * (uv_h as u64) * 2;
1503 self.image_bytes_total += bytes;
1504
1505 self.images.insert(
1506 handle,
1507 ImageTex::Nv12 {
1508 tex_y,
1509 view_y,
1510 tex_uv,
1511 view_uv,
1512 bind,
1513 w,
1514 h,
1515 full_range,
1516 last_used_frame: self.frame_index,
1517 bytes,
1518 },
1519 );
1520 }
1521
1522 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
1523 Some(ImageTex::Nv12 {
1524 tex_y,
1525 tex_uv,
1526 bind,
1527 ..
1528 }) => (tex_y, tex_uv, bind),
1529 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
1530 };
1531
1532 self.queue.write_texture(
1533 wgpu::TexelCopyTextureInfo {
1534 texture: tex_y,
1535 mip_level: 0,
1536 origin: wgpu::Origin3d::ZERO,
1537 aspect: wgpu::TextureAspect::All,
1538 },
1539 &y[..y_expected],
1540 wgpu::TexelCopyBufferLayout {
1541 offset: 0,
1542 bytes_per_row: Some(w),
1543 rows_per_image: Some(h),
1544 },
1545 wgpu::Extent3d {
1546 width: w,
1547 height: h,
1548 depth_or_array_layers: 1,
1549 },
1550 );
1551
1552 self.queue.write_texture(
1553 wgpu::TexelCopyTextureInfo {
1554 texture: tex_uv,
1555 mip_level: 0,
1556 origin: wgpu::Origin3d::ZERO,
1557 aspect: wgpu::TextureAspect::All,
1558 },
1559 &uv[..uv_expected],
1560 wgpu::TexelCopyBufferLayout {
1561 offset: 0,
1562 bytes_per_row: Some(2 * uv_w),
1563 rows_per_image: Some(uv_h),
1564 },
1565 wgpu::Extent3d {
1566 width: uv_w,
1567 height: uv_h,
1568 depth_or_array_layers: 1,
1569 },
1570 );
1571
1572 self.evict_budget_excess();
1573 Ok(())
1574 }
1575
1576 pub fn remove_image(&mut self, handle: u64) {
1577 if let Some(img) = self.images.remove(&handle) {
1578 let b = match img {
1579 ImageTex::Rgba { bytes, .. } => bytes,
1580 ImageTex::Nv12 { bytes, .. } => bytes,
1581 };
1582 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
1583 }
1584 }
1585
1586 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
1588 let handle = self.next_image_handle;
1589 self.next_image_handle += 1;
1590 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
1591 log::error!("Failed to register image: {e}");
1592 }
1593 handle
1594 }
1595
1596 fn evict_unused_images(&mut self) {
1597 let now = self.frame_index;
1598 let evict_after = self.image_evict_after_frames;
1599
1600 let mut to_remove = Vec::new();
1602 for (h, t) in self.images.iter() {
1603 let last = match t {
1604 ImageTex::Rgba {
1605 last_used_frame, ..
1606 } => *last_used_frame,
1607 ImageTex::Nv12 {
1608 last_used_frame, ..
1609 } => *last_used_frame,
1610 };
1611 if now.saturating_sub(last) > evict_after {
1612 to_remove.push(*h);
1613 }
1614 }
1615 for h in to_remove {
1616 self.remove_image(h);
1617 }
1618
1619 self.evict_budget_excess();
1620 }
1621
1622 fn evict_budget_excess(&mut self) {
1623 if self.image_bytes_total <= self.image_budget_bytes {
1624 return;
1625 }
1626 let mut candidates: Vec<(u64, u64, u64)> = self
1628 .images
1629 .iter()
1630 .map(|(h, t)| {
1631 let (last, bytes) = match t {
1632 ImageTex::Rgba {
1633 last_used_frame,
1634 bytes,
1635 ..
1636 } => (*last_used_frame, *bytes),
1637 ImageTex::Nv12 {
1638 last_used_frame,
1639 bytes,
1640 ..
1641 } => (*last_used_frame, *bytes),
1642 };
1643 (*h, last, bytes)
1644 })
1645 .collect();
1646
1647 candidates.sort_by_key(|k| k.1);
1649
1650 let now = self.frame_index;
1651 for (h, last, _bytes) in candidates {
1652 if self.image_bytes_total <= self.image_budget_bytes {
1653 break;
1654 }
1655 if last == now {
1657 continue;
1658 }
1659 self.remove_image(h);
1660 }
1661 }
1662
1663 fn recreate_msaa_and_depth_stencil(&mut self) {
1664 if self.msaa_samples > 1 {
1665 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1666 label: Some("msaa color"),
1667 size: wgpu::Extent3d {
1668 width: self.config.width.max(1),
1669 height: self.config.height.max(1),
1670 depth_or_array_layers: 1,
1671 },
1672 mip_level_count: 1,
1673 sample_count: self.msaa_samples,
1674 dimension: wgpu::TextureDimension::D2,
1675 format: self.config.format,
1676 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1677 view_formats: &[],
1678 });
1679 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1680 self.msaa_tex = Some(tex);
1681 self.msaa_view = Some(view);
1682 } else {
1683 self.msaa_tex = None;
1684 self.msaa_view = None;
1685 }
1686
1687 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1688 label: Some("depth-stencil (stencil clips)"),
1689 size: wgpu::Extent3d {
1690 width: self.config.width.max(1),
1691 height: self.config.height.max(1),
1692 depth_or_array_layers: 1,
1693 },
1694 mip_level_count: 1,
1695 sample_count: self.msaa_samples,
1696 dimension: wgpu::TextureDimension::D2,
1697 format: wgpu::TextureFormat::Depth24PlusStencil8,
1698 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1699 view_formats: &[],
1700 });
1701 self.depth_stencil_view = self
1702 .depth_stencil_tex
1703 .create_view(&wgpu::TextureViewDescriptor::default());
1704 }
1705
1706 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1707 let size = 1024u32;
1708 let tex = device.create_texture(&wgpu::TextureDescriptor {
1709 label: Some("glyph atlas A8"),
1710 size: wgpu::Extent3d {
1711 width: size,
1712 height: size,
1713 depth_or_array_layers: 1,
1714 },
1715 mip_level_count: 1,
1716 sample_count: 1,
1717 dimension: wgpu::TextureDimension::D2,
1718 format: wgpu::TextureFormat::R8Unorm,
1719 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1720 view_formats: &[],
1721 });
1722 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1723 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1724 label: Some("glyph atlas sampler A8"),
1725 address_mode_u: wgpu::AddressMode::ClampToEdge,
1726 address_mode_v: wgpu::AddressMode::ClampToEdge,
1727 address_mode_w: wgpu::AddressMode::ClampToEdge,
1728 mag_filter: wgpu::FilterMode::Linear,
1729 min_filter: wgpu::FilterMode::Linear,
1730 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1731 ..Default::default()
1732 });
1733
1734 Ok(AtlasA8 {
1735 tex,
1736 view,
1737 sampler,
1738 size,
1739 next_x: 1,
1740 next_y: 1,
1741 row_h: 0,
1742 map: HashMap::new(),
1743 })
1744 }
1745
1746 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1747 let size = 1024u32;
1748 let tex = device.create_texture(&wgpu::TextureDescriptor {
1749 label: Some("glyph atlas RGBA"),
1750 size: wgpu::Extent3d {
1751 width: size,
1752 height: size,
1753 depth_or_array_layers: 1,
1754 },
1755 mip_level_count: 1,
1756 sample_count: 1,
1757 dimension: wgpu::TextureDimension::D2,
1758 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1759 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1760 view_formats: &[],
1761 });
1762 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1763 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1764 label: Some("glyph atlas sampler RGBA"),
1765 address_mode_u: wgpu::AddressMode::ClampToEdge,
1766 address_mode_v: wgpu::AddressMode::ClampToEdge,
1767 address_mode_w: wgpu::AddressMode::ClampToEdge,
1768 mag_filter: wgpu::FilterMode::Linear,
1769 min_filter: wgpu::FilterMode::Linear,
1770 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1771 ..Default::default()
1772 });
1773 Ok(AtlasRGBA {
1774 tex,
1775 view,
1776 sampler,
1777 size,
1778 next_x: 1,
1779 next_y: 1,
1780 row_h: 0,
1781 map: HashMap::new(),
1782 })
1783 }
1784
1785 fn get_or_create_layer(
1786 &mut self,
1787 layer_id: u32,
1788 width: u32,
1789 height: u32,
1790 rect: repose_core::Rect,
1791 ) {
1792 let needs_alloc = match self.layer_pool.get(&layer_id) {
1793 Some(lt) => lt.width != width || lt.height != height,
1794 None => true,
1795 };
1796 if !needs_alloc {
1797 return;
1798 }
1799 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1800 label: Some("graphics layer"),
1801 size: wgpu::Extent3d {
1802 width: width.max(1),
1803 height: height.max(1),
1804 depth_or_array_layers: 1,
1805 },
1806 mip_level_count: 1,
1807 sample_count: 1,
1808 dimension: wgpu::TextureDimension::D2,
1809 format: self.config.format,
1810 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1811 view_formats: &[],
1812 });
1813 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1814 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1815 label: Some("layer bind"),
1816 layout: &self.image_bind_layout_rgba,
1817 entries: &[
1818 wgpu::BindGroupEntry {
1819 binding: 0,
1820 resource: wgpu::BindingResource::TextureView(&view),
1821 },
1822 wgpu::BindGroupEntry {
1823 binding: 1,
1824 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1825 },
1826 ],
1827 });
1828 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1829 label: Some("graphics layer depth-stencil"),
1830 size: wgpu::Extent3d {
1831 width: width.max(1),
1832 height: height.max(1),
1833 depth_or_array_layers: 1,
1834 },
1835 mip_level_count: 1,
1836 sample_count: 1,
1837 dimension: wgpu::TextureDimension::D2,
1838 format: wgpu::TextureFormat::Depth24PlusStencil8,
1839 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1840 view_formats: &[],
1841 });
1842 let depth_stencil_view = depth_stencil_tex
1843 .create_view(&wgpu::TextureViewDescriptor::default());
1844 self.layer_pool.insert(
1845 layer_id,
1846 LayerTarget {
1847 texture: tex,
1848 view,
1849 bind,
1850 depth_stencil_tex,
1851 depth_stencil_view,
1852 width,
1853 height,
1854 rect_px: (rect.x, rect.y, rect.w, rect.h),
1855 },
1856 );
1857 }
1858
1859 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
1860 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1861 label: Some("atlas bind"),
1862 layout: &self.text_bind_layout,
1863 entries: &[
1864 wgpu::BindGroupEntry {
1865 binding: 0,
1866 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
1867 },
1868 wgpu::BindGroupEntry {
1869 binding: 1,
1870 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
1871 },
1872 ],
1873 })
1874 }
1875
1876 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
1877 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1878 label: Some("atlas bind color"),
1879 layout: &self.text_bind_layout,
1880 entries: &[
1881 wgpu::BindGroupEntry {
1882 binding: 0,
1883 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
1884 },
1885 wgpu::BindGroupEntry {
1886 binding: 1,
1887 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1888 },
1889 ],
1890 })
1891 }
1892
1893 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1894 let keyp = (key, px);
1895 if let Some(info) = self.atlas_mask.map.get(&keyp) {
1896 return Some(*info);
1897 }
1898
1899 let gb = repose_text::rasterize(key, px as f32)?;
1900 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
1901 return None;
1902 }
1903
1904 let coverage = match swash_to_a8_coverage(gb.content, &gb.data) {
1905 Some(c) => c,
1906 None => return None,
1907 };
1908
1909 let w = gb.w.max(1);
1910 let h = gb.h.max(1);
1911
1912 if !self.alloc_space_mask(w, h) {
1913 self.grow_mask_and_rebuild();
1914 }
1915 if !self.alloc_space_mask(w, h) {
1916 return None;
1917 }
1918 let x = self.atlas_mask.next_x;
1919 let y = self.atlas_mask.next_y;
1920 self.atlas_mask.next_x += w + 1;
1921 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
1922
1923 let layout = wgpu::TexelCopyBufferLayout {
1924 offset: 0,
1925 bytes_per_row: Some(w),
1926 rows_per_image: Some(h),
1927 };
1928 let size = wgpu::Extent3d {
1929 width: w,
1930 height: h,
1931 depth_or_array_layers: 1,
1932 };
1933 self.queue.write_texture(
1934 wgpu::TexelCopyTextureInfoBase {
1935 texture: &self.atlas_mask.tex,
1936 mip_level: 0,
1937 origin: wgpu::Origin3d { x, y, z: 0 },
1938 aspect: wgpu::TextureAspect::All,
1939 },
1940 &coverage,
1941 layout,
1942 size,
1943 );
1944
1945 let info = GlyphInfo {
1946 u0: x as f32 / self.atlas_mask.size as f32,
1947 v0: y as f32 / self.atlas_mask.size as f32,
1948 u1: (x + w) as f32 / self.atlas_mask.size as f32,
1949 v1: (y + h) as f32 / self.atlas_mask.size as f32,
1950 w: w as f32,
1951 h: h as f32,
1952 bearing_x: 0.0,
1953 bearing_y: 0.0,
1954 advance: 0.0,
1955 };
1956 self.atlas_mask.map.insert(keyp, info);
1957 Some(info)
1958 }
1959
1960 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1961 let keyp = (key, px);
1962 if let Some(info) = self.atlas_color.map.get(&keyp) {
1963 return Some(*info);
1964 }
1965 let gb = repose_text::rasterize(key, px as f32)?;
1966 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
1967 return None;
1968 }
1969 let w = gb.w.max(1);
1970 let h = gb.h.max(1);
1971 if !self.alloc_space_color(w, h) {
1972 self.grow_color_and_rebuild();
1973 }
1974 if !self.alloc_space_color(w, h) {
1975 return None;
1976 }
1977 let x = self.atlas_color.next_x;
1978 let y = self.atlas_color.next_y;
1979 self.atlas_color.next_x += w + 1;
1980 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
1981
1982 let layout = wgpu::TexelCopyBufferLayout {
1983 offset: 0,
1984 bytes_per_row: Some(w * 4),
1985 rows_per_image: Some(h),
1986 };
1987 let size = wgpu::Extent3d {
1988 width: w,
1989 height: h,
1990 depth_or_array_layers: 1,
1991 };
1992 self.queue.write_texture(
1993 wgpu::TexelCopyTextureInfoBase {
1994 texture: &self.atlas_color.tex,
1995 mip_level: 0,
1996 origin: wgpu::Origin3d { x, y, z: 0 },
1997 aspect: wgpu::TextureAspect::All,
1998 },
1999 &gb.data,
2000 layout,
2001 size,
2002 );
2003 let info = GlyphInfo {
2004 u0: x as f32 / self.atlas_color.size as f32,
2005 v0: y as f32 / self.atlas_color.size as f32,
2006 u1: (x + w) as f32 / self.atlas_color.size as f32,
2007 v1: (y + h) as f32 / self.atlas_color.size as f32,
2008 w: w as f32,
2009 h: h as f32,
2010 bearing_x: 0.0,
2011 bearing_y: 0.0,
2012 advance: 0.0,
2013 };
2014 self.atlas_color.map.insert(keyp, info);
2015 Some(info)
2016 }
2017
2018 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
2019 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
2020 self.atlas_mask.next_x = 1;
2021 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
2022 self.atlas_mask.row_h = 0;
2023 }
2024 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
2025 return false;
2026 }
2027 true
2028 }
2029
2030 fn grow_mask_and_rebuild(&mut self) {
2031 let new_size = (self.atlas_mask.size * 2).min(4096);
2032 if new_size == self.atlas_mask.size {
2033 return;
2034 }
2035 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2036 label: Some("glyph atlas A8 (grown)"),
2037 size: wgpu::Extent3d {
2038 width: new_size,
2039 height: new_size,
2040 depth_or_array_layers: 1,
2041 },
2042 mip_level_count: 1,
2043 sample_count: 1,
2044 dimension: wgpu::TextureDimension::D2,
2045 format: wgpu::TextureFormat::R8Unorm,
2046 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2047 view_formats: &[],
2048 });
2049 self.atlas_mask.tex = tex;
2050 self.atlas_mask.view = self
2051 .atlas_mask
2052 .tex
2053 .create_view(&wgpu::TextureViewDescriptor::default());
2054 self.atlas_mask.size = new_size;
2055 self.atlas_mask.next_x = 1;
2056 self.atlas_mask.next_y = 1;
2057 self.atlas_mask.row_h = 0;
2058 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
2059 self.atlas_mask.map.clear();
2060 for (k, px) in keys {
2061 let _ = self.upload_glyph_mask(k, px);
2062 }
2063 }
2064
2065 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
2066 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
2067 self.atlas_color.next_x = 1;
2068 self.atlas_color.next_y += self.atlas_color.row_h + 1;
2069 self.atlas_color.row_h = 0;
2070 }
2071 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
2072 return false;
2073 }
2074 true
2075 }
2076
2077 fn grow_color_and_rebuild(&mut self) {
2078 let new_size = (self.atlas_color.size * 2).min(4096);
2079 if new_size == self.atlas_color.size {
2080 return;
2081 }
2082 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2083 label: Some("glyph atlas RGBA (grown)"),
2084 size: wgpu::Extent3d {
2085 width: new_size,
2086 height: new_size,
2087 depth_or_array_layers: 1,
2088 },
2089 mip_level_count: 1,
2090 sample_count: 1,
2091 dimension: wgpu::TextureDimension::D2,
2092 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2093 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2094 view_formats: &[],
2095 });
2096 self.atlas_color.tex = tex;
2097 self.atlas_color.view = self
2098 .atlas_color
2099 .tex
2100 .create_view(&wgpu::TextureViewDescriptor::default());
2101 self.atlas_color.size = new_size;
2102 self.atlas_color.next_x = 1;
2103 self.atlas_color.next_y = 1;
2104 self.atlas_color.row_h = 0;
2105 let keys: Vec<(repose_text::GlyphKey, u32)> =
2106 self.atlas_color.map.keys().copied().collect();
2107 self.atlas_color.map.clear();
2108 for (k, px) in keys {
2109 let _ = self.upload_glyph_color(k, px);
2110 }
2111 }
2112}
2113
2114fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
2115 match brush {
2116 Brush::Solid(c) => (
2117 0u32,
2118 c.to_linear(),
2119 [0.0, 0.0, 0.0, 0.0],
2120 [0.0, 0.0],
2121 [0.0, 1.0],
2122 ),
2123 Brush::Linear {
2124 start,
2125 end,
2126 start_color,
2127 end_color,
2128 } => (
2129 1u32,
2130 start_color.to_linear(),
2131 end_color.to_linear(),
2132 [start.x, start.y],
2133 [end.x, end.y],
2134 ),
2135 }
2136}
2137
2138fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
2139 match brush {
2140 Brush::Solid(c) => c.to_linear(),
2141 Brush::Linear { start_color, .. } => start_color.to_linear(),
2142 }
2143}
2144
2145impl RenderBackend for WgpuBackend {
2146 fn configure_surface(&mut self, width: u32, height: u32) {
2147 if width == 0 || height == 0 {
2148 return;
2149 }
2150 self.config.width = width;
2151 self.config.height = height;
2152 self.surface.configure(&self.device, &self.config);
2153 self.recreate_msaa_and_depth_stencil();
2154 }
2155
2156 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
2157 self.frame_index = self.frame_index.wrapping_add(1);
2159
2160 if self.config.width == 0 || self.config.height == 0 {
2161 return;
2162 }
2163 let mut retries = 0u32;
2164 const MAX_RETRIES: u32 = 4;
2165 let frame = loop {
2166 match self.surface.get_current_texture() {
2167 wgpu::CurrentSurfaceTexture::Success(f) => break f,
2168 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
2169 log::warn!("suboptimal surface; reconfiguring");
2170 self.surface.configure(&self.device, &self.config);
2171 break f;
2172 }
2173 wgpu::CurrentSurfaceTexture::Outdated => {
2174 log::warn!("surface outdated; reconfiguring");
2175 self.surface.configure(&self.device, &self.config);
2176 }
2177 wgpu::CurrentSurfaceTexture::Lost => {
2178 log::warn!("surface lost; reconfiguring");
2179 self.surface.configure(&self.device, &self.config);
2180 }
2181 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
2182 return;
2183 }
2184 wgpu::CurrentSurfaceTexture::Validation => {
2185 retries += 1;
2186 if retries >= MAX_RETRIES {
2187 log::warn!("surface validation persisted after {MAX_RETRIES} retries; skipping frame");
2188 return;
2189 }
2190 self.surface.configure(&self.device, &self.config);
2191 }
2192 }
2193 };
2194
2195 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
2196 let x0 = (x / fb_w) * 2.0 - 1.0;
2197 let y0 = 1.0 - (y / fb_h) * 2.0;
2198 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
2199 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
2200 let min_x = x0.min(x1);
2201 let min_y = y0.min(y1);
2202 let w_ndc = (x1 - x0).abs();
2203 let h_ndc = (y1 - y0).abs();
2204 [min_x, min_y, w_ndc, h_ndc]
2205 }
2206
2207 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
2208 let mut x = r.x.floor() as i64;
2209 let mut y = r.y.floor() as i64;
2210 let fb_wi = fb_w as i64;
2211 let fb_hi = fb_h as i64;
2212 x = x.clamp(0, fb_wi.saturating_sub(1));
2213 y = y.clamp(0, fb_hi.saturating_sub(1));
2214 let w_req = r.w.ceil().max(1.0) as i64;
2215 let h_req = r.h.ceil().max(1.0) as i64;
2216 let w = (w_req).min(fb_wi - x).max(1);
2217 let h = (h_req).min(fb_hi - y).max(1);
2218 (x as u32, y as u32, w as u32, h as u32)
2219 }
2220
2221 let fb_w = self.config.width as f32;
2222 let fb_h = self.config.height as f32;
2223
2224 let globals = Globals {
2225 ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
2226 _pad: [0.0, 0.0],
2227 };
2228 self.queue
2229 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
2230
2231 let mut passes: Vec<Pass> = Vec::with_capacity(1);
2232 let mut current_pass: Pass = Pass {
2233 target: PassTarget::Surface,
2234 initial_scissor: (0, 0, self.config.width, self.config.height),
2235 clear_color: Some([
2236 scene.clear_color.0 as f32 / 255.0,
2237 scene.clear_color.1 as f32 / 255.0,
2238 scene.clear_color.2 as f32 / 255.0,
2239 scene.clear_color.3 as f32 / 255.0,
2240 ]),
2241 cmds: Vec::with_capacity(scene.nodes.len()),
2242 };
2243 let mut target_stack: Vec<PassTarget> = Vec::new();
2244 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
2245 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
2246
2247 struct Batch {
2248 rects: Vec<RectInstance>,
2249 borders: Vec<BorderInstance>,
2250 ellipses: Vec<EllipseInstance>,
2251 e_borders: Vec<EllipseBorderInstance>,
2252 masks: Vec<GlyphInstance>,
2253 colors: Vec<GlyphInstance>,
2254 nv12s: Vec<Nv12Instance>,
2255 }
2256
2257 impl Batch {
2258 fn new() -> Self {
2259 Self {
2260 rects: vec![],
2261 borders: vec![],
2262 ellipses: vec![],
2263 e_borders: vec![],
2264 masks: vec![],
2265 colors: vec![],
2266 nv12s: vec![],
2267 }
2268 }
2269
2270 fn is_empty(&self) -> bool {
2271 self.rects.is_empty()
2272 && self.borders.is_empty()
2273 && self.ellipses.is_empty()
2274 && self.e_borders.is_empty()
2275 && self.masks.is_empty()
2276 && self.colors.is_empty()
2277 && self.nv12s.is_empty()
2278 }
2279
2280 fn flush(
2281 &mut self,
2282 pipes: (
2283 &mut InstancedPipe<RectInstance>,
2284 &mut InstancedPipe<BorderInstance>,
2285 &mut InstancedPipe<EllipseInstance>,
2286 &mut InstancedPipe<EllipseBorderInstance>,
2287 ),
2288 glyph_pipes: (
2289 &mut InstancedPipe<GlyphInstance>,
2290 &mut InstancedPipe<GlyphInstance>,
2291 ),
2292 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
2293 device: &wgpu::Device,
2294 queue: &wgpu::Queue,
2295 cmds: &mut Vec<Cmd>,
2296 ) {
2297 let (rects, borders, ellipses, e_borders) = pipes;
2298 let (masks, colors) = glyph_pipes;
2299
2300 if !self.rects.is_empty() {
2301 if let Some((off, cnt)) = rects.upload(device, queue, &self.rects) {
2302 cmds.push(Cmd::Rect { off, cnt });
2303 }
2304 self.rects.clear();
2305 }
2306 if !self.borders.is_empty() {
2307 if let Some((off, cnt)) = borders.upload(device, queue, &self.borders) {
2308 cmds.push(Cmd::Border { off, cnt });
2309 }
2310 self.borders.clear();
2311 }
2312 if !self.ellipses.is_empty() {
2313 if let Some((off, cnt)) = ellipses.upload(device, queue, &self.ellipses) {
2314 cmds.push(Cmd::Ellipse { off, cnt });
2315 }
2316 self.ellipses.clear();
2317 }
2318 if !self.e_borders.is_empty() {
2319 if let Some((off, cnt)) = e_borders.upload(device, queue, &self.e_borders) {
2320 cmds.push(Cmd::EllipseBorder { off, cnt });
2321 }
2322 self.e_borders.clear();
2323 }
2324 if !self.masks.is_empty() {
2325 if let Some((off, cnt)) = masks.upload(device, queue, &self.masks) {
2326 cmds.push(Cmd::GlyphsMask { off, cnt });
2327 }
2328 self.masks.clear();
2329 }
2330 if !self.colors.is_empty() {
2331 if let Some((off, cnt)) = colors.upload(device, queue, &self.colors) {
2332 cmds.push(Cmd::GlyphsColor { off, cnt });
2333 }
2334 self.colors.clear();
2335 }
2336 if !self.nv12s.is_empty() {
2337 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
2338 let _ = (off, cnt);
2340 }
2341 self.nv12s.clear();
2342 }
2343 }
2344 }
2345
2346 self.rects.reset();
2347 self.borders.reset();
2348 self.ellipses.reset();
2349 self.ellipse_borders.reset();
2350 self.glyph_mask.reset();
2351 self.glyph_color.reset();
2352 self.clip_ring.reset();
2353 self.nv12.reset();
2354
2355 let mut batch = Batch::new();
2356 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
2357 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
2358 let root_clip_rect = repose_core::Rect {
2359 x: 0.0,
2360 y: 0.0,
2361 w: fb_w,
2362 h: fb_h,
2363 };
2364
2365 let mut current_prim: Option<&'static str> = None;
2366
2367 macro_rules! flush_if_prim_changed {
2368 ($prim:literal, $pipe:expr) => {
2369 if current_prim != Some($prim) {
2370 flush_batch!();
2371 current_prim = Some($prim);
2372 }
2373 };
2374 }
2375
2376 macro_rules! flush_batch {
2377 () => {
2378 if !batch.is_empty() {
2379 batch.flush(
2380 (
2381 &mut self.rects,
2382 &mut self.borders,
2383 &mut self.ellipses,
2384 &mut self.ellipse_borders,
2385 ),
2386 (&mut self.glyph_mask, &mut self.glyph_color),
2387 &mut self.nv12,
2388 &self.device,
2389 &self.queue,
2390 &mut current_pass.cmds,
2391 )
2392 }
2393 current_prim = None;
2394 };
2395 }
2396
2397 for node in &scene.nodes {
2398 let t_identity = Transform::identity();
2399 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2400
2401 match node {
2402 SceneNode::Rect {
2403 rect,
2404 brush,
2405 radius,
2406 } => {
2407 flush_if_prim_changed!("rect", &self.rects);
2408 let transformed_rect = current_transform.apply_to_rect(*rect);
2409 let (brush_type, color0, color1, grad_start, grad_end) =
2410 brush_to_instance_fields(brush);
2411 batch.rects.push(RectInstance {
2412 xywh: to_ndc(
2413 transformed_rect.x,
2414 transformed_rect.y,
2415 transformed_rect.w,
2416 transformed_rect.h,
2417 current_target_size.0,
2418 current_target_size.1,
2419 ),
2420 radius: *radius,
2421 brush_type,
2422 color0,
2423 color1,
2424 grad_start,
2425 grad_end,
2426 });
2427 }
2428 SceneNode::Border {
2429 rect,
2430 color,
2431 width,
2432 radius,
2433 } => {
2434 flush_if_prim_changed!("border", &self.borders);
2435 let transformed_rect = current_transform.apply_to_rect(*rect);
2436 batch.borders.push(BorderInstance {
2437 xywh: to_ndc(
2438 transformed_rect.x,
2439 transformed_rect.y,
2440 transformed_rect.w,
2441 transformed_rect.h,
2442 current_target_size.0,
2443 current_target_size.1,
2444 ),
2445 radius: *radius,
2446 stroke: *width,
2447 color: color.to_linear(),
2448 });
2449 }
2450 SceneNode::Ellipse { rect, brush } => {
2451 flush_if_prim_changed!("ellipse", &self.ellipses);
2452 let transformed = current_transform.apply_to_rect(*rect);
2453 let color = brush_to_solid_color(brush);
2454 batch.ellipses.push(EllipseInstance {
2455 xywh: to_ndc(
2456 transformed.x,
2457 transformed.y,
2458 transformed.w,
2459 transformed.h,
2460 current_target_size.0,
2461 current_target_size.1,
2462 ),
2463 color,
2464 });
2465 }
2466 SceneNode::EllipseBorder { rect, color, width } => {
2467 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
2468 let transformed = current_transform.apply_to_rect(*rect);
2469 batch.e_borders.push(EllipseBorderInstance {
2470 xywh: to_ndc(
2471 transformed.x,
2472 transformed.y,
2473 transformed.w,
2474 transformed.h,
2475 current_target_size.0,
2476 current_target_size.1,
2477 ),
2478 stroke: *width,
2479 color: color.to_linear(),
2480 });
2481 }
2482 SceneNode::Text {
2483 rect,
2484 text,
2485 color,
2486 size,
2487 font_family,
2488 } => {
2489 flush_batch!(); let px = (*size).clamp(8.0, 96.0);
2492 let shaped = repose_text::shape_line(text.as_ref(), px, *font_family);
2493 let transformed_rect = current_transform.apply_to_rect(*rect);
2494
2495 for sg in shaped {
2496 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
2497 let x = transformed_rect.x + sg.x + sg.bearing_x;
2498 let y = transformed_rect.y + sg.y - sg.bearing_y;
2499 batch.colors.push(GlyphInstance {
2500 xywh: to_ndc(x, y, info.w, info.h, current_target_size.0, current_target_size.1),
2501 uv: [info.u0, info.v1, info.u1, info.v0],
2502 color: color.to_linear(),
2503 });
2504 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
2505 let x = transformed_rect.x + sg.x + sg.bearing_x;
2506 let y = transformed_rect.y + sg.y - sg.bearing_y;
2507 batch.masks.push(GlyphInstance {
2508 xywh: to_ndc(x, y, info.w, info.h, current_target_size.0, current_target_size.1),
2509 uv: [info.u0, info.v1, info.u1, info.v0],
2510 color: color.to_linear(),
2511 });
2512 }
2513 }
2514 }
2516 SceneNode::Image {
2517 rect,
2518 handle,
2519 tint,
2520 fit,
2521 } => {
2522 flush_batch!();
2523
2524 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
2526 match t {
2527 ImageTex::Rgba {
2528 w,
2529 h,
2530 last_used_frame,
2531 ..
2532 } => {
2533 *last_used_frame = self.frame_index;
2534 (*w, *h, false)
2535 }
2536 ImageTex::Nv12 {
2537 w,
2538 h,
2539 last_used_frame,
2540 ..
2541 } => {
2542 *last_used_frame = self.frame_index;
2543 (*w, *h, true)
2544 }
2545 }
2546 } else {
2547 log::warn!("Image handle {} not found", handle);
2548 continue;
2549 };
2550
2551 let src_w = img_w as f32;
2552 let src_h = img_h as f32;
2553 let transformed = current_transform.apply_to_rect(*rect);
2554 let dst_w = transformed.w.max(0.0);
2555 let dst_h = transformed.h.max(0.0);
2556 if dst_w <= 0.0 || dst_h <= 0.0 {
2557 continue;
2558 }
2559
2560 let (xywh_ndc, uv_rect) = match fit {
2561 repose_core::view::ImageFit::Contain => {
2562 let scale = (dst_w / src_w).min(dst_h / src_h);
2563 let w = src_w * scale;
2564 let h = src_h * scale;
2565 let x = transformed.x + (dst_w - w) * 0.5;
2566 let y = transformed.y + (dst_h - h) * 0.5;
2567 (to_ndc(x, y, w, h, current_target_size.0, current_target_size.1), [0.0, 1.0, 1.0, 0.0])
2568 }
2569 repose_core::view::ImageFit::Cover => {
2570 let scale = (dst_w / src_w).max(dst_h / src_h);
2571 let content_w = src_w * scale;
2572 let content_h = src_h * scale;
2573 let overflow_x = (content_w - dst_w) * 0.5;
2574 let overflow_y = (content_h - dst_h) * 0.5;
2575 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
2576 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
2577 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
2578 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
2579 (
2580 to_ndc(transformed.x, transformed.y, dst_w, dst_h, current_target_size.0, current_target_size.1),
2581 [u0, 1.0 - v1, u1, 1.0 - v0],
2582 )
2583 }
2584 repose_core::view::ImageFit::FitWidth => {
2585 let scale = dst_w / src_w;
2586 let w = dst_w;
2587 let h = src_h * scale;
2588 let y = transformed.y + (dst_h - h) * 0.5;
2589 (to_ndc(transformed.x, y, w, h, current_target_size.0, current_target_size.1), [0.0, 1.0, 1.0, 0.0])
2590 }
2591 repose_core::view::ImageFit::FitHeight => {
2592 let scale = dst_h / src_h;
2593 let w = src_w * scale;
2594 let h = dst_h;
2595 let x = transformed.x + (dst_w - w) * 0.5;
2596 (to_ndc(x, transformed.y, w, h, current_target_size.0, current_target_size.1), [0.0, 1.0, 1.0, 0.0])
2597 }
2598 };
2599
2600 if is_nv12 {
2601 let full_range = if let Some(ImageTex::Nv12 { full_range, .. }) =
2602 self.images.get(handle)
2603 {
2604 if *full_range { 1.0 } else { 0.0 }
2605 } else {
2606 0.0
2607 };
2608
2609 let inst = Nv12Instance {
2610 xywh: xywh_ndc,
2611 uv: uv_rect,
2612 color: tint.to_linear(),
2613 full_range,
2614 _pad: [0.0; 3],
2615 };
2616 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
2617 {
2618 current_pass.cmds.push(Cmd::ImageNv12 {
2619 off,
2620 cnt: 1,
2621 handle: *handle,
2622 });
2623 }
2624 } else {
2625 let inst = GlyphInstance {
2627 xywh: xywh_ndc,
2628 uv: uv_rect,
2629 color: tint.to_linear(),
2630 };
2631 if let Some((off, _)) =
2632 self.glyph_color.upload(&self.device, &self.queue, &[inst])
2633 {
2634 current_pass.cmds.push(Cmd::ImageRgba {
2635 off,
2636 cnt: 1,
2637 handle: *handle,
2638 });
2639 }
2640 }
2641 }
2642 SceneNode::PushClip { rect, radius } => {
2643 flush_batch!(); let t_identity = Transform::identity();
2646 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2647 let transformed = current_transform.apply_to_rect(*rect);
2648
2649 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
2650 let next_scissor = intersect(top, transformed);
2651 scissor_stack.push(next_scissor);
2652 let scissor = to_scissor(
2653 &next_scissor,
2654 current_target_size.0 as u32,
2655 current_target_size.1 as u32,
2656 );
2657
2658 let inst = ClipInstance {
2659 xywh: to_ndc(
2660 transformed.x,
2661 transformed.y,
2662 transformed.w,
2663 transformed.h,
2664 current_target_size.0,
2665 current_target_size.1,
2666 ),
2667 radius: *radius,
2668 _pad: [0.0; 3],
2669 };
2670 let bytes = bytemuck::bytes_of(&inst);
2671 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
2672 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
2673
2674 current_pass.cmds.push(Cmd::ClipPush {
2675 off,
2676 cnt: 1,
2677 scissor,
2678 });
2679 }
2680 SceneNode::PopClip => {
2681 flush_batch!();
2682
2683 if !scissor_stack.is_empty() {
2684 scissor_stack.pop();
2685 } else {
2686 log::warn!("PopClip with empty stack");
2687 }
2688
2689 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
2690 let scissor = to_scissor(
2691 &top,
2692 current_target_size.0 as u32,
2693 current_target_size.1 as u32,
2694 );
2695 current_pass.cmds.push(Cmd::ClipPop { scissor });
2696 }
2697 SceneNode::Shadow {
2698 rect,
2699 radius,
2700 elevation: _,
2701 color,
2702 } => {
2703 flush_if_prim_changed!("rect", &self.rects);
2704 let transformed_rect = current_transform.apply_to_rect(*rect);
2705 let (brush_type, color0, _color1, _grad_start, _grad_end) =
2706 brush_to_instance_fields(&Brush::Solid(*color));
2707 batch.rects.push(RectInstance {
2708 xywh: to_ndc(
2709 transformed_rect.x,
2710 transformed_rect.y,
2711 transformed_rect.w,
2712 transformed_rect.h,
2713 current_target_size.0,
2714 current_target_size.1,
2715 ),
2716 radius: *radius,
2717 brush_type,
2718 color0,
2719 color1: [0.0; 4],
2720 grad_start: [0.0; 2],
2721 grad_end: [0.0; 2],
2722 });
2723 }
2724 SceneNode::PushTransform { transform } => {
2725 flush_batch!(); let combined = current_transform.combine(transform);
2727 if transform.rotate != 0.0 {
2728 ROT_WARN_ONCE.call_once(|| {
2729 log::warn!(
2730 "Transform rotation is not supported for Rect/Text/Image; rotation will be ignored."
2731 );
2732 });
2733 }
2734 transform_stack.push(combined);
2735 }
2736 SceneNode::PopTransform => {
2737 flush_batch!(); transform_stack.pop();
2739 }
2740 SceneNode::BeginLayer { rect, layer_id, alpha } => {
2741 flush_batch!();
2742 let w = (rect.w.max(1.0)).ceil() as u32;
2743 let h = (rect.h.max(1.0)).ceil() as u32;
2744 let prev_target = current_pass.target;
2746 let prev_scissor = current_pass.initial_scissor;
2747 let saved = std::mem::replace(
2748 &mut current_pass,
2749 Pass {
2750 target: PassTarget::Layer(*layer_id),
2751 initial_scissor: (0, 0, w, h),
2752 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
2753 cmds: Vec::new(),
2754 },
2755 );
2756 passes.push(saved);
2757 target_stack.push(prev_target);
2758 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
2762 current_target_size = (w as f32, h as f32);
2763 layer_alphas.push((
2764 *layer_id,
2765 *alpha,
2766 current_pass.initial_scissor,
2767 ));
2768 }
2769 SceneNode::EndLayer { layer_id } => {
2770 flush_batch!();
2771 let saved = std::mem::replace(
2773 &mut current_pass,
2774 Pass {
2775 target: target_stack.pop().unwrap_or(PassTarget::Surface),
2776 initial_scissor: (0, 0, self.config.width, self.config.height),
2777 clear_color: None, cmds: Vec::new(),
2779 },
2780 );
2781 passes.push(saved);
2782 current_target_size = (fb_w, fb_h);
2783 if let Some((_, layer_alpha, _)) = layer_alphas
2785 .iter()
2786 .find(|(id, _, _)| id == layer_id)
2787 .copied()
2788 {
2789 let layer = self.layer_pool.get(layer_id).expect("layer target");
2790 let inst = GlyphInstance {
2791 xywh: to_ndc(
2792 layer.rect_px.0,
2793 layer.rect_px.1,
2794 layer.rect_px.2,
2795 layer.rect_px.3,
2796 fb_w,
2797 fb_h,
2798 ),
2799 uv: [0.0, 1.0, 1.0, 0.0],
2800 color: [1.0, 1.0, 1.0, layer_alpha],
2801 };
2802 if let Some((off, cnt)) =
2803 self.glyph_color.upload(&self.device, &self.queue, &[inst])
2804 {
2805 current_pass.cmds.push(Cmd::CompositeLayer {
2806 off,
2807 cnt,
2808 layer_id: *layer_id,
2809 alpha: layer_alpha,
2810 });
2811 }
2812 }
2813 }
2814 SceneNode::CompositeShadow {
2815 layer_id,
2816 blur_px,
2817 offset_px,
2818 color,
2819 } => {
2820 flush_batch!();
2821 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
2822 let sx = layer.rect_px.0 + offset_px.0;
2824 let sy = layer.rect_px.1 + offset_px.1;
2825 let sw = layer.rect_px.2;
2826 let sh = layer.rect_px.3;
2827 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
2830 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
2831 let inst = BlurInstance {
2832 xywh: to_ndc(sx, sy, sw, sh, fb_w, fb_h),
2833 uv: [0.0, 0.0, 1.0, 1.0],
2834 color: [
2835 color.0 as f32 / 255.0,
2836 color.1 as f32 / 255.0,
2837 color.2 as f32 / 255.0,
2838 color.3 as f32 / 255.0,
2839 ],
2840 blur_uv: [bw_uv, bh_uv],
2841 };
2842 self.blur_ring
2843 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
2844 let bytes = bytemuck::bytes_of(&inst);
2845 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
2846 current_pass.cmds.push(Cmd::CompositeShadow {
2847 off,
2848 cnt: 1,
2849 layer_id: *layer_id,
2850 });
2851 }
2852 }
2853 }
2854 }
2855
2856 flush_batch!();
2857
2858 passes.push(current_pass);
2860
2861 let mut encoder = self
2862 .device
2863 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2864 label: Some("frame encoder"),
2865 });
2866
2867 let bind_mask = self.atlas_bind_group_mask();
2868 let bind_color = self.atlas_bind_group_color();
2869 let mut clip_depth: u32 = 0;
2870
2871 for pass in std::mem::take(&mut passes) {
2872 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
2873 PassTarget::Surface => {
2874 let swap_view = frame
2875 .texture
2876 .create_view(&wgpu::TextureViewDescriptor::default());
2877 let (color, resolve) = if let Some(msaa_view) = &self.msaa_view {
2878 (msaa_view.clone(), Some(swap_view))
2879 } else {
2880 (swap_view, None)
2881 };
2882 (color, resolve, self.depth_stencil_view.clone(), false)
2883 }
2884 PassTarget::Layer(layer_id) => {
2885 if let Some(lt) = self.layer_pool.get(&layer_id) {
2886 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
2887 } else {
2888 log::warn!("missing layer target {layer_id}");
2889 continue;
2890 }
2891 }
2892 };
2893
2894 if is_layer {
2895 clip_depth = 0;
2896 }
2897
2898 let pipes: &Pipelines = if is_layer {
2899 &self.layer_pipes
2900 } else {
2901 &self.surface_pipes
2902 };
2903
2904 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2905 label: Some("pass"),
2906 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2907 view: &color_view,
2908 resolve_target: resolve_target.as_ref(),
2909 ops: wgpu::Operations {
2910 load: match pass.clear_color {
2911 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
2912 r: c[0] as f64,
2913 g: c[1] as f64,
2914 b: c[2] as f64,
2915 a: c[3] as f64,
2916 }),
2917 None => wgpu::LoadOp::Load,
2918 },
2919 store: wgpu::StoreOp::Store,
2920 },
2921 depth_slice: None,
2922 })],
2923 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2924 view: &depth_stencil_view,
2925 depth_ops: None,
2926 stencil_ops: Some(wgpu::Operations {
2927 load: if is_layer || pass.clear_color.is_some() {
2928 wgpu::LoadOp::Clear(0)
2929 } else {
2930 wgpu::LoadOp::Load
2931 },
2932 store: wgpu::StoreOp::Store,
2933 }),
2934 }),
2935 timestamp_writes: None,
2936 occlusion_query_set: None,
2937 multiview_mask: None,
2938 });
2939
2940 rpass.set_bind_group(0, &self.globals_bind, &[]);
2941 rpass.set_stencil_reference(clip_depth);
2942 rpass.set_scissor_rect(
2943 pass.initial_scissor.0,
2944 pass.initial_scissor.1,
2945 pass.initial_scissor.2,
2946 pass.initial_scissor.3,
2947 );
2948
2949 for cmd in pass.cmds {
2950 match cmd {
2951 Cmd::ClipPush {
2952 off,
2953 cnt: n,
2954 scissor,
2955 } => {
2956 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2957 rpass.set_stencil_reference(clip_depth);
2958
2959 if self.msaa_samples > 1 && !is_layer {
2960 rpass.set_pipeline(&pipes.clip_a2c);
2961 } else {
2962 rpass.set_pipeline(&pipes.clip_bin);
2963 }
2964
2965 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
2966 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
2967 rpass.draw(0..6, 0..n);
2968
2969 clip_depth = (clip_depth + 1).min(255);
2970 rpass.set_stencil_reference(clip_depth);
2971 }
2972
2973 Cmd::ClipPop { scissor } => {
2974 clip_depth = clip_depth.saturating_sub(1);
2975 rpass.set_stencil_reference(clip_depth);
2976 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2977 }
2978
2979 Cmd::Rect { off, cnt: n } => {
2980 rpass.set_pipeline(&pipes.rects);
2981 let bytes = (n as u64) * std::mem::size_of::<RectInstance>() as u64;
2982 rpass.set_vertex_buffer(0, self.rects.ring.buf.slice(off..off + bytes));
2983 rpass.draw(0..6, 0..n);
2984 }
2985
2986 Cmd::Border { off, cnt: n } => {
2987 rpass.set_pipeline(&pipes.borders);
2988 let bytes = (n as u64) * std::mem::size_of::<BorderInstance>() as u64;
2989 rpass.set_vertex_buffer(0, self.borders.ring.buf.slice(off..off + bytes));
2990 rpass.draw(0..6, 0..n);
2991 }
2992
2993 Cmd::GlyphsMask { off, cnt: n } => {
2994 rpass.set_pipeline(&pipes.text_mask);
2995 rpass.set_bind_group(1, &bind_mask, &[]);
2996 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2997 rpass
2998 .set_vertex_buffer(0, self.glyph_mask.ring.buf.slice(off..off + bytes));
2999 rpass.draw(0..6, 0..n);
3000 }
3001
3002 Cmd::GlyphsColor { off, cnt: n } => {
3003 rpass.set_pipeline(&pipes.text_color);
3004 rpass.set_bind_group(1, &bind_color, &[]);
3005 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
3006 rpass.set_vertex_buffer(
3007 0,
3008 self.glyph_color.ring.buf.slice(off..off + bytes),
3009 );
3010 rpass.draw(0..6, 0..n);
3011 }
3012
3013 Cmd::ImageRgba {
3014 off,
3015 cnt: n,
3016 handle,
3017 } => {
3018 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
3019 rpass.set_pipeline(&pipes.image_rgba);
3020 rpass.set_bind_group(1, bind, &[]);
3021 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
3022 rpass.set_vertex_buffer(
3023 0,
3024 self.glyph_color.ring.buf.slice(off..off + bytes),
3025 );
3026 rpass.draw(0..6, 0..n);
3027 }
3028 }
3029
3030 Cmd::ImageNv12 {
3031 off,
3032 cnt: n,
3033 handle,
3034 } => {
3035 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
3036 rpass.set_pipeline(&pipes.image_nv12);
3037 rpass.set_bind_group(1, bind, &[]);
3038 let bytes = (n as u64) * std::mem::size_of::<Nv12Instance>() as u64;
3039 rpass.set_vertex_buffer(0, self.nv12.ring.buf.slice(off..off + bytes));
3040 rpass.draw(0..6, 0..n);
3041 }
3042 }
3043
3044 Cmd::Ellipse { off, cnt: n } => {
3045 rpass.set_pipeline(&pipes.ellipses);
3046 let bytes = (n as u64) * std::mem::size_of::<EllipseInstance>() as u64;
3047 rpass.set_vertex_buffer(0, self.ellipses.ring.buf.slice(off..off + bytes));
3048 rpass.draw(0..6, 0..n);
3049 }
3050
3051 Cmd::EllipseBorder { off, cnt: n } => {
3052 rpass.set_pipeline(&pipes.ellipse_borders);
3053 let bytes =
3054 (n as u64) * std::mem::size_of::<EllipseBorderInstance>() as u64;
3055 rpass.set_vertex_buffer(
3056 0,
3057 self.ellipse_borders.ring.buf.slice(off..off + bytes),
3058 );
3059 rpass.draw(0..6, 0..n);
3060 }
3061
3062 Cmd::PushTransform(_) => {}
3063 Cmd::PopTransform => {}
3064 Cmd::CompositeLayer {
3065 off,
3066 cnt: n,
3067 layer_id,
3068 alpha: _,
3069 } => {
3070 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3071 rpass.set_pipeline(&pipes.image_rgba);
3072 rpass.set_bind_group(1, <.bind, &[]);
3073 let bytes =
3074 (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
3075 rpass.set_vertex_buffer(
3076 0,
3077 self.glyph_color.ring.buf.slice(off..off + bytes),
3078 );
3079 rpass.draw(0..6, 0..n);
3080 }
3081 }
3082 Cmd::CompositeShadow {
3083 off,
3084 cnt: n,
3085 layer_id,
3086 } => {
3087 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3088 rpass.set_pipeline(&pipes.blur);
3089 rpass.set_bind_group(1, <.bind, &[]);
3090 let bytes =
3091 (n as u64) * std::mem::size_of::<BlurInstance>() as u64;
3092 rpass.set_vertex_buffer(
3093 0,
3094 self.blur_ring.buf.slice(off..off + bytes),
3095 );
3096 rpass.draw(0..6, 0..n);
3097 }
3098 }
3099 }
3100 }
3101 }
3102
3103 self.queue.submit(std::iter::once(encoder.finish()));
3104 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
3105 log::warn!("frame.present panicked: {:?}", e);
3106 }
3107
3108 self.evict_unused_images();
3110 }
3111}
3112
3113fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
3114 let x0 = a.x.max(b.x);
3115 let y0 = a.y.max(b.y);
3116 let x1 = (a.x + a.w).min(b.x + b.w);
3117 let y1 = (a.y + a.h).min(b.y + b.h);
3118 repose_core::Rect {
3119 x: x0,
3120 y: y0,
3121 w: (x1 - x0).max(0.0),
3122 h: (y1 - y0).max(0.0),
3123 }
3124}