Skip to main content

ratatui_wgpu/backend/
builder.rs

1use std::marker::PhantomData;
2use std::num::NonZeroU32;
3use std::num::NonZeroU64;
4
5use bitvec::vec::BitVec;
6use ratatui_core::style::Color;
7use rustybuzz::UnicodeBuffer;
8use web_time::Duration;
9use web_time::Instant;
10use wgpu::include_wgsl;
11use wgpu::util::BufferInitDescriptor;
12use wgpu::util::DeviceExt;
13use wgpu::vertex_attr_array;
14use wgpu::AddressMode;
15use wgpu::Backends;
16use wgpu::BindGroupDescriptor;
17use wgpu::BindGroupEntry;
18use wgpu::BindGroupLayoutDescriptor;
19use wgpu::BindGroupLayoutEntry;
20use wgpu::BindingResource;
21use wgpu::BindingType;
22use wgpu::BlendState;
23use wgpu::Buffer;
24use wgpu::BufferBindingType;
25use wgpu::BufferDescriptor;
26use wgpu::BufferUsages;
27use wgpu::ColorTargetState;
28use wgpu::ColorWrites;
29use wgpu::Device;
30use wgpu::Extent3d;
31use wgpu::FilterMode;
32use wgpu::FragmentState;
33use wgpu::Instance;
34use wgpu::InstanceDescriptor;
35use wgpu::InstanceFlags;
36use wgpu::Limits;
37use wgpu::MipmapFilterMode;
38use wgpu::MultisampleState;
39use wgpu::PipelineCompilationOptions;
40use wgpu::PipelineLayoutDescriptor;
41use wgpu::PresentMode;
42use wgpu::PrimitiveState;
43use wgpu::PrimitiveTopology;
44use wgpu::RenderPipelineDescriptor;
45use wgpu::Sampler;
46use wgpu::SamplerBindingType;
47use wgpu::SamplerDescriptor;
48use wgpu::ShaderStages;
49use wgpu::Surface;
50use wgpu::SurfaceTarget;
51use wgpu::TextureDescriptor;
52use wgpu::TextureDimension;
53use wgpu::TextureFormat;
54use wgpu::TextureSampleType;
55use wgpu::TextureUsages;
56use wgpu::TextureView;
57use wgpu::TextureViewDescriptor;
58use wgpu::TextureViewDimension;
59use wgpu::VertexBufferLayout;
60use wgpu::VertexState;
61use wgpu::VertexStepMode;
62
63use crate::backend::build_wgpu_state;
64use crate::backend::private::Token;
65use crate::backend::wgpu_backend::WgpuBackend;
66use crate::backend::Dimensions;
67use crate::backend::PostProcessor;
68use crate::backend::RenderSurface;
69use crate::backend::TextBgVertexMember;
70use crate::backend::TextCacheBgPipeline;
71use crate::backend::TextCacheFgPipeline;
72use crate::backend::TextVertexMember;
73use crate::backend::Viewport;
74use crate::colors::named;
75use crate::colors::ColorTable;
76use crate::fonts::Font;
77use crate::fonts::Fonts;
78use crate::shaders::DefaultPostProcessor;
79use crate::utils::plan_cache::PlanCache;
80use crate::utils::text_atlas::Atlas;
81use crate::Error;
82use crate::Result;
83
84const CACHE_WIDTH: u32 = 1800;
85const CACHE_HEIGHT: u32 = 1200;
86
87/// Builds a [`WgpuBackend`] instance.
88///
89/// Height and width will default to 1x1, so don't forget to call
90/// [`Builder::with_dimensions`] to configure the backend presentation
91/// dimensions.
92pub struct Builder<'a, P: PostProcessor = DefaultPostProcessor> {
93    user_data: P::UserData,
94    fonts: Fonts<'a>,
95    instance: Option<Instance>,
96    limits: Option<Limits>,
97    present_mode: Option<PresentMode>,
98    width: NonZeroU32,
99    height: NonZeroU32,
100    viewport: Viewport,
101    colors: ColorTable,
102    reset_fg: Color,
103    reset_bg: Color,
104    fast_blink: Duration,
105    slow_blink: Duration,
106}
107
108impl<'a, P: PostProcessor> Builder<'a, P>
109where
110    P::UserData: Default,
111{
112    /// Create a new Builder from a specified [`Font`] and default
113    /// [`PostProcessor::UserData`].
114    pub fn from_font(font: Font<'a>) -> Self {
115        Self {
116            user_data: Default::default(),
117            instance: None,
118            fonts: Fonts::new(font, 24),
119            limits: None,
120            present_mode: None,
121            width: NonZeroU32::new(1).unwrap(),
122            height: NonZeroU32::new(1).unwrap(),
123            viewport: Viewport::Full,
124            colors: named::DEFAULT_COLORS,
125            reset_fg: Color::Black,
126            reset_bg: Color::White,
127            fast_blink: Duration::from_millis(200),
128            slow_blink: Duration::from_millis(1000),
129        }
130    }
131}
132
133impl<'a, P: PostProcessor> Builder<'a, P> {
134    /// Create a new Builder from a specified [`Font`] and supplied
135    /// [`PostProcessor::UserData`].
136    pub fn from_font_and_user_data(
137        font: Font<'a>,
138        user_data: P::UserData,
139    ) -> Self {
140        Self {
141            user_data,
142            instance: None,
143            fonts: Fonts::new(font, 24),
144            limits: None,
145            present_mode: None,
146            width: NonZeroU32::new(1).unwrap(),
147            height: NonZeroU32::new(1).unwrap(),
148            viewport: Viewport::Full,
149            colors: named::DEFAULT_COLORS,
150            reset_fg: Color::Black,
151            reset_bg: Color::White,
152            fast_blink: Duration::from_millis(200),
153            slow_blink: Duration::from_millis(1000),
154        }
155    }
156
157    /// Use the supplied [`wgpu::Instance`] when building the backend.
158    #[must_use]
159    pub fn with_instance(
160        mut self,
161        instance: Instance,
162    ) -> Self {
163        self.instance = Some(instance);
164        self
165    }
166
167    /// Use the supplied [`Viewport`] for rendering. Defaults to
168    /// [`Viewport::Full`].
169    #[must_use]
170    pub fn with_viewport(
171        mut self,
172        viewport: Viewport,
173    ) -> Self {
174        self.viewport = viewport;
175        self
176    }
177
178    /// Use the specified font size in pixels. Defaults to 24px.
179    #[must_use]
180    pub fn with_font_size_px(
181        mut self,
182        size: u32,
183    ) -> Self {
184        self.fonts.set_size_px(size);
185        self
186    }
187
188    /// Use the specified list of fonts for rendering. You may call this
189    /// multiple times to extend the list of fallback fonts. Note that this will
190    /// automatically organize fonts by relative width in order to optimize
191    /// fallback rendering quality. The ordering of already provided fonts will
192    /// remain unchanged.
193    ///
194    /// See also [`Fonts::add_fonts`].
195    pub fn with_fonts<I: IntoIterator<Item = Font<'a>>>(
196        mut self,
197        fonts: I,
198    ) -> Self {
199        self.fonts.add_fonts(fonts);
200        self
201    }
202
203    /// Use the specified list of regular fonts for rendering. You may call this
204    /// multiple times to extend the list of fallback fonts.
205    ///
206    /// See also [`Fonts::add_regular_fonts`].
207    #[must_use]
208    pub fn with_regular_fonts<I: IntoIterator<Item = Font<'a>>>(
209        mut self,
210        fonts: I,
211    ) -> Self {
212        self.fonts.add_regular_fonts(fonts);
213        self
214    }
215
216    /// Use the specified list of bold fonts for rendering. You may call this
217    /// multiple times to extend the list of fallback fonts.
218    ///
219    /// See also [`Fonts::add_bold_fonts`].
220    #[must_use]
221    pub fn with_bold_fonts<I: IntoIterator<Item = Font<'a>>>(
222        mut self,
223        fonts: I,
224    ) -> Self {
225        self.fonts.add_bold_fonts(fonts);
226        self
227    }
228
229    /// Use the specified list of italic fonts for rendering. You may call this
230    /// multiple times to extend the list of fallback fonts.
231    ///
232    /// See also [`Fonts::add_italic_fonts`].
233    #[must_use]
234    pub fn with_italic_fonts<I: IntoIterator<Item = Font<'a>>>(
235        mut self,
236        fonts: I,
237    ) -> Self {
238        self.fonts.add_italic_fonts(fonts);
239        self
240    }
241
242    /// Use the specified list of bold italic fonts for rendering. You may call
243    /// this multiple times to extend the list of fallback fonts.
244    ///
245    /// See also [`Fonts::add_bold_italic_fonts`].
246    #[must_use]
247    pub fn with_bold_italic_fonts<I: IntoIterator<Item = Font<'a>>>(
248        mut self,
249        fonts: I,
250    ) -> Self {
251        self.fonts.add_bold_italic_fonts(fonts);
252        self
253    }
254
255    /// Use the specified [`wgpu::Limits`]. Defaults to
256    /// [`wgpu::Adapter::limits`].
257    #[must_use]
258    pub fn with_limits(
259        mut self,
260        limits: Limits,
261    ) -> Self {
262        self.limits = Some(limits);
263        self
264    }
265
266    /// Use the specified [`wgpu::PresentMode`].
267    #[must_use]
268    pub fn with_present_mode(
269        mut self,
270        mode: PresentMode,
271    ) -> Self {
272        self.present_mode = Some(mode);
273        self
274    }
275
276    /// Use the specified height and width when creating the surface. Defaults
277    /// to 1x1.
278    #[must_use]
279    pub fn with_dimensions(
280        mut self,
281        dimensions: Dimensions,
282    ) -> Self {
283        self.width = dimensions.width;
284        self.height = dimensions.height;
285        self
286    }
287
288    /// Use the specified height and width when creating the surface. Defaults
289    /// to 1x1.
290    #[must_use]
291    pub fn with_width_and_height(
292        mut self,
293        dimensions: Dimensions,
294    ) -> Self {
295        self.width = dimensions.width;
296        self.height = dimensions.height;
297        self
298    }
299
300    /// Use the specified [`ColorTable`] for the base-16 colors.
301    /// There is a default value for this.
302    pub fn with_color_table(
303        mut self,
304        colors: ColorTable,
305    ) -> Self {
306        self.colors = colors;
307        self
308    }
309
310    /// Use the specified [`ratatui::style::Color`] for the default foreground
311    /// color. Defaults to Black.
312    #[must_use]
313    pub fn with_fg_color(
314        mut self,
315        fg: Color,
316    ) -> Self {
317        self.reset_fg = fg;
318        self
319    }
320
321    /// Use the specified [`ratatui::style::Color`] for the default background
322    /// color. Defaults to White.
323    #[must_use]
324    pub fn with_bg_color(
325        mut self,
326        bg: Color,
327    ) -> Self {
328        self.reset_bg = bg;
329        self
330    }
331
332    /// Use the specified interval in milliseconds as the rapid blink speed.
333    /// Note that this library doesn't spin off rendering into a separate thread
334    /// for you. If you want text to blink, you must ensure that a call to
335    /// `flush` is made frequently enough. Defaults to 200ms.
336    #[must_use]
337    pub fn with_rapid_blink_millis(
338        mut self,
339        millis: u64,
340    ) -> Self {
341        self.fast_blink = Duration::from_millis(millis);
342        self
343    }
344
345    /// Use the specified interval in milliseconds as the slow blink speed.
346    /// Note that this library doesn't spin off rendering into a separate thread
347    /// for you. If you want text to blink, you must ensure that a call to
348    /// `flush` is made frequently enough. Defaults to 1000ms.
349    #[must_use]
350    pub fn with_slow_blink_millis(
351        mut self,
352        millis: u64,
353    ) -> Self {
354        self.slow_blink = Duration::from_millis(millis);
355        self
356    }
357}
358
359impl<'a, P: PostProcessor> Builder<'a, P> {
360    /// Build a new backend with the provided surface target - e.g. a winit
361    /// `Window`.
362    pub async fn build_with_target<'s>(
363        mut self,
364        target: impl Into<SurfaceTarget<'s>>,
365    ) -> Result<WgpuBackend<'a, 's, P>> {
366        let instance = self.instance.get_or_insert_with(|| {
367            wgpu::Instance::new(&InstanceDescriptor {
368                backends: Backends::default(),
369                flags: InstanceFlags::default(),
370                ..Default::default()
371            })
372        });
373        let surface = instance
374            .create_surface(target)
375            .map_err(Error::SurfaceCreationFailed)?;
376
377        self.build_with_surface(surface).await
378    }
379
380    /// Build a new backend from this builder with the supplied surface. You
381    /// almost certainly want to call this with the instance you used to create
382    /// the provided surface - see [`Builder::with_instance`]. If one is not
383    /// provided, a default instance will be created.
384    pub async fn build_with_surface<'s>(
385        self,
386        surface: Surface<'s>,
387    ) -> Result<WgpuBackend<'a, 's, P>> {
388        self.build_with_render_surface(surface).await
389    }
390
391    #[cfg(test)]
392    pub(crate) async fn build_headless(
393        self
394    ) -> Result<WgpuBackend<'a, 'static, P, super::HeadlessSurface>> {
395        self.build_with_render_surface(super::HeadlessSurface::default())
396            .await
397    }
398
399    #[cfg(test)]
400    pub(crate) async fn build_headless_with_format(
401        self,
402        format: TextureFormat,
403    ) -> Result<WgpuBackend<'a, 'static, P, super::HeadlessSurface>> {
404        self.build_with_render_surface(super::HeadlessSurface::new(format))
405            .await
406    }
407
408    async fn build_with_render_surface<'s, S: RenderSurface<'s> + 's>(
409        mut self,
410        mut surface: S,
411    ) -> Result<WgpuBackend<'a, 's, P, S>> {
412        let instance = self.instance.get_or_insert_with(|| {
413            wgpu::Instance::new(&InstanceDescriptor {
414                backends: Backends::default(),
415                flags: InstanceFlags::default(),
416                ..Default::default()
417            })
418        });
419
420        let adapter = instance
421            .request_adapter(&wgpu::RequestAdapterOptions {
422                compatible_surface: surface.wgpu_surface(Token),
423                ..Default::default()
424            })
425            .await
426            .map_err(Error::AdapterRequestFailed)?;
427
428        let limits = if let Some(limits) = self.limits {
429            min_limits(&adapter, limits)
430        } else {
431            adapter.limits()
432        };
433
434        let (device, queue) = adapter
435            .request_device(&wgpu::DeviceDescriptor {
436                required_limits: limits.clone(),
437                ..Default::default()
438            })
439            .await
440            .map_err(Error::DeviceRequestFailed)?;
441
442        let mut surface_config = surface
443            .get_default_config(
444                &adapter,
445                self.width.get().min(limits.max_texture_dimension_2d),
446                self.height.get().min(limits.max_texture_dimension_2d),
447                Token,
448            )
449            .ok_or(Error::SurfaceConfigurationRequestFailed)?;
450
451        if let Some(mode) = self.present_mode {
452            surface_config.present_mode = mode;
453        }
454
455        surface.configure(&device, &surface_config, Token);
456
457        let (inset_width, inset_height) = match self.viewport {
458            Viewport::Full => (0, 0),
459            Viewport::Shrink { width, height } => (width, height),
460        };
461
462        let drawable_width = surface_config.width - inset_width;
463        let drawable_height = surface_config.height - inset_height;
464
465        info!(
466            "char width x height: {}x{}",
467            self.fonts.min_width_px(),
468            self.fonts.height_px()
469        );
470
471        let text_cache = device.create_texture(&TextureDescriptor {
472            label: Some("Text Atlas"),
473            size: Extent3d {
474                width: CACHE_WIDTH,
475                height: CACHE_HEIGHT,
476                depth_or_array_layers: 1,
477            },
478            mip_level_count: 1,
479            sample_count: 1,
480            dimension: TextureDimension::D2,
481            format: TextureFormat::Rgba8Unorm,
482            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
483            view_formats: &[],
484        });
485
486        let text_cache_view = text_cache.create_view(&TextureViewDescriptor::default());
487
488        let text_mask = device.create_texture(&TextureDescriptor {
489            label: Some("Text Mask"),
490            size: Extent3d {
491                width: CACHE_WIDTH,
492                height: CACHE_HEIGHT,
493                depth_or_array_layers: 1,
494            },
495            mip_level_count: 1,
496            sample_count: 1,
497            dimension: TextureDimension::D2,
498            format: TextureFormat::R8Unorm,
499            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
500            view_formats: &[],
501        });
502
503        let text_mask_view = text_mask.create_view(&TextureViewDescriptor::default());
504
505        let sampler = device.create_sampler(&SamplerDescriptor {
506            address_mode_u: AddressMode::ClampToEdge,
507            address_mode_v: AddressMode::ClampToEdge,
508            address_mode_w: AddressMode::ClampToEdge,
509            mag_filter: FilterMode::Nearest,
510            min_filter: FilterMode::Nearest,
511            mipmap_filter: MipmapFilterMode::Nearest,
512            ..Default::default()
513        });
514
515        let text_screen_size_buffer = device.create_buffer(&BufferDescriptor {
516            label: Some("Text Uniforms Buffer"),
517            size: size_of::<[f32; 4]>() as u64,
518            mapped_at_creation: false,
519            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
520        });
521
522        let atlas_size_buffer = device.create_buffer_init(&BufferInitDescriptor {
523            label: Some("Atlas Size buffer"),
524            contents: bytemuck::cast_slice(&[CACHE_WIDTH as f32, CACHE_HEIGHT as f32, 0.0, 0.0]),
525            usage: BufferUsages::UNIFORM,
526        });
527
528        let text_bg_compositor = build_text_bg_compositor(&device, &text_screen_size_buffer);
529
530        let text_fg_compositor = build_text_fg_compositor(
531            &device,
532            &text_screen_size_buffer,
533            &atlas_size_buffer,
534            &text_cache_view,
535            &text_mask_view,
536            &sampler,
537        );
538
539        let wgpu_state = build_wgpu_state(
540            &device,
541            (drawable_width / self.fonts.min_width_px()) * self.fonts.min_width_px(),
542            (drawable_height / self.fonts.height_px()) * self.fonts.height_px(),
543        );
544
545        let reset_fg = self.colors.c2c(self.reset_fg, [0, 0, 0]);
546        let reset_bg = self.colors.c2c(self.reset_bg, [255, 255, 255]);
547
548        Ok(WgpuBackend {
549            post_process: P::compile(
550                &device,
551                &wgpu_state.text_dest_view,
552                &surface_config,
553                self.user_data,
554            ),
555            cells: vec![],
556            dirty_rows: vec![],
557            dirty_cells: BitVec::new(),
558            rendered: vec![],
559            sourced: vec![],
560            fast_blinking: BitVec::new(),
561            slow_blinking: BitVec::new(),
562            cursor: (0, 0),
563            surface,
564            _surface: PhantomData,
565            surface_config,
566            device,
567            queue,
568            plan_cache: PlanCache::new(self.fonts.count().max(2)),
569            buffer: UnicodeBuffer::new(),
570            row: String::new(),
571            rowmap: vec![],
572            viewport: self.viewport,
573            cached: Atlas::new(&self.fonts, CACHE_WIDTH, CACHE_HEIGHT),
574            text_cache,
575            text_mask,
576            bg_vertices: vec![],
577            text_indices: vec![],
578            text_vertices: vec![],
579            text_screen_size_buffer,
580            text_bg_compositor,
581            text_fg_compositor,
582            wgpu_state,
583            fonts: self.fonts,
584            colors: self.colors,
585            reset_fg,
586            reset_bg,
587            fast_duration: self.fast_blink,
588            last_fast_toggle: Instant::now(),
589            show_fast: true,
590            slow_duration: self.slow_blink,
591            last_slow_toggle: Instant::now(),
592            show_slow: true,
593        })
594    }
595}
596
597fn build_text_bg_compositor(
598    device: &Device,
599    screen_size: &Buffer,
600) -> TextCacheBgPipeline {
601    let shader = device.create_shader_module(include_wgsl!("shaders/composite_bg.wgsl"));
602
603    let vertex_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
604        label: Some("Text Bg Compositor Uniforms Binding Layout"),
605        entries: &[BindGroupLayoutEntry {
606            binding: 0,
607            visibility: ShaderStages::VERTEX,
608            ty: BindingType::Buffer {
609                ty: BufferBindingType::Uniform,
610                has_dynamic_offset: false,
611                min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
612            },
613            count: None,
614        }],
615    });
616
617    let fs_uniforms = device.create_bind_group(&BindGroupDescriptor {
618        label: Some("Text Bg Compositor Uniforms Binding"),
619        layout: &vertex_shader_layout,
620        entries: &[BindGroupEntry {
621            binding: 0,
622            resource: screen_size.as_entire_binding(),
623        }],
624    });
625
626    let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
627        label: Some("Text Bg Compositor Layout"),
628        bind_group_layouts: &[&vertex_shader_layout],
629        immediate_size: 0,
630    });
631
632    let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
633        label: Some("Text Bg Compositor Pipeline"),
634        layout: Some(&pipeline_layout),
635        vertex: VertexState {
636            module: &shader,
637            entry_point: Some("vs_main"),
638            compilation_options: PipelineCompilationOptions::default(),
639            buffers: &[VertexBufferLayout {
640                array_stride: size_of::<TextBgVertexMember>() as u64,
641                step_mode: VertexStepMode::Vertex,
642                attributes: &vertex_attr_array![0 => Float32x2, 1 => Uint32],
643            }],
644        },
645        primitive: PrimitiveState {
646            topology: PrimitiveTopology::TriangleList,
647            ..Default::default()
648        },
649        depth_stencil: None,
650        multisample: MultisampleState::default(),
651        fragment: Some(FragmentState {
652            module: &shader,
653            entry_point: Some("fs_main"),
654            compilation_options: PipelineCompilationOptions::default(),
655            targets: &[Some(ColorTargetState {
656                format: TextureFormat::Rgba8Unorm,
657                blend: None,
658                write_mask: ColorWrites::ALL,
659            })],
660        }),
661        multiview_mask: None,
662        cache: None,
663    });
664
665    TextCacheBgPipeline {
666        pipeline,
667        fs_uniforms,
668    }
669}
670
671fn build_text_fg_compositor(
672    device: &Device,
673    screen_size: &Buffer,
674    atlas_size: &Buffer,
675    cache_view: &TextureView,
676    mask_view: &TextureView,
677    sampler: &Sampler,
678) -> TextCacheFgPipeline {
679    let shader = device.create_shader_module(include_wgsl!("shaders/composite_fg.wgsl"));
680
681    let vertex_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
682        label: Some("Text Compositor Uniforms Binding Layout"),
683        entries: &[BindGroupLayoutEntry {
684            binding: 0,
685            visibility: ShaderStages::VERTEX,
686            ty: BindingType::Buffer {
687                ty: BufferBindingType::Uniform,
688                has_dynamic_offset: false,
689                min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
690            },
691            count: None,
692        }],
693    });
694
695    let fragment_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
696        label: Some("Text Compositor Fragment Binding Layout"),
697        entries: &[
698            BindGroupLayoutEntry {
699                binding: 0,
700                visibility: ShaderStages::FRAGMENT,
701                ty: BindingType::Texture {
702                    sample_type: TextureSampleType::Float { filterable: true },
703                    view_dimension: TextureViewDimension::D2,
704                    multisampled: false,
705                },
706                count: None,
707            },
708            BindGroupLayoutEntry {
709                binding: 1,
710                visibility: ShaderStages::FRAGMENT,
711                ty: BindingType::Texture {
712                    sample_type: TextureSampleType::Float { filterable: true },
713                    view_dimension: TextureViewDimension::D2,
714                    multisampled: false,
715                },
716                count: None,
717            },
718            BindGroupLayoutEntry {
719                binding: 2,
720                visibility: ShaderStages::FRAGMENT,
721                ty: BindingType::Sampler(SamplerBindingType::Filtering),
722                count: None,
723            },
724            BindGroupLayoutEntry {
725                binding: 3,
726                visibility: ShaderStages::FRAGMENT,
727                ty: BindingType::Buffer {
728                    ty: BufferBindingType::Uniform,
729                    has_dynamic_offset: false,
730                    min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
731                },
732                count: None,
733            },
734        ],
735    });
736
737    let fs_uniforms = device.create_bind_group(&BindGroupDescriptor {
738        label: Some("Text Compositor Uniforms Binding"),
739        layout: &vertex_shader_layout,
740        entries: &[BindGroupEntry {
741            binding: 0,
742            resource: screen_size.as_entire_binding(),
743        }],
744    });
745
746    let atlas_bindings = device.create_bind_group(&BindGroupDescriptor {
747        label: Some("Text Compositor Fragment Binding"),
748        layout: &fragment_shader_layout,
749        entries: &[
750            BindGroupEntry {
751                binding: 0,
752                resource: BindingResource::TextureView(cache_view),
753            },
754            BindGroupEntry {
755                binding: 1,
756                resource: BindingResource::TextureView(mask_view),
757            },
758            BindGroupEntry {
759                binding: 2,
760                resource: BindingResource::Sampler(sampler),
761            },
762            BindGroupEntry {
763                binding: 3,
764                resource: atlas_size.as_entire_binding(),
765            },
766        ],
767    });
768
769    let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
770        label: Some("Text Compositor Layout"),
771        bind_group_layouts: &[&vertex_shader_layout, &fragment_shader_layout],
772        immediate_size: 0,
773    });
774
775    let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
776        label: Some("Text Compositor Pipeline"),
777        layout: Some(&pipeline_layout),
778        vertex: VertexState {
779            module: &shader,
780            entry_point: Some("vs_main"),
781            compilation_options: PipelineCompilationOptions::default(),
782            buffers: &[VertexBufferLayout {
783                array_stride: size_of::<TextVertexMember>() as u64,
784                step_mode: VertexStepMode::Vertex,
785                attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32, 3 => Uint32, 4 => Uint32, 5 => Uint32, 6 => Uint32],
786            }],
787        },
788        primitive: PrimitiveState {
789            topology: PrimitiveTopology::TriangleList,
790            ..Default::default()
791        },
792        depth_stencil: None,
793        multisample: MultisampleState::default(),
794        fragment: Some(FragmentState {
795            module: &shader,
796            entry_point: Some("fs_main"),
797            compilation_options: PipelineCompilationOptions::default(),
798            targets: &[Some(ColorTargetState {
799                format: TextureFormat::Rgba8Unorm,
800                blend: Some(BlendState::ALPHA_BLENDING),
801                write_mask: ColorWrites::ALL,
802            })],
803        }),
804        multiview_mask: None,
805        cache: None,
806    });
807
808    TextCacheFgPipeline {
809        pipeline,
810        fs_uniforms,
811        atlas_bindings,
812    }
813}
814
815fn min_limits(
816    adapter: &wgpu::Adapter,
817    limits: Limits,
818) -> Limits {
819    let Limits {
820        max_texture_dimension_1d: max_texture_dimension_1d_wl,
821        max_texture_dimension_2d: max_texture_dimension_2d_wl,
822        max_texture_dimension_3d: max_texture_dimension_3d_wl,
823        max_texture_array_layers: max_texture_array_layers_wl,
824        max_bind_groups: max_bind_groups_wl,
825        max_bindings_per_bind_group: max_bindings_per_bind_group_wl,
826        max_dynamic_uniform_buffers_per_pipeline_layout:
827            max_dynamic_uniform_buffers_per_pipeline_layout_wl,
828        max_dynamic_storage_buffers_per_pipeline_layout:
829            max_dynamic_storage_buffers_per_pipeline_layout_wl,
830        max_sampled_textures_per_shader_stage: max_sampled_textures_per_shader_stage_wl,
831        max_samplers_per_shader_stage: max_samplers_per_shader_stage_wl,
832        max_storage_buffers_per_shader_stage: max_storage_buffers_per_shader_stage_wl,
833        max_storage_textures_per_shader_stage: max_storage_textures_per_shader_stage_wl,
834        max_uniform_buffers_per_shader_stage: max_uniform_buffers_per_shader_stage_wl,
835        max_uniform_buffer_binding_size: max_uniform_buffer_binding_size_wl,
836        max_storage_buffer_binding_size: max_storage_buffer_binding_size_wl,
837        max_vertex_buffers: max_vertex_buffers_wl,
838        max_buffer_size: max_buffer_size_wl,
839        max_vertex_attributes: max_vertex_attributes_wl,
840        max_vertex_buffer_array_stride: max_vertex_buffer_array_stride_wl,
841        min_uniform_buffer_offset_alignment: min_uniform_buffer_offset_alignment_wl,
842        min_storage_buffer_offset_alignment: min_storage_buffer_offset_alignment_wl,
843        max_inter_stage_shader_components: max_inter_stage_shader_components_wl,
844        max_color_attachments: max_color_attachments_wl,
845        max_color_attachment_bytes_per_sample: max_color_attachment_bytes_per_sample_wl,
846        max_compute_workgroup_storage_size: max_compute_workgroup_storage_size_wl,
847        max_compute_invocations_per_workgroup: max_compute_invocations_per_workgroup_wl,
848        max_compute_workgroup_size_x: max_compute_workgroup_size_x_wl,
849        max_compute_workgroup_size_y: max_compute_workgroup_size_y_wl,
850        max_compute_workgroup_size_z: max_compute_workgroup_size_z_wl,
851        max_compute_workgroups_per_dimension: max_compute_workgroups_per_dimension_wl,
852        max_non_sampler_bindings: max_non_sampler_bindings_wl,
853        max_binding_array_elements_per_shader_stage: max_binding_array_elements_per_shader_stage_wl,
854        max_binding_array_sampler_elements_per_shader_stage:
855            max_binding_array_sampler_elements_per_shader_stage_wl,
856        max_blas_primitive_count: max_blas_primitive_count_wl,
857        max_blas_geometry_count: max_blas_geometry_count_wl,
858        max_tlas_instance_count: max_tlas_instance_count_wl,
859        max_acceleration_structures_per_shader_stage:
860            max_acceleration_structures_per_shader_stage_wl,
861        max_immediate_size: max_immediate_size_wl,
862        max_task_mesh_workgroup_total_count: max_task_mesh_workgroup_total_count_wl,
863        max_task_mesh_workgroups_per_dimension: max_task_mesh_workgroups_per_dimension_wl,
864        max_task_invocations_per_workgroup: max_task_invocations_per_workgroup_wl,
865        max_task_invocations_per_dimension: max_task_invocations_per_dimension_wl,
866        max_mesh_invocations_per_workgroup: max_mesh_invocations_per_workgroup_wl,
867        max_mesh_invocations_per_dimension: max_mesh_invocations_per_dimension_wl,
868        max_task_payload_size: max_task_payload_size_wl,
869        max_mesh_output_vertices: max_mesh_output_vertices_wl,
870        max_mesh_output_primitives: max_mesh_output_primitives_wl,
871        max_mesh_output_layers: max_mesh_output_layers_wl,
872        max_mesh_multiview_view_count: max_mesh_multiview_view_count_wl,
873        max_multiview_view_count: max_multiview_view_count_wl,
874    } = limits;
875    let Limits {
876        max_texture_dimension_1d: max_texture_dimension_1d_al,
877        max_texture_dimension_2d: max_texture_dimension_2d_al,
878        max_texture_dimension_3d: max_texture_dimension_3d_al,
879        max_texture_array_layers: max_texture_array_layers_al,
880        max_bind_groups: max_bind_groups_al,
881        max_bindings_per_bind_group: max_bindings_per_bind_group_al,
882        max_dynamic_uniform_buffers_per_pipeline_layout:
883            max_dynamic_uniform_buffers_per_pipeline_layout_al,
884        max_dynamic_storage_buffers_per_pipeline_layout:
885            max_dynamic_storage_buffers_per_pipeline_layout_al,
886        max_sampled_textures_per_shader_stage: max_sampled_textures_per_shader_stage_al,
887        max_samplers_per_shader_stage: max_samplers_per_shader_stage_al,
888        max_storage_buffers_per_shader_stage: max_storage_buffers_per_shader_stage_al,
889        max_storage_textures_per_shader_stage: max_storage_textures_per_shader_stage_al,
890        max_uniform_buffers_per_shader_stage: max_uniform_buffers_per_shader_stage_al,
891        max_uniform_buffer_binding_size: max_uniform_buffer_binding_size_al,
892        max_storage_buffer_binding_size: max_storage_buffer_binding_size_al,
893        max_vertex_buffers: max_vertex_buffers_al,
894        max_buffer_size: max_buffer_size_al,
895        max_vertex_attributes: max_vertex_attributes_al,
896        max_vertex_buffer_array_stride: max_vertex_buffer_array_stride_al,
897        min_uniform_buffer_offset_alignment: min_uniform_buffer_offset_alignment_al,
898        min_storage_buffer_offset_alignment: min_storage_buffer_offset_alignment_al,
899        max_inter_stage_shader_components: max_inter_stage_shader_components_al,
900        max_color_attachments: max_color_attachments_al,
901        max_color_attachment_bytes_per_sample: max_color_attachment_bytes_per_sample_al,
902        max_compute_workgroup_storage_size: max_compute_workgroup_storage_size_al,
903        max_compute_invocations_per_workgroup: max_compute_invocations_per_workgroup_al,
904        max_compute_workgroup_size_x: max_compute_workgroup_size_x_al,
905        max_compute_workgroup_size_y: max_compute_workgroup_size_y_al,
906        max_compute_workgroup_size_z: max_compute_workgroup_size_z_al,
907        max_compute_workgroups_per_dimension: max_compute_workgroups_per_dimension_al,
908        max_non_sampler_bindings: max_non_sampler_bindings_al,
909        max_binding_array_elements_per_shader_stage: max_binding_array_elements_per_shader_stage_al,
910        max_binding_array_sampler_elements_per_shader_stage:
911            max_binding_array_sampler_elements_per_shader_stage_al,
912        max_blas_primitive_count: max_blas_primitive_count_al,
913        max_blas_geometry_count: max_blas_geometry_count_al,
914        max_tlas_instance_count: max_tlas_instance_count_al,
915        max_acceleration_structures_per_shader_stage:
916            max_acceleration_structures_per_shader_stage_al,
917        max_immediate_size: max_immediate_size_al,
918        max_task_mesh_workgroup_total_count: max_task_mesh_workgroup_total_count_al,
919        max_task_mesh_workgroups_per_dimension: max_task_mesh_workgroups_per_dimension_al,
920        max_task_invocations_per_workgroup: max_task_invocations_per_workgroup_al,
921        max_task_invocations_per_dimension: max_task_invocations_per_dimension_al,
922        max_mesh_invocations_per_workgroup: max_mesh_invocations_per_workgroup_al,
923        max_mesh_invocations_per_dimension: max_mesh_invocations_per_dimension_al,
924        max_task_payload_size: max_task_payload_size_al,
925        max_mesh_output_vertices: max_mesh_output_vertices_al,
926        max_mesh_output_primitives: max_mesh_output_primitives_al,
927        max_mesh_output_layers: max_mesh_output_layers_al,
928        max_mesh_multiview_view_count: max_mesh_multiview_view_count_al,
929        max_multiview_view_count: max_multiview_view_count_al,
930    } = adapter.limits();
931
932    Limits {
933        max_texture_dimension_1d: if max_texture_dimension_1d_wl <= max_texture_dimension_1d_al {
934            max_texture_dimension_1d_wl
935        } else {
936            max_texture_dimension_1d_al
937        },
938        max_texture_dimension_2d: if max_texture_dimension_2d_wl <= max_texture_dimension_2d_al {
939            max_texture_dimension_2d_wl
940        } else {
941            max_texture_dimension_2d_al
942        },
943        max_texture_dimension_3d: if max_texture_dimension_3d_wl <= max_texture_dimension_3d_al {
944            max_texture_dimension_3d_wl
945        } else {
946            max_texture_dimension_3d_al
947        },
948        max_texture_array_layers: if max_texture_array_layers_wl <= max_texture_array_layers_al {
949            max_texture_array_layers_wl
950        } else {
951            max_texture_array_layers_al
952        },
953        max_bind_groups: if max_bind_groups_wl <= max_bind_groups_al {
954            max_bind_groups_wl
955        } else {
956            max_bind_groups_al
957        },
958        max_bindings_per_bind_group: if max_bindings_per_bind_group_wl
959            <= max_bindings_per_bind_group_al
960        {
961            max_bindings_per_bind_group_wl
962        } else {
963            max_bindings_per_bind_group_al
964        },
965        max_dynamic_uniform_buffers_per_pipeline_layout:
966            if max_dynamic_uniform_buffers_per_pipeline_layout_wl
967                <= max_dynamic_uniform_buffers_per_pipeline_layout_al
968            {
969                max_dynamic_uniform_buffers_per_pipeline_layout_wl
970            } else {
971                max_dynamic_uniform_buffers_per_pipeline_layout_al
972            },
973        max_dynamic_storage_buffers_per_pipeline_layout:
974            if max_dynamic_storage_buffers_per_pipeline_layout_wl
975                <= max_dynamic_storage_buffers_per_pipeline_layout_al
976            {
977                max_dynamic_storage_buffers_per_pipeline_layout_wl
978            } else {
979                max_dynamic_storage_buffers_per_pipeline_layout_al
980            },
981        max_sampled_textures_per_shader_stage: if max_sampled_textures_per_shader_stage_wl
982            <= max_sampled_textures_per_shader_stage_al
983        {
984            max_sampled_textures_per_shader_stage_wl
985        } else {
986            max_sampled_textures_per_shader_stage_al
987        },
988        max_samplers_per_shader_stage: if max_samplers_per_shader_stage_wl
989            <= max_samplers_per_shader_stage_al
990        {
991            max_samplers_per_shader_stage_wl
992        } else {
993            max_samplers_per_shader_stage_al
994        },
995        max_storage_buffers_per_shader_stage: if max_storage_buffers_per_shader_stage_wl
996            <= max_storage_buffers_per_shader_stage_al
997        {
998            max_storage_buffers_per_shader_stage_wl
999        } else {
1000            max_storage_buffers_per_shader_stage_al
1001        },
1002        max_storage_textures_per_shader_stage: if max_storage_textures_per_shader_stage_wl
1003            <= max_storage_textures_per_shader_stage_al
1004        {
1005            max_storage_textures_per_shader_stage_wl
1006        } else {
1007            max_storage_textures_per_shader_stage_al
1008        },
1009        max_uniform_buffers_per_shader_stage: if max_uniform_buffers_per_shader_stage_wl
1010            <= max_uniform_buffers_per_shader_stage_al
1011        {
1012            max_uniform_buffers_per_shader_stage_wl
1013        } else {
1014            max_uniform_buffers_per_shader_stage_al
1015        },
1016        max_uniform_buffer_binding_size: if max_uniform_buffer_binding_size_wl
1017            <= max_uniform_buffer_binding_size_al
1018        {
1019            max_uniform_buffer_binding_size_wl
1020        } else {
1021            max_uniform_buffer_binding_size_al
1022        },
1023        max_storage_buffer_binding_size: if max_storage_buffer_binding_size_wl
1024            <= max_storage_buffer_binding_size_al
1025        {
1026            max_storage_buffer_binding_size_wl
1027        } else {
1028            max_storage_buffer_binding_size_al
1029        },
1030        max_vertex_buffers: if max_vertex_buffers_wl <= max_vertex_buffers_al {
1031            max_vertex_buffers_wl
1032        } else {
1033            max_vertex_buffers_al
1034        },
1035        max_buffer_size: if max_buffer_size_wl <= max_buffer_size_al {
1036            max_buffer_size_wl
1037        } else {
1038            max_buffer_size_al
1039        },
1040        max_vertex_attributes: if max_vertex_attributes_wl <= max_vertex_attributes_al {
1041            max_vertex_attributes_wl
1042        } else {
1043            max_vertex_attributes_al
1044        },
1045        max_vertex_buffer_array_stride: if max_vertex_buffer_array_stride_wl
1046            <= max_vertex_buffer_array_stride_al
1047        {
1048            max_vertex_buffer_array_stride_wl
1049        } else {
1050            max_vertex_buffer_array_stride_al
1051        },
1052        min_uniform_buffer_offset_alignment: if min_uniform_buffer_offset_alignment_wl
1053            <= min_uniform_buffer_offset_alignment_al
1054        {
1055            min_uniform_buffer_offset_alignment_wl
1056        } else {
1057            min_uniform_buffer_offset_alignment_al
1058        },
1059        min_storage_buffer_offset_alignment: if min_storage_buffer_offset_alignment_wl
1060            <= min_storage_buffer_offset_alignment_al
1061        {
1062            min_storage_buffer_offset_alignment_wl
1063        } else {
1064            min_storage_buffer_offset_alignment_al
1065        },
1066        max_inter_stage_shader_components: if max_inter_stage_shader_components_wl
1067            <= max_inter_stage_shader_components_al
1068        {
1069            max_inter_stage_shader_components_wl
1070        } else {
1071            max_inter_stage_shader_components_al
1072        },
1073        max_color_attachments: if max_color_attachments_wl <= max_color_attachments_al {
1074            max_color_attachments_wl
1075        } else {
1076            max_color_attachments_al
1077        },
1078        max_color_attachment_bytes_per_sample: if max_color_attachment_bytes_per_sample_wl
1079            <= max_color_attachment_bytes_per_sample_al
1080        {
1081            max_color_attachment_bytes_per_sample_wl
1082        } else {
1083            max_color_attachment_bytes_per_sample_al
1084        },
1085        max_compute_workgroup_storage_size: if max_compute_workgroup_storage_size_wl
1086            <= max_compute_workgroup_storage_size_al
1087        {
1088            max_compute_workgroup_storage_size_wl
1089        } else {
1090            max_compute_workgroup_storage_size_al
1091        },
1092        max_compute_invocations_per_workgroup: if max_compute_invocations_per_workgroup_wl
1093            <= max_compute_invocations_per_workgroup_al
1094        {
1095            max_compute_invocations_per_workgroup_wl
1096        } else {
1097            max_compute_invocations_per_workgroup_al
1098        },
1099        max_compute_workgroup_size_x: if max_compute_workgroup_size_x_wl
1100            <= max_compute_workgroup_size_x_al
1101        {
1102            max_compute_workgroup_size_x_wl
1103        } else {
1104            max_compute_workgroup_size_x_al
1105        },
1106        max_compute_workgroup_size_y: if max_compute_workgroup_size_y_wl
1107            <= max_compute_workgroup_size_y_al
1108        {
1109            max_compute_workgroup_size_y_wl
1110        } else {
1111            max_compute_workgroup_size_y_al
1112        },
1113        max_compute_workgroup_size_z: if max_compute_workgroup_size_z_wl
1114            <= max_compute_workgroup_size_z_al
1115        {
1116            max_compute_workgroup_size_z_wl
1117        } else {
1118            max_compute_workgroup_size_z_al
1119        },
1120        max_compute_workgroups_per_dimension: if max_compute_workgroups_per_dimension_wl
1121            <= max_compute_workgroups_per_dimension_al
1122        {
1123            max_compute_workgroups_per_dimension_wl
1124        } else {
1125            max_compute_workgroups_per_dimension_al
1126        },
1127        max_non_sampler_bindings: if max_non_sampler_bindings_wl <= max_non_sampler_bindings_al {
1128            max_non_sampler_bindings_wl
1129        } else {
1130            max_non_sampler_bindings_al
1131        },
1132        max_binding_array_elements_per_shader_stage:
1133            if max_binding_array_elements_per_shader_stage_wl
1134                <= max_binding_array_elements_per_shader_stage_al
1135            {
1136                max_binding_array_elements_per_shader_stage_wl
1137            } else {
1138                max_binding_array_elements_per_shader_stage_al
1139            },
1140        max_binding_array_sampler_elements_per_shader_stage:
1141            if max_binding_array_sampler_elements_per_shader_stage_wl
1142                <= max_binding_array_sampler_elements_per_shader_stage_al
1143            {
1144                max_binding_array_sampler_elements_per_shader_stage_wl
1145            } else {
1146                max_binding_array_sampler_elements_per_shader_stage_al
1147            },
1148        max_blas_primitive_count: if max_blas_primitive_count_wl <= max_blas_primitive_count_al {
1149            max_blas_primitive_count_wl
1150        } else {
1151            max_blas_primitive_count_al
1152        },
1153        max_blas_geometry_count: if max_blas_geometry_count_wl <= max_blas_geometry_count_al {
1154            max_blas_geometry_count_wl
1155        } else {
1156            max_blas_geometry_count_al
1157        },
1158        max_tlas_instance_count: if max_tlas_instance_count_wl <= max_tlas_instance_count_al {
1159            max_tlas_instance_count_wl
1160        } else {
1161            max_tlas_instance_count_al
1162        },
1163        max_acceleration_structures_per_shader_stage:
1164            if max_acceleration_structures_per_shader_stage_wl
1165                <= max_acceleration_structures_per_shader_stage_al
1166            {
1167                max_acceleration_structures_per_shader_stage_wl
1168            } else {
1169                max_acceleration_structures_per_shader_stage_al
1170            },
1171        max_immediate_size: if max_immediate_size_wl <= max_immediate_size_al {
1172            max_immediate_size_wl
1173        } else {
1174            max_immediate_size_al
1175        },
1176        max_task_mesh_workgroup_total_count: if max_task_mesh_workgroup_total_count_wl
1177            <= max_task_mesh_workgroup_total_count_al
1178        {
1179            max_task_mesh_workgroup_total_count_wl
1180        } else {
1181            max_task_mesh_workgroup_total_count_al
1182        },
1183        max_task_mesh_workgroups_per_dimension: if max_task_mesh_workgroups_per_dimension_wl
1184            <= max_task_mesh_workgroups_per_dimension_al
1185        {
1186            max_task_mesh_workgroups_per_dimension_wl
1187        } else {
1188            max_task_mesh_workgroups_per_dimension_al
1189        },
1190        max_task_invocations_per_workgroup: if max_task_invocations_per_workgroup_wl
1191            <= max_task_invocations_per_workgroup_al
1192        {
1193            max_task_invocations_per_workgroup_wl
1194        } else {
1195            max_task_invocations_per_workgroup_al
1196        },
1197        max_task_invocations_per_dimension: if max_task_invocations_per_dimension_wl
1198            <= max_task_invocations_per_dimension_al
1199        {
1200            max_task_invocations_per_dimension_wl
1201        } else {
1202            max_task_invocations_per_dimension_al
1203        },
1204        max_mesh_invocations_per_workgroup: if max_mesh_invocations_per_workgroup_wl
1205            <= max_mesh_invocations_per_workgroup_al
1206        {
1207            max_mesh_invocations_per_workgroup_wl
1208        } else {
1209            max_mesh_invocations_per_workgroup_al
1210        },
1211        max_mesh_invocations_per_dimension: if max_mesh_invocations_per_dimension_wl
1212            <= max_mesh_invocations_per_dimension_al
1213        {
1214            max_mesh_invocations_per_dimension_wl
1215        } else {
1216            max_mesh_invocations_per_dimension_al
1217        },
1218        max_task_payload_size: if max_task_payload_size_wl <= max_task_payload_size_al {
1219            max_task_payload_size_wl
1220        } else {
1221            max_task_payload_size_al
1222        },
1223        max_mesh_output_vertices: if max_mesh_output_vertices_wl <= max_mesh_output_vertices_al {
1224            max_mesh_output_vertices_wl
1225        } else {
1226            max_mesh_output_vertices_al
1227        },
1228        max_mesh_output_primitives: if max_mesh_output_primitives_wl
1229            <= max_mesh_output_primitives_al
1230        {
1231            max_mesh_output_primitives_wl
1232        } else {
1233            max_mesh_output_primitives_al
1234        },
1235        max_mesh_output_layers: if max_mesh_output_layers_wl <= max_mesh_output_layers_al {
1236            max_mesh_output_layers_wl
1237        } else {
1238            max_mesh_output_layers_al
1239        },
1240        max_mesh_multiview_view_count: if max_mesh_multiview_view_count_wl
1241            <= max_mesh_multiview_view_count_al
1242        {
1243            max_mesh_multiview_view_count_wl
1244        } else {
1245            max_mesh_multiview_view_count_al
1246        },
1247        max_multiview_view_count: if max_multiview_view_count_wl <= max_multiview_view_count_al {
1248            max_multiview_view_count_wl
1249        } else {
1250            max_multiview_view_count_al
1251        },
1252    }
1253}