Skip to main content

pax_gpu/render_backend/
mod.rs

1#[cfg(target_arch = "wasm32")]
2mod browser_canvas;
3pub(crate) mod capture;
4#[cfg(feature = "shaders")]
5mod effects;
6use anyhow::anyhow;
7use bytemuck::Pod;
8use std::cell::RefCell;
9use std::collections::{HashMap, HashSet};
10#[cfg(any(target_os = "ios", target_os = "macos"))]
11use std::ffi::c_void;
12use std::rc::Rc;
13use std::sync::{Arc, Mutex};
14
15use lyon::lyon_tessellation::VertexBuffers;
16use wgpu::{
17    util::{DeviceExt, StagingBelt},
18    BindGroup, BindGroupLayout, BufferUsages, CommandBuffer, CompositeAlphaMode, Device,
19    IndexFormat, PresentMode, RenderPipeline, SurfaceConfiguration, SurfaceTexture, Texture,
20    TextureFormat, TextureFormatFeatureFlags, TextureUsages, TextureView,
21};
22
23pub(crate) mod alpha_mask;
24pub mod data;
25mod gpu_resources;
26pub(crate) mod mesh_paint;
27pub mod stencil;
28mod texture;
29mod transform_pages;
30use transform_pages::{transform_runs, TransformPages, TransformRun, TRANSFORMS_PER_PAGE};
31
32#[cfg(all(test, target_os = "macos"))]
33mod retained_clip_tests;
34
35#[cfg(all(test, target_os = "macos"))]
36mod shared_texture_tests;
37
38pub(crate) use texture::CachedTextureResource;
39
40use data::{GpuGlobals, GpuPrimitive, GpuSceneLighting, GpuVertex};
41
42use crate::{
43    render_backend::texture::{
44        corners_to_texture_vertices, ImageTextureCache, RetainedImageResource,
45        TexturePipelineResources, TextureRenderer,
46    },
47    Box2D, Transform2D,
48};
49
50use self::{
51    data::{GpuColor, GpuGradient, GpuMaterial, GpuTransform},
52    gpu_resources::create_multisampled_framebuffer,
53    stencil::{StencilPipelineResources, StencilRenderer},
54};
55
56const TRANSPARENT_CLEAR_COLOR: wgpu::Color = wgpu::Color {
57    r: 0.0,
58    g: 0.0,
59    b: 0.0,
60    a: 0.0,
61};
62
63const OPAQUE_FALLBACK_CLEAR_COLOR: wgpu::Color = wgpu::Color {
64    r: 1.0,
65    g: 1.0,
66    b: 1.0,
67    a: 1.0,
68};
69const STAGING_BELT_CHUNK_SIZE: wgpu::BufferAddress = 256 * 1024;
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72struct VectorPipelineKey {
73    format: TextureFormat,
74    sample_count: u32,
75}
76
77/// GPU device, pipelines, and a weak index of resident image textures shared by sibling surfaces.
78pub struct GpuContext {
79    instance: wgpu::Instance,
80    adapter: wgpu::Adapter,
81    device: wgpu::Device,
82    queue: wgpu::Queue,
83    max_surface_dimension: u32,
84    primitive_bind_group_layout: BindGroupLayout,
85    alpha_mask_layout: BindGroupLayout,
86    mesh_paint_layout: BindGroupLayout,
87    transform_layout: BindGroupLayout,
88    vector_pipelines: RefCell<HashMap<VectorPipelineKey, Rc<RenderPipeline>>>,
89    texture_pipelines: RefCell<HashMap<VectorPipelineKey, TexturePipelineResources>>,
90    image_textures: RefCell<ImageTextureCache>,
91    stencil_pipelines: RefCell<HashMap<VectorPipelineKey, StencilPipelineResources>>,
92}
93
94pub type SharedGpuContext = Rc<GpuContext>;
95
96impl GpuContext {
97    async fn new(
98        instance: wgpu::Instance,
99        compatible_surface: &wgpu::Surface<'_>,
100        _config: &RenderConfig,
101    ) -> Result<SharedGpuContext, anyhow::Error> {
102        let adapter = instance
103            .request_adapter(&wgpu::RequestAdapterOptions {
104                power_preference: default_power_preference(),
105                compatible_surface: Some(compatible_surface),
106                force_fallback_adapter: false,
107            })
108            .await
109            .map_err(|_| anyhow!("couldn't find adapter"))?;
110        let adapter_info = adapter.get_info();
111        log::info!(
112            "render backend: using {:?} adapter \"{}\"",
113            adapter_info.backend,
114            adapter_info.name
115        );
116        // Keep wgpu's conservative downlevel browser preset as the common WebGPU baseline. The
117        // preset name reflects its upstream compatibility tier; it does not enable a GL backend.
118        #[cfg(target_arch = "wasm32")]
119        let required_limits = wgpu::Limits {
120            // Interrupted paint mixtures have a variable number of endpoints.
121            max_storage_buffers_per_shader_stage: 1,
122            max_storage_buffer_binding_size: 128 * 1024 * 1024,
123            ..wgpu::Limits::downlevel_webgl2_defaults()
124        };
125        #[cfg(not(target_arch = "wasm32"))]
126        let required_limits = adapter.limits();
127        #[cfg(test)]
128        let required_limits = _config.required_limits.clone().unwrap_or(required_limits);
129        let (device, queue) = adapter
130            .request_device(&wgpu::DeviceDescriptor {
131                label: None,
132                required_features: wgpu::Features::default(),
133                required_limits,
134                experimental_features: wgpu::ExperimentalFeatures::disabled(),
135                memory_hints: wgpu::MemoryHints::Performance,
136                trace: wgpu::Trace::Off,
137            })
138            .await
139            .expect("couldn't find device");
140        let max_surface_dimension = device.limits().max_texture_dimension_2d.max(1);
141        log::info!(
142            "render backend: max surface dimension {}",
143            max_surface_dimension
144        );
145        let primitive_bind_group_layout = create_primitive_bind_group_layout(&device);
146        let alpha_mask_layout = alpha_mask::sampling_layout(&device);
147        let mesh_paint_layout = mesh_paint::sampling_layout(&device);
148        let transform_layout = transform_pages::layout(&device);
149
150        Ok(Rc::new(Self {
151            instance,
152            adapter,
153            device,
154            queue,
155            max_surface_dimension,
156            primitive_bind_group_layout,
157            alpha_mask_layout,
158            mesh_paint_layout,
159            transform_layout,
160            vector_pipelines: RefCell::new(HashMap::new()),
161            texture_pipelines: RefCell::new(HashMap::new()),
162            image_textures: RefCell::new(ImageTextureCache::default()),
163            stencil_pipelines: RefCell::new(HashMap::new()),
164        }))
165    }
166
167    fn vector_pipeline(&self, format: TextureFormat, sample_count: u32) -> Rc<RenderPipeline> {
168        let key = VectorPipelineKey {
169            format,
170            sample_count,
171        };
172        if let Some(pipeline) = self.vector_pipelines.borrow().get(&key) {
173            return Rc::clone(pipeline);
174        }
175
176        let pipeline = Rc::new(RenderBackend::create_pipeline(
177            &self.device,
178            format,
179            sample_count,
180            &self.primitive_bind_group_layout,
181            &self.alpha_mask_layout,
182            &self.mesh_paint_layout,
183            &self.transform_layout,
184        ));
185        self.vector_pipelines
186            .borrow_mut()
187            .insert(key, Rc::clone(&pipeline));
188        pipeline
189    }
190
191    fn texture_pipeline_resources(
192        &self,
193        format: TextureFormat,
194        sample_count: u32,
195    ) -> TexturePipelineResources {
196        let key = VectorPipelineKey {
197            format,
198            sample_count,
199        };
200        if let Some(resources) = self.texture_pipelines.borrow().get(&key) {
201            return resources.clone();
202        }
203
204        let resources = TextureRenderer::create_pipeline_resources(
205            &self.device,
206            format,
207            sample_count,
208            &self.alpha_mask_layout,
209        );
210        self.texture_pipelines
211            .borrow_mut()
212            .insert(key, resources.clone());
213        resources
214    }
215
216    fn stencil_pipeline_resources(
217        &self,
218        format: TextureFormat,
219        sample_count: u32,
220    ) -> StencilPipelineResources {
221        let key = VectorPipelineKey {
222            format,
223            sample_count,
224        };
225        if let Some(resources) = self.stencil_pipelines.borrow().get(&key) {
226            return resources.clone();
227        }
228
229        let resources =
230            StencilRenderer::create_pipeline_resources(&self.device, sample_count, format);
231        self.stencil_pipelines
232            .borrow_mut()
233            .insert(key, resources.clone());
234        resources
235    }
236
237    pub(crate) fn submit_command_buffers<I>(&self, command_buffers: I)
238    where
239        I: IntoIterator<Item = CommandBuffer>,
240    {
241        self.queue.submit(command_buffers);
242    }
243}
244
245fn surface_clear_color(alpha_mode: CompositeAlphaMode) -> wgpu::Color {
246    if alpha_mode == CompositeAlphaMode::Opaque {
247        // Opaque browser fallbacks expose the clear RGB directly; keep the old white default there.
248        OPAQUE_FALLBACK_CLEAR_COLOR
249    } else {
250        // Alpha-capable surfaces must clear to transparent black so translucent geometry does not
251        // accumulate white RGB in otherwise-transparent tile regions.
252        TRANSPARENT_CLEAR_COLOR
253    }
254}
255
256fn default_power_preference() -> wgpu::PowerPreference {
257    #[cfg(any(target_os = "ios", target_os = "macos"))]
258    {
259        wgpu::PowerPreference::HighPerformance
260    }
261
262    #[cfg(not(any(target_os = "ios", target_os = "macos")))]
263    {
264        wgpu::PowerPreference::LowPower
265    }
266}
267
268/// GPU resource sizing and initial surface configuration.
269pub struct RenderConfig {
270    #[cfg(test)]
271    required_limits: Option<wgpu::Limits>,
272    pub debug: bool,
273    index_buffer_size: u64,
274    vertex_buffer_size: u64,
275    primitive_buffer_size: u64,
276    colors_buffer_size: u64,
277    gradients_buffer_size: u64,
278    materials_buffer_size: u64,
279    transforms_buffer_size: u64,
280    clip_transforms_buffer_size: u64,
281    pub initial_width: u32,
282    pub initial_height: u32,
283    pub initial_dpr: [f32; 2],
284    prefer_browser_premultiplied_alpha: bool,
285}
286
287pub(crate) const MAX_BATCH_PRIMITIVES: usize = 512;
288pub(crate) const MAX_BATCH_COLORS: usize = 512;
289pub(crate) const MAX_BATCH_GRADIENTS: usize = 64;
290pub(crate) const MAX_BATCH_MATERIALS: usize = 512;
291pub(crate) const MAX_BATCH_TRANSFORMS: usize = TRANSFORMS_PER_PAGE;
292
293impl RenderConfig {
294    /// Construct default buffer capacities for an initial surface size.
295    pub fn new(_debug: bool, width: u32, height: u32, dpr: [f32; 2]) -> Self {
296        Self {
297            #[cfg(test)]
298            required_limits: None,
299            debug: false,
300            index_buffer_size: 2 << 12,
301            vertex_buffer_size: 2 << 12,
302            primitive_buffer_size: MAX_BATCH_PRIMITIVES as u64,
303            colors_buffer_size: MAX_BATCH_COLORS as u64,
304            gradients_buffer_size: MAX_BATCH_GRADIENTS as u64,
305            materials_buffer_size: MAX_BATCH_MATERIALS as u64,
306            transforms_buffer_size: TRANSFORMS_PER_PAGE as u64,
307            clip_transforms_buffer_size: TRANSFORMS_PER_PAGE as u64,
308            initial_width: width,
309            initial_height: height,
310            initial_dpr: dpr,
311            prefer_browser_premultiplied_alpha: false,
312        }
313    }
314
315    /// Request premultiplied browser canvas alpha for WebGPU surfaces.
316    ///
317    /// wgpu's web backend currently reports only `Opaque` alpha in surface capabilities, but its
318    /// configure path accepts `PreMultiplied` and maps it to `GPUCanvasAlphaMode::Premultiplied`.
319    /// Pax needs that for transparent browser-owned scroller islands.
320    pub fn with_browser_premultiplied_alpha(mut self, enabled: bool) -> Self {
321        self.prefer_browser_premultiplied_alpha = enabled;
322        self
323    }
324}
325
326#[cfg(target_arch = "wasm32")]
327fn sync_browser_canvas_backing_size(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) {
328    if canvas.width() != width {
329        canvas.set_width(width);
330    }
331    if canvas.height() != height {
332        canvas.set_height(height);
333    }
334}
335
336fn next_capacity(required: usize) -> u64 {
337    required.max(1).next_power_of_two() as u64
338}
339
340fn write_u16_buffer_padded(queue: &wgpu::Queue, buffer: &wgpu::Buffer, data: &[u16]) {
341    if data.len() % 2 == 0 {
342        queue.write_buffer(buffer, 0, bytemuck::cast_slice(data));
343        return;
344    }
345
346    let mut padded = Vec::with_capacity(data.len() + 1);
347    padded.extend_from_slice(data);
348    padded.push(0);
349    queue.write_buffer(buffer, 0, bytemuck::cast_slice(&padded));
350}
351
352fn write_staged_buffer(
353    staging_belt: &mut StagingBelt,
354    encoder: &mut wgpu::CommandEncoder,
355    buffer: &wgpu::Buffer,
356    bytes: &[u8],
357) {
358    if bytes.is_empty() {
359        return;
360    }
361
362    let size = wgpu::BufferSize::new(bytes.len() as wgpu::BufferAddress)
363        .expect("staged buffer writes must be non-empty");
364    let mut view = staging_belt.write_buffer(encoder, buffer, 0, size);
365    view.copy_from_slice(bytes);
366}
367
368fn physical_scissor_rect(
369    scissor: Option<ScissorRect>,
370    dpr: [f32; 2],
371    origin: [f32; 2],
372    surface_width: u32,
373    surface_height: u32,
374) -> Option<(u32, u32, u32, u32)> {
375    let Some(scissor) = scissor else {
376        return Some((0, 0, surface_width.max(1), surface_height.max(1)));
377    };
378
379    let scissor = ScissorRect {
380        min_x: scissor.min_x - origin[0],
381        min_y: scissor.min_y - origin[1],
382        max_x: scissor.max_x - origin[0],
383        max_y: scissor.max_y - origin[1],
384    };
385    let dpr_x = dpr[0].max(f32::EPSILON);
386    let dpr_y = dpr[1].max(f32::EPSILON);
387    let min_x = (scissor.min_x.min(scissor.max_x) * dpr_x)
388        .floor()
389        .clamp(0.0, surface_width as f32) as u32;
390    let min_y = (scissor.min_y.min(scissor.max_y) * dpr_y)
391        .floor()
392        .clamp(0.0, surface_height as f32) as u32;
393    let max_x = (scissor.min_x.max(scissor.max_x) * dpr_x)
394        .ceil()
395        .clamp(0.0, surface_width as f32) as u32;
396    let max_y = (scissor.min_y.max(scissor.max_y) * dpr_y)
397        .ceil()
398        .clamp(0.0, surface_height as f32) as u32;
399    let width = max_x.saturating_sub(min_x);
400    let height = max_y.saturating_sub(min_y);
401    if width == 0 || height == 0 {
402        return None;
403    }
404
405    Some((min_x, min_y, width, height))
406}
407
408/// Low-level wgpu backend that owns surface, pipeline, and GPU buffers.
409pub struct RenderBackend<'w> {
410    context: SharedGpuContext,
411    //configuration
412    config: RenderConfig,
413    pub(crate) globals: GpuGlobals,
414
415    //gpu pipeline references
416    device: wgpu::Device,
417    queue: wgpu::Queue,
418    staging_belt: StagingBelt,
419    surface: wgpu::Surface<'w>,
420    surface_config: SurfaceConfiguration,
421    max_surface_dimension: u32,
422    pipeline: Rc<RenderPipeline>,
423    bind_group: BindGroup,
424
425    //buffers
426    mesh_rasterizer: mesh_paint::MeshRasterizer,
427    empty_mesh_page: mesh_paint::PaintPage,
428    pub(crate) alpha_mesh_pages: HashMap<u32, Rc<mesh_paint::PaintPage>>,
429    globals_buffer: wgpu::Buffer,
430    vertex_buffer: wgpu::Buffer,
431    index_buffer: wgpu::Buffer,
432    primitive_buffer: wgpu::Buffer,
433    transforms_buffer: wgpu::Buffer,
434    scene_transforms: TransformPages,
435    batch_transform_bind_group: BindGroup,
436    colors_buffer: wgpu::Buffer,
437    gradients_buffer: wgpu::Buffer,
438    materials_buffer: wgpu::Buffer,
439    lighting_buffer: wgpu::Buffer,
440
441    index_count: u64,
442
443    // plugins / extensions
444    texture_renderer: TextureRenderer,
445    stencil_renderer: StencilRenderer,
446    source_stencil: Option<StencilRenderer>,
447    source_domain: Option<(GpuGlobals, u32, u32)>,
448    pub(crate) alpha_masks: alpha_mask::AlphaMasks,
449    multisampled_target: Option<MultisampledTarget>,
450    sample_count: u32,
451    clear_color: wgpu::Color,
452    active_frame: Option<ActiveFrame>,
453    capture_target: Option<CaptureTarget>,
454    #[cfg(feature = "shaders")]
455    effects: Option<effects::Effects>,
456    retained_surfaces: Option<capture::RetainedSurfaces>,
457    pending_clear: bool,
458    pending_command_buffers: Vec<CommandBuffer>,
459    retained_encoder: Option<wgpu::CommandEncoder>,
460    staging_belt_pending_recall: bool,
461    needs_device_poll: bool,
462    pending_capture_ids: Vec<u32>,
463    completed_captures: Arc<Mutex<HashMap<u32, CapturedFrame>>>,
464    // Drop this last, after every surface/frame handle has released the canvas.
465    #[cfg(target_arch = "wasm32")]
466    browser_canvas: Option<browser_canvas::BrowserCanvasLease>,
467}
468
469struct ActiveFrame {
470    view: TextureView,
471    surface: SurfaceTexture,
472}
473
474struct MultisampledTarget {
475    _texture: Texture,
476    view: TextureView,
477}
478
479struct CaptureTarget {
480    texture: Texture,
481    view: TextureView,
482    multisampled_target: Option<MultisampledTarget>,
483    initialized: bool,
484}
485
486impl CaptureTarget {
487    fn color_attachment_views(&self) -> (&TextureView, Option<&TextureView>) {
488        if let Some(multisampled_target) = &self.multisampled_target {
489            (&multisampled_target.view, Some(&self.view))
490        } else {
491            (&self.view, None)
492        }
493    }
494}
495
496#[derive(Clone)]
497/// CPU-readable screenshot payload captured from a rendered frame.
498pub struct CapturedFrame {
499    pub width: u32,
500    pub height: u32,
501    pub rgba: Vec<u8>,
502}
503
504pub(crate) struct SharedRetainedVectorResource {
505    vertex_buffer: wgpu::Buffer,
506    index_buffer: wgpu::Buffer,
507    index_count: u32,
508    transform_runs: Vec<TransformRun>,
509    vertex_capacity: usize,
510    index_capacity: usize,
511    primitive_capacity: usize,
512    color_capacity: usize,
513    gradient_capacity: usize,
514    material_capacity: usize,
515    _primitive_buffer: wgpu::Buffer,
516    _colors_buffer: wgpu::Buffer,
517    _gradients_buffer: wgpu::Buffer,
518    _materials_buffer: wgpu::Buffer,
519}
520
521pub(crate) struct RetainedVectorResource {
522    bind_group: BindGroup,
523    pub(crate) mesh_bind_group: Option<BindGroup>,
524    shared: Rc<SharedRetainedVectorResource>,
525}
526
527impl RetainedVectorResource {
528    pub(crate) fn shared(&self) -> &Rc<SharedRetainedVectorResource> {
529        &self.shared
530    }
531
532    pub(crate) fn is_drawable(&self) -> bool {
533        self.shared.index_count > 0
534            && self.shared.vertex_capacity > 0
535            && self.shared.index_capacity > 0
536    }
537}
538
539#[derive(Clone, Copy, Debug, Default)]
540pub(crate) struct VectorResourceDirty {
541    pub geometry: bool,
542    pub primitives: bool,
543    pub transforms: bool,
544    pub fill: bool,
545}
546
547impl VectorResourceDirty {
548    pub fn any(self) -> bool {
549        self.geometry || self.primitives || self.transforms || self.fill
550    }
551}
552
553pub(crate) enum RetainedDraw<'a> {
554    #[cfg(feature = "shaders")]
555    Effect(u32),
556    Vector(&'a RetainedVectorResource),
557    Image {
558        texture: &'a CachedTextureResource,
559        draw: &'a RetainedImageDraw,
560    },
561}
562
563pub(crate) struct RetainedBatchRun<'a> {
564    pub alpha_mask: Option<u32>,
565    pub stencil_index: u32,
566    pub scissor: Option<ScissorRect>,
567    pub draws: Vec<RetainedDraw<'a>>,
568}
569
570#[derive(Clone, Copy, Debug, PartialEq)]
571pub(crate) struct ScissorRect {
572    pub min_x: f32,
573    pub min_y: f32,
574    pub max_x: f32,
575    pub max_y: f32,
576}
577
578pub(crate) struct PrimitiveBatch<'a> {
579    pub buffers: CpuBuffers,
580    pub segments: Vec<PrimitiveBatchSegment<'a>>,
581}
582
583pub(crate) struct PrimitiveBatchSegment<'a> {
584    pub alpha_mask: Option<u32>,
585    pub stencil_depth: u32,
586    pub clips: Vec<stencil::ClipDraw<'a>>,
587    pub scissor: Option<ScissorRect>,
588    pub index_start: u32,
589    pub index_count: u32,
590}
591
592struct PrimitiveBatchRenderPlan {
593    alpha_mask: Option<u32>,
594    stencil_sync: stencil::PreparedStencilSync,
595    stencil_reference: u32,
596    scissor: Option<(u32, u32, u32, u32)>,
597    index_start: u32,
598    index_count: u32,
599}
600
601// Two 12 KiB views preserve the 512-primitive batch without requiring a >16 KiB
602// uniform binding. They share one allocation and add no padding or upload work.
603const PRIMITIVES_PER_BINDING: usize = MAX_BATCH_PRIMITIVES / 2;
604fn primitive_binding(buffer: &wgpu::Buffer, bank: u64) -> wgpu::BindingResource<'_> {
605    let size = (PRIMITIVES_PER_BINDING * std::mem::size_of::<GpuPrimitive>()) as u64;
606    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
607        buffer,
608        offset: bank * size,
609        size: wgpu::BufferSize::new(size),
610    })
611}
612
613fn create_primitive_bind_group_layout(device: &Device) -> BindGroupLayout {
614    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
615        entries: &[
616            wgpu::BindGroupLayoutEntry {
617                binding: 0,
618                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
619                ty: wgpu::BindingType::Buffer {
620                    ty: wgpu::BufferBindingType::Uniform,
621                    has_dynamic_offset: false,
622                    min_binding_size: None,
623                },
624                count: None,
625            },
626            wgpu::BindGroupLayoutEntry {
627                binding: 1,
628                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
629                ty: wgpu::BindingType::Buffer {
630                    ty: wgpu::BufferBindingType::Uniform,
631                    has_dynamic_offset: false,
632                    min_binding_size: None,
633                },
634                count: None,
635            },
636            wgpu::BindGroupLayoutEntry {
637                binding: 2,
638                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
639                ty: wgpu::BindingType::Buffer {
640                    ty: wgpu::BufferBindingType::Uniform,
641                    has_dynamic_offset: false,
642                    min_binding_size: None,
643                },
644                count: None,
645            },
646            wgpu::BindGroupLayoutEntry {
647                binding: 3,
648                visibility: wgpu::ShaderStages::FRAGMENT,
649                ty: wgpu::BindingType::Buffer {
650                    ty: wgpu::BufferBindingType::Uniform,
651                    has_dynamic_offset: false,
652                    min_binding_size: None,
653                },
654                count: None,
655            },
656            wgpu::BindGroupLayoutEntry {
657                binding: 4,
658                visibility: wgpu::ShaderStages::FRAGMENT,
659                ty: wgpu::BindingType::Buffer {
660                    ty: wgpu::BufferBindingType::Storage { read_only: true },
661                    has_dynamic_offset: false,
662                    min_binding_size: None,
663                },
664                count: None,
665            },
666            wgpu::BindGroupLayoutEntry {
667                binding: 5,
668                visibility: wgpu::ShaderStages::FRAGMENT,
669                ty: wgpu::BindingType::Buffer {
670                    ty: wgpu::BufferBindingType::Uniform,
671                    has_dynamic_offset: false,
672                    min_binding_size: None,
673                },
674                count: None,
675            },
676            wgpu::BindGroupLayoutEntry {
677                binding: 6,
678                visibility: wgpu::ShaderStages::FRAGMENT,
679                ty: wgpu::BindingType::Buffer {
680                    ty: wgpu::BufferBindingType::Uniform,
681                    has_dynamic_offset: false,
682                    min_binding_size: None,
683                },
684                count: None,
685            },
686        ],
687        label: Some("bind_group_layout"),
688    })
689}
690
691pub(crate) struct RetainedImageDraw {
692    pub resource: RetainedImageResource,
693}
694
695impl<'w> RenderBackend<'w> {
696    fn rebuild_main_bind_group(&mut self) {
697        self.bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
698            layout: &self.context.primitive_bind_group_layout,
699            entries: &[
700                wgpu::BindGroupEntry {
701                    binding: 0,
702                    resource: self.globals_buffer.as_entire_binding(),
703                },
704                wgpu::BindGroupEntry {
705                    binding: 1,
706                    resource: primitive_binding(&self.primitive_buffer, 0),
707                },
708                wgpu::BindGroupEntry {
709                    binding: 2,
710                    resource: primitive_binding(&self.primitive_buffer, 1),
711                },
712                wgpu::BindGroupEntry {
713                    binding: 3,
714                    resource: self.colors_buffer.as_entire_binding(),
715                },
716                wgpu::BindGroupEntry {
717                    binding: 4,
718                    resource: self.gradients_buffer.as_entire_binding(),
719                },
720                wgpu::BindGroupEntry {
721                    binding: 5,
722                    resource: self.materials_buffer.as_entire_binding(),
723                },
724                wgpu::BindGroupEntry {
725                    binding: 6,
726                    resource: self.lighting_buffer.as_entire_binding(),
727                },
728            ],
729            label: Some("bind_group"),
730        });
731    }
732
733    fn resize_shared_buffers_if_needed(
734        &mut self,
735        required_indices: usize,
736        required_vertices: usize,
737        required_primitives: usize,
738        required_transforms: usize,
739        required_colors: usize,
740        required_gradients: usize,
741        required_materials: usize,
742    ) {
743        let mut rebind_main_group = false;
744
745        if required_indices > self.config.index_buffer_size as usize {
746            self.config.index_buffer_size = next_capacity(required_indices);
747            self.index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
748                label: Some("Index Buffer"),
749                size: self.config.index_buffer_size * std::mem::size_of::<u16>() as u64,
750                usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
751                mapped_at_creation: false,
752            });
753        }
754
755        if required_vertices > self.config.vertex_buffer_size as usize {
756            self.config.vertex_buffer_size = next_capacity(required_vertices);
757            self.vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
758                label: Some("Vertex Buffer"),
759                size: self.config.vertex_buffer_size * std::mem::size_of::<GpuVertex>() as u64,
760                usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
761                mapped_at_creation: false,
762            });
763        }
764
765        if required_primitives > self.config.primitive_buffer_size as usize {
766            self.config.primitive_buffer_size = next_capacity(required_primitives);
767            self.primitive_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
768                label: Some("Primitive Buffer"),
769                size: self.config.primitive_buffer_size
770                    * std::mem::size_of::<GpuPrimitive>() as u64,
771                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
772                mapped_at_creation: false,
773            });
774            rebind_main_group = true;
775        }
776
777        assert!(
778            required_transforms <= self.config.transforms_buffer_size as usize,
779            "transient batch exceeds one transform page"
780        );
781
782        if required_colors > self.config.colors_buffer_size as usize {
783            self.config.colors_buffer_size = next_capacity(required_colors);
784            self.colors_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
785                label: Some("Colors Buffer"),
786                size: self.config.colors_buffer_size * std::mem::size_of::<GpuColor>() as u64,
787                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
788                mapped_at_creation: false,
789            });
790            rebind_main_group = true;
791        }
792
793        if required_gradients > self.config.gradients_buffer_size as usize {
794            self.config.gradients_buffer_size = next_capacity(required_gradients);
795            self.gradients_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
796                label: Some("Gradients Buffer"),
797                size: self.config.gradients_buffer_size * std::mem::size_of::<GpuGradient>() as u64,
798                usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
799                mapped_at_creation: false,
800            });
801            rebind_main_group = true;
802        }
803
804        if required_materials > self.config.materials_buffer_size as usize {
805            self.config.materials_buffer_size = next_capacity(required_materials);
806            self.materials_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
807                label: Some("Materials Buffer"),
808                size: self.config.materials_buffer_size * std::mem::size_of::<GpuMaterial>() as u64,
809                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
810                mapped_at_creation: false,
811            });
812            rebind_main_group = true;
813        }
814
815        if rebind_main_group {
816            self.rebuild_main_bind_group();
817        }
818    }
819
820    #[cfg(target_arch = "wasm32")]
821    pub async fn to_canvas(
822        canvas: web_sys::HtmlCanvasElement,
823        config: RenderConfig,
824    ) -> Result<Self, anyhow::Error> {
825        let (backend, _) = Self::to_canvas_with_context(canvas, config, None).await?;
826        Ok(backend)
827    }
828
829    #[cfg(target_arch = "wasm32")]
830    pub async fn to_canvas_with_context(
831        canvas: web_sys::HtmlCanvasElement,
832        config: RenderConfig,
833        shared_context: Option<SharedGpuContext>,
834    ) -> Result<(Self, SharedGpuContext), anyhow::Error> {
835        let lease = browser_canvas::BrowserCanvasLease::acquire(canvas.clone());
836        sync_browser_canvas_backing_size(&canvas, config.initial_width, config.initial_height);
837        if let Some(context) = shared_context {
838            let surface_target = wgpu::SurfaceTarget::Canvas(canvas.clone());
839            let surface = context.instance.create_surface(surface_target)?;
840            let mut backend = Self::new_with_context(surface, Rc::clone(&context), config)?;
841            backend.browser_canvas = Some(lease);
842            return Ok((backend, context));
843        }
844
845        let instance = Self::new_browser_instance(config.debug).await;
846        let surface_target = wgpu::SurfaceTarget::Canvas(canvas.clone());
847        let surface = instance.create_surface(surface_target)?;
848        let context = GpuContext::new(instance, &surface, &config).await?;
849        let mut backend = Self::new_with_context(surface, Rc::clone(&context), config)?;
850        backend.browser_canvas = Some(lease);
851        Ok((backend, context))
852    }
853
854    #[cfg(not(target_arch = "wasm32"))]
855    pub async fn to_canvas(
856        _canvas: web_sys::HtmlCanvasElement,
857        _config: RenderConfig,
858    ) -> Result<Self, anyhow::Error> {
859        Err(anyhow!(
860            "canvas surfaces are only supported on wasm32 targets"
861        ))
862    }
863
864    #[cfg(not(target_arch = "wasm32"))]
865    pub async fn to_canvas_with_context(
866        _canvas: web_sys::HtmlCanvasElement,
867        _config: RenderConfig,
868        _shared_context: Option<SharedGpuContext>,
869    ) -> Result<(Self, SharedGpuContext), anyhow::Error> {
870        Err(anyhow!(
871            "canvas surfaces are only supported on wasm32 targets"
872        ))
873    }
874
875    #[cfg(any(target_os = "ios", target_os = "macos"))]
876    pub async unsafe fn to_core_animation_layer(
877        layer: *mut c_void,
878        config: RenderConfig,
879    ) -> Result<RenderBackend<'static>, anyhow::Error> {
880        let (backend, _) =
881            unsafe { Self::to_core_animation_layer_with_context(layer, config, None).await? };
882        Ok(backend)
883    }
884
885    #[cfg(any(target_os = "ios", target_os = "macos"))]
886    pub async unsafe fn to_core_animation_layer_with_context(
887        layer: *mut c_void,
888        config: RenderConfig,
889        shared_context: Option<SharedGpuContext>,
890    ) -> Result<(RenderBackend<'static>, SharedGpuContext), anyhow::Error> {
891        if let Some(context) = shared_context {
892            let surface = unsafe {
893                context
894                    .instance
895                    .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::CoreAnimationLayer(layer))?
896            };
897            let backend =
898                RenderBackend::<'static>::new_with_context(surface, Rc::clone(&context), config)?;
899            return Ok((backend, context));
900        }
901
902        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
903            backends: wgpu::Backends::all(),
904            flags: if config.debug {
905                wgpu::InstanceFlags::DEBUG
906            } else {
907                wgpu::InstanceFlags::default()
908            },
909            memory_budget_thresholds: Default::default(),
910            backend_options: Default::default(),
911        });
912        let surface = unsafe {
913            instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::CoreAnimationLayer(layer))?
914        };
915        let context = GpuContext::new(instance, &surface, &config).await?;
916        let backend =
917            RenderBackend::<'static>::new_with_context(surface, Rc::clone(&context), config)?;
918        Ok((backend, context))
919    }
920
921    pub async fn new(
922        surface: wgpu::Surface<'w>,
923        instance: wgpu::Instance,
924        config: RenderConfig,
925    ) -> Result<Self, anyhow::Error> {
926        let context = GpuContext::new(instance, &surface, &config).await?;
927        Self::new_with_context(surface, context, config)
928    }
929
930    pub fn new_with_context(
931        surface: wgpu::Surface<'w>,
932        context: SharedGpuContext,
933        config: RenderConfig,
934    ) -> Result<Self, anyhow::Error> {
935        let adapter = &context.adapter;
936        let device = context.device.clone();
937        let queue = context.queue.clone();
938        let staging_belt = StagingBelt::new(device.clone(), STAGING_BELT_CHUNK_SIZE);
939        let max_surface_dimension = context.max_surface_dimension;
940
941        let surface_caps = surface.get_capabilities(&adapter);
942        let surface_format = surface_caps
943            .formats
944            .iter()
945            .copied()
946            .find(|format| {
947                matches!(
948                    format,
949                    TextureFormat::Bgra8Unorm | TextureFormat::Rgba8Unorm
950                )
951            })
952            .or_else(|| {
953                surface_caps
954                    .formats
955                    .iter()
956                    .copied()
957                    .find(|format| format.is_srgb())
958            })
959            .or_else(|| {
960                surface_caps
961                    .formats
962                    .iter()
963                    .copied()
964                    .find(|format| *format == TextureFormat::Rgba16Float)
965            })
966            .or_else(|| surface_caps.formats.first().copied())
967            .ok_or_else(|| anyhow!("surface reported no compatible texture formats"))?;
968        log::info!(
969            "render backend: surface format {:?} (srgb={})",
970            surface_format,
971            surface_format.is_srgb()
972        );
973        let alpha_mode =
974            if cfg!(target_arch = "wasm32") && config.prefer_browser_premultiplied_alpha {
975                CompositeAlphaMode::PreMultiplied
976            } else {
977                [
978                    CompositeAlphaMode::PreMultiplied,
979                    CompositeAlphaMode::PostMultiplied,
980                    CompositeAlphaMode::Opaque,
981                ]
982                .into_iter()
983                .find(|mode| surface_caps.alpha_modes.contains(mode))
984                .or_else(|| surface_caps.alpha_modes.first().copied())
985                .ok_or_else(|| anyhow!("surface reported no compatible alpha modes"))?
986            };
987        #[cfg(target_arch = "wasm32")]
988        if alpha_mode == CompositeAlphaMode::Opaque {
989            log::debug!("render backend: browser surface only exposes opaque alpha");
990        }
991        let surface_format_features = adapter.get_texture_format_features(surface_format).flags;
992        #[cfg(target_arch = "wasm32")]
993        let stencil_format_features = {
994            let queried = adapter
995                .get_texture_format_features(wgpu::TextureFormat::Stencil8)
996                .flags;
997            if queried.is_empty() {
998                surface_format_features
999            } else {
1000                queried
1001            }
1002        };
1003        #[cfg(not(target_arch = "wasm32"))]
1004        let stencil_format_features = adapter
1005            .get_texture_format_features(wgpu::TextureFormat::Stencil8)
1006            .flags;
1007        let sample_count = select_sample_count(surface_format_features, stencil_format_features);
1008        let mut surface_usage = TextureUsages::RENDER_ATTACHMENT;
1009        if surface_caps.usages.contains(TextureUsages::COPY_SRC) {
1010            surface_usage |= TextureUsages::COPY_SRC;
1011        } else {
1012            #[cfg(target_arch = "wasm32")]
1013            log::debug!(
1014                "render backend: surface does not support COPY_SRC; screenshots will use mirror target"
1015            );
1016        }
1017        let initial_width = config.initial_width.max(1).min(max_surface_dimension);
1018        let initial_height = config.initial_height.max(1).min(max_surface_dimension);
1019        if initial_width != config.initial_width || initial_height != config.initial_height {
1020            log::warn!(
1021                "render backend: clamped initial surface size from {}x{} to {}x{} (max {})",
1022                config.initial_width,
1023                config.initial_height,
1024                initial_width,
1025                initial_height,
1026                max_surface_dimension
1027            );
1028        }
1029        let surface_config = SurfaceConfiguration {
1030            usage: surface_usage,
1031            format: surface_format,
1032            width: initial_width,
1033            height: initial_height,
1034            present_mode: PresentMode::Fifo,
1035            alpha_mode,
1036            view_formats: vec![],
1037            desired_maximum_frame_latency: 2, //TODO 1 for lower latency?
1038        };
1039        surface.configure(&device, &surface_config);
1040
1041        fn create_buffer<T: Default + Clone + Pod>(
1042            device: &Device,
1043            name: &str,
1044            size: u64,
1045            usage_flags: BufferUsages,
1046        ) -> (Vec<T>, wgpu::Buffer) {
1047            let data: Vec<T> = vec![T::default(); size as usize];
1048            let buffer_ref = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1049                label: Some(name),
1050                contents: bytemuck::cast_slice(&data),
1051                usage: usage_flags,
1052            });
1053            (data, buffer_ref)
1054        }
1055
1056        let globals = GpuGlobals {
1057            resolution: [initial_width as f32, initial_height as f32],
1058            dpr: config.initial_dpr,
1059            ..Default::default()
1060        };
1061        let (_, globals_buffer) = create_buffer::<GpuGlobals>(
1062            &device,
1063            "Primitive Buffer",
1064            1,
1065            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1066        );
1067
1068        let (_, vertex_buffer) = create_buffer::<GpuVertex>(
1069            &device,
1070            "Vertex Buffer",
1071            config.vertex_buffer_size,
1072            BufferUsages::VERTEX | BufferUsages::COPY_DST,
1073        );
1074        let (_, index_buffer) = create_buffer::<u16>(
1075            &device,
1076            "Index Buffer",
1077            config.index_buffer_size,
1078            BufferUsages::INDEX | BufferUsages::COPY_DST,
1079        );
1080        let (_, primitive_buffer) = create_buffer::<GpuPrimitive>(
1081            &device,
1082            "Primitive Buffer",
1083            config.primitive_buffer_size,
1084            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1085        );
1086        let (_, transforms_buffer) = create_buffer::<GpuTransform>(
1087            &device,
1088            "Transform Buffer",
1089            config.transforms_buffer_size,
1090            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1091        );
1092        let (_, clip_transforms_buffer) = create_buffer::<GpuTransform>(
1093            &device,
1094            "Clip Transform Buffer",
1095            config.clip_transforms_buffer_size,
1096            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1097        );
1098        let (_, colors_buffer) = create_buffer::<GpuColor>(
1099            &device,
1100            "Colors Buffer",
1101            config.colors_buffer_size,
1102            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1103        );
1104        let (_, gradients_buffer) = create_buffer::<GpuGradient>(
1105            &device,
1106            "Gradients Buffer",
1107            config.gradients_buffer_size,
1108            BufferUsages::STORAGE | BufferUsages::COPY_DST,
1109        );
1110        let (_, materials_buffer) = create_buffer::<GpuMaterial>(
1111            &device,
1112            "Materials Buffer",
1113            config.materials_buffer_size,
1114            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1115        );
1116        let (_, lighting_buffer) = create_buffer::<GpuSceneLighting>(
1117            &device,
1118            "Scene Lighting Buffer",
1119            1,
1120            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1121        );
1122
1123        let batch_transform_bind_group =
1124            transform_pages::bind_group(&device, &context.transform_layout, &transforms_buffer);
1125        let scene_transforms = TransformPages::new(context.transform_layout.clone());
1126        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1127            layout: &context.primitive_bind_group_layout,
1128            entries: &[
1129                wgpu::BindGroupEntry {
1130                    binding: 0,
1131                    resource: globals_buffer.as_entire_binding(),
1132                },
1133                wgpu::BindGroupEntry {
1134                    binding: 1,
1135                    resource: primitive_binding(&primitive_buffer, 0),
1136                },
1137                wgpu::BindGroupEntry {
1138                    binding: 2,
1139                    resource: primitive_binding(&primitive_buffer, 1),
1140                },
1141                wgpu::BindGroupEntry {
1142                    binding: 3,
1143                    resource: colors_buffer.as_entire_binding(),
1144                },
1145                wgpu::BindGroupEntry {
1146                    binding: 4,
1147                    resource: gradients_buffer.as_entire_binding(),
1148                },
1149                wgpu::BindGroupEntry {
1150                    binding: 5,
1151                    resource: materials_buffer.as_entire_binding(),
1152                },
1153                wgpu::BindGroupEntry {
1154                    binding: 6,
1155                    resource: lighting_buffer.as_entire_binding(),
1156                },
1157            ],
1158            label: Some("bind_group"),
1159        });
1160
1161        let pipeline = context.vector_pipeline(surface_config.format, sample_count);
1162
1163        let texture_renderer = TextureRenderer::with_pipeline_resources(
1164            &device,
1165            context.texture_pipeline_resources(surface_config.format, sample_count),
1166        );
1167        let stencil_renderer = StencilRenderer::with_pipeline_resources(
1168            &device,
1169            initial_width,
1170            initial_height,
1171            sample_count,
1172            &globals_buffer,
1173            &clip_transforms_buffer,
1174            context.stencil_pipeline_resources(surface_config.format, sample_count),
1175        );
1176
1177        let initial_width = initial_width;
1178        let initial_height = initial_height;
1179        let initial_dpr = config.initial_dpr;
1180        let alpha_masks = alpha_mask::AlphaMasks::new(
1181            &device,
1182            &queue,
1183            context.alpha_mask_layout.clone(),
1184            &context.mesh_paint_layout,
1185        );
1186        let mesh_rasterizer = mesh_paint::MeshRasterizer::new(&device);
1187        let empty_mesh_page =
1188            mesh_paint::PaintPage::new(&device, &context.mesh_paint_layout, [1, 1, 1]);
1189        let mut backend = Self {
1190            alpha_mesh_pages: HashMap::new(),
1191            mesh_rasterizer,
1192            empty_mesh_page,
1193            alpha_masks,
1194            context: Rc::clone(&context),
1195            texture_renderer,
1196            stencil_renderer,
1197            source_stencil: None,
1198            source_domain: None,
1199            surface,
1200            #[cfg(target_arch = "wasm32")]
1201            browser_canvas: None,
1202            device,
1203            queue,
1204            staging_belt,
1205            config,
1206            surface_config,
1207            max_surface_dimension,
1208            bind_group,
1209            pipeline,
1210            vertex_buffer,
1211            index_buffer,
1212            primitive_buffer,
1213            transforms_buffer,
1214            scene_transforms,
1215            batch_transform_bind_group,
1216            globals_buffer,
1217            colors_buffer,
1218            gradients_buffer,
1219            materials_buffer,
1220            lighting_buffer,
1221            globals,
1222            index_count: 0,
1223            multisampled_target: None,
1224            sample_count,
1225            clear_color: surface_clear_color(alpha_mode),
1226            active_frame: None,
1227            capture_target: None,
1228            #[cfg(feature = "shaders")]
1229            effects: None,
1230            retained_surfaces: None,
1231            pending_clear: false,
1232            pending_command_buffers: Vec::new(),
1233            retained_encoder: None,
1234            staging_belt_pending_recall: false,
1235            needs_device_poll: false,
1236            pending_capture_ids: Vec::new(),
1237            completed_captures: Arc::new(Mutex::new(HashMap::new())),
1238        };
1239        backend.globals.dpr = initial_dpr;
1240        backend.resize(initial_width, initial_height);
1241        Ok(backend)
1242    }
1243
1244    #[cfg(target_arch = "wasm32")]
1245    async fn new_browser_instance(debug: bool) -> wgpu::Instance {
1246        let descriptor = wgpu::InstanceDescriptor {
1247            backends: wgpu::Backends::BROWSER_WEBGPU,
1248            flags: if debug {
1249                wgpu::InstanceFlags::DEBUG
1250            } else {
1251                wgpu::InstanceFlags::default()
1252            },
1253            memory_budget_thresholds: Default::default(),
1254            backend_options: Default::default(),
1255        };
1256
1257        wgpu::util::new_instance_with_webgpu_detection(&descriptor).await
1258    }
1259
1260    fn create_pipeline(
1261        device: &Device,
1262        format: TextureFormat,
1263        sample_count: u32,
1264        primitive_bind_group_layout: &BindGroupLayout,
1265        alpha_mask_layout: &BindGroupLayout,
1266        mesh_paint_layout: &BindGroupLayout,
1267        transform_layout: &BindGroupLayout,
1268    ) -> RenderPipeline {
1269        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1270            label: Some("Shader"),
1271            source: wgpu::ShaderSource::Wgsl(
1272                concat!(
1273                    include_str!("radial_gradient.wgsl"),
1274                    "\n",
1275                    include_str!("geometry.wgsl")
1276                )
1277                .into(),
1278            ),
1279        });
1280        let render_pipeline_layout =
1281            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1282                label: Some("Render Pipeline Layout"),
1283                bind_group_layouts: &[
1284                    primitive_bind_group_layout,
1285                    alpha_mask_layout,
1286                    mesh_paint_layout,
1287                    transform_layout,
1288                ],
1289                immediate_size: 0,
1290            });
1291
1292        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1293            label: Some("Render Pipeline"),
1294            layout: Some(&render_pipeline_layout),
1295            vertex: wgpu::VertexState {
1296                module: &shader,
1297                entry_point: Some("vs_main"),
1298                buffers: &[GpuVertex::desc()],
1299                compilation_options: Default::default(),
1300            },
1301            fragment: Some(wgpu::FragmentState {
1302                module: &shader,
1303                entry_point: Some("fs_main"),
1304                targets: &[Some(wgpu::ColorTargetState {
1305                    format,
1306                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1307                    write_mask: wgpu::ColorWrites::ALL,
1308                })],
1309                compilation_options: Default::default(),
1310            }),
1311            primitive: wgpu::PrimitiveState {
1312                topology: wgpu::PrimitiveTopology::TriangleList,
1313                strip_index_format: None,
1314                front_face: wgpu::FrontFace::Ccw,
1315                cull_mode: None,
1316                polygon_mode: wgpu::PolygonMode::Fill,
1317                unclipped_depth: false,
1318                conservative: false,
1319            },
1320            depth_stencil: Some(wgpu::DepthStencilState {
1321                format: wgpu::TextureFormat::Stencil8,
1322                depth_write_enabled: false,
1323                depth_compare: wgpu::CompareFunction::Always,
1324                stencil: wgpu::StencilState {
1325                    front: wgpu::StencilFaceState {
1326                        compare: wgpu::CompareFunction::Equal,
1327                        fail_op: wgpu::StencilOperation::Keep,
1328                        depth_fail_op: wgpu::StencilOperation::Keep,
1329                        pass_op: wgpu::StencilOperation::Keep,
1330                    },
1331                    back: wgpu::StencilFaceState::IGNORE,
1332                    read_mask: !0,
1333                    write_mask: !0,
1334                },
1335                bias: Default::default(),
1336            }),
1337            multisample: wgpu::MultisampleState {
1338                count: sample_count,
1339                mask: !0,
1340                alpha_to_coverage_enabled: false,
1341            },
1342            multiview_mask: None,
1343            cache: None,
1344        })
1345    }
1346
1347    /// Current stencil clip depth.
1348    pub fn get_clip_depth(&mut self) -> u32 {
1349        let (_, depth) = self.stencil_renderer.get_stencil();
1350        depth
1351    }
1352
1353    /// Resize both the physical surface and logical viewport.
1354    pub fn resize(&mut self, width: u32, height: u32) {
1355        self.resize_surface(width, height);
1356        self.set_viewport(width as f32, height as f32, self.globals.dpr);
1357    }
1358
1359    /// Resize the backing surface, clamping to device limits.
1360    pub fn resize_surface(&mut self, width: u32, height: u32) {
1361        let max_dim = self.max_surface_dimension.max(1);
1362        let requested_width = width.max(1);
1363        let requested_height = height.max(1);
1364        let width = requested_width.min(max_dim);
1365        let height = requested_height.min(max_dim);
1366        if width != requested_width || height != requested_height {
1367            log::warn!(
1368                "render backend: clamped surface resize from {}x{} to {}x{} (max {})",
1369                requested_width,
1370                requested_height,
1371                width,
1372                height,
1373                max_dim
1374            );
1375        }
1376        self.active_frame = None;
1377        self.capture_target = None;
1378        // New backing attachments have undefined contents. Force the next render to clear before
1379        // translucent retained geometry blends over the target.
1380        self.pending_clear = true;
1381        self.pending_command_buffers.clear();
1382        self.retained_encoder = None;
1383        // Resize abandons encoded uploads. Forget their mask signatures and
1384        // staging allocations instead of recalling work that was never submitted.
1385        // Dropped WGPU handles remain alive for any older in-flight commands.
1386        self.alpha_masks.invalidate();
1387        self.reset_captures();
1388        self.staging_belt = StagingBelt::new(self.device.clone(), STAGING_BELT_CHUNK_SIZE);
1389        self.staging_belt_pending_recall = false;
1390        self.needs_device_poll = false;
1391        self.surface_config.width = width;
1392        self.surface_config.height = height;
1393        #[cfg(target_arch = "wasm32")]
1394        if let Some(canvas) = self.browser_canvas.as_ref() {
1395            sync_browser_canvas_backing_size(&canvas.canvas, width, height);
1396        }
1397        self.stencil_renderer.resize(&self.device, width, height);
1398        self.surface.configure(&self.device, &self.surface_config);
1399        self.multisampled_target = if self.sample_count > 1 {
1400            let (texture, view) = create_multisampled_framebuffer(
1401                &self.device,
1402                &self.surface_config,
1403                self.sample_count,
1404            );
1405            Some(MultisampledTarget {
1406                _texture: texture,
1407                view,
1408            })
1409        } else {
1410            None
1411        };
1412    }
1413
1414    /// Update logical viewport uniforms without reallocating the surface.
1415    pub fn set_viewport(&mut self, width: f32, height: f32, dpr: [f32; 2]) {
1416        self.globals.resolution = [width.max(1.0), height.max(1.0)];
1417        self.globals.dpr = dpr;
1418        self.queue.write_buffer(
1419            &self.globals_buffer,
1420            0,
1421            bytemuck::cast_slice(&[self.globals]),
1422        );
1423    }
1424
1425    fn finish_retained_encoding(&mut self) {
1426        if let Some(encoder) = self.retained_encoder.take() {
1427            self.pending_command_buffers.push(encoder.finish());
1428        }
1429    }
1430
1431    fn take_retained_encoder(&mut self) -> wgpu::CommandEncoder {
1432        // A large mixed scene can alternate thousands of stencil and color passes.
1433        // One encoder preserves their order without exhausting Metal's finite pool
1434        // of unsubmitted command buffers while wgpu prepares a queue submission.
1435        self.retained_encoder.take().unwrap_or_else(|| {
1436            self.device
1437                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1438                    label: Some("Retained scene encoder"),
1439                })
1440        })
1441    }
1442
1443    fn enqueue_command_buffer(&mut self, command_buffer: CommandBuffer) {
1444        self.finish_retained_encoding();
1445        self.pending_command_buffers.push(command_buffer);
1446    }
1447
1448    fn enqueue_staged_command_buffer(&mut self, encoder: wgpu::CommandEncoder) {
1449        self.staging_belt.finish();
1450        self.enqueue_command_buffer(encoder.finish());
1451        self.staging_belt_pending_recall = true;
1452        self.needs_device_poll = true;
1453    }
1454
1455    pub(crate) fn take_pending_command_buffers(&mut self) -> Vec<CommandBuffer> {
1456        self.finish_retained_encoding();
1457        std::mem::take(&mut self.pending_command_buffers)
1458    }
1459
1460    pub(crate) fn has_pending_submission_cleanup(&self) -> bool {
1461        self.staging_belt_pending_recall || self.needs_device_poll
1462    }
1463
1464    pub(crate) fn complete_submitted_work(&mut self) {
1465        if self.staging_belt_pending_recall {
1466            self.staging_belt.recall();
1467            self.staging_belt_pending_recall = false;
1468        }
1469        if self.needs_device_poll {
1470            let _ = self.device.poll(wgpu::PollType::Poll);
1471            self.needs_device_poll = false;
1472        }
1473    }
1474
1475    pub(crate) fn submit_command_buffers(&self, command_buffers: Vec<CommandBuffer>) {
1476        self.context.submit_command_buffers(command_buffers);
1477    }
1478
1479    pub(crate) fn submit_pending_commands(&mut self) {
1480        let command_buffers = self.take_pending_command_buffers();
1481        if !command_buffers.is_empty() {
1482            self.context.submit_command_buffers(command_buffers);
1483        }
1484        self.complete_submitted_work();
1485    }
1486
1487    /// Maximum texture dimension supported by the active adapter.
1488    pub fn max_surface_dimension(&self) -> u32 {
1489        self.max_surface_dimension
1490    }
1491
1492    pub fn shared_context_id(&self) -> usize {
1493        Rc::as_ptr(&self.context) as usize
1494    }
1495
1496    pub(crate) fn shared_context(&self) -> SharedGpuContext {
1497        Rc::clone(&self.context)
1498    }
1499
1500    pub(crate) fn request_screenshot_capture(&mut self, request_id: u32) {
1501        self.pending_capture_ids.push(request_id);
1502        if !self.surface_supports_copy_src() {
1503            self.ensure_capture_target();
1504        }
1505    }
1506
1507    pub(crate) fn take_screenshot_capture(&mut self, request_id: u32) -> Option<CapturedFrame> {
1508        self.completed_captures.lock().ok()?.remove(&request_id)
1509    }
1510
1511    pub(crate) fn prepare_mesh_page(
1512        &mut self,
1513        page: &mut Option<Rc<mesh_paint::PaintPage>>,
1514        inputs: &[mesh_paint::MeshInput],
1515        stats: &mut crate::ResourceChurnStats,
1516    ) {
1517        let mut encoder = self.device.create_command_encoder(&Default::default());
1518        let mut mesh_stats = mesh_paint::RasterStats::default();
1519        if let Err(error) = self.mesh_rasterizer.prepare_page(
1520            &self.device,
1521            &self.queue,
1522            &mut encoder,
1523            &self.context.mesh_paint_layout,
1524            page,
1525            inputs,
1526            self.globals.dpr,
1527            &mut mesh_stats,
1528        ) {
1529            *page = None;
1530            // Stable deduplication by failure category, without retaining every animated value.
1531            thread_local! { static REPORTED: RefCell<HashSet<String>> = RefCell::new(HashSet::new()); }
1532            REPORTED.with(|reported| {
1533                if reported.borrow_mut().insert(error.clone()) {
1534                    log::warn!("{error}; mesh paints no field");
1535                }
1536            });
1537            return;
1538        }
1539        stats.mesh_texture_allocations += mesh_stats.texture_allocations;
1540        stats.mesh_texture_bytes += mesh_stats.texture_bytes;
1541        stats.mesh_coefficient_upload_bytes += mesh_stats.coefficient_upload_bytes;
1542        stats.mesh_raster_passes += mesh_stats.raster_passes;
1543        stats.mesh_cache_hits += mesh_stats.cache_hits;
1544        stats.mesh_topology_allocations += mesh_stats.topology_allocations;
1545        if mesh_stats.raster_passes > 0 {
1546            self.enqueue_command_buffer(encoder.finish());
1547        }
1548    }
1549
1550    fn create_retained_bind_group(
1551        &self,
1552        primitive_buffer: &wgpu::Buffer,
1553        colors_buffer: &wgpu::Buffer,
1554        gradients_buffer: &wgpu::Buffer,
1555        materials_buffer: &wgpu::Buffer,
1556    ) -> BindGroup {
1557        self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1558            layout: &self.context.primitive_bind_group_layout,
1559            entries: &[
1560                wgpu::BindGroupEntry {
1561                    binding: 0,
1562                    resource: self.globals_buffer.as_entire_binding(),
1563                },
1564                wgpu::BindGroupEntry {
1565                    binding: 1,
1566                    resource: primitive_binding(&primitive_buffer, 0),
1567                },
1568                wgpu::BindGroupEntry {
1569                    binding: 2,
1570                    resource: primitive_binding(&primitive_buffer, 1),
1571                },
1572                wgpu::BindGroupEntry {
1573                    binding: 3,
1574                    resource: colors_buffer.as_entire_binding(),
1575                },
1576                wgpu::BindGroupEntry {
1577                    binding: 4,
1578                    resource: gradients_buffer.as_entire_binding(),
1579                },
1580                wgpu::BindGroupEntry {
1581                    binding: 5,
1582                    resource: materials_buffer.as_entire_binding(),
1583                },
1584                wgpu::BindGroupEntry {
1585                    binding: 6,
1586                    resource: self.lighting_buffer.as_entire_binding(),
1587                },
1588            ],
1589            label: Some("retained_bind_group"),
1590        })
1591    }
1592
1593    pub(crate) fn create_vector_resource_from_shared(
1594        &self,
1595        shared: Rc<SharedRetainedVectorResource>,
1596    ) -> RetainedVectorResource {
1597        RetainedVectorResource {
1598            mesh_bind_group: None,
1599            bind_group: self.create_retained_bind_group(
1600                &shared._primitive_buffer,
1601                &shared._colors_buffer,
1602                &shared._gradients_buffer,
1603                &shared._materials_buffer,
1604            ),
1605            shared,
1606        }
1607    }
1608
1609    pub(crate) fn create_shared_vector_resource(
1610        &self,
1611        buffers: &mut CpuBuffers,
1612        retained_primitives: &[GpuPrimitive],
1613    ) -> (Rc<SharedRetainedVectorResource>, u64) {
1614        let (vertices, indices, _, _, mut colors, gradients, mut materials) =
1615            aligned_cpu_buffers(buffers);
1616        let vertex_capacity = vertices.len();
1617        let index_capacity = indices.len();
1618        let primitive_capacity = retained_primitives.len();
1619        let color_capacity = colors.len();
1620        let gradient_capacity = gradients.len();
1621        let material_capacity = materials.len();
1622        let mut primitives = retained_primitives.to_vec();
1623        primitives.resize(
1624            self.config.primitive_buffer_size as usize,
1625            GpuPrimitive::default(),
1626        );
1627        colors.resize(self.config.colors_buffer_size as usize, GpuColor::default());
1628        materials.resize(
1629            self.config.materials_buffer_size as usize,
1630            GpuMaterial::default(),
1631        );
1632        let upload_bytes = (std::mem::size_of_val(vertices.as_slice())
1633            + std::mem::size_of_val(indices.as_slice())
1634            + std::mem::size_of_val(primitives.as_slice())
1635            + std::mem::size_of_val(colors.as_slice())
1636            + std::mem::size_of_val(gradients.as_slice())
1637            + std::mem::size_of_val(materials.as_slice())) as u64;
1638        let vertex_buffer = self
1639            .device
1640            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1641                label: Some("Retained Vertex Buffer"),
1642                contents: bytemuck::cast_slice(&vertices),
1643                usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
1644            });
1645        let index_buffer = self
1646            .device
1647            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1648                label: Some("Retained Index Buffer"),
1649                contents: bytemuck::cast_slice(&indices),
1650                usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
1651            });
1652        let primitive_buffer = self
1653            .device
1654            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1655                label: Some("Retained Primitive Buffer"),
1656                contents: bytemuck::cast_slice(&primitives),
1657                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1658            });
1659        let colors_buffer = self
1660            .device
1661            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1662                label: Some("Retained Color Buffer"),
1663                contents: bytemuck::cast_slice(&colors),
1664                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1665            });
1666        let gradients_buffer = self
1667            .device
1668            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1669                label: Some("Retained Gradient Buffer"),
1670                contents: bytemuck::cast_slice(&gradients),
1671                usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
1672            });
1673        let materials_buffer = self
1674            .device
1675            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1676                label: Some("Retained Material Buffer"),
1677                contents: bytemuck::cast_slice(&materials),
1678                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
1679            });
1680        (
1681            Rc::new(SharedRetainedVectorResource {
1682                vertex_buffer,
1683                index_buffer,
1684                index_count: buffers.geometry.indices.len() as u32,
1685                transform_runs: transform_runs(&buffers.geometry, retained_primitives),
1686                vertex_capacity,
1687                index_capacity,
1688                primitive_capacity,
1689                color_capacity,
1690                gradient_capacity,
1691                material_capacity,
1692                _primitive_buffer: primitive_buffer,
1693                _colors_buffer: colors_buffer,
1694                _gradients_buffer: gradients_buffer,
1695                _materials_buffer: materials_buffer,
1696            }),
1697            upload_bytes,
1698        )
1699    }
1700
1701    pub(crate) fn update_vector_resource(
1702        &self,
1703        resource: &mut RetainedVectorResource,
1704        buffers: &mut CpuBuffers,
1705        retained_primitives: &[GpuPrimitive],
1706        dirty: VectorResourceDirty,
1707    ) -> Option<u64> {
1708        let shared = Rc::get_mut(&mut resource.shared)?;
1709        let mut geometry = None;
1710        if dirty.geometry {
1711            let aligned = aligned_geometry_buffers(buffers);
1712            if aligned.0.len() > shared.vertex_capacity || aligned.1.len() > shared.index_capacity {
1713                return None;
1714            }
1715            geometry = Some(aligned);
1716        }
1717
1718        if dirty.primitives && retained_primitives.len() > shared.primitive_capacity {
1719            return None;
1720        }
1721
1722        let mut fill = None;
1723        if dirty.fill {
1724            let colors = aligned_pod_slice(&buffers.colors);
1725            let gradients = aligned_pod_slice(&buffers.gradients);
1726            let materials = aligned_pod_slice(&buffers.materials);
1727            if colors.len() > shared.color_capacity
1728                || gradients.len() > shared.gradient_capacity
1729                || materials.len() > shared.material_capacity
1730            {
1731                return None;
1732            }
1733            fill = Some((colors, gradients, materials));
1734        }
1735
1736        let mut upload_bytes = 0;
1737        if dirty.geometry {
1738            let (vertices, indices) =
1739                geometry.expect("aligned geometry should be available for dirty geometry");
1740            upload_bytes += std::mem::size_of_val(vertices.as_slice()) as u64;
1741            upload_bytes += std::mem::size_of_val(indices.as_slice()) as u64;
1742            self.queue
1743                .write_buffer(&shared.vertex_buffer, 0, bytemuck::cast_slice(&vertices));
1744            write_u16_buffer_padded(&self.queue, &shared.index_buffer, &indices);
1745        }
1746        if dirty.primitives {
1747            upload_bytes += std::mem::size_of_val(retained_primitives) as u64;
1748            self.queue.write_buffer(
1749                &shared._primitive_buffer,
1750                0,
1751                bytemuck::cast_slice(retained_primitives),
1752            );
1753        }
1754        if dirty.fill {
1755            let (colors, gradients, materials) =
1756                fill.expect("aligned fill should be available for dirty fill");
1757            upload_bytes += std::mem::size_of_val(colors.as_slice()) as u64;
1758            upload_bytes += std::mem::size_of_val(gradients.as_slice()) as u64;
1759            upload_bytes += std::mem::size_of_val(materials.as_slice()) as u64;
1760            self.queue
1761                .write_buffer(&shared._colors_buffer, 0, bytemuck::cast_slice(&colors));
1762            self.queue.write_buffer(
1763                &shared._gradients_buffer,
1764                0,
1765                bytemuck::cast_slice(&gradients),
1766            );
1767            self.queue.write_buffer(
1768                &shared._materials_buffer,
1769                0,
1770                bytemuck::cast_slice(&materials),
1771            );
1772        }
1773        if dirty.geometry || dirty.primitives {
1774            shared.transform_runs = transform_runs(&buffers.geometry, retained_primitives);
1775        }
1776        shared.index_count = buffers.geometry.indices.len() as u32;
1777        Some(upload_bytes)
1778    }
1779
1780    pub(crate) fn update_scene_transforms(&mut self, transforms: &[GpuTransform]) {
1781        self.scene_transforms
1782            .update(&self.device, &self.queue, transforms);
1783    }
1784
1785    pub(crate) fn update_scene_clip_transforms(&mut self, transforms: &[GpuTransform]) {
1786        self.stencil_renderer
1787            .update_transforms(&self.device, &self.queue, transforms);
1788    }
1789
1790    fn draw_transform_runs<'a>(
1791        &'a self,
1792        pass: &mut wgpu::RenderPass<'a>,
1793        resource: &'a RetainedVectorResource,
1794    ) {
1795        for run in &resource.shared.transform_runs {
1796            pass.set_bind_group(3, self.scene_transforms.bind_group(run.page), &[]);
1797            pass.draw_indexed(run.indices.clone(), 0, 0..1);
1798        }
1799    }
1800
1801    #[allow(dead_code)]
1802    pub(crate) fn draw_vector_resource(&mut self, resource: &RetainedVectorResource) {
1803        if !resource.is_drawable() {
1804            return;
1805        }
1806
1807        let load_op = self.take_color_load_op();
1808        self.ensure_active_frame();
1809        let (screen_texture, resolve_target) = self.current_color_attachment_views();
1810        let mut encoder = self
1811            .device
1812            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1813                label: Some("Retained Render Encoder"),
1814            });
1815
1816        {
1817            let (stencil_texture, stencil_index) = self.stencil_renderer.get_stencil();
1818            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1819                label: Some("Retained Render Pass"),
1820                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1821                    view: screen_texture,
1822                    depth_slice: None,
1823                    resolve_target,
1824                    ops: wgpu::Operations {
1825                        load: load_op,
1826                        store: wgpu::StoreOp::Store,
1827                    },
1828                })],
1829                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1830                    view: stencil_texture,
1831                    depth_ops: None,
1832                    stencil_ops: Some(wgpu::Operations {
1833                        load: wgpu::LoadOp::Load,
1834                        store: wgpu::StoreOp::Store,
1835                    }),
1836                }),
1837                timestamp_writes: None,
1838                occlusion_query_set: None,
1839                multiview_mask: None,
1840            });
1841            render_pass.set_pipeline(self.pipeline.as_ref());
1842            render_pass.set_bind_group(0, &resource.bind_group, &[]);
1843            render_pass.set_bind_group(
1844                2,
1845                resource
1846                    .mesh_bind_group
1847                    .as_ref()
1848                    .unwrap_or(&self.empty_mesh_page.bind_group),
1849                &[],
1850            );
1851            render_pass.set_bind_group(1, self.alpha_masks.bind_group(None), &[]);
1852            render_pass.set_vertex_buffer(0, resource.shared.vertex_buffer.slice(..));
1853            render_pass.set_stencil_reference(stencil_index);
1854            render_pass
1855                .set_index_buffer(resource.shared.index_buffer.slice(..), IndexFormat::Uint16);
1856            self.draw_transform_runs(&mut render_pass, resource);
1857        }
1858
1859        if self.should_render_capture_target() {
1860            self.ensure_capture_target();
1861            let capture_load_op = self.take_capture_color_load_op(load_op);
1862            let capture_target = self.capture_target.as_ref().unwrap();
1863            let (capture_texture, capture_resolve_target) = capture_target.color_attachment_views();
1864            let (stencil_texture, stencil_index) = self.stencil_renderer.get_stencil();
1865            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1866                label: Some("Retained Screenshot Mirror Pass"),
1867                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1868                    view: capture_texture,
1869                    depth_slice: None,
1870                    resolve_target: capture_resolve_target,
1871                    ops: wgpu::Operations {
1872                        load: capture_load_op,
1873                        store: wgpu::StoreOp::Store,
1874                    },
1875                })],
1876                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1877                    view: stencil_texture,
1878                    depth_ops: None,
1879                    stencil_ops: Some(wgpu::Operations {
1880                        load: wgpu::LoadOp::Load,
1881                        store: wgpu::StoreOp::Store,
1882                    }),
1883                }),
1884                timestamp_writes: None,
1885                occlusion_query_set: None,
1886                multiview_mask: None,
1887            });
1888            render_pass.set_pipeline(self.pipeline.as_ref());
1889            render_pass.set_bind_group(0, &resource.bind_group, &[]);
1890            render_pass.set_bind_group(
1891                2,
1892                resource
1893                    .mesh_bind_group
1894                    .as_ref()
1895                    .unwrap_or(&self.empty_mesh_page.bind_group),
1896                &[],
1897            );
1898            render_pass.set_bind_group(1, self.alpha_masks.bind_group(None), &[]);
1899            render_pass.set_vertex_buffer(0, resource.shared.vertex_buffer.slice(..));
1900            render_pass.set_stencil_reference(stencil_index);
1901            render_pass
1902                .set_index_buffer(resource.shared.index_buffer.slice(..), IndexFormat::Uint16);
1903            self.draw_transform_runs(&mut render_pass, resource);
1904        }
1905
1906        self.enqueue_command_buffer(encoder.finish());
1907    }
1908
1909    pub(crate) fn source_domain_fits(&self, margin: f32) -> bool {
1910        [self.surface_config.width, self.surface_config.height]
1911            .into_iter()
1912            .zip(self.globals.dpr)
1913            .all(|(size, dpr)| {
1914                let extent = size as f64 + 2.0 * ((margin * dpr).ceil() as f64 + 1.0);
1915                dpr.is_finite()
1916                    && dpr > 0.0
1917                    && extent.is_finite()
1918                    && extent <= self.max_surface_dimension as f64
1919            })
1920    }
1921
1922    pub(crate) fn begin_source_domain(&mut self, margin: f32) -> bool {
1923        assert!(self.source_domain.is_none());
1924        let dpr = self.globals.dpr;
1925        let gutter = [
1926            (margin * dpr[0]).ceil() + 1.0,
1927            (margin * dpr[1]).ceil() + 1.0,
1928        ];
1929        let extent = [
1930            self.surface_config.width as f64 + 2.0 * gutter[0] as f64,
1931            self.surface_config.height as f64 + 2.0 * gutter[1] as f64,
1932        ];
1933        if extent
1934            .iter()
1935            .any(|v| !v.is_finite() || *v > self.max_surface_dimension as f64)
1936        {
1937            log::error!("alpha-source feather domain exceeds GPU texture limits");
1938            return false;
1939        }
1940        let width = extent[0] as u32;
1941        let height = extent[1] as u32;
1942        let mut stencil = self.source_stencil.take().unwrap_or_else(|| {
1943            StencilRenderer::with_shared_transforms(
1944                &self.device,
1945                width,
1946                height,
1947                self.sample_count,
1948                &self.globals_buffer,
1949                &self.stencil_renderer,
1950                self.context
1951                    .stencil_pipeline_resources(self.surface_config.format, self.sample_count),
1952            )
1953        });
1954        stencil.resize(&self.device, width, height);
1955        std::mem::swap(&mut self.stencil_renderer, &mut stencil);
1956        self.source_stencil = Some(stencil);
1957        self.source_domain = Some((
1958            self.globals,
1959            self.surface_config.width,
1960            self.surface_config.height,
1961        ));
1962        self.surface_config.width = width;
1963        self.surface_config.height = height;
1964        self.globals.origin = [-gutter[0] / dpr[0], -gutter[1] / dpr[1]];
1965        self.globals.resolution = [width as f32 / dpr[0], height as f32 / dpr[1]];
1966        self.stage_capture_globals();
1967        true
1968    }
1969
1970    pub(crate) fn end_source_domain(&mut self) {
1971        let (globals, width, height) = self.source_domain.take().expect("source domain is active");
1972        self.globals = globals;
1973        self.surface_config.width = width;
1974        self.surface_config.height = height;
1975        std::mem::swap(
1976            &mut self.stencil_renderer,
1977            self.source_stencil.as_mut().unwrap(),
1978        );
1979        self.stage_capture_globals();
1980    }
1981
1982    fn stage_capture_globals(&mut self) {
1983        let mut encoder = self
1984            .device
1985            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1986                label: Some("Capture coordinate domain"),
1987            });
1988        write_staged_buffer(
1989            &mut self.staging_belt,
1990            &mut encoder,
1991            &self.globals_buffer,
1992            bytemuck::bytes_of(&self.globals),
1993        );
1994        self.stencil_renderer.encode_clear(&mut encoder);
1995        self.enqueue_staged_command_buffer(encoder);
1996    }
1997
1998    pub(crate) fn render_captured_alpha(
1999        &mut self,
2000        id: u32,
2001        signature: u64,
2002        key: capture::CaptureKey,
2003        feather: f32,
2004        parent: Option<u32>,
2005    ) {
2006        let (view, crop) = self
2007            .retained_surfaces
2008            .as_ref()
2009            .unwrap()
2010            .texture_view(key, [self.surface_config.width, self.surface_config.height]);
2011        let mut encoder = self
2012            .device
2013            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2014                label: Some("Captured alpha"),
2015            });
2016        if self.alpha_masks.render(
2017            &self.device,
2018            &mut encoder,
2019            &mut self.staging_belt,
2020            id,
2021            signature,
2022            &[],
2023            [self.surface_config.width, self.surface_config.height],
2024            self.globals,
2025            feather,
2026            parent,
2027            Some((&view, crop)),
2028            &self.empty_mesh_page.bind_group,
2029        ) {
2030            self.enqueue_staged_command_buffer(encoder);
2031        }
2032    }
2033
2034    pub(crate) fn render_alpha_mask(
2035        &mut self,
2036        id: u32,
2037        signature: u64,
2038        draws: &[alpha_mask::Draw],
2039        feather: f32,
2040        parent: Option<u32>,
2041        meshes: &[mesh_paint::MeshInput],
2042        stats: &mut crate::ResourceChurnStats,
2043    ) {
2044        let mut page = self.alpha_mesh_pages.remove(&id);
2045        self.prepare_mesh_page(&mut page, meshes, stats);
2046        if let Some(page) = page {
2047            self.alpha_mesh_pages.insert(id, page);
2048        }
2049        let mut encoder = self
2050            .device
2051            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2052                label: Some("Alpha mask encoder"),
2053            });
2054        let rendered = self.alpha_masks.render(
2055            &self.device,
2056            &mut encoder,
2057            &mut self.staging_belt,
2058            id,
2059            signature,
2060            draws,
2061            [
2062                self.surface_config.width.max(1),
2063                self.surface_config.height.max(1),
2064            ],
2065            self.globals,
2066            feather,
2067            parent,
2068            None,
2069            self.alpha_mesh_pages
2070                .get(&id)
2071                .map_or(&self.empty_mesh_page.bind_group, |page| &page.bind_group),
2072        );
2073        if rendered {
2074            self.enqueue_staged_command_buffer(encoder);
2075        }
2076    }
2077
2078    pub(crate) fn draw_retained_batch_runs(&mut self, runs: &[RetainedBatchRun<'_>]) {
2079        if runs.iter().all(|run| run.draws.is_empty()) {
2080            return;
2081        }
2082
2083        let physical_scissors: Vec<_> = runs
2084            .iter()
2085            .map(|run| {
2086                physical_scissor_rect(
2087                    run.scissor,
2088                    self.globals.dpr,
2089                    self.globals.origin,
2090                    self.surface_config.width,
2091                    self.surface_config.height,
2092                )
2093            })
2094            .collect();
2095        if runs
2096            .iter()
2097            .zip(physical_scissors.iter())
2098            .all(|(run, scissor)| run.draws.is_empty() || scissor.is_none())
2099        {
2100            return;
2101        }
2102
2103        let load_op = self.take_color_load_op();
2104        self.ensure_active_frame();
2105        let mut encoder = self.take_retained_encoder();
2106
2107        {
2108            let (screen_texture, resolve_target) = self.current_color_attachment_view_clones();
2109            let stencil_texture = self.stencil_renderer.stencil_view_clone();
2110            let depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
2111                view: &stencil_texture,
2112                depth_ops: None,
2113                stencil_ops: Some(wgpu::Operations {
2114                    load: wgpu::LoadOp::Load,
2115                    store: wgpu::StoreOp::Store,
2116                }),
2117            });
2118            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2119                label: Some("Retained Batch Runs Pass"),
2120                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2121                    view: &screen_texture,
2122                    depth_slice: None,
2123                    resolve_target: resolve_target.as_ref(),
2124                    ops: wgpu::Operations {
2125                        load: load_op,
2126                        store: wgpu::StoreOp::Store,
2127                    },
2128                })],
2129                depth_stencil_attachment,
2130                timestamp_writes: None,
2131                occlusion_query_set: None,
2132                multiview_mask: None,
2133            });
2134
2135            for (run, scissor) in runs.iter().zip(physical_scissors.iter()) {
2136                let Some(scissor) = scissor else {
2137                    continue;
2138                };
2139                if run.draws.is_empty() {
2140                    continue;
2141                }
2142                render_pass.set_stencil_reference(run.stencil_index);
2143                render_pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2144                render_pass.set_bind_group(1, self.alpha_masks.bind_group(run.alpha_mask), &[]);
2145                for draw in &run.draws {
2146                    match draw {
2147                        #[cfg(feature = "shaders")]
2148                        RetainedDraw::Effect(id) => {
2149                            if let Some(effects) = &self.effects {
2150                                effects.draw(*id, &mut render_pass);
2151                            }
2152                        }
2153                        RetainedDraw::Vector(resource) => {
2154                            if !resource.is_drawable() {
2155                                continue;
2156                            }
2157                            render_pass.set_pipeline(self.pipeline.as_ref());
2158                            render_pass.set_bind_group(0, &resource.bind_group, &[]);
2159                            render_pass.set_bind_group(
2160                                2,
2161                                resource
2162                                    .mesh_bind_group
2163                                    .as_ref()
2164                                    .unwrap_or(&self.empty_mesh_page.bind_group),
2165                                &[],
2166                            );
2167                            render_pass
2168                                .set_vertex_buffer(0, resource.shared.vertex_buffer.slice(..));
2169                            render_pass.set_index_buffer(
2170                                resource.shared.index_buffer.slice(..),
2171                                IndexFormat::Uint16,
2172                            );
2173                            self.draw_transform_runs(&mut render_pass, resource);
2174                        }
2175                        RetainedDraw::Image { texture, draw } => {
2176                            self.texture_renderer.draw_retained_image_in_pass(
2177                                &mut render_pass,
2178                                texture,
2179                                &draw.resource,
2180                            );
2181                        }
2182                    }
2183                }
2184            }
2185        }
2186
2187        if self.should_render_capture_target() {
2188            self.ensure_capture_target();
2189            let capture_load_op = self.take_capture_color_load_op(load_op);
2190            let capture_target = self.capture_target.as_ref().unwrap();
2191            let (capture_texture, capture_resolve_target) = capture_target.color_attachment_views();
2192            let stencil_texture = self.stencil_renderer.stencil_view_clone();
2193            let depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
2194                view: &stencil_texture,
2195                depth_ops: None,
2196                stencil_ops: Some(wgpu::Operations {
2197                    load: wgpu::LoadOp::Load,
2198                    store: wgpu::StoreOp::Store,
2199                }),
2200            });
2201            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2202                label: Some("Retained Batch Runs Screenshot Mirror Pass"),
2203                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2204                    view: capture_texture,
2205                    depth_slice: None,
2206                    resolve_target: capture_resolve_target,
2207                    ops: wgpu::Operations {
2208                        load: capture_load_op,
2209                        store: wgpu::StoreOp::Store,
2210                    },
2211                })],
2212                depth_stencil_attachment,
2213                timestamp_writes: None,
2214                occlusion_query_set: None,
2215                multiview_mask: None,
2216            });
2217            for (run, scissor) in runs.iter().zip(physical_scissors.iter()) {
2218                let Some(scissor) = scissor else {
2219                    continue;
2220                };
2221                if run.draws.is_empty() {
2222                    continue;
2223                }
2224                render_pass.set_stencil_reference(run.stencil_index);
2225                render_pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2226                render_pass.set_bind_group(1, self.alpha_masks.bind_group(run.alpha_mask), &[]);
2227                for draw in &run.draws {
2228                    match draw {
2229                        #[cfg(feature = "shaders")]
2230                        RetainedDraw::Effect(id) => {
2231                            if let Some(effects) = &self.effects {
2232                                effects.draw(*id, &mut render_pass);
2233                            }
2234                        }
2235                        RetainedDraw::Vector(resource) => {
2236                            if !resource.is_drawable() {
2237                                continue;
2238                            }
2239                            render_pass.set_pipeline(self.pipeline.as_ref());
2240                            render_pass.set_bind_group(0, &resource.bind_group, &[]);
2241                            render_pass.set_bind_group(
2242                                2,
2243                                resource
2244                                    .mesh_bind_group
2245                                    .as_ref()
2246                                    .unwrap_or(&self.empty_mesh_page.bind_group),
2247                                &[],
2248                            );
2249                            render_pass
2250                                .set_vertex_buffer(0, resource.shared.vertex_buffer.slice(..));
2251                            render_pass.set_index_buffer(
2252                                resource.shared.index_buffer.slice(..),
2253                                IndexFormat::Uint16,
2254                            );
2255                            self.draw_transform_runs(&mut render_pass, resource);
2256                        }
2257                        RetainedDraw::Image { texture, draw } => {
2258                            self.texture_renderer.draw_retained_image_in_pass(
2259                                &mut render_pass,
2260                                texture,
2261                                &draw.resource,
2262                            );
2263                        }
2264                    }
2265                }
2266            }
2267        }
2268
2269        self.retained_encoder = Some(encoder);
2270    }
2271
2272    pub(crate) fn sync_stencil_stack(&mut self, depth: u32, clips: &[stencil::ClipDraw<'_>]) {
2273        if !self.stencil_renderer.needs_stack_sync(depth, clips) {
2274            return;
2275        }
2276        let mut encoder = self.take_retained_encoder();
2277        self.stencil_renderer
2278            .encode_stencil_stack_sync(&self.device, &mut encoder, depth, clips);
2279        self.retained_encoder = Some(encoder);
2280    }
2281
2282    pub(crate) fn retain_stencil_resources(
2283        &mut self,
2284        active_signatures: &HashSet<u64>,
2285        active_clip_ids: &HashSet<u32>,
2286    ) {
2287        self.stencil_renderer
2288            .retain_cached_resources(active_signatures, active_clip_ids);
2289        if let Some(stencil) = &mut self.source_stencil {
2290            stencil.retain_cached_resources(active_signatures, active_clip_ids);
2291        }
2292    }
2293
2294    pub(crate) fn create_cached_texture(
2295        &self,
2296        identity: &str,
2297        version: u64,
2298        rgba: &[u8],
2299        rgba_width: u32,
2300        rgba_height: u32,
2301    ) -> (CachedTextureResource, bool) {
2302        self.texture_renderer.create_cached_texture(
2303            &self.device,
2304            &self.queue,
2305            &self.globals_buffer,
2306            &mut self.context.image_textures.borrow_mut(),
2307            identity,
2308            version,
2309            rgba,
2310            rgba_width,
2311            rgba_height,
2312        )
2313    }
2314
2315    pub(crate) fn create_image_draw(
2316        &self,
2317        image_key: String,
2318        transform: Transform2D,
2319        rect: Box2D,
2320        opacity: f32,
2321    ) -> RetainedImageDraw {
2322        let corners = transformed_corners(&rect, &transform);
2323        let verts = corners_to_texture_vertices(corners, opacity);
2324        let resource =
2325            self.texture_renderer
2326                .create_retained_image_resource(&self.device, image_key, verts);
2327        RetainedImageDraw { resource }
2328    }
2329
2330    #[allow(dead_code)]
2331    pub(crate) fn update_image_draw(
2332        &self,
2333        draw: &RetainedImageDraw,
2334        transform: Transform2D,
2335        rect: Box2D,
2336        opacity: f32,
2337    ) {
2338        let corners = transformed_corners(&rect, &transform);
2339        let verts = corners_to_texture_vertices(corners, opacity);
2340        self.texture_renderer
2341            .update_retained_image_resource(&self.queue, &draw.resource, verts);
2342    }
2343
2344    #[allow(dead_code)]
2345    pub(crate) fn draw_image_resource(
2346        &mut self,
2347        texture: &CachedTextureResource,
2348        draw: &RetainedImageDraw,
2349    ) {
2350        let clear_target = std::mem::take(&mut self.pending_clear);
2351        self.ensure_active_frame();
2352        let (screen_texture, resolve_target) = self.current_color_attachment_views();
2353        self.texture_renderer.draw_retained_image(
2354            &self.device,
2355            &self.queue,
2356            screen_texture,
2357            resolve_target,
2358            &self.stencil_renderer,
2359            clear_target,
2360            self.clear_color,
2361            texture,
2362            &draw.resource,
2363        );
2364        if self.should_render_capture_target() {
2365            self.ensure_capture_target();
2366            let capture_clear_target = self.take_capture_clear_target(clear_target);
2367            let capture_target = self.capture_target.as_ref().unwrap();
2368            let (capture_texture, capture_resolve_target) = capture_target.color_attachment_views();
2369            self.texture_renderer.draw_retained_image(
2370                &self.device,
2371                &self.queue,
2372                capture_texture,
2373                capture_resolve_target,
2374                &self.stencil_renderer,
2375                capture_clear_target,
2376                self.clear_color,
2377                texture,
2378                &draw.resource,
2379            );
2380        }
2381    }
2382
2383    fn write_buffers(&mut self, buffers: &mut CpuBuffers, encoder: &mut wgpu::CommandEncoder) {
2384        let CpuBuffers {
2385            geometry: ref mut geom,
2386            ref mut primitives,
2387            ref mut colors,
2388            ref mut gradients,
2389            ref mut materials,
2390            ref mut transforms,
2391            mesh_paints: _,
2392        } = buffers;
2393        //Add ghost triangles to follow COPY_BUFFER_ALIGNMENT requirement
2394        const ALIGNMENT: usize = 16;
2395        while geom.indices.len() * std::mem::size_of::<u16>() % ALIGNMENT != 0 {
2396            geom.indices.push(0);
2397            geom.indices.push(0);
2398            geom.indices.push(0);
2399        }
2400        while geom.vertices.len() * std::mem::size_of::<GpuVertex>() % ALIGNMENT != 0 {
2401            geom.vertices.push(GpuVertex::default());
2402        }
2403        while primitives.len() * std::mem::size_of::<GpuPrimitive>() % ALIGNMENT != 0 {
2404            primitives.push(GpuPrimitive::default());
2405        }
2406        while transforms.len() * std::mem::size_of::<GpuTransform>() % ALIGNMENT != 0 {
2407            transforms.push(GpuTransform::default());
2408        }
2409        while colors.len() * std::mem::size_of::<GpuColor>() % ALIGNMENT != 0 {
2410            colors.push(GpuColor::default());
2411        }
2412        while gradients.len() * std::mem::size_of::<GpuGradient>() % ALIGNMENT != 0 {
2413            gradients.push(GpuGradient::default());
2414        }
2415        while materials.len() * std::mem::size_of::<GpuMaterial>() % ALIGNMENT != 0 {
2416            materials.push(GpuMaterial::default());
2417        }
2418
2419        self.resize_shared_buffers_if_needed(
2420            geom.indices.len(),
2421            geom.vertices.len(),
2422            primitives.len(),
2423            transforms.len(),
2424            colors.len(),
2425            gradients.len(),
2426            materials.len(),
2427        );
2428
2429        write_staged_buffer(
2430            &mut self.staging_belt,
2431            encoder,
2432            &self.index_buffer,
2433            bytemuck::cast_slice(&geom.indices),
2434        );
2435        write_staged_buffer(
2436            &mut self.staging_belt,
2437            encoder,
2438            &self.vertex_buffer,
2439            bytemuck::cast_slice(&geom.vertices),
2440        );
2441        write_staged_buffer(
2442            &mut self.staging_belt,
2443            encoder,
2444            &self.primitive_buffer,
2445            bytemuck::cast_slice(primitives),
2446        );
2447        write_staged_buffer(
2448            &mut self.staging_belt,
2449            encoder,
2450            &self.colors_buffer,
2451            bytemuck::cast_slice(colors),
2452        );
2453        write_staged_buffer(
2454            &mut self.staging_belt,
2455            encoder,
2456            &self.gradients_buffer,
2457            bytemuck::cast_slice(gradients),
2458        );
2459        write_staged_buffer(
2460            &mut self.staging_belt,
2461            encoder,
2462            &self.materials_buffer,
2463            bytemuck::cast_slice(materials),
2464        );
2465        write_staged_buffer(
2466            &mut self.staging_belt,
2467            encoder,
2468            &self.transforms_buffer,
2469            bytemuck::cast_slice(transforms),
2470        );
2471
2472        self.index_count = geom.indices.len() as u64;
2473    }
2474
2475    pub(crate) fn render_primitive_batches(&mut self, batches: &mut [PrimitiveBatch<'_>]) {
2476        if batches.is_empty() {
2477            return;
2478        }
2479
2480        if self.should_render_capture_target() {
2481            self.render_primitive_batches_segmented(batches);
2482            return;
2483        }
2484
2485        let mut encoder = self
2486            .device
2487            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2488                label: Some("Primitive Batch Encoder"),
2489            });
2490
2491        if !self.pending_clear {
2492            self.stencil_renderer.encode_clear(&mut encoder);
2493            self.pending_clear = true;
2494        }
2495
2496        for batch in batches {
2497            self.write_buffers(&mut batch.buffers, &mut encoder);
2498
2499            let mut plans = Vec::new();
2500            for segment in &batch.segments {
2501                let stencil_sync = if self
2502                    .stencil_renderer
2503                    .needs_stack_sync(segment.stencil_depth, &segment.clips)
2504                {
2505                    self.stencil_renderer.prepare_stencil_stack_sync(
2506                        &self.device,
2507                        segment.stencil_depth,
2508                        &segment.clips,
2509                    )
2510                } else {
2511                    stencil::PreparedStencilSync::default()
2512                };
2513                let stencil_reference = self.stencil_renderer.get_stencil().1;
2514                let scissor = physical_scissor_rect(
2515                    segment.scissor,
2516                    self.globals.dpr,
2517                    self.globals.origin,
2518                    self.surface_config.width,
2519                    self.surface_config.height,
2520                );
2521                plans.push(PrimitiveBatchRenderPlan {
2522                    alpha_mask: segment.alpha_mask,
2523                    stencil_sync,
2524                    stencil_reference,
2525                    scissor,
2526                    index_start: segment.index_start,
2527                    index_count: segment.index_count,
2528                });
2529            }
2530
2531            let has_work = plans.iter().any(|plan| {
2532                !plan.stencil_sync.is_empty() || (plan.scissor.is_some() && plan.index_count > 0)
2533            });
2534            if !has_work {
2535                continue;
2536            }
2537
2538            let load_op = self.take_color_load_op();
2539            self.ensure_active_frame();
2540            {
2541                let (screen_texture, resolve_target) = self.current_color_attachment_view_clones();
2542                let stencil_texture = self.stencil_renderer.stencil_view_clone();
2543                let depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
2544                    view: &stencil_texture,
2545                    depth_ops: None,
2546                    stencil_ops: Some(wgpu::Operations {
2547                        load: wgpu::LoadOp::Load,
2548                        store: wgpu::StoreOp::Store,
2549                    }),
2550                });
2551                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2552                    label: Some("Primitive Batch Pass"),
2553                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2554                        view: &screen_texture,
2555                        depth_slice: None,
2556                        resolve_target: resolve_target.as_ref(),
2557                        ops: wgpu::Operations {
2558                            load: load_op,
2559                            store: wgpu::StoreOp::Store,
2560                        },
2561                    })],
2562                    depth_stencil_attachment,
2563                    timestamp_writes: None,
2564                    occlusion_query_set: None,
2565                    multiview_mask: None,
2566                });
2567
2568                let full_surface_scissor = (
2569                    0,
2570                    0,
2571                    self.surface_config.width.max(1),
2572                    self.surface_config.height.max(1),
2573                );
2574                for plan in &plans {
2575                    if !plan.stencil_sync.is_empty() {
2576                        // Scissor state is sticky within a render pass. Stencil syncs must cover
2577                        // their full geometry, then drawable segments can restore their own scissor.
2578                        render_pass.set_scissor_rect(
2579                            full_surface_scissor.0,
2580                            full_surface_scissor.1,
2581                            full_surface_scissor.2,
2582                            full_surface_scissor.3,
2583                        );
2584                        self.stencil_renderer.encode_prepared_stencil_sync(
2585                            &mut render_pass,
2586                            &plan.stencil_sync,
2587                            true,
2588                        );
2589                    }
2590
2591                    let Some(scissor) = plan.scissor else {
2592                        continue;
2593                    };
2594                    if plan.index_count == 0 {
2595                        continue;
2596                    }
2597                    render_pass.set_pipeline(self.pipeline.as_ref());
2598                    render_pass.set_bind_group(0, &self.bind_group, &[]);
2599                    render_pass.set_bind_group(2, &self.empty_mesh_page.bind_group, &[]);
2600                    render_pass.set_bind_group(3, &self.batch_transform_bind_group, &[]);
2601                    render_pass.set_bind_group(
2602                        1,
2603                        self.alpha_masks.bind_group(plan.alpha_mask),
2604                        &[],
2605                    );
2606                    render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2607                    render_pass.set_stencil_reference(plan.stencil_reference);
2608                    render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
2609                    render_pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2610                    render_pass.draw_indexed(
2611                        plan.index_start..plan.index_start + plan.index_count,
2612                        0,
2613                        0..1,
2614                    );
2615                }
2616            }
2617        }
2618
2619        self.enqueue_staged_command_buffer(encoder);
2620    }
2621
2622    fn render_primitive_batches_segmented(&mut self, batches: &mut [PrimitiveBatch<'_>]) {
2623        if batches.is_empty() {
2624            return;
2625        }
2626
2627        let mut encoder = self
2628            .device
2629            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2630                label: Some("Primitive Batch Encoder"),
2631            });
2632
2633        if !self.pending_clear {
2634            self.stencil_renderer.encode_clear(&mut encoder);
2635            self.pending_clear = true;
2636        }
2637
2638        for batch in batches {
2639            self.write_buffers(&mut batch.buffers, &mut encoder);
2640
2641            let mut segment_index = 0;
2642            while segment_index < batch.segments.len() {
2643                let segment = &batch.segments[segment_index];
2644                if self
2645                    .stencil_renderer
2646                    .needs_stack_sync(segment.stencil_depth, &segment.clips)
2647                {
2648                    self.stencil_renderer.encode_stencil_stack_sync(
2649                        &self.device,
2650                        &mut encoder,
2651                        segment.stencil_depth,
2652                        &segment.clips,
2653                    );
2654                }
2655                let stencil_reference = self.stencil_renderer.get_stencil().1;
2656                let run_start = segment_index;
2657                segment_index += 1;
2658                while segment_index < batch.segments.len() {
2659                    let next = &batch.segments[segment_index];
2660                    if self
2661                        .stencil_renderer
2662                        .needs_stack_sync(next.stencil_depth, &next.clips)
2663                    {
2664                        break;
2665                    }
2666                    segment_index += 1;
2667                }
2668                let drawable_segments: Vec<_> = batch.segments[run_start..segment_index]
2669                    .iter()
2670                    .filter_map(|segment| {
2671                        physical_scissor_rect(
2672                            segment.scissor,
2673                            self.globals.dpr,
2674                            self.globals.origin,
2675                            self.surface_config.width,
2676                            self.surface_config.height,
2677                        )
2678                        .map(|scissor| (segment, scissor))
2679                    })
2680                    .collect();
2681                if drawable_segments.is_empty() {
2682                    continue;
2683                }
2684                let load_op = self.take_color_load_op();
2685                self.ensure_active_frame();
2686                {
2687                    let (screen_texture, resolve_target) = self.current_color_attachment_views();
2688                    let (stencil_texture, _) = self.stencil_renderer.get_stencil();
2689                    let depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
2690                        view: stencil_texture,
2691                        depth_ops: None,
2692                        stencil_ops: Some(wgpu::Operations {
2693                            load: wgpu::LoadOp::Load,
2694                            store: wgpu::StoreOp::Store,
2695                        }),
2696                    });
2697                    let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2698                        label: Some("Primitive Batch Pass"),
2699                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2700                            view: screen_texture,
2701                            depth_slice: None,
2702                            resolve_target,
2703                            ops: wgpu::Operations {
2704                                load: load_op,
2705                                store: wgpu::StoreOp::Store,
2706                            },
2707                        })],
2708                        depth_stencil_attachment,
2709                        timestamp_writes: None,
2710                        occlusion_query_set: None,
2711                        multiview_mask: None,
2712                    });
2713                    render_pass.set_pipeline(self.pipeline.as_ref());
2714                    render_pass.set_bind_group(0, &self.bind_group, &[]);
2715                    render_pass.set_bind_group(2, &self.empty_mesh_page.bind_group, &[]);
2716                    render_pass.set_bind_group(3, &self.batch_transform_bind_group, &[]);
2717                    render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2718                    render_pass.set_stencil_reference(stencil_reference);
2719                    render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
2720                    for (segment, scissor) in &drawable_segments {
2721                        render_pass.set_bind_group(
2722                            1,
2723                            self.alpha_masks.bind_group(segment.alpha_mask),
2724                            &[],
2725                        );
2726                        render_pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2727                        render_pass.draw_indexed(
2728                            segment.index_start..segment.index_start + segment.index_count,
2729                            0,
2730                            0..1,
2731                        );
2732                    }
2733                }
2734
2735                if self.should_render_capture_target() {
2736                    self.ensure_capture_target();
2737                    let capture_load_op = self.take_capture_color_load_op(load_op);
2738                    let capture_target = self.capture_target.as_ref().unwrap();
2739                    let (capture_texture, capture_resolve_target) =
2740                        capture_target.color_attachment_views();
2741                    let (stencil_texture, _) = self.stencil_renderer.get_stencil();
2742                    let depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
2743                        view: stencil_texture,
2744                        depth_ops: None,
2745                        stencil_ops: Some(wgpu::Operations {
2746                            load: wgpu::LoadOp::Load,
2747                            store: wgpu::StoreOp::Store,
2748                        }),
2749                    });
2750                    let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2751                        label: Some("Primitive Batch Screenshot Mirror Pass"),
2752                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2753                            view: capture_texture,
2754                            depth_slice: None,
2755                            resolve_target: capture_resolve_target,
2756                            ops: wgpu::Operations {
2757                                load: capture_load_op,
2758                                store: wgpu::StoreOp::Store,
2759                            },
2760                        })],
2761                        depth_stencil_attachment,
2762                        timestamp_writes: None,
2763                        occlusion_query_set: None,
2764                        multiview_mask: None,
2765                    });
2766                    render_pass.set_pipeline(self.pipeline.as_ref());
2767                    render_pass.set_bind_group(0, &self.bind_group, &[]);
2768                    render_pass.set_bind_group(2, &self.empty_mesh_page.bind_group, &[]);
2769                    render_pass.set_bind_group(3, &self.batch_transform_bind_group, &[]);
2770                    render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2771                    render_pass.set_stencil_reference(stencil_reference);
2772                    render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
2773                    for (segment, scissor) in &drawable_segments {
2774                        render_pass.set_bind_group(
2775                            1,
2776                            self.alpha_masks.bind_group(segment.alpha_mask),
2777                            &[],
2778                        );
2779                        render_pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2780                        render_pass.draw_indexed(
2781                            segment.index_start..segment.index_start + segment.index_count,
2782                            0,
2783                            0..1,
2784                        );
2785                    }
2786                }
2787            }
2788        }
2789
2790        self.enqueue_staged_command_buffer(encoder);
2791    }
2792
2793    fn ensure_active_frame(&mut self) {
2794        if self.active_frame.is_none() {
2795            let surface = self.surface.get_current_texture().unwrap();
2796            let view = surface
2797                .texture
2798                .create_view(&wgpu::TextureViewDescriptor::default());
2799            self.active_frame = Some(ActiveFrame { view, surface });
2800        }
2801    }
2802
2803    fn take_color_load_op(&mut self) -> wgpu::LoadOp<wgpu::Color> {
2804        if let Some(target) = self
2805            .retained_surfaces
2806            .as_mut()
2807            .and_then(|s| s.stack.last_mut())
2808        {
2809            return if std::mem::replace(&mut target.initialized, true) {
2810                wgpu::LoadOp::Load
2811            } else {
2812                wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
2813            };
2814        }
2815        if std::mem::take(&mut self.pending_clear) {
2816            wgpu::LoadOp::Clear(self.clear_color)
2817        } else {
2818            wgpu::LoadOp::Load
2819        }
2820    }
2821
2822    #[allow(dead_code)]
2823    pub(crate) fn render_image(&mut self, image: &Image, transform: Transform2D, rect: Box2D) {
2824        let clear_target = std::mem::take(&mut self.pending_clear);
2825        self.ensure_active_frame();
2826        let (screen_texture, resolve_target) = self.current_color_attachment_views();
2827        self.texture_renderer.render_image(
2828            &self.device,
2829            &self.queue,
2830            screen_texture,
2831            resolve_target,
2832            &self.globals_buffer,
2833            &self.stencil_renderer,
2834            clear_target,
2835            self.clear_color,
2836            &image.rgba,
2837            image.pixel_width,
2838            transform,
2839            rect,
2840        );
2841        if self.should_render_capture_target() {
2842            self.ensure_capture_target();
2843            let capture_clear_target = self.take_capture_clear_target(clear_target);
2844            let capture_target = self.capture_target.as_ref().unwrap();
2845            let (capture_texture, capture_resolve_target) = capture_target.color_attachment_views();
2846            self.texture_renderer.render_image(
2847                &self.device,
2848                &self.queue,
2849                capture_texture,
2850                capture_resolve_target,
2851                &self.globals_buffer,
2852                &self.stencil_renderer,
2853                capture_clear_target,
2854                self.clear_color,
2855                &image.rgba,
2856                image.pixel_width,
2857                transform,
2858                rect,
2859            );
2860        }
2861    }
2862
2863    pub(crate) fn clear(&mut self) {
2864        let mut encoder = self
2865            .device
2866            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2867                label: Some("Stencil Clear Encoder"),
2868            });
2869        self.stencil_renderer.encode_clear(&mut encoder);
2870        self.enqueue_command_buffer(encoder.finish());
2871        self.pending_clear = true;
2872    }
2873
2874    pub(crate) fn set_scene_lighting(&mut self, lighting: GpuSceneLighting) {
2875        self.queue
2876            .write_buffer(&self.lighting_buffer, 0, bytemuck::bytes_of(&lighting));
2877    }
2878
2879    pub(crate) fn ensure_frame_cleared(&mut self) {
2880        if !self.pending_clear {
2881            self.clear();
2882        }
2883    }
2884
2885    pub(crate) fn finish_frame(&mut self) {
2886        self.finish_retained_encoding();
2887        if self.pending_clear {
2888            let load_op = self.take_color_load_op();
2889            self.ensure_active_frame();
2890            let (screen_texture, resolve_target) = self.current_color_attachment_views();
2891            let mut encoder = self
2892                .device
2893                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2894                    label: Some("Render Encoder"),
2895                });
2896
2897            {
2898                let _r = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2899                    label: Some("Render Pass"),
2900                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2901                        view: screen_texture,
2902                        depth_slice: None,
2903                        resolve_target,
2904                        ops: wgpu::Operations {
2905                            load: load_op,
2906                            store: wgpu::StoreOp::Store,
2907                        },
2908                    })],
2909                    depth_stencil_attachment: None,
2910                    timestamp_writes: None,
2911                    occlusion_query_set: None,
2912                    multiview_mask: None,
2913                });
2914            }
2915            if self.should_render_capture_target() {
2916                self.ensure_capture_target();
2917                let capture_load_op = self.take_capture_color_load_op(load_op);
2918                let capture_target = self.capture_target.as_ref().unwrap();
2919                let (capture_texture, capture_resolve_target) =
2920                    capture_target.color_attachment_views();
2921                let _r = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2922                    label: Some("Screenshot Mirror Clear Pass"),
2923                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2924                        view: capture_texture,
2925                        depth_slice: None,
2926                        resolve_target: capture_resolve_target,
2927                        ops: wgpu::Operations {
2928                            load: capture_load_op,
2929                            store: wgpu::StoreOp::Store,
2930                        },
2931                    })],
2932                    depth_stencil_attachment: None,
2933                    timestamp_writes: None,
2934                    occlusion_query_set: None,
2935                    multiview_mask: None,
2936                });
2937            }
2938            self.enqueue_command_buffer(encoder.finish());
2939        }
2940
2941        self.capture_surface_for_pending_requests();
2942    }
2943
2944    pub(crate) fn present_queued_frame(&mut self) {
2945        if let Some(screen_surface) = self.active_frame.take() {
2946            screen_surface.surface.present();
2947        }
2948    }
2949
2950    pub(crate) fn present(&mut self) {
2951        self.finish_frame();
2952        self.submit_pending_commands();
2953        self.present_queued_frame();
2954    }
2955
2956    fn current_color_attachment_views(&self) -> (&TextureView, Option<&TextureView>) {
2957        let surface_view = &self
2958            .active_frame
2959            .as_ref()
2960            .expect("active frame should exist after acquisition")
2961            .view;
2962        if let Some(multisampled_target) = &self.multisampled_target {
2963            (&multisampled_target.view, Some(surface_view))
2964        } else {
2965            (surface_view, None)
2966        }
2967    }
2968
2969    fn current_color_attachment_view_clones(&self) -> (TextureView, Option<TextureView>) {
2970        if let Some(target) = self.retained_surfaces.as_ref().and_then(|s| s.stack.last()) {
2971            let (view, resolve) = target.color_attachment_views();
2972            return (view.clone(), resolve.cloned());
2973        }
2974        let surface_view = self
2975            .active_frame
2976            .as_ref()
2977            .expect("active frame should exist after acquisition")
2978            .view
2979            .clone();
2980        if let Some(multisampled_target) = &self.multisampled_target {
2981            (multisampled_target.view.clone(), Some(surface_view))
2982        } else {
2983            (surface_view, None)
2984        }
2985    }
2986
2987    fn surface_supports_copy_src(&self) -> bool {
2988        self.surface_config.usage.contains(TextureUsages::COPY_SRC)
2989    }
2990
2991    fn should_render_capture_target(&self) -> bool {
2992        self.retained_surfaces
2993            .as_ref()
2994            .is_none_or(|s| s.stack.is_empty())
2995            && !self.surface_supports_copy_src()
2996            && !self.pending_capture_ids.is_empty()
2997    }
2998
2999    fn ensure_capture_target(&mut self) {
3000        if self.surface_supports_copy_src() || self.capture_target.is_some() {
3001            return;
3002        }
3003
3004        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
3005            label: Some("Surface Screenshot Mirror"),
3006            size: wgpu::Extent3d {
3007                width: self.surface_config.width.max(1),
3008                height: self.surface_config.height.max(1),
3009                depth_or_array_layers: 1,
3010            },
3011            mip_level_count: 1,
3012            sample_count: 1,
3013            dimension: wgpu::TextureDimension::D2,
3014            format: self.surface_config.format,
3015            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
3016            view_formats: &[self.surface_config.format],
3017        });
3018        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3019        let multisampled_target = if self.sample_count > 1 {
3020            let (texture, view) = create_multisampled_framebuffer(
3021                &self.device,
3022                &self.surface_config,
3023                self.sample_count,
3024            );
3025            Some(MultisampledTarget {
3026                _texture: texture,
3027                view,
3028            })
3029        } else {
3030            None
3031        };
3032        self.capture_target = Some(CaptureTarget {
3033            texture,
3034            view,
3035            multisampled_target,
3036            initialized: false,
3037        });
3038    }
3039
3040    fn take_capture_color_load_op(
3041        &mut self,
3042        screen_load_op: wgpu::LoadOp<wgpu::Color>,
3043    ) -> wgpu::LoadOp<wgpu::Color> {
3044        let Some(capture_target) = self.capture_target.as_mut() else {
3045            return screen_load_op;
3046        };
3047        if matches!(screen_load_op, wgpu::LoadOp::Clear(_)) {
3048            capture_target.initialized = true;
3049            return screen_load_op;
3050        }
3051        if !capture_target.initialized {
3052            capture_target.initialized = true;
3053            return wgpu::LoadOp::Clear(self.clear_color);
3054        }
3055        screen_load_op
3056    }
3057
3058    fn take_capture_clear_target(&mut self, screen_clear_target: bool) -> bool {
3059        let Some(capture_target) = self.capture_target.as_mut() else {
3060            return screen_clear_target;
3061        };
3062        if screen_clear_target || !capture_target.initialized {
3063            capture_target.initialized = true;
3064            return true;
3065        }
3066        false
3067    }
3068
3069    fn capture_surface_for_pending_requests(&mut self) {
3070        let capture_ids = std::mem::take(&mut self.pending_capture_ids);
3071        if capture_ids.is_empty() {
3072            return;
3073        }
3074
3075        let width = self.surface_config.width.max(1);
3076        let height = self.surface_config.height.max(1);
3077        let surface_format = self.surface_config.format;
3078        let unpadded_bytes_per_row = width as usize * 4;
3079        let padded_bytes_per_row =
3080            unpadded_bytes_per_row.next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize);
3081        let readback_size = padded_bytes_per_row * height as usize;
3082        let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
3083            label: Some("Surface Screenshot Readback"),
3084            size: readback_size as u64,
3085            usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
3086            mapped_at_creation: false,
3087        });
3088
3089        let mut encoder = self
3090            .device
3091            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3092                label: Some("Surface Screenshot Encoder"),
3093            });
3094        let source_texture = if self.surface_supports_copy_src() {
3095            let Some(active_frame) = self.active_frame.as_ref() else {
3096                return;
3097            };
3098            &active_frame.surface.texture
3099        } else {
3100            let Some(capture_target) = self.capture_target.as_ref() else {
3101                return;
3102            };
3103            &capture_target.texture
3104        };
3105        encoder.copy_texture_to_buffer(
3106            wgpu::TexelCopyTextureInfo {
3107                texture: source_texture,
3108                mip_level: 0,
3109                origin: wgpu::Origin3d::ZERO,
3110                aspect: wgpu::TextureAspect::All,
3111            },
3112            wgpu::TexelCopyBufferInfo {
3113                buffer: &readback,
3114                layout: wgpu::TexelCopyBufferLayout {
3115                    offset: 0,
3116                    bytes_per_row: Some(padded_bytes_per_row as u32),
3117                    rows_per_image: Some(height),
3118                },
3119            },
3120            wgpu::Extent3d {
3121                width,
3122                height,
3123                depth_or_array_layers: 1,
3124            },
3125        );
3126        let completed_captures = Arc::clone(&self.completed_captures);
3127        let readback_for_callback = readback.clone();
3128        // The copy may be submitted later as part of a multi-surface frame. Mapping now would
3129        // make the buffer unavailable to that copy when its command buffer reaches the queue.
3130        encoder.map_buffer_on_submit(&readback, wgpu::MapMode::Read, .., move |result| {
3131            if let Err(err) = result {
3132                log::warn!("failed to map surface screenshot readback buffer: {err}");
3133                return;
3134            }
3135
3136            let mapped = readback_for_callback.slice(..).get_mapped_range();
3137            let mut rgba = vec![0; unpadded_bytes_per_row * height as usize];
3138            for row in 0..height as usize {
3139                let src_start = row * padded_bytes_per_row;
3140                let dst_start = row * unpadded_bytes_per_row;
3141                rgba[dst_start..dst_start + unpadded_bytes_per_row]
3142                    .copy_from_slice(&mapped[src_start..src_start + unpadded_bytes_per_row]);
3143            }
3144            if matches!(
3145                surface_format,
3146                wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
3147            ) {
3148                for pixel in rgba.chunks_exact_mut(4) {
3149                    pixel.swap(0, 2);
3150                }
3151            }
3152            drop(mapped);
3153            readback_for_callback.unmap();
3154
3155            let capture = CapturedFrame {
3156                width,
3157                height,
3158                rgba,
3159            };
3160            match completed_captures.lock() {
3161                Ok(mut completed) => {
3162                    for request_id in capture_ids {
3163                        completed.insert(request_id, capture.clone());
3164                    }
3165                }
3166                Err(err) => {
3167                    log::warn!(
3168                        "failed to store surface screenshot readback result: {:?}",
3169                        err
3170                    );
3171                }
3172            }
3173        });
3174        self.enqueue_command_buffer(encoder.finish());
3175        self.needs_device_poll = true;
3176    }
3177}
3178
3179fn select_sample_count(
3180    surface_format_features: TextureFormatFeatureFlags,
3181    stencil_format_features: TextureFormatFeatureFlags,
3182) -> u32 {
3183    [4, 2]
3184        .into_iter()
3185        .find(|count| {
3186            surface_format_features.sample_count_supported(*count)
3187                && surface_format_features.contains(TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
3188                && stencil_format_features.sample_count_supported(*count)
3189        })
3190        .unwrap_or(1)
3191}
3192
3193fn aligned_cpu_buffers(
3194    buffers: &mut CpuBuffers,
3195) -> (
3196    Vec<GpuVertex>,
3197    Vec<u16>,
3198    Vec<GpuPrimitive>,
3199    Vec<GpuTransform>,
3200    Vec<GpuColor>,
3201    Vec<GpuGradient>,
3202    Vec<GpuMaterial>,
3203) {
3204    let CpuBuffers {
3205        geometry,
3206        primitives,
3207        transforms,
3208        colors,
3209        gradients,
3210        materials,
3211        mesh_paints: _,
3212    } = buffers;
3213    let mut indices = geometry.indices.clone();
3214    let mut vertices = geometry.vertices.clone();
3215    let mut primitives = primitives.clone();
3216    let mut transforms = transforms.clone();
3217    let mut colors = colors.clone();
3218    let mut gradients = gradients.clone();
3219    let mut materials = materials.clone();
3220
3221    const ALIGNMENT: usize = 16;
3222    if primitives.is_empty() {
3223        primitives.push(GpuPrimitive::default());
3224    }
3225    if transforms.is_empty() {
3226        transforms.push(GpuTransform::default());
3227    }
3228    if colors.is_empty() {
3229        colors.push(GpuColor::default());
3230    }
3231    if gradients.is_empty() {
3232        gradients.push(GpuGradient::default());
3233    }
3234    if materials.is_empty() {
3235        materials.push(GpuMaterial::default());
3236    }
3237    while indices.len() * std::mem::size_of::<u16>() % ALIGNMENT != 0 {
3238        indices.push(0);
3239        indices.push(0);
3240        indices.push(0);
3241    }
3242    while vertices.len() * std::mem::size_of::<GpuVertex>() % ALIGNMENT != 0 {
3243        vertices.push(GpuVertex::default());
3244    }
3245    while primitives.len() * std::mem::size_of::<GpuPrimitive>() % ALIGNMENT != 0 {
3246        primitives.push(GpuPrimitive::default());
3247    }
3248    while transforms.len() * std::mem::size_of::<GpuTransform>() % ALIGNMENT != 0 {
3249        transforms.push(GpuTransform::default());
3250    }
3251    while colors.len() * std::mem::size_of::<GpuColor>() % ALIGNMENT != 0 {
3252        colors.push(GpuColor::default());
3253    }
3254    while gradients.len() * std::mem::size_of::<GpuGradient>() % ALIGNMENT != 0 {
3255        gradients.push(GpuGradient::default());
3256    }
3257    while materials.len() * std::mem::size_of::<GpuMaterial>() % ALIGNMENT != 0 {
3258        materials.push(GpuMaterial::default());
3259    }
3260
3261    (
3262        vertices, indices, primitives, transforms, colors, gradients, materials,
3263    )
3264}
3265
3266fn aligned_geometry_buffers(buffers: &CpuBuffers) -> (Vec<GpuVertex>, Vec<u16>) {
3267    let mut vertices = buffers.geometry.vertices.clone();
3268    let mut indices = buffers.geometry.indices.clone();
3269
3270    const ALIGNMENT: usize = 16;
3271    while indices.len() * std::mem::size_of::<u16>() % ALIGNMENT != 0 {
3272        indices.push(0);
3273        indices.push(0);
3274        indices.push(0);
3275    }
3276    while vertices.len() * std::mem::size_of::<GpuVertex>() % ALIGNMENT != 0 {
3277        vertices.push(GpuVertex::default());
3278    }
3279
3280    (vertices, indices)
3281}
3282
3283fn aligned_pod_slice<T: Default + Clone + Pod>(slice: &[T]) -> Vec<T> {
3284    let mut aligned = slice.to_vec();
3285
3286    const ALIGNMENT: usize = 16;
3287    if aligned.is_empty() {
3288        aligned.push(T::default());
3289    }
3290    while aligned.len() * std::mem::size_of::<T>() % ALIGNMENT != 0 {
3291        aligned.push(T::default());
3292    }
3293
3294    aligned
3295}
3296
3297fn transformed_corners(rect: &Box2D, transform: &Transform2D) -> [[f32; 2]; 4] {
3298    let min = rect.min;
3299    let max = rect.max;
3300    let corners = [
3301        crate::Point2D::new(min.x, min.y),
3302        crate::Point2D::new(max.x, min.y),
3303        crate::Point2D::new(min.x, max.y),
3304        crate::Point2D::new(max.x, max.y),
3305    ];
3306    [
3307        transform.transform_point(corners[0]).to_array(),
3308        transform.transform_point(corners[1]).to_array(),
3309        transform.transform_point(corners[2]).to_array(),
3310        transform.transform_point(corners[3]).to_array(),
3311    ]
3312}
3313
3314#[derive(Debug)]
3315pub(crate) struct CpuBuffers {
3316    pub geometry: VertexBuffers<GpuVertex, u16>,
3317    pub primitives: Vec<GpuPrimitive>,
3318    pub transforms: Vec<GpuTransform>,
3319    pub colors: Vec<GpuColor>,
3320    pub gradients: Vec<GpuGradient>,
3321    pub mesh_paints: Vec<mesh_paint::MeshInput>,
3322    pub materials: Vec<GpuMaterial>,
3323}
3324
3325impl CpuBuffers {
3326    #[allow(dead_code)]
3327    pub(crate) fn reset(&mut self) {
3328        let CpuBuffers {
3329            geometry,
3330            primitives,
3331            transforms,
3332            colors,
3333            gradients,
3334            mesh_paints,
3335            materials,
3336        } = self;
3337        geometry.vertices.clear();
3338        geometry.indices.clear();
3339        primitives.clear();
3340        colors.clear();
3341        gradients.clear();
3342        mesh_paints.clear();
3343        materials.clear();
3344        // leave the identity transform at the start
3345        transforms.truncate(1);
3346    }
3347}
3348
3349#[cfg(test)]
3350mod shader_tests {
3351    #[test]
3352    fn alpha_mask_shaders_parse_and_validate() {
3353        for source in [
3354            concat!(
3355                include_str!("radial_gradient.wgsl"),
3356                "\n",
3357                include_str!("alpha_mask.wgsl")
3358            ),
3359            include_str!("alpha_blur.wgsl"),
3360            include_str!("alpha_capture.wgsl"),
3361            include_str!("textures.wgsl"),
3362        ] {
3363            let module = naga::front::wgsl::parse_str(source).expect("alpha shader parses");
3364            naga::valid::Validator::new(
3365                naga::valid::ValidationFlags::all(),
3366                naga::valid::Capabilities::all(),
3367            )
3368            .validate(&module)
3369            .expect("alpha shader validates");
3370        }
3371    }
3372    #[test]
3373    fn geometry_shader_parses_and_validates() {
3374        let module = naga::front::wgsl::parse_str(concat!(
3375            include_str!("radial_gradient.wgsl"),
3376            "\n",
3377            include_str!("geometry.wgsl")
3378        ))
3379        .expect("geometry shader should parse");
3380        naga::valid::Validator::new(
3381            naga::valid::ValidationFlags::all(),
3382            naga::valid::Capabilities::all(),
3383        )
3384        .validate(&module)
3385        .expect("geometry shader should validate");
3386    }
3387}
3388
3389#[derive(Clone)]
3390/// Decoded RGBA image data ready for upload as a GPU texture.
3391pub struct Image {
3392    pub rgba: Vec<u8>,
3393    pub pixel_width: u32,
3394    pub pixel_height: u32,
3395}