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