Skip to main content

jag_draw/
pipeline.rs

1use std::sync::Arc;
2use wgpu::util::DeviceExt;
3
4use crate::upload::GpuScene;
5
6pub struct BasicSolidRenderer {
7    pipeline: wgpu::RenderPipeline,
8    bgl: wgpu::BindGroupLayout,
9    z_bgl: wgpu::BindGroupLayout,
10}
11
12impl BasicSolidRenderer {
13    pub fn new(
14        device: Arc<wgpu::Device>,
15        target_format: wgpu::TextureFormat,
16        sample_count: u32,
17    ) -> Self {
18        Self::new_with_depth_state(
19            device,
20            target_format,
21            sample_count,
22            true,
23            wgpu::CompareFunction::LessEqual,
24        )
25    }
26
27    pub fn new_with_depth_write(
28        device: Arc<wgpu::Device>,
29        target_format: wgpu::TextureFormat,
30        sample_count: u32,
31        depth_write_enabled: bool,
32    ) -> Self {
33        Self::new_with_depth_state(
34            device,
35            target_format,
36            sample_count,
37            depth_write_enabled,
38            wgpu::CompareFunction::LessEqual,
39        )
40    }
41
42    pub fn new_with_depth_state(
43        device: Arc<wgpu::Device>,
44        target_format: wgpu::TextureFormat,
45        sample_count: u32,
46        depth_write_enabled: bool,
47        depth_compare: wgpu::CompareFunction,
48    ) -> Self {
49        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
50            label: Some("solid-shader"),
51            source: wgpu::ShaderSource::Wgsl(jag_shaders::SOLID_WGSL.into()),
52        });
53
54        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
55            label: Some("solid-vp-bgl"),
56            entries: &[wgpu::BindGroupLayoutEntry {
57                binding: 0,
58                visibility: wgpu::ShaderStages::VERTEX,
59                ty: wgpu::BindingType::Buffer {
60                    ty: wgpu::BufferBindingType::Uniform,
61                    has_dynamic_offset: false,
62                    min_binding_size: std::num::NonZeroU64::new(32),
63                },
64                count: None,
65            }],
66        });
67
68        let z_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
69            label: Some("solid-z-bgl"),
70            entries: &[wgpu::BindGroupLayoutEntry {
71                binding: 0,
72                visibility: wgpu::ShaderStages::VERTEX,
73                ty: wgpu::BindingType::Buffer {
74                    ty: wgpu::BufferBindingType::Uniform,
75                    has_dynamic_offset: false,
76                    min_binding_size: std::num::NonZeroU64::new(4),
77                },
78                count: None,
79            }],
80        });
81
82        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
83            label: Some("solid-pipeline-layout"),
84            bind_group_layouts: &[&bgl],
85            push_constant_ranges: &[],
86        });
87
88        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
89            label: Some("solid-pipeline"),
90            layout: Some(&layout),
91            vertex: wgpu::VertexState {
92                module: &shader,
93                entry_point: "vs_main",
94                buffers: &[wgpu::VertexBufferLayout {
95                    array_stride: std::mem::size_of::<crate::upload::Vertex>() as u64,
96                    step_mode: wgpu::VertexStepMode::Vertex,
97                    attributes: &[
98                        wgpu::VertexAttribute {
99                            offset: 0,
100                            shader_location: 0,
101                            format: wgpu::VertexFormat::Float32x2,
102                        },
103                        wgpu::VertexAttribute {
104                            offset: 8,
105                            shader_location: 1,
106                            format: wgpu::VertexFormat::Float32x4,
107                        },
108                        wgpu::VertexAttribute {
109                            offset: 24,
110                            shader_location: 2,
111                            format: wgpu::VertexFormat::Float32,
112                        },
113                    ],
114                }],
115            },
116            fragment: Some(wgpu::FragmentState {
117                module: &shader,
118                entry_point: "fs_main",
119                targets: &[Some(wgpu::ColorTargetState {
120                    format: target_format,
121                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
122                    write_mask: wgpu::ColorWrites::ALL,
123                })],
124            }),
125            primitive: wgpu::PrimitiveState::default(),
126            depth_stencil: Some(wgpu::DepthStencilState {
127                format: wgpu::TextureFormat::Depth32Float,
128                depth_write_enabled,
129                depth_compare,
130                stencil: wgpu::StencilState::default(),
131                bias: wgpu::DepthBiasState::default(),
132            }),
133            multisample: wgpu::MultisampleState {
134                count: sample_count,
135                ..Default::default()
136            },
137            multiview: None,
138        });
139
140        Self {
141            pipeline,
142            bgl,
143            z_bgl,
144        }
145    }
146
147    pub fn record<'a>(
148        &'a self,
149        pass: &mut wgpu::RenderPass<'a>,
150        vp_bg: &'a wgpu::BindGroup,
151        scene: &'a GpuScene,
152    ) {
153        pass.set_pipeline(&self.pipeline);
154        pass.set_bind_group(0, vp_bg, &[]);
155        pass.set_vertex_buffer(0, scene.vertex.buffer.slice(..));
156        pass.set_index_buffer(scene.index.buffer.slice(..), wgpu::IndexFormat::Uint16);
157        pass.draw_indexed(0..scene.indices, 0, 0..1);
158    }
159
160    pub fn record_index_range<'a>(
161        &'a self,
162        pass: &mut wgpu::RenderPass<'a>,
163        vp_bg: &'a wgpu::BindGroup,
164        scene: &'a GpuScene,
165        index_start: u32,
166        index_count: u32,
167    ) {
168        if index_count == 0 {
169            return;
170        }
171        pass.set_pipeline(&self.pipeline);
172        pass.set_bind_group(0, vp_bg, &[]);
173        pass.set_vertex_buffer(0, scene.vertex.buffer.slice(..));
174        pass.set_index_buffer(scene.index.buffer.slice(..), wgpu::IndexFormat::Uint16);
175        pass.draw_indexed(index_start..(index_start + index_count), 0, 0..1);
176    }
177
178    pub fn viewport_bgl(&self) -> &wgpu::BindGroupLayout {
179        &self.bgl
180    }
181
182    pub fn z_index_bgl(&self) -> &wgpu::BindGroupLayout {
183        &self.z_bgl
184    }
185}
186
187/// Solid renderer variant for overlays that do not participate in depth testing.
188/// Uses the same SOLID_WGSL shader and vertex layout as `BasicSolidRenderer`, but
189/// disables depth-stencil so overlay quads simply blend over existing content.
190pub struct OverlaySolidRenderer {
191    pipeline: wgpu::RenderPipeline,
192    bgl: wgpu::BindGroupLayout,
193}
194
195impl OverlaySolidRenderer {
196    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
197        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
198            label: Some("overlay-solid-shader"),
199            source: wgpu::ShaderSource::Wgsl(jag_shaders::SOLID_WGSL.into()),
200        });
201
202        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
203            label: Some("overlay-solid-vp-bgl"),
204            entries: &[wgpu::BindGroupLayoutEntry {
205                binding: 0,
206                visibility: wgpu::ShaderStages::VERTEX,
207                ty: wgpu::BindingType::Buffer {
208                    ty: wgpu::BufferBindingType::Uniform,
209                    has_dynamic_offset: false,
210                    min_binding_size: std::num::NonZeroU64::new(32),
211                },
212                count: None,
213            }],
214        });
215
216        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
217            label: Some("overlay-solid-pipeline-layout"),
218            bind_group_layouts: &[&bgl],
219            push_constant_ranges: &[],
220        });
221
222        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
223            label: Some("overlay-solid-pipeline"),
224            layout: Some(&layout),
225            vertex: wgpu::VertexState {
226                module: &shader,
227                entry_point: "vs_main",
228                buffers: &[wgpu::VertexBufferLayout {
229                    array_stride: std::mem::size_of::<crate::upload::Vertex>() as u64,
230                    step_mode: wgpu::VertexStepMode::Vertex,
231                    attributes: &[
232                        wgpu::VertexAttribute {
233                            offset: 0,
234                            shader_location: 0,
235                            format: wgpu::VertexFormat::Float32x2,
236                        },
237                        wgpu::VertexAttribute {
238                            offset: 8,
239                            shader_location: 1,
240                            format: wgpu::VertexFormat::Float32x4,
241                        },
242                        wgpu::VertexAttribute {
243                            offset: 24,
244                            shader_location: 2,
245                            format: wgpu::VertexFormat::Float32,
246                        },
247                    ],
248                }],
249            },
250            fragment: Some(wgpu::FragmentState {
251                module: &shader,
252                entry_point: "fs_main",
253                targets: &[Some(wgpu::ColorTargetState {
254                    format: target_format,
255                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
256                    write_mask: wgpu::ColorWrites::ALL,
257                })],
258            }),
259            primitive: wgpu::PrimitiveState::default(),
260            depth_stencil: None,
261            multisample: wgpu::MultisampleState::default(),
262            multiview: None,
263        });
264
265        Self { pipeline, bgl }
266    }
267
268    pub fn viewport_bgl(&self) -> &wgpu::BindGroupLayout {
269        &self.bgl
270    }
271
272    pub fn record<'a>(
273        &'a self,
274        pass: &mut wgpu::RenderPass<'a>,
275        vp_bg: &'a wgpu::BindGroup,
276        vbuf: &'a wgpu::Buffer,
277        ibuf: &'a wgpu::Buffer,
278        icount: u32,
279    ) {
280        pass.set_pipeline(&self.pipeline);
281        pass.set_bind_group(0, vp_bg, &[]);
282        pass.set_vertex_buffer(0, vbuf.slice(..));
283        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
284        pass.draw_indexed(0..icount, 0, 0..1);
285    }
286}
287
288/// Scrim renderer that blends over all existing content without depth testing.
289/// Uses depth_write_enabled=false and depth_compare=Always so the scrim:
290/// 1. Always passes depth test (renders regardless of existing depth)
291/// 2. Doesn't write to depth buffer (allows content at higher z to render on top)
292/// This is ideal for modal scrim overlays that should dim background content
293/// while allowing the modal panel to render on top.
294pub struct ScrimSolidRenderer {
295    pipeline: wgpu::RenderPipeline,
296    bgl: wgpu::BindGroupLayout,
297}
298
299impl ScrimSolidRenderer {
300    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
301        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
302            label: Some("scrim-solid-shader"),
303            source: wgpu::ShaderSource::Wgsl(jag_shaders::SOLID_WGSL.into()),
304        });
305
306        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
307            label: Some("scrim-solid-vp-bgl"),
308            entries: &[wgpu::BindGroupLayoutEntry {
309                binding: 0,
310                visibility: wgpu::ShaderStages::VERTEX,
311                ty: wgpu::BindingType::Buffer {
312                    ty: wgpu::BufferBindingType::Uniform,
313                    has_dynamic_offset: false,
314                    min_binding_size: std::num::NonZeroU64::new(32),
315                },
316                count: None,
317            }],
318        });
319
320        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
321            label: Some("scrim-solid-pipeline-layout"),
322            bind_group_layouts: &[&bgl],
323            push_constant_ranges: &[],
324        });
325
326        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
327            label: Some("scrim-solid-pipeline"),
328            layout: Some(&layout),
329            vertex: wgpu::VertexState {
330                module: &shader,
331                entry_point: "vs_main",
332                buffers: &[wgpu::VertexBufferLayout {
333                    array_stride: std::mem::size_of::<crate::upload::Vertex>() as u64,
334                    step_mode: wgpu::VertexStepMode::Vertex,
335                    attributes: &[
336                        wgpu::VertexAttribute {
337                            offset: 0,
338                            shader_location: 0,
339                            format: wgpu::VertexFormat::Float32x2,
340                        },
341                        wgpu::VertexAttribute {
342                            offset: 8,
343                            shader_location: 1,
344                            format: wgpu::VertexFormat::Float32x4,
345                        },
346                        wgpu::VertexAttribute {
347                            offset: 24,
348                            shader_location: 2,
349                            format: wgpu::VertexFormat::Float32,
350                        },
351                    ],
352                }],
353            },
354            fragment: Some(wgpu::FragmentState {
355                module: &shader,
356                entry_point: "fs_main",
357                targets: &[Some(wgpu::ColorTargetState {
358                    format: target_format,
359                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
360                    write_mask: wgpu::ColorWrites::ALL,
361                })],
362            }),
363            primitive: wgpu::PrimitiveState::default(),
364            // No depth attachment - scrim renders directly to surface without depth testing
365            depth_stencil: None,
366            // No MSAA - scrim renders directly to final surface
367            multisample: wgpu::MultisampleState::default(),
368            multiview: None,
369        });
370
371        Self { pipeline, bgl }
372    }
373
374    pub fn viewport_bgl(&self) -> &wgpu::BindGroupLayout {
375        &self.bgl
376    }
377
378    pub fn record<'a>(
379        &'a self,
380        pass: &mut wgpu::RenderPass<'a>,
381        vp_bg: &'a wgpu::BindGroup,
382        vbuf: &'a wgpu::Buffer,
383        ibuf: &'a wgpu::Buffer,
384        icount: u32,
385    ) {
386        pass.set_pipeline(&self.pipeline);
387        pass.set_bind_group(0, vp_bg, &[]);
388        pass.set_vertex_buffer(0, vbuf.slice(..));
389        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
390        pass.draw_indexed(0..icount, 0, 0..1);
391    }
392}
393
394/// Stencil-only mask writer for scrim cutouts (color writes disabled).
395pub struct ScrimStencilMaskRenderer {
396    pipeline: wgpu::RenderPipeline,
397    bgl: wgpu::BindGroupLayout,
398}
399
400impl ScrimStencilMaskRenderer {
401    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
402        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
403            label: Some("scrim-stencil-mask-shader"),
404            source: wgpu::ShaderSource::Wgsl(jag_shaders::SOLID_WGSL.into()),
405        });
406
407        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
408            label: Some("scrim-stencil-mask-vp-bgl"),
409            entries: &[wgpu::BindGroupLayoutEntry {
410                binding: 0,
411                visibility: wgpu::ShaderStages::VERTEX,
412                ty: wgpu::BindingType::Buffer {
413                    ty: wgpu::BufferBindingType::Uniform,
414                    has_dynamic_offset: false,
415                    min_binding_size: std::num::NonZeroU64::new(32),
416                },
417                count: None,
418            }],
419        });
420
421        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
422            label: Some("scrim-stencil-mask-pipeline-layout"),
423            bind_group_layouts: &[&bgl],
424            push_constant_ranges: &[],
425        });
426
427        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
428            label: Some("scrim-stencil-mask-pipeline"),
429            layout: Some(&layout),
430            vertex: wgpu::VertexState {
431                module: &shader,
432                entry_point: "vs_main",
433                buffers: &[wgpu::VertexBufferLayout {
434                    array_stride: std::mem::size_of::<crate::upload::Vertex>() as u64,
435                    step_mode: wgpu::VertexStepMode::Vertex,
436                    attributes: &[
437                        wgpu::VertexAttribute {
438                            offset: 0,
439                            shader_location: 0,
440                            format: wgpu::VertexFormat::Float32x2,
441                        },
442                        wgpu::VertexAttribute {
443                            offset: 8,
444                            shader_location: 1,
445                            format: wgpu::VertexFormat::Float32x4,
446                        },
447                        wgpu::VertexAttribute {
448                            offset: 24,
449                            shader_location: 2,
450                            format: wgpu::VertexFormat::Float32,
451                        },
452                    ],
453                }],
454            },
455            fragment: Some(wgpu::FragmentState {
456                module: &shader,
457                entry_point: "fs_main",
458                targets: &[Some(wgpu::ColorTargetState {
459                    format: target_format,
460                    blend: Some(wgpu::BlendState::REPLACE),
461                    write_mask: wgpu::ColorWrites::empty(), // disable color writes
462                })],
463            }),
464            primitive: wgpu::PrimitiveState::default(),
465            depth_stencil: Some(wgpu::DepthStencilState {
466                format: wgpu::TextureFormat::Depth24PlusStencil8,
467                depth_write_enabled: false,
468                depth_compare: wgpu::CompareFunction::Always,
469                stencil: wgpu::StencilState {
470                    front: wgpu::StencilFaceState {
471                        compare: wgpu::CompareFunction::Always,
472                        fail_op: wgpu::StencilOperation::Replace,
473                        depth_fail_op: wgpu::StencilOperation::Replace,
474                        pass_op: wgpu::StencilOperation::Replace,
475                    },
476                    back: wgpu::StencilFaceState {
477                        compare: wgpu::CompareFunction::Always,
478                        fail_op: wgpu::StencilOperation::Replace,
479                        depth_fail_op: wgpu::StencilOperation::Replace,
480                        pass_op: wgpu::StencilOperation::Replace,
481                    },
482                    read_mask: 0xFF,
483                    write_mask: 0xFF,
484                },
485                bias: wgpu::DepthBiasState::default(),
486            }),
487            multisample: wgpu::MultisampleState::default(),
488            multiview: None,
489        });
490
491        Self { pipeline, bgl }
492    }
493
494    pub fn viewport_bgl(&self) -> &wgpu::BindGroupLayout {
495        &self.bgl
496    }
497
498    pub fn record<'a>(
499        &'a self,
500        pass: &mut wgpu::RenderPass<'a>,
501        vp_bg: &'a wgpu::BindGroup,
502        vbuf: &'a wgpu::Buffer,
503        ibuf: &'a wgpu::Buffer,
504        icount: u32,
505    ) {
506        pass.set_pipeline(&self.pipeline);
507        pass.set_bind_group(0, vp_bg, &[]);
508        pass.set_vertex_buffer(0, vbuf.slice(..));
509        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
510        pass.draw_indexed(0..icount, 0, 0..1);
511    }
512}
513
514/// Scrim renderer that clips against stencil (hole stays transparent).
515pub struct ScrimStencilRenderer {
516    pipeline: wgpu::RenderPipeline,
517    bgl: wgpu::BindGroupLayout,
518}
519
520impl ScrimStencilRenderer {
521    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
522        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
523            label: Some("scrim-stencil-shader"),
524            source: wgpu::ShaderSource::Wgsl(jag_shaders::SOLID_WGSL.into()),
525        });
526
527        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
528            label: Some("scrim-stencil-vp-bgl"),
529            entries: &[wgpu::BindGroupLayoutEntry {
530                binding: 0,
531                visibility: wgpu::ShaderStages::VERTEX,
532                ty: wgpu::BindingType::Buffer {
533                    ty: wgpu::BufferBindingType::Uniform,
534                    has_dynamic_offset: false,
535                    min_binding_size: std::num::NonZeroU64::new(32),
536                },
537                count: None,
538            }],
539        });
540
541        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
542            label: Some("scrim-stencil-pipeline-layout"),
543            bind_group_layouts: &[&bgl],
544            push_constant_ranges: &[],
545        });
546
547        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
548            label: Some("scrim-stencil-pipeline"),
549            layout: Some(&layout),
550            vertex: wgpu::VertexState {
551                module: &shader,
552                entry_point: "vs_main",
553                buffers: &[wgpu::VertexBufferLayout {
554                    array_stride: std::mem::size_of::<crate::upload::Vertex>() as u64,
555                    step_mode: wgpu::VertexStepMode::Vertex,
556                    attributes: &[
557                        wgpu::VertexAttribute {
558                            offset: 0,
559                            shader_location: 0,
560                            format: wgpu::VertexFormat::Float32x2,
561                        },
562                        wgpu::VertexAttribute {
563                            offset: 8,
564                            shader_location: 1,
565                            format: wgpu::VertexFormat::Float32x4,
566                        },
567                        wgpu::VertexAttribute {
568                            offset: 24,
569                            shader_location: 2,
570                            format: wgpu::VertexFormat::Float32,
571                        },
572                    ],
573                }],
574            },
575            fragment: Some(wgpu::FragmentState {
576                module: &shader,
577                entry_point: "fs_main",
578                targets: &[Some(wgpu::ColorTargetState {
579                    format: target_format,
580                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
581                    write_mask: wgpu::ColorWrites::ALL,
582                })],
583            }),
584            primitive: wgpu::PrimitiveState::default(),
585            depth_stencil: Some(wgpu::DepthStencilState {
586                format: wgpu::TextureFormat::Depth24PlusStencil8,
587                depth_write_enabled: false,
588                depth_compare: wgpu::CompareFunction::Always,
589                stencil: wgpu::StencilState {
590                    front: wgpu::StencilFaceState {
591                        compare: wgpu::CompareFunction::Equal,
592                        fail_op: wgpu::StencilOperation::Keep,
593                        depth_fail_op: wgpu::StencilOperation::Keep,
594                        pass_op: wgpu::StencilOperation::Keep,
595                    },
596                    back: wgpu::StencilFaceState {
597                        compare: wgpu::CompareFunction::Equal,
598                        fail_op: wgpu::StencilOperation::Keep,
599                        depth_fail_op: wgpu::StencilOperation::Keep,
600                        pass_op: wgpu::StencilOperation::Keep,
601                    },
602                    read_mask: 0xFF,
603                    write_mask: 0x00,
604                },
605                bias: wgpu::DepthBiasState::default(),
606            }),
607            multisample: wgpu::MultisampleState::default(),
608            multiview: None,
609        });
610
611        Self { pipeline, bgl }
612    }
613
614    pub fn viewport_bgl(&self) -> &wgpu::BindGroupLayout {
615        &self.bgl
616    }
617
618    pub fn record<'a>(
619        &'a self,
620        pass: &mut wgpu::RenderPass<'a>,
621        vp_bg: &'a wgpu::BindGroup,
622        vbuf: &'a wgpu::Buffer,
623        ibuf: &'a wgpu::Buffer,
624        icount: u32,
625    ) {
626        pass.set_pipeline(&self.pipeline);
627        pass.set_bind_group(0, vp_bg, &[]);
628        pass.set_vertex_buffer(0, vbuf.slice(..));
629        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
630        pass.set_stencil_reference(0);
631        pass.draw_indexed(0..icount, 0, 0..1);
632    }
633}
634
635pub struct Compositor {
636    pipeline: wgpu::RenderPipeline,
637    bgl: wgpu::BindGroupLayout,
638    sampler: wgpu::Sampler,
639}
640
641impl Compositor {
642    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
643        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
644            label: Some("compositor-shader"),
645            source: wgpu::ShaderSource::Wgsl(jag_shaders::COMPOSITOR_WGSL.into()),
646        });
647
648        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
649            label: Some("compositor-bgl"),
650            entries: &[
651                wgpu::BindGroupLayoutEntry {
652                    binding: 0,
653                    visibility: wgpu::ShaderStages::FRAGMENT,
654                    ty: wgpu::BindingType::Texture {
655                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
656                        view_dimension: wgpu::TextureViewDimension::D2,
657                        multisampled: false,
658                    },
659                    count: None,
660                },
661                wgpu::BindGroupLayoutEntry {
662                    binding: 1,
663                    visibility: wgpu::ShaderStages::FRAGMENT,
664                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
665                    count: None,
666                },
667            ],
668        });
669
670        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
671            label: Some("compositor-pipeline-layout"),
672            bind_group_layouts: &[&bgl],
673            push_constant_ranges: &[],
674        });
675
676        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
677            label: Some("compositor-pipeline"),
678            layout: Some(&layout),
679            vertex: wgpu::VertexState {
680                module: &shader,
681                entry_point: "vs_main",
682                buffers: &[],
683            },
684            fragment: Some(wgpu::FragmentState {
685                module: &shader,
686                entry_point: "fs_main",
687                targets: &[Some(wgpu::ColorTargetState {
688                    format: target_format,
689                    blend: Some(wgpu::BlendState::REPLACE),
690                    write_mask: wgpu::ColorWrites::ALL,
691                })],
692            }),
693            primitive: wgpu::PrimitiveState::default(),
694            depth_stencil: None,
695            multisample: wgpu::MultisampleState::default(),
696            multiview: None,
697        });
698
699        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
700            label: Some("compositor-sampler"),
701            mag_filter: wgpu::FilterMode::Linear,
702            min_filter: wgpu::FilterMode::Linear,
703            mipmap_filter: wgpu::FilterMode::Nearest,
704            address_mode_u: wgpu::AddressMode::ClampToEdge,
705            address_mode_v: wgpu::AddressMode::ClampToEdge,
706            address_mode_w: wgpu::AddressMode::ClampToEdge,
707            ..Default::default()
708        });
709
710        Self {
711            pipeline,
712            bgl,
713            sampler,
714        }
715    }
716
717    pub fn bind_group(
718        &self,
719        device: &wgpu::Device,
720        tex_view: &wgpu::TextureView,
721    ) -> wgpu::BindGroup {
722        device.create_bind_group(&wgpu::BindGroupDescriptor {
723            label: Some("compositor-bg"),
724            layout: &self.bgl,
725            entries: &[
726                wgpu::BindGroupEntry {
727                    binding: 0,
728                    resource: wgpu::BindingResource::TextureView(tex_view),
729                },
730                wgpu::BindGroupEntry {
731                    binding: 1,
732                    resource: wgpu::BindingResource::Sampler(&self.sampler),
733                },
734            ],
735        })
736    }
737
738    pub fn record<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
739        pass.set_pipeline(&self.pipeline);
740        pass.set_bind_group(0, bg, &[]);
741        pass.draw(0..3, 0..1);
742    }
743}
744
745pub struct Blitter {
746    pipeline: wgpu::RenderPipeline,
747    bgl: wgpu::BindGroupLayout,
748    sampler: wgpu::Sampler,
749}
750
751impl Blitter {
752    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
753        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
754            label: Some("blit-shader"),
755            source: wgpu::ShaderSource::Wgsl(jag_shaders::BLIT_WGSL.into()),
756        });
757
758        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
759            label: Some("blit-bgl"),
760            entries: &[
761                wgpu::BindGroupLayoutEntry {
762                    binding: 0,
763                    visibility: wgpu::ShaderStages::FRAGMENT,
764                    ty: wgpu::BindingType::Texture {
765                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
766                        view_dimension: wgpu::TextureViewDimension::D2,
767                        multisampled: false,
768                    },
769                    count: None,
770                },
771                wgpu::BindGroupLayoutEntry {
772                    binding: 1,
773                    visibility: wgpu::ShaderStages::FRAGMENT,
774                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
775                    count: None,
776                },
777            ],
778        });
779
780        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
781            label: Some("blit-pipeline-layout"),
782            bind_group_layouts: &[&bgl],
783            push_constant_ranges: &[],
784        });
785
786        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
787            label: Some("blit-pipeline"),
788            layout: Some(&layout),
789            vertex: wgpu::VertexState {
790                module: &shader,
791                entry_point: "vs_main",
792                buffers: &[],
793            },
794            fragment: Some(wgpu::FragmentState {
795                module: &shader,
796                entry_point: "fs_main",
797                targets: &[Some(wgpu::ColorTargetState {
798                    format: target_format,
799                    blend: Some(wgpu::BlendState::REPLACE),
800                    write_mask: wgpu::ColorWrites::ALL,
801                })],
802            }),
803            primitive: wgpu::PrimitiveState::default(),
804            depth_stencil: None,
805            multisample: wgpu::MultisampleState::default(),
806            multiview: None,
807        });
808
809        // Use nearest-neighbor sampling for fastest blit
810        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
811            label: Some("blit-sampler"),
812            mag_filter: wgpu::FilterMode::Nearest,
813            min_filter: wgpu::FilterMode::Nearest,
814            mipmap_filter: wgpu::FilterMode::Nearest,
815            address_mode_u: wgpu::AddressMode::ClampToEdge,
816            address_mode_v: wgpu::AddressMode::ClampToEdge,
817            address_mode_w: wgpu::AddressMode::ClampToEdge,
818            ..Default::default()
819        });
820
821        Self {
822            pipeline,
823            bgl,
824            sampler,
825        }
826    }
827
828    pub fn bind_group(
829        &self,
830        device: &wgpu::Device,
831        tex_view: &wgpu::TextureView,
832    ) -> wgpu::BindGroup {
833        device.create_bind_group(&wgpu::BindGroupDescriptor {
834            label: Some("blit-bg"),
835            layout: &self.bgl,
836            entries: &[
837                wgpu::BindGroupEntry {
838                    binding: 0,
839                    resource: wgpu::BindingResource::TextureView(tex_view),
840                },
841                wgpu::BindGroupEntry {
842                    binding: 1,
843                    resource: wgpu::BindingResource::Sampler(&self.sampler),
844                },
845            ],
846        })
847    }
848
849    pub fn record<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
850        pass.set_pipeline(&self.pipeline);
851        pass.set_bind_group(0, bg, &[]);
852        pass.draw(0..3, 0..1);
853    }
854}
855
856pub struct SmaaRenderer {
857    edge_bgl: wgpu::BindGroupLayout,
858    blend_bgl: wgpu::BindGroupLayout,
859    resolve_bgl: wgpu::BindGroupLayout,
860    edge_pipeline: wgpu::RenderPipeline,
861    blend_pipeline: wgpu::RenderPipeline,
862    resolve_pipeline: wgpu::RenderPipeline,
863    pub sampler_linear: wgpu::Sampler,
864    pub sampler_nearest: wgpu::Sampler,
865}
866
867impl SmaaRenderer {
868    pub fn new(device: Arc<wgpu::Device>, output_format: wgpu::TextureFormat) -> Self {
869        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
870            label: Some("smaa-shader"),
871            source: wgpu::ShaderSource::Wgsl(jag_shaders::SMAA_WGSL.into()),
872        });
873
874        let edge_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
875            label: Some("smaa-edge-bgl"),
876            entries: &[
877                wgpu::BindGroupLayoutEntry {
878                    binding: 0,
879                    visibility: wgpu::ShaderStages::FRAGMENT,
880                    ty: wgpu::BindingType::Texture {
881                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
882                        view_dimension: wgpu::TextureViewDimension::D2,
883                        multisampled: false,
884                    },
885                    count: None,
886                },
887                wgpu::BindGroupLayoutEntry {
888                    binding: 1,
889                    visibility: wgpu::ShaderStages::FRAGMENT,
890                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
891                    count: None,
892                },
893                wgpu::BindGroupLayoutEntry {
894                    binding: 2,
895                    visibility: wgpu::ShaderStages::FRAGMENT,
896                    ty: wgpu::BindingType::Buffer {
897                        ty: wgpu::BufferBindingType::Uniform,
898                        has_dynamic_offset: false,
899                        min_binding_size: std::num::NonZeroU64::new(16),
900                    },
901                    count: None,
902                },
903            ],
904        });
905
906        let blend_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
907            label: Some("smaa-blend-bgl"),
908            entries: &[
909                wgpu::BindGroupLayoutEntry {
910                    binding: 0,
911                    visibility: wgpu::ShaderStages::FRAGMENT,
912                    ty: wgpu::BindingType::Texture {
913                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
914                        view_dimension: wgpu::TextureViewDimension::D2,
915                        multisampled: false,
916                    },
917                    count: None,
918                },
919                wgpu::BindGroupLayoutEntry {
920                    binding: 1,
921                    visibility: wgpu::ShaderStages::FRAGMENT,
922                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
923                    count: None,
924                },
925                wgpu::BindGroupLayoutEntry {
926                    binding: 2,
927                    visibility: wgpu::ShaderStages::FRAGMENT,
928                    ty: wgpu::BindingType::Buffer {
929                        ty: wgpu::BufferBindingType::Uniform,
930                        has_dynamic_offset: false,
931                        min_binding_size: std::num::NonZeroU64::new(16),
932                    },
933                    count: None,
934                },
935            ],
936        });
937
938        let resolve_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
939            label: Some("smaa-resolve-bgl"),
940            entries: &[
941                wgpu::BindGroupLayoutEntry {
942                    binding: 0,
943                    visibility: wgpu::ShaderStages::FRAGMENT,
944                    ty: wgpu::BindingType::Texture {
945                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
946                        view_dimension: wgpu::TextureViewDimension::D2,
947                        multisampled: false,
948                    },
949                    count: None,
950                },
951                wgpu::BindGroupLayoutEntry {
952                    binding: 1,
953                    visibility: wgpu::ShaderStages::FRAGMENT,
954                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
955                    count: None,
956                },
957                wgpu::BindGroupLayoutEntry {
958                    binding: 2,
959                    visibility: wgpu::ShaderStages::FRAGMENT,
960                    ty: wgpu::BindingType::Texture {
961                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
962                        view_dimension: wgpu::TextureViewDimension::D2,
963                        multisampled: false,
964                    },
965                    count: None,
966                },
967                wgpu::BindGroupLayoutEntry {
968                    binding: 3,
969                    visibility: wgpu::ShaderStages::FRAGMENT,
970                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
971                    count: None,
972                },
973                wgpu::BindGroupLayoutEntry {
974                    binding: 4,
975                    visibility: wgpu::ShaderStages::FRAGMENT,
976                    ty: wgpu::BindingType::Buffer {
977                        ty: wgpu::BufferBindingType::Uniform,
978                        has_dynamic_offset: false,
979                        min_binding_size: std::num::NonZeroU64::new(16),
980                    },
981                    count: None,
982                },
983            ],
984        });
985
986        let edge_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
987            label: Some("smaa-edge-layout"),
988            bind_group_layouts: &[&edge_bgl],
989            push_constant_ranges: &[],
990        });
991        let blend_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
992            label: Some("smaa-blend-layout"),
993            bind_group_layouts: &[&blend_bgl],
994            push_constant_ranges: &[],
995        });
996        let resolve_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
997            label: Some("smaa-resolve-layout"),
998            bind_group_layouts: &[&resolve_bgl],
999            push_constant_ranges: &[],
1000        });
1001
1002        let edge_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1003            label: Some("smaa-edge-pipeline"),
1004            layout: Some(&edge_layout),
1005            vertex: wgpu::VertexState {
1006                module: &shader,
1007                entry_point: "vs_full",
1008                buffers: &[],
1009            },
1010            fragment: Some(wgpu::FragmentState {
1011                module: &shader,
1012                entry_point: "fs_edges",
1013                targets: &[Some(wgpu::ColorTargetState {
1014                    format: wgpu::TextureFormat::Rgba8Unorm,
1015                    blend: Some(wgpu::BlendState::REPLACE),
1016                    write_mask: wgpu::ColorWrites::ALL,
1017                })],
1018            }),
1019            primitive: wgpu::PrimitiveState::default(),
1020            depth_stencil: None,
1021            multisample: wgpu::MultisampleState::default(),
1022            multiview: None,
1023        });
1024
1025        let blend_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1026            label: Some("smaa-blend-pipeline"),
1027            layout: Some(&blend_layout),
1028            vertex: wgpu::VertexState {
1029                module: &shader,
1030                entry_point: "vs_full",
1031                buffers: &[],
1032            },
1033            fragment: Some(wgpu::FragmentState {
1034                module: &shader,
1035                entry_point: "fs_weights",
1036                targets: &[Some(wgpu::ColorTargetState {
1037                    format: wgpu::TextureFormat::Rgba8Unorm,
1038                    blend: Some(wgpu::BlendState::REPLACE),
1039                    write_mask: wgpu::ColorWrites::ALL,
1040                })],
1041            }),
1042            primitive: wgpu::PrimitiveState::default(),
1043            depth_stencil: None,
1044            multisample: wgpu::MultisampleState::default(),
1045            multiview: None,
1046        });
1047
1048        let resolve_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1049            label: Some("smaa-resolve-pipeline"),
1050            layout: Some(&resolve_layout),
1051            vertex: wgpu::VertexState {
1052                module: &shader,
1053                entry_point: "vs_full",
1054                buffers: &[],
1055            },
1056            fragment: Some(wgpu::FragmentState {
1057                module: &shader,
1058                entry_point: "fs_resolve",
1059                targets: &[Some(wgpu::ColorTargetState {
1060                    format: output_format,
1061                    blend: Some(wgpu::BlendState::REPLACE),
1062                    write_mask: wgpu::ColorWrites::ALL,
1063                })],
1064            }),
1065            primitive: wgpu::PrimitiveState::default(),
1066            depth_stencil: None,
1067            multisample: wgpu::MultisampleState::default(),
1068            multiview: None,
1069        });
1070
1071        let sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
1072            label: Some("smaa-linear-sampler"),
1073            mag_filter: wgpu::FilterMode::Linear,
1074            min_filter: wgpu::FilterMode::Linear,
1075            mipmap_filter: wgpu::FilterMode::Linear,
1076            address_mode_u: wgpu::AddressMode::ClampToEdge,
1077            address_mode_v: wgpu::AddressMode::ClampToEdge,
1078            address_mode_w: wgpu::AddressMode::ClampToEdge,
1079            ..Default::default()
1080        });
1081        let sampler_nearest = device.create_sampler(&wgpu::SamplerDescriptor {
1082            label: Some("smaa-nearest-sampler"),
1083            mag_filter: wgpu::FilterMode::Nearest,
1084            min_filter: wgpu::FilterMode::Nearest,
1085            mipmap_filter: wgpu::FilterMode::Nearest,
1086            address_mode_u: wgpu::AddressMode::ClampToEdge,
1087            address_mode_v: wgpu::AddressMode::ClampToEdge,
1088            address_mode_w: wgpu::AddressMode::ClampToEdge,
1089            ..Default::default()
1090        });
1091
1092        Self {
1093            edge_bgl,
1094            blend_bgl,
1095            resolve_bgl,
1096            edge_pipeline,
1097            blend_pipeline,
1098            resolve_pipeline,
1099            sampler_linear,
1100            sampler_nearest,
1101        }
1102    }
1103
1104    pub fn edge_bind_group(
1105        &self,
1106        device: &wgpu::Device,
1107        color_view: &wgpu::TextureView,
1108        params: &wgpu::Buffer,
1109    ) -> wgpu::BindGroup {
1110        device.create_bind_group(&wgpu::BindGroupDescriptor {
1111            label: Some("smaa-edge-bg"),
1112            layout: &self.edge_bgl,
1113            entries: &[
1114                wgpu::BindGroupEntry {
1115                    binding: 0,
1116                    resource: wgpu::BindingResource::TextureView(color_view),
1117                },
1118                wgpu::BindGroupEntry {
1119                    binding: 1,
1120                    resource: wgpu::BindingResource::Sampler(&self.sampler_nearest),
1121                },
1122                wgpu::BindGroupEntry {
1123                    binding: 2,
1124                    resource: params.as_entire_binding(),
1125                },
1126            ],
1127        })
1128    }
1129
1130    pub fn blend_bind_group(
1131        &self,
1132        device: &wgpu::Device,
1133        edge_view: &wgpu::TextureView,
1134        params: &wgpu::Buffer,
1135    ) -> wgpu::BindGroup {
1136        device.create_bind_group(&wgpu::BindGroupDescriptor {
1137            label: Some("smaa-blend-bg"),
1138            layout: &self.blend_bgl,
1139            entries: &[
1140                wgpu::BindGroupEntry {
1141                    binding: 0,
1142                    resource: wgpu::BindingResource::TextureView(edge_view),
1143                },
1144                wgpu::BindGroupEntry {
1145                    binding: 1,
1146                    resource: wgpu::BindingResource::Sampler(&self.sampler_nearest),
1147                },
1148                wgpu::BindGroupEntry {
1149                    binding: 2,
1150                    resource: params.as_entire_binding(),
1151                },
1152            ],
1153        })
1154    }
1155
1156    pub fn resolve_bind_group(
1157        &self,
1158        device: &wgpu::Device,
1159        color_view: &wgpu::TextureView,
1160        weight_view: &wgpu::TextureView,
1161        params: &wgpu::Buffer,
1162    ) -> wgpu::BindGroup {
1163        device.create_bind_group(&wgpu::BindGroupDescriptor {
1164            label: Some("smaa-resolve-bg"),
1165            layout: &self.resolve_bgl,
1166            entries: &[
1167                wgpu::BindGroupEntry {
1168                    binding: 0,
1169                    resource: wgpu::BindingResource::TextureView(color_view),
1170                },
1171                wgpu::BindGroupEntry {
1172                    binding: 1,
1173                    resource: wgpu::BindingResource::Sampler(&self.sampler_linear),
1174                },
1175                wgpu::BindGroupEntry {
1176                    binding: 2,
1177                    resource: wgpu::BindingResource::TextureView(weight_view),
1178                },
1179                wgpu::BindGroupEntry {
1180                    binding: 3,
1181                    resource: wgpu::BindingResource::Sampler(&self.sampler_linear),
1182                },
1183                wgpu::BindGroupEntry {
1184                    binding: 4,
1185                    resource: params.as_entire_binding(),
1186                },
1187            ],
1188        })
1189    }
1190
1191    pub fn record_edges<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1192        pass.set_pipeline(&self.edge_pipeline);
1193        pass.set_bind_group(0, bg, &[]);
1194        pass.draw(0..3, 0..1);
1195    }
1196
1197    pub fn record_blend<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1198        pass.set_pipeline(&self.blend_pipeline);
1199        pass.set_bind_group(0, bg, &[]);
1200        pass.draw(0..3, 0..1);
1201    }
1202
1203    pub fn record_resolve<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1204        pass.set_pipeline(&self.resolve_pipeline);
1205        pass.set_bind_group(0, bg, &[]);
1206        pass.draw(0..3, 0..1);
1207    }
1208}
1209
1210pub struct BackgroundRenderer {
1211    pipeline: wgpu::RenderPipeline,
1212    bgl: wgpu::BindGroupLayout,
1213}
1214
1215impl BackgroundRenderer {
1216    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
1217        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1218            label: Some("background-shader"),
1219            source: wgpu::ShaderSource::Wgsl(jag_shaders::BACKGROUND_WGSL.into()),
1220        });
1221        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1222            label: Some("bg-bgl"),
1223            entries: &[
1224                wgpu::BindGroupLayoutEntry {
1225                    binding: 0,
1226                    visibility: wgpu::ShaderStages::FRAGMENT,
1227                    ty: wgpu::BindingType::Buffer {
1228                        ty: wgpu::BufferBindingType::Uniform,
1229                        has_dynamic_offset: false,
1230                        min_binding_size: std::num::NonZeroU64::new(64),
1231                    },
1232                    count: None,
1233                },
1234                wgpu::BindGroupLayoutEntry {
1235                    binding: 1,
1236                    visibility: wgpu::ShaderStages::FRAGMENT,
1237                    ty: wgpu::BindingType::Buffer {
1238                        ty: wgpu::BufferBindingType::Uniform,
1239                        has_dynamic_offset: false,
1240                        min_binding_size: std::num::NonZeroU64::new(256),
1241                    },
1242                    count: None,
1243                },
1244            ],
1245        });
1246        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1247            label: Some("bg-pipeline-layout"),
1248            bind_group_layouts: &[&bgl],
1249            push_constant_ranges: &[],
1250        });
1251        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1252            label: Some("bg-pipeline"),
1253            layout: Some(&layout),
1254            vertex: wgpu::VertexState {
1255                module: &shader,
1256                entry_point: "vs_main",
1257                buffers: &[],
1258            },
1259            fragment: Some(wgpu::FragmentState {
1260                module: &shader,
1261                entry_point: "fs_main",
1262                targets: &[Some(wgpu::ColorTargetState {
1263                    format: target_format,
1264                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1265                    write_mask: wgpu::ColorWrites::ALL,
1266                })],
1267            }),
1268            primitive: wgpu::PrimitiveState::default(),
1269            depth_stencil: Some(wgpu::DepthStencilState {
1270                format: wgpu::TextureFormat::Depth32Float,
1271                depth_write_enabled: true,
1272                depth_compare: wgpu::CompareFunction::LessEqual,
1273                stencil: wgpu::StencilState::default(),
1274                bias: wgpu::DepthBiasState::default(),
1275            }),
1276            multisample: wgpu::MultisampleState::default(),
1277            multiview: None,
1278        });
1279        Self { pipeline, bgl }
1280    }
1281
1282    pub fn record<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1283        pass.set_pipeline(&self.pipeline);
1284        pass.set_bind_group(0, bg, &[]);
1285        pass.draw(0..3, 0..1);
1286    }
1287
1288    pub fn bgl(&self) -> &wgpu::BindGroupLayout {
1289        &self.bgl
1290    }
1291}
1292
1293pub struct BlurRenderer {
1294    pipeline: wgpu::RenderPipeline,
1295    bgl: wgpu::BindGroupLayout,
1296    sampler: wgpu::Sampler,
1297    pub param_buffer: wgpu::Buffer,
1298}
1299
1300impl BlurRenderer {
1301    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
1302        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1303            label: Some("shadow-blur-shader"),
1304            source: wgpu::ShaderSource::Wgsl(jag_shaders::SHADOW_BLUR_WGSL.into()),
1305        });
1306        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1307            label: Some("shadow-blur-bgl"),
1308            entries: &[
1309                wgpu::BindGroupLayoutEntry {
1310                    binding: 0,
1311                    visibility: wgpu::ShaderStages::FRAGMENT,
1312                    ty: wgpu::BindingType::Texture {
1313                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
1314                        view_dimension: wgpu::TextureViewDimension::D2,
1315                        multisampled: false,
1316                    },
1317                    count: None,
1318                },
1319                wgpu::BindGroupLayoutEntry {
1320                    binding: 1,
1321                    visibility: wgpu::ShaderStages::FRAGMENT,
1322                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1323                    count: None,
1324                },
1325                wgpu::BindGroupLayoutEntry {
1326                    binding: 2,
1327                    visibility: wgpu::ShaderStages::FRAGMENT,
1328                    ty: wgpu::BindingType::Buffer {
1329                        ty: wgpu::BufferBindingType::Uniform,
1330                        has_dynamic_offset: false,
1331                        min_binding_size: std::num::NonZeroU64::new(32),
1332                    },
1333                    count: None,
1334                },
1335            ],
1336        });
1337        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1338            label: Some("shadow-blur-pipeline-layout"),
1339            bind_group_layouts: &[&bgl],
1340            push_constant_ranges: &[],
1341        });
1342        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1343            label: Some("shadow-blur-pipeline"),
1344            layout: Some(&layout),
1345            vertex: wgpu::VertexState {
1346                module: &shader,
1347                entry_point: "vs_main",
1348                buffers: &[],
1349            },
1350            fragment: Some(wgpu::FragmentState {
1351                module: &shader,
1352                entry_point: "fs_main",
1353                targets: &[Some(wgpu::ColorTargetState {
1354                    format: target_format,
1355                    blend: Some(wgpu::BlendState::REPLACE),
1356                    write_mask: wgpu::ColorWrites::ALL,
1357                })],
1358            }),
1359            primitive: wgpu::PrimitiveState::default(),
1360            depth_stencil: None,
1361            multisample: wgpu::MultisampleState::default(),
1362            multiview: None,
1363        });
1364        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1365            label: Some("shadow-blur-sampler"),
1366            mag_filter: wgpu::FilterMode::Linear,
1367            min_filter: wgpu::FilterMode::Linear,
1368            mipmap_filter: wgpu::FilterMode::Nearest,
1369            address_mode_u: wgpu::AddressMode::ClampToEdge,
1370            address_mode_v: wgpu::AddressMode::ClampToEdge,
1371            address_mode_w: wgpu::AddressMode::ClampToEdge,
1372            ..Default::default()
1373        });
1374        let param_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1375            label: Some("shadow-blur-params"),
1376            size: 32, // vec2 + vec2 + sigma + pad -> round up to 32
1377            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1378            mapped_at_creation: false,
1379        });
1380        Self {
1381            pipeline,
1382            bgl,
1383            sampler,
1384            param_buffer,
1385        }
1386    }
1387
1388    pub fn bind_group(
1389        &self,
1390        device: &wgpu::Device,
1391        tex_view: &wgpu::TextureView,
1392    ) -> wgpu::BindGroup {
1393        device.create_bind_group(&wgpu::BindGroupDescriptor {
1394            label: Some("shadow-blur-bg"),
1395            layout: &self.bgl,
1396            entries: &[
1397                wgpu::BindGroupEntry {
1398                    binding: 0,
1399                    resource: wgpu::BindingResource::TextureView(tex_view),
1400                },
1401                wgpu::BindGroupEntry {
1402                    binding: 1,
1403                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1404                },
1405                wgpu::BindGroupEntry {
1406                    binding: 2,
1407                    resource: self.param_buffer.as_entire_binding(),
1408                },
1409            ],
1410        })
1411    }
1412
1413    pub fn record<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1414        pass.set_pipeline(&self.pipeline);
1415        pass.set_bind_group(0, bg, &[]);
1416        pass.draw(0..3, 0..1);
1417    }
1418}
1419
1420pub struct ShadowCompositeRenderer {
1421    pipeline: wgpu::RenderPipeline,
1422    bgl: wgpu::BindGroupLayout,
1423    sampler: wgpu::Sampler,
1424    pub color_buffer: wgpu::Buffer,
1425}
1426
1427impl ShadowCompositeRenderer {
1428    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
1429        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1430            label: Some("shadow-composite-shader"),
1431            source: wgpu::ShaderSource::Wgsl(jag_shaders::SHADOW_COMPOSITE_WGSL.into()),
1432        });
1433        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1434            label: Some("shadow-composite-bgl"),
1435            entries: &[
1436                wgpu::BindGroupLayoutEntry {
1437                    binding: 0,
1438                    visibility: wgpu::ShaderStages::FRAGMENT,
1439                    ty: wgpu::BindingType::Texture {
1440                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
1441                        view_dimension: wgpu::TextureViewDimension::D2,
1442                        multisampled: false,
1443                    },
1444                    count: None,
1445                },
1446                wgpu::BindGroupLayoutEntry {
1447                    binding: 1,
1448                    visibility: wgpu::ShaderStages::FRAGMENT,
1449                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1450                    count: None,
1451                },
1452                wgpu::BindGroupLayoutEntry {
1453                    binding: 2,
1454                    visibility: wgpu::ShaderStages::FRAGMENT,
1455                    ty: wgpu::BindingType::Buffer {
1456                        ty: wgpu::BufferBindingType::Uniform,
1457                        has_dynamic_offset: false,
1458                        min_binding_size: std::num::NonZeroU64::new(16),
1459                    },
1460                    count: None,
1461                },
1462            ],
1463        });
1464        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1465            label: Some("shadow-composite-pipeline-layout"),
1466            bind_group_layouts: &[&bgl],
1467            push_constant_ranges: &[],
1468        });
1469        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1470            label: Some("shadow-composite-pipeline"),
1471            layout: Some(&layout),
1472            vertex: wgpu::VertexState {
1473                module: &shader,
1474                entry_point: "vs_main",
1475                buffers: &[],
1476            },
1477            fragment: Some(wgpu::FragmentState {
1478                module: &shader,
1479                entry_point: "fs_main",
1480                targets: &[Some(wgpu::ColorTargetState {
1481                    format: target_format,
1482                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1483                    write_mask: wgpu::ColorWrites::ALL,
1484                })],
1485            }),
1486            primitive: wgpu::PrimitiveState::default(),
1487            depth_stencil: None,
1488            multisample: wgpu::MultisampleState::default(),
1489            multiview: None,
1490        });
1491        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1492            label: Some("shadow-composite-sampler"),
1493            mag_filter: wgpu::FilterMode::Linear,
1494            min_filter: wgpu::FilterMode::Linear,
1495            mipmap_filter: wgpu::FilterMode::Nearest,
1496            address_mode_u: wgpu::AddressMode::ClampToEdge,
1497            address_mode_v: wgpu::AddressMode::ClampToEdge,
1498            address_mode_w: wgpu::AddressMode::ClampToEdge,
1499            ..Default::default()
1500        });
1501        let color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1502            label: Some("shadow-color"),
1503            size: 16,
1504            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1505            mapped_at_creation: false,
1506        });
1507        Self {
1508            pipeline,
1509            bgl,
1510            sampler,
1511            color_buffer,
1512        }
1513    }
1514
1515    pub fn bind_group(
1516        &self,
1517        device: &wgpu::Device,
1518        tex_view: &wgpu::TextureView,
1519    ) -> wgpu::BindGroup {
1520        device.create_bind_group(&wgpu::BindGroupDescriptor {
1521            label: Some("shadow-composite-bg"),
1522            layout: &self.bgl,
1523            entries: &[
1524                wgpu::BindGroupEntry {
1525                    binding: 0,
1526                    resource: wgpu::BindingResource::TextureView(tex_view),
1527                },
1528                wgpu::BindGroupEntry {
1529                    binding: 1,
1530                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1531                },
1532                wgpu::BindGroupEntry {
1533                    binding: 2,
1534                    resource: self.color_buffer.as_entire_binding(),
1535                },
1536            ],
1537        })
1538    }
1539
1540    pub fn record<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, bg: &'a wgpu::BindGroup) {
1541        pass.set_pipeline(&self.pipeline);
1542        pass.set_bind_group(0, bg, &[]);
1543        pass.draw(0..3, 0..1);
1544    }
1545}
1546
1547pub struct TextRenderer {
1548    pub pipeline: wgpu::RenderPipeline,
1549    vp_bgl: wgpu::BindGroupLayout,
1550    _z_bgl: wgpu::BindGroupLayout,
1551    pub tex_bgl: wgpu::BindGroupLayout,
1552    pub sampler: wgpu::Sampler,
1553    pub color_buffer: wgpu::Buffer,
1554}
1555
1556impl TextRenderer {
1557    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
1558        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1559            label: Some("text-shader"),
1560            source: wgpu::ShaderSource::Wgsl(jag_shaders::TEXT_WGSL.into()),
1561        });
1562
1563        // Viewport uniform group (matches solids layout)
1564        let vp_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1565            label: Some("text-vp-bgl"),
1566            entries: &[wgpu::BindGroupLayoutEntry {
1567                binding: 0,
1568                visibility: wgpu::ShaderStages::VERTEX,
1569                ty: wgpu::BindingType::Buffer {
1570                    ty: wgpu::BufferBindingType::Uniform,
1571                    has_dynamic_offset: false,
1572                    min_binding_size: std::num::NonZeroU64::new(32),
1573                },
1574                count: None,
1575            }],
1576        });
1577
1578        // Z-index uniform
1579        let z_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1580            label: Some("text-z-bgl"),
1581            entries: &[wgpu::BindGroupLayoutEntry {
1582                binding: 0,
1583                visibility: wgpu::ShaderStages::VERTEX,
1584                ty: wgpu::BindingType::Buffer {
1585                    ty: wgpu::BufferBindingType::Uniform,
1586                    has_dynamic_offset: false,
1587                    min_binding_size: std::num::NonZeroU64::new(4),
1588                },
1589                count: None,
1590            }],
1591        });
1592
1593        // Texture + sampler (color is now per-vertex)
1594        let tex_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1595            label: Some("text-tex-bgl"),
1596            entries: &[
1597                wgpu::BindGroupLayoutEntry {
1598                    binding: 0,
1599                    visibility: wgpu::ShaderStages::FRAGMENT,
1600                    ty: wgpu::BindingType::Texture {
1601                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
1602                        view_dimension: wgpu::TextureViewDimension::D2,
1603                        multisampled: false,
1604                    },
1605                    count: None,
1606                },
1607                wgpu::BindGroupLayoutEntry {
1608                    binding: 1,
1609                    visibility: wgpu::ShaderStages::FRAGMENT,
1610                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1611                    count: None,
1612                },
1613            ],
1614        });
1615
1616        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1617            label: Some("text-pipeline-layout"),
1618            bind_group_layouts: &[&vp_bgl, &z_bgl, &tex_bgl],
1619            push_constant_ranges: &[],
1620        });
1621
1622        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1623            label: Some("text-pipeline"),
1624            layout: Some(&layout),
1625            vertex: wgpu::VertexState {
1626                module: &shader,
1627                entry_point: "vs_main",
1628                buffers: &[wgpu::VertexBufferLayout {
1629                    array_stride: (std::mem::size_of::<f32>() * 8) as u64, // pos(2) + uv(2) + color(4)
1630                    step_mode: wgpu::VertexStepMode::Vertex,
1631                    attributes: &[
1632                        wgpu::VertexAttribute {
1633                            offset: 0,
1634                            shader_location: 0,
1635                            format: wgpu::VertexFormat::Float32x2,
1636                        },
1637                        wgpu::VertexAttribute {
1638                            offset: 8,
1639                            shader_location: 1,
1640                            format: wgpu::VertexFormat::Float32x2,
1641                        },
1642                        wgpu::VertexAttribute {
1643                            offset: 16,
1644                            shader_location: 2,
1645                            format: wgpu::VertexFormat::Float32x4,
1646                        },
1647                    ],
1648                }],
1649            },
1650            fragment: Some(wgpu::FragmentState {
1651                module: &shader,
1652                entry_point: "fs_main",
1653                targets: &[Some(wgpu::ColorTargetState {
1654                    format: target_format,
1655                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1656                    write_mask: wgpu::ColorWrites::ALL,
1657                })],
1658            }),
1659            primitive: wgpu::PrimitiveState::default(),
1660            depth_stencil: Some(wgpu::DepthStencilState {
1661                format: wgpu::TextureFormat::Depth32Float,
1662                depth_write_enabled: true,
1663                depth_compare: wgpu::CompareFunction::LessEqual,
1664                stencil: wgpu::StencilState::default(),
1665                bias: wgpu::DepthBiasState::default(),
1666            }),
1667            multisample: wgpu::MultisampleState::default(),
1668            multiview: None,
1669        });
1670
1671        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1672            label: Some("text-sampler"),
1673            mag_filter: wgpu::FilterMode::Nearest,
1674            min_filter: wgpu::FilterMode::Nearest,
1675            mipmap_filter: wgpu::FilterMode::Nearest,
1676            address_mode_u: wgpu::AddressMode::ClampToEdge,
1677            address_mode_v: wgpu::AddressMode::ClampToEdge,
1678            address_mode_w: wgpu::AddressMode::ClampToEdge,
1679            ..Default::default()
1680        });
1681
1682        let color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1683            label: Some("text-color"),
1684            size: 16,
1685            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1686            mapped_at_creation: false,
1687        });
1688
1689        Self {
1690            pipeline,
1691            vp_bgl,
1692            _z_bgl: z_bgl,
1693            tex_bgl,
1694            sampler,
1695            color_buffer,
1696        }
1697    }
1698
1699    pub fn vp_bind_group(
1700        &self,
1701        device: &wgpu::Device,
1702        vp_buffer: &wgpu::Buffer,
1703    ) -> wgpu::BindGroup {
1704        device.create_bind_group(&wgpu::BindGroupDescriptor {
1705            label: Some("text-vp-bg"),
1706            layout: &self.vp_bgl,
1707            entries: &[wgpu::BindGroupEntry {
1708                binding: 0,
1709                resource: vp_buffer.as_entire_binding(),
1710            }],
1711        })
1712    }
1713
1714    pub fn tex_bind_group(
1715        &self,
1716        device: &wgpu::Device,
1717        tex_view: &wgpu::TextureView,
1718    ) -> wgpu::BindGroup {
1719        device.create_bind_group(&wgpu::BindGroupDescriptor {
1720            label: Some("text-tex-bg"),
1721            layout: &self.tex_bgl,
1722            entries: &[
1723                wgpu::BindGroupEntry {
1724                    binding: 0,
1725                    resource: wgpu::BindingResource::TextureView(tex_view),
1726                },
1727                wgpu::BindGroupEntry {
1728                    binding: 1,
1729                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1730                },
1731            ],
1732        })
1733    }
1734
1735    pub fn record<'a>(
1736        &'a self,
1737        pass: &mut wgpu::RenderPass<'a>,
1738        vp_bg: &'a wgpu::BindGroup,
1739        z_bg: &'a wgpu::BindGroup,
1740        tex_bg: &'a wgpu::BindGroup,
1741        vbuf: &'a wgpu::Buffer,
1742        ibuf: &'a wgpu::Buffer,
1743        icount: u32,
1744    ) {
1745        pass.set_pipeline(&self.pipeline);
1746        pass.set_bind_group(0, vp_bg, &[]);
1747        pass.set_bind_group(1, z_bg, &[]);
1748        pass.set_bind_group(2, tex_bg, &[]);
1749        pass.set_vertex_buffer(0, vbuf.slice(..));
1750        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
1751        pass.draw_indexed(0..icount, 0, 0..1);
1752    }
1753}
1754
1755pub struct ImageRenderer {
1756    pipeline: wgpu::RenderPipeline,
1757    vp_bgl: wgpu::BindGroupLayout,
1758    _z_bgl: wgpu::BindGroupLayout,
1759    tex_bgl: wgpu::BindGroupLayout,
1760    params_bgl: wgpu::BindGroupLayout,
1761    sampler: wgpu::Sampler,
1762}
1763
1764impl ImageRenderer {
1765    pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
1766        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1767            label: Some("image-shader"),
1768            source: wgpu::ShaderSource::Wgsl(jag_shaders::IMAGE_WGSL.into()),
1769        });
1770
1771        // Optional debug/escape hatch: allow disabling depth testing for images.
1772        // When JAG_IMAGE_NO_DEPTH=1, raster images (including WebView textures)
1773        // are rendered without depth testing so they always blend on top.
1774        let disable_depth = std::env::var("JAG_IMAGE_NO_DEPTH")
1775            .ok()
1776            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1777            .unwrap_or(false);
1778
1779        // Viewport uniform group (matches solids layout)
1780        let vp_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1781            label: Some("image-vp-bgl"),
1782            entries: &[wgpu::BindGroupLayoutEntry {
1783                binding: 0,
1784                visibility: wgpu::ShaderStages::VERTEX,
1785                ty: wgpu::BindingType::Buffer {
1786                    ty: wgpu::BufferBindingType::Uniform,
1787                    has_dynamic_offset: false,
1788                    min_binding_size: std::num::NonZeroU64::new(32),
1789                },
1790                count: None,
1791            }],
1792        });
1793
1794        // Z-index uniform
1795        let z_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1796            label: Some("image-z-bgl"),
1797            entries: &[wgpu::BindGroupLayoutEntry {
1798                binding: 0,
1799                visibility: wgpu::ShaderStages::VERTEX,
1800                ty: wgpu::BindingType::Buffer {
1801                    ty: wgpu::BufferBindingType::Uniform,
1802                    has_dynamic_offset: false,
1803                    min_binding_size: std::num::NonZeroU64::new(4),
1804                },
1805                count: None,
1806            }],
1807        });
1808
1809        // Texture + sampler
1810        let tex_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1811            label: Some("image-tex-bgl"),
1812            entries: &[
1813                wgpu::BindGroupLayoutEntry {
1814                    binding: 0,
1815                    visibility: wgpu::ShaderStages::FRAGMENT,
1816                    ty: wgpu::BindingType::Texture {
1817                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
1818                        view_dimension: wgpu::TextureViewDimension::D2,
1819                        multisampled: false,
1820                    },
1821                    count: None,
1822                },
1823                wgpu::BindGroupLayoutEntry {
1824                    binding: 1,
1825                    visibility: wgpu::ShaderStages::FRAGMENT,
1826                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1827                    count: None,
1828                },
1829            ],
1830        });
1831
1832        // Per-draw image params:
1833        // [opacity, premultiplied_input_flag, pad0, pad1]
1834        let params_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1835            label: Some("image-params-bgl"),
1836            entries: &[wgpu::BindGroupLayoutEntry {
1837                binding: 0,
1838                visibility: wgpu::ShaderStages::FRAGMENT,
1839                ty: wgpu::BindingType::Buffer {
1840                    ty: wgpu::BufferBindingType::Uniform,
1841                    has_dynamic_offset: false,
1842                    min_binding_size: std::num::NonZeroU64::new(16),
1843                },
1844                count: None,
1845            }],
1846        });
1847
1848        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1849            label: Some("image-pipeline-layout"),
1850            bind_group_layouts: &[&vp_bgl, &z_bgl, &tex_bgl, &params_bgl],
1851            push_constant_ranges: &[],
1852        });
1853
1854        let depth_stencil = if disable_depth {
1855            None
1856        } else {
1857            Some(wgpu::DepthStencilState {
1858                format: wgpu::TextureFormat::Depth32Float,
1859                depth_write_enabled: true,
1860                depth_compare: wgpu::CompareFunction::LessEqual,
1861                stencil: wgpu::StencilState::default(),
1862                bias: wgpu::DepthBiasState::default(),
1863            })
1864        };
1865
1866        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1867            label: Some("image-pipeline"),
1868            layout: Some(&layout),
1869            vertex: wgpu::VertexState {
1870                module: &shader,
1871                entry_point: "vs_main",
1872                buffers: &[wgpu::VertexBufferLayout {
1873                    array_stride: (std::mem::size_of::<f32>() * 4) as u64,
1874                    step_mode: wgpu::VertexStepMode::Vertex,
1875                    attributes: &[
1876                        wgpu::VertexAttribute {
1877                            offset: 0,
1878                            shader_location: 0,
1879                            format: wgpu::VertexFormat::Float32x2,
1880                        },
1881                        wgpu::VertexAttribute {
1882                            offset: 8,
1883                            shader_location: 1,
1884                            format: wgpu::VertexFormat::Float32x2,
1885                        },
1886                    ],
1887                }],
1888            },
1889            fragment: Some(wgpu::FragmentState {
1890                module: &shader,
1891                entry_point: "fs_main",
1892                targets: &[Some(wgpu::ColorTargetState {
1893                    format: target_format,
1894                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1895                    write_mask: wgpu::ColorWrites::ALL,
1896                })],
1897            }),
1898            primitive: wgpu::PrimitiveState::default(),
1899            depth_stencil,
1900            multisample: wgpu::MultisampleState::default(),
1901            multiview: None,
1902        });
1903
1904        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1905            label: Some("image-sampler"),
1906            mag_filter: wgpu::FilterMode::Linear,
1907            min_filter: wgpu::FilterMode::Linear,
1908            mipmap_filter: wgpu::FilterMode::Nearest,
1909            address_mode_u: wgpu::AddressMode::ClampToEdge,
1910            address_mode_v: wgpu::AddressMode::ClampToEdge,
1911            address_mode_w: wgpu::AddressMode::ClampToEdge,
1912            ..Default::default()
1913        });
1914
1915        Self {
1916            pipeline,
1917            vp_bgl,
1918            _z_bgl: z_bgl,
1919            tex_bgl,
1920            params_bgl,
1921            sampler,
1922        }
1923    }
1924
1925    pub fn vp_bind_group(
1926        &self,
1927        device: &wgpu::Device,
1928        vp_buffer: &wgpu::Buffer,
1929    ) -> wgpu::BindGroup {
1930        device.create_bind_group(&wgpu::BindGroupDescriptor {
1931            label: Some("image-vp-bg"),
1932            layout: &self.vp_bgl,
1933            entries: &[wgpu::BindGroupEntry {
1934                binding: 0,
1935                resource: vp_buffer.as_entire_binding(),
1936            }],
1937        })
1938    }
1939
1940    pub fn tex_bind_group(
1941        &self,
1942        device: &wgpu::Device,
1943        tex_view: &wgpu::TextureView,
1944    ) -> wgpu::BindGroup {
1945        device.create_bind_group(&wgpu::BindGroupDescriptor {
1946            label: Some("image-tex-bg"),
1947            layout: &self.tex_bgl,
1948            entries: &[
1949                wgpu::BindGroupEntry {
1950                    binding: 0,
1951                    resource: wgpu::BindingResource::TextureView(tex_view),
1952                },
1953                wgpu::BindGroupEntry {
1954                    binding: 1,
1955                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1956                },
1957            ],
1958        })
1959    }
1960
1961    pub fn params_bind_group(
1962        &self,
1963        device: &wgpu::Device,
1964        opacity: f32,
1965        premultiplied_input: bool,
1966    ) -> (wgpu::BindGroup, wgpu::Buffer) {
1967        let params: [f32; 4] = [
1968            opacity.clamp(0.0, 1.0),
1969            if premultiplied_input { 1.0 } else { 0.0 },
1970            0.0,
1971            0.0,
1972        ];
1973        let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1974            label: Some("image-params-buffer"),
1975            contents: bytemuck::cast_slice(&params),
1976            usage: wgpu::BufferUsages::UNIFORM,
1977        });
1978        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1979            label: Some("image-params-bg"),
1980            layout: &self.params_bgl,
1981            entries: &[wgpu::BindGroupEntry {
1982                binding: 0,
1983                resource: buffer.as_entire_binding(),
1984            }],
1985        });
1986        (bind_group, buffer)
1987    }
1988
1989    pub fn record<'a>(
1990        &'a self,
1991        pass: &mut wgpu::RenderPass<'a>,
1992        vp_bg: &'a wgpu::BindGroup,
1993        z_bg: &'a wgpu::BindGroup,
1994        tex_bg: &'a wgpu::BindGroup,
1995        params_bg: &'a wgpu::BindGroup,
1996        vbuf: &'a wgpu::Buffer,
1997        ibuf: &'a wgpu::Buffer,
1998        icount: u32,
1999    ) {
2000        pass.set_pipeline(&self.pipeline);
2001        pass.set_bind_group(0, vp_bg, &[]);
2002        pass.set_bind_group(1, z_bg, &[]);
2003        pass.set_bind_group(2, tex_bg, &[]);
2004        pass.set_bind_group(3, params_bg, &[]);
2005        pass.set_vertex_buffer(0, vbuf.slice(..));
2006        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint16);
2007        pass.draw_indexed(0..icount, 0, 0..1);
2008    }
2009}