Skip to main content

pax_gpu/render_backend/
mod.rs

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