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            .ok_or(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(
395                &wgpu::DeviceDescriptor {
396                    required_limits: limits.clone(),
397                    ..Default::default()
398                },
399                None,
400            )
401            .await
402            .map_err(Error::DeviceRequestFailed)?;
403
404        let mut surface_config = surface
405            .get_default_config(
406                &adapter,
407                self.width.get().min(limits.max_texture_dimension_2d),
408                self.height.get().min(limits.max_texture_dimension_2d),
409                Token,
410            )
411            .ok_or(Error::SurfaceConfigurationRequestFailed)?;
412
413        if let Some(mode) = self.present_mode {
414            surface_config.present_mode = mode;
415        }
416
417        surface.configure(&device, &surface_config, Token);
418
419        let (inset_width, inset_height) = match self.viewport {
420            Viewport::Full => (0, 0),
421            Viewport::Shrink { width, height } => (width, height),
422        };
423
424        let drawable_width = surface_config.width - inset_width;
425        let drawable_height = surface_config.height - inset_height;
426
427        info!(
428            "char width x height: {}x{}",
429            self.fonts.min_width_px(),
430            self.fonts.height_px()
431        );
432
433        let text_cache = device.create_texture(&TextureDescriptor {
434            label: Some("Text Atlas"),
435            size: Extent3d {
436                width: CACHE_WIDTH,
437                height: CACHE_HEIGHT,
438                depth_or_array_layers: 1,
439            },
440            mip_level_count: 1,
441            sample_count: 1,
442            dimension: TextureDimension::D2,
443            format: TextureFormat::Rgba8Unorm,
444            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
445            view_formats: &[],
446        });
447
448        let text_cache_view = text_cache.create_view(&TextureViewDescriptor::default());
449
450        let text_mask = device.create_texture(&TextureDescriptor {
451            label: Some("Text Mask"),
452            size: Extent3d {
453                width: CACHE_WIDTH,
454                height: CACHE_HEIGHT,
455                depth_or_array_layers: 1,
456            },
457            mip_level_count: 1,
458            sample_count: 1,
459            dimension: TextureDimension::D2,
460            format: TextureFormat::R8Unorm,
461            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
462            view_formats: &[],
463        });
464
465        let text_mask_view = text_mask.create_view(&TextureViewDescriptor::default());
466
467        let sampler = device.create_sampler(&SamplerDescriptor {
468            address_mode_u: AddressMode::ClampToEdge,
469            address_mode_v: AddressMode::ClampToEdge,
470            address_mode_w: AddressMode::ClampToEdge,
471            mag_filter: FilterMode::Nearest,
472            min_filter: FilterMode::Nearest,
473            mipmap_filter: FilterMode::Nearest,
474            ..Default::default()
475        });
476
477        let text_screen_size_buffer = device.create_buffer(&BufferDescriptor {
478            label: Some("Text Uniforms Buffer"),
479            size: size_of::<[f32; 4]>() as u64,
480            mapped_at_creation: false,
481            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
482        });
483
484        let atlas_size_buffer = device.create_buffer_init(&BufferInitDescriptor {
485            label: Some("Atlas Size buffer"),
486            contents: bytemuck::cast_slice(&[CACHE_WIDTH as f32, CACHE_HEIGHT as f32, 0.0, 0.0]),
487            usage: BufferUsages::UNIFORM,
488        });
489
490        let text_bg_compositor = build_text_bg_compositor(&device, &text_screen_size_buffer);
491
492        let text_fg_compositor = build_text_fg_compositor(
493            &device,
494            &text_screen_size_buffer,
495            &atlas_size_buffer,
496            &text_cache_view,
497            &text_mask_view,
498            &sampler,
499        );
500
501        let wgpu_state = build_wgpu_state(
502            &device,
503            (drawable_width / self.fonts.min_width_px()) * self.fonts.min_width_px(),
504            (drawable_height / self.fonts.height_px()) * self.fonts.height_px(),
505        );
506
507        Ok(WgpuBackend {
508            post_process: P::compile(
509                &device,
510                &wgpu_state.text_dest_view,
511                &surface_config,
512                self.user_data,
513            ),
514            cells: vec![],
515            dirty_rows: vec![],
516            dirty_cells: BitVec::new(),
517            rendered: vec![],
518            sourced: vec![],
519            fast_blinking: BitVec::new(),
520            slow_blinking: BitVec::new(),
521            cursor: (0, 0),
522            surface,
523            _surface: PhantomData,
524            surface_config,
525            device,
526            queue,
527            plan_cache: PlanCache::new(self.fonts.count().max(2)),
528            buffer: UnicodeBuffer::new(),
529            row: String::new(),
530            rowmap: vec![],
531            viewport: self.viewport,
532            cached: Atlas::new(&self.fonts, CACHE_WIDTH, CACHE_HEIGHT),
533            text_cache,
534            text_mask,
535            bg_vertices: vec![],
536            text_indices: vec![],
537            text_vertices: vec![],
538            text_screen_size_buffer,
539            text_bg_compositor,
540            text_fg_compositor,
541            wgpu_state,
542            fonts: self.fonts,
543            reset_fg: self.reset_fg,
544            reset_bg: self.reset_bg,
545            fast_duration: self.fast_blink,
546            last_fast_toggle: Instant::now(),
547            show_fast: true,
548            slow_duration: self.slow_blink,
549            last_slow_toggle: Instant::now(),
550            show_slow: true,
551        })
552    }
553}
554
555fn build_text_bg_compositor(device: &Device, screen_size: &Buffer) -> TextCacheBgPipeline {
556    let shader = device.create_shader_module(include_wgsl!("shaders/composite_bg.wgsl"));
557
558    let vertex_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
559        label: Some("Text Bg Compositor Uniforms Binding Layout"),
560        entries: &[BindGroupLayoutEntry {
561            binding: 0,
562            visibility: ShaderStages::VERTEX,
563            ty: BindingType::Buffer {
564                ty: BufferBindingType::Uniform,
565                has_dynamic_offset: false,
566                min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
567            },
568            count: None,
569        }],
570    });
571
572    let fs_uniforms = device.create_bind_group(&BindGroupDescriptor {
573        label: Some("Text Bg Compositor Uniforms Binding"),
574        layout: &vertex_shader_layout,
575        entries: &[BindGroupEntry {
576            binding: 0,
577            resource: screen_size.as_entire_binding(),
578        }],
579    });
580
581    let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
582        label: Some("Text Bg Compositor Layout"),
583        bind_group_layouts: &[&vertex_shader_layout],
584        push_constant_ranges: &[],
585    });
586
587    let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
588        label: Some("Text Bg Compositor Pipeline"),
589        layout: Some(&pipeline_layout),
590        vertex: VertexState {
591            module: &shader,
592            entry_point: Some("vs_main"),
593            compilation_options: PipelineCompilationOptions::default(),
594            buffers: &[VertexBufferLayout {
595                array_stride: size_of::<TextBgVertexMember>() as u64,
596                step_mode: VertexStepMode::Vertex,
597                attributes: &vertex_attr_array![0 => Float32x2, 1 => Uint32],
598            }],
599        },
600        primitive: PrimitiveState {
601            topology: PrimitiveTopology::TriangleList,
602            ..Default::default()
603        },
604        depth_stencil: None,
605        multisample: MultisampleState::default(),
606        fragment: Some(FragmentState {
607            module: &shader,
608            entry_point: Some("fs_main"),
609            compilation_options: PipelineCompilationOptions::default(),
610            targets: &[Some(ColorTargetState {
611                format: TextureFormat::Rgba8Unorm,
612                blend: None,
613                write_mask: ColorWrites::ALL,
614            })],
615        }),
616        multiview: None,
617        cache: None,
618    });
619
620    TextCacheBgPipeline {
621        pipeline,
622        fs_uniforms,
623    }
624}
625
626fn build_text_fg_compositor(
627    device: &Device,
628    screen_size: &Buffer,
629    atlas_size: &Buffer,
630    cache_view: &TextureView,
631    mask_view: &TextureView,
632    sampler: &Sampler,
633) -> TextCacheFgPipeline {
634    let shader = device.create_shader_module(include_wgsl!("shaders/composite_fg.wgsl"));
635
636    let vertex_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
637        label: Some("Text Compositor Uniforms Binding Layout"),
638        entries: &[BindGroupLayoutEntry {
639            binding: 0,
640            visibility: ShaderStages::VERTEX,
641            ty: BindingType::Buffer {
642                ty: BufferBindingType::Uniform,
643                has_dynamic_offset: false,
644                min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
645            },
646            count: None,
647        }],
648    });
649
650    let fragment_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
651        label: Some("Text Compositor Fragment Binding Layout"),
652        entries: &[
653            BindGroupLayoutEntry {
654                binding: 0,
655                visibility: ShaderStages::FRAGMENT,
656                ty: BindingType::Texture {
657                    sample_type: TextureSampleType::Float { filterable: true },
658                    view_dimension: TextureViewDimension::D2,
659                    multisampled: false,
660                },
661                count: None,
662            },
663            BindGroupLayoutEntry {
664                binding: 1,
665                visibility: ShaderStages::FRAGMENT,
666                ty: BindingType::Texture {
667                    sample_type: TextureSampleType::Float { filterable: true },
668                    view_dimension: TextureViewDimension::D2,
669                    multisampled: false,
670                },
671                count: None,
672            },
673            BindGroupLayoutEntry {
674                binding: 2,
675                visibility: ShaderStages::FRAGMENT,
676                ty: BindingType::Sampler(SamplerBindingType::Filtering),
677                count: None,
678            },
679            BindGroupLayoutEntry {
680                binding: 3,
681                visibility: ShaderStages::FRAGMENT,
682                ty: BindingType::Buffer {
683                    ty: BufferBindingType::Uniform,
684                    has_dynamic_offset: false,
685                    min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
686                },
687                count: None,
688            },
689        ],
690    });
691
692    let fs_uniforms = device.create_bind_group(&BindGroupDescriptor {
693        label: Some("Text Compositor Uniforms Binding"),
694        layout: &vertex_shader_layout,
695        entries: &[BindGroupEntry {
696            binding: 0,
697            resource: screen_size.as_entire_binding(),
698        }],
699    });
700
701    let atlas_bindings = device.create_bind_group(&BindGroupDescriptor {
702        label: Some("Text Compositor Fragment Binding"),
703        layout: &fragment_shader_layout,
704        entries: &[
705            BindGroupEntry {
706                binding: 0,
707                resource: BindingResource::TextureView(cache_view),
708            },
709            BindGroupEntry {
710                binding: 1,
711                resource: BindingResource::TextureView(mask_view),
712            },
713            BindGroupEntry {
714                binding: 2,
715                resource: BindingResource::Sampler(sampler),
716            },
717            BindGroupEntry {
718                binding: 3,
719                resource: atlas_size.as_entire_binding(),
720            },
721        ],
722    });
723
724    let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
725        label: Some("Text Compositor Layout"),
726        bind_group_layouts: &[&vertex_shader_layout, &fragment_shader_layout],
727        push_constant_ranges: &[],
728    });
729
730    let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
731        label: Some("Text Compositor Pipeline"),
732        layout: Some(&pipeline_layout),
733        vertex: VertexState {
734            module: &shader,
735            entry_point: Some("vs_main"),
736            compilation_options: PipelineCompilationOptions::default(),
737            buffers: &[VertexBufferLayout {
738                array_stride: size_of::<TextVertexMember>() as u64,
739                step_mode: VertexStepMode::Vertex,
740                attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32, 3 => Uint32, 4 => Uint32],
741            }],
742        },
743        primitive: PrimitiveState {
744            topology: PrimitiveTopology::TriangleList,
745            ..Default::default()
746        },
747        depth_stencil: None,
748        multisample: MultisampleState::default(),
749        fragment: Some(FragmentState {
750            module: &shader,
751            entry_point: Some("fs_main"),
752            compilation_options: PipelineCompilationOptions::default(),
753            targets: &[Some(ColorTargetState {
754                format: TextureFormat::Rgba8Unorm,
755                blend: Some(BlendState::ALPHA_BLENDING),
756                write_mask: ColorWrites::ALL,
757            })],
758        }),
759        multiview: None,
760        cache: None,
761    });
762
763    TextCacheFgPipeline {
764        pipeline,
765        fs_uniforms,
766        atlas_bindings,
767    }
768}
769
770fn min_limits(adapter: &wgpu::Adapter, limits: Limits) -> Limits {
771    let Limits {
772        max_texture_dimension_1d: max_texture_dimension_1d_wl,
773        max_texture_dimension_2d: max_texture_dimension_2d_wl,
774        max_texture_dimension_3d: max_texture_dimension_3d_wl,
775        max_texture_array_layers: max_texture_array_layers_wl,
776        max_bind_groups: max_bind_groups_wl,
777        max_bindings_per_bind_group: max_bindings_per_bind_group_wl,
778        max_dynamic_uniform_buffers_per_pipeline_layout:
779            max_dynamic_uniform_buffers_per_pipeline_layout_wl,
780        max_dynamic_storage_buffers_per_pipeline_layout:
781            max_dynamic_storage_buffers_per_pipeline_layout_wl,
782        max_sampled_textures_per_shader_stage: max_sampled_textures_per_shader_stage_wl,
783        max_samplers_per_shader_stage: max_samplers_per_shader_stage_wl,
784        max_storage_buffers_per_shader_stage: max_storage_buffers_per_shader_stage_wl,
785        max_storage_textures_per_shader_stage: max_storage_textures_per_shader_stage_wl,
786        max_uniform_buffers_per_shader_stage: max_uniform_buffers_per_shader_stage_wl,
787        max_uniform_buffer_binding_size: max_uniform_buffer_binding_size_wl,
788        max_storage_buffer_binding_size: max_storage_buffer_binding_size_wl,
789        max_vertex_buffers: max_vertex_buffers_wl,
790        max_buffer_size: max_buffer_size_wl,
791        max_vertex_attributes: max_vertex_attributes_wl,
792        max_vertex_buffer_array_stride: max_vertex_buffer_array_stride_wl,
793        min_uniform_buffer_offset_alignment: min_uniform_buffer_offset_alignment_wl,
794        min_storage_buffer_offset_alignment: min_storage_buffer_offset_alignment_wl,
795        max_inter_stage_shader_components: max_inter_stage_shader_components_wl,
796        max_color_attachments: max_color_attachments_wl,
797        max_color_attachment_bytes_per_sample: max_color_attachment_bytes_per_sample_wl,
798        max_compute_workgroup_storage_size: max_compute_workgroup_storage_size_wl,
799        max_compute_invocations_per_workgroup: max_compute_invocations_per_workgroup_wl,
800        max_compute_workgroup_size_x: max_compute_workgroup_size_x_wl,
801        max_compute_workgroup_size_y: max_compute_workgroup_size_y_wl,
802        max_compute_workgroup_size_z: max_compute_workgroup_size_z_wl,
803        max_compute_workgroups_per_dimension: max_compute_workgroups_per_dimension_wl,
804        min_subgroup_size: min_subgroup_size_wl,
805        max_subgroup_size: max_subgroup_size_wl,
806        max_push_constant_size: max_push_constant_size_wl,
807        max_non_sampler_bindings: max_non_sampler_bindings_wl,
808    } = limits;
809    let Limits {
810        max_texture_dimension_1d: max_texture_dimension_1d_al,
811        max_texture_dimension_2d: max_texture_dimension_2d_al,
812        max_texture_dimension_3d: max_texture_dimension_3d_al,
813        max_texture_array_layers: max_texture_array_layers_al,
814        max_bind_groups: max_bind_groups_al,
815        max_bindings_per_bind_group: max_bindings_per_bind_group_al,
816        max_dynamic_uniform_buffers_per_pipeline_layout:
817            max_dynamic_uniform_buffers_per_pipeline_layout_al,
818        max_dynamic_storage_buffers_per_pipeline_layout:
819            max_dynamic_storage_buffers_per_pipeline_layout_al,
820        max_sampled_textures_per_shader_stage: max_sampled_textures_per_shader_stage_al,
821        max_samplers_per_shader_stage: max_samplers_per_shader_stage_al,
822        max_storage_buffers_per_shader_stage: max_storage_buffers_per_shader_stage_al,
823        max_storage_textures_per_shader_stage: max_storage_textures_per_shader_stage_al,
824        max_uniform_buffers_per_shader_stage: max_uniform_buffers_per_shader_stage_al,
825        max_uniform_buffer_binding_size: max_uniform_buffer_binding_size_al,
826        max_storage_buffer_binding_size: max_storage_buffer_binding_size_al,
827        max_vertex_buffers: max_vertex_buffers_al,
828        max_buffer_size: max_buffer_size_al,
829        max_vertex_attributes: max_vertex_attributes_al,
830        max_vertex_buffer_array_stride: max_vertex_buffer_array_stride_al,
831        min_uniform_buffer_offset_alignment: min_uniform_buffer_offset_alignment_al,
832        min_storage_buffer_offset_alignment: min_storage_buffer_offset_alignment_al,
833        max_inter_stage_shader_components: max_inter_stage_shader_components_al,
834        max_color_attachments: max_color_attachments_al,
835        max_color_attachment_bytes_per_sample: max_color_attachment_bytes_per_sample_al,
836        max_compute_workgroup_storage_size: max_compute_workgroup_storage_size_al,
837        max_compute_invocations_per_workgroup: max_compute_invocations_per_workgroup_al,
838        max_compute_workgroup_size_x: max_compute_workgroup_size_x_al,
839        max_compute_workgroup_size_y: max_compute_workgroup_size_y_al,
840        max_compute_workgroup_size_z: max_compute_workgroup_size_z_al,
841        max_compute_workgroups_per_dimension: max_compute_workgroups_per_dimension_al,
842        min_subgroup_size: min_subgroup_size_al,
843        max_subgroup_size: max_subgroup_size_al,
844        max_push_constant_size: max_push_constant_size_al,
845        max_non_sampler_bindings: max_non_sampler_bindings_al,
846    } = adapter.limits();
847
848    Limits {
849        max_texture_dimension_1d: if max_texture_dimension_1d_wl <= max_texture_dimension_1d_al {
850            max_texture_dimension_1d_wl
851        } else {
852            max_texture_dimension_1d_al
853        },
854        max_texture_dimension_2d: if max_texture_dimension_2d_wl <= max_texture_dimension_2d_al {
855            max_texture_dimension_2d_wl
856        } else {
857            max_texture_dimension_2d_al
858        },
859        max_texture_dimension_3d: if max_texture_dimension_3d_wl <= max_texture_dimension_3d_al {
860            max_texture_dimension_3d_wl
861        } else {
862            max_texture_dimension_3d_al
863        },
864        max_texture_array_layers: if max_texture_array_layers_wl <= max_texture_array_layers_al {
865            max_texture_array_layers_wl
866        } else {
867            max_texture_array_layers_al
868        },
869        max_bind_groups: if max_bind_groups_wl <= max_bind_groups_al {
870            max_bind_groups_wl
871        } else {
872            max_bind_groups_al
873        },
874        max_bindings_per_bind_group: if max_bindings_per_bind_group_wl
875            <= max_bindings_per_bind_group_al
876        {
877            max_bindings_per_bind_group_wl
878        } else {
879            max_bindings_per_bind_group_al
880        },
881        max_dynamic_uniform_buffers_per_pipeline_layout:
882            if max_dynamic_uniform_buffers_per_pipeline_layout_wl
883                <= max_dynamic_uniform_buffers_per_pipeline_layout_al
884            {
885                max_dynamic_uniform_buffers_per_pipeline_layout_wl
886            } else {
887                max_dynamic_uniform_buffers_per_pipeline_layout_al
888            },
889        max_dynamic_storage_buffers_per_pipeline_layout:
890            if max_dynamic_storage_buffers_per_pipeline_layout_wl
891                <= max_dynamic_storage_buffers_per_pipeline_layout_al
892            {
893                max_dynamic_storage_buffers_per_pipeline_layout_wl
894            } else {
895                max_dynamic_storage_buffers_per_pipeline_layout_al
896            },
897        max_sampled_textures_per_shader_stage: if max_sampled_textures_per_shader_stage_wl
898            <= max_sampled_textures_per_shader_stage_al
899        {
900            max_sampled_textures_per_shader_stage_wl
901        } else {
902            max_sampled_textures_per_shader_stage_al
903        },
904        max_samplers_per_shader_stage: if max_samplers_per_shader_stage_wl
905            <= max_samplers_per_shader_stage_al
906        {
907            max_samplers_per_shader_stage_wl
908        } else {
909            max_samplers_per_shader_stage_al
910        },
911        max_storage_buffers_per_shader_stage: if max_storage_buffers_per_shader_stage_wl
912            <= max_storage_buffers_per_shader_stage_al
913        {
914            max_storage_buffers_per_shader_stage_wl
915        } else {
916            max_storage_buffers_per_shader_stage_al
917        },
918        max_storage_textures_per_shader_stage: if max_storage_textures_per_shader_stage_wl
919            <= max_storage_textures_per_shader_stage_al
920        {
921            max_storage_textures_per_shader_stage_wl
922        } else {
923            max_storage_textures_per_shader_stage_al
924        },
925        max_uniform_buffers_per_shader_stage: if max_uniform_buffers_per_shader_stage_wl
926            <= max_uniform_buffers_per_shader_stage_al
927        {
928            max_uniform_buffers_per_shader_stage_wl
929        } else {
930            max_uniform_buffers_per_shader_stage_al
931        },
932        max_uniform_buffer_binding_size: if max_uniform_buffer_binding_size_wl
933            <= max_uniform_buffer_binding_size_al
934        {
935            max_uniform_buffer_binding_size_wl
936        } else {
937            max_uniform_buffer_binding_size_al
938        },
939        max_storage_buffer_binding_size: if max_storage_buffer_binding_size_wl
940            <= max_storage_buffer_binding_size_al
941        {
942            max_storage_buffer_binding_size_wl
943        } else {
944            max_storage_buffer_binding_size_al
945        },
946        max_vertex_buffers: if max_vertex_buffers_wl <= max_vertex_buffers_al {
947            max_vertex_buffers_wl
948        } else {
949            max_vertex_buffers_al
950        },
951        max_buffer_size: if max_buffer_size_wl <= max_buffer_size_al {
952            max_buffer_size_wl
953        } else {
954            max_buffer_size_al
955        },
956        max_vertex_attributes: if max_vertex_attributes_wl <= max_vertex_attributes_al {
957            max_vertex_attributes_wl
958        } else {
959            max_vertex_attributes_al
960        },
961        max_vertex_buffer_array_stride: if max_vertex_buffer_array_stride_wl
962            <= max_vertex_buffer_array_stride_al
963        {
964            max_vertex_buffer_array_stride_wl
965        } else {
966            max_vertex_buffer_array_stride_al
967        },
968        min_uniform_buffer_offset_alignment: if min_uniform_buffer_offset_alignment_wl
969            <= min_uniform_buffer_offset_alignment_al
970        {
971            min_uniform_buffer_offset_alignment_wl
972        } else {
973            min_uniform_buffer_offset_alignment_al
974        },
975        min_storage_buffer_offset_alignment: if min_storage_buffer_offset_alignment_wl
976            <= min_storage_buffer_offset_alignment_al
977        {
978            min_storage_buffer_offset_alignment_wl
979        } else {
980            min_storage_buffer_offset_alignment_al
981        },
982        max_inter_stage_shader_components: if max_inter_stage_shader_components_wl
983            <= max_inter_stage_shader_components_al
984        {
985            max_inter_stage_shader_components_wl
986        } else {
987            max_inter_stage_shader_components_al
988        },
989        max_color_attachments: if max_color_attachments_wl <= max_color_attachments_al {
990            max_color_attachments_wl
991        } else {
992            max_color_attachments_al
993        },
994        max_color_attachment_bytes_per_sample: if max_color_attachment_bytes_per_sample_wl
995            <= max_color_attachment_bytes_per_sample_al
996        {
997            max_color_attachment_bytes_per_sample_wl
998        } else {
999            max_color_attachment_bytes_per_sample_al
1000        },
1001        max_compute_workgroup_storage_size: if max_compute_workgroup_storage_size_wl
1002            <= max_compute_workgroup_storage_size_al
1003        {
1004            max_compute_workgroup_storage_size_wl
1005        } else {
1006            max_compute_workgroup_storage_size_al
1007        },
1008        max_compute_invocations_per_workgroup: if max_compute_invocations_per_workgroup_wl
1009            <= max_compute_invocations_per_workgroup_al
1010        {
1011            max_compute_invocations_per_workgroup_wl
1012        } else {
1013            max_compute_invocations_per_workgroup_al
1014        },
1015        max_compute_workgroup_size_x: if max_compute_workgroup_size_x_wl
1016            <= max_compute_workgroup_size_x_al
1017        {
1018            max_compute_workgroup_size_x_wl
1019        } else {
1020            max_compute_workgroup_size_x_al
1021        },
1022        max_compute_workgroup_size_y: if max_compute_workgroup_size_y_wl
1023            <= max_compute_workgroup_size_y_al
1024        {
1025            max_compute_workgroup_size_y_wl
1026        } else {
1027            max_compute_workgroup_size_y_al
1028        },
1029        max_compute_workgroup_size_z: if max_compute_workgroup_size_z_wl
1030            <= max_compute_workgroup_size_z_al
1031        {
1032            max_compute_workgroup_size_z_wl
1033        } else {
1034            max_compute_workgroup_size_z_al
1035        },
1036        max_compute_workgroups_per_dimension: if max_compute_workgroups_per_dimension_wl
1037            <= max_compute_workgroups_per_dimension_al
1038        {
1039            max_compute_workgroups_per_dimension_wl
1040        } else {
1041            max_compute_workgroups_per_dimension_al
1042        },
1043        min_subgroup_size: if min_subgroup_size_wl <= min_subgroup_size_al {
1044            min_subgroup_size_wl
1045        } else {
1046            min_subgroup_size_al
1047        },
1048        max_subgroup_size: if max_subgroup_size_wl <= max_subgroup_size_al {
1049            max_subgroup_size_wl
1050        } else {
1051            max_subgroup_size_al
1052        },
1053        max_push_constant_size: if max_push_constant_size_wl <= max_push_constant_size_al {
1054            max_push_constant_size_wl
1055        } else {
1056            max_push_constant_size_al
1057        },
1058        max_non_sampler_bindings: if max_non_sampler_bindings_wl <= max_non_sampler_bindings_al {
1059            max_non_sampler_bindings_wl
1060        } else {
1061            max_non_sampler_bindings_al
1062        },
1063    }
1064}