1use super::data::GpuTransform;
2use super::transform_pages::{self, TransformPages, TRANSFORMS_PER_PAGE};
3use bytemuck::{Pod, Zeroable};
4use lyon::tessellation::VertexBuffers;
5use std::cell::RefCell;
6use std::collections::{HashMap, HashSet};
7use std::rc::Rc;
8use wgpu::util::DeviceExt;
9use wgpu::{BufferUsages, Device, Queue, RenderPipeline};
10
11fn padded_u16_buffer(data: &[u16]) -> Vec<u16> {
12 if data.len() % 2 == 0 {
13 return data.to_vec();
14 }
15
16 let mut padded = Vec::with_capacity(data.len() + 1);
17 padded.extend_from_slice(data);
18 padded.push(0);
19 padded
20}
21
22fn has_drawable_stencil_geometry(index_count: u32) -> bool {
23 index_count > 0
24}
25
26struct CachedStencilGeometry {
27 vertices_buffer: wgpu::Buffer,
28 indices_buffer: wgpu::Buffer,
29 index_count: u32,
30}
31
32#[derive(Clone, Copy, Debug)]
33struct StencilStackEntry {
34 clip_id: u32,
35 geometry_signature: u64,
36}
37
38#[derive(Default)]
39pub(crate) struct PreparedStencilSync {
40 popped: Vec<StencilStackEntry>,
41 pushed: Vec<StencilStackEntry>,
42}
43
44impl PreparedStencilSync {
45 pub(crate) fn is_empty(&self) -> bool {
46 self.popped.is_empty() && self.pushed.is_empty()
47 }
48}
49
50pub struct ClipDraw<'a> {
52 pub clip_id: u32,
53 pub geometry_signature: u64,
54 pub geometry: &'a VertexBuffers<Vertex, u16>,
55}
56
57pub struct StencilRenderer {
59 stencil_pipeline: Rc<RenderPipeline>,
60 decrement_pipeline: Rc<RenderPipeline>,
61 color_stencil_pipeline: Rc<RenderPipeline>,
62 color_decrement_pipeline: Rc<RenderPipeline>,
63 stencil_texture: wgpu::Texture,
64 stencil_view: wgpu::TextureView,
65 stencil_layer: u32,
66 stencil_geometry_stack: Vec<StencilStackEntry>,
67 cached_geometry: HashMap<u64, CachedStencilGeometry>,
68 cached_clip_instances: HashMap<u32, wgpu::Buffer>,
69 width: u32,
70 height: u32,
71 sample_count: u32,
72 stencil_bind_group: wgpu::BindGroup,
73 transforms: Rc<RefCell<TransformPages>>,
74}
75
76#[derive(Clone)]
77pub(crate) struct StencilPipelineResources {
78 stencil_pipeline: Rc<RenderPipeline>,
79 decrement_pipeline: Rc<RenderPipeline>,
80 color_stencil_pipeline: Rc<RenderPipeline>,
81 color_decrement_pipeline: Rc<RenderPipeline>,
82 stencil_bind_group_layout: Rc<wgpu::BindGroupLayout>,
83 transform_layout: wgpu::BindGroupLayout,
84}
85
86#[repr(C)]
87#[derive(Copy, Clone, Debug, Pod, Zeroable)]
88pub struct Vertex {
90 pub position: [f32; 2],
91}
92
93impl StencilRenderer {
94 #[allow(dead_code)]
96 pub fn new(
97 device: &Device,
98 width: u32,
99 height: u32,
100 sample_count: u32,
101 color_format: wgpu::TextureFormat,
102 globals: &wgpu::Buffer,
103 clip_transforms: &wgpu::Buffer,
104 ) -> Self {
105 let resources = Self::create_pipeline_resources(device, sample_count, color_format);
106 Self::with_pipeline_resources(
107 device,
108 width,
109 height,
110 sample_count,
111 globals,
112 clip_transforms,
113 resources,
114 )
115 }
116
117 pub(crate) fn with_pipeline_resources(
118 device: &Device,
119 width: u32,
120 height: u32,
121 sample_count: u32,
122 globals: &wgpu::Buffer,
123 clip_transforms: &wgpu::Buffer,
124 resources: StencilPipelineResources,
125 ) -> Self {
126 let transforms = Rc::new(RefCell::new(TransformPages::from_buffer(
127 device,
128 resources.transform_layout.clone(),
129 clip_transforms,
130 )));
131 Self::with_transform_pages(
132 device,
133 width,
134 height,
135 sample_count,
136 globals,
137 resources,
138 transforms,
139 )
140 }
141
142 pub(crate) fn with_shared_transforms(
143 device: &Device,
144 width: u32,
145 height: u32,
146 sample_count: u32,
147 globals: &wgpu::Buffer,
148 source: &Self,
149 resources: StencilPipelineResources,
150 ) -> Self {
151 Self::with_transform_pages(
154 device,
155 width,
156 height,
157 sample_count,
158 globals,
159 resources,
160 Rc::clone(&source.transforms),
161 )
162 }
163
164 fn with_transform_pages(
165 device: &Device,
166 width: u32,
167 height: u32,
168 sample_count: u32,
169 globals: &wgpu::Buffer,
170 resources: StencilPipelineResources,
171 transforms: Rc<RefCell<TransformPages>>,
172 ) -> Self {
173 let stencil_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
174 layout: resources.stencil_bind_group_layout.as_ref(),
175 entries: &[wgpu::BindGroupEntry {
176 binding: 0,
177 resource: globals.as_entire_binding(),
178 }],
179 label: Some("stencil_bind_group"),
180 });
181 let (stencil_texture, stencil_view) =
182 Self::create_stencil_texture(device, width, height, sample_count);
183
184 Self {
185 stencil_pipeline: resources.stencil_pipeline,
186 decrement_pipeline: resources.decrement_pipeline,
187 color_stencil_pipeline: resources.color_stencil_pipeline,
188 color_decrement_pipeline: resources.color_decrement_pipeline,
189 stencil_texture,
190 stencil_view,
191 width,
192 height,
193 sample_count,
194 stencil_layer: 0,
195 stencil_geometry_stack: vec![],
196 cached_geometry: HashMap::new(),
197 cached_clip_instances: HashMap::new(),
198 stencil_bind_group,
199 transforms,
200 }
201 }
202
203 pub(crate) fn create_pipeline_resources(
204 device: &Device,
205 sample_count: u32,
206 color_format: wgpu::TextureFormat,
207 ) -> StencilPipelineResources {
208 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
209 label: Some("Stencil Shader"),
210 source: wgpu::ShaderSource::Wgsl(include_str!("stencil.wgsl").into()),
211 });
212
213 let stencil_bind_group_layout =
214 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
215 entries: &[wgpu::BindGroupLayoutEntry {
216 binding: 0,
217 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
218 ty: wgpu::BindingType::Buffer {
219 ty: wgpu::BufferBindingType::Uniform,
220 has_dynamic_offset: false,
221 min_binding_size: None,
222 },
223 count: None,
224 }],
225 label: Some("stencil_bind_group_layout"),
226 });
227
228 let transform_layout = transform_pages::layout(device);
229 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
230 label: Some("Stencil Pipeline Layout"),
231 bind_group_layouts: &[&stencil_bind_group_layout, &transform_layout],
232 immediate_size: 0,
233 });
234
235 fn create_pipeline(
236 device: &Device,
237 shader: &wgpu::ShaderModule,
238 pipeline_layout: &wgpu::PipelineLayout,
239 label: &'static str,
240 sample_count: u32,
241 color_target: Option<wgpu::TextureFormat>,
242 stencil_op: wgpu::StencilOperation,
243 ) -> RenderPipeline {
244 let color_targets: Vec<Option<wgpu::ColorTargetState>> = match color_target {
245 Some(format) => vec![Some(wgpu::ColorTargetState {
246 format,
247 blend: None,
248 write_mask: wgpu::ColorWrites::empty(),
249 })],
250 None => vec![None],
251 };
252
253 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
254 label: Some(label),
255 layout: Some(pipeline_layout),
256 vertex: wgpu::VertexState {
257 module: shader,
258 entry_point: Some("vs_main"),
259 buffers: &[
260 wgpu::VertexBufferLayout {
261 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
262 step_mode: wgpu::VertexStepMode::Vertex,
263 attributes: &wgpu::vertex_attr_array![0 => Float32x2],
264 },
265 wgpu::VertexBufferLayout {
266 array_stride: std::mem::size_of::<u32>() as wgpu::BufferAddress,
267 step_mode: wgpu::VertexStepMode::Instance,
268 attributes: &wgpu::vertex_attr_array![1 => Uint32],
269 },
270 ],
271 compilation_options: Default::default(),
272 },
273 fragment: Some(wgpu::FragmentState {
274 module: shader,
275 entry_point: Some("fs_main"),
276 targets: &color_targets,
277 compilation_options: Default::default(),
278 }),
279 primitive: wgpu::PrimitiveState {
280 topology: wgpu::PrimitiveTopology::TriangleList,
281 strip_index_format: None,
282 front_face: wgpu::FrontFace::Ccw,
283 cull_mode: None,
284 polygon_mode: wgpu::PolygonMode::Fill,
285 unclipped_depth: false,
286 conservative: false,
287 },
288 depth_stencil: Some(wgpu::DepthStencilState {
289 format: wgpu::TextureFormat::Stencil8,
290 depth_write_enabled: false,
291 depth_compare: wgpu::CompareFunction::Always,
292 stencil: wgpu::StencilState {
293 front: wgpu::StencilFaceState {
294 compare: wgpu::CompareFunction::Always,
295 fail_op: wgpu::StencilOperation::Keep,
296 depth_fail_op: wgpu::StencilOperation::Keep,
297 pass_op: stencil_op,
298 },
299 back: wgpu::StencilFaceState::IGNORE,
300 read_mask: !0,
301 write_mask: !0,
302 },
303 bias: Default::default(),
304 }),
305 multisample: wgpu::MultisampleState {
306 count: sample_count,
307 ..Default::default()
308 },
309 multiview_mask: None,
310 cache: None,
311 })
312 }
313
314 let stencil_pipeline = create_pipeline(
316 device,
317 &shader,
318 &pipeline_layout,
319 "Stencil Pipeline",
320 sample_count,
321 None,
322 wgpu::StencilOperation::IncrementClamp,
323 );
324 let color_stencil_pipeline = create_pipeline(
325 device,
326 &shader,
327 &pipeline_layout,
328 "Color Stencil Pipeline",
329 sample_count,
330 Some(color_format),
331 wgpu::StencilOperation::IncrementClamp,
332 );
333 let decrement_pipeline = create_pipeline(
334 device,
335 &shader,
336 &pipeline_layout,
337 "Decrement Pipeline",
338 sample_count,
339 None,
340 wgpu::StencilOperation::DecrementClamp,
341 );
342 let color_decrement_pipeline = create_pipeline(
343 device,
344 &shader,
345 &pipeline_layout,
346 "Color Decrement Pipeline",
347 sample_count,
348 Some(color_format),
349 wgpu::StencilOperation::DecrementClamp,
350 );
351
352 StencilPipelineResources {
353 stencil_pipeline: Rc::new(stencil_pipeline),
354 decrement_pipeline: Rc::new(decrement_pipeline),
355 color_stencil_pipeline: Rc::new(color_stencil_pipeline),
356 color_decrement_pipeline: Rc::new(color_decrement_pipeline),
357 stencil_bind_group_layout: Rc::new(stencil_bind_group_layout),
358 transform_layout,
359 }
360 }
361
362 fn create_stencil_texture(
363 device: &Device,
364 width: u32,
365 height: u32,
366 sample_count: u32,
367 ) -> (wgpu::Texture, wgpu::TextureView) {
368 let texture = device.create_texture(&wgpu::TextureDescriptor {
369 label: Some("Stencil Texture"),
370 size: wgpu::Extent3d {
371 width,
372 height,
373 depth_or_array_layers: 1,
374 },
375 mip_level_count: 1,
376 sample_count,
377 dimension: wgpu::TextureDimension::D2,
378 format: wgpu::TextureFormat::Stencil8,
379 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
380 view_formats: &[],
381 });
382
383 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
384 (texture, view)
385 }
386
387 pub fn resize(&mut self, device: &Device, width: u32, height: u32) {
388 if width == self.width && height == self.height {
389 return;
390 }
391
392 (self.stencil_texture, self.stencil_view) =
393 Self::create_stencil_texture(device, width, height, self.sample_count);
394 self.width = width;
395 self.height = height;
396 }
397
398 fn create_cached_geometry(
399 device: &Device,
400 geometry: &VertexBuffers<Vertex, u16>,
401 ) -> CachedStencilGeometry {
402 let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
403 label: Some("Stencil Vertices"),
404 contents: bytemuck::cast_slice(&geometry.vertices),
405 usage: BufferUsages::VERTEX,
406 });
407 let padded_indices = padded_u16_buffer(&geometry.indices);
408 let indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
409 label: Some("Stencil Indices"),
410 contents: bytemuck::cast_slice(&padded_indices),
411 usage: BufferUsages::INDEX,
412 });
413
414 CachedStencilGeometry {
415 vertices_buffer,
416 indices_buffer,
417 index_count: geometry.indices.len() as u32,
418 }
419 }
420
421 fn ensure_cached_geometry(
422 &mut self,
423 device: &Device,
424 signature: u64,
425 geometry: &VertexBuffers<Vertex, u16>,
426 ) {
427 self.cached_geometry
428 .entry(signature)
429 .or_insert_with(|| Self::create_cached_geometry(device, geometry));
430 }
431
432 fn ensure_cached_clip_instance(&mut self, device: &Device, clip_id: u32) {
433 self.cached_clip_instances
434 .entry(clip_id)
435 .or_insert_with(|| {
436 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
437 label: Some("Stencil Clip Instance Buffer"),
438 contents: bytemuck::bytes_of(&clip_id),
439 usage: BufferUsages::VERTEX,
440 })
441 });
442 }
443
444 pub fn sync_stencil_stack(
445 &mut self,
446 device: &Device,
447 queue: &Queue,
448 depth: u32,
449 clips: &[ClipDraw<'_>],
450 ) {
451 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
452 label: Some("Stencil Sync Encoder"),
453 });
454 self.encode_stencil_stack_sync(device, &mut encoder, depth, clips);
455 queue.submit(std::iter::once(encoder.finish()));
456 }
457
458 pub fn encode_stencil_stack_sync(
459 &mut self,
460 device: &Device,
461 encoder: &mut wgpu::CommandEncoder,
462 depth: u32,
463 clips: &[ClipDraw<'_>],
464 ) {
465 let prepared = self.prepare_stencil_stack_sync(device, depth, clips);
466 if prepared.is_empty() {
467 return;
468 }
469
470 {
471 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
472 label: Some("Stencil Sync Pass"),
473 color_attachments: &[None],
474 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
475 view: &self.stencil_view,
476 depth_ops: None,
477 stencil_ops: Some(wgpu::Operations {
478 load: wgpu::LoadOp::Load,
479 store: wgpu::StoreOp::Store,
480 }),
481 }),
482 timestamp_writes: None,
483 occlusion_query_set: None,
484 multiview_mask: None,
485 });
486 self.encode_prepared_stencil_sync(&mut render_pass, &prepared, false);
487 }
488 }
489
490 pub(crate) fn prepare_stencil_stack_sync(
491 &mut self,
492 device: &Device,
493 depth: u32,
494 clips: &[ClipDraw<'_>],
495 ) -> PreparedStencilSync {
496 let mut prepared = PreparedStencilSync::default();
497 while self.stencil_layer > depth {
498 let Some(entry) = self.stencil_geometry_stack.pop() else {
499 log::error!("geometry stack shouldn't be embty when stencil layer > 0");
500 break;
501 };
502 prepared.popped.push(entry);
503 self.stencil_layer = self.stencil_layer.saturating_sub(1);
504 }
505
506 for clip in clips {
507 self.ensure_cached_geometry(device, clip.geometry_signature, clip.geometry);
508 self.ensure_cached_clip_instance(device, clip.clip_id);
509 }
510
511 for clip in clips {
512 let entry = StencilStackEntry {
513 clip_id: clip.clip_id,
514 geometry_signature: clip.geometry_signature,
515 };
516 self.stencil_geometry_stack.push(entry);
517 self.stencil_layer += 1;
518 prepared.pushed.push(entry);
519 }
520
521 prepared
522 }
523
524 pub(crate) fn encode_prepared_stencil_sync<'pass>(
525 &'pass self,
526 render_pass: &mut wgpu::RenderPass<'pass>,
527 prepared: &'pass PreparedStencilSync,
528 has_color_attachment: bool,
529 ) {
530 if prepared.is_empty() {
531 return;
532 }
533
534 render_pass.set_bind_group(0, &self.stencil_bind_group, &[]);
535
536 if !prepared.popped.is_empty() {
537 let pipeline = if has_color_attachment {
538 self.color_decrement_pipeline.as_ref()
539 } else {
540 self.decrement_pipeline.as_ref()
541 };
542 render_pass.set_pipeline(pipeline);
543 for entry in &prepared.popped {
544 self.draw_stencil_entry(render_pass, entry);
545 }
546 }
547
548 if !prepared.pushed.is_empty() {
549 let pipeline = if has_color_attachment {
550 self.color_stencil_pipeline.as_ref()
551 } else {
552 self.stencil_pipeline.as_ref()
553 };
554 render_pass.set_pipeline(pipeline);
555 for entry in &prepared.pushed {
556 self.draw_stencil_entry(render_pass, entry);
557 }
558 }
559 }
560
561 fn draw_stencil_entry<'pass>(
562 &'pass self,
563 render_pass: &mut wgpu::RenderPass<'pass>,
564 entry: &StencilStackEntry,
565 ) {
566 let Some(cached_geometry) = self.cached_geometry.get(&entry.geometry_signature) else {
567 log::error!(
568 "missing cached stencil geometry for signature {}",
569 entry.geometry_signature
570 );
571 return;
572 };
573 if !has_drawable_stencil_geometry(cached_geometry.index_count) {
578 return;
579 }
580 let Some(instance_buffer) = self.cached_clip_instances.get(&entry.clip_id) else {
581 log::error!(
582 "missing cached stencil clip instance for clip {}",
583 entry.clip_id
584 );
585 return;
586 };
587 render_pass.set_bind_group(
588 1,
589 self.transforms
590 .borrow()
591 .bind_group(entry.clip_id as usize / TRANSFORMS_PER_PAGE),
592 &[],
593 );
594 render_pass.set_vertex_buffer(0, cached_geometry.vertices_buffer.slice(..));
595 render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
596 render_pass.set_index_buffer(
597 cached_geometry.indices_buffer.slice(..),
598 wgpu::IndexFormat::Uint16,
599 );
600 render_pass.draw_indexed(0..cached_geometry.index_count, 0, 0..1);
601 }
602
603 pub(crate) fn update_transforms(
604 &mut self,
605 device: &Device,
606 queue: &Queue,
607 transforms: &[GpuTransform],
608 ) {
609 self.transforms
610 .borrow_mut()
611 .update(device, queue, transforms);
612 }
613
614 pub fn needs_stack_sync(&self, depth: u32, clips: &[ClipDraw<'_>]) -> bool {
615 self.stencil_layer > depth || !clips.is_empty()
616 }
617
618 pub fn retain_cached_resources(
619 &mut self,
620 active_signatures: &HashSet<u64>,
621 active_clip_ids: &HashSet<u32>,
622 ) {
623 let stack_signatures: HashSet<u64> = self
624 .stencil_geometry_stack
625 .iter()
626 .map(|entry| entry.geometry_signature)
627 .collect();
628 self.cached_geometry.retain(|signature, _| {
629 active_signatures.contains(signature) || stack_signatures.contains(signature)
630 });
631 let stack_clip_ids: HashSet<u32> = self
632 .stencil_geometry_stack
633 .iter()
634 .map(|entry| entry.clip_id)
635 .collect();
636 self.cached_clip_instances.retain(|clip_id, _| {
637 active_clip_ids.contains(clip_id) || stack_clip_ids.contains(clip_id)
638 });
639 }
640
641 pub fn get_stencil(&self) -> (&wgpu::TextureView, u32) {
642 (&self.stencil_view, self.stencil_layer)
643 }
644
645 pub(crate) fn stencil_view_clone(&self) -> wgpu::TextureView {
646 self.stencil_view.clone()
647 }
648
649 pub fn clear(&mut self, device: &Device, queue: &Queue) {
650 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
651 label: Some("Stencil Clear Encoder"),
652 });
653 self.encode_clear(&mut encoder);
654 queue.submit(std::iter::once(encoder.finish()));
655 }
656
657 pub fn encode_clear(&mut self, encoder: &mut wgpu::CommandEncoder) {
658 {
659 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
660 label: Some("Stencil Clear Pass"),
661 color_attachments: &[None],
662 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
663 view: &self.stencil_view,
664 depth_ops: None,
665 stencil_ops: Some(wgpu::Operations {
666 load: wgpu::LoadOp::Clear(0),
667 store: wgpu::StoreOp::Store,
668 }),
669 }),
670 timestamp_writes: None,
671 occlusion_query_set: None,
672 multiview_mask: None,
673 });
674
675 render_pass.set_pipeline(self.stencil_pipeline.as_ref());
676 }
677
678 self.stencil_layer = 0;
679 self.stencil_geometry_stack.clear();
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 #[test]
688 fn empty_stencil_geometry_is_not_submitted_to_wgpu() {
689 assert!(!has_drawable_stencil_geometry(0));
690 assert!(has_drawable_stencil_geometry(3));
691 }
692}