feather_ui/render/
compositor.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4use crate::color::sRGB32;
5use crate::graphics::{Driver, Vec2f};
6use crate::render::atlas::PxBox;
7use crate::{PxDim, PxPoint, PxRect, PxVector, RelDim, SourceID};
8use derive_where::derive_where;
9use num_traits::Zero;
10use smallvec::SmallVec;
11use std::collections::HashMap;
12use std::num::NonZero;
13use std::sync::Arc;
14use wgpu::wgt::SamplerDescriptor;
15use wgpu::{
16    BindGroupEntry, BindGroupLayoutEntry, Buffer, BufferDescriptor, BufferUsages, TextureView,
17};
18
19use parking_lot::RwLock;
20
21use super::atlas::Atlas;
22
23#[derive(Debug)]
24/// Shared resources that are the same between all windows
25pub struct Shared {
26    layout: wgpu::PipelineLayout,
27    shader: wgpu::ShaderModule,
28    sampler: wgpu::Sampler,
29    pipelines: RwLock<HashMap<wgpu::TextureFormat, wgpu::RenderPipeline>>,
30    layers: RwLock<HashMap<Arc<SourceID>, Layer>>,
31}
32
33pub const TARGET_BLEND: wgpu::ColorTargetState = wgpu::ColorTargetState {
34    format: crate::render::atlas::ATLAS_FORMAT,
35    blend: Some(wgpu::BlendState::REPLACE),
36    write_mask: wgpu::ColorWrites::ALL,
37};
38
39impl Shared {
40    pub fn new(device: &wgpu::Device) -> Self {
41        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
42            label: Some("Compositor"),
43            source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/compositor.wgsl").into()),
44        });
45
46        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
47            label: Some("Compositor Bind Group"),
48            entries: &[
49                BindGroupLayoutEntry {
50                    binding: 0,
51                    visibility: wgpu::ShaderStages::VERTEX,
52                    ty: wgpu::BindingType::Buffer {
53                        ty: wgpu::BufferBindingType::Uniform,
54                        has_dynamic_offset: false,
55                        min_binding_size: NonZero::new(size_of::<crate::Mat4x4>() as u64),
56                    },
57                    count: None,
58                },
59                BindGroupLayoutEntry {
60                    binding: 1,
61                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
62                    ty: wgpu::BindingType::Buffer {
63                        ty: wgpu::BufferBindingType::Storage { read_only: true },
64                        has_dynamic_offset: true,
65                        min_binding_size: None,
66                    },
67                    count: None,
68                },
69                BindGroupLayoutEntry {
70                    binding: 2,
71                    visibility: wgpu::ShaderStages::FRAGMENT,
72                    ty: wgpu::BindingType::Buffer {
73                        ty: wgpu::BufferBindingType::Storage { read_only: true },
74                        has_dynamic_offset: false,
75                        min_binding_size: None,
76                    },
77                    count: None,
78                },
79                BindGroupLayoutEntry {
80                    binding: 3,
81                    visibility: wgpu::ShaderStages::FRAGMENT,
82                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
83                    count: None,
84                },
85                BindGroupLayoutEntry {
86                    binding: 4,
87                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
88                    ty: wgpu::BindingType::Texture {
89                        multisampled: false,
90                        view_dimension: wgpu::TextureViewDimension::D2Array,
91                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
92                    },
93                    count: None,
94                },
95                BindGroupLayoutEntry {
96                    binding: 5,
97                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
98                    ty: wgpu::BindingType::Texture {
99                        multisampled: false,
100                        view_dimension: wgpu::TextureViewDimension::D2Array,
101                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
102                    },
103                    count: None,
104                },
105            ],
106        });
107
108        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
109            label: Some("Compositor Pipeline"),
110            bind_group_layouts: &[&bind_group_layout],
111            push_constant_ranges: &[],
112        });
113
114        let sampler = device.create_sampler(&SamplerDescriptor {
115            label: Some("Compositor Sampler"),
116            address_mode_u: wgpu::AddressMode::ClampToEdge,
117            address_mode_v: wgpu::AddressMode::ClampToEdge,
118            address_mode_w: wgpu::AddressMode::ClampToEdge,
119            mag_filter: wgpu::FilterMode::Linear,
120            min_filter: wgpu::FilterMode::Linear,
121            mipmap_filter: wgpu::FilterMode::Linear,
122            border_color: Some(wgpu::SamplerBorderColor::TransparentBlack),
123            ..Default::default()
124        });
125
126        Self {
127            layout,
128            shader,
129            sampler,
130            pipelines: HashMap::new().into(),
131            layers: HashMap::new().into(),
132        }
133    }
134
135    pub fn access_layers(
136        &self,
137    ) -> parking_lot::lock_api::RwLockReadGuard<
138        '_,
139        parking_lot::RawRwLock,
140        HashMap<Arc<SourceID>, Layer>,
141    > {
142        self.layers.read()
143    }
144
145    fn get_pipeline(
146        &self,
147        device: &wgpu::Device,
148        format: wgpu::TextureFormat,
149    ) -> wgpu::RenderPipeline {
150        self.pipelines
151            .write()
152            .entry(format)
153            .or_insert_with(|| {
154                device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
155                    label: None,
156                    layout: Some(&self.layout),
157                    vertex: wgpu::VertexState {
158                        module: &self.shader,
159                        entry_point: Some("vs_main"),
160                        buffers: &[],
161                        compilation_options: Default::default(),
162                    },
163                    fragment: Some(wgpu::FragmentState {
164                        module: &self.shader,
165                        entry_point: Some("fs_main"),
166                        compilation_options: Default::default(),
167                        targets: &[Some(wgpu::ColorTargetState {
168                            format,
169                            blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
170                            write_mask: wgpu::ColorWrites::ALL,
171                        })],
172                    }),
173                    primitive: wgpu::PrimitiveState {
174                        front_face: wgpu::FrontFace::Cw,
175                        topology: wgpu::PrimitiveTopology::TriangleList,
176                        ..Default::default()
177                    },
178                    depth_stencil: None,
179                    multisample: wgpu::MultisampleState::default(),
180                    multiview: None,
181                    cache: None,
182                })
183            })
184            .clone()
185    }
186
187    pub fn create_layer(
188        &self,
189        _device: &wgpu::Device,
190        id: Arc<SourceID>,
191        mut area: PxRect,
192        dest: Option<PxRect>,
193        color: sRGB32,
194        rotation: f32,
195        force: bool,
196    ) -> Option<Layer> {
197        // Snap the layer area to the nearest pixel. This is necessary because the layer
198        // is treated as a compositing target, which is always assumed to be on
199        // a pixel grid.
200        let array = area.v.as_array_mut();
201        array[0] = array[0].floor();
202        array[1] = array[1].floor();
203        array[2] = array[2].ceil();
204        array[3] = array[3].ceil();
205
206        let dest = dest.unwrap_or(area);
207
208        // If true, this is a clipping layer, not a texture-backed one
209        let target = if color == sRGB32::white() && rotation.is_zero() && !force && dest == area {
210            None
211        } else {
212            Some(RwLock::new(LayerTarget {
213                dependents: Default::default(),
214            }))
215        };
216
217        let layer = Layer {
218            area,
219            dest,
220            color,
221            rotation,
222            target,
223        };
224
225        if let Some(prev) = self.layers.read().get(&id)
226            && *prev == layer
227        {
228            return None;
229        }
230
231        self.layers.write().insert(id, layer)
232    }
233}
234
235// This holds the information for rendering to a layer, which can only be done
236// if the layer is texture-backed.
237#[derive(Debug)]
238pub struct LayerTarget {
239    pub dependents: Vec<std::sync::Weak<SourceID>>, /* Layers that draw on to this one (does not
240                                                     * include fake layers) */
241}
242
243#[derive(Debug)]
244pub struct Layer {
245    // Renderable area representing what children draw onto. This corresponds to this layer's
246    // compositor viewport, if it has one.
247    pub area: PxRect,
248    // destination area that the layer is composited onto. Usually this is the same as area, but
249    // can be different when scaling down
250    dest: PxRect,
251    color: sRGB32,
252    rotation: f32,
253    // Layers aren't always texture-backed so this may not exist
254    pub target: Option<RwLock<LayerTarget>>,
255}
256
257impl PartialEq for Layer {
258    fn eq(&self, other: &Self) -> bool {
259        self.area == other.area
260            && self.dest == other.dest
261            && self.color == other.color
262            && self.rotation == other.rotation
263    }
264}
265
266type DeferFn = dyn FnOnce(&Driver, &mut Data) + Send + Sync;
267type CustomDrawFn = dyn FnMut(&Driver, &mut wgpu::RenderPass<'_>, PxVector) + Send + Sync;
268
269/// Fundamentally, the compositor works on a massive set of pre-allocated
270/// vertices that it assembles into quads in the vertex shader, which then moves
271/// them into position and assigns them UV coordinates. Then the pixel shader
272/// checks if it must do per-pixel clipping and discards the pixel if it's out
273/// of bounds, then samples a texture from the provided texture bank, does color
274/// modulation if applicable, then draws the final pixel to the screen using
275/// pre-multiplied alpha blending. This allows the compositor to avoid
276/// allocating a vertex buffer, instead using a SSBO (or webgpu storage buffer)
277/// to store the per-quad data, which it then accesses from the built-in vertex
278/// index.
279///
280/// The compositor can accept GPU generated instructions written directly into
281/// it's buffer using a compute shader, if desired.
282///
283/// The compositor can also accept custom draw calls that break up the batched
284/// compositor instructions, which is intended for situations where rendering to
285/// the texture atlas is either impractical, or a different blending operation
286/// is required (such as subpixel blended text, which requires the SRC1
287/// dual-source blending mode, instead of standard pre-multiplied alpha).
288#[derive(Debug)]
289pub struct Compositor {
290    pipeline: wgpu::RenderPipeline,
291    mvp: Buffer,
292    clip: Buffer,
293    clipdata: Vec<PxRect>, // Clipping Rectangles
294    pub(crate) segments: SmallVec<[HashMap<u8, Segment>; 1]>,
295    view: std::sync::Weak<TextureView>,
296    layer_view: std::sync::Weak<TextureView>,
297    layer: bool, // Tells us which layer atlas to use (the first or second)
298}
299
300/// This stores the compositing data for a single render pass. The window
301/// compositor only ever has one segment, but the compositors for the layer
302/// caches can have many different segments for each dependency layer and each
303/// target slice in the layer atlas for that dependency layer. Each segment
304/// contains a set of CPU-side copy-regions to enable GPU generation of
305/// compositing data, it's own deferred rendering queue, and its own list of
306/// custom draw commands.
307#[derive_where(Debug)]
308pub struct Segment {
309    group: wgpu::BindGroup,
310    buffer: Buffer,
311    data: Vec<Data>,
312    regions: Vec<std::ops::Range<u32>>,
313    #[derive_where(skip)]
314    defer: HashMap<u32, (Box<DeferFn>, PxRect, PxVector)>,
315    #[derive_where(skip)]
316    custom: Vec<(u32, Box<CustomDrawFn>, PxVector)>,
317}
318
319impl Compositor {
320    fn reserve(&mut self, driver: &Driver, pass: u8, slice: u8) {
321        self.segments.resize_with(pass as usize + 1, HashMap::new);
322        self.segments[pass as usize]
323            .entry(slice)
324            .or_insert_with(|| {
325                let buffer = driver.device.create_buffer(&BufferDescriptor {
326                    label: Some("Compositor Data"),
327                    size: 32 * size_of::<Data>() as u64,
328                    usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
329                    mapped_at_creation: false,
330                });
331
332                let group = Self::gen_binding(
333                    &self.mvp,
334                    &buffer,
335                    &self.clip,
336                    &driver.shared,
337                    &driver.device,
338                    &driver.atlas.read().view,
339                    &driver.layer_atlas[self.layer as usize].read().view,
340                    &self.pipeline.get_bind_group_layout(0),
341                );
342
343                #[allow(clippy::single_range_in_vec_init)]
344                Segment {
345                    group,
346                    buffer,
347                    data: Vec::new(),
348                    regions: vec![0..0],
349                    defer: HashMap::new(),
350                    custom: Vec::new(),
351                }
352            });
353    }
354
355    fn gen_binding(
356        mvp: &Buffer,
357        buffer: &Buffer,
358        clip: &Buffer,
359        shared: &Shared,
360        device: &wgpu::Device,
361        atlasview: &TextureView,
362        layerview: &TextureView,
363        layout: &wgpu::BindGroupLayout,
364    ) -> wgpu::BindGroup {
365        let bindings = [
366            BindGroupEntry {
367                binding: 0,
368                resource: mvp.as_entire_binding(),
369            },
370            BindGroupEntry {
371                binding: 1,
372                resource: buffer.as_entire_binding(),
373            },
374            BindGroupEntry {
375                binding: 2,
376                resource: clip.as_entire_binding(),
377            },
378            BindGroupEntry {
379                binding: 3,
380                resource: wgpu::BindingResource::Sampler(&shared.sampler),
381            },
382            BindGroupEntry {
383                binding: 4,
384                resource: wgpu::BindingResource::TextureView(atlasview),
385            },
386            BindGroupEntry {
387                binding: 5,
388                resource: wgpu::BindingResource::TextureView(layerview),
389            },
390        ];
391
392        device.create_bind_group(&wgpu::BindGroupDescriptor {
393            layout,
394            entries: &bindings,
395            label: None,
396        })
397    }
398
399    // This cannot take a Driver because we have to create two Compositors before
400    // the Driver object is made
401    pub fn new(
402        device: &wgpu::Device,
403        shared: &Shared,
404        atlasview: &Arc<TextureView>,
405        layerview: &Arc<TextureView>,
406        format: wgpu::TextureFormat,
407        layer: bool,
408    ) -> Self {
409        let pipeline = shared.get_pipeline(device, format);
410
411        let mvp = device.create_buffer(&BufferDescriptor {
412            label: Some("MVP"),
413            size: std::mem::size_of::<crate::Mat4x4>() as u64,
414            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
415            mapped_at_creation: false,
416        });
417
418        let clip = device.create_buffer(&BufferDescriptor {
419            label: Some("Compositor Clip Data"),
420            size: 4 * size_of::<PxRect>() as u64,
421            usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
422            mapped_at_creation: false,
423        });
424
425        let buffer = device.create_buffer(&BufferDescriptor {
426            label: Some("Compositor Data"),
427            size: 32 * size_of::<Data>() as u64,
428            usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
429            mapped_at_creation: false,
430        });
431
432        let group = Self::gen_binding(
433            &mvp,
434            &buffer,
435            &clip,
436            shared,
437            device,
438            atlasview,
439            layerview,
440            &pipeline.get_bind_group_layout(0),
441        );
442
443        #[allow(clippy::single_range_in_vec_init)]
444        let segment = Segment {
445            group,
446            buffer,
447            data: Vec::new(),
448            regions: vec![0..0],
449            defer: HashMap::new(),
450            custom: Vec::new(),
451        };
452
453        Self {
454            pipeline,
455            mvp,
456            clip,
457            clipdata: vec![PxRect::zero()],
458            segments: SmallVec::from_buf([HashMap::from_iter([(0, segment)])]),
459            view: Arc::downgrade(atlasview),
460            layer_view: Arc::downgrade(layerview),
461            layer,
462        }
463    }
464
465    /// Should be called when any external or internal buffer gets invalidated
466    /// (such as atlas views)
467    fn rebind(&mut self, shared: &Shared, device: &wgpu::Device, atlas: &Atlas, layers: &Atlas) {
468        self.view = Arc::downgrade(&atlas.view);
469        self.layer_view = Arc::downgrade(&layers.view);
470
471        for slices in &mut self.segments {
472            for segment in slices.values_mut() {
473                segment.group = Self::gen_binding(
474                    &self.mvp,
475                    &segment.buffer,
476                    &self.clip,
477                    shared,
478                    device,
479                    &atlas.view,
480                    &layers.view,
481                    &self.pipeline.get_bind_group_layout(0),
482                );
483            }
484        }
485    }
486
487    fn check_data(
488        mvp: &Buffer,
489        clip: &Buffer,
490        layout: &wgpu::BindGroupLayout,
491        segment: &mut Segment,
492        shared: &Shared,
493        device: &wgpu::Device,
494        atlas: &Atlas,
495        layers: &Atlas,
496    ) {
497        let size = segment.data.len() * size_of::<Data>();
498        if (segment.buffer.size() as usize) < size {
499            segment.buffer.destroy();
500            segment.buffer = device.create_buffer(&BufferDescriptor {
501                label: Some("Compositor Data"),
502                size: size.next_power_of_two() as u64,
503                usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
504                mapped_at_creation: false,
505            });
506
507            segment.group = Self::gen_binding(
508                mvp,
509                &segment.buffer,
510                clip,
511                shared,
512                device,
513                &atlas.view,
514                &layers.view,
515                layout,
516            );
517        }
518    }
519
520    fn check_clip(
521        &mut self,
522        shared: &Shared,
523        device: &wgpu::Device,
524        atlas: &Atlas,
525        layers: &Atlas,
526    ) {
527        let size = self.clipdata.len() * size_of::<PxRect>();
528        if (self.clip.size() as usize) < size {
529            self.clip.destroy();
530            self.clip = device.create_buffer(&BufferDescriptor {
531                label: Some("Compositor Clip Data"),
532                size: size.next_power_of_two() as u64,
533                usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
534                mapped_at_creation: false,
535            });
536            self.rebind(shared, device, atlas, layers);
537        }
538    }
539
540    #[inline]
541    fn scissor_check(dim: &PxDim, clip: PxRect) -> Result<PxRect, PxRect> {
542        if PxRect::new(0.0, 0.0, dim.width, dim.height) == clip {
543            Err(clip)
544        } else {
545            Ok(clip)
546        }
547    }
548
549    #[inline]
550    fn clip_data(
551        clipdata: &mut Vec<PxRect>,
552        cliprect: Result<PxRect, PxRect>,
553        offset: PxVector,
554        mut data: Data,
555    ) -> Option<Data> {
556        match cliprect {
557            Ok(clip) => {
558                if data.rotation.is_zero() {
559                    let (mut x, mut y, mut w, mut h) =
560                        (data.pos.0[0], data.pos.0[1], data.dim.0[0], data.dim.0[1]);
561
562                    // If the whole rect is outside the cliprect, don't render it at all.
563                    if !clip.collides(&PxRect::new(x, y, x + w, y + h)) {
564                        // TODO: When we start reserving slots, this will need to instead insert a
565                        // special zero size rect.
566                        return None;
567                    }
568
569                    let (mut u, mut v, mut uw, mut vh) =
570                        (data.uv.0[0], data.uv.0[1], data.uvdim.0[0], data.uvdim.0[1]);
571
572                    // If rotation is zero, we don't need to do per-pixel clipping, we can just
573                    // modify the rect itself.
574                    let bounds = clip.v.as_array_ref();
575                    let (min_x, min_y, max_x, max_y) = (bounds[0], bounds[1], bounds[2], bounds[3]);
576
577                    // Get the ratio from our target rect to the source UV sampler rect
578                    let uv_ratio = RelDim::new(uw / w, vh / h);
579
580                    // Clip left edge
581                    if x < min_x {
582                        let right_shift = min_x - x;
583
584                        x += right_shift;
585                        u += right_shift * uv_ratio.width;
586                        w -= right_shift;
587                        uw -= right_shift * uv_ratio.width;
588                    }
589
590                    // Clip right edge
591                    if x + w > max_x {
592                        let right_shift = max_x - (x + w);
593                        w += right_shift;
594                        uw += right_shift * uv_ratio.width;
595                    }
596
597                    // Clip top edge
598                    if y < min_y {
599                        let bottom_shift = min_y - y;
600
601                        y += bottom_shift;
602                        v += bottom_shift * uv_ratio.height;
603                        h -= bottom_shift;
604                        vh -= bottom_shift * uv_ratio.height;
605                    }
606
607                    // Clip bottom edge
608                    if y + h > max_y {
609                        let bottom_shift = max_y - (y + h);
610                        h += bottom_shift;
611                        vh += bottom_shift * uv_ratio.height;
612                    }
613
614                    Some(Data {
615                        pos: [x + offset.x, y + offset.y].into(),
616                        dim: [w, h].into(),
617                        uv: [u, v].into(),
618                        uvdim: [uw, vh].into(),
619                        color: data.color,
620                        rotation: data.rotation,
621                        flags: data.flags,
622                        _padding: data._padding,
623                    })
624                } else {
625                    // TODO: Beyond some size, like 32, skip all elements except the last N clipping
626                    // rects and only check those
627                    let idx = if let Some((idx, _)) =
628                        clipdata.iter().enumerate().find(|(_, r)| **r == clip)
629                    {
630                        idx
631                    } else {
632                        clipdata.push(clip);
633                        clipdata.len() - 1
634                    };
635
636                    debug_assert!(idx < 0xFFFF);
637                    data.flags = DataFlags::from_bits(data.flags)
638                        .with_clip(idx as u16)
639                        .into();
640                    data.pos.0[0] += offset.x;
641                    data.pos.0[1] += offset.y;
642                    Some(data)
643                }
644            }
645            Err(clip) => {
646                // If the current cliprect is just the scissor rect, we do NOT add a custom
647                // clipping rect or do further clipping, but we do check to see
648                // if we need to bother rendering this at all.
649                if data.rotation.is_zero() {
650                    let (x, y, w, h) = (data.pos.0[0], data.pos.0[1], data.dim.0[0], data.dim.0[1]);
651                    if !clip.collides(&PxRect::new(x, y, x + w, y + h)) {
652                        // TODO: When we start reserving slots, this will need to instead insert a
653                        // special zero size rect.
654                        return None;
655                    }
656                }
657                data.pos.0[0] += offset.x;
658                data.pos.0[1] += offset.y;
659                Some(data)
660            }
661        }
662    }
663
664    pub fn prepare(&mut self, driver: &Driver, _: &mut wgpu::CommandEncoder, surface_dim: PxDim) {
665        // Check to see if we need to rebind either atlas view
666        if self.view.strong_count() == 0 || self.layer_view.strong_count() == 0 {
667            self.rebind(
668                &driver.shared,
669                &driver.device,
670                &driver.atlas.read(),
671                &driver.layer_atlas[self.layer as usize].read(),
672            );
673        }
674
675        // Resolve all defers
676        for slices in &mut self.segments {
677            for segment in slices.values_mut() {
678                for (idx, (f, clip, offset)) in segment.defer.drain() {
679                    f(driver, &mut segment.data[idx as usize]);
680                    segment.data[idx as usize].flags =
681                        DataFlags::from_bits(segment.data[idx as usize].flags).into();
682                    segment.data[idx as usize] = Self::clip_data(
683                        &mut self.clipdata,
684                        Self::scissor_check(&surface_dim, clip),
685                        offset,
686                        segment.data[idx as usize],
687                    )
688                    .unwrap_or_default();
689                }
690
691                if !segment.data.is_empty() {
692                    Self::check_data(
693                        &self.mvp,
694                        &self.clip,
695                        &self.pipeline.get_bind_group_layout(0),
696                        segment,
697                        &driver.shared,
698                        &driver.device,
699                        &driver.atlas.read(),
700                        &driver.layer_atlas[self.layer as usize].read(),
701                    );
702
703                    // TODO turn into write_buffer_with (is that actually going to be faster?)
704                    let mut offset = 0;
705                    for range in &segment.regions {
706                        let len = range.end - range.start;
707                        driver.queue.write_buffer(
708                            &segment.buffer,
709                            range.start as u64,
710                            bytemuck::cast_slice(
711                                &segment.data.as_slice()[offset as usize..(offset + len) as usize],
712                            ),
713                        );
714                        offset += len;
715                    }
716                }
717            }
718        }
719
720        driver.queue.write_buffer(
721            &self.mvp,
722            0,
723            bytemuck::cast_slice(
724                &crate::graphics::mat4_proj(
725                    0.0,
726                    surface_dim.height,
727                    surface_dim.width,
728                    -(surface_dim.height),
729                    0.2,
730                    10000.0,
731                )
732                .to_array(),
733            ),
734        );
735
736        // Very important that we do this AFTER resolving all defers, since those can
737        // add cliprects
738        if !self.clipdata.is_empty() {
739            self.check_clip(
740                &driver.shared,
741                &driver.device,
742                &driver.atlas.read(),
743                &driver.layer_atlas[self.layer as usize].read(),
744            );
745            driver.queue.write_buffer(
746                &self.clip,
747                0,
748                bytemuck::cast_slice(self.clipdata.as_slice()),
749            );
750        }
751    }
752
753    #[inline]
754    fn append_internal(
755        &mut self,
756        clipstack: &[PxRect],
757        surface_dim: PxDim,
758        layer_offset: PxVector,
759        pos: PxPoint,
760        dim: PxDim,
761        uv: PxPoint,
762        uvdim: PxDim,
763        color: u32,
764        rotation: f32,
765        tex: u8,
766        pass: u8,
767        slice: u8,
768        raw: bool,
769        layer: bool,
770    ) -> u32 {
771        let data = Data {
772            pos: pos.to_array().into(),
773            dim: dim.to_array().into(),
774            uv: uv.to_array().into(),
775            uvdim: uvdim.to_array().into(),
776            color,
777            rotation,
778            flags: DataFlags::new()
779                .with_tex(tex)
780                .with_raw(raw)
781                .with_layer(layer)
782                .into(),
783            ..Default::default()
784        };
785
786        if let Some(d) = Self::clip_data(
787            &mut self.clipdata,
788            clipstack.last().ok_or(surface_dim.into()).copied(),
789            layer_offset,
790            data,
791        ) {
792            self.preprocessed(d, pass, slice)
793        } else {
794            u32::MAX // TODO: Once we start reserving slots, we will always need to return a valid one by inserting an empty rect in clip_data
795        }
796    }
797
798    #[inline]
799    fn preprocessed(&mut self, data: Data, index: u8, slice: u8) -> u32 {
800        let segment = &mut self.segments[index as usize].get_mut(&slice).unwrap();
801        let region = segment.regions.last_mut().unwrap();
802        if region.end == u32::MAX {
803            panic!(
804                "Still processing a compute operation! Finish it by calling set_compute_buffer() first."
805            );
806        }
807
808        let idx = region.end;
809        segment.data.push(data);
810        region.end += 1;
811        idx
812    }
813
814    pub fn draw(&mut self, driver: &Driver, pass: &mut wgpu::RenderPass<'_>, index: u8, slice: u8) {
815        let segment = &mut self.segments[index as usize].get_mut(&slice).unwrap();
816
817        let mut last_index = 0;
818        for (i, f, draw_offset) in &mut segment.custom {
819            if last_index < *i {
820                pass.set_pipeline(&self.pipeline);
821                pass.set_bind_group(0, &segment.group, &[0]);
822                pass.draw(last_index..*i, 0..1);
823            }
824            last_index = *i;
825            f(driver, pass, *draw_offset);
826        }
827
828        pass.set_pipeline(&self.pipeline);
829        pass.set_bind_group(0, &segment.group, &[0]);
830        pass.draw(last_index..(segment.regions.last().unwrap().end * 6), 0..1);
831    }
832
833    pub fn cleanup(&mut self) {
834        for slices in &mut self.segments {
835            for segment in slices.values_mut() {
836                segment.data.clear();
837                segment.regions.clear();
838                segment.regions.push(0..0);
839            }
840        }
841
842        self.clipdata.clear();
843        self.clipdata.push(PxRect::zero());
844    }
845}
846
847/// A Compositor is associated with a render target, which is usually a window,
848/// but can also be an intermediate buffer, used for Layers. As a result, a
849/// compositor does not control the clipping rect stack, the window itself does.
850/// This associates a compositor with a clipstack and any other information it
851/// might need to properly append data during a render, such as the offset.
852/// Because of this auxillairy information, you cannot append directly to a
853/// compositor, only to a CompositorView.
854pub struct CompositorView<'a> {
855    pub index: u8, /* While we carry mutable references of all 3 possible compositors, this
856                    * tells us which we're currently using */
857    pub window: &'a mut Compositor, // index 0
858    pub layer0: &'a mut Compositor, // index 1
859    pub layer1: &'a mut Compositor, // index 2
860    pub clipstack: &'a mut Vec<PxRect>,
861    pub offset: PxVector,
862    pub surface_dim: PxDim, // Dimension of the top-level window surface.
863    pub pass: u8,
864    pub slice: u8, // This is the atlas slice index that this is being rendered to
865}
866
867impl<'a> CompositorView<'a> {
868    #[inline]
869    pub fn with_clip<T>(
870        &mut self,
871        clip: PxRect,
872        f: impl FnOnce(&mut Self) -> Result<T, crate::Error>,
873    ) -> Result<T, crate::Error> {
874        if let Some(prev) = self.clipstack.last() {
875            self.clipstack.push(clip.intersect(*prev));
876        } else {
877            self.clipstack.push(clip);
878        }
879        let r = f(self);
880        self.clipstack
881            .pop()
882            .expect("Tried to pop a clipping rect but the stack was empty!");
883        r
884    }
885
886    #[inline]
887    pub fn current_clip(&self) -> PxRect {
888        *self.clipstack.last().unwrap_or(&self.surface_dim.into())
889    }
890
891    #[inline]
892    pub fn segment(&mut self) -> &mut Segment {
893        let pass = self.pass;
894        let slice = self.slice;
895        self.compositor().segments[pass as usize]
896            .get_mut(&slice)
897            .unwrap()
898    }
899
900    #[inline]
901    pub fn compositor(&mut self) -> &mut Compositor {
902        match self.index {
903            0 => self.window,
904            1 => self.layer0,
905            2 => self.layer1,
906            _ => panic!("Illegal compositor index!"),
907        }
908    }
909
910    #[inline]
911    pub fn append_data(
912        &mut self,
913        pos: PxPoint,
914        dim: PxDim,
915        uv: PxPoint,
916        uvdim: PxDim,
917        color: u32,
918        rotation: f32,
919        tex: u8,
920        raw: bool,
921    ) -> u32 {
922        // I really wish rust had partial borrows
923        let compositor = match self.index {
924            0 => &mut self.window,
925            1 => &mut self.layer0,
926            2 => &mut self.layer1,
927            _ => panic!("Illegal compositor index!"),
928        };
929        compositor.append_internal(
930            self.clipstack,
931            self.surface_dim,
932            self.offset,
933            pos,
934            dim,
935            uv,
936            uvdim,
937            color,
938            rotation,
939            tex,
940            self.pass,
941            self.slice,
942            raw,
943            false,
944        )
945    }
946
947    #[inline]
948    pub(crate) fn append_layer(&mut self, layer: &Layer, parent_pos: PxPoint, uv: PxBox) -> u32 {
949        // I really wish rust had partial borrows
950        let compositor = match self.index {
951            0 => &mut self.window,
952            1 => &mut self.layer0,
953            2 => &mut self.layer1,
954            _ => panic!("Illegal compositor index!"),
955        };
956        compositor.append_internal(
957            self.clipstack,
958            self.surface_dim,
959            self.offset,
960            layer.dest.topleft() + parent_pos.to_vector(),
961            layer.dest.dim(),
962            uv.min.to_f32().to_array().into(),
963            uv.size().to_f32().to_array().into(),
964            layer.color.rgba,
965            layer.rotation,
966            0,
967            self.pass,
968            self.slice,
969            false,
970            true,
971        )
972    }
973
974    #[inline]
975    pub fn preprocessed(&mut self, mut data: Data) -> u32 {
976        data.pos.0[0] += self.offset.x;
977        data.pos.0[1] += self.offset.y;
978        data.flags = DataFlags::from_bits(data.flags).into();
979        let pass = self.pass;
980        let slice = self.slice;
981        self.compositor().preprocessed(data, pass, slice)
982    }
983
984    #[inline]
985    pub fn defer(&mut self, f: impl FnOnce(&Driver, &mut Data) + Send + Sync + 'static) {
986        let clip = self.current_clip();
987        let offset = self.offset;
988        let segment = self.segment();
989        let region = segment.regions.last_mut().unwrap();
990        if region.end == u32::MAX {
991            panic!(
992                "Still processing a compute operation! Finish it by calling set_compute_buffer() first."
993            );
994        }
995
996        let idx = region.end;
997        segment.data.push(Default::default());
998        segment.defer.insert(idx, (Box::new(f), clip, offset));
999        region.end += 1;
1000    }
1001
1002    pub fn append_custom(
1003        &mut self,
1004        f: impl FnMut(&Driver, &mut wgpu::RenderPass<'_>, PxVector) + Send + Sync + 'static,
1005    ) {
1006        let index = self.segment().regions.last().unwrap().end;
1007        if index == u32::MAX {
1008            panic!(
1009                "Still processing a compute operation! Finish it by calling set_compute_buffer() first."
1010            );
1011        }
1012        let offset = self.offset;
1013        self.segment().custom.push((index, Box::new(f), offset));
1014    }
1015
1016    /// Returns the GPU buffer and the current offset, which allows a compute
1017    /// shader to accumulate commands in the GPU buffer directly, provided
1018    /// it calls set_compute_buffer afterwards with the command count.
1019    /// Attempting to insert a non-GPU command before calling set_compute_buffer
1020    /// will panic.
1021    pub fn get_compute_buffer(&mut self) -> (&Buffer, u32) {
1022        let offset = self.segment().regions.last().unwrap().end;
1023        if offset == u32::MAX {
1024            panic!(
1025                "Still processing a compute operation! Finish it by calling set_compute_buffer() first."
1026            );
1027        }
1028        self.segment().regions.push(offset..u32::MAX);
1029        (&self.segment().buffer, offset)
1030    }
1031
1032    /// After executing a compute shader that added a series of compositor
1033    /// commands to the command buffer, this must be called with the number
1034    /// of commands that were contiguously inserted into the buffer.
1035    pub fn set_compute_buffer(&mut self, count: u32) {
1036        let region = self.segment().regions.last_mut().unwrap();
1037        region.start += count;
1038        region.end = region.start;
1039    }
1040
1041    pub fn reserve(&mut self, driver: &Driver) {
1042        let pass = self.pass;
1043        let slice = self.slice;
1044        self.compositor().reserve(driver, pass, slice);
1045    }
1046}
1047
1048#[bitfield_struct::bitfield(u32)]
1049pub struct DataFlags {
1050    #[bits(16)]
1051    pub clip: u16,
1052    #[bits(8)]
1053    pub tex: u8,
1054    #[bits(6)]
1055    pub __: u8,
1056    pub layer: bool,
1057    pub raw: bool,
1058}
1059
1060// Renderdoc Format:
1061// struct Data {
1062// 	float pos[2];
1063// 	float dim[2];
1064//  float uv[2];
1065//  float uvdim[2];
1066// 	uint32_t color;
1067// 	float rotation;
1068// 	uint32_t texclip;
1069//  char padding[4];
1070// };
1071// Data d[];
1072
1073#[repr(C)]
1074#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::NoUninit)]
1075pub struct Data {
1076    pub pos: Vec2f,
1077    pub dim: Vec2f,
1078    pub uv: Vec2f,
1079    pub uvdim: Vec2f,
1080    pub color: u32, // Encoded as a non-linear, non-premultiplied sRGB32 color
1081    pub rotation: f32,
1082    pub flags: u32,
1083    pub _padding: [u8; 4], // We have to manually specify this to satisfy bytemuck
1084}
1085
1086static_assertions::const_assert_eq!(std::mem::size_of::<Data>(), 48);
1087
1088// Our shader will assemble a rotation based on this matrix, but transposed:
1089// [ cos(r) -sin(r) 0 (x - x*cos(r) + y*sin(r)) ]
1090// [ sin(r)  cos(r) 0 (y - x*sin(r) - y*cos(r)) ]
1091// [ 0       0      1 0 ]
1092// [ 0       0      0 1 ] ^ -1