1use std::borrow::Cow;
23
24use damascene_core::ir::TextAnchor;
25use damascene_core::shader::stock_wgsl;
26use damascene_core::text::atlas::{
27 ATLAS_BYTES_PER_PIXEL, AtlasPage, AtlasRect, GlyphAtlas, RunStyle, ShapedGlyph, ShapedRun,
28};
29use damascene_core::text::msdf::apply_weight_variation;
30use damascene_core::text::msdf_atlas::{
31 DEFAULT_BASE_EM, DEFAULT_SPREAD, MSDF_BYTES_PER_PIXEL, MsdfAtlas, MsdfAtlasPage, MsdfGlyphKey,
32 MsdfRect, MsdfSlot,
33};
34use damascene_core::text::msdf_snapshot::{SnapshotError, font_token_hash};
35use damascene_core::text::snap_to_physical;
36use damascene_core::tree::{FontFamily, Rect, TextWrap};
37
38const STOCK_WEIGHTS: &[u16] = &[400, 500, 600, 700];
43
44use bytemuck::{Pod, Zeroable};
45use cosmic_text::fontdb;
46use ttf_parser::Face;
47
48use damascene_core::color::ColorSpace;
49use damascene_core::paint::{DEFAULT_WORKING_COLOR_SPACE, PhysicalScissor, rgba_f32_in};
50use damascene_core::runtime::TextRecorder;
51
52const INITIAL_INSTANCE_CAPACITY: usize = 256;
53
54const COLOR_INSTANCE_ATTRS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
55 1 => Float32x4, 2 => Float32x4, 3 => Float32x4, ];
59
60const MSDF_INSTANCE_ATTRS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
61 1 => Float32x4, 2 => Float32x4, 3 => Float32x4, 4 => Float32x4, ];
66
67const HIGHLIGHT_INSTANCE_ATTRS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
68 1 => Float32x4, 2 => Float32x4, ];
71
72#[repr(C)]
73#[derive(Copy, Clone, Pod, Zeroable, Debug)]
74pub(crate) struct ColorGlyphInstance {
75 pub rect: [f32; 4],
76 pub uv: [f32; 4],
77 pub color: [f32; 4],
78}
79
80#[repr(C)]
81#[derive(Copy, Clone, Pod, Zeroable, Debug)]
82pub(crate) struct MsdfGlyphInstance {
83 pub rect: [f32; 4],
84 pub uv: [f32; 4],
85 pub color: [f32; 4],
86 pub params: [f32; 4],
87}
88
89#[repr(C)]
90#[derive(Copy, Clone, Pod, Zeroable, Debug)]
91pub(crate) struct HighlightInstance {
92 pub rect: [f32; 4],
93 pub color: [f32; 4],
94}
95
96#[derive(Clone, Copy, PartialEq, Eq)]
97pub(crate) enum TextRunKind {
98 Color,
99 Msdf,
100 Highlight,
101}
102
103#[derive(Clone, Copy)]
104pub(crate) struct TextRun {
105 pub kind: TextRunKind,
106 pub page: u32,
107 pub scissor: Option<PhysicalScissor>,
108 pub first: u32,
109 pub count: u32,
110}
111
112struct PageTexture {
113 texture: wgpu::Texture,
114 bind_group: wgpu::BindGroup,
115}
116
117#[derive(Clone)]
138pub struct SharedText(pub(crate) std::sync::Arc<std::sync::Mutex<SharedTextInner>>);
139
140pub(crate) struct SharedTextInner {
141 pub(crate) atlas: GlyphAtlas,
142 pub(crate) msdf_atlas: MsdfAtlas,
143
144 color_pages: Vec<PageTexture>,
145 color_page_bind_layout: wgpu::BindGroupLayout,
146 color_sampler: wgpu::Sampler,
147
148 msdf_pages: Vec<PageTexture>,
149 msdf_page_bind_layout: wgpu::BindGroupLayout,
150 msdf_sampler: wgpu::Sampler,
151
152 attached: u32,
156}
157
158impl SharedText {
159 pub fn new(device: &wgpu::Device) -> Self {
164 let color_page_bind_layout = create_page_bind_layout(device, "color");
165 let msdf_page_bind_layout = create_page_bind_layout(device, "msdf");
166 let color_sampler = create_page_sampler(device, "color");
167 let msdf_sampler = create_page_sampler(device, "msdf");
168 Self(std::sync::Arc::new(std::sync::Mutex::new(
169 SharedTextInner {
170 atlas: GlyphAtlas::new(),
171 msdf_atlas: MsdfAtlas::new(DEFAULT_BASE_EM, DEFAULT_SPREAD),
172 color_pages: Vec::new(),
173 color_page_bind_layout,
174 color_sampler,
175 msdf_pages: Vec::new(),
176 msdf_page_bind_layout,
177 msdf_sampler,
178 attached: 0,
179 },
180 )))
181 }
182
183 pub fn warm_default_glyphs(&self) {
191 self.lock().warm_default_glyphs();
192 }
193
194 pub fn warm_glyphs(&self, families: &[FontFamily], chars: &[char]) {
201 self.lock().warm_msdf_for_chars(chars, families);
202 }
203
204 pub fn export_msdf_snapshot(&self) -> Vec<u8> {
215 self.lock().export_msdf_snapshot()
216 }
217
218 pub fn import_msdf_snapshot(&self, bytes: &[u8]) -> Result<usize, SnapshotError> {
226 self.lock().import_msdf_snapshot(bytes)
227 }
228
229 pub(crate) fn lock(&self) -> std::sync::MutexGuard<'_, SharedTextInner> {
230 match self.0.lock() {
234 Ok(g) => g,
235 Err(poisoned) => poisoned.into_inner(),
236 }
237 }
238}
239
240pub(crate) struct TextPaint {
241 shared: SharedText,
243
244 color_page_bgs: Vec<wgpu::BindGroup>,
250 msdf_page_bgs: Vec<wgpu::BindGroup>,
251
252 color_instances: Vec<ColorGlyphInstance>,
254 color_instance_buf: wgpu::Buffer,
255 color_instance_capacity: usize,
256 color_pipeline: wgpu::RenderPipeline,
257
258 msdf_instances: Vec<MsdfGlyphInstance>,
260 msdf_instance_buf: wgpu::Buffer,
261 msdf_instance_capacity: usize,
262 msdf_pipeline: wgpu::RenderPipeline,
263
264 highlight_instances: Vec<HighlightInstance>,
266 highlight_instance_buf: wgpu::Buffer,
267 highlight_instance_capacity: usize,
268 highlight_pipeline: wgpu::RenderPipeline,
269
270 color_pipeline_layout: wgpu::PipelineLayout,
276 msdf_pipeline_layout: wgpu::PipelineLayout,
277 highlight_pipeline_layout: wgpu::PipelineLayout,
278 sample_count: u32,
279
280 runs: Vec<TextRun>,
281
282 working_color_space: ColorSpace,
287}
288
289impl Drop for TextPaint {
290 fn drop(&mut self) {
291 let mut inner = self.shared.lock();
292 inner.attached = inner.attached.saturating_sub(1);
293 let n = inner.attached.max(1);
294 inner.atlas.set_lru_protection_window(n);
295 inner.msdf_atlas.set_lru_protection_window(n);
296 }
297}
298
299fn create_page_bind_layout(device: &wgpu::Device, kind: &str) -> wgpu::BindGroupLayout {
302 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
303 label: Some(&format!("damascene_wgpu::text::{kind}_page_bind_layout")),
304 entries: &[
305 wgpu::BindGroupLayoutEntry {
306 binding: 0,
307 visibility: wgpu::ShaderStages::FRAGMENT,
308 ty: wgpu::BindingType::Texture {
309 sample_type: wgpu::TextureSampleType::Float { filterable: true },
310 view_dimension: wgpu::TextureViewDimension::D2,
311 multisampled: false,
312 },
313 count: None,
314 },
315 wgpu::BindGroupLayoutEntry {
316 binding: 1,
317 visibility: wgpu::ShaderStages::FRAGMENT,
318 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
319 count: None,
320 },
321 ],
322 })
323}
324
325fn create_page_sampler(device: &wgpu::Device, kind: &str) -> wgpu::Sampler {
326 device.create_sampler(&wgpu::SamplerDescriptor {
327 label: Some(&format!("damascene_wgpu::text::{kind}_sampler")),
328 address_mode_u: wgpu::AddressMode::ClampToEdge,
329 address_mode_v: wgpu::AddressMode::ClampToEdge,
330 address_mode_w: wgpu::AddressMode::ClampToEdge,
331 mag_filter: wgpu::FilterMode::Linear,
332 min_filter: wgpu::FilterMode::Linear,
333 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
334 ..Default::default()
335 })
336}
337
338impl TextPaint {
339 pub(crate) fn new(
340 device: &wgpu::Device,
341 target_format: wgpu::TextureFormat,
342 sample_count: u32,
343 frame_bind_layout: &wgpu::BindGroupLayout,
344 ) -> Self {
345 Self::with_shared(
346 device,
347 target_format,
348 sample_count,
349 frame_bind_layout,
350 SharedText::new(device),
351 )
352 }
353
354 pub(crate) fn with_shared(
359 device: &wgpu::Device,
360 target_format: wgpu::TextureFormat,
361 sample_count: u32,
362 frame_bind_layout: &wgpu::BindGroupLayout,
363 shared: SharedText,
364 ) -> Self {
365 let (color_pipeline_layout, msdf_pipeline_layout) = {
366 let mut inner = shared.lock();
367 inner.attached += 1;
368 let n = inner.attached;
369 inner.atlas.set_lru_protection_window(n);
370 inner.msdf_atlas.set_lru_protection_window(n);
371 (
372 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
373 label: Some("damascene_wgpu::text::color_pipeline_layout"),
374 bind_group_layouts: &[
375 Some(frame_bind_layout),
376 Some(&inner.color_page_bind_layout),
377 ],
378 immediate_size: 0,
379 }),
380 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
381 label: Some("damascene_wgpu::text::msdf_pipeline_layout"),
382 bind_group_layouts: &[
383 Some(frame_bind_layout),
384 Some(&inner.msdf_page_bind_layout),
385 ],
386 immediate_size: 0,
387 }),
388 )
389 };
390
391 let color_pipeline =
392 build_color_pipeline(device, &color_pipeline_layout, target_format, sample_count);
393 let msdf_pipeline =
394 build_msdf_pipeline(device, &msdf_pipeline_layout, target_format, sample_count);
395
396 let color_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
397 label: Some("damascene_wgpu::text::color_instance_buf"),
398 size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<ColorGlyphInstance>()) as u64,
399 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
400 mapped_at_creation: false,
401 });
402 let msdf_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
403 label: Some("damascene_wgpu::text::msdf_instance_buf"),
404 size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<MsdfGlyphInstance>()) as u64,
405 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
406 mapped_at_creation: false,
407 });
408
409 let highlight_pipeline_layout =
412 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
413 label: Some("damascene_wgpu::text::highlight_pipeline_layout"),
414 bind_group_layouts: &[Some(frame_bind_layout)],
415 immediate_size: 0,
416 });
417 let highlight_pipeline = build_highlight_pipeline(
418 device,
419 &highlight_pipeline_layout,
420 target_format,
421 sample_count,
422 );
423 let highlight_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
424 label: Some("damascene_wgpu::text::highlight_instance_buf"),
425 size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<HighlightInstance>()) as u64,
426 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
427 mapped_at_creation: false,
428 });
429
430 Self {
431 shared,
432 color_page_bgs: Vec::new(),
433 msdf_page_bgs: Vec::new(),
434 color_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
435 color_instance_buf,
436 color_instance_capacity: INITIAL_INSTANCE_CAPACITY,
437 color_pipeline,
438 msdf_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
439 msdf_instance_buf,
440 msdf_instance_capacity: INITIAL_INSTANCE_CAPACITY,
441 msdf_pipeline,
442 highlight_instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
443 highlight_instance_buf,
444 highlight_instance_capacity: INITIAL_INSTANCE_CAPACITY,
445 highlight_pipeline,
446 color_pipeline_layout,
447 msdf_pipeline_layout,
448 highlight_pipeline_layout,
449 sample_count,
450 runs: Vec::new(),
451 working_color_space: DEFAULT_WORKING_COLOR_SPACE,
452 }
453 }
454
455 pub(crate) fn shared(&self) -> &SharedText {
458 &self.shared
459 }
460
461 pub(crate) fn set_working_color_space(&mut self, space: ColorSpace) {
464 self.working_color_space = space;
465 }
466
467 pub(crate) fn set_target_format(
475 &mut self,
476 device: &wgpu::Device,
477 target_format: wgpu::TextureFormat,
478 ) {
479 self.color_pipeline = build_color_pipeline(
480 device,
481 &self.color_pipeline_layout,
482 target_format,
483 self.sample_count,
484 );
485 self.msdf_pipeline = build_msdf_pipeline(
486 device,
487 &self.msdf_pipeline_layout,
488 target_format,
489 self.sample_count,
490 );
491 self.highlight_pipeline = build_highlight_pipeline(
492 device,
493 &self.highlight_pipeline_layout,
494 target_format,
495 self.sample_count,
496 );
497 }
498
499 pub(crate) fn frame_begin(&mut self) {
500 self.color_instances.clear();
501 self.msdf_instances.clear();
502 self.highlight_instances.clear();
503 self.runs.clear();
504 }
505
506 #[allow(clippy::too_many_arguments)]
507 fn record_inner(
508 &mut self,
509 rect: Rect,
510 scissor: Option<PhysicalScissor>,
511 runs: &[(String, RunStyle)],
512 size: f32,
513 line_height: f32,
514 wrap: TextWrap,
515 anchor: TextAnchor,
516 scale_factor: f32,
517 ) -> std::ops::Range<usize> {
518 let avail = wrap_available_width(rect.w, scale_factor, wrap, anchor);
523 let runs_ref: Vec<(&str, RunStyle)> = runs
524 .iter()
525 .map(|(text, style)| (text.as_str(), style.clone()))
526 .collect();
527 let shared = self.shared.clone();
533 let mut inner = shared.lock();
534 let shaped = {
535 damascene_core::profile_span!("paint::text::shape_runs");
536 inner.atlas.shape_runs_with_line_height(
537 &runs_ref,
538 size,
539 line_height,
540 wrap,
541 anchor,
542 avail,
543 )
544 };
545 damascene_core::profile_span!("paint::text::emit_shaped");
546 self.emit_shaped_glyphs(&mut inner, rect, scissor, &shaped, wrap, scale_factor)
547 }
548
549 fn emit_shaped_glyphs(
550 &mut self,
551 inner: &mut SharedTextInner,
552 rect: Rect,
553 scissor: Option<PhysicalScissor>,
554 shaped: &ShapedRun,
555 wrap: TextWrap,
556 scale_factor: f32,
557 ) -> std::ops::Range<usize> {
558 let runs_start = self.runs.len();
559 if shaped.glyphs.is_empty() && shaped.highlights.is_empty() && shaped.decorations.is_empty()
560 {
561 return runs_start..runs_start;
562 }
563
564 let v_offset = match wrap {
573 TextWrap::NoWrap => ((rect.h - shaped.layout.height).max(0.0)) * 0.5,
574 TextWrap::Wrap => 0.0,
575 };
576 let origin_x = rect.x;
577 let origin_y = rect.y + v_offset;
578
579 if !shaped.highlights.is_empty() {
585 let first = self.highlight_instances.len() as u32;
586 for h in &shaped.highlights {
587 self.highlight_instances.push(HighlightInstance {
588 rect: [origin_x + h.x, origin_y + h.y, h.w, h.h],
589 color: rgba_f32_in(h.color, self.working_color_space),
590 });
591 }
592 let count = self.highlight_instances.len() as u32 - first;
593 if count > 0 {
594 self.runs.push(TextRun {
595 kind: TextRunKind::Highlight,
596 page: 0,
597 scissor,
598 first,
599 count,
600 });
601 }
602 }
603
604 let mut current: Option<(TextRunKind, u32, u32)> = None; for glyph in &shaped.glyphs {
610 let font_id = glyph.key.font;
611 let is_color = inner.atlas.is_color_font(font_id);
612 if is_color {
613 let color_key = glyph.key.at_scale(scale_factor);
616 inner.atlas.ensure_color_glyph(color_key);
617 let Some(slot) = inner.atlas.slot(color_key) else {
618 continue;
619 };
620 if slot.rect.w == 0 || slot.rect.h == 0 {
621 continue;
622 }
623 let page = slot.page;
624 let next_kind = TextRunKind::Color;
625 self.maybe_close_run(&mut current, next_kind, page, scissor);
626 self.push_color_glyph(inner, glyph, slot, origin_x, origin_y, scale_factor);
627 } else {
628 let mkey = MsdfGlyphKey {
629 font: font_id,
630 glyph_id: glyph.key.glyph_id,
631 weight: inner.atlas.msdf_raster_weight(font_id, glyph.key.weight),
632 };
633 let Some(slot) = ensure_msdf(inner, mkey, font_id, glyph.key.weight) else {
634 continue;
637 };
638 let page = slot.page;
639 let next_kind = TextRunKind::Msdf;
640 self.maybe_close_run(&mut current, next_kind, page, scissor);
641 self.push_msdf_glyph(inner, glyph, slot, origin_x, origin_y, scale_factor);
642 }
643 }
644
645 if let Some((kind, page, first)) = current {
647 let count = self.instance_count_after(kind, first);
648 if count > 0 {
649 self.runs.push(TextRun {
650 kind,
651 page,
652 scissor,
653 first,
654 count,
655 });
656 }
657 }
658
659 if !shaped.decorations.is_empty() {
664 let first = self.highlight_instances.len() as u32;
665 for d in &shaped.decorations {
666 let top = snap_to_physical(origin_y + d.y, scale_factor);
670 let h = snap_to_physical(d.h, scale_factor).max(1.0 / scale_factor.max(1.0));
671 self.highlight_instances.push(HighlightInstance {
672 rect: [origin_x + d.x, top, d.w, h],
673 color: rgba_f32_in(d.color, self.working_color_space),
674 });
675 }
676 let count = self.highlight_instances.len() as u32 - first;
677 if count > 0 {
678 self.runs.push(TextRun {
679 kind: TextRunKind::Highlight,
680 page: 0,
681 scissor,
682 first,
683 count,
684 });
685 }
686 }
687
688 runs_start..self.runs.len()
689 }
690
691 fn maybe_close_run(
692 &mut self,
693 current: &mut Option<(TextRunKind, u32, u32)>,
694 next_kind: TextRunKind,
695 next_page: u32,
696 scissor: Option<PhysicalScissor>,
697 ) {
698 let new_start = match next_kind {
699 TextRunKind::Color => self.color_instances.len() as u32,
700 TextRunKind::Msdf => self.msdf_instances.len() as u32,
701 TextRunKind::Highlight => self.highlight_instances.len() as u32,
702 };
703 let needs_close = match current {
704 Some((kind, page, _)) => !same_kind(*kind, next_kind) || *page != next_page,
705 None => false,
706 };
707 if needs_close {
708 let (kind, page, first) = current.take().unwrap();
709 let count = self.instance_count_after(kind, first);
710 if count > 0 {
711 self.runs.push(TextRun {
712 kind,
713 page,
714 scissor,
715 first,
716 count,
717 });
718 }
719 }
720 if current.is_none() {
721 *current = Some((next_kind, next_page, new_start));
722 }
723 }
724
725 fn instance_count_after(&self, kind: TextRunKind, first: u32) -> u32 {
726 let len = match kind {
727 TextRunKind::Color => self.color_instances.len() as u32,
728 TextRunKind::Msdf => self.msdf_instances.len() as u32,
729 TextRunKind::Highlight => self.highlight_instances.len() as u32,
730 };
731 len.saturating_sub(first)
732 }
733
734 fn push_color_glyph(
735 &mut self,
736 inner: &SharedTextInner,
737 glyph: &ShapedGlyph,
738 slot: damascene_core::text::atlas::GlyphSlot,
739 origin_x: f32,
740 origin_y: f32,
741 scale_factor: f32,
742 ) {
743 let physical_em = glyph.key.size() * scale_factor;
755 let ratio = if slot.raster_size > 0.0 {
756 physical_em / slot.raster_size
757 } else {
758 1.0
759 };
760 let bx = origin_x + glyph.x + slot.offset.0 as f32 * ratio / scale_factor;
761 let by = origin_y + glyph.y - slot.offset.1 as f32 * ratio / scale_factor;
762 let bw = slot.rect.w as f32 * ratio / scale_factor;
763 let bh = slot.rect.h as f32 * ratio / scale_factor;
764 let atlas_page = inner
765 .atlas
766 .page(slot.page)
767 .expect("shaped glyph references missing colour atlas page");
768 let page_w = atlas_page.width as f32;
769 let page_h = atlas_page.height as f32;
770 let uv = [
771 slot.rect.x as f32 / page_w,
772 slot.rect.y as f32 / page_h,
773 slot.rect.w as f32 / page_w,
774 slot.rect.h as f32 / page_h,
775 ];
776 let inst_color = if slot.is_color {
777 [1.0, 1.0, 1.0, 1.0]
778 } else {
779 rgba_f32_in(glyph.color, self.working_color_space)
780 };
781 self.color_instances.push(ColorGlyphInstance {
782 rect: [bx, by, bw, bh],
783 uv,
784 color: inst_color,
785 });
786 }
787
788 fn push_msdf_glyph(
789 &mut self,
790 inner: &SharedTextInner,
791 glyph: &ShapedGlyph,
792 slot: MsdfSlot,
793 origin_x: f32,
794 origin_y: f32,
795 scale_factor: f32,
796 ) {
797 let logical_em = glyph.key.size();
800 let base_em = inner.msdf_atlas.base_em() as f32;
801 let scale = logical_em / base_em;
802 let bx = origin_x + glyph.x + slot.bearing_x * scale;
803 let baseline = snap_to_physical(origin_y + glyph.y, scale_factor);
809 let by = baseline + slot.bearing_y * scale;
810 let bw = slot.rect.w as f32 * scale;
811 let bh = slot.rect.h as f32 * scale;
812 let atlas_page = inner
813 .msdf_atlas
814 .page(slot.page)
815 .expect("shaped glyph references missing MSDF atlas page");
816 let page_w = atlas_page.width as f32;
817 let page_h = atlas_page.height as f32;
818 let uv = [
819 slot.rect.x as f32 / page_w,
820 slot.rect.y as f32 / page_h,
821 slot.rect.w as f32 / page_w,
822 slot.rect.h as f32 / page_h,
823 ];
824 let color = rgba_f32_in(glyph.color, self.working_color_space);
825 self.msdf_instances.push(MsdfGlyphInstance {
826 rect: [bx, by, bw, bh],
827 uv,
828 color,
829 params: [slot.spread, 0.0, 0.0, 0.0],
830 });
831 }
832
833 pub fn warm_default_glyphs(&mut self) {
849 self.shared.clone().lock().warm_default_glyphs();
850 }
851
852 pub fn warm_glyphs(&mut self, families: &[FontFamily], chars: &[char]) {
855 self.shared
856 .clone()
857 .lock()
858 .warm_msdf_for_chars(chars, families);
859 }
860
861 pub fn export_msdf_snapshot(&self) -> Vec<u8> {
864 self.shared.clone().lock().export_msdf_snapshot()
865 }
866
867 pub fn import_msdf_snapshot(&self, bytes: &[u8]) -> Result<usize, SnapshotError> {
870 self.shared.clone().lock().import_msdf_snapshot(bytes)
871 }
872
873 pub(crate) fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
876 {
877 let shared = self.shared.clone();
878 let mut inner = shared.lock();
879 inner.flush_pages(device, queue);
880 self.color_page_bgs = inner
885 .color_pages
886 .iter()
887 .map(|p| p.bind_group.clone())
888 .collect();
889 self.msdf_page_bgs = inner
890 .msdf_pages
891 .iter()
892 .map(|p| p.bind_group.clone())
893 .collect();
894 }
895
896 if self.color_instances.len() > self.color_instance_capacity {
898 let new_cap = self.color_instances.len().next_power_of_two();
899 self.color_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
900 label: Some("damascene_wgpu::text::color_instance_buf (resized)"),
901 size: (new_cap * std::mem::size_of::<ColorGlyphInstance>()) as u64,
902 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
903 mapped_at_creation: false,
904 });
905 self.color_instance_capacity = new_cap;
906 }
907 if !self.color_instances.is_empty() {
908 queue.write_buffer(
909 &self.color_instance_buf,
910 0,
911 bytemuck::cast_slice(&self.color_instances),
912 );
913 }
914
915 if self.msdf_instances.len() > self.msdf_instance_capacity {
917 let new_cap = self.msdf_instances.len().next_power_of_two();
918 self.msdf_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
919 label: Some("damascene_wgpu::text::msdf_instance_buf (resized)"),
920 size: (new_cap * std::mem::size_of::<MsdfGlyphInstance>()) as u64,
921 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
922 mapped_at_creation: false,
923 });
924 self.msdf_instance_capacity = new_cap;
925 }
926 if !self.msdf_instances.is_empty() {
927 queue.write_buffer(
928 &self.msdf_instance_buf,
929 0,
930 bytemuck::cast_slice(&self.msdf_instances),
931 );
932 }
933
934 if self.highlight_instances.len() > self.highlight_instance_capacity {
936 let new_cap = self.highlight_instances.len().next_power_of_two();
937 self.highlight_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
938 label: Some("damascene_wgpu::text::highlight_instance_buf (resized)"),
939 size: (new_cap * std::mem::size_of::<HighlightInstance>()) as u64,
940 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
941 mapped_at_creation: false,
942 });
943 self.highlight_instance_capacity = new_cap;
944 }
945 if !self.highlight_instances.is_empty() {
946 queue.write_buffer(
947 &self.highlight_instance_buf,
948 0,
949 bytemuck::cast_slice(&self.highlight_instances),
950 );
951 }
952 }
953
954 pub(crate) fn run(&self, index: usize) -> TextRun {
955 self.runs[index]
956 }
957
958 pub(crate) fn pipeline_for(&self, kind: TextRunKind) -> &wgpu::RenderPipeline {
959 match kind {
960 TextRunKind::Color => &self.color_pipeline,
961 TextRunKind::Msdf => &self.msdf_pipeline,
962 TextRunKind::Highlight => &self.highlight_pipeline,
963 }
964 }
965
966 pub(crate) fn instance_buf_for(&self, kind: TextRunKind) -> &wgpu::Buffer {
967 match kind {
968 TextRunKind::Color => &self.color_instance_buf,
969 TextRunKind::Msdf => &self.msdf_instance_buf,
970 TextRunKind::Highlight => &self.highlight_instance_buf,
971 }
972 }
973
974 pub(crate) fn page_bind_group(&self, kind: TextRunKind, page: u32) -> &wgpu::BindGroup {
979 match kind {
980 TextRunKind::Color => &self.color_page_bgs[page as usize],
981 TextRunKind::Msdf => &self.msdf_page_bgs[page as usize],
982 TextRunKind::Highlight => unreachable!("highlight runs carry no page binding"),
983 }
984 }
985}
986
987impl SharedTextInner {
988 fn flush_pages(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
994 let color_dirty = self.atlas.take_dirty();
996 while self.color_pages.len() < self.atlas.pages().len() {
997 let i = self.color_pages.len();
998 let page = &self.atlas.pages()[i];
999 self.color_pages.push(create_color_page(
1000 device,
1001 &self.color_page_bind_layout,
1002 &self.color_sampler,
1003 page.width,
1004 page.height,
1005 ));
1006 }
1007 for (page_idx, rect) in color_dirty {
1008 let page = &self.atlas.pages()[page_idx];
1009 upload_color_region(queue, &self.color_pages[page_idx].texture, page, rect);
1010 }
1011
1012 let msdf_dirty = self.msdf_atlas.take_dirty();
1014 while self.msdf_pages.len() < self.msdf_atlas.pages().len() {
1015 let i = self.msdf_pages.len();
1016 let page = &self.msdf_atlas.pages()[i];
1017 self.msdf_pages.push(create_msdf_page(
1018 device,
1019 &self.msdf_page_bind_layout,
1020 &self.msdf_sampler,
1021 page.width,
1022 page.height,
1023 ));
1024 }
1025 for (page_idx, rect) in msdf_dirty {
1026 let page = &self.msdf_atlas.pages()[page_idx];
1027 upload_msdf_region(queue, &self.msdf_pages[page_idx].texture, page, rect);
1028 }
1029 }
1030
1031 pub(crate) fn warm_default_glyphs(&mut self) {
1033 #[cfg(feature = "prebaked-default-fonts")]
1037 if self.warm_from_prebaked() {
1038 return;
1039 }
1040 let chars: Vec<char> = (0x20u32..=0x7Eu32).filter_map(char::from_u32).collect();
1041 self.warm_msdf_weights(&chars, &[FontFamily::Inter], STOCK_WEIGHTS);
1045 self.warm_msdf_weights(&chars, &[FontFamily::JetBrainsMono], &[400]);
1046 }
1047
1048 #[cfg(feature = "prebaked-default-fonts")]
1052 fn warm_from_prebaked(&mut self) -> bool {
1053 use damascene_core::prebaked::{DEFAULT_ATLAS, TOKEN_INTER, TOKEN_JETBRAINS_MONO};
1054 let inter = self.resolve_family_font_id(FontFamily::Inter);
1055 let mono = self.resolve_family_font_id(FontFamily::JetBrainsMono);
1056 let id_of = |token| match token {
1057 TOKEN_INTER => inter,
1058 TOKEN_JETBRAINS_MONO => mono,
1059 _ => None,
1060 };
1061 matches!(self.msdf_atlas.import_snapshot(DEFAULT_ATLAS, id_of), Ok(n) if n > 0)
1062 }
1063
1064 fn resolve_family_font_id(&self, family: FontFamily) -> Option<fontdb::ID> {
1069 self.atlas.font_system().db().query(&fontdb::Query {
1070 families: &[fontdb::Family::Name(family.family_name())],
1071 weight: fontdb::Weight::NORMAL,
1072 ..fontdb::Query::default()
1073 })
1074 }
1075
1076 pub(crate) fn warm_msdf_for_chars(&mut self, chars: &[char], families: &[FontFamily]) {
1082 self.warm_msdf_weights(chars, families, STOCK_WEIGHTS);
1083 }
1084
1085 pub(crate) fn warm_msdf_weights(
1089 &mut self,
1090 chars: &[char],
1091 families: &[FontFamily],
1092 weights: &[u16],
1093 ) {
1094 for family in families {
1095 let Some(font_id) = self.resolve_family_font_id(*family) else {
1096 continue;
1097 };
1098 let face_index = self
1099 .atlas
1100 .font_system()
1101 .db()
1102 .face(font_id)
1103 .map(|f| f.index)
1104 .unwrap_or(0);
1105 let Some(font) = self
1106 .atlas
1107 .font_system_mut()
1108 .get_font(font_id, fontdb::Weight::NORMAL)
1109 else {
1110 continue;
1111 };
1112 for &weight in weights {
1113 let Ok(mut face) = Face::parse(font.data(), face_index) else {
1114 continue;
1115 };
1116 let applied = apply_weight_variation(&mut face, weight);
1117 let keys: Vec<MsdfGlyphKey> = chars
1118 .iter()
1119 .filter_map(|&ch| face.glyph_index(ch))
1120 .map(|glyph_id| MsdfGlyphKey {
1121 font: font_id,
1122 glyph_id: glyph_id.0,
1123 weight: applied,
1124 })
1125 .collect();
1126 self.msdf_atlas.ensure_many(&keys, &face);
1132 }
1133 }
1134 }
1135
1136 pub(crate) fn export_msdf_snapshot(&self) -> Vec<u8> {
1140 let db = self.atlas.font_system().db();
1141 self.msdf_atlas
1142 .export_snapshot(|id| db.with_face_data(id, |data, _| font_token_hash(data)))
1143 }
1144
1145 pub(crate) fn import_msdf_snapshot(&mut self, bytes: &[u8]) -> Result<usize, SnapshotError> {
1148 let by_hash: std::collections::HashMap<u64, fontdb::ID> = {
1152 let db = self.atlas.font_system().db();
1153 db.faces()
1154 .filter_map(|f| {
1155 db.with_face_data(f.id, |data, _| font_token_hash(data))
1156 .map(|h| (h, f.id))
1157 })
1158 .collect()
1159 };
1160 self.msdf_atlas
1161 .import_snapshot(bytes, |t| by_hash.get(&t).copied())
1162 }
1163}
1164
1165fn ensure_msdf(
1167 inner: &mut SharedTextInner,
1168 key: MsdfGlyphKey,
1169 font_id: fontdb::ID,
1170 weight: fontdb::Weight,
1171) -> Option<MsdfSlot> {
1172 if let Some(slot) = inner.msdf_atlas.touch(key) {
1175 return Some(slot);
1176 }
1177 let font = inner.atlas.font_system_mut().get_font(font_id, weight)?;
1183 let face_index = inner.atlas.font_system().db().face(font_id)?.index;
1184 let mut face = Face::parse(font.data(), face_index).ok()?;
1185 let _applied = apply_weight_variation(&mut face, key.weight);
1190 debug_assert_eq!(
1191 _applied, key.weight,
1192 "normalized key weight must be applicable to its face"
1193 );
1194 inner.msdf_atlas.ensure(key, &face)
1195}
1196
1197fn same_kind(a: TextRunKind, b: TextRunKind) -> bool {
1198 a == b
1199}
1200
1201fn build_color_pipeline(
1205 device: &wgpu::Device,
1206 layout: &wgpu::PipelineLayout,
1207 target_format: wgpu::TextureFormat,
1208 sample_count: u32,
1209) -> wgpu::RenderPipeline {
1210 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1211 label: Some("stock::text"),
1212 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT)),
1213 });
1214 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1215 label: Some("damascene_wgpu::text::color_pipeline"),
1216 layout: Some(layout),
1217 vertex: wgpu::VertexState {
1218 module: &shader,
1219 entry_point: Some("vs_main"),
1220 compilation_options: Default::default(),
1221 buffers: &[
1222 Some(wgpu::VertexBufferLayout {
1223 array_stride: (2 * std::mem::size_of::<f32>()) as u64,
1224 step_mode: wgpu::VertexStepMode::Vertex,
1225 attributes: &[wgpu::VertexAttribute {
1226 shader_location: 0,
1227 format: wgpu::VertexFormat::Float32x2,
1228 offset: 0,
1229 }],
1230 }),
1231 Some(wgpu::VertexBufferLayout {
1232 array_stride: std::mem::size_of::<ColorGlyphInstance>() as u64,
1233 step_mode: wgpu::VertexStepMode::Instance,
1234 attributes: &COLOR_INSTANCE_ATTRS,
1235 }),
1236 ],
1237 },
1238 fragment: Some(wgpu::FragmentState {
1239 module: &shader,
1240 entry_point: Some("fs_main"),
1241 compilation_options: Default::default(),
1242 targets: &[Some(wgpu::ColorTargetState {
1243 format: target_format,
1244 blend: Some(premultiplied_blend()),
1245 write_mask: wgpu::ColorWrites::ALL,
1246 })],
1247 }),
1248 primitive: triangle_strip(),
1249 depth_stencil: None,
1250 multisample: wgpu::MultisampleState {
1251 count: sample_count,
1252 mask: !0,
1253 alpha_to_coverage_enabled: false,
1254 },
1255 multiview_mask: None,
1256 cache: None,
1257 })
1258}
1259
1260fn build_msdf_pipeline(
1263 device: &wgpu::Device,
1264 layout: &wgpu::PipelineLayout,
1265 target_format: wgpu::TextureFormat,
1266 sample_count: u32,
1267) -> wgpu::RenderPipeline {
1268 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1269 label: Some("stock::text_msdf"),
1270 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT_MSDF)),
1271 });
1272 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1273 label: Some("damascene_wgpu::text::msdf_pipeline"),
1274 layout: Some(layout),
1275 vertex: wgpu::VertexState {
1276 module: &shader,
1277 entry_point: Some("vs_main"),
1278 compilation_options: Default::default(),
1279 buffers: &[
1280 Some(wgpu::VertexBufferLayout {
1281 array_stride: (2 * std::mem::size_of::<f32>()) as u64,
1282 step_mode: wgpu::VertexStepMode::Vertex,
1283 attributes: &[wgpu::VertexAttribute {
1284 shader_location: 0,
1285 format: wgpu::VertexFormat::Float32x2,
1286 offset: 0,
1287 }],
1288 }),
1289 Some(wgpu::VertexBufferLayout {
1290 array_stride: std::mem::size_of::<MsdfGlyphInstance>() as u64,
1291 step_mode: wgpu::VertexStepMode::Instance,
1292 attributes: &MSDF_INSTANCE_ATTRS,
1293 }),
1294 ],
1295 },
1296 fragment: Some(wgpu::FragmentState {
1297 module: &shader,
1298 entry_point: Some("fs_main"),
1299 compilation_options: Default::default(),
1300 targets: &[Some(wgpu::ColorTargetState {
1301 format: target_format,
1302 blend: Some(premultiplied_blend()),
1303 write_mask: wgpu::ColorWrites::ALL,
1304 })],
1305 }),
1306 primitive: triangle_strip(),
1307 depth_stencil: None,
1308 multisample: wgpu::MultisampleState {
1309 count: sample_count,
1310 mask: !0,
1311 alpha_to_coverage_enabled: false,
1312 },
1313 multiview_mask: None,
1314 cache: None,
1315 })
1316}
1317
1318fn build_highlight_pipeline(
1321 device: &wgpu::Device,
1322 layout: &wgpu::PipelineLayout,
1323 target_format: wgpu::TextureFormat,
1324 sample_count: u32,
1325) -> wgpu::RenderPipeline {
1326 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1327 label: Some("stock::text_highlight"),
1328 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::TEXT_HIGHLIGHT)),
1329 });
1330 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1331 label: Some("damascene_wgpu::text::highlight_pipeline"),
1332 layout: Some(layout),
1333 vertex: wgpu::VertexState {
1334 module: &shader,
1335 entry_point: Some("vs_main"),
1336 compilation_options: Default::default(),
1337 buffers: &[
1338 Some(wgpu::VertexBufferLayout {
1339 array_stride: (2 * std::mem::size_of::<f32>()) as u64,
1340 step_mode: wgpu::VertexStepMode::Vertex,
1341 attributes: &[wgpu::VertexAttribute {
1342 shader_location: 0,
1343 format: wgpu::VertexFormat::Float32x2,
1344 offset: 0,
1345 }],
1346 }),
1347 Some(wgpu::VertexBufferLayout {
1348 array_stride: std::mem::size_of::<HighlightInstance>() as u64,
1349 step_mode: wgpu::VertexStepMode::Instance,
1350 attributes: &HIGHLIGHT_INSTANCE_ATTRS,
1351 }),
1352 ],
1353 },
1354 fragment: Some(wgpu::FragmentState {
1355 module: &shader,
1356 entry_point: Some("fs_main"),
1357 compilation_options: Default::default(),
1358 targets: &[Some(wgpu::ColorTargetState {
1359 format: target_format,
1360 blend: Some(premultiplied_blend()),
1361 write_mask: wgpu::ColorWrites::ALL,
1362 })],
1363 }),
1364 primitive: triangle_strip(),
1365 depth_stencil: None,
1366 multisample: wgpu::MultisampleState {
1367 count: sample_count,
1368 mask: !0,
1369 alpha_to_coverage_enabled: false,
1370 },
1371 multiview_mask: None,
1372 cache: None,
1373 })
1374}
1375
1376fn premultiplied_blend() -> wgpu::BlendState {
1377 wgpu::BlendState {
1378 color: wgpu::BlendComponent {
1379 src_factor: wgpu::BlendFactor::One,
1380 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1381 operation: wgpu::BlendOperation::Add,
1382 },
1383 alpha: wgpu::BlendComponent {
1384 src_factor: wgpu::BlendFactor::One,
1385 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1386 operation: wgpu::BlendOperation::Add,
1387 },
1388 }
1389}
1390
1391fn triangle_strip() -> wgpu::PrimitiveState {
1392 wgpu::PrimitiveState {
1393 topology: wgpu::PrimitiveTopology::TriangleStrip,
1394 strip_index_format: None,
1395 front_face: wgpu::FrontFace::Ccw,
1396 cull_mode: None,
1397 polygon_mode: wgpu::PolygonMode::Fill,
1398 unclipped_depth: false,
1399 conservative: false,
1400 }
1401}
1402
1403fn create_color_page(
1404 device: &wgpu::Device,
1405 layout: &wgpu::BindGroupLayout,
1406 sampler: &wgpu::Sampler,
1407 width: u32,
1408 height: u32,
1409) -> PageTexture {
1410 let texture = device.create_texture(&wgpu::TextureDescriptor {
1411 label: Some("damascene_wgpu::text::color_page"),
1412 size: wgpu::Extent3d {
1413 width,
1414 height,
1415 depth_or_array_layers: 1,
1416 },
1417 mip_level_count: 1,
1418 sample_count: 1,
1419 dimension: wgpu::TextureDimension::D2,
1420 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1421 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1422 view_formats: &[],
1423 });
1424 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1425 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1426 label: Some("damascene_wgpu::text::color_page_bg"),
1427 layout,
1428 entries: &[
1429 wgpu::BindGroupEntry {
1430 binding: 0,
1431 resource: wgpu::BindingResource::TextureView(&view),
1432 },
1433 wgpu::BindGroupEntry {
1434 binding: 1,
1435 resource: wgpu::BindingResource::Sampler(sampler),
1436 },
1437 ],
1438 });
1439 PageTexture {
1440 texture,
1441 bind_group,
1442 }
1443}
1444
1445fn create_msdf_page(
1446 device: &wgpu::Device,
1447 layout: &wgpu::BindGroupLayout,
1448 sampler: &wgpu::Sampler,
1449 width: u32,
1450 height: u32,
1451) -> PageTexture {
1452 let texture = device.create_texture(&wgpu::TextureDescriptor {
1453 label: Some("damascene_wgpu::text::msdf_page"),
1454 size: wgpu::Extent3d {
1455 width,
1456 height,
1457 depth_or_array_layers: 1,
1458 },
1459 mip_level_count: 1,
1460 sample_count: 1,
1461 dimension: wgpu::TextureDimension::D2,
1462 format: wgpu::TextureFormat::Rgba8Unorm,
1465 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1466 view_formats: &[],
1467 });
1468 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1469 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1470 label: Some("damascene_wgpu::text::msdf_page_bg"),
1471 layout,
1472 entries: &[
1473 wgpu::BindGroupEntry {
1474 binding: 0,
1475 resource: wgpu::BindingResource::TextureView(&view),
1476 },
1477 wgpu::BindGroupEntry {
1478 binding: 1,
1479 resource: wgpu::BindingResource::Sampler(sampler),
1480 },
1481 ],
1482 });
1483 PageTexture {
1484 texture,
1485 bind_group,
1486 }
1487}
1488
1489impl TextRecorder for TextPaint {
1490 fn record(
1491 &mut self,
1492 rect: Rect,
1493 scissor: Option<PhysicalScissor>,
1494 style: &RunStyle,
1495 text: &str,
1496 size: f32,
1497 line_height: f32,
1498 wrap: TextWrap,
1499 anchor: TextAnchor,
1500 scale_factor: f32,
1501 ) -> std::ops::Range<usize> {
1502 self.record_inner(
1503 rect,
1504 scissor,
1505 &[(text.to_string(), style.clone())],
1506 size,
1507 line_height,
1508 wrap,
1509 anchor,
1510 scale_factor,
1511 )
1512 }
1513
1514 fn record_runs(
1515 &mut self,
1516 rect: Rect,
1517 scissor: Option<PhysicalScissor>,
1518 runs: &[(String, RunStyle)],
1519 size: f32,
1520 line_height: f32,
1521 wrap: TextWrap,
1522 anchor: TextAnchor,
1523 scale_factor: f32,
1524 ) -> std::ops::Range<usize> {
1525 self.record_inner(
1526 rect,
1527 scissor,
1528 runs,
1529 size,
1530 line_height,
1531 wrap,
1532 anchor,
1533 scale_factor,
1534 )
1535 }
1536}
1537
1538fn wrap_available_width(
1539 rect_w: f32,
1540 _scale_factor: f32,
1541 wrap: TextWrap,
1542 anchor: TextAnchor,
1543) -> Option<f32> {
1544 match (wrap, anchor) {
1547 (TextWrap::Wrap, _) => Some(rect_w),
1548 (TextWrap::NoWrap, TextAnchor::Start) => None,
1549 (TextWrap::NoWrap, TextAnchor::Middle | TextAnchor::End) => Some(rect_w),
1550 }
1551}
1552
1553fn upload_color_region(
1554 queue: &wgpu::Queue,
1555 texture: &wgpu::Texture,
1556 page: &AtlasPage,
1557 rect: AtlasRect,
1558) {
1559 if rect.w == 0 || rect.h == 0 {
1560 return;
1561 }
1562 let bpp = ATLAS_BYTES_PER_PIXEL as usize;
1563 let row_bytes = rect.w as usize * bpp;
1564 let mut bytes = Vec::with_capacity(row_bytes * rect.h as usize);
1565 for row in 0..rect.h {
1566 let y = rect.y + row;
1567 let start = (y as usize * page.width as usize + rect.x as usize) * bpp;
1568 let end = start + row_bytes;
1569 bytes.extend_from_slice(&page.pixels[start..end]);
1570 }
1571 queue.write_texture(
1572 wgpu::TexelCopyTextureInfo {
1573 texture,
1574 mip_level: 0,
1575 origin: wgpu::Origin3d {
1576 x: rect.x,
1577 y: rect.y,
1578 z: 0,
1579 },
1580 aspect: wgpu::TextureAspect::All,
1581 },
1582 &bytes,
1583 wgpu::TexelCopyBufferLayout {
1584 offset: 0,
1585 bytes_per_row: Some(rect.w * ATLAS_BYTES_PER_PIXEL),
1586 rows_per_image: Some(rect.h),
1587 },
1588 wgpu::Extent3d {
1589 width: rect.w,
1590 height: rect.h,
1591 depth_or_array_layers: 1,
1592 },
1593 );
1594}
1595
1596fn upload_msdf_region(
1597 queue: &wgpu::Queue,
1598 texture: &wgpu::Texture,
1599 page: &MsdfAtlasPage,
1600 rect: MsdfRect,
1601) {
1602 if rect.w == 0 || rect.h == 0 {
1603 return;
1604 }
1605 let bpp = MSDF_BYTES_PER_PIXEL as usize;
1606 let row_bytes = rect.w as usize * bpp;
1607 let mut bytes = Vec::with_capacity(row_bytes * rect.h as usize);
1608 for row in 0..rect.h {
1609 let y = rect.y + row;
1610 let start = (y as usize * page.width as usize + rect.x as usize) * bpp;
1611 let end = start + row_bytes;
1612 bytes.extend_from_slice(&page.pixels[start..end]);
1613 }
1614 queue.write_texture(
1615 wgpu::TexelCopyTextureInfo {
1616 texture,
1617 mip_level: 0,
1618 origin: wgpu::Origin3d {
1619 x: rect.x,
1620 y: rect.y,
1621 z: 0,
1622 },
1623 aspect: wgpu::TextureAspect::All,
1624 },
1625 &bytes,
1626 wgpu::TexelCopyBufferLayout {
1627 offset: 0,
1628 bytes_per_row: Some(rect.w * MSDF_BYTES_PER_PIXEL),
1629 rows_per_image: Some(rect.h),
1630 },
1631 wgpu::Extent3d {
1632 width: rect.w,
1633 height: rect.h,
1634 depth_or_array_layers: 1,
1635 },
1636 );
1637}