Skip to main content

astrelis_text/renderer/
sdf.rs

1//! SDF-only text renderer.
2//!
3//! This module provides [`SdfTextRenderer`], a text renderer that uses only
4//! Signed Distance Field (SDF) glyph atlas (~8 MB with default atlas size).
5//!
6//! # When to Use
7//!
8//! Use `SdfTextRenderer` when:
9//! - You need text effects (shadows, outlines, glows)
10//! - You need text that scales smoothly at any size
11//! - You're primarily rendering large text (24px+)
12//!
13//! # Memory Usage
14//!
15//! | Config | Atlas Size | GPU Memory | CPU Memory | Total |
16//! |--------|------------|------------|------------|-------|
17//! | small() | 512x512 | ~0.25 MB | ~0.25 MB | ~0.5 MB |
18//! | medium() | 1024x1024 | ~1 MB | ~1 MB | ~2 MB |
19//! | large() | 2048x2048 | ~4 MB | ~4 MB | ~8 MB |
20//!
21//! # Example
22//!
23//! ```ignore
24//! use astrelis_text::{SdfTextRenderer, Text, TextEffectsBuilder, FontSystem, Color};
25//! use astrelis_core::math::Vec2;
26//!
27//! let font_system = FontSystem::with_system_fonts();
28//! let mut renderer = SdfTextRenderer::new(context, font_system);
29//!
30//! let text = Text::new("Title")
31//!     .size(48.0)
32//!     .color(Color::WHITE);
33//!
34//! let mut buffer = renderer.prepare(&text);
35//!
36//! // Draw with effects
37//! let effects = TextEffectsBuilder::new()
38//!     .shadow(Vec2::new(2.0, 2.0), Color::BLACK)
39//!     .outline(1.5, Color::rgba(0.0, 0.0, 0.0, 0.8))
40//!     .build();
41//!
42//! renderer.draw_text_with_effects(&mut buffer, Vec2::new(100.0, 100.0), &effects);
43//! renderer.render(&mut render_pass);
44//! ```
45
46use std::sync::Arc;
47
48use astrelis_core::alloc::HashMap;
49use astrelis_core::math::Vec2;
50use astrelis_core::profiling::profile_function;
51use cosmic_text::{CacheKey, Color as CosmicColor, Metrics};
52
53use astrelis_render::{AsWgpu, GpuTexture, GraphicsContext, UniformBuffer, Viewport, wgpu};
54
55use crate::effects::TextEffects;
56use crate::font::FontSystem;
57use crate::sdf::{SdfConfig, generate_sdf};
58use crate::text::{Text, TextMetrics};
59
60use super::shared::{
61    AtlasEntry, AtlasPacker, GlyphPlacement, SdfAtlasEntry, SdfCacheKey, SdfParams, SharedContext,
62    TextBuffer, TextRender, TextRendererConfig, TextVertex,
63};
64use super::{SDF_BASE_SIZE, SDF_DEFAULT_SPREAD, orthographic_projection};
65
66/// SDF text renderer backend.
67///
68/// Manages the SDF glyph atlas and rendering pipeline.
69pub(crate) struct SdfBackend {
70    // GPU resources
71    pub(crate) pipeline: wgpu::RenderPipeline,
72    #[allow(dead_code)]
73    pub(crate) bind_group_layout: wgpu::BindGroupLayout,
74    /// GPU texture with cached view and metadata.
75    pub(crate) atlas: GpuTexture,
76    #[allow(dead_code)]
77    pub(crate) sampler: wgpu::Sampler,
78    pub(crate) bind_group: wgpu::BindGroup,
79    /// Typed uniform buffer for SDF parameters.
80    pub(crate) params_buffer: UniformBuffer<SdfParams>,
81    #[allow(dead_code)]
82    pub(crate) params_bind_group_layout: wgpu::BindGroupLayout,
83    pub(crate) params_bind_group: wgpu::BindGroup,
84
85    // Atlas management
86    pub(crate) atlas_data: Vec<u8>,
87    pub(crate) atlas_entries: HashMap<SdfCacheKey, SdfAtlasEntry>,
88    pub(crate) atlas_packer: AtlasPacker,
89    pub(crate) atlas_dirty: bool,
90
91    // Configuration
92    pub(crate) config: SdfConfig,
93}
94
95impl SdfBackend {
96    /// Create a new SDF backend.
97    pub fn new(shared: &SharedContext, atlas_size: u32, config: SdfConfig) -> Self {
98        let renderer = &shared.renderer;
99
100        // Create SDF shader
101        let shader = renderer.create_shader(
102            Some("Text SDF Shader"),
103            include_str!("../../shaders/text_sdf.wgsl"),
104        );
105
106        // Create SDF atlas texture using GpuTexture
107        let atlas = renderer.create_gpu_texture_2d(
108            Some("SDF Text Atlas"),
109            atlas_size,
110            atlas_size,
111            wgpu::TextureFormat::R8Unorm,
112            wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
113        );
114
115        let atlas_data = vec![0u8; (atlas_size * atlas_size) as usize];
116        let sampler = renderer.create_linear_sampler(Some("SDF Text Sampler"));
117
118        // SDF bind group layout
119        let bind_group_layout = renderer.create_bind_group_layout(
120            Some("SDF Text Bind Group Layout"),
121            &[
122                wgpu::BindGroupLayoutEntry {
123                    binding: 0,
124                    visibility: wgpu::ShaderStages::FRAGMENT,
125                    ty: wgpu::BindingType::Texture {
126                        multisampled: false,
127                        view_dimension: wgpu::TextureViewDimension::D2,
128                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
129                    },
130                    count: None,
131                },
132                wgpu::BindGroupLayoutEntry {
133                    binding: 1,
134                    visibility: wgpu::ShaderStages::FRAGMENT,
135                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
136                    count: None,
137                },
138            ],
139        );
140
141        let bind_group = renderer.create_bind_group(
142            Some("SDF Text Bind Group"),
143            &bind_group_layout,
144            &[
145                wgpu::BindGroupEntry {
146                    binding: 0,
147                    resource: wgpu::BindingResource::TextureView(atlas.view()),
148                },
149                wgpu::BindGroupEntry {
150                    binding: 1,
151                    resource: wgpu::BindingResource::Sampler(&sampler),
152                },
153            ],
154        );
155
156        // SDF params uniform buffer using UniformBuffer
157        let params_buffer =
158            renderer.create_typed_uniform(Some("SDF Params Buffer"), &SdfParams::default());
159
160        // SDF params bind group layout
161        let params_bind_group_layout = renderer.create_bind_group_layout(
162            Some("SDF Params Bind Group Layout"),
163            &[wgpu::BindGroupLayoutEntry {
164                binding: 0,
165                visibility: wgpu::ShaderStages::FRAGMENT,
166                ty: wgpu::BindingType::Buffer {
167                    ty: wgpu::BufferBindingType::Uniform,
168                    has_dynamic_offset: false,
169                    min_binding_size: None,
170                },
171                count: None,
172            }],
173        );
174
175        let params_bind_group = renderer.create_bind_group(
176            Some("SDF Params Bind Group"),
177            &params_bind_group_layout,
178            &[wgpu::BindGroupEntry {
179                binding: 0,
180                resource: params_buffer.as_binding(),
181            }],
182        );
183
184        // Create SDF pipeline layout
185        let pipeline_layout = renderer.create_pipeline_layout(
186            Some("SDF Text Pipeline Layout"),
187            &[
188                &bind_group_layout,
189                &shared.uniform_bind_group_layout,
190                &params_bind_group_layout,
191            ],
192            &[],
193        );
194
195        // Create SDF pipeline
196        let pipeline = renderer.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
197            label: Some("SDF Text Pipeline"),
198            layout: Some(&pipeline_layout),
199            vertex: wgpu::VertexState {
200                module: &shader,
201                entry_point: Some("vs_main"),
202                buffers: &[wgpu::VertexBufferLayout {
203                    array_stride: std::mem::size_of::<TextVertex>() as u64,
204                    step_mode: wgpu::VertexStepMode::Vertex,
205                    attributes: &wgpu::vertex_attr_array![
206                        0 => Float32x2,
207                        1 => Float32x2,
208                        2 => Float32x4,
209                    ],
210                }],
211                compilation_options: wgpu::PipelineCompilationOptions::default(),
212            },
213            fragment: Some(wgpu::FragmentState {
214                module: &shader,
215                entry_point: Some("fs_main"),
216                targets: &[Some(wgpu::ColorTargetState {
217                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
218                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
219                    write_mask: wgpu::ColorWrites::ALL,
220                })],
221                compilation_options: wgpu::PipelineCompilationOptions::default(),
222            }),
223            primitive: wgpu::PrimitiveState {
224                topology: wgpu::PrimitiveTopology::TriangleList,
225                strip_index_format: None,
226                front_face: wgpu::FrontFace::Ccw,
227                cull_mode: None,
228                polygon_mode: wgpu::PolygonMode::Fill,
229                unclipped_depth: false,
230                conservative: false,
231            },
232            depth_stencil: None,
233            multisample: wgpu::MultisampleState {
234                count: 1,
235                mask: !0,
236                alpha_to_coverage_enabled: false,
237            },
238            multiview: None,
239            cache: None,
240        });
241
242        Self {
243            pipeline,
244            bind_group_layout,
245            atlas,
246            sampler,
247            bind_group,
248            params_buffer,
249            params_bind_group_layout,
250            params_bind_group,
251            atlas_data,
252            atlas_entries: HashMap::new(),
253            atlas_packer: AtlasPacker::new(atlas_size),
254            atlas_dirty: false,
255            config,
256        }
257    }
258
259    /// Ensure a glyph is in the SDF atlas.
260    pub fn ensure_glyph(
261        &mut self,
262        shared: &SharedContext,
263        cache_key: CacheKey,
264    ) -> Option<&SdfAtlasEntry> {
265        let sdf_key = SdfCacheKey::from_cache_key(cache_key);
266
267        // Check if already in SDF atlas
268        if self.atlas_entries.contains_key(&sdf_key) {
269            return self.atlas_entries.get(&sdf_key);
270        }
271
272        // Create a cache key at base size for rasterization
273        let base_cache_key = CacheKey {
274            font_id: cache_key.font_id,
275            glyph_id: cache_key.glyph_id,
276            font_size_bits: SDF_BASE_SIZE.to_bits(),
277            x_bin: cache_key.x_bin,
278            y_bin: cache_key.y_bin,
279            flags: cache_key.flags,
280        };
281
282        // Rasterize the glyph at base size
283        let mut font_system = shared
284            .font_system
285            .write()
286            .map_err(|e| crate::error::TextError::LockPoisoned(e.to_string()))
287            .ok()?;
288        let mut swash_cache = shared
289            .swash_cache
290            .write()
291            .map_err(|e| crate::error::TextError::LockPoisoned(e.to_string()))
292            .ok()?;
293        let image = match swash_cache.get_image(&mut font_system, base_cache_key) {
294            Some(img) => img.clone(),
295            None => return None,
296        };
297
298        drop(font_system);
299        drop(swash_cache);
300
301        let width = image.placement.width;
302        let height = image.placement.height;
303
304        if width == 0 || height == 0 {
305            return None;
306        }
307
308        // Generate SDF from the rasterized bitmap
309        let spread = self.config.mode.spread().max(SDF_DEFAULT_SPREAD);
310        let sdf_data = generate_sdf(&image, spread);
311
312        if sdf_data.is_empty() {
313            return None;
314        }
315
316        // Add padding for effects
317        let padding = (spread.ceil() as u32) * 2;
318        let padded_width = width + padding * 2;
319        let padded_height = height + padding * 2;
320
321        // Try to pack into SDF atlas
322        let atlas_entry = self.atlas_packer.pack(padded_width, padded_height)?;
323
324        // Copy SDF data into atlas with padding
325        let atlas_size = self.atlas.width();
326        for y in 0..height {
327            for x in 0..width {
328                let src_idx = (y * width + x) as usize;
329                let dst_x = atlas_entry.x + padding + x;
330                let dst_y = atlas_entry.y + padding + y;
331                let dst_idx = (dst_y * atlas_size + dst_x) as usize;
332                if src_idx < sdf_data.len() && dst_idx < self.atlas_data.len() {
333                    self.atlas_data[dst_idx] = sdf_data[src_idx];
334                }
335            }
336        }
337
338        // Store the base placement info
339        let base_placement = GlyphPlacement {
340            left: image.placement.left as f32,
341            top: image.placement.top as f32,
342            width: width as f32,
343            height: height as f32,
344        };
345
346        let sdf_entry = SdfAtlasEntry {
347            entry: AtlasEntry {
348                x: atlas_entry.x + padding,
349                y: atlas_entry.y + padding,
350                width,
351                height,
352            },
353            spread,
354            base_size: SDF_BASE_SIZE,
355            base_placement,
356        };
357
358        self.atlas_dirty = true;
359        self.atlas_entries.insert(sdf_key, sdf_entry);
360        self.atlas_entries.get(&sdf_key)
361    }
362
363    /// Upload SDF atlas data to GPU if dirty.
364    pub fn upload_atlas(&mut self, shared: &SharedContext) {
365        if !self.atlas_dirty {
366            return;
367        }
368
369        let atlas_size = self.atlas.width();
370        shared.renderer.queue().write_texture(
371            wgpu::TexelCopyTextureInfo {
372                texture: self.atlas.as_wgpu(),
373                mip_level: 0,
374                origin: wgpu::Origin3d::ZERO,
375                aspect: wgpu::TextureAspect::All,
376            },
377            &self.atlas_data,
378            wgpu::TexelCopyBufferLayout {
379                offset: 0,
380                bytes_per_row: Some(atlas_size),
381                rows_per_image: Some(atlas_size),
382            },
383            wgpu::Extent3d {
384                width: atlas_size,
385                height: atlas_size,
386                depth_or_array_layers: 1,
387            },
388        );
389
390        self.atlas_dirty = false;
391    }
392
393    /// Update SDF params uniform buffer.
394    pub fn update_params(&self, shared: &SharedContext, params: &SdfParams) {
395        self.params_buffer
396            .write_uniform(shared.renderer.queue(), params);
397    }
398}
399
400/// SDF-only text renderer.
401///
402/// Uses only an SDF glyph atlas for rendering, with no bitmap support.
403/// This provides scalable text rendering with effects (~8 MB with default atlas).
404///
405/// Best for large text, titles, and text that needs effects like shadows/outlines.
406pub struct SdfTextRenderer {
407    shared: SharedContext,
408    backend: SdfBackend,
409
410    // Staging data
411    vertices: Vec<TextVertex>,
412    indices: Vec<u16>,
413}
414
415impl SdfTextRenderer {
416    /// Create a new SDF text renderer with default configuration.
417    pub fn new(context: Arc<GraphicsContext>, font_system: FontSystem) -> Self {
418        Self::with_config(context, font_system, TextRendererConfig::default())
419    }
420
421    /// Create a new SDF text renderer with custom configuration.
422    pub fn with_config(
423        context: Arc<GraphicsContext>,
424        font_system: FontSystem,
425        config: TextRendererConfig,
426    ) -> Self {
427        let shared = SharedContext::new(context, font_system.inner());
428        let backend = SdfBackend::new(&shared, config.atlas_size, config.sdf);
429
430        Self {
431            shared,
432            backend,
433            vertices: Vec::new(),
434            indices: Vec::new(),
435        }
436    }
437
438    /// Measure text dimensions without rendering.
439    pub fn measure_text(&self, text: &Text) -> (f32, f32) {
440        profile_function!();
441        let scale = self.shared.scale_factor();
442
443        // Handle lock poisoning gracefully
444        let mut font_system = match self.shared.font_system.write() {
445            Ok(guard) => guard,
446            Err(e) => {
447                tracing::error!("Font system lock poisoned: {}. Returning zero size.", e);
448                return (0.0, 0.0);
449            }
450        };
451
452        let mut buffer = TextBuffer::new(&mut font_system);
453        buffer.set_text(&mut font_system, text, scale);
454        buffer.layout(&mut font_system);
455        let (width, height) = buffer.bounds();
456        (width / scale, height / scale)
457    }
458
459    /// Get the logical (unscaled) bounds of a prepared text buffer.
460    pub fn buffer_bounds(&self, buffer: &TextBuffer) -> (f32, f32) {
461        let scale = self.shared.scale_factor();
462        let (width, height) = buffer.bounds();
463        (width / scale, height / scale)
464    }
465
466    /// Get font metrics for the given text style.
467    pub fn get_text_metrics(&self, text: &Text) -> TextMetrics {
468        profile_function!();
469        let font_size = text.get_font_size();
470        let line_height_multiplier = text.get_line_height();
471        let scale = self.shared.scale_factor();
472
473        let metrics = Metrics::new(
474            font_size * scale,
475            font_size * scale * line_height_multiplier,
476        );
477
478        let line_height = metrics.line_height / scale;
479        let ascent = font_size * 0.8;
480        let descent = font_size * 0.2;
481
482        TextMetrics {
483            ascent,
484            descent,
485            line_height,
486            baseline_offset: ascent,
487        }
488    }
489
490    /// Set the viewport for rendering.
491    ///
492    /// Note: SDF atlas doesn't need to be cleared on scale factor change
493    /// because SDF glyphs are resolution-independent.
494    pub fn set_viewport(&mut self, viewport: Viewport) {
495        self.shared.set_viewport(viewport);
496    }
497
498    /// Set SDF configuration.
499    pub fn set_sdf_config(&mut self, config: SdfConfig) {
500        self.backend.config = config;
501    }
502
503    /// Get the current SDF configuration.
504    pub fn sdf_config(&self) -> &SdfConfig {
505        &self.backend.config
506    }
507
508    /// Prepare text for rendering.
509    pub fn prepare(&mut self, text: &Text) -> TextBuffer {
510        profile_function!();
511
512        // Handle lock poisoning gracefully - create empty buffer on error
513        let mut font_system = match self.shared.font_system.write() {
514            Ok(guard) => guard,
515            Err(e) => {
516                tracing::error!(
517                    "Font system lock poisoned during prepare: {}. Attempting recovery.",
518                    e
519                );
520                // Try to recover by taking the poisoned lock
521                self.shared.font_system.write().unwrap_or_else(|poisoned| {
522                    tracing::warn!("Clearing poisoned lock and continuing");
523                    poisoned.into_inner()
524                })
525            }
526        };
527
528        let mut buffer = TextBuffer::new(&mut font_system);
529        buffer.set_text(&mut font_system, text, self.shared.scale_factor());
530        buffer.layout(&mut font_system);
531        buffer
532    }
533
534    /// Draw text at a position (without effects).
535    pub fn draw_text(&mut self, buffer: &mut TextBuffer, position: Vec2) {
536        // Use default (no effects) params
537        let params = SdfParams::default();
538        self.backend.update_params(&self.shared, &params);
539        self.draw_text_internal(buffer, position);
540    }
541
542    /// Draw text with effects at a position.
543    pub fn draw_text_with_effects(
544        &mut self,
545        buffer: &mut TextBuffer,
546        position: Vec2,
547        effects: &TextEffects,
548    ) {
549        profile_function!();
550
551        // Update SDF params from effects
552        let sdf_params = SdfParams::from_effects(effects, &self.backend.config);
553        self.backend.update_params(&self.shared, &sdf_params);
554
555        // Draw text
556        self.draw_text_internal(buffer, position);
557    }
558
559    /// Internal SDF text drawing implementation.
560    fn draw_text_internal(&mut self, buffer: &mut TextBuffer, position: Vec2) {
561        profile_function!();
562
563        let scale = self.shared.scale_factor();
564
565        // Handle lock poisoning gracefully
566        let mut font_system = match self.shared.font_system.write() {
567            Ok(guard) => guard,
568            Err(e) => {
569                tracing::error!(
570                    "Font system lock poisoned during draw: {}. Skipping layout.",
571                    e
572                );
573                return; // Skip rendering on error
574            }
575        };
576
577        buffer.layout(&mut font_system);
578        drop(font_system);
579
580        // Render glyphs using SDF atlas
581        for run in buffer.buffer.layout_runs() {
582            for glyph in run.glyphs.iter() {
583                let physical_glyph = glyph.physical((position.x, position.y + run.line_y), 1.0);
584                let cache_key = physical_glyph.cache_key;
585
586                // Ensure glyph is in SDF atlas
587                let sdf_entry = match self.backend.ensure_glyph(&self.shared, cache_key) {
588                    Some(e) => e.clone(),
589                    None => continue,
590                };
591
592                // Calculate scale factor from base size to target size
593                let target_size = f32::from_bits(cache_key.font_size_bits);
594                let size_scale = target_size / sdf_entry.base_size;
595
596                // Scale placement based on size ratio
597                let scaled_left = sdf_entry.base_placement.left * size_scale;
598                let scaled_top = sdf_entry.base_placement.top * size_scale;
599                let scaled_width = sdf_entry.base_placement.width * size_scale;
600                let scaled_height = sdf_entry.base_placement.height * size_scale;
601
602                let x = physical_glyph.x as f32 + scaled_left;
603                let y = physical_glyph.y as f32 - scaled_top;
604                let w = scaled_width;
605                let h = scaled_height;
606
607                let x = x / scale;
608                let y = y / scale;
609                let w = w / scale;
610                let h = h / scale;
611
612                let (u0, v0, u1, v1) = sdf_entry.entry.uv_coords(self.backend.atlas.width());
613
614                let color = glyph.color_opt.unwrap_or(CosmicColor::rgb(255, 255, 255));
615                let color_f = [
616                    color.r() as f32 / 255.0,
617                    color.g() as f32 / 255.0,
618                    color.b() as f32 / 255.0,
619                    color.a() as f32 / 255.0,
620                ];
621
622                // Pixel snapping for crisp rendering
623                let x = (x * scale).round() / scale;
624                let y = (y * scale).round() / scale;
625
626                // Create quad
627                let idx = self.vertices.len() as u16;
628
629                self.vertices.push(TextVertex {
630                    position: [x, y],
631                    tex_coords: [u0, v0],
632                    color: color_f,
633                });
634                self.vertices.push(TextVertex {
635                    position: [x + w, y],
636                    tex_coords: [u1, v0],
637                    color: color_f,
638                });
639                self.vertices.push(TextVertex {
640                    position: [x + w, y + h],
641                    tex_coords: [u1, v1],
642                    color: color_f,
643                });
644                self.vertices.push(TextVertex {
645                    position: [x, y + h],
646                    tex_coords: [u0, v1],
647                    color: color_f,
648                });
649
650                self.indices
651                    .extend_from_slice(&[idx, idx + 1, idx + 2, idx, idx + 2, idx + 3]);
652            }
653        }
654    }
655
656    /// Render all queued text to the given render pass.
657    pub fn render(&mut self, render_pass: &mut wgpu::RenderPass) {
658        profile_function!();
659
660        debug_assert!(
661            self.shared.viewport.is_valid(),
662            "Viewport size must be set before rendering text."
663        );
664
665        if self.vertices.is_empty() {
666            return;
667        }
668
669        self.backend.upload_atlas(&self.shared);
670
671        // Create buffers
672        let vertex_buffer = self
673            .shared
674            .renderer
675            .create_vertex_buffer(Some("SDF Text Vertex Buffer"), &self.vertices);
676
677        let index_buffer = self
678            .shared
679            .renderer
680            .create_index_buffer(Some("SDF Text Index Buffer"), &self.indices);
681
682        // Create projection uniform
683        let size = self.shared.viewport.to_logical();
684        let projection = orthographic_projection(size.width, size.height);
685        let uniform_buffer = self
686            .shared
687            .renderer
688            .create_uniform_buffer(Some("SDF Text Projection"), &projection);
689
690        // Create uniform bind group
691        let uniform_bind_group = self.shared.renderer.create_bind_group(
692            Some("SDF Text Uniform Bind Group"),
693            &self.shared.uniform_bind_group_layout,
694            &[wgpu::BindGroupEntry {
695                binding: 0,
696                resource: uniform_buffer.as_entire_binding(),
697            }],
698        );
699
700        // Render with SDF pipeline
701        render_pass.set_pipeline(&self.backend.pipeline);
702        render_pass.set_bind_group(0, &self.backend.bind_group, &[]);
703        render_pass.set_bind_group(1, &uniform_bind_group, &[]);
704        render_pass.set_bind_group(2, &self.backend.params_bind_group, &[]);
705        render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
706        render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
707        render_pass.draw_indexed(0..self.indices.len() as u32, 0, 0..1);
708
709        // Clear for next frame
710        self.vertices.clear();
711        self.indices.clear();
712    }
713
714    /// Get the font system.
715    pub fn font_system(&self) -> std::sync::Arc<std::sync::RwLock<cosmic_text::FontSystem>> {
716        self.shared.font_system.clone()
717    }
718
719    /// Get the swash cache.
720    pub fn swash_cache(&self) -> std::sync::Arc<std::sync::RwLock<cosmic_text::SwashCache>> {
721        self.shared.swash_cache.clone()
722    }
723
724    /// Get the atlas size in pixels.
725    pub fn atlas_size(&self) -> u32 {
726        self.backend.atlas.width()
727    }
728}
729
730impl TextRender for SdfTextRenderer {
731    fn prepare(&mut self, text: &Text) -> TextBuffer {
732        SdfTextRenderer::prepare(self, text)
733    }
734
735    fn draw_text(&mut self, buffer: &mut TextBuffer, position: Vec2) {
736        SdfTextRenderer::draw_text(self, buffer, position)
737    }
738
739    fn render(&mut self, render_pass: &mut wgpu::RenderPass) {
740        SdfTextRenderer::render(self, render_pass)
741    }
742
743    fn measure_text(&self, text: &Text) -> (f32, f32) {
744        SdfTextRenderer::measure_text(self, text)
745    }
746
747    fn set_viewport(&mut self, viewport: Viewport) {
748        SdfTextRenderer::set_viewport(self, viewport)
749    }
750
751    fn buffer_bounds(&self, buffer: &TextBuffer) -> (f32, f32) {
752        SdfTextRenderer::buffer_bounds(self, buffer)
753    }
754}