1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::num::NonZeroU32;
4use std::sync::Arc;
5
6use ab_glyph::{Font, FontArc, Glyph, PxScale, ScaleFont, point};
7use cosmic_text;
8use fontdb::Database;
9use repose_core::{Color, GlyphRasterConfig, RenderBackend, Scene, SceneNode, Transform};
10use std::panic::{AssertUnwindSafe, catch_unwind};
11use wgpu::util::DeviceExt;
12
13struct UploadRing {
14 buf: wgpu::Buffer,
15 cap: u64,
16 head: u64,
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 fn reset(&mut self) {
29 self.head = 0;
30 }
31 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
32 let len = bytes.len() as u64;
33 let align = 4u64; let start = (self.head + (align - 1)) & !(align - 1);
35 let end = start + len;
36 if end > self.cap {
37 self.head = 0;
39 let start = 0;
40 let end = len.min(self.cap);
41 queue.write_buffer(&self.buf, start, &bytes[0..end as usize]);
42 self.head = end;
43 (start, len.min(self.cap - start))
44 } else {
45 queue.write_buffer(&self.buf, start, bytes);
46 self.head = end;
47 (start, len)
48 }
49 }
50}
51
52pub struct WgpuBackend {
53 surface: wgpu::Surface<'static>,
54 device: wgpu::Device,
55 queue: wgpu::Queue,
56 config: wgpu::SurfaceConfiguration,
57
58 rect_pipeline: wgpu::RenderPipeline,
59 border_pipeline: wgpu::RenderPipeline,
61 text_pipeline_mask: wgpu::RenderPipeline,
63 text_pipeline_color: wgpu::RenderPipeline,
64 text_bind_layout: wgpu::BindGroupLayout,
65
66 atlas_mask: AtlasA8,
68 atlas_color: AtlasRGBA,
69
70 ring_rect: UploadRing,
72 ring_border: UploadRing,
73 ring_glyph_mask: UploadRing,
74 ring_glyph_color: UploadRing,
75}
76
77struct AtlasA8 {
78 tex: wgpu::Texture,
79 view: wgpu::TextureView,
80 sampler: wgpu::Sampler,
81 size: u32,
82 next_x: u32,
83 next_y: u32,
84 row_h: u32,
85 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
86}
87
88struct AtlasRGBA {
89 tex: wgpu::Texture,
90 view: wgpu::TextureView,
91 sampler: wgpu::Sampler,
92 size: u32,
93 next_x: u32,
94 next_y: u32,
95 row_h: u32,
96 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
97}
98
99#[derive(Clone, Copy)]
100struct GlyphInfo {
101 u0: f32,
102 v0: f32,
103 u1: f32,
104 v1: f32,
105 w: f32,
106 h: f32,
107 bearing_x: f32,
108 bearing_y: f32,
109 advance: f32,
110}
111
112#[repr(C)]
113#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
114struct RectInstance {
115 xywh: [f32; 4],
117 radius: f32,
119 color: [f32; 4],
121}
122
123#[repr(C)]
124#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
125struct BorderInstance {
126 xywh: [f32; 4],
128 radius_outer: f32,
130 stroke: f32,
132 color: [f32; 4],
134}
135
136#[repr(C)]
137#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
138struct GlyphInstance {
139 xywh: [f32; 4],
141 uv: [f32; 4],
143 color: [f32; 4],
145}
146
147impl WgpuBackend {
148 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
149 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::from_env_or_default());
151 let surface = instance.create_surface(window.clone())?;
152
153 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
155 power_preference: wgpu::PowerPreference::HighPerformance,
156 compatible_surface: Some(&surface),
157 force_fallback_adapter: false,
158 }))
159 .map_err(|_e| anyhow::anyhow!("No adapter"))?;
160
161 let (device, queue) =
162 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
163 label: Some("repose-rs device"),
164 required_features: wgpu::Features::empty(),
165 required_limits: wgpu::Limits::default(),
166 experimental_features: wgpu::ExperimentalFeatures::disabled(),
167 memory_hints: wgpu::MemoryHints::default(),
168 trace: wgpu::Trace::Off,
169 }))?;
170
171 let size = window.inner_size();
172
173 let caps = surface.get_capabilities(&adapter);
174 let format = caps
175 .formats
176 .iter()
177 .copied()
178 .find(|f| f.is_srgb()) .unwrap_or(caps.formats[0]);
180 let present_mode = caps
181 .present_modes
182 .iter()
183 .copied()
184 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
185 .unwrap_or(wgpu::PresentMode::Fifo);
186 let alpha_mode = caps.alpha_modes[0];
187
188 let config = wgpu::SurfaceConfiguration {
189 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
190 format,
191 width: size.width.max(1),
192 height: size.height.max(1),
193 present_mode,
194 alpha_mode,
195 view_formats: vec![],
196 desired_maximum_frame_latency: 2,
197 };
198 surface.configure(&device, &config);
199
200 let rect_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
202 label: Some("rect.wgsl"),
203 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/rect.wgsl"))),
204 });
205 let rect_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
206 label: Some("rect bind layout"),
207 entries: &[],
208 });
209 let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
210 label: Some("rect pipeline layout"),
211 bind_group_layouts: &[], push_constant_ranges: &[],
213 });
214 let rect_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
215 label: Some("rect pipeline"),
216 layout: Some(&rect_pipeline_layout),
217 vertex: wgpu::VertexState {
218 module: &rect_shader,
219 entry_point: Some("vs_main"),
220 buffers: &[wgpu::VertexBufferLayout {
221 array_stride: std::mem::size_of::<RectInstance>() as u64,
222 step_mode: wgpu::VertexStepMode::Instance,
223 attributes: &[
224 wgpu::VertexAttribute {
226 shader_location: 0,
227 offset: 0,
228 format: wgpu::VertexFormat::Float32x4,
229 },
230 wgpu::VertexAttribute {
232 shader_location: 1,
233 offset: 16,
234 format: wgpu::VertexFormat::Float32,
235 },
236 wgpu::VertexAttribute {
238 shader_location: 2,
239 offset: 20,
240 format: wgpu::VertexFormat::Float32x4,
241 },
242 ],
243 }],
244 compilation_options: wgpu::PipelineCompilationOptions::default(),
245 },
246 fragment: Some(wgpu::FragmentState {
247 module: &rect_shader,
248 entry_point: Some("fs_main"),
249 targets: &[Some(wgpu::ColorTargetState {
250 format: config.format,
251 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
252 write_mask: wgpu::ColorWrites::ALL,
253 })],
254 compilation_options: wgpu::PipelineCompilationOptions::default(),
255 }),
256 primitive: wgpu::PrimitiveState::default(),
257 depth_stencil: None,
258 multisample: wgpu::MultisampleState::default(),
259 multiview: None,
260 cache: None,
261 });
262
263 let border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
265 label: Some("border.wgsl"),
266 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/border.wgsl"))),
267 });
268 let border_bind_layout =
269 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
270 label: Some("border bind layout"),
271 entries: &[],
272 });
273 let border_pipeline_layout =
274 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
275 label: Some("border pipeline layout"),
276 bind_group_layouts: &[], push_constant_ranges: &[],
278 });
279 let border_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
280 label: Some("border pipeline"),
281 layout: Some(&border_pipeline_layout),
282 vertex: wgpu::VertexState {
283 module: &border_shader,
284 entry_point: Some("vs_main"),
285 buffers: &[wgpu::VertexBufferLayout {
286 array_stride: std::mem::size_of::<BorderInstance>() as u64,
287 step_mode: wgpu::VertexStepMode::Instance,
288 attributes: &[
289 wgpu::VertexAttribute {
291 shader_location: 0,
292 offset: 0,
293 format: wgpu::VertexFormat::Float32x4,
294 },
295 wgpu::VertexAttribute {
297 shader_location: 1,
298 offset: 16,
299 format: wgpu::VertexFormat::Float32,
300 },
301 wgpu::VertexAttribute {
303 shader_location: 2,
304 offset: 20,
305 format: wgpu::VertexFormat::Float32,
306 },
307 wgpu::VertexAttribute {
309 shader_location: 3,
310 offset: 24,
311 format: wgpu::VertexFormat::Float32x4,
312 },
313 ],
314 }],
315 compilation_options: wgpu::PipelineCompilationOptions::default(),
316 },
317 fragment: Some(wgpu::FragmentState {
318 module: &border_shader,
319 entry_point: Some("fs_main"),
320 targets: &[Some(wgpu::ColorTargetState {
321 format: config.format,
322 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
323 write_mask: wgpu::ColorWrites::ALL,
324 })],
325 compilation_options: wgpu::PipelineCompilationOptions::default(),
326 }),
327 primitive: wgpu::PrimitiveState::default(),
328 depth_stencil: None,
329 multisample: wgpu::MultisampleState::default(),
330 multiview: None,
331 cache: None,
332 });
333
334 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
336 label: Some("text.wgsl"),
337 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
338 });
339 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
340 label: Some("text_color.wgsl"),
341 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
342 "shaders/text_color.wgsl"
343 ))),
344 });
345 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
346 label: Some("text bind layout"),
347 entries: &[
348 wgpu::BindGroupLayoutEntry {
349 binding: 0,
350 visibility: wgpu::ShaderStages::FRAGMENT,
351 ty: wgpu::BindingType::Texture {
352 multisampled: false,
353 view_dimension: wgpu::TextureViewDimension::D2,
354 sample_type: wgpu::TextureSampleType::Float { filterable: true },
355 },
356 count: None,
357 },
358 wgpu::BindGroupLayoutEntry {
359 binding: 1,
360 visibility: wgpu::ShaderStages::FRAGMENT,
361 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
362 count: None,
363 },
364 ],
365 });
366 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
367 label: Some("text pipeline layout"),
368 bind_group_layouts: &[&text_bind_layout],
369 push_constant_ranges: &[],
370 });
371 let text_pipeline_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
372 label: Some("text pipeline (mask)"),
373 layout: Some(&text_pipeline_layout),
374 vertex: wgpu::VertexState {
375 module: &text_mask_shader,
376 entry_point: Some("vs_main"),
377 buffers: &[wgpu::VertexBufferLayout {
378 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
379 step_mode: wgpu::VertexStepMode::Instance,
380 attributes: &[
381 wgpu::VertexAttribute {
382 shader_location: 0,
383 offset: 0,
384 format: wgpu::VertexFormat::Float32x4,
385 },
386 wgpu::VertexAttribute {
387 shader_location: 1,
388 offset: 16,
389 format: wgpu::VertexFormat::Float32x4,
390 },
391 wgpu::VertexAttribute {
392 shader_location: 2,
393 offset: 32,
394 format: wgpu::VertexFormat::Float32x4,
395 },
396 ],
397 }],
398 compilation_options: wgpu::PipelineCompilationOptions::default(),
399 },
400 fragment: Some(wgpu::FragmentState {
401 module: &text_mask_shader,
402 entry_point: Some("fs_main"),
403 targets: &[Some(wgpu::ColorTargetState {
404 format: config.format,
405 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
406 write_mask: wgpu::ColorWrites::ALL,
407 })],
408 compilation_options: wgpu::PipelineCompilationOptions::default(),
409 }),
410 primitive: wgpu::PrimitiveState::default(),
411 depth_stencil: None,
412 multisample: wgpu::MultisampleState::default(),
413 multiview: None,
414 cache: None,
415 });
416 let text_pipeline_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
417 label: Some("text pipeline (color)"),
418 layout: Some(&text_pipeline_layout),
419 vertex: wgpu::VertexState {
420 module: &text_color_shader,
421 entry_point: Some("vs_main"),
422 buffers: &[wgpu::VertexBufferLayout {
423 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
424 step_mode: wgpu::VertexStepMode::Instance,
425 attributes: &[
426 wgpu::VertexAttribute {
427 shader_location: 0,
428 offset: 0,
429 format: wgpu::VertexFormat::Float32x4,
430 },
431 wgpu::VertexAttribute {
432 shader_location: 1,
433 offset: 16,
434 format: wgpu::VertexFormat::Float32x4,
435 },
436 wgpu::VertexAttribute {
437 shader_location: 2,
438 offset: 32,
439 format: wgpu::VertexFormat::Float32x4,
440 },
441 ],
442 }],
443 compilation_options: wgpu::PipelineCompilationOptions::default(),
444 },
445 fragment: Some(wgpu::FragmentState {
446 module: &text_color_shader,
447 entry_point: Some("fs_main"),
448 targets: &[Some(wgpu::ColorTargetState {
449 format: config.format,
450 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
451 write_mask: wgpu::ColorWrites::ALL,
452 })],
453 compilation_options: wgpu::PipelineCompilationOptions::default(),
454 }),
455 primitive: wgpu::PrimitiveState::default(),
456 depth_stencil: None,
457 multisample: wgpu::MultisampleState::default(),
458 multiview: None,
459 cache: None,
460 });
461
462 let atlas_mask = Self::init_atlas_mask(&device)?;
464 let atlas_color = Self::init_atlas_color(&device)?;
465
466 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20); let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
469 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
470 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
471
472 Ok(Self {
473 surface,
474 device,
475 queue,
476 config,
477 rect_pipeline,
478 border_pipeline,
480 text_pipeline_mask,
482 text_pipeline_color,
483 text_bind_layout,
484 atlas_mask,
485 atlas_color,
486 ring_rect,
487 ring_border,
488 ring_glyph_color,
489 ring_glyph_mask,
490 })
491 }
492
493 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
494 let size = 1024u32;
495 let tex = device.create_texture(&wgpu::TextureDescriptor {
496 label: Some("glyph atlas A8"),
497 size: wgpu::Extent3d {
498 width: size,
499 height: size,
500 depth_or_array_layers: 1,
501 },
502 mip_level_count: 1,
503 sample_count: 1,
504 dimension: wgpu::TextureDimension::D2,
505 format: wgpu::TextureFormat::R8Unorm,
506 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
507 view_formats: &[],
508 });
509 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
510 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
511 label: Some("glyph atlas sampler A8"),
512 address_mode_u: wgpu::AddressMode::ClampToEdge,
513 address_mode_v: wgpu::AddressMode::ClampToEdge,
514 address_mode_w: wgpu::AddressMode::ClampToEdge,
515 mag_filter: wgpu::FilterMode::Linear,
516 min_filter: wgpu::FilterMode::Linear,
517 mipmap_filter: wgpu::FilterMode::Linear,
518 ..Default::default()
519 });
520
521 Ok(AtlasA8 {
522 tex,
523 view,
524 sampler,
525 size,
526 next_x: 1,
527 next_y: 1,
528 row_h: 0,
529 map: HashMap::new(),
530 })
531 }
532
533 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
534 let size = 1024u32;
535 let tex = device.create_texture(&wgpu::TextureDescriptor {
536 label: Some("glyph atlas RGBA"),
537 size: wgpu::Extent3d {
538 width: size,
539 height: size,
540 depth_or_array_layers: 1,
541 },
542 mip_level_count: 1,
543 sample_count: 1,
544 dimension: wgpu::TextureDimension::D2,
545 format: wgpu::TextureFormat::Rgba8UnormSrgb,
546 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
547 view_formats: &[],
548 });
549 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
550 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
551 label: Some("glyph atlas sampler RGBA"),
552 address_mode_u: wgpu::AddressMode::ClampToEdge,
553 address_mode_v: wgpu::AddressMode::ClampToEdge,
554 address_mode_w: wgpu::AddressMode::ClampToEdge,
555 mag_filter: wgpu::FilterMode::Linear,
556 min_filter: wgpu::FilterMode::Linear,
557 mipmap_filter: wgpu::FilterMode::Linear,
558 ..Default::default()
559 });
560 Ok(AtlasRGBA {
561 tex,
562 view,
563 sampler,
564 size,
565 next_x: 1,
566 next_y: 1,
567 row_h: 0,
568 map: HashMap::new(),
569 })
570 }
571
572 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
573 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
574 label: Some("atlas bind"),
575 layout: &self.text_bind_layout,
576 entries: &[
577 wgpu::BindGroupEntry {
578 binding: 0,
579 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
580 },
581 wgpu::BindGroupEntry {
582 binding: 1,
583 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
584 },
585 ],
586 })
587 }
588 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
589 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
590 label: Some("atlas bind color"),
591 layout: &self.text_bind_layout,
592 entries: &[
593 wgpu::BindGroupEntry {
594 binding: 0,
595 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
596 },
597 wgpu::BindGroupEntry {
598 binding: 1,
599 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
600 },
601 ],
602 })
603 }
604
605 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
606 let keyp = (key, px);
607 if let Some(info) = self.atlas_mask.map.get(&keyp) {
608 return Some(*info);
609 }
610
611 let gb = repose_text::rasterize(key, px as f32)?;
612 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
613 return None; }
615 if !matches!(
616 gb.content,
617 cosmic_text::SwashContent::Mask | cosmic_text::SwashContent::SubpixelMask
618 ) {
619 return None; }
621 let w = gb.w.max(1);
622 let h = gb.h.max(1);
623 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
625 self.atlas_mask.next_x = 1;
626 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
627 self.atlas_mask.row_h = 0;
628 }
629 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
630 return None;
632 }
633 let x = self.atlas_mask.next_x;
634 let y = self.atlas_mask.next_y;
635 self.atlas_mask.next_x += w + 1;
636 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
637
638 let buf = gb.data;
639
640 let layout = wgpu::TexelCopyBufferLayout {
642 offset: 0,
643 bytes_per_row: Some(w),
644 rows_per_image: Some(h),
645 };
646 let size = wgpu::Extent3d {
647 width: w,
648 height: h,
649 depth_or_array_layers: 1,
650 };
651 self.queue.write_texture(
652 wgpu::TexelCopyTextureInfoBase {
653 texture: &self.atlas_mask.tex,
654 mip_level: 0,
655 origin: wgpu::Origin3d { x, y, z: 0 },
656 aspect: wgpu::TextureAspect::All,
657 },
658 &buf,
659 layout,
660 size,
661 );
662
663 let info = GlyphInfo {
664 u0: x as f32 / self.atlas_mask.size as f32,
665 v0: y as f32 / self.atlas_mask.size as f32,
666 u1: (x + w) as f32 / self.atlas_mask.size as f32,
667 v1: (y + h) as f32 / self.atlas_mask.size as f32,
668 w: w as f32,
669 h: h as f32,
670 bearing_x: 0.0, bearing_y: 0.0,
672 advance: 0.0,
673 };
674 self.atlas_mask.map.insert(keyp, info);
675 Some(info)
676 }
677 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
678 let keyp = (key, px);
679 if let Some(info) = self.atlas_color.map.get(&keyp) {
680 return Some(*info);
681 }
682 let gb = repose_text::rasterize(key, px as f32)?;
683 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
684 return None;
685 }
686 let w = gb.w.max(1);
687 let h = gb.h.max(1);
688 if !self.alloc_space_color(w, h) {
689 self.grow_color_and_rebuild();
690 }
691 if !self.alloc_space_color(w, h) {
692 return None;
693 }
694 let x = self.atlas_color.next_x;
695 let y = self.atlas_color.next_y;
696 self.atlas_color.next_x += w + 1;
697 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
698
699 let layout = wgpu::TexelCopyBufferLayout {
700 offset: 0,
701 bytes_per_row: Some(w * 4),
702 rows_per_image: Some(h),
703 };
704 let size = wgpu::Extent3d {
705 width: w,
706 height: h,
707 depth_or_array_layers: 1,
708 };
709 self.queue.write_texture(
710 wgpu::TexelCopyTextureInfoBase {
711 texture: &self.atlas_color.tex,
712 mip_level: 0,
713 origin: wgpu::Origin3d { x, y, z: 0 },
714 aspect: wgpu::TextureAspect::All,
715 },
716 &gb.data,
717 layout,
718 size,
719 );
720 let info = GlyphInfo {
721 u0: x as f32 / self.atlas_color.size as f32,
722 v0: y as f32 / self.atlas_color.size as f32,
723 u1: (x + w) as f32 / self.atlas_color.size as f32,
724 v1: (y + h) as f32 / self.atlas_color.size as f32,
725 w: w as f32,
726 h: h as f32,
727 bearing_x: 0.0,
728 bearing_y: 0.0,
729 advance: 0.0,
730 };
731 self.atlas_color.map.insert(keyp, info);
732 Some(info)
733 }
734
735 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
737 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
738 self.atlas_mask.next_x = 1;
739 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
740 self.atlas_mask.row_h = 0;
741 }
742 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
743 return false;
744 }
745 true
746 }
747 fn grow_mask_and_rebuild(&mut self) {
748 let new_size = (self.atlas_mask.size * 2).min(4096);
749 if new_size == self.atlas_mask.size {
750 return;
751 }
752 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
754 label: Some("glyph atlas A8 (grown)"),
755 size: wgpu::Extent3d {
756 width: new_size,
757 height: new_size,
758 depth_or_array_layers: 1,
759 },
760 mip_level_count: 1,
761 sample_count: 1,
762 dimension: wgpu::TextureDimension::D2,
763 format: wgpu::TextureFormat::R8Unorm,
764 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
765 view_formats: &[],
766 });
767 self.atlas_mask.tex = tex;
768 self.atlas_mask.view = self
769 .atlas_mask
770 .tex
771 .create_view(&wgpu::TextureViewDescriptor::default());
772 self.atlas_mask.size = new_size;
773 self.atlas_mask.next_x = 1;
774 self.atlas_mask.next_y = 1;
775 self.atlas_mask.row_h = 0;
776 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
778 self.atlas_mask.map.clear();
779 for (k, px) in keys {
780 let _ = self.upload_glyph_mask(k, px);
781 }
782 }
783 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
785 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
786 self.atlas_color.next_x = 1;
787 self.atlas_color.next_y += self.atlas_color.row_h + 1;
788 self.atlas_color.row_h = 0;
789 }
790 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
791 return false;
792 }
793 true
794 }
795 fn grow_color_and_rebuild(&mut self) {
796 let new_size = (self.atlas_color.size * 2).min(4096);
797 if new_size == self.atlas_color.size {
798 return;
799 }
800 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
801 label: Some("glyph atlas RGBA (grown)"),
802 size: wgpu::Extent3d {
803 width: new_size,
804 height: new_size,
805 depth_or_array_layers: 1,
806 },
807 mip_level_count: 1,
808 sample_count: 1,
809 dimension: wgpu::TextureDimension::D2,
810 format: wgpu::TextureFormat::Rgba8UnormSrgb,
811 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
812 view_formats: &[],
813 });
814 self.atlas_color.tex = tex;
815 self.atlas_color.view = self
816 .atlas_color
817 .tex
818 .create_view(&wgpu::TextureViewDescriptor::default());
819 self.atlas_color.size = new_size;
820 self.atlas_color.next_x = 1;
821 self.atlas_color.next_y = 1;
822 self.atlas_color.row_h = 0;
823 let keys: Vec<(repose_text::GlyphKey, u32)> =
824 self.atlas_color.map.keys().copied().collect();
825 self.atlas_color.map.clear();
826 for (k, px) in keys {
827 let _ = self.upload_glyph_color(k, px);
828 }
829 }
830}
831
832impl RenderBackend for WgpuBackend {
833 fn configure_surface(&mut self, width: u32, height: u32) {
834 if width == 0 || height == 0 {
835 return;
836 }
837 self.config.width = width;
838 self.config.height = height;
839 self.surface.configure(&self.device, &self.config);
840 }
841
842 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
843 if self.config.width == 0 || self.config.height == 0 {
844 return;
845 }
846 let frame = loop {
847 match self.surface.get_current_texture() {
848 Ok(f) => break f,
849 Err(wgpu::SurfaceError::Lost) => {
850 log::warn!("surface lost; reconfiguring");
851 self.surface.configure(&self.device, &self.config);
852 }
853 Err(wgpu::SurfaceError::Outdated) => {
854 log::warn!("surface outdated; reconfiguring");
855 self.surface.configure(&self.device, &self.config);
856 }
857 Err(wgpu::SurfaceError::Timeout) => {
858 log::warn!("surface timeout; retrying");
859 continue;
860 }
861 Err(wgpu::SurfaceError::OutOfMemory) => {
862 log::error!("surface OOM");
863 return;
864 }
865 Err(wgpu::SurfaceError::Other) => {
866 log::error!("Other error");
867 return;
868 }
869 }
870 };
871 let view = frame
872 .texture
873 .create_view(&wgpu::TextureViewDescriptor::default());
874
875 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
877 let x0 = (x / fb_w) * 2.0 - 1.0;
878 let y0 = 1.0 - (y / fb_h) * 2.0;
879 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
880 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
881 let min_x = x0.min(x1);
882 let min_y = y0.min(y1);
883 let w_ndc = (x1 - x0).abs();
884 let h_ndc = (y1 - y0).abs();
885 [min_x, min_y, w_ndc, h_ndc]
886 }
887 fn to_ndc_scalar(px: f32, fb_dim: f32) -> f32 {
888 (px / fb_dim) * 2.0
889 }
890 fn to_ndc_radius(r: f32, fb_w: f32, fb_h: f32) -> f32 {
891 let rx = to_ndc_scalar(r, fb_w);
892 let ry = to_ndc_scalar(r, fb_h);
893 rx.min(ry)
894 }
895 fn to_ndc_stroke(w: f32, fb_w: f32, fb_h: f32) -> f32 {
896 let sx = to_ndc_scalar(w, fb_w);
897 let sy = to_ndc_scalar(w, fb_h);
898 sx.min(sy)
899 }
900 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
901 let x = r.x.max(0.0).floor() as u32;
902 let y = r.y.max(0.0).floor() as u32;
903 let w = ((r.w.max(0.0).ceil() as u32).min(fb_w.saturating_sub(x))).max(1);
904 let h = ((r.h.max(0.0).ceil() as u32).min(fb_h.saturating_sub(y))).max(1);
905 (x, y, w, h)
906 }
907
908 let fb_w = self.config.width as f32;
909 let fb_h = self.config.height as f32;
910
911 enum Cmd {
913 SetClipPush(repose_core::Rect),
914 SetClipPop,
915 Rect { off: u64, cnt: u32 },
916 Border { off: u64, cnt: u32 },
917 GlyphsMask { off: u64, cnt: u32 },
918 GlyphsColor { off: u64, cnt: u32 },
919 PushTransform(Transform),
920 PopTransform,
921 }
922 let mut cmds: Vec<Cmd> = Vec::with_capacity(scene.nodes.len());
923 struct Batch {
924 rects: Vec<RectInstance>,
925 borders: Vec<BorderInstance>,
926 masks: Vec<GlyphInstance>,
927 colors: Vec<GlyphInstance>,
928 }
929 impl Batch {
930 fn new() -> Self {
931 Self {
932 rects: vec![],
933 borders: vec![],
934 masks: vec![],
935 colors: vec![],
936 }
937 }
938
939 fn flush(
940 &mut self,
941 rings: (
942 &mut UploadRing,
943 &mut UploadRing,
944 &mut UploadRing,
945 &mut UploadRing,
946 ),
947 queue: &wgpu::Queue,
948 cmds: &mut Vec<Cmd>,
949 ) {
950 let (ring_rect, ring_border, ring_mask, ring_color) = rings;
951
952 if !self.rects.is_empty() {
953 let bytes = bytemuck::cast_slice(&self.rects);
954 let (off, wrote) = ring_rect.alloc_write(queue, bytes);
955 debug_assert_eq!(wrote as usize, bytes.len());
956 cmds.push(Cmd::Rect {
957 off,
958 cnt: self.rects.len() as u32,
959 });
960 self.rects.clear();
961 }
962 if !self.borders.is_empty() {
963 let bytes = bytemuck::cast_slice(&self.borders);
964 let (off, wrote) = ring_border.alloc_write(queue, bytes);
965 debug_assert_eq!(wrote as usize, bytes.len());
966 cmds.push(Cmd::Border {
967 off,
968 cnt: self.borders.len() as u32,
969 });
970 self.borders.clear();
971 }
972 if !self.masks.is_empty() {
973 let bytes = bytemuck::cast_slice(&self.masks);
974 let (off, wrote) = ring_mask.alloc_write(queue, bytes);
975 debug_assert_eq!(wrote as usize, bytes.len());
976 cmds.push(Cmd::GlyphsMask {
977 off,
978 cnt: self.masks.len() as u32,
979 });
980 self.masks.clear();
981 }
982 if !self.colors.is_empty() {
983 let bytes = bytemuck::cast_slice(&self.colors);
984 let (off, wrote) = ring_color.alloc_write(queue, bytes);
985 debug_assert_eq!(wrote as usize, bytes.len());
986 cmds.push(Cmd::GlyphsColor {
987 off,
988 cnt: self.colors.len() as u32,
989 });
990 self.colors.clear();
991 }
992 }
993 }
994 self.ring_rect.reset();
996 self.ring_border.reset();
997 self.ring_glyph_mask.reset();
998 self.ring_glyph_color.reset();
999 let mut batch = Batch::new();
1000
1001 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
1002
1003 for node in &scene.nodes {
1004 let t_identity = Transform::identity();
1005 let current_transform = transform_stack.last().unwrap_or(&t_identity);
1006
1007 match node {
1008 SceneNode::Rect {
1009 rect,
1010 color,
1011 radius,
1012 } => {
1013 let transformed_rect = current_transform.apply_to_rect(*rect);
1014 batch.rects.push(RectInstance {
1015 xywh: to_ndc(
1016 transformed_rect.x,
1017 transformed_rect.y,
1018 transformed_rect.w,
1019 transformed_rect.h,
1020 fb_w,
1021 fb_h,
1022 ),
1023 radius: to_ndc_radius(*radius, fb_w, fb_h),
1024 color: color.to_linear(),
1025 });
1026 }
1027 SceneNode::Border {
1028 rect,
1029 color,
1030 width,
1031 radius,
1032 } => {
1033 let transformed_rect = current_transform.apply_to_rect(*rect);
1034
1035 batch.borders.push(BorderInstance {
1036 xywh: to_ndc(
1037 transformed_rect.x,
1038 transformed_rect.y,
1039 transformed_rect.w,
1040 transformed_rect.h,
1041 fb_w,
1042 fb_h,
1043 ),
1044 radius_outer: to_ndc_radius(*radius, fb_w, fb_h),
1045 stroke: to_ndc_stroke(*width, fb_w, fb_h),
1046 color: color.to_linear(),
1047 });
1048 }
1049 SceneNode::Text {
1050 rect,
1051 text,
1052 color,
1053 size,
1054 } => {
1055 let px = (*size).clamp(8.0, 96.0);
1056 let shaped = repose_text::shape_line(text, px);
1058 for sg in shaped {
1059 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
1061 let x = rect.x + sg.x + sg.bearing_x;
1062 let y = rect.y + sg.y - sg.bearing_y;
1063 batch.colors.push(GlyphInstance {
1064 xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1065 uv: [info.u0, info.v1, info.u1, info.v0],
1066 color: [1.0, 1.0, 1.0, 1.0], });
1068 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
1069 let x = rect.x + sg.x + sg.bearing_x;
1070 let y = rect.y + sg.y - sg.bearing_y;
1071 batch.masks.push(GlyphInstance {
1072 xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1073 uv: [info.u0, info.v1, info.u1, info.v0],
1074 color: color.to_linear(),
1075 });
1076 }
1077 }
1078 }
1079 SceneNode::PushClip { rect, .. } => {
1080 batch.flush(
1081 (
1082 &mut self.ring_rect,
1083 &mut self.ring_border,
1084 &mut self.ring_glyph_mask,
1085 &mut self.ring_glyph_color,
1086 ),
1087 &self.queue,
1088 &mut cmds,
1089 );
1090 cmds.push(Cmd::SetClipPush(*rect));
1091 }
1092 SceneNode::PopClip => {
1093 batch.flush(
1094 (
1095 &mut self.ring_rect,
1096 &mut self.ring_border,
1097 &mut self.ring_glyph_mask,
1098 &mut self.ring_glyph_color,
1099 ),
1100 &self.queue,
1101 &mut cmds,
1102 );
1103 cmds.push(Cmd::SetClipPop);
1104 }
1105 SceneNode::PushTransform { transform } => {
1106 let combined = current_transform.combine(transform);
1107 transform_stack.push(combined);
1108 }
1109 SceneNode::PopTransform => {
1110 transform_stack.pop();
1111 }
1112 }
1113 batch.flush(
1115 (
1116 &mut self.ring_rect,
1117 &mut self.ring_border,
1118 &mut self.ring_glyph_mask,
1119 &mut self.ring_glyph_color,
1120 ),
1121 &self.queue,
1122 &mut cmds,
1123 );
1124 }
1125
1126 let mut encoder = self
1127 .device
1128 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1129 label: Some("frame encoder"),
1130 });
1131
1132 {
1133 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1134 label: Some("main pass"),
1135 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1136 view: &view,
1137 resolve_target: None,
1138 ops: wgpu::Operations {
1139 load: wgpu::LoadOp::Clear(wgpu::Color {
1140 r: scene.clear_color.0 as f64 / 255.0,
1141 g: scene.clear_color.1 as f64 / 255.0,
1142 b: scene.clear_color.2 as f64 / 255.0,
1143 a: scene.clear_color.3 as f64 / 255.0,
1144 }),
1145 store: wgpu::StoreOp::Store,
1146 },
1147 depth_slice: None,
1148 })],
1149 depth_stencil_attachment: None,
1150 timestamp_writes: None,
1151 occlusion_query_set: None,
1152 });
1153
1154 rpass.set_scissor_rect(0, 0, self.config.width, self.config.height);
1156 let bind_mask = self.atlas_bind_group_mask();
1157 let bind_color = self.atlas_bind_group_color();
1158 let root_clip = repose_core::Rect {
1159 x: 0.0,
1160 y: 0.0,
1161 w: fb_w,
1162 h: fb_h,
1163 };
1164 let mut clip_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
1165
1166 for cmd in cmds {
1167 match cmd {
1168 Cmd::SetClipPush(r) => {
1169 let top = clip_stack.last().copied().unwrap_or(root_clip);
1170 let next = intersect(top, r);
1171 clip_stack.push(next);
1172 let (x, y, w, h) = to_scissor(&next, self.config.width, self.config.height);
1173 rpass.set_scissor_rect(x, y, w, h);
1174 }
1175 Cmd::SetClipPop => {
1176 if clip_stack.pop().is_none() {
1177 log::warn!("PopClip with empty stack; ignoring.");
1178 }
1179 let top = clip_stack.last().copied().unwrap_or(root_clip);
1180 let (x, y, w, h) = to_scissor(&top, self.config.width, self.config.height);
1181 rpass.set_scissor_rect(x, y, w, h);
1182 }
1183 Cmd::Rect { off, cnt: n } => {
1184 rpass.set_pipeline(&self.rect_pipeline);
1185 let bytes = (n as u64) * std::mem::size_of::<RectInstance>() as u64;
1186 rpass.set_vertex_buffer(0, self.ring_rect.buf.slice(off..off + bytes));
1187 rpass.draw(0..6, 0..n);
1188 }
1189 Cmd::Border { off, cnt: n } => {
1190 rpass.set_pipeline(&self.border_pipeline);
1191 let bytes = (n as u64) * std::mem::size_of::<BorderInstance>() as u64;
1192 rpass.set_vertex_buffer(0, self.ring_border.buf.slice(off..off + bytes));
1193 rpass.draw(0..6, 0..n);
1194 }
1195 Cmd::GlyphsMask { off, cnt: n } => {
1196 rpass.set_pipeline(&self.text_pipeline_mask);
1197 rpass.set_bind_group(0, &bind_mask, &[]);
1198 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
1199 rpass
1200 .set_vertex_buffer(0, self.ring_glyph_mask.buf.slice(off..off + bytes));
1201 rpass.draw(0..6, 0..n);
1202 }
1203 Cmd::GlyphsColor { off, cnt: n } => {
1204 rpass.set_pipeline(&self.text_pipeline_color);
1205 rpass.set_bind_group(0, &bind_color, &[]);
1206 let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
1207 rpass.set_vertex_buffer(
1208 0,
1209 self.ring_glyph_color.buf.slice(off..off + bytes),
1210 );
1211 rpass.draw(0..6, 0..n);
1212 }
1213 Cmd::PushTransform(transform) => {}
1214 Cmd::PopTransform => {}
1215 }
1216 }
1217 }
1218
1219 self.queue.submit(std::iter::once(encoder.finish()));
1220 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
1221 log::warn!("frame.present panicked: {:?}", e);
1222 }
1223 }
1224}
1225
1226fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
1227 let x0 = a.x.max(b.x);
1228 let y0 = a.y.max(b.y);
1229 let x1 = (a.x + a.w).min(b.x + b.w);
1230 let y1 = (a.y + a.h).min(b.y + b.h);
1231 repose_core::Rect {
1232 x: x0,
1233 y: y0,
1234 w: (x1 - x0).max(0.0),
1235 h: (y1 - y0).max(0.0),
1236 }
1237}