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
66pub 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 #[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_FALLBACK_CLEAR_COLOR
221 } else {
222 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
240pub 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;
262pub(crate) const MAX_SCENE_TRANSFORMS: usize = 480;
265pub(crate) const MAX_SCENE_CLIPS: usize = 480;
266
267impl RenderConfig {
268 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 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
373pub struct RenderBackend<'w> {
375 context: SharedGpuContext,
376 config: RenderConfig,
378 pub(crate) globals: GpuGlobals,
379
380 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 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 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)]
451pub 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, };
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 pub fn get_clip_depth(&mut self) -> u32 {
1254 let (_, depth) = self.stencil_renderer.get_stencil();
1255 depth
1256 }
1257
1258 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 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 self.pending_clear = true;
1286 self.pending_command_buffers.clear();
1287 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 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 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 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 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 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 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)]
3001pub struct Image {
3003 pub rgba: Vec<u8>,
3004 pub pixel_width: u32,
3005 pub pixel_height: u32,
3006}