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