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