1use bytemuck::{Pod, Zeroable};
12use wgpu::util::DeviceExt;
13
14use crate::application::Application;
15use crate::texture::render_texture::RenderTexture;
16
17pub(crate) const MAX_PATH_POINTS: usize = 32;
20
21#[repr(C)]
22#[derive(Clone, Copy, Pod, Zeroable)]
23struct PathClipUniforms {
24 count: u32,
25 invert: u32,
26 _pad: [u32; 2],
27 points: [[f32; 4]; MAX_PATH_POINTS],
31}
32
33pub(crate) struct PathClipPipeline {
34 pipeline: wgpu::RenderPipeline,
35 bind_group_layout: wgpu::BindGroupLayout,
36 sampler: wgpu::Sampler,
37}
38
39impl PathClipPipeline {
40 pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
41 let device = app.device();
42
43 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
44 label: Some("wisp::path_clip bg layout"),
45 entries: &[
46 wgpu::BindGroupLayoutEntry {
47 binding: 0,
48 visibility: wgpu::ShaderStages::FRAGMENT,
49 ty: wgpu::BindingType::Texture {
50 sample_type: wgpu::TextureSampleType::Float { filterable: true },
51 view_dimension: wgpu::TextureViewDimension::D2,
52 multisampled: false,
53 },
54 count: None,
55 },
56 wgpu::BindGroupLayoutEntry {
57 binding: 1,
58 visibility: wgpu::ShaderStages::FRAGMENT,
59 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
60 count: None,
61 },
62 wgpu::BindGroupLayoutEntry {
63 binding: 2,
64 visibility: wgpu::ShaderStages::FRAGMENT,
65 ty: wgpu::BindingType::Buffer {
66 ty: wgpu::BufferBindingType::Uniform,
67 has_dynamic_offset: false,
68 min_binding_size: None,
69 },
70 count: None,
71 },
72 ],
73 });
74
75 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
76 label: Some("wisp::path_clip pipeline layout"),
77 bind_group_layouts: &[&bind_group_layout],
78 push_constant_ranges: &[],
79 });
80
81 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
82 label: Some("wisp::path_clip shader"),
83 source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/path_clip.wgsl").into()),
84 });
85
86 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
87 label: Some("wisp::path_clip pipeline"),
88 layout: Some(&pipeline_layout),
89 vertex: wgpu::VertexState {
90 module: &shader,
91 entry_point: Some("main_vs"),
92 buffers: &[],
93 compilation_options: wgpu::PipelineCompilationOptions::default(),
94 },
95 fragment: Some(wgpu::FragmentState {
96 module: &shader,
97 entry_point: Some("main_fs"),
98 targets: &[Some(wgpu::ColorTargetState {
99 format: output_format,
100 blend: Some(wgpu::BlendState::REPLACE),
101 write_mask: wgpu::ColorWrites::ALL,
102 })],
103 compilation_options: wgpu::PipelineCompilationOptions::default(),
104 }),
105 primitive: wgpu::PrimitiveState::default(),
106 depth_stencil: None,
107 multisample: wgpu::MultisampleState::default(),
108 multiview: None,
109 cache: None,
110 });
111
112 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
113 label: Some("wisp::path_clip sampler"),
114 address_mode_u: wgpu::AddressMode::ClampToEdge,
115 address_mode_v: wgpu::AddressMode::ClampToEdge,
116 address_mode_w: wgpu::AddressMode::ClampToEdge,
117 mag_filter: wgpu::FilterMode::Linear,
118 min_filter: wgpu::FilterMode::Linear,
119 mipmap_filter: wgpu::FilterMode::Nearest,
120 ..Default::default()
121 });
122
123 Self {
124 pipeline,
125 bind_group_layout,
126 sampler,
127 }
128 }
129
130 pub(crate) fn apply(
134 &self,
135 app: &Application,
136 points: &[glam::Vec2],
137 invert: bool,
138 foreground: &RenderTexture,
139 output: &RenderTexture,
140 ) {
141 let mut padded = [[0.0_f32; 4]; MAX_PATH_POINTS];
142 let count = points.len().min(MAX_PATH_POINTS);
143 for (slot, p) in padded.iter_mut().zip(points.iter()).take(count) {
144 *slot = [p.x, p.y, 0.0, 0.0];
145 }
146 let count_u32 = u32::try_from(count).unwrap_or(0);
147
148 let uniforms = PathClipUniforms {
149 count: count_u32,
150 invert: u32::from(invert),
151 _pad: [0, 0],
152 points: padded,
153 };
154 let buffer = app
155 .device()
156 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
157 label: Some("wisp::path_clip uniforms"),
158 contents: bytemuck::bytes_of(&uniforms),
159 usage: wgpu::BufferUsages::UNIFORM,
160 });
161
162 let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
163 label: Some("wisp::path_clip bg"),
164 layout: &self.bind_group_layout,
165 entries: &[
166 wgpu::BindGroupEntry {
167 binding: 0,
168 resource: wgpu::BindingResource::TextureView(foreground.view()),
169 },
170 wgpu::BindGroupEntry {
171 binding: 1,
172 resource: wgpu::BindingResource::Sampler(&self.sampler),
173 },
174 wgpu::BindGroupEntry {
175 binding: 2,
176 resource: buffer.as_entire_binding(),
177 },
178 ],
179 });
180
181 let mut encoder = app
182 .device()
183 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
184 label: Some("wisp::path_clip encoder"),
185 });
186 {
187 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
188 label: Some("wisp::path_clip pass"),
189 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
190 view: output.view(),
191 resolve_target: None,
192 ops: wgpu::Operations {
193 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
194 store: wgpu::StoreOp::Store,
195 },
196 })],
197 depth_stencil_attachment: None,
198 timestamp_writes: None,
199 occlusion_query_set: None,
200 });
201 pass.set_pipeline(&self.pipeline);
202 pass.set_bind_group(0, &bg, &[]);
203 pass.draw(0..3, 0..1);
204 }
205 app.queue().submit(std::iter::once(encoder.finish()));
206 }
207}