1use crate::error::Result;
7use std::sync::Arc;
8use wgpu::{Device, Queue, TextureView};
9
10pub struct BlendRenderer {
12 device: Arc<Device>,
13 queue: Arc<Queue>,
14 pipeline: wgpu::RenderPipeline,
15 bind_group_layout: wgpu::BindGroupLayout,
16 uniform_buffer: wgpu::Buffer,
17 sampler: wgpu::Sampler,
18 cached_bind_group: Option<CachedBindGroup>,
20}
21
22struct CachedBindGroup {
24 key_a: usize,
25 key_b: usize,
26 bind_group: wgpu::BindGroup,
27}
28
29#[repr(C)]
30#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
31struct BlendUniforms {
32 blend_pattern: u32,
33 blend_amount: f32,
34 time: f32,
35 _padding: f32,
36}
37
38impl BlendRenderer {
39 pub fn new(
41 device: Arc<Device>,
42 queue: Arc<Queue>,
43 texture_format: wgpu::TextureFormat,
44 ) -> Result<Self> {
45 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
47 label: Some("Blend Uniform Buffer"),
48 size: std::mem::size_of::<BlendUniforms>() as u64,
49 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
50 mapped_at_creation: false,
51 });
52
53 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
55 label: Some("Blend Sampler"),
56 address_mode_u: wgpu::AddressMode::ClampToEdge,
57 address_mode_v: wgpu::AddressMode::ClampToEdge,
58 address_mode_w: wgpu::AddressMode::ClampToEdge,
59 mag_filter: wgpu::FilterMode::Linear,
60 min_filter: wgpu::FilterMode::Linear,
61 mipmap_filter: wgpu::MipmapFilterMode::Linear,
62 ..Default::default()
63 });
64
65 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
67 label: Some("Blend Bind Group Layout"),
68 entries: &[
69 wgpu::BindGroupLayoutEntry {
71 binding: 0,
72 visibility: wgpu::ShaderStages::FRAGMENT,
73 ty: wgpu::BindingType::Texture {
74 sample_type: wgpu::TextureSampleType::Float { filterable: true },
75 view_dimension: wgpu::TextureViewDimension::D2,
76 multisampled: false,
77 },
78 count: None,
79 },
80 wgpu::BindGroupLayoutEntry {
82 binding: 1,
83 visibility: wgpu::ShaderStages::FRAGMENT,
84 ty: wgpu::BindingType::Texture {
85 sample_type: wgpu::TextureSampleType::Float { filterable: true },
86 view_dimension: wgpu::TextureViewDimension::D2,
87 multisampled: false,
88 },
89 count: None,
90 },
91 wgpu::BindGroupLayoutEntry {
93 binding: 2,
94 visibility: wgpu::ShaderStages::FRAGMENT,
95 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
96 count: None,
97 },
98 wgpu::BindGroupLayoutEntry {
100 binding: 3,
101 visibility: wgpu::ShaderStages::FRAGMENT,
102 ty: wgpu::BindingType::Buffer {
103 ty: wgpu::BufferBindingType::Uniform,
104 has_dynamic_offset: false,
105 min_binding_size: None,
106 },
107 count: None,
108 },
109 ],
110 });
111
112 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
114 label: Some("Blend Shader"),
115 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blend.wgsl").into()),
116 });
117
118 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
120 label: Some("Blend Pipeline Layout"),
121 bind_group_layouts: &[Some(&bind_group_layout)],
122 immediate_size: 0,
123 });
124
125 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
127 label: Some("Blend Pipeline"),
128 layout: Some(&pipeline_layout),
129 vertex: wgpu::VertexState {
130 module: &shader,
131 entry_point: Some("vs_main"),
132 buffers: &[],
133 compilation_options: Default::default(),
134 },
135 fragment: Some(wgpu::FragmentState {
136 module: &shader,
137 entry_point: Some("fs_main"),
138 targets: &[Some(wgpu::ColorTargetState {
139 format: texture_format,
140 blend: Some(wgpu::BlendState::REPLACE),
141 write_mask: wgpu::ColorWrites::ALL,
142 })],
143 compilation_options: Default::default(),
144 }),
145 primitive: wgpu::PrimitiveState {
146 topology: wgpu::PrimitiveTopology::TriangleList,
147 strip_index_format: None,
148 front_face: wgpu::FrontFace::Ccw,
149 cull_mode: None,
150 polygon_mode: wgpu::PolygonMode::Fill,
151 unclipped_depth: false,
152 conservative: false,
153 },
154 depth_stencil: None,
155 multisample: wgpu::MultisampleState {
156 count: 1,
157 mask: !0,
158 alpha_to_coverage_enabled: false,
159 },
160 multiview_mask: None,
161 cache: None,
162 });
163
164 Ok(Self {
165 device,
166 queue,
167 pipeline,
168 bind_group_layout,
169 uniform_buffer,
170 sampler,
171 cached_bind_group: None,
172 })
173 }
174
175 fn is_cached(&self, key_a: usize, key_b: usize) -> bool {
177 if let Some(ref cached) = self.cached_bind_group {
178 cached.key_a == key_a && cached.key_b == key_b
179 } else {
180 false
181 }
182 }
183
184 fn get_cached_bind_group(&self) -> Option<&wgpu::BindGroup> {
186 self.cached_bind_group.as_ref().map(|c| &c.bind_group)
187 }
188
189 fn create_and_cache_bind_group(&mut self, texture_a: &TextureView, texture_b: &TextureView) {
191 let key_a = texture_a as *const _ as usize;
193 let key_b = texture_b as *const _ as usize;
194
195 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
197 label: Some("Blend Bind Group"),
198 layout: &self.bind_group_layout,
199 entries: &[
200 wgpu::BindGroupEntry {
201 binding: 0,
202 resource: wgpu::BindingResource::TextureView(texture_a),
203 },
204 wgpu::BindGroupEntry {
205 binding: 1,
206 resource: wgpu::BindingResource::TextureView(texture_b),
207 },
208 wgpu::BindGroupEntry {
209 binding: 2,
210 resource: wgpu::BindingResource::Sampler(&self.sampler),
211 },
212 wgpu::BindGroupEntry {
213 binding: 3,
214 resource: self.uniform_buffer.as_entire_binding(),
215 },
216 ],
217 });
218
219 self.cached_bind_group = Some(CachedBindGroup {
221 key_a,
222 key_b,
223 bind_group,
224 });
225 }
226
227 pub fn render(
229 &mut self,
230 texture_a: &TextureView,
231 texture_b: &TextureView,
232 output: &TextureView,
233 blend_pattern: u32,
234 blend_amount: f32,
235 time: f32,
236 ) -> Result<()> {
237 let uniforms = BlendUniforms {
239 blend_pattern,
240 blend_amount,
241 time,
242 _padding: 0.0,
243 };
244 self.queue
245 .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
246
247 let key_a = texture_a as *const _ as usize;
249 let key_b = texture_b as *const _ as usize;
250
251 if !self.is_cached(key_a, key_b) {
252 self.create_and_cache_bind_group(texture_a, texture_b);
253 }
254
255 let bind_group = self
256 .get_cached_bind_group()
257 .expect("bind group should be cached");
258
259 let mut encoder = self
261 .device
262 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
263 label: Some("Blend Encoder"),
264 });
265
266 {
268 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
269 label: Some("Blend Render Pass"),
270 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
271 view: output,
272 depth_slice: None,
273 resolve_target: None,
274 ops: wgpu::Operations {
275 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
276 store: wgpu::StoreOp::Store,
277 },
278 })],
279 depth_stencil_attachment: None,
280 timestamp_writes: None,
281 occlusion_query_set: None,
282 multiview_mask: None,
283 });
284
285 render_pass.set_pipeline(&self.pipeline);
286 render_pass.set_bind_group(0, bind_group, &[]);
287 render_pass.draw(0..3, 0..1);
288 }
289
290 self.queue.submit(std::iter::once(encoder.finish()));
292
293 Ok(())
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_blend_uniforms_size() {
303 assert_eq!(std::mem::size_of::<BlendUniforms>(), 16);
304 }
305}