1use bytemuck::{Pod, Zeroable};
17use wgpu::util::DeviceExt;
18
19use crate::application::Application;
20use crate::scene::clip::MaskShape;
21use crate::texture::render_texture::RenderTexture;
22
23#[repr(C)]
24#[derive(Clone, Copy, Pod, Zeroable)]
25struct ClipUniforms {
26 center: [f32; 2],
27 half_extents: [f32; 2],
28 radius: f32,
29 aa: f32,
30 invert: f32,
31 shape_kind: f32,
32}
33
34pub(crate) struct ClipPipeline {
35 pipeline: wgpu::RenderPipeline,
36 bind_group_layout: wgpu::BindGroupLayout,
37 sampler: wgpu::Sampler,
38}
39
40impl ClipPipeline {
41 pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
42 let device = app.device();
43
44 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
45 label: Some("wisp::clip bg layout"),
46 entries: &[
47 wgpu::BindGroupLayoutEntry {
48 binding: 0,
49 visibility: wgpu::ShaderStages::FRAGMENT,
50 ty: wgpu::BindingType::Texture {
51 sample_type: wgpu::TextureSampleType::Float { filterable: true },
52 view_dimension: wgpu::TextureViewDimension::D2,
53 multisampled: false,
54 },
55 count: None,
56 },
57 wgpu::BindGroupLayoutEntry {
58 binding: 1,
59 visibility: wgpu::ShaderStages::FRAGMENT,
60 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
61 count: None,
62 },
63 wgpu::BindGroupLayoutEntry {
64 binding: 2,
65 visibility: wgpu::ShaderStages::FRAGMENT,
66 ty: wgpu::BindingType::Buffer {
67 ty: wgpu::BufferBindingType::Uniform,
68 has_dynamic_offset: false,
69 min_binding_size: None,
70 },
71 count: None,
72 },
73 ],
74 });
75
76 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
77 label: Some("wisp::clip pipeline layout"),
78 bind_group_layouts: &[&bind_group_layout],
79 push_constant_ranges: &[],
80 });
81
82 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
83 label: Some("wisp::clip shader"),
84 source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/clip.wgsl").into()),
85 });
86
87 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
88 label: Some("wisp::clip pipeline"),
89 layout: Some(&pipeline_layout),
90 vertex: wgpu::VertexState {
91 module: &shader,
92 entry_point: Some("main_vs"),
93 buffers: &[],
94 compilation_options: wgpu::PipelineCompilationOptions::default(),
95 },
96 fragment: Some(wgpu::FragmentState {
97 module: &shader,
98 entry_point: Some("main_fs"),
99 targets: &[Some(wgpu::ColorTargetState {
100 format: output_format,
101 blend: Some(wgpu::BlendState::REPLACE),
102 write_mask: wgpu::ColorWrites::ALL,
103 })],
104 compilation_options: wgpu::PipelineCompilationOptions::default(),
105 }),
106 primitive: wgpu::PrimitiveState::default(),
107 depth_stencil: None,
108 multisample: wgpu::MultisampleState::default(),
109 multiview: None,
110 cache: None,
111 });
112
113 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
114 label: Some("wisp::clip sampler"),
115 address_mode_u: wgpu::AddressMode::ClampToEdge,
116 address_mode_v: wgpu::AddressMode::ClampToEdge,
117 address_mode_w: wgpu::AddressMode::ClampToEdge,
118 mag_filter: wgpu::FilterMode::Linear,
119 min_filter: wgpu::FilterMode::Linear,
120 mipmap_filter: wgpu::FilterMode::Nearest,
121 ..Default::default()
122 });
123
124 Self {
125 pipeline,
126 bind_group_layout,
127 sampler,
128 }
129 }
130
131 pub(crate) fn apply(
135 &self,
136 app: &Application,
137 shape: MaskShape,
138 foreground: &RenderTexture,
139 output: &RenderTexture,
140 ) {
141 self.apply_with_invert(app, shape, foreground, output, false);
142 }
143
144 fn apply_with_invert(
145 &self,
146 app: &Application,
147 shape: MaskShape,
148 foreground: &RenderTexture,
149 output: &RenderTexture,
150 invert: bool,
151 ) {
152 let (cx, cy, hx, hy, radius, shape_kind) = match shape {
153 MaskShape::Rect { rect } => {
154 let cx = rect.min.x + rect.size.x * 0.5;
155 let cy = rect.min.y + rect.size.y * 0.5;
156 let hx = (rect.size.x * 0.5).max(0.0);
157 let hy = (rect.size.y * 0.5).max(0.0);
158 (cx, cy, hx, hy, 0.0, 0.0)
159 }
160 MaskShape::RoundedRect { rect, radius } => {
161 let cx = rect.min.x + rect.size.x * 0.5;
162 let cy = rect.min.y + rect.size.y * 0.5;
163 let hx = (rect.size.x * 0.5).max(0.0);
164 let hy = (rect.size.y * 0.5).max(0.0);
165 let r = radius.clamp(0.0, hx.min(hy));
166 (cx, cy, hx, hy, r, 0.0)
167 }
168 MaskShape::Circle { center, radius } => {
169 let r = radius.max(0.0);
173 (center.x, center.y, r, r, r, 0.0)
174 }
175 MaskShape::Ellipse {
176 center,
177 half_extents,
178 } => {
179 let hx = half_extents.x.max(0.0);
180 let hy = half_extents.y.max(0.0);
181 (center.x, center.y, hx, hy, 0.0, 1.0)
183 }
184 };
185
186 let w_f = f32::from(u16::try_from(output.width().min(u32::from(u16::MAX))).unwrap_or(1));
187 let h_f = f32::from(u16::try_from(output.height().min(u32::from(u16::MAX))).unwrap_or(1));
188 let aa = 2.0 / w_f.min(h_f).max(1.0);
189
190 let uniforms = ClipUniforms {
191 center: [cx, cy],
192 half_extents: [hx, hy],
193 radius,
194 aa,
195 invert: if invert { 1.0 } else { 0.0 },
196 shape_kind,
197 };
198 let buffer = app
199 .device()
200 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
201 label: Some("wisp::clip uniforms"),
202 contents: bytemuck::bytes_of(&uniforms),
203 usage: wgpu::BufferUsages::UNIFORM,
204 });
205
206 let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
207 label: Some("wisp::clip bg"),
208 layout: &self.bind_group_layout,
209 entries: &[
210 wgpu::BindGroupEntry {
211 binding: 0,
212 resource: wgpu::BindingResource::TextureView(foreground.view()),
213 },
214 wgpu::BindGroupEntry {
215 binding: 1,
216 resource: wgpu::BindingResource::Sampler(&self.sampler),
217 },
218 wgpu::BindGroupEntry {
219 binding: 2,
220 resource: buffer.as_entire_binding(),
221 },
222 ],
223 });
224
225 let mut encoder = app
226 .device()
227 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
228 label: Some("wisp::clip encoder"),
229 });
230 {
231 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
232 label: Some("wisp::clip pass"),
233 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
234 view: output.view(),
235 resolve_target: None,
236 ops: wgpu::Operations {
237 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
238 store: wgpu::StoreOp::Store,
239 },
240 })],
241 depth_stencil_attachment: None,
242 timestamp_writes: None,
243 occlusion_query_set: None,
244 });
245 pass.set_pipeline(&self.pipeline);
246 pass.set_bind_group(0, &bg, &[]);
247 pass.draw(0..3, 0..1);
248 }
249 app.queue().submit(std::iter::once(encoder.finish()));
250 }
251}