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