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 align = 4u64;
51 let start = (self.head + (align - 1)) & !(align - 1);
52 let end = start + len;
53 if end > self.cap {
54 self.head = 0;
55 let start = 0;
56 let end = len.min(self.cap);
57 queue.write_buffer(&self.buf, start, &bytes[0..end as usize]);
58 self.head = end;
59 (start, len.min(self.cap - start))
60 } else {
61 queue.write_buffer(&self.buf, start, bytes);
62 self.head = end;
63 (start, len)
64 }
65 }
66}
67
68#[repr(C)]
69#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
70struct Globals {
71 ndc_to_px: [f32; 2],
72 _pad: [f32; 2],
73}
74
75pub struct WgpuBackend {
76 surface: wgpu::Surface<'static>,
77 device: wgpu::Device,
78 queue: wgpu::Queue,
79 config: wgpu::SurfaceConfiguration,
80
81 rect_pipeline: wgpu::RenderPipeline,
82 border_pipeline: wgpu::RenderPipeline,
83 ellipse_pipeline: wgpu::RenderPipeline,
84 ellipse_border_pipeline: wgpu::RenderPipeline,
85 text_pipeline_mask: wgpu::RenderPipeline,
86 text_pipeline_color: wgpu::RenderPipeline,
87 text_bind_layout: wgpu::BindGroupLayout,
88
89 clip_pipeline_a2c: wgpu::RenderPipeline,
91 clip_pipeline_bin: wgpu::RenderPipeline,
92 msaa_samples: u32,
93
94 depth_stencil_tex: wgpu::Texture,
96 depth_stencil_view: wgpu::TextureView,
97
98 msaa_tex: Option<wgpu::Texture>,
100 msaa_view: Option<wgpu::TextureView>,
101
102 globals_layout: wgpu::BindGroupLayout,
103 globals_buf: wgpu::Buffer,
104 globals_bind: wgpu::BindGroup,
105
106 atlas_mask: AtlasA8,
108 atlas_color: AtlasRGBA,
109
110 ring_rect: UploadRing,
112 ring_border: UploadRing,
113 ring_ellipse: UploadRing,
114 ring_ellipse_border: UploadRing,
115 ring_glyph_mask: UploadRing,
116 ring_glyph_color: UploadRing,
117 ring_clip: UploadRing,
118
119 next_image_handle: u64,
120 images: std::collections::HashMap<u64, ImageTex>,
121}
122
123struct ImageTex {
124 view: wgpu::TextureView,
125 bind: wgpu::BindGroup,
126 w: u32,
127 h: u32,
128}
129
130struct AtlasA8 {
131 tex: wgpu::Texture,
132 view: wgpu::TextureView,
133 sampler: wgpu::Sampler,
134 size: u32,
135 next_x: u32,
136 next_y: u32,
137 row_h: u32,
138 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
139}
140
141struct AtlasRGBA {
142 tex: wgpu::Texture,
143 view: wgpu::TextureView,
144 sampler: wgpu::Sampler,
145 size: u32,
146 next_x: u32,
147 next_y: u32,
148 row_h: u32,
149 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
150}
151
152#[derive(Clone, Copy)]
153struct GlyphInfo {
154 u0: f32,
155 v0: f32,
156 u1: f32,
157 v1: f32,
158 w: f32,
159 h: f32,
160 bearing_x: f32,
161 bearing_y: f32,
162 advance: f32,
163}
164
165#[repr(C)]
166#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
167struct RectInstance {
168 xywh: [f32; 4],
170 radius: f32,
172 brush_type: u32,
173 color0: [f32; 4],
174 color1: [f32; 4],
175 grad_start: [f32; 2],
176 grad_end: [f32; 2],
177}
178
179#[repr(C)]
180#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
181struct BorderInstance {
182 xywh: [f32; 4],
183 radius: f32,
184 stroke: f32,
185 color: [f32; 4],
186}
187
188#[repr(C)]
189#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
190struct EllipseInstance {
191 xywh: [f32; 4],
192 color: [f32; 4],
193}
194
195#[repr(C)]
196#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
197struct EllipseBorderInstance {
198 xywh: [f32; 4],
199 stroke: f32,
200 color: [f32; 4],
201}
202
203#[repr(C)]
204#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
205struct GlyphInstance {
206 xywh: [f32; 4],
207 uv: [f32; 4],
208 color: [f32; 4],
209}
210
211#[repr(C)]
212#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
213struct ClipInstance {
214 xywh: [f32; 4],
215 radius: f32,
216 _pad: [f32; 3],
217}
218
219fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
220 match content {
221 cosmic_text::SwashContent::Mask => Some(data.to_vec()),
222 cosmic_text::SwashContent::SubpixelMask => {
223 let mut out = Vec::with_capacity(data.len() / 4);
224 for px in data.chunks_exact(4) {
225 let r = px[0];
226 let g = px[1];
227 let b = px[2];
228 out.push(r.max(g).max(b));
229 }
230 Some(out)
231 }
232 cosmic_text::SwashContent::Color => None,
233 }
234}
235
236impl WgpuBackend {
237 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
238 let mut desc = wgpu::InstanceDescriptor::from_env_or_default();
239 let instance: Instance;
240
241 if cfg!(target_arch = "wasm32") {
242 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
243 instance = wgpu::util::new_instance_with_webgpu_detection(&desc).await;
244 } else {
245 instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::from_env_or_default());
246 };
247
248 let surface = instance.create_surface(window.clone())?;
249
250 let adapter = instance
251 .request_adapter(&wgpu::RequestAdapterOptions {
252 power_preference: wgpu::PowerPreference::HighPerformance,
253 compatible_surface: Some(&surface),
254 force_fallback_adapter: false,
255 })
256 .await
257 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
258
259 let limits = if cfg!(target_arch = "wasm32") {
260 wgpu::Limits::downlevel_webgl2_defaults()
261 } else {
262 wgpu::Limits::default()
263 };
264
265 let (device, queue) = adapter
266 .request_device(&wgpu::DeviceDescriptor {
267 label: Some("repose-rs device"),
268 required_features: wgpu::Features::empty(),
269 required_limits: limits,
270 experimental_features: wgpu::ExperimentalFeatures::disabled(),
271 memory_hints: wgpu::MemoryHints::default(),
272 trace: wgpu::Trace::Off,
273 })
274 .await
275 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
276
277 let size = window.inner_size();
278
279 let caps = surface.get_capabilities(&adapter);
280 let format = caps
281 .formats
282 .iter()
283 .copied()
284 .find(|f| f.is_srgb())
285 .unwrap_or(caps.formats[0]);
286 let present_mode = caps
287 .present_modes
288 .iter()
289 .copied()
290 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
291 .unwrap_or(wgpu::PresentMode::Fifo);
292 let alpha_mode = caps.alpha_modes[0];
293
294 let config = wgpu::SurfaceConfiguration {
295 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
296 format,
297 width: size.width.max(1),
298 height: size.height.max(1),
299 present_mode,
300 alpha_mode,
301 view_formats: vec![],
302 desired_maximum_frame_latency: 2,
303 };
304 surface.configure(&device, &config);
305
306 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
307 label: Some("globals layout"),
308 entries: &[wgpu::BindGroupLayoutEntry {
309 binding: 0,
310 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
311 ty: wgpu::BindingType::Buffer {
312 ty: wgpu::BufferBindingType::Uniform,
313 has_dynamic_offset: false,
314 min_binding_size: None,
315 },
316 count: None,
317 }],
318 });
319
320 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
321 label: Some("globals buf"),
322 size: std::mem::size_of::<Globals>() as u64,
323 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
324 mapped_at_creation: false,
325 });
326
327 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
328 label: Some("globals bind"),
329 layout: &globals_layout,
330 entries: &[wgpu::BindGroupEntry {
331 binding: 0,
332 resource: globals_buf.as_entire_binding(),
333 }],
334 });
335
336 let fmt_features = adapter.get_texture_format_features(format);
338 let msaa_samples = if fmt_features.flags.sample_count_supported(4)
339 && fmt_features
340 .flags
341 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
342 {
343 4
344 } else {
345 1
346 };
347
348 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
349
350 let stencil_for_content = wgpu::DepthStencilState {
351 format: ds_format,
352 depth_write_enabled: false,
353 depth_compare: wgpu::CompareFunction::Always,
354 stencil: wgpu::StencilState {
355 front: wgpu::StencilFaceState {
356 compare: wgpu::CompareFunction::Equal,
357 fail_op: wgpu::StencilOperation::Keep,
358 depth_fail_op: wgpu::StencilOperation::Keep,
359 pass_op: wgpu::StencilOperation::Keep,
360 },
361 back: wgpu::StencilFaceState {
362 compare: wgpu::CompareFunction::Equal,
363 fail_op: wgpu::StencilOperation::Keep,
364 depth_fail_op: wgpu::StencilOperation::Keep,
365 pass_op: wgpu::StencilOperation::Keep,
366 },
367 read_mask: 0xFF,
368 write_mask: 0x00,
369 },
370 bias: wgpu::DepthBiasState::default(),
371 };
372
373 let stencil_for_clip_inc = wgpu::DepthStencilState {
374 format: ds_format,
375 depth_write_enabled: false,
376 depth_compare: wgpu::CompareFunction::Always,
377 stencil: wgpu::StencilState {
378 front: wgpu::StencilFaceState {
379 compare: wgpu::CompareFunction::Equal,
380 fail_op: wgpu::StencilOperation::Keep,
381 depth_fail_op: wgpu::StencilOperation::Keep,
382 pass_op: wgpu::StencilOperation::IncrementClamp,
383 },
384 back: wgpu::StencilFaceState {
385 compare: wgpu::CompareFunction::Equal,
386 fail_op: wgpu::StencilOperation::Keep,
387 depth_fail_op: wgpu::StencilOperation::Keep,
388 pass_op: wgpu::StencilOperation::IncrementClamp,
389 },
390 read_mask: 0xFF,
391 write_mask: 0xFF,
392 },
393 bias: wgpu::DepthBiasState::default(),
394 };
395
396 let multisample_state = wgpu::MultisampleState {
397 count: msaa_samples,
398 mask: !0,
399 alpha_to_coverage_enabled: false,
400 };
401
402 let rect_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
404 label: Some("rect.wgsl"),
405 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/rect.wgsl"))),
406 });
407 let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
408 label: Some("rect pipeline layout"),
409 bind_group_layouts: &[&globals_layout],
410 immediate_size: 0,
411 });
412 let rect_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
413 label: Some("rect pipeline"),
414 layout: Some(&rect_pipeline_layout),
415 vertex: wgpu::VertexState {
416 module: &rect_shader,
417 entry_point: Some("vs_main"),
418 buffers: &[wgpu::VertexBufferLayout {
419 array_stride: std::mem::size_of::<RectInstance>() as u64,
420 step_mode: wgpu::VertexStepMode::Instance,
421 attributes: &[
422 wgpu::VertexAttribute {
423 shader_location: 0,
424 offset: 0,
425 format: wgpu::VertexFormat::Float32x4,
426 },
427 wgpu::VertexAttribute {
428 shader_location: 1,
429 offset: 16,
430 format: wgpu::VertexFormat::Float32,
431 },
432 wgpu::VertexAttribute {
433 shader_location: 2,
434 offset: 20,
435 format: wgpu::VertexFormat::Uint32,
436 },
437 wgpu::VertexAttribute {
438 shader_location: 3,
439 offset: 24,
440 format: wgpu::VertexFormat::Float32x4,
441 },
442 wgpu::VertexAttribute {
443 shader_location: 4,
444 offset: 40,
445 format: wgpu::VertexFormat::Float32x4,
446 },
447 wgpu::VertexAttribute {
448 shader_location: 5,
449 offset: 56,
450 format: wgpu::VertexFormat::Float32x2,
451 },
452 wgpu::VertexAttribute {
453 shader_location: 6,
454 offset: 64,
455 format: wgpu::VertexFormat::Float32x2,
456 },
457 ],
458 }],
459 compilation_options: wgpu::PipelineCompilationOptions::default(),
460 },
461 fragment: Some(wgpu::FragmentState {
462 module: &rect_shader,
463 entry_point: Some("fs_main"),
464 targets: &[Some(wgpu::ColorTargetState {
465 format: config.format,
466 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
467 write_mask: wgpu::ColorWrites::ALL,
468 })],
469 compilation_options: wgpu::PipelineCompilationOptions::default(),
470 }),
471 primitive: wgpu::PrimitiveState::default(),
472 depth_stencil: Some(stencil_for_content.clone()),
473 multisample: multisample_state,
474 multiview_mask: None,
475 cache: None,
476 });
477
478 let border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
480 label: Some("border.wgsl"),
481 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/border.wgsl"))),
482 });
483 let border_pipeline_layout =
484 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
485 label: Some("border pipeline layout"),
486 bind_group_layouts: &[&globals_layout],
487 immediate_size: 0,
488 });
489 let border_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
490 label: Some("border pipeline"),
491 layout: Some(&border_pipeline_layout),
492 vertex: wgpu::VertexState {
493 module: &border_shader,
494 entry_point: Some("vs_main"),
495 buffers: &[wgpu::VertexBufferLayout {
496 array_stride: std::mem::size_of::<BorderInstance>() as u64,
497 step_mode: wgpu::VertexStepMode::Instance,
498 attributes: &[
499 wgpu::VertexAttribute {
500 shader_location: 0,
501 offset: 0,
502 format: wgpu::VertexFormat::Float32x4,
503 },
504 wgpu::VertexAttribute {
505 shader_location: 1,
506 offset: 16,
507 format: wgpu::VertexFormat::Float32,
508 },
509 wgpu::VertexAttribute {
510 shader_location: 2,
511 offset: 20,
512 format: wgpu::VertexFormat::Float32,
513 },
514 wgpu::VertexAttribute {
515 shader_location: 3,
516 offset: 24,
517 format: wgpu::VertexFormat::Float32x4,
518 },
519 ],
520 }],
521 compilation_options: wgpu::PipelineCompilationOptions::default(),
522 },
523 fragment: Some(wgpu::FragmentState {
524 module: &border_shader,
525 entry_point: Some("fs_main"),
526 targets: &[Some(wgpu::ColorTargetState {
527 format: config.format,
528 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
529 write_mask: wgpu::ColorWrites::ALL,
530 })],
531 compilation_options: wgpu::PipelineCompilationOptions::default(),
532 }),
533 primitive: wgpu::PrimitiveState::default(),
534 depth_stencil: Some(stencil_for_content.clone()),
535 multisample: multisample_state,
536 multiview_mask: None,
537 cache: None,
538 });
539
540 let ellipse_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
542 label: Some("ellipse.wgsl"),
543 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/ellipse.wgsl"))),
544 });
545 let ellipse_pipeline_layout =
546 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
547 label: Some("ellipse pipeline layout"),
548 bind_group_layouts: &[&globals_layout],
549 immediate_size: 0,
550 });
551 let ellipse_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
552 label: Some("ellipse pipeline"),
553 layout: Some(&ellipse_pipeline_layout),
554 vertex: wgpu::VertexState {
555 module: &ellipse_shader,
556 entry_point: Some("vs_main"),
557 buffers: &[wgpu::VertexBufferLayout {
558 array_stride: std::mem::size_of::<EllipseInstance>() as u64,
559 step_mode: wgpu::VertexStepMode::Instance,
560 attributes: &[
561 wgpu::VertexAttribute {
562 shader_location: 0,
563 offset: 0,
564 format: wgpu::VertexFormat::Float32x4,
565 },
566 wgpu::VertexAttribute {
567 shader_location: 1,
568 offset: 16,
569 format: wgpu::VertexFormat::Float32x4,
570 },
571 ],
572 }],
573 compilation_options: wgpu::PipelineCompilationOptions::default(),
574 },
575 fragment: Some(wgpu::FragmentState {
576 module: &ellipse_shader,
577 entry_point: Some("fs_main"),
578 targets: &[Some(wgpu::ColorTargetState {
579 format: config.format,
580 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
581 write_mask: wgpu::ColorWrites::ALL,
582 })],
583 compilation_options: wgpu::PipelineCompilationOptions::default(),
584 }),
585 primitive: wgpu::PrimitiveState::default(),
586 depth_stencil: Some(stencil_for_content.clone()),
587 multisample: multisample_state,
588 multiview_mask: None,
589 cache: None,
590 });
591
592 let ellipse_border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
594 label: Some("ellipse_border.wgsl"),
595 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
596 "shaders/ellipse_border.wgsl"
597 ))),
598 });
599 let ellipse_border_layout =
600 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
601 label: Some("ellipse border layout"),
602 bind_group_layouts: &[&globals_layout],
603 immediate_size: 0,
604 });
605 let ellipse_border_pipeline =
606 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
607 label: Some("ellipse border pipeline"),
608 layout: Some(&ellipse_border_layout),
609 vertex: wgpu::VertexState {
610 module: &ellipse_border_shader,
611 entry_point: Some("vs_main"),
612 buffers: &[wgpu::VertexBufferLayout {
613 array_stride: std::mem::size_of::<EllipseBorderInstance>() as u64,
614 step_mode: wgpu::VertexStepMode::Instance,
615 attributes: &[
616 wgpu::VertexAttribute {
617 shader_location: 0,
618 offset: 0,
619 format: wgpu::VertexFormat::Float32x4,
620 },
621 wgpu::VertexAttribute {
622 shader_location: 1,
623 offset: 16,
624 format: wgpu::VertexFormat::Float32,
625 },
626 wgpu::VertexAttribute {
627 shader_location: 2,
628 offset: 20,
629 format: wgpu::VertexFormat::Float32x4,
630 },
631 ],
632 }],
633 compilation_options: wgpu::PipelineCompilationOptions::default(),
634 },
635 fragment: Some(wgpu::FragmentState {
636 module: &ellipse_border_shader,
637 entry_point: Some("fs_main"),
638 targets: &[Some(wgpu::ColorTargetState {
639 format: config.format,
640 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
641 write_mask: wgpu::ColorWrites::ALL,
642 })],
643 compilation_options: wgpu::PipelineCompilationOptions::default(),
644 }),
645 primitive: wgpu::PrimitiveState::default(),
646 depth_stencil: Some(stencil_for_content.clone()),
647 multisample: multisample_state,
648 multiview_mask: None,
649 cache: None,
650 });
651
652 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
654 label: Some("text.wgsl"),
655 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
656 });
657 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
658 label: Some("text_color.wgsl"),
659 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
660 "shaders/text_color.wgsl"
661 ))),
662 });
663 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
664 label: Some("text bind layout"),
665 entries: &[
666 wgpu::BindGroupLayoutEntry {
667 binding: 0,
668 visibility: wgpu::ShaderStages::FRAGMENT,
669 ty: wgpu::BindingType::Texture {
670 multisampled: false,
671 view_dimension: wgpu::TextureViewDimension::D2,
672 sample_type: wgpu::TextureSampleType::Float { filterable: true },
673 },
674 count: None,
675 },
676 wgpu::BindGroupLayoutEntry {
677 binding: 1,
678 visibility: wgpu::ShaderStages::FRAGMENT,
679 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
680 count: None,
681 },
682 ],
683 });
684 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
685 label: Some("text pipeline layout"),
686 bind_group_layouts: &[&globals_layout, &text_bind_layout],
687 immediate_size: 0,
688 });
689 let text_pipeline_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
690 label: Some("text pipeline (mask)"),
691 layout: Some(&text_pipeline_layout),
692 vertex: wgpu::VertexState {
693 module: &text_mask_shader,
694 entry_point: Some("vs_main"),
695 buffers: &[wgpu::VertexBufferLayout {
696 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
697 step_mode: wgpu::VertexStepMode::Instance,
698 attributes: &[
699 wgpu::VertexAttribute {
700 shader_location: 0,
701 offset: 0,
702 format: wgpu::VertexFormat::Float32x4,
703 },
704 wgpu::VertexAttribute {
705 shader_location: 1,
706 offset: 16,
707 format: wgpu::VertexFormat::Float32x4,
708 },
709 wgpu::VertexAttribute {
710 shader_location: 2,
711 offset: 32,
712 format: wgpu::VertexFormat::Float32x4,
713 },
714 ],
715 }],
716 compilation_options: wgpu::PipelineCompilationOptions::default(),
717 },
718 fragment: Some(wgpu::FragmentState {
719 module: &text_mask_shader,
720 entry_point: Some("fs_main"),
721 targets: &[Some(wgpu::ColorTargetState {
722 format: config.format,
723 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
724 write_mask: wgpu::ColorWrites::ALL,
725 })],
726 compilation_options: wgpu::PipelineCompilationOptions::default(),
727 }),
728 primitive: wgpu::PrimitiveState::default(),
729 depth_stencil: Some(stencil_for_content.clone()),
730 multisample: multisample_state,
731 multiview_mask: None,
732 cache: None,
733 });
734 let text_pipeline_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
735 label: Some("text pipeline (color)"),
736 layout: Some(&text_pipeline_layout),
737 vertex: wgpu::VertexState {
738 module: &text_color_shader,
739 entry_point: Some("vs_main"),
740 buffers: &[wgpu::VertexBufferLayout {
741 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
742 step_mode: wgpu::VertexStepMode::Instance,
743 attributes: &[
744 wgpu::VertexAttribute {
745 shader_location: 0,
746 offset: 0,
747 format: wgpu::VertexFormat::Float32x4,
748 },
749 wgpu::VertexAttribute {
750 shader_location: 1,
751 offset: 16,
752 format: wgpu::VertexFormat::Float32x4,
753 },
754 wgpu::VertexAttribute {
755 shader_location: 2,
756 offset: 32,
757 format: wgpu::VertexFormat::Float32x4,
758 },
759 ],
760 }],
761 compilation_options: wgpu::PipelineCompilationOptions::default(),
762 },
763 fragment: Some(wgpu::FragmentState {
764 module: &text_color_shader,
765 entry_point: Some("fs_main"),
766 targets: &[Some(wgpu::ColorTargetState {
767 format: config.format,
768 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
769 write_mask: wgpu::ColorWrites::ALL,
770 })],
771 compilation_options: wgpu::PipelineCompilationOptions::default(),
772 }),
773 primitive: wgpu::PrimitiveState::default(),
774 depth_stencil: Some(stencil_for_content.clone()),
775 multisample: multisample_state,
776 multiview_mask: None,
777 cache: None,
778 });
779
780 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
782 label: Some("clip_round_rect_a2c.wgsl"),
783 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
784 "shaders/clip_round_rect_a2c.wgsl"
785 ))),
786 });
787 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
788 label: Some("clip_round_rect_bin.wgsl"),
789 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
790 "shaders/clip_round_rect_bin.wgsl"
791 ))),
792 });
793
794 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
795 label: Some("clip pipeline layout"),
796 bind_group_layouts: &[&globals_layout],
797 immediate_size: 0,
798 });
799
800 let clip_vertex_layout = wgpu::VertexBufferLayout {
801 array_stride: std::mem::size_of::<ClipInstance>() as u64,
802 step_mode: wgpu::VertexStepMode::Instance,
803 attributes: &[
804 wgpu::VertexAttribute {
805 shader_location: 0,
806 offset: 0,
807 format: wgpu::VertexFormat::Float32x4,
808 },
809 wgpu::VertexAttribute {
810 shader_location: 1,
811 offset: 16,
812 format: wgpu::VertexFormat::Float32,
813 },
814 ],
815 };
816
817 let clip_color_target = wgpu::ColorTargetState {
818 format: config.format,
819 blend: None,
820 write_mask: wgpu::ColorWrites::empty(),
821 };
822
823 let clip_pipeline_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
824 label: Some("clip pipeline (a2c)"),
825 layout: Some(&clip_pipeline_layout),
826 vertex: wgpu::VertexState {
827 module: &clip_shader_a2c,
828 entry_point: Some("vs_main"),
829 buffers: &[clip_vertex_layout.clone()],
830 compilation_options: wgpu::PipelineCompilationOptions::default(),
831 },
832 fragment: Some(wgpu::FragmentState {
833 module: &clip_shader_a2c,
834 entry_point: Some("fs_main"),
835 targets: &[Some(clip_color_target.clone())],
836 compilation_options: wgpu::PipelineCompilationOptions::default(),
837 }),
838 primitive: wgpu::PrimitiveState::default(),
839 depth_stencil: Some(stencil_for_clip_inc.clone()),
840 multisample: wgpu::MultisampleState {
841 count: msaa_samples,
842 mask: !0,
843 alpha_to_coverage_enabled: msaa_samples > 1,
844 },
845 multiview_mask: None,
846 cache: None,
847 });
848
849 let clip_pipeline_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
850 label: Some("clip pipeline (bin)"),
851 layout: Some(&clip_pipeline_layout),
852 vertex: wgpu::VertexState {
853 module: &clip_shader_bin,
854 entry_point: Some("vs_main"),
855 buffers: &[clip_vertex_layout],
856 compilation_options: wgpu::PipelineCompilationOptions::default(),
857 },
858 fragment: Some(wgpu::FragmentState {
859 module: &clip_shader_bin,
860 entry_point: Some("fs_main"),
861 targets: &[Some(clip_color_target)],
862 compilation_options: wgpu::PipelineCompilationOptions::default(),
863 }),
864 primitive: wgpu::PrimitiveState::default(),
865 depth_stencil: Some(stencil_for_clip_inc),
866 multisample: wgpu::MultisampleState {
867 count: msaa_samples,
868 mask: !0,
869 alpha_to_coverage_enabled: false,
870 },
871 multiview_mask: None,
872 cache: None,
873 });
874
875 let atlas_mask = Self::init_atlas_mask(&device)?;
877 let atlas_color = Self::init_atlas_color(&device)?;
878
879 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
881 let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
882 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
883 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
884 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
885 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
886 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
887
888 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
890 label: Some("temp ds"),
891 size: wgpu::Extent3d {
892 width: 1,
893 height: 1,
894 depth_or_array_layers: 1,
895 },
896 mip_level_count: 1,
897 sample_count: 1,
898 dimension: wgpu::TextureDimension::D2,
899 format: wgpu::TextureFormat::Depth24PlusStencil8,
900 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
901 view_formats: &[],
902 });
903 let depth_stencil_view =
904 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
905
906 let mut backend = Self {
907 surface,
908 device,
909 queue,
910 config,
911 rect_pipeline,
912 border_pipeline,
913 text_pipeline_mask,
914 text_pipeline_color,
915 text_bind_layout,
916 ellipse_pipeline,
917 ellipse_border_pipeline,
918 atlas_mask,
919 atlas_color,
920 ring_rect,
921 ring_border,
922 ring_ellipse,
923 ring_ellipse_border,
924 ring_glyph_color,
925 ring_glyph_mask,
926 ring_clip,
927 next_image_handle: 1,
928 images: HashMap::new(),
929 clip_pipeline_a2c,
930 clip_pipeline_bin,
931 msaa_samples,
932 depth_stencil_tex,
933 depth_stencil_view,
934 msaa_tex: None,
935 msaa_view: None,
936 globals_bind,
937 globals_buf,
938 globals_layout,
939 };
940
941 backend.recreate_msaa_and_depth_stencil();
942 Ok(backend)
943 }
944
945 #[cfg(not(target_arch = "wasm32"))]
946 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
947 pollster::block_on(Self::new_async(window))
948 }
949
950 #[cfg(target_arch = "wasm32")]
951 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
952 anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
953 }
954
955 fn recreate_msaa_and_depth_stencil(&mut self) {
956 if self.msaa_samples > 1 {
957 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
958 label: Some("msaa color"),
959 size: wgpu::Extent3d {
960 width: self.config.width.max(1),
961 height: self.config.height.max(1),
962 depth_or_array_layers: 1,
963 },
964 mip_level_count: 1,
965 sample_count: self.msaa_samples,
966 dimension: wgpu::TextureDimension::D2,
967 format: self.config.format,
968 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
969 view_formats: &[],
970 });
971 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
972 self.msaa_tex = Some(tex);
973 self.msaa_view = Some(view);
974 } else {
975 self.msaa_tex = None;
976 self.msaa_view = None;
977 }
978
979 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
980 label: Some("depth-stencil (stencil clips)"),
981 size: wgpu::Extent3d {
982 width: self.config.width.max(1),
983 height: self.config.height.max(1),
984 depth_or_array_layers: 1,
985 },
986 mip_level_count: 1,
987 sample_count: self.msaa_samples,
988 dimension: wgpu::TextureDimension::D2,
989 format: wgpu::TextureFormat::Depth24PlusStencil8,
990 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
991 view_formats: &[],
992 });
993 self.depth_stencil_view = self
994 .depth_stencil_tex
995 .create_view(&wgpu::TextureViewDescriptor::default());
996 }
997
998 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
999 let img = image::load_from_memory(data).expect("decode image");
1000 let rgba = img.to_rgba8();
1001 let (w, h) = rgba.dimensions();
1002 let format = if srgb {
1003 wgpu::TextureFormat::Rgba8UnormSrgb
1004 } else {
1005 wgpu::TextureFormat::Rgba8Unorm
1006 };
1007 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1008 label: Some("user image"),
1009 size: wgpu::Extent3d {
1010 width: w,
1011 height: h,
1012 depth_or_array_layers: 1,
1013 },
1014 mip_level_count: 1,
1015 sample_count: 1,
1016 dimension: wgpu::TextureDimension::D2,
1017 format,
1018 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1019 view_formats: &[],
1020 });
1021 self.queue.write_texture(
1022 wgpu::TexelCopyTextureInfoBase {
1023 texture: &tex,
1024 mip_level: 0,
1025 origin: wgpu::Origin3d::ZERO,
1026 aspect: wgpu::TextureAspect::All,
1027 },
1028 &rgba,
1029 wgpu::TexelCopyBufferLayout {
1030 offset: 0,
1031 bytes_per_row: Some(4 * w),
1032 rows_per_image: Some(h),
1033 },
1034 wgpu::Extent3d {
1035 width: w,
1036 height: h,
1037 depth_or_array_layers: 1,
1038 },
1039 );
1040 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1041 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1042 label: Some("image bind"),
1043 layout: &self.text_bind_layout,
1044 entries: &[
1045 wgpu::BindGroupEntry {
1046 binding: 0,
1047 resource: wgpu::BindingResource::TextureView(&view),
1048 },
1049 wgpu::BindGroupEntry {
1050 binding: 1,
1051 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1052 },
1053 ],
1054 });
1055 let handle = self.next_image_handle;
1056 self.next_image_handle += 1;
1057 self.images.insert(handle, ImageTex { view, bind, w, h });
1058 handle
1059 }
1060
1061 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1062 let size = 1024u32;
1063 let tex = device.create_texture(&wgpu::TextureDescriptor {
1064 label: Some("glyph atlas A8"),
1065 size: wgpu::Extent3d {
1066 width: size,
1067 height: size,
1068 depth_or_array_layers: 1,
1069 },
1070 mip_level_count: 1,
1071 sample_count: 1,
1072 dimension: wgpu::TextureDimension::D2,
1073 format: wgpu::TextureFormat::R8Unorm,
1074 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1075 view_formats: &[],
1076 });
1077 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1078 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1079 label: Some("glyph atlas sampler A8"),
1080 address_mode_u: wgpu::AddressMode::ClampToEdge,
1081 address_mode_v: wgpu::AddressMode::ClampToEdge,
1082 address_mode_w: wgpu::AddressMode::ClampToEdge,
1083 mag_filter: wgpu::FilterMode::Linear,
1084 min_filter: wgpu::FilterMode::Linear,
1085 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1086 ..Default::default()
1087 });
1088
1089 Ok(AtlasA8 {
1090 tex,
1091 view,
1092 sampler,
1093 size,
1094 next_x: 1,
1095 next_y: 1,
1096 row_h: 0,
1097 map: HashMap::new(),
1098 })
1099 }
1100
1101 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1102 let size = 1024u32;
1103 let tex = device.create_texture(&wgpu::TextureDescriptor {
1104 label: Some("glyph atlas RGBA"),
1105 size: wgpu::Extent3d {
1106 width: size,
1107 height: size,
1108 depth_or_array_layers: 1,
1109 },
1110 mip_level_count: 1,
1111 sample_count: 1,
1112 dimension: wgpu::TextureDimension::D2,
1113 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1114 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1115 view_formats: &[],
1116 });
1117 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1118 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1119 label: Some("glyph atlas sampler RGBA"),
1120 address_mode_u: wgpu::AddressMode::ClampToEdge,
1121 address_mode_v: wgpu::AddressMode::ClampToEdge,
1122 address_mode_w: wgpu::AddressMode::ClampToEdge,
1123 mag_filter: wgpu::FilterMode::Linear,
1124 min_filter: wgpu::FilterMode::Linear,
1125 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1126 ..Default::default()
1127 });
1128 Ok(AtlasRGBA {
1129 tex,
1130 view,
1131 sampler,
1132 size,
1133 next_x: 1,
1134 next_y: 1,
1135 row_h: 0,
1136 map: HashMap::new(),
1137 })
1138 }
1139
1140 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
1141 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1142 label: Some("atlas bind"),
1143 layout: &self.text_bind_layout,
1144 entries: &[
1145 wgpu::BindGroupEntry {
1146 binding: 0,
1147 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
1148 },
1149 wgpu::BindGroupEntry {
1150 binding: 1,
1151 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
1152 },
1153 ],
1154 })
1155 }
1156
1157 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
1158 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1159 label: Some("atlas bind color"),
1160 layout: &self.text_bind_layout,
1161 entries: &[
1162 wgpu::BindGroupEntry {
1163 binding: 0,
1164 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
1165 },
1166 wgpu::BindGroupEntry {
1167 binding: 1,
1168 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1169 },
1170 ],
1171 })
1172 }
1173
1174 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1175 let keyp = (key, px);
1176 if let Some(info) = self.atlas_mask.map.get(&keyp) {
1177 return Some(*info);
1178 }
1179
1180 let gb = repose_text::rasterize(key, px as f32)?;
1181 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
1182 return None;
1183 }
1184
1185 let coverage = match swash_to_a8_coverage(gb.content, &gb.data) {
1186 Some(c) => c,
1187 None => return None,
1188 };
1189
1190 let w = gb.w.max(1);
1191 let h = gb.h.max(1);
1192
1193 if !self.alloc_space_mask(w, h) {
1194 self.grow_mask_and_rebuild();
1195 }
1196 if !self.alloc_space_mask(w, h) {
1197 return None;
1198 }
1199 let x = self.atlas_mask.next_x;
1200 let y = self.atlas_mask.next_y;
1201 self.atlas_mask.next_x += w + 1;
1202 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
1203
1204 let layout = wgpu::TexelCopyBufferLayout {
1205 offset: 0,
1206 bytes_per_row: Some(w),
1207 rows_per_image: Some(h),
1208 };
1209 let size = wgpu::Extent3d {
1210 width: w,
1211 height: h,
1212 depth_or_array_layers: 1,
1213 };
1214 self.queue.write_texture(
1215 wgpu::TexelCopyTextureInfoBase {
1216 texture: &self.atlas_mask.tex,
1217 mip_level: 0,
1218 origin: wgpu::Origin3d { x, y, z: 0 },
1219 aspect: wgpu::TextureAspect::All,
1220 },
1221 &coverage,
1222 layout,
1223 size,
1224 );
1225
1226 let info = GlyphInfo {
1227 u0: x as f32 / self.atlas_mask.size as f32,
1228 v0: y as f32 / self.atlas_mask.size as f32,
1229 u1: (x + w) as f32 / self.atlas_mask.size as f32,
1230 v1: (y + h) as f32 / self.atlas_mask.size as f32,
1231 w: w as f32,
1232 h: h as f32,
1233 bearing_x: 0.0,
1234 bearing_y: 0.0,
1235 advance: 0.0,
1236 };
1237 self.atlas_mask.map.insert(keyp, info);
1238 Some(info)
1239 }
1240
1241 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1242 let keyp = (key, px);
1243 if let Some(info) = self.atlas_color.map.get(&keyp) {
1244 return Some(*info);
1245 }
1246 let gb = repose_text::rasterize(key, px as f32)?;
1247 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
1248 return None;
1249 }
1250 let w = gb.w.max(1);
1251 let h = gb.h.max(1);
1252 if !self.alloc_space_color(w, h) {
1253 self.grow_color_and_rebuild();
1254 }
1255 if !self.alloc_space_color(w, h) {
1256 return None;
1257 }
1258 let x = self.atlas_color.next_x;
1259 let y = self.atlas_color.next_y;
1260 self.atlas_color.next_x += w + 1;
1261 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
1262
1263 let layout = wgpu::TexelCopyBufferLayout {
1264 offset: 0,
1265 bytes_per_row: Some(w * 4),
1266 rows_per_image: Some(h),
1267 };
1268 let size = wgpu::Extent3d {
1269 width: w,
1270 height: h,
1271 depth_or_array_layers: 1,
1272 };
1273 self.queue.write_texture(
1274 wgpu::TexelCopyTextureInfoBase {
1275 texture: &self.atlas_color.tex,
1276 mip_level: 0,
1277 origin: wgpu::Origin3d { x, y, z: 0 },
1278 aspect: wgpu::TextureAspect::All,
1279 },
1280 &gb.data,
1281 layout,
1282 size,
1283 );
1284 let info = GlyphInfo {
1285 u0: x as f32 / self.atlas_color.size as f32,
1286 v0: y as f32 / self.atlas_color.size as f32,
1287 u1: (x + w) as f32 / self.atlas_color.size as f32,
1288 v1: (y + h) as f32 / self.atlas_color.size as f32,
1289 w: w as f32,
1290 h: h as f32,
1291 bearing_x: 0.0,
1292 bearing_y: 0.0,
1293 advance: 0.0,
1294 };
1295 self.atlas_color.map.insert(keyp, info);
1296 Some(info)
1297 }
1298
1299 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
1300 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
1301 self.atlas_mask.next_x = 1;
1302 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
1303 self.atlas_mask.row_h = 0;
1304 }
1305 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
1306 return false;
1307 }
1308 true
1309 }
1310
1311 fn grow_mask_and_rebuild(&mut self) {
1312 let new_size = (self.atlas_mask.size * 2).min(4096);
1313 if new_size == self.atlas_mask.size {
1314 return;
1315 }
1316 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1317 label: Some("glyph atlas A8 (grown)"),
1318 size: wgpu::Extent3d {
1319 width: new_size,
1320 height: new_size,
1321 depth_or_array_layers: 1,
1322 },
1323 mip_level_count: 1,
1324 sample_count: 1,
1325 dimension: wgpu::TextureDimension::D2,
1326 format: wgpu::TextureFormat::R8Unorm,
1327 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1328 view_formats: &[],
1329 });
1330 self.atlas_mask.tex = tex;
1331 self.atlas_mask.view = self
1332 .atlas_mask
1333 .tex
1334 .create_view(&wgpu::TextureViewDescriptor::default());
1335 self.atlas_mask.size = new_size;
1336 self.atlas_mask.next_x = 1;
1337 self.atlas_mask.next_y = 1;
1338 self.atlas_mask.row_h = 0;
1339 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
1340 self.atlas_mask.map.clear();
1341 for (k, px) in keys {
1342 let _ = self.upload_glyph_mask(k, px);
1343 }
1344 }
1345
1346 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
1347 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
1348 self.atlas_color.next_x = 1;
1349 self.atlas_color.next_y += self.atlas_color.row_h + 1;
1350 self.atlas_color.row_h = 0;
1351 }
1352 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
1353 return false;
1354 }
1355 true
1356 }
1357
1358 fn grow_color_and_rebuild(&mut self) {
1359 let new_size = (self.atlas_color.size * 2).min(4096);
1360 if new_size == self.atlas_color.size {
1361 return;
1362 }
1363 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1364 label: Some("glyph atlas RGBA (grown)"),
1365 size: wgpu::Extent3d {
1366 width: new_size,
1367 height: new_size,
1368 depth_or_array_layers: 1,
1369 },
1370 mip_level_count: 1,
1371 sample_count: 1,
1372 dimension: wgpu::TextureDimension::D2,
1373 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1374 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1375 view_formats: &[],
1376 });
1377 self.atlas_color.tex = tex;
1378 self.atlas_color.view = self
1379 .atlas_color
1380 .tex
1381 .create_view(&wgpu::TextureViewDescriptor::default());
1382 self.atlas_color.size = new_size;
1383 self.atlas_color.next_x = 1;
1384 self.atlas_color.next_y = 1;
1385 self.atlas_color.row_h = 0;
1386 let keys: Vec<(repose_text::GlyphKey, u32)> =
1387 self.atlas_color.map.keys().copied().collect();
1388 self.atlas_color.map.clear();
1389 for (k, px) in keys {
1390 let _ = self.upload_glyph_color(k, px);
1391 }
1392 }
1393}
1394
1395fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
1396 match brush {
1397 Brush::Solid(c) => (
1398 0u32,
1399 c.to_linear(),
1400 [0.0, 0.0, 0.0, 0.0],
1401 [0.0, 0.0],
1402 [0.0, 1.0],
1403 ),
1404 Brush::Linear {
1405 start,
1406 end,
1407 start_color,
1408 end_color,
1409 } => (
1410 1u32,
1411 start_color.to_linear(),
1412 end_color.to_linear(),
1413 [start.x, start.y],
1414 [end.x, end.y],
1415 ),
1416 }
1417}
1418
1419fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
1420 match brush {
1421 Brush::Solid(c) => c.to_linear(),
1422 Brush::Linear { start_color, .. } => start_color.to_linear(),
1423 }
1424}
1425
1426impl RenderBackend for WgpuBackend {
1427 fn configure_surface(&mut self, width: u32, height: u32) {
1428 if width == 0 || height == 0 {
1429 return;
1430 }
1431 self.config.width = width;
1432 self.config.height = height;
1433 self.surface.configure(&self.device, &self.config);
1434 self.recreate_msaa_and_depth_stencil();
1435 }
1436
1437 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
1438 if self.config.width == 0 || self.config.height == 0 {
1439 return;
1440 }
1441 let frame = loop {
1442 match self.surface.get_current_texture() {
1443 Ok(f) => break f,
1444 Err(wgpu::SurfaceError::Lost) => {
1445 log::warn!("surface lost; reconfiguring");
1446 self.surface.configure(&self.device, &self.config);
1447 }
1448 Err(wgpu::SurfaceError::Outdated) => {
1449 log::warn!("surface outdated; reconfiguring");
1450 self.surface.configure(&self.device, &self.config);
1451 }
1452 Err(wgpu::SurfaceError::Timeout) => {
1453 log::warn!("surface timeout; retrying");
1454 continue;
1455 }
1456 Err(wgpu::SurfaceError::OutOfMemory) => {
1457 log::error!("surface OOM");
1458 return;
1459 }
1460 Err(wgpu::SurfaceError::Other) => {
1461 log::error!("Other error");
1462 return;
1463 }
1464 }
1465 };
1466
1467 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
1468 let x0 = (x / fb_w) * 2.0 - 1.0;
1469 let y0 = 1.0 - (y / fb_h) * 2.0;
1470 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
1471 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
1472 let min_x = x0.min(x1);
1473 let min_y = y0.min(y1);
1474 let w_ndc = (x1 - x0).abs();
1475 let h_ndc = (y1 - y0).abs();
1476 [min_x, min_y, w_ndc, h_ndc]
1477 }
1478
1479 fn to_ndc_scalar(px: f32, fb_dim: f32) -> f32 {
1480 (px / fb_dim) * 2.0
1481 }
1482
1483 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
1484 let mut x = r.x.floor() as i64;
1485 let mut y = r.y.floor() as i64;
1486 let fb_wi = fb_w as i64;
1487 let fb_hi = fb_h as i64;
1488 x = x.clamp(0, fb_wi.saturating_sub(1));
1489 y = y.clamp(0, fb_hi.saturating_sub(1));
1490 let w_req = r.w.ceil().max(1.0) as i64;
1491 let h_req = r.h.ceil().max(1.0) as i64;
1492 let w = (w_req).min(fb_wi - x).max(1);
1493 let h = (h_req).min(fb_hi - y).max(1);
1494 (x as u32, y as u32, w as u32, h as u32)
1495 }
1496
1497 let fb_w = self.config.width as f32;
1498 let fb_h = self.config.height as f32;
1499
1500 let globals = Globals {
1501 ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
1502 _pad: [0.0, 0.0],
1503 };
1504 self.queue
1505 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
1506
1507 enum Cmd {
1508 ClipPush {
1509 off: u64,
1510 cnt: u32,
1511 scissor: (u32, u32, u32, u32),
1512 },
1513 ClipPop {
1514 scissor: (u32, u32, u32, u32),
1515 },
1516 Rect {
1517 off: u64,
1518 cnt: u32,
1519 },
1520 Border {
1521 off: u64,
1522 cnt: u32,
1523 },
1524 Ellipse {
1525 off: u64,
1526 cnt: u32,
1527 },
1528 EllipseBorder {
1529 off: u64,
1530 cnt: u32,
1531 },
1532 GlyphsMask {
1533 off: u64,
1534 cnt: u32,
1535 },
1536 GlyphsColor {
1537 off: u64,
1538 cnt: u32,
1539 },
1540 Image {
1541 off: u64,
1542 cnt: u32,
1543 handle: u64,
1544 },
1545 PushTransform(Transform),
1546 PopTransform,
1547 }
1548
1549 let mut cmds: Vec<Cmd> = Vec::with_capacity(scene.nodes.len());
1550
1551 struct Batch {
1552 rects: Vec<RectInstance>,
1553 borders: Vec<BorderInstance>,
1554 ellipses: Vec<EllipseInstance>,
1555 e_borders: Vec<EllipseBorderInstance>,
1556 masks: Vec<GlyphInstance>,
1557 colors: Vec<GlyphInstance>,
1558 }
1559
1560 impl Batch {
1561 fn new() -> Self {
1562 Self {
1563 rects: vec![],
1564 borders: vec![],
1565 ellipses: vec![],
1566 e_borders: vec![],
1567 masks: vec![],
1568 colors: vec![],
1569 }
1570 }
1571
1572 fn flush(
1573 &mut self,
1574 rings: (
1575 &mut UploadRing,
1576 &mut UploadRing,
1577 &mut UploadRing,
1578 &mut UploadRing,
1579 &mut UploadRing,
1580 &mut UploadRing,
1581 ),
1582 device: &wgpu::Device,
1583 queue: &wgpu::Queue,
1584 cmds: &mut Vec<Cmd>,
1585 ) {
1586 let (
1587 ring_rect,
1588 ring_border,
1589 ring_ellipse,
1590 ring_ellipse_border,
1591 ring_mask,
1592 ring_color,
1593 ) = rings;
1594
1595 if !self.rects.is_empty() {
1596 let bytes = bytemuck::cast_slice(&self.rects);
1597 ring_rect.grow_to_fit(device, bytes.len() as u64);
1598 let (off, wrote) = ring_rect.alloc_write(queue, bytes);
1599 debug_assert_eq!(wrote as usize, bytes.len());
1600 cmds.push(Cmd::Rect {
1601 off,
1602 cnt: self.rects.len() as u32,
1603 });
1604 self.rects.clear();
1605 }
1606 if !self.borders.is_empty() {
1607 let bytes = bytemuck::cast_slice(&self.borders);
1608 ring_border.grow_to_fit(device, bytes.len() as u64);
1609 let (off, wrote) = ring_border.alloc_write(queue, bytes);
1610 debug_assert_eq!(wrote as usize, bytes.len());
1611 cmds.push(Cmd::Border {
1612 off,
1613 cnt: self.borders.len() as u32,
1614 });
1615 self.borders.clear();
1616 }
1617 if !self.ellipses.is_empty() {
1618 let bytes = bytemuck::cast_slice(&self.ellipses);
1619 ring_ellipse.grow_to_fit(device, bytes.len() as u64);
1620 let (off, wrote) = ring_ellipse.alloc_write(queue, bytes);
1621 debug_assert_eq!(wrote as usize, bytes.len());
1622 cmds.push(Cmd::Ellipse {
1623 off,
1624 cnt: self.ellipses.len() as u32,
1625 });
1626 self.ellipses.clear();
1627 }
1628 if !self.e_borders.is_empty() {
1629 let bytes = bytemuck::cast_slice(&self.e_borders);
1630 ring_ellipse_border.grow_to_fit(device, bytes.len() as u64);
1631 let (off, wrote) = ring_ellipse_border.alloc_write(queue, bytes);
1632 debug_assert_eq!(wrote as usize, bytes.len());
1633 cmds.push(Cmd::EllipseBorder {
1634 off,
1635 cnt: self.e_borders.len() as u32,
1636 });
1637 self.e_borders.clear();
1638 }
1639 if !self.masks.is_empty() {
1640 let bytes = bytemuck::cast_slice(&self.masks);
1641 ring_mask.grow_to_fit(device, bytes.len() as u64);
1642 let (off, wrote) = ring_mask.alloc_write(queue, bytes);
1643 debug_assert_eq!(wrote as usize, bytes.len());
1644 cmds.push(Cmd::GlyphsMask {
1645 off,
1646 cnt: self.masks.len() as u32,
1647 });
1648 self.masks.clear();
1649 }
1650 if !self.colors.is_empty() {
1651 let bytes = bytemuck::cast_slice(&self.colors);
1652 ring_color.grow_to_fit(device, bytes.len() as u64);
1653 let (off, wrote) = ring_color.alloc_write(queue, bytes);
1654 debug_assert_eq!(wrote as usize, bytes.len());
1655 cmds.push(Cmd::GlyphsColor {
1656 off,
1657 cnt: self.colors.len() as u32,
1658 });
1659 self.colors.clear();
1660 }
1661 }
1662 }
1663
1664 self.ring_rect.reset();
1665 self.ring_border.reset();
1666 self.ring_ellipse.reset();
1667 self.ring_ellipse_border.reset();
1668 self.ring_glyph_mask.reset();
1669 self.ring_glyph_color.reset();
1670 self.ring_clip.reset();
1671
1672 let mut batch = Batch::new();
1673 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
1674 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
1675 let root_clip_rect = repose_core::Rect {
1676 x: 0.0,
1677 y: 0.0,
1678 w: fb_w,
1679 h: fb_h,
1680 };
1681
1682 for node in &scene.nodes {
1683 let t_identity = Transform::identity();
1684 let current_transform = transform_stack.last().unwrap_or(&t_identity);
1685
1686 match node {
1687 SceneNode::Rect {
1688 rect,
1689 brush,
1690 radius,
1691 } => {
1692 let transformed_rect = current_transform.apply_to_rect(*rect);
1693 let (brush_type, color0, color1, grad_start, grad_end) =
1694 brush_to_instance_fields(brush);
1695 batch.rects.push(RectInstance {
1696 xywh: to_ndc(
1697 transformed_rect.x,
1698 transformed_rect.y,
1699 transformed_rect.w,
1700 transformed_rect.h,
1701 fb_w,
1702 fb_h,
1703 ),
1704 radius: *radius,
1705 brush_type,
1706 color0,
1707 color1,
1708 grad_start,
1709 grad_end,
1710 });
1711 }
1712 SceneNode::Border {
1713 rect,
1714 color,
1715 width,
1716 radius,
1717 } => {
1718 let transformed_rect = current_transform.apply_to_rect(*rect);
1719 batch.borders.push(BorderInstance {
1720 xywh: to_ndc(
1721 transformed_rect.x,
1722 transformed_rect.y,
1723 transformed_rect.w,
1724 transformed_rect.h,
1725 fb_w,
1726 fb_h,
1727 ),
1728 radius: *radius,
1729 stroke: *width,
1730 color: color.to_linear(),
1731 });
1732 }
1733 SceneNode::Ellipse { rect, brush } => {
1734 let transformed = current_transform.apply_to_rect(*rect);
1735 let color = brush_to_solid_color(brush);
1736 batch.ellipses.push(EllipseInstance {
1737 xywh: to_ndc(
1738 transformed.x,
1739 transformed.y,
1740 transformed.w,
1741 transformed.h,
1742 fb_w,
1743 fb_h,
1744 ),
1745 color,
1746 });
1747 }
1748 SceneNode::EllipseBorder { rect, color, width } => {
1749 let transformed = current_transform.apply_to_rect(*rect);
1750 batch.e_borders.push(EllipseBorderInstance {
1751 xywh: to_ndc(
1752 transformed.x,
1753 transformed.y,
1754 transformed.w,
1755 transformed.h,
1756 fb_w,
1757 fb_h,
1758 ),
1759 stroke: *width,
1760 color: color.to_linear(),
1761 });
1762 }
1763 SceneNode::Text {
1764 rect,
1765 text,
1766 color,
1767 size,
1768 } => {
1769 let px = (*size).clamp(8.0, 96.0);
1770 let shaped = repose_text::shape_line(text, px);
1771 let transformed_rect = current_transform.apply_to_rect(*rect);
1772
1773 for sg in shaped {
1774 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
1775 let x = transformed_rect.x + sg.x + sg.bearing_x;
1776 let y = transformed_rect.y + sg.y - sg.bearing_y;
1777 batch.colors.push(GlyphInstance {
1778 xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1779 uv: [info.u0, info.v1, info.u1, info.v0],
1780 color: [1.0, 1.0, 1.0, 1.0],
1781 });
1782 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
1783 let x = transformed_rect.x + sg.x + sg.bearing_x;
1784 let y = transformed_rect.y + sg.y - sg.bearing_y;
1785 batch.masks.push(GlyphInstance {
1786 xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1787 uv: [info.u0, info.v1, info.u1, info.v0],
1788 color: color.to_linear(),
1789 });
1790 }
1791 }
1792 }
1793 SceneNode::Image {
1794 rect,
1795 handle,
1796 tint,
1797 fit,
1798 } => {
1799 let tex = if let Some(t) = self.images.get(handle) {
1800 t
1801 } else {
1802 log::warn!("Image handle {} not found", handle);
1803 continue;
1804 };
1805 let src_w = tex.w as f32;
1806 let src_h = tex.h as f32;
1807 let dst_w = rect.w.max(0.0);
1808 let dst_h = rect.h.max(0.0);
1809 if dst_w <= 0.0 || dst_h <= 0.0 {
1810 continue;
1811 }
1812 let (xywh_ndc, uv_rect) = match fit {
1813 repose_core::view::ImageFit::Contain => {
1814 let scale = (dst_w / src_w).min(dst_h / src_h);
1815 let w = src_w * scale;
1816 let h = src_h * scale;
1817 let x = rect.x + (dst_w - w) * 0.5;
1818 let y = rect.y + (dst_h - h) * 0.5;
1819 (to_ndc(x, y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1820 }
1821 repose_core::view::ImageFit::Cover => {
1822 let scale = (dst_w / src_w).max(dst_h / src_h);
1823 let content_w = src_w * scale;
1824 let content_h = src_h * scale;
1825 let overflow_x = (content_w - dst_w) * 0.5;
1826 let overflow_y = (content_h - dst_h) * 0.5;
1827 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
1828 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
1829 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
1830 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
1831 (
1832 to_ndc(rect.x, rect.y, dst_w, dst_h, fb_w, fb_h),
1833 [u0, 1.0 - v1, u1, 1.0 - v0],
1834 )
1835 }
1836 repose_core::view::ImageFit::FitWidth => {
1837 let scale = dst_w / src_w;
1838 let w = dst_w;
1839 let h = src_h * scale;
1840 let y = rect.y + (dst_h - h) * 0.5;
1841 (to_ndc(rect.x, y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1842 }
1843 repose_core::view::ImageFit::FitHeight => {
1844 let scale = dst_h / src_h;
1845 let w = src_w * scale;
1846 let h = dst_h;
1847 let x = rect.x + (dst_w - w) * 0.5;
1848 (to_ndc(x, rect.y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1849 }
1850 };
1851 let inst = GlyphInstance {
1852 xywh: xywh_ndc,
1853 uv: uv_rect,
1854 color: tint.to_linear(),
1855 };
1856 let bytes = bytemuck::bytes_of(&inst);
1857 self.ring_glyph_color
1858 .grow_to_fit(&self.device, bytes.len() as u64);
1859 let (off, wrote) = self.ring_glyph_color.alloc_write(&self.queue, bytes);
1860 debug_assert_eq!(wrote as usize, bytes.len());
1861 batch.flush(
1862 (
1863 &mut self.ring_rect,
1864 &mut self.ring_border,
1865 &mut self.ring_ellipse,
1866 &mut self.ring_ellipse_border,
1867 &mut self.ring_glyph_mask,
1868 &mut self.ring_glyph_color,
1869 ),
1870 &self.device,
1871 &self.queue,
1872 &mut cmds,
1873 );
1874 cmds.push(Cmd::Image {
1875 off,
1876 cnt: 1,
1877 handle: *handle,
1878 });
1879 }
1880 SceneNode::PushClip { rect, radius } => {
1881 batch.flush(
1882 (
1883 &mut self.ring_rect,
1884 &mut self.ring_border,
1885 &mut self.ring_ellipse,
1886 &mut self.ring_ellipse_border,
1887 &mut self.ring_glyph_mask,
1888 &mut self.ring_glyph_color,
1889 ),
1890 &self.device,
1891 &self.queue,
1892 &mut cmds,
1893 );
1894
1895 let t_identity = Transform::identity();
1896 let current_transform = transform_stack.last().unwrap_or(&t_identity);
1897 let transformed = current_transform.apply_to_rect(*rect);
1898
1899 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
1900 let next_scissor = intersect(top, transformed);
1901 scissor_stack.push(next_scissor);
1902 let scissor = to_scissor(&next_scissor, self.config.width, self.config.height);
1903
1904 let inst = ClipInstance {
1905 xywh: to_ndc(
1906 transformed.x,
1907 transformed.y,
1908 transformed.w,
1909 transformed.h,
1910 fb_w,
1911 fb_h,
1912 ),
1913 radius: *radius,
1914 _pad: [0.0; 3],
1915 };
1916 let bytes = bytemuck::bytes_of(&inst);
1917 self.ring_clip.grow_to_fit(&self.device, bytes.len() as u64);
1918 let (off, wrote) = self.ring_clip.alloc_write(&self.queue, bytes);
1919 debug_assert_eq!(wrote as usize, bytes.len());
1920
1921 cmds.push(Cmd::ClipPush {
1922 off,
1923 cnt: 1,
1924 scissor,
1925 });
1926 }
1927 SceneNode::PopClip => {
1928 batch.flush(
1929 (
1930 &mut self.ring_rect,
1931 &mut self.ring_border,
1932 &mut self.ring_ellipse,
1933 &mut self.ring_ellipse_border,
1934 &mut self.ring_glyph_mask,
1935 &mut self.ring_glyph_color,
1936 ),
1937 &self.device,
1938 &self.queue,
1939 &mut cmds,
1940 );
1941
1942 if !scissor_stack.is_empty() {
1943 scissor_stack.pop();
1944 } else {
1945 log::warn!("PopClip with empty stack");
1946 }
1947
1948 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
1949 let scissor = to_scissor(&top, self.config.width, self.config.height);
1950 cmds.push(Cmd::ClipPop { scissor });
1951 }
1952 SceneNode::PushTransform { transform } => {
1953 let combined = current_transform.combine(transform);
1954 if transform.rotate != 0.0 {
1955 ROT_WARN_ONCE.call_once(|| {
1956 log::warn!(
1957 "Transform rotation is not supported for Rect/Text/Image; rotation will be ignored."
1958 );
1959 });
1960 }
1961 transform_stack.push(combined);
1962 }
1963 SceneNode::PopTransform => {
1964 transform_stack.pop();
1965 }
1966 }
1967 }
1968
1969 batch.flush(
1970 (
1971 &mut self.ring_rect,
1972 &mut self.ring_border,
1973 &mut self.ring_ellipse,
1974 &mut self.ring_ellipse_border,
1975 &mut self.ring_glyph_mask,
1976 &mut self.ring_glyph_color,
1977 ),
1978 &self.device,
1979 &self.queue,
1980 &mut cmds,
1981 );
1982
1983 let mut encoder = self
1984 .device
1985 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1986 label: Some("frame encoder"),
1987 });
1988
1989 {
1990 let swap_view = frame
1991 .texture
1992 .create_view(&wgpu::TextureViewDescriptor::default());
1993
1994 let (color_view, resolve_target) = if let Some(msaa_view) = &self.msaa_view {
1995 (msaa_view, Some(&swap_view))
1996 } else {
1997 (&swap_view, None)
1998 };
1999
2000 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2001 label: Some("main pass"),
2002 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2003 view: color_view,
2004 resolve_target,
2005 ops: wgpu::Operations {
2006 load: wgpu::LoadOp::Clear(wgpu::Color {
2007 r: scene.clear_color.0 as f64 / 255.0,
2008 g: scene.clear_color.1 as f64 / 255.0,
2009 b: scene.clear_color.2 as f64 / 255.0,
2010 a: scene.clear_color.3 as f64 / 255.0,
2011 }),
2012 store: wgpu::StoreOp::Store,
2013 },
2014 depth_slice: None,
2015 })],
2016 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2017 view: &self.depth_stencil_view,
2018 depth_ops: None,
2019 stencil_ops: Some(wgpu::Operations {
2020 load: wgpu::LoadOp::Clear(0),
2021 store: wgpu::StoreOp::Store,
2022 }),
2023 }),
2024 timestamp_writes: None,
2025 occlusion_query_set: None,
2026 multiview_mask: None,
2027 });
2028
2029 let mut clip_depth: u32 = 0;
2030 rpass.set_bind_group(0, &self.globals_bind, &[]);
2031 rpass.set_stencil_reference(clip_depth);
2032 rpass.set_scissor_rect(0, 0, self.config.width, self.config.height);
2033
2034 let bind_mask = self.atlas_bind_group_mask();
2035 let bind_color = self.atlas_bind_group_color();
2036
2037 for cmd in cmds {
2038 match cmd {
2039 Cmd::ClipPush {
2040 off,
2041 cnt: n,
2042 scissor,
2043 } => {
2044 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2045 rpass.set_stencil_reference(clip_depth);
2046
2047 if self.msaa_samples > 1 {
2048 rpass.set_pipeline(&self.clip_pipeline_a2c);
2049 } else {
2050 rpass.set_pipeline(&self.clip_pipeline_bin);
2051 }
2052
2053 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
2054 rpass.set_vertex_buffer(0, self.ring_clip.buf.slice(off..off + bytes));
2055 rpass.draw(0..6, 0..n);
2056
2057 clip_depth = (clip_depth + 1).min(255);
2058 rpass.set_stencil_reference(clip_depth);
2059 }
2060
2061 Cmd::ClipPop { scissor } => {
2062 clip_depth = clip_depth.saturating_sub(1);
2063 rpass.set_stencil_reference(clip_depth);
2064 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2065 }
2066
2067 Cmd::Rect { off, cnt: n } => {
2068 rpass.set_pipeline(&self.rect_pipeline);
2069 let bytes = (n as u64) * std::mem::size_of::<RectInstance>() as u64;
2070 rpass.set_vertex_buffer(0, self.ring_rect.buf.slice(off..off + bytes));
2071 rpass.draw(0..6, 0..n);
2072 }
2073
2074 Cmd::Border { off, cnt: n } => {
2075 rpass.set_pipeline(&self.border_pipeline);
2076 let bytes = (n as u64) * std::mem::size_of::<BorderInstance>() as u64;
2077 rpass.set_vertex_buffer(0, self.ring_border.buf.slice(off..off + bytes));
2078 rpass.draw(0..6, 0..n);
2079 }
2080
2081 Cmd::GlyphsMask { off, cnt: n } => {
2082 rpass.set_pipeline(&self.text_pipeline_mask);
2083 rpass.set_bind_group(1, &bind_mask, &[]);
2084 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2085 rpass
2086 .set_vertex_buffer(0, self.ring_glyph_mask.buf.slice(off..off + bytes));
2087 rpass.draw(0..6, 0..n);
2088 }
2089
2090 Cmd::GlyphsColor { off, cnt: n } => {
2091 rpass.set_pipeline(&self.text_pipeline_color);
2092 rpass.set_bind_group(1, &bind_color, &[]);
2093 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2094 rpass.set_vertex_buffer(
2095 0,
2096 self.ring_glyph_color.buf.slice(off..off + bytes),
2097 );
2098 rpass.draw(0..6, 0..n);
2099 }
2100
2101 Cmd::Image {
2102 off,
2103 cnt: n,
2104 handle,
2105 } => {
2106 if let Some(tex) = self.images.get(&handle) {
2107 rpass.set_pipeline(&self.text_pipeline_color);
2108 rpass.set_bind_group(1, &tex.bind, &[]);
2109 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2110 rpass.set_vertex_buffer(
2111 0,
2112 self.ring_glyph_color.buf.slice(off..off + bytes),
2113 );
2114 rpass.draw(0..6, 0..n);
2115 } else {
2116 log::warn!("Image handle {} not found; skipping draw", handle);
2117 }
2118 }
2119
2120 Cmd::Ellipse { off, cnt: n } => {
2121 rpass.set_pipeline(&self.ellipse_pipeline);
2122 let bytes = (n as u64) * std::mem::size_of::<EllipseInstance>() as u64;
2123 rpass.set_vertex_buffer(0, self.ring_ellipse.buf.slice(off..off + bytes));
2124 rpass.draw(0..6, 0..n);
2125 }
2126
2127 Cmd::EllipseBorder { off, cnt: n } => {
2128 rpass.set_pipeline(&self.ellipse_border_pipeline);
2129 let bytes =
2130 (n as u64) * std::mem::size_of::<EllipseBorderInstance>() as u64;
2131 rpass.set_vertex_buffer(
2132 0,
2133 self.ring_ellipse_border.buf.slice(off..off + bytes),
2134 );
2135 rpass.draw(0..6, 0..n);
2136 }
2137
2138 Cmd::PushTransform(_) => {}
2139 Cmd::PopTransform => {}
2140 }
2141 }
2142 }
2143
2144 self.queue.submit(std::iter::once(encoder.finish()));
2145 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
2146 log::warn!("frame.present panicked: {:?}", e);
2147 }
2148 }
2149}
2150
2151fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
2152 let x0 = a.x.max(b.x);
2153 let y0 = a.y.max(b.y);
2154 let x1 = (a.x + a.w).min(b.x + b.w);
2155 let y1 = (a.y + a.h).min(b.y + b.h);
2156 repose_core::Rect {
2157 x: x0,
2158 y: y0,
2159 w: (x1 - x0).max(0.0),
2160 h: (y1 - y0).max(0.0),
2161 }
2162}