ratatui_wgpu/backend/
builder.rs

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