Skip to main content

pfx_live/flat/
pass.rs

1use super::icons::{FORMAT as ICON_FORMAT, GpuIcons, Icons};
2use super::{
3    Draw, FX, Fill, Fit, FlatScene, Glyphs, INSTANCE_ATTRIBUTES, Instance, KIND_SHADOW, LIT, Order,
4    Shape, Step, pack_shadow, pack_shape, shader,
5};
6use crate::text::{GpuAtlas, PackedGlyph, TextSpace, pack_glyphs, pack_quads, screen_bounds};
7use bytemuck::{Pod, Zeroable};
8use pfx_gpu::{GpuProfiler, wgpu};
9use std::ops::Range;
10use wgpu::util::DeviceExt;
11
12pub const COLOUR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
13pub const ID_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint;
14const TEXT_SLOT: u64 = 256;
15const TEXT_CACHE: usize = 64;
16const BASE_PIPELINES: usize = 6;
17const ABSORBED_AREA: f32 = 128.0 * 128.0;
18const GRADIENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
19const GRADIENT_TEXELS: u32 = 12;
20
21pub struct FlatIcons {
22    pub icons: Icons,
23    gpu: GpuIcons,
24}
25
26impl FlatIcons {
27    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, icons: Icons) -> Self {
28        let gpu = icons.upload(device, queue);
29        Self { icons, gpu }
30    }
31
32    pub fn view(&self) -> &wgpu::TextureView {
33        &self.gpu.view
34    }
35}
36
37pub struct Environment {
38    _texture: wgpu::Texture,
39    view: wgpu::TextureView,
40    mips: u32,
41    pub intensity: f32,
42}
43
44impl Environment {
45    pub fn new(
46        device: &wgpu::Device,
47        queue: &wgpu::Queue,
48        width: u32,
49        height: u32,
50        linear: &[[f32; 4]],
51        intensity: f32,
52    ) -> Result<Self, String> {
53        if width == 0 || height == 0 || linear.len() != (width * height) as usize {
54            return Err("the environment needs width × height linear RGBA texels".into());
55        }
56        if width > 4096 || height > 4096 {
57            return Err("the environment is larger than 4096 texels a side".into());
58        }
59        if !intensity.is_finite() || intensity < 0.0 {
60            return Err("the environment intensity must be finite and nonnegative".into());
61        }
62        let mut levels = vec![(width, height, linear.to_vec())];
63        while let Some((w, h, texels)) = levels.last().filter(|(w, h, _)| *w > 1 || *h > 1) {
64            let (nw, nh) = ((w / 2).max(1), (h / 2).max(1));
65            let mut next = vec![[0.0f32; 4]; (nw * nh) as usize];
66            for y in 0..nh {
67                for x in 0..nw {
68                    let mut sum = [0.0f32; 4];
69                    for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
70                        let sx = (x * 2 + dx).min(w - 1);
71                        let sy = (y * 2 + dy).min(h - 1);
72                        let texel = texels[(sy * w + sx) as usize];
73                        for channel in 0..4 {
74                            sum[channel] += texel[channel] * 0.25;
75                        }
76                    }
77                    next[(y * nw + x) as usize] = sum;
78                }
79            }
80            levels.push((nw, nh, next));
81        }
82        let mips = levels.len() as u32;
83        let texture = device.create_texture(&wgpu::TextureDescriptor {
84            label: Some("flat environment"),
85            size: wgpu::Extent3d {
86                width,
87                height,
88                depth_or_array_layers: 1,
89            },
90            mip_level_count: mips,
91            sample_count: 1,
92            dimension: wgpu::TextureDimension::D2,
93            format: wgpu::TextureFormat::Rgba16Float,
94            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
95            view_formats: &[],
96        });
97        for (level, (w, h, texels)) in levels.iter().enumerate() {
98            let halves = texels
99                .iter()
100                .flatten()
101                .map(|value| half::f16::from_f32(*value).to_bits())
102                .collect::<Vec<_>>();
103            queue.write_texture(
104                wgpu::TexelCopyTextureInfo {
105                    texture: &texture,
106                    mip_level: level as u32,
107                    origin: wgpu::Origin3d::ZERO,
108                    aspect: wgpu::TextureAspect::All,
109                },
110                bytemuck::cast_slice(&halves),
111                wgpu::TexelCopyBufferLayout {
112                    offset: 0,
113                    bytes_per_row: Some(w * 8),
114                    rows_per_image: Some(*h),
115                },
116                wgpu::Extent3d {
117                    width: *w,
118                    height: *h,
119                    depth_or_array_layers: 1,
120                },
121            );
122        }
123        let view = texture.create_view(&Default::default());
124        Ok(Self {
125            _texture: texture,
126            view,
127            mips,
128            intensity,
129        })
130    }
131
132    pub fn mips(&self) -> u32 {
133        self.mips
134    }
135}
136
137pub struct FlatTarget<'a> {
138    pub colour: &'a wgpu::TextureView,
139    pub ids: Option<&'a wgpu::TextureView>,
140    pub size: [u32; 2],
141    pub clear: Option<[f32; 4]>,
142    pub clear_ids: bool,
143}
144
145#[repr(C)]
146#[derive(Clone, Copy, Pod, Zeroable)]
147struct View {
148    size: [f32; 4],
149    light: [f32; 4],
150    environment: [f32; 4],
151}
152
153#[repr(C)]
154#[derive(Clone, Copy, Pod, Zeroable)]
155struct LayerQuad {
156    rect: [f32; 4],
157    opacity: [f32; 4],
158}
159
160#[repr(C)]
161#[derive(Clone, Copy, Pod, Zeroable)]
162struct TextUniform {
163    matrix: [[f32; 4]; 4],
164    view_projection: [[f32; 4]; 4],
165    model: [[f32; 4]; 4],
166    rest_normal: [f32; 4],
167    atlas_size: [f32; 2],
168    mode: u32,
169    deformer: u32,
170    cells: [u32; 4],
171}
172
173#[derive(Clone, Debug, PartialEq)]
174enum Op {
175    Instances {
176        range: Range<u32>,
177        lit: bool,
178    },
179    Text {
180        glyphs: Range<u32>,
181        slot: u32,
182        atlas: usize,
183        id: u32,
184    },
185    Layer {
186        quad: u32,
187    },
188    EndLayer {
189        quad: u32,
190    },
191}
192
193struct Pool<T: Pod> {
194    label: &'static str,
195    usage: wgpu::BufferUsages,
196    buffer: Option<wgpu::Buffer>,
197    capacity: u64,
198    items: Vec<T>,
199}
200
201impl<T: Pod> Pool<T> {
202    fn new(label: &'static str, usage: wgpu::BufferUsages) -> Self {
203        Self {
204            label,
205            usage,
206            buffer: None,
207            capacity: 0,
208            items: Vec::new(),
209        }
210    }
211
212    fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) -> bool {
213        let bytes = (self.items.len() * std::mem::size_of::<T>()) as u64;
214        if bytes == 0 {
215            return false;
216        }
217        let grown = self.buffer.is_none() || bytes > self.capacity;
218        if grown {
219            self.capacity = bytes.next_power_of_two().max(4096);
220            self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
221                label: Some(self.label),
222                size: self.capacity,
223                usage: self.usage | wgpu::BufferUsages::COPY_DST,
224                mapped_at_creation: false,
225            }));
226        }
227        if let Some(buffer) = &self.buffer {
228            queue.write_buffer(buffer, 0, bytemuck::cast_slice(&self.items));
229        }
230        grown
231    }
232}
233
234struct Finish {
235    format: wgpu::TextureFormat,
236    linear: bool,
237    blend: bool,
238    pipeline: wgpu::RenderPipeline,
239}
240
241pub struct FlatPass {
242    view_layout: wgpu::BindGroupLayout,
243    layer_layout: wgpu::BindGroupLayout,
244    text_layout: wgpu::BindGroupLayout,
245    finish_layout: wgpu::BindGroupLayout,
246    colour: wgpu::RenderPipeline,
247    ids: wgpu::RenderPipeline,
248    lit: Option<(wgpu::RenderPipeline, wgpu::RenderPipeline)>,
249    module: wgpu::ShaderModule,
250    main_layout: wgpu::PipelineLayout,
251    clear: wgpu::RenderPipeline,
252    composite: wgpu::RenderPipeline,
253    text: wgpu::RenderPipeline,
254    text_ids: wgpu::RenderPipeline,
255    finish_module: wgpu::ShaderModule,
256    finish_pipeline_layout: wgpu::PipelineLayout,
257    finishes: Vec<Finish>,
258    uniform: wgpu::Buffer,
259    icon_sampler: wgpu::Sampler,
260    environment_sampler: wgpu::Sampler,
261    text_sampler: wgpu::Sampler,
262    empty_icons: wgpu::TextureView,
263    empty_sprites: wgpu::TextureView,
264    sprite_sampler: wgpu::Sampler,
265    empty_environment: wgpu::TextureView,
266    empty_gradients: wgpu::TextureView,
267    gradients: Option<(wgpu::Texture, wgpu::TextureView, u32)>,
268    gradient_rows: Vec<[[f32; 4]; GRADIENT_TEXELS as usize]>,
269    uploaded_rows: Vec<[[f32; 4]; GRADIENT_TEXELS as usize]>,
270    bind: Option<(
271        wgpu::TextureView,
272        wgpu::TextureView,
273        wgpu::TextureView,
274        wgpu::TextureView,
275        wgpu::BindGroup,
276    )>,
277    text_binds: Vec<(wgpu::TextureView, wgpu::BindGroup)>,
278    instances: Pool<Instance>,
279    quads: Pool<LayerQuad>,
280    glyphs: Pool<PackedGlyph>,
281    text_uniforms: Pool<[u8; TEXT_SLOT as usize]>,
282    scratch: Vec<PackedGlyph>,
283    text_items: Vec<(usize, usize, Range<u32>, u32)>,
284    order: Order,
285    ops: Vec<Op>,
286    layer: Option<(wgpu::Texture, wgpu::TextureView, wgpu::BindGroup, [u32; 2])>,
287    colour_target: Option<(wgpu::Texture, wgpu::TextureView, [u32; 2])>,
288    size: [u32; 2],
289    capacity: Option<[u32; 2]>,
290    clip: Option<[u32; 4]>,
291    passes: u32,
292    made: usize,
293}
294
295fn instance_attributes() -> [wgpu::VertexAttribute; INSTANCE_ATTRIBUTES as usize] {
296    std::array::from_fn(|index| wgpu::VertexAttribute {
297        format: if index == 9 {
298            wgpu::VertexFormat::Uint32x4
299        } else {
300            wgpu::VertexFormat::Float32x4
301        },
302        offset: (index * 16) as u64,
303        shader_location: index as u32,
304    })
305}
306
307pub fn view_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
308    let texture = |binding| wgpu::BindGroupLayoutEntry {
309        binding,
310        visibility: wgpu::ShaderStages::FRAGMENT,
311        ty: wgpu::BindingType::Texture {
312            sample_type: wgpu::TextureSampleType::Float { filterable: true },
313            view_dimension: wgpu::TextureViewDimension::D2,
314            multisampled: false,
315        },
316        count: None,
317    };
318    let sampler = |binding| wgpu::BindGroupLayoutEntry {
319        binding,
320        visibility: wgpu::ShaderStages::FRAGMENT,
321        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
322        count: None,
323    };
324    vec![
325        wgpu::BindGroupLayoutEntry {
326            binding: 0,
327            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
328            ty: wgpu::BindingType::Buffer {
329                ty: wgpu::BufferBindingType::Uniform,
330                has_dynamic_offset: false,
331                min_binding_size: None,
332            },
333            count: None,
334        },
335        texture(1),
336        sampler(2),
337        texture(3),
338        sampler(4),
339        texture(6),
340        sampler(7),
341        wgpu::BindGroupLayoutEntry {
342            binding: 5,
343            visibility: wgpu::ShaderStages::FRAGMENT,
344            ty: wgpu::BindingType::Texture {
345                sample_type: wgpu::TextureSampleType::Float { filterable: false },
346                view_dimension: wgpu::TextureViewDimension::D2,
347                multisampled: false,
348            },
349            count: None,
350        },
351    ]
352}
353
354pub fn layer_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
355    vec![wgpu::BindGroupLayoutEntry {
356        binding: 0,
357        visibility: wgpu::ShaderStages::FRAGMENT,
358        ty: wgpu::BindingType::Texture {
359            sample_type: wgpu::TextureSampleType::Float { filterable: false },
360            view_dimension: wgpu::TextureViewDimension::D2,
361            multisampled: false,
362        },
363        count: None,
364    }]
365}
366
367pub fn text_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
368    let mut entries = crate::text::text_entries();
369    entries[0].ty = wgpu::BindingType::Buffer {
370        ty: wgpu::BufferBindingType::Uniform,
371        has_dynamic_offset: true,
372        min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<TextUniform>() as u64),
373    };
374    entries
375}
376
377pub fn finish_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
378    layer_entries()
379}
380
381fn empty(
382    device: &wgpu::Device,
383    queue: &wgpu::Queue,
384    format: wgpu::TextureFormat,
385    bytes: &[u8],
386) -> wgpu::TextureView {
387    device
388        .create_texture_with_data(
389            queue,
390            &wgpu::TextureDescriptor {
391                label: Some("flat empty input"),
392                size: wgpu::Extent3d {
393                    width: 1,
394                    height: 1,
395                    depth_or_array_layers: 1,
396                },
397                mip_level_count: 1,
398                sample_count: 1,
399                dimension: wgpu::TextureDimension::D2,
400                format,
401                usage: wgpu::TextureUsages::TEXTURE_BINDING,
402                view_formats: &[],
403            },
404            wgpu::util::TextureDataOrder::LayerMajor,
405            bytes,
406        )
407        .create_view(&Default::default())
408}
409
410fn colour_state(format: wgpu::TextureFormat, blend: bool) -> wgpu::ColorTargetState {
411    wgpu::ColorTargetState {
412        format,
413        blend: blend.then_some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
414        write_mask: wgpu::ColorWrites::ALL,
415    }
416}
417
418#[allow(clippy::too_many_arguments)]
419fn pipeline(
420    device: &wgpu::Device,
421    label: &str,
422    layout: &wgpu::PipelineLayout,
423    module: &wgpu::ShaderModule,
424    entries: (&str, &str),
425    buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
426    target: wgpu::ColorTargetState,
427    topology: wgpu::PrimitiveTopology,
428) -> wgpu::RenderPipeline {
429    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
430        label: Some(label),
431        layout: Some(layout),
432        vertex: wgpu::VertexState {
433            module,
434            entry_point: Some(entries.0),
435            buffers,
436            compilation_options: Default::default(),
437        },
438        primitive: wgpu::PrimitiveState {
439            topology,
440            cull_mode: None,
441            ..Default::default()
442        },
443        depth_stencil: None,
444        multisample: Default::default(),
445        fragment: Some(wgpu::FragmentState {
446            module,
447            entry_point: Some(entries.1),
448            targets: &[Some(target)],
449            compilation_options: Default::default(),
450        }),
451        multiview_mask: None,
452        cache: None,
453    })
454}
455
456fn rect_union(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
457    [
458        a[0].min(b[0]),
459        a[1].min(b[1]),
460        a[2].max(b[2]),
461        a[3].max(b[3]),
462    ]
463}
464
465impl FlatPass {
466    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
467        Self::with_source(device, queue, shader::source())
468    }
469
470    pub(crate) fn with_source(device: &wgpu::Device, queue: &wgpu::Queue, source: String) -> Self {
471        let group = |label: &str, entries: &[wgpu::BindGroupLayoutEntry]| {
472            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
473                label: Some(label),
474                entries,
475            })
476        };
477        let view_layout = group("flat view", &view_entries());
478        let layer_layout = group("flat layer", &layer_entries());
479        let text_layout = group("flat text", &text_entries());
480        let finish_layout = group("flat finish", &finish_entries());
481        let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
482            label: Some("flat pass"),
483            source: wgpu::ShaderSource::Wgsl(source.into()),
484        });
485        let text_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
486            label: Some("flat text"),
487            source: wgpu::ShaderSource::Wgsl(crate::text::shader_source().into()),
488        });
489        let finish_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
490            label: Some("flat finish"),
491            source: wgpu::ShaderSource::Wgsl(shader::FINISH_WGSL.into()),
492        });
493        let main_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
494            label: Some("flat"),
495            bind_group_layouts: &[Some(&view_layout)],
496            immediate_size: 0,
497        });
498        let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
499            label: Some("flat layer composite"),
500            bind_group_layouts: &[Some(&view_layout), Some(&layer_layout)],
501            immediate_size: 0,
502        });
503        let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
504            label: Some("flat text"),
505            bind_group_layouts: &[Some(&text_layout)],
506            immediate_size: 0,
507        });
508        let finish_pipeline_layout =
509            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
510                label: Some("flat finish"),
511                bind_group_layouts: &[Some(&finish_layout)],
512                immediate_size: 0,
513            });
514        let attributes = instance_attributes();
515        let instance_buffers = [Some(wgpu::VertexBufferLayout {
516            array_stride: std::mem::size_of::<Instance>() as u64,
517            step_mode: wgpu::VertexStepMode::Instance,
518            attributes: &attributes,
519        })];
520        let quad_attributes = [
521            wgpu::VertexAttribute {
522                format: wgpu::VertexFormat::Float32x4,
523                offset: 0,
524                shader_location: 0,
525            },
526            wgpu::VertexAttribute {
527                format: wgpu::VertexFormat::Float32x4,
528                offset: 16,
529                shader_location: 1,
530            },
531        ];
532        let quad_buffers = [Some(wgpu::VertexBufferLayout {
533            array_stride: std::mem::size_of::<LayerQuad>() as u64,
534            step_mode: wgpu::VertexStepMode::Instance,
535            attributes: &quad_attributes,
536        })];
537        let glyph_attributes: [wgpu::VertexAttribute; 6] =
538            std::array::from_fn(|index| wgpu::VertexAttribute {
539                format: wgpu::VertexFormat::Float32x4,
540                offset: (index * 16) as u64,
541                shader_location: index as u32,
542            });
543        let glyph_buffers = [Some(wgpu::VertexBufferLayout {
544            array_stride: std::mem::size_of::<PackedGlyph>() as u64,
545            step_mode: wgpu::VertexStepMode::Instance,
546            attributes: &glyph_attributes,
547        })];
548        let strip = wgpu::PrimitiveTopology::TriangleStrip;
549        let list = wgpu::PrimitiveTopology::TriangleList;
550        let colour = pipeline(
551            device,
552            "flat colour",
553            &main_layout,
554            &module,
555            ("flat_vertex", "flat_colour"),
556            &instance_buffers,
557            colour_state(COLOUR_FORMAT, true),
558            strip,
559        );
560        let ids = pipeline(
561            device,
562            "flat ids",
563            &main_layout,
564            &module,
565            ("flat_id_vertex", "flat_id"),
566            &instance_buffers,
567            wgpu::ColorTargetState {
568                format: ID_FORMAT,
569                blend: None,
570                write_mask: wgpu::ColorWrites::ALL,
571            },
572            strip,
573        );
574        let clear = pipeline(
575            device,
576            "flat layer clear",
577            &main_layout,
578            &module,
579            ("flat_layer_vertex", "flat_layer_clear"),
580            &quad_buffers,
581            colour_state(COLOUR_FORMAT, false),
582            strip,
583        );
584        let composite = pipeline(
585            device,
586            "flat layer composite",
587            &composite_layout,
588            &module,
589            ("flat_layer_vertex", "flat_layer_composite"),
590            &quad_buffers,
591            colour_state(COLOUR_FORMAT, true),
592            strip,
593        );
594        let text = pipeline(
595            device,
596            "flat text",
597            &text_pipeline_layout,
598            &text_module,
599            ("vs_main", "fs_main"),
600            &glyph_buffers,
601            colour_state(COLOUR_FORMAT, true),
602            list,
603        );
604        let text_ids = pipeline(
605            device,
606            "flat text ids",
607            &text_pipeline_layout,
608            &text_module,
609            ("vs_main", "fs_id"),
610            &glyph_buffers,
611            wgpu::ColorTargetState {
612                format: ID_FORMAT,
613                blend: None,
614                write_mask: wgpu::ColorWrites::ALL,
615            },
616            list,
617        );
618        let uniform = device.create_buffer(&wgpu::BufferDescriptor {
619            label: Some("flat view"),
620            size: std::mem::size_of::<View>() as u64,
621            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
622            mapped_at_creation: false,
623        });
624        let linear = |label, mipmaps: bool, repeat: bool| {
625            device.create_sampler(&wgpu::SamplerDescriptor {
626                label: Some(label),
627                address_mode_u: if repeat {
628                    wgpu::AddressMode::Repeat
629                } else {
630                    wgpu::AddressMode::ClampToEdge
631                },
632                address_mode_v: wgpu::AddressMode::ClampToEdge,
633                mag_filter: wgpu::FilterMode::Linear,
634                min_filter: wgpu::FilterMode::Linear,
635                mipmap_filter: if mipmaps {
636                    wgpu::MipmapFilterMode::Linear
637                } else {
638                    wgpu::MipmapFilterMode::Nearest
639                },
640                ..Default::default()
641            })
642        };
643        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
644            label: Some("flat text"),
645            mag_filter: wgpu::FilterMode::Linear,
646            min_filter: wgpu::FilterMode::Linear,
647            mipmap_filter: wgpu::MipmapFilterMode::Linear,
648            lod_max_clamp: 3.0,
649            ..Default::default()
650        });
651        let far = half::f16::from_f32(1.0e4).to_bits().to_le_bytes();
652        Self {
653            view_layout,
654            layer_layout,
655            text_layout,
656            finish_layout,
657            colour,
658            ids,
659            lit: None,
660            module,
661            main_layout,
662            clear,
663            composite,
664            text,
665            text_ids,
666            finish_module,
667            finish_pipeline_layout,
668            finishes: Vec::new(),
669            uniform,
670            icon_sampler: linear("flat icons", false, false),
671            environment_sampler: linear("flat environment", true, true),
672            text_sampler,
673            empty_icons: empty(device, queue, ICON_FORMAT, &far),
674            empty_sprites: empty(device, queue, super::sprites::SPRITE_FORMAT, &[255; 4]),
675            sprite_sampler: super::sprites::sampler(device, super::SpriteFilter::Linear),
676            empty_environment: empty(device, queue, wgpu::TextureFormat::Rgba16Float, &[0; 8]),
677            empty_gradients: empty(device, queue, GRADIENT_FORMAT, &[0; 16]),
678            gradients: None,
679            gradient_rows: Vec::new(),
680            uploaded_rows: Vec::new(),
681            bind: None,
682            text_binds: Vec::new(),
683            instances: Pool::new("flat instances", wgpu::BufferUsages::VERTEX),
684            quads: Pool::new("flat layer quads", wgpu::BufferUsages::VERTEX),
685            glyphs: Pool::new("flat glyphs", wgpu::BufferUsages::VERTEX),
686            text_uniforms: Pool::new("flat text uniforms", wgpu::BufferUsages::UNIFORM),
687            scratch: Vec::new(),
688            text_items: Vec::new(),
689            order: Order::default(),
690            ops: Vec::new(),
691            layer: None,
692            colour_target: None,
693            size: [0, 0],
694            capacity: None,
695            clip: None,
696            passes: 0,
697            made: BASE_PIPELINES,
698        }
699    }
700
701    pub fn target_textures(&self) -> Vec<wgpu::Texture> {
702        self.layer
703            .iter()
704            .map(|layer| layer.0.clone())
705            .chain(self.colour_target.iter().map(|target| target.0.clone()))
706            .collect()
707    }
708
709    pub fn set_clip(&mut self, clip: Option<[u32; 4]>) {
710        self.clip = clip;
711    }
712
713    pub fn set_capacity(&mut self, capacity: Option<[u32; 2]>) {
714        self.capacity = capacity;
715    }
716
717    fn viewport(&self) -> Option<[u32; 2]> {
718        self.capacity.map(|_| self.size)
719    }
720
721    pub fn pipelines_made(&self) -> usize {
722        self.made
723    }
724
725    pub fn warm(
726        &mut self,
727        device: &wgpu::Device,
728        size: [u32; 2],
729        finishes: &[(wgpu::TextureFormat, bool, bool)],
730        colour: bool,
731    ) {
732        self.ensure_lit(device);
733        self.ensure_layer(device, size);
734        if colour {
735            self.colour_target(device, size);
736        }
737        for &(format, linear, blend) in finishes {
738            self.finish_index(device, format, linear, blend);
739        }
740    }
741
742    pub fn instance_count(&self) -> usize {
743        self.instances.items.len()
744    }
745
746    pub fn instances(&self) -> &[Instance] {
747        &self.instances.items
748    }
749
750    pub fn layer_count(&self) -> usize {
751        self.quads.items.len()
752    }
753
754    fn push_instance(&mut self, instance: Instance) {
755        let start = self.instances.items.len() as u32;
756        let lit = instance.info[0] & (LIT | FX) != 0;
757        let [x0, y0, x1, y1] = instance.pixel_bounds();
758        let small = (x1 - x0) * (y1 - y0) <= ABSORBED_AREA;
759        self.instances.items.push(instance);
760        match self.ops.last_mut() {
761            Some(Op::Instances {
762                range,
763                lit: run_lit,
764            }) if range.end == start && (*run_lit == lit || (*run_lit && small)) => {
765                range.end = start + 1
766            }
767            _ => self.ops.push(Op::Instances {
768                range: start..start + 1,
769                lit,
770            }),
771        }
772    }
773
774    fn ensure_lit(&mut self, device: &wgpu::Device) {
775        if self.lit.is_some() {
776            return;
777        }
778        let attributes = instance_attributes();
779        let buffers = [Some(wgpu::VertexBufferLayout {
780            array_stride: std::mem::size_of::<Instance>() as u64,
781            step_mode: wgpu::VertexStepMode::Instance,
782            attributes: &attributes,
783        })];
784        let colour = pipeline(
785            device,
786            "flat colour lit",
787            &self.main_layout,
788            &self.module,
789            ("flat_vertex", "flat_colour_lit"),
790            &buffers,
791            colour_state(COLOUR_FORMAT, true),
792            wgpu::PrimitiveTopology::TriangleStrip,
793        );
794        let ids = pipeline(
795            device,
796            "flat ids lit",
797            &self.main_layout,
798            &self.module,
799            ("flat_id_vertex_rich", "flat_id_lit"),
800            &buffers,
801            wgpu::ColorTargetState {
802                format: ID_FORMAT,
803                blend: None,
804                write_mask: wgpu::ColorWrites::ALL,
805            },
806            wgpu::PrimitiveTopology::TriangleStrip,
807        );
808        self.lit = Some((colour, ids));
809        self.made += 2;
810    }
811
812    fn text_bind(&mut self, device: &wgpu::Device, atlas: &GpuAtlas) -> Result<usize, String> {
813        if let Some(index) = self
814            .text_binds
815            .iter()
816            .position(|(view, _)| view == atlas.view())
817        {
818            return Ok(index);
819        }
820        let buffer = self
821            .text_uniforms
822            .buffer
823            .as_ref()
824            .ok_or("flat text uniforms are missing")?;
825        let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
826            label: Some("flat text"),
827            layout: &self.text_layout,
828            entries: &[
829                wgpu::BindGroupEntry {
830                    binding: 0,
831                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
832                        buffer,
833                        offset: 0,
834                        size: wgpu::BufferSize::new(std::mem::size_of::<TextUniform>() as u64),
835                    }),
836                },
837                wgpu::BindGroupEntry {
838                    binding: 1,
839                    resource: wgpu::BindingResource::TextureView(atlas.view()),
840                },
841                wgpu::BindGroupEntry {
842                    binding: 2,
843                    resource: wgpu::BindingResource::Sampler(&self.text_sampler),
844                },
845            ],
846        });
847        self.text_binds.push((atlas.view().clone(), group));
848        Ok(self.text_binds.len() - 1)
849    }
850
851    pub fn prepare(
852        &mut self,
853        device: &wgpu::Device,
854        queue: &wgpu::Queue,
855        scene: &FlatScene<'_>,
856        size: [u32; 2],
857    ) -> Result<Fit, String> {
858        let fit = Fit::new(scene.layout, size)?;
859        self.size = size;
860        self.instances.items.clear();
861        self.quads.items.clear();
862        self.glyphs.items.clear();
863        self.text_uniforms.items.clear();
864        self.ops.clear();
865        self.gradient_rows.clear();
866        self.order.build(scene.draws, scene.groups.len())?;
867        for group in scene.groups {
868            if !(0.0..=1.0).contains(&group.opacity) {
869                return Err("a group's opacity must be in 0..=1".into());
870            }
871        }
872        let icons = scene.icons.map(|icons| &icons.icons);
873        let clip = fit.clip_from_layout();
874        let mut open_quad: Option<(u32, [f32; 4])> = None;
875        let mut text_items = std::mem::take(&mut self.text_items);
876        text_items.clear();
877        if self.text_binds.len() >= TEXT_CACHE {
878            self.text_binds.clear();
879        }
880        let steps = std::mem::take(&mut self.order.steps);
881        for step in &steps {
882            match *step {
883                Step::Layer(group) => {
884                    let quad = self.quads.items.len() as u32;
885                    self.quads.items.push(LayerQuad {
886                        rect: [f32::MAX, f32::MAX, f32::MIN, f32::MIN],
887                        opacity: [scene.groups[usize::from(group)].opacity, 0.0, 0.0, 0.0],
888                    });
889                    self.ops.push(Op::Layer { quad });
890                    open_quad = Some((quad, [f32::MAX, f32::MAX, f32::MIN, f32::MIN]));
891                }
892                Step::EndLayer(_) => {
893                    if let Some((quad, rect)) = open_quad.take() {
894                        let clamped = [
895                            rect[0].floor().max(0.0),
896                            rect[1].floor().max(0.0),
897                            rect[2].ceil().min(size[0] as f32),
898                            rect[3].ceil().min(size[1] as f32),
899                        ];
900                        self.quads.items[quad as usize].rect = clamped;
901                        self.ops.push(Op::EndLayer { quad });
902                    }
903                }
904                Step::Draw(index) => {
905                    let draw = &scene.draws[index];
906                    let bounds =
907                        self.push_draw(draw, scene, &fit, icons, &clip, &mut text_items)?;
908                    if let (Some((_, rect)), Some(bounds)) = (open_quad.as_mut(), bounds) {
909                        *rect = rect_union(*rect, bounds);
910                    }
911                }
912            }
913        }
914        self.order.steps = steps;
915        self.instances.upload(device, queue);
916        self.quads.upload(device, queue);
917        self.glyphs.upload(device, queue);
918        if self.text_uniforms.upload(device, queue) {
919            self.text_binds.clear();
920        }
921        for (op_index, text_index, glyphs, slot) in text_items.drain(..) {
922            let atlas = self.text_bind(device, scene.text[text_index].atlas)?;
923            if let Op::Text {
924                atlas: bound,
925                glyphs: range,
926                slot: at,
927                ..
928            } = &mut self.ops[op_index]
929            {
930                *bound = atlas;
931                *range = glyphs;
932                *at = slot;
933            }
934        }
935        self.text_items = text_items;
936        let light = scene.light;
937        let key = super::normalize3(light.key);
938        let (mips, intensity, present) = scene.environment.map_or((1.0, 0.0, 0.0), |environment| {
939            (environment.mips as f32, environment.intensity, 1.0)
940        });
941        queue.write_buffer(
942            &self.uniform,
943            0,
944            bytemuck::bytes_of(&View {
945                size: [
946                    size[0] as f32,
947                    size[1] as f32,
948                    1.0 / size[0] as f32,
949                    1.0 / size[1] as f32,
950                ],
951                light: [key[0], key[1], key[2], light.ambient.clamp(0.0, 1.0)],
952                environment: [mips, intensity, present, 0.0],
953            }),
954        );
955        self.upload_gradients(device, queue);
956        let icon_view = scene.icons.map_or(&self.empty_icons, |icons| icons.view());
957        let environment_view = scene
958            .environment
959            .map_or(&self.empty_environment, |environment| &environment.view);
960        let gradient_view = self
961            .gradients
962            .as_ref()
963            .filter(|_| !self.gradient_rows.is_empty())
964            .map_or(&self.empty_gradients, |gradients| &gradients.1);
965        let (sprite_view, sprite_sampler) = scene
966            .sprites
967            .map_or((&self.empty_sprites, &self.sprite_sampler), |sprites| {
968                (&sprites.view, &sprites.sampler)
969            });
970        let stale = self
971            .bind
972            .as_ref()
973            .is_none_or(|(icons, environment, gradients, sprites, _)| {
974                icons != icon_view
975                    || environment != environment_view
976                    || gradients != gradient_view
977                    || sprites != sprite_view
978            });
979        if stale {
980            let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
981                label: Some("flat view"),
982                layout: &self.view_layout,
983                entries: &[
984                    wgpu::BindGroupEntry {
985                        binding: 0,
986                        resource: self.uniform.as_entire_binding(),
987                    },
988                    wgpu::BindGroupEntry {
989                        binding: 1,
990                        resource: wgpu::BindingResource::TextureView(icon_view),
991                    },
992                    wgpu::BindGroupEntry {
993                        binding: 2,
994                        resource: wgpu::BindingResource::Sampler(&self.icon_sampler),
995                    },
996                    wgpu::BindGroupEntry {
997                        binding: 3,
998                        resource: wgpu::BindingResource::TextureView(environment_view),
999                    },
1000                    wgpu::BindGroupEntry {
1001                        binding: 4,
1002                        resource: wgpu::BindingResource::Sampler(&self.environment_sampler),
1003                    },
1004                    wgpu::BindGroupEntry {
1005                        binding: 5,
1006                        resource: wgpu::BindingResource::TextureView(gradient_view),
1007                    },
1008                    wgpu::BindGroupEntry {
1009                        binding: 6,
1010                        resource: wgpu::BindingResource::TextureView(sprite_view),
1011                    },
1012                    wgpu::BindGroupEntry {
1013                        binding: 7,
1014                        resource: wgpu::BindingResource::Sampler(sprite_sampler),
1015                    },
1016                ],
1017            });
1018            self.bind = Some((
1019                icon_view.clone(),
1020                environment_view.clone(),
1021                gradient_view.clone(),
1022                sprite_view.clone(),
1023                group,
1024            ));
1025        }
1026        if !self.quads.items.is_empty() {
1027            self.ensure_layer(device, self.capacity.unwrap_or(size));
1028        }
1029        if self
1030            .ops
1031            .iter()
1032            .any(|op| matches!(op, Op::Instances { lit: true, .. }))
1033        {
1034            self.ensure_lit(device);
1035        }
1036        self.passes = 1 + 2 * self.quads.items.len() as u32;
1037        Ok(fit)
1038    }
1039
1040    fn push_draw(
1041        &mut self,
1042        draw: &Draw,
1043        scene: &FlatScene<'_>,
1044        fit: &Fit,
1045        icons: Option<&Icons>,
1046        clip: &super::Matrix,
1047        text_items: &mut Vec<(usize, usize, Range<u32>, u32)>,
1048    ) -> Result<Option<[f32; 4]>, String> {
1049        if let Shape::Text(index) = draw.shape {
1050            let text = scene
1051                .text
1052                .get(index)
1053                .ok_or_else(|| format!("a draw names text {index}, which is missing"))?;
1054            if !super::positive(draw.opacity) || super::placed(&draw.transform, fit).is_none() {
1055                return Ok(None);
1056            }
1057            self.scratch.clear();
1058            let mode = match text.glyphs {
1059                Glyphs::Paragraph(paragraph) => {
1060                    self.scratch.extend(pack_glyphs(&paragraph.glyphs));
1061                    u32::from(paragraph.atlas.channels == 3)
1062                }
1063                Glyphs::Quads(quads) => {
1064                    self.scratch.extend(pack_quads(quads));
1065                    match text.atlas.format() {
1066                        wgpu::TextureFormat::Rgba16Float => 2,
1067                        wgpu::TextureFormat::Rgba8Unorm => 1,
1068                        _ => 0,
1069                    }
1070                }
1071            };
1072            if self.scratch.is_empty() {
1073                return Ok(None);
1074            }
1075            let tint = text.colour.0;
1076            for glyph in &mut self.scratch {
1077                for (channel, value) in glyph.color.iter_mut().zip(tint).take(3) {
1078                    *channel *= value;
1079                }
1080                glyph.color[3] *= tint[3] * draw.opacity.min(1.0);
1081            }
1082            let matrix = super::multiply(*clip, draw.transform);
1083            let bounds = screen_bounds(
1084                &TextSpace::Surface {
1085                    model_view_projection: matrix,
1086                },
1087                self.scratch.iter().copied(),
1088                self.size,
1089            );
1090            let start = self.glyphs.items.len() as u32;
1091            self.glyphs.items.extend_from_slice(&self.scratch);
1092            let end = self.glyphs.items.len() as u32;
1093            let slot = self.text_uniforms.items.len() as u32;
1094            let size = text.atlas.size();
1095            let uniform = TextUniform {
1096                matrix,
1097                view_projection: super::IDENTITY,
1098                model: super::IDENTITY,
1099                rest_normal: [0.0, 0.0, 1.0, 0.0],
1100                atlas_size: [size[0] as f32, size[1] as f32],
1101                mode,
1102                deformer: u32::MAX,
1103                cells: [crate::text::DEFORMED_CELLS, draw.id, 0, 0],
1104            };
1105            let mut bytes = [0u8; TEXT_SLOT as usize];
1106            bytes[..std::mem::size_of::<TextUniform>()]
1107                .copy_from_slice(bytemuck::bytes_of(&uniform));
1108            self.text_uniforms.items.push(bytes);
1109            text_items.push((self.ops.len(), index, start..end, slot));
1110            self.ops.push(Op::Text {
1111                glyphs: start..end,
1112                slot,
1113                atlas: 0,
1114                id: draw.id,
1115            });
1116            return Ok(bounds);
1117        }
1118        let mut bounds = None;
1119        if let Some(shadow) = pack_shadow(draw, fit, scene.curve, &scene.light) {
1120            bounds = Some(shadow.pixel_bounds());
1121            self.push_instance(shadow);
1122        }
1123        if let Some(mut shape) = pack_shape(draw, fit, icons) {
1124            if let Fill::Gradient(gradient) = &draw.fill {
1125                let packed = gradient.packed();
1126                let row = match self.gradient_rows.iter().position(|row| *row == packed) {
1127                    Some(row) => row,
1128                    None => {
1129                        self.gradient_rows.push(packed);
1130                        self.gradient_rows.len() - 1
1131                    }
1132                };
1133                shape.fill_bottom[0] = row as f32;
1134            }
1135            let rect = shape.pixel_bounds();
1136            bounds = Some(bounds.map_or(rect, |bounds| rect_union(bounds, rect)));
1137            self.push_instance(shape);
1138        }
1139        Ok(bounds)
1140    }
1141
1142    fn upload_gradients(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
1143        let rows = self.gradient_rows.len() as u32;
1144        if rows == 0 {
1145            return;
1146        }
1147        if self
1148            .gradients
1149            .as_ref()
1150            .is_none_or(|gradients| gradients.2 < rows)
1151        {
1152            let capacity = rows.next_power_of_two().max(8);
1153            let texture = device.create_texture(&wgpu::TextureDescriptor {
1154                label: Some("flat gradients"),
1155                size: wgpu::Extent3d {
1156                    width: GRADIENT_TEXELS,
1157                    height: capacity,
1158                    depth_or_array_layers: 1,
1159                },
1160                mip_level_count: 1,
1161                sample_count: 1,
1162                dimension: wgpu::TextureDimension::D2,
1163                format: GRADIENT_FORMAT,
1164                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1165                view_formats: &[],
1166            });
1167            let view = texture.create_view(&Default::default());
1168            self.gradients = Some((texture, view, capacity));
1169            self.uploaded_rows.clear();
1170        }
1171        if self.uploaded_rows == self.gradient_rows {
1172            return;
1173        }
1174        let texture = &self.gradients.as_ref().unwrap().0;
1175        queue.write_texture(
1176            texture.as_image_copy(),
1177            bytemuck::cast_slice(&self.gradient_rows),
1178            wgpu::TexelCopyBufferLayout {
1179                offset: 0,
1180                bytes_per_row: Some(GRADIENT_TEXELS * 16),
1181                rows_per_image: Some(rows),
1182            },
1183            wgpu::Extent3d {
1184                width: GRADIENT_TEXELS,
1185                height: rows,
1186                depth_or_array_layers: 1,
1187            },
1188        );
1189        self.uploaded_rows.clone_from(&self.gradient_rows);
1190    }
1191
1192    fn ensure_layer(&mut self, device: &wgpu::Device, size: [u32; 2]) {
1193        if self.layer.as_ref().is_some_and(|layer| layer.3 == size) {
1194            return;
1195        }
1196        let texture = device.create_texture(&wgpu::TextureDescriptor {
1197            label: Some("flat group layer"),
1198            size: wgpu::Extent3d {
1199                width: size[0],
1200                height: size[1],
1201                depth_or_array_layers: 1,
1202            },
1203            mip_level_count: 1,
1204            sample_count: 1,
1205            dimension: wgpu::TextureDimension::D2,
1206            format: COLOUR_FORMAT,
1207            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1208            view_formats: &[],
1209        });
1210        let view = texture.create_view(&Default::default());
1211        let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1212            label: Some("flat group layer"),
1213            layout: &self.layer_layout,
1214            entries: &[wgpu::BindGroupEntry {
1215                binding: 0,
1216                resource: wgpu::BindingResource::TextureView(&view),
1217            }],
1218        });
1219        self.layer = Some((texture, view, group, size));
1220    }
1221
1222    pub fn colour_target(&mut self, device: &wgpu::Device, size: [u32; 2]) -> wgpu::TextureView {
1223        if self
1224            .colour_target
1225            .as_ref()
1226            .is_none_or(|target| target.2 != size)
1227        {
1228            let texture = device.create_texture(&wgpu::TextureDescriptor {
1229                label: Some("flat colour"),
1230                size: wgpu::Extent3d {
1231                    width: size[0],
1232                    height: size[1],
1233                    depth_or_array_layers: 1,
1234                },
1235                mip_level_count: 1,
1236                sample_count: 1,
1237                dimension: wgpu::TextureDimension::D2,
1238                format: COLOUR_FORMAT,
1239                usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1240                    | wgpu::TextureUsages::TEXTURE_BINDING
1241                    | wgpu::TextureUsages::COPY_SRC,
1242                view_formats: &[],
1243            });
1244            let view = texture.create_view(&Default::default());
1245            self.colour_target = Some((texture, view, size));
1246        }
1247        self.colour_target.as_ref().unwrap().1.clone()
1248    }
1249
1250    fn draw_op(&self, pass: &mut wgpu::RenderPass<'_>, op: &Op, ids: bool) {
1251        match op {
1252            Op::Instances { range, lit } => {
1253                let Some(buffer) = &self.instances.buffer else {
1254                    return;
1255                };
1256                let pipeline = match (lit, &self.lit, ids) {
1257                    (false, _, false) => &self.colour,
1258                    (false, _, true) => &self.ids,
1259                    (true, Some((colour, _)), false) => colour,
1260                    (true, Some((_, lit_ids)), true) => lit_ids,
1261                    (true, None, _) => return,
1262                };
1263                pass.set_pipeline(pipeline);
1264                pass.set_bind_group(0, &self.bind.as_ref().unwrap().4, &[]);
1265                pass.set_vertex_buffer(0, buffer.slice(..));
1266                pass.draw(0..4, range.clone());
1267            }
1268            Op::Text {
1269                glyphs,
1270                slot,
1271                atlas,
1272                id,
1273            } => {
1274                if ids && *id == 0 {
1275                    return;
1276                }
1277                let (Some(buffer), Some((_, group))) =
1278                    (&self.glyphs.buffer, self.text_binds.get(*atlas))
1279                else {
1280                    return;
1281                };
1282                pass.set_pipeline(if ids { &self.text_ids } else { &self.text });
1283                pass.set_bind_group(0, group, &[(u64::from(*slot) * TEXT_SLOT) as u32]);
1284                pass.set_vertex_buffer(0, buffer.slice(..));
1285                pass.draw(0..6, glyphs.clone());
1286            }
1287            Op::Layer { .. } | Op::EndLayer { .. } => {}
1288        }
1289    }
1290
1291    fn quad(&self, pass: &mut wgpu::RenderPass<'_>, quad: u32, composite: bool) {
1292        let Some(buffer) = &self.quads.buffer else {
1293            return;
1294        };
1295        if composite {
1296            pass.set_pipeline(&self.composite);
1297            pass.set_bind_group(1, &self.layer.as_ref().unwrap().2, &[]);
1298        } else {
1299            pass.set_pipeline(&self.clear);
1300        }
1301        pass.set_bind_group(0, &self.bind.as_ref().unwrap().4, &[]);
1302        pass.set_vertex_buffer(0, buffer.slice(..));
1303        pass.draw(0..4, quad..quad + 1);
1304    }
1305
1306    pub fn encode_colour(
1307        &self,
1308        encoder: &mut wgpu::CommandEncoder,
1309        target: &wgpu::TextureView,
1310        clear: Option<[f32; 4]>,
1311        mut profiler: Option<&mut GpuProfiler>,
1312    ) {
1313        let timing = profiler
1314            .as_deref_mut()
1315            .and_then(|profiler| profiler.pass("flat"));
1316        let mut index = 0;
1317        let last = self.passes.saturating_sub(1);
1318        let writes = |index: u32| {
1319            profiler.as_deref().and_then(|profiler| {
1320                profiler
1321                    .render_writes(timing)
1322                    .map(|writes| wgpu::RenderPassTimestampWrites {
1323                        beginning_of_pass_write_index: writes
1324                            .beginning_of_pass_write_index
1325                            .filter(|_| index == 0),
1326                        end_of_pass_write_index: writes
1327                            .end_of_pass_write_index
1328                            .filter(|_| index == last),
1329                        ..writes
1330                    })
1331                    .filter(|writes| {
1332                        writes.beginning_of_pass_write_index.is_some()
1333                            || writes.end_of_pass_write_index.is_some()
1334                    })
1335            })
1336        };
1337        let viewport = self.viewport();
1338        let begin = |encoder: &mut wgpu::CommandEncoder,
1339                     view: &wgpu::TextureView,
1340                     load: wgpu::LoadOp<wgpu::Color>,
1341                     index: u32| {
1342            let mut pass = encoder
1343                .begin_render_pass(&wgpu::RenderPassDescriptor {
1344                    multiview_mask: None,
1345                    label: Some("flat"),
1346                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1347                        view,
1348                        resolve_target: None,
1349                        depth_slice: None,
1350                        ops: wgpu::Operations {
1351                            load,
1352                            store: wgpu::StoreOp::Store,
1353                        },
1354                    })],
1355                    depth_stencil_attachment: None,
1356                    timestamp_writes: writes(index),
1357                    occlusion_query_set: None,
1358                })
1359                .forget_lifetime();
1360            if let Some(viewport) = viewport {
1361                crate::viewport::apply(&mut pass, viewport);
1362            }
1363            pass
1364        };
1365        let load = clear.map_or(wgpu::LoadOp::Load, |c| {
1366            wgpu::LoadOp::Clear(wgpu::Color {
1367                r: f64::from(c[0]),
1368                g: f64::from(c[1]),
1369                b: f64::from(c[2]),
1370                a: f64::from(c[3]),
1371            })
1372        });
1373        let mut pass = begin(encoder, target, load, index);
1374        let layer_view = self.layer.as_ref().map(|layer| &layer.1);
1375        for op in &self.ops {
1376            match op {
1377                Op::Layer { quad } => {
1378                    drop(pass);
1379                    index += 1;
1380                    pass = begin(encoder, layer_view.unwrap(), wgpu::LoadOp::Load, index);
1381                    self.quad(&mut pass, *quad, false);
1382                }
1383                Op::EndLayer { quad } => {
1384                    drop(pass);
1385                    index += 1;
1386                    pass = begin(encoder, target, wgpu::LoadOp::Load, index);
1387                    self.quad(&mut pass, *quad, true);
1388                }
1389                op => self.draw_op(&mut pass, op, false),
1390            }
1391        }
1392    }
1393
1394    pub fn encode_ids(
1395        &self,
1396        encoder: &mut wgpu::CommandEncoder,
1397        ids: &wgpu::TextureView,
1398        clear: bool,
1399        mut profiler: Option<&mut GpuProfiler>,
1400    ) {
1401        let timing = profiler
1402            .as_deref_mut()
1403            .and_then(|profiler| profiler.pass("flat ids"));
1404        let writes = profiler
1405            .as_deref()
1406            .and_then(|profiler| profiler.render_writes(timing));
1407        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1408            multiview_mask: None,
1409            label: Some("flat ids"),
1410            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1411                view: ids,
1412                resolve_target: None,
1413                depth_slice: None,
1414                ops: wgpu::Operations {
1415                    load: if clear {
1416                        wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
1417                    } else {
1418                        wgpu::LoadOp::Load
1419                    },
1420                    store: wgpu::StoreOp::Store,
1421                },
1422            })],
1423            depth_stencil_attachment: None,
1424            timestamp_writes: writes,
1425            occlusion_query_set: None,
1426        });
1427        if let Some(viewport) = self.viewport() {
1428            crate::viewport::apply(&mut pass, viewport);
1429        }
1430        for op in &self.ops {
1431            self.draw_op(&mut pass, op, true);
1432        }
1433    }
1434
1435    pub fn has_ids(&self) -> bool {
1436        self.instances
1437            .items
1438            .iter()
1439            .any(|instance| instance.kind() != KIND_SHADOW)
1440            || self
1441                .ops
1442                .iter()
1443                .any(|op| matches!(op, Op::Text { id, .. } if *id != 0))
1444    }
1445
1446    fn finish_index(
1447        &mut self,
1448        device: &wgpu::Device,
1449        format: wgpu::TextureFormat,
1450        linear: bool,
1451        blend: bool,
1452    ) -> usize {
1453        match self.finishes.iter().position(|finish| {
1454            finish.format == format && finish.linear == linear && finish.blend == blend
1455        }) {
1456            Some(index) => index,
1457            None => {
1458                let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1459                    label: Some("flat finish"),
1460                    layout: Some(&self.finish_pipeline_layout),
1461                    vertex: wgpu::VertexState {
1462                        module: &self.finish_module,
1463                        entry_point: Some("flat_finish_vertex"),
1464                        buffers: &[],
1465                        compilation_options: Default::default(),
1466                    },
1467                    primitive: Default::default(),
1468                    depth_stencil: None,
1469                    multisample: Default::default(),
1470                    fragment: Some(wgpu::FragmentState {
1471                        module: &self.finish_module,
1472                        entry_point: Some(if linear {
1473                            "flat_finish_linear"
1474                        } else {
1475                            "flat_finish_copy"
1476                        }),
1477                        targets: &[Some(colour_state(format, blend))],
1478                        compilation_options: Default::default(),
1479                    }),
1480                    multiview_mask: None,
1481                    cache: None,
1482                });
1483                self.finishes.push(Finish {
1484                    format,
1485                    linear,
1486                    blend,
1487                    pipeline,
1488                });
1489                self.made += 1;
1490                self.finishes.len() - 1
1491            }
1492        }
1493    }
1494
1495    #[allow(clippy::too_many_arguments)]
1496    pub fn encode_finish(
1497        &mut self,
1498        device: &wgpu::Device,
1499        encoder: &mut wgpu::CommandEncoder,
1500        source: &wgpu::TextureView,
1501        target: &wgpu::TextureView,
1502        format: wgpu::TextureFormat,
1503        linear: bool,
1504        blend: bool,
1505        mut profiler: Option<&mut GpuProfiler>,
1506    ) {
1507        let index = self.finish_index(device, format, linear, blend);
1508        let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1509            label: Some("flat finish"),
1510            layout: &self.finish_layout,
1511            entries: &[wgpu::BindGroupEntry {
1512                binding: 0,
1513                resource: wgpu::BindingResource::TextureView(source),
1514            }],
1515        });
1516        let timing = profiler
1517            .as_deref_mut()
1518            .and_then(|profiler| profiler.pass("flat finish"));
1519        let writes = profiler
1520            .as_deref()
1521            .and_then(|profiler| profiler.render_writes(timing));
1522        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1523            multiview_mask: None,
1524            label: Some("flat finish"),
1525            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1526                view: target,
1527                resolve_target: None,
1528                depth_slice: None,
1529                ops: wgpu::Operations {
1530                    load: if blend {
1531                        wgpu::LoadOp::Load
1532                    } else {
1533                        wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
1534                    },
1535                    store: wgpu::StoreOp::Store,
1536                },
1537            })],
1538            depth_stencil_attachment: None,
1539            timestamp_writes: writes,
1540            occlusion_query_set: None,
1541        });
1542        if let Some(viewport) = self.viewport() {
1543            crate::viewport::apply(&mut pass, viewport);
1544        }
1545        if let Some([x, y, width, height]) = self.clip {
1546            pass.set_scissor_rect(x, y, width, height);
1547        }
1548        pass.set_pipeline(&self.finishes[index].pipeline);
1549        pass.set_bind_group(0, &group, &[]);
1550        pass.draw(0..3, 0..1);
1551    }
1552
1553    pub fn render(
1554        &mut self,
1555        device: &wgpu::Device,
1556        queue: &wgpu::Queue,
1557        encoder: &mut wgpu::CommandEncoder,
1558        scene: &FlatScene<'_>,
1559        target: FlatTarget<'_>,
1560        mut profiler: Option<&mut GpuProfiler>,
1561    ) -> Result<Fit, String> {
1562        let fit = self.prepare(device, queue, scene, target.size)?;
1563        self.encode_colour(
1564            encoder,
1565            target.colour,
1566            target.clear,
1567            profiler.as_deref_mut(),
1568        );
1569        if let Some(ids) = target.ids {
1570            self.encode_ids(encoder, ids, target.clear_ids, profiler);
1571        }
1572        Ok(fit)
1573    }
1574}