1use image::GenericImageView;
2use wgpu::util::DeviceExt;
3
4pub struct Renderer {
5 pub instance: wgpu::Instance,
6 pub adapter: wgpu::Adapter,
7 pub device: wgpu::Device,
8 pub queue: wgpu::Queue,
9 pub bind_group_layout_tex: wgpu::BindGroupLayout,
10 pub bind_group_layout_uni: wgpu::BindGroupLayout,
11 pipeline_layout: wgpu::PipelineLayout,
12 shader: wgpu::ShaderModule,
13 pipeline: std::sync::Mutex<Option<(wgpu::TextureFormat, wgpu::RenderPipeline)>>,
15}
16
17pub struct PerOutputUniforms {
20 pub buffer: wgpu::Buffer,
21 pub bind_group: wgpu::BindGroup,
22}
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
26pub struct Uniforms {
27 pub time: f32,
28 pub progress: f32,
29 pub effect_type: u32,
30 pub padding: u32,
31 pub resolution: [f32; 2],
32 pub image_resolution: [f32; 2],
33 pub old_image_resolution: [f32; 2],
34 pub param_a: f32,
35 pub param_b: f32,
36 pub param_c: f32,
37 pub param_d: f32,
38 pub origin: [f32; 2],
39 pub direction: [f32; 2],
40 pub easing: u32,
41 pub scaling_mode: u32,
42}
43
44impl Uniforms {
45 pub fn from_effect(effect: &crate::animation::EffectUniforms) -> Self {
46 Self {
47 time: effect.progress,
48 progress: effect.progress,
49 effect_type: effect.effect_type,
50 padding: 0,
51 resolution: [1920.0, 1080.0],
52 image_resolution: [1920.0, 1080.0],
53 old_image_resolution: [1920.0, 1080.0],
54 param_a: effect.param_a,
55 param_b: effect.param_b,
56 param_c: effect.param_c,
57 param_d: effect.param_d,
58 origin: effect.origin,
59 direction: effect.direction,
60 easing: effect.easing,
61 scaling_mode: 0,
62 }
63 }
64}
65
66impl Default for Uniforms {
67 fn default() -> Self {
68 Self {
69 time: 0.0,
70 progress: 0.0,
71 effect_type: 0,
72 padding: 0,
73 resolution: [1920.0, 1080.0],
74 image_resolution: [1920.0, 1080.0],
75 old_image_resolution: [1920.0, 1080.0],
76 param_a: 0.0,
77 param_b: 0.0,
78 param_c: 0.0,
79 param_d: 0.0,
80 origin: [0.5, 0.5],
81 direction: [0.0, 0.0],
82 easing: 3,
83 scaling_mode: 0,
84 }
85 }
86}
87
88impl Renderer {
89 pub async fn new() -> anyhow::Result<Self> {
90 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
91 backends: wgpu::Backends::all(),
92 ..Default::default()
93 });
94
95 let adapter = instance
96 .request_adapter(&wgpu::RequestAdapterOptions {
97 power_preference: wgpu::PowerPreference::HighPerformance,
98 compatible_surface: None,
99 force_fallback_adapter: false,
100 })
101 .await
102 .ok_or_else(|| anyhow::anyhow!("Failed to find suitable adapter"))?;
103
104 let (device, queue) = adapter
105 .request_device(
106 &wgpu::DeviceDescriptor {
107 label: None,
108 required_features: wgpu::Features::empty(),
109 required_limits: wgpu::Limits::default(),
110 memory_hints: wgpu::MemoryHints::default(),
111 },
112 None,
113 )
114 .await?;
115
116 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
117 label: Some("Effects Shader"),
118 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
119 crate::shader::EFFECTS_SHADER,
120 )),
121 });
122
123 let bind_group_layout_tex =
124 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
125 entries: &[
126 wgpu::BindGroupLayoutEntry {
127 binding: 0,
128 visibility: wgpu::ShaderStages::FRAGMENT,
129 ty: wgpu::BindingType::Texture {
130 multisampled: false,
131 view_dimension: wgpu::TextureViewDimension::D2,
132 sample_type: wgpu::TextureSampleType::Float { filterable: true },
133 },
134 count: None,
135 },
136 wgpu::BindGroupLayoutEntry {
137 binding: 1,
138 visibility: wgpu::ShaderStages::FRAGMENT,
139 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
140 count: None,
141 },
142 ],
143 label: Some("texture_bind_group_layout"),
144 });
145
146 let bind_group_layout_uni =
147 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
148 entries: &[wgpu::BindGroupLayoutEntry {
149 binding: 0,
150 visibility: wgpu::ShaderStages::FRAGMENT,
151 ty: wgpu::BindingType::Buffer {
152 ty: wgpu::BufferBindingType::Uniform,
153 has_dynamic_offset: false,
154 min_binding_size: None,
155 },
156 count: None,
157 }],
158 label: Some("uniform_bind_group_layout"),
159 });
160
161 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
162 label: Some("Render Pipeline Layout"),
163 bind_group_layouts: &[
164 &bind_group_layout_tex,
165 &bind_group_layout_tex,
166 &bind_group_layout_uni,
167 ],
168 push_constant_ranges: &[],
169 });
170
171 Ok(Self {
172 instance,
173 adapter,
174 device,
175 queue,
176 pipeline_layout,
177 shader,
178 bind_group_layout_tex,
179 bind_group_layout_uni,
180 pipeline: std::sync::Mutex::new(None),
181 })
182 }
183
184 pub fn create_per_output_uniforms(&self) -> PerOutputUniforms {
187 let uniforms = Uniforms::default();
188 let buffer = self
189 .device
190 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
191 label: Some("Per-Output Uniform Buffer"),
192 contents: bytemuck::cast_slice(&[uniforms]),
193 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
194 });
195 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
196 layout: &self.bind_group_layout_uni,
197 entries: &[wgpu::BindGroupEntry {
198 binding: 0,
199 resource: buffer.as_entire_binding(),
200 }],
201 label: Some("per_output_uniform_bind_group"),
202 });
203 PerOutputUniforms { buffer, bind_group }
204 }
205
206 fn get_pipeline(&self, format: wgpu::TextureFormat) -> wgpu::RenderPipeline {
207 let mut cache = self.pipeline.lock().unwrap();
208 if let Some((cached_fmt, ref pipeline)) = *cache
209 && cached_fmt == format
210 {
211 return pipeline.clone();
212 }
213
214 let pipeline = self
215 .device
216 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
217 label: Some("Render Pipeline"),
218 layout: Some(&self.pipeline_layout),
219 vertex: wgpu::VertexState {
220 module: &self.shader,
221 entry_point: Some("vs_main"),
222 buffers: &[],
223 compilation_options: wgpu::PipelineCompilationOptions::default(),
224 },
225 fragment: Some(wgpu::FragmentState {
226 module: &self.shader,
227 entry_point: Some("fs_main"),
228 targets: &[Some(wgpu::ColorTargetState {
229 format,
230 blend: Some(wgpu::BlendState::REPLACE),
231 write_mask: wgpu::ColorWrites::ALL,
232 })],
233 compilation_options: wgpu::PipelineCompilationOptions::default(),
234 }),
235 primitive: wgpu::PrimitiveState {
236 topology: wgpu::PrimitiveTopology::TriangleList,
237 strip_index_format: None,
238 front_face: wgpu::FrontFace::Ccw,
239 cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
241 unclipped_depth: false,
242 conservative: false,
243 },
244 depth_stencil: None,
245 multisample: wgpu::MultisampleState {
246 count: 1,
247 mask: !0,
248 alpha_to_coverage_enabled: false,
249 },
250 multiview: None,
251 cache: None,
252 });
253
254 *cache = Some((format, pipeline.clone()));
255 pipeline
256 }
257
258 pub fn create_texture(&self, width: u32, height: u32) -> (wgpu::Texture, wgpu::BindGroup) {
259 let size = wgpu::Extent3d {
260 width,
261 height,
262 depth_or_array_layers: 1,
263 };
264
265 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
266 label: None,
267 size,
268 mip_level_count: 1,
269 sample_count: 1,
270 dimension: wgpu::TextureDimension::D2,
271 format: wgpu::TextureFormat::Rgba8UnormSrgb,
272 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
273 view_formats: &[],
274 });
275
276 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
277 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
278 address_mode_u: wgpu::AddressMode::ClampToEdge,
279 address_mode_v: wgpu::AddressMode::ClampToEdge,
280 address_mode_w: wgpu::AddressMode::ClampToEdge,
281 mag_filter: wgpu::FilterMode::Linear,
282 min_filter: wgpu::FilterMode::Nearest,
283 mipmap_filter: wgpu::FilterMode::Nearest,
284 ..Default::default()
285 });
286
287 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
288 layout: &self.bind_group_layout_tex,
289 entries: &[
290 wgpu::BindGroupEntry {
291 binding: 0,
292 resource: wgpu::BindingResource::TextureView(&view),
293 },
294 wgpu::BindGroupEntry {
295 binding: 1,
296 resource: wgpu::BindingResource::Sampler(&sampler),
297 },
298 ],
299 label: None,
300 });
301
302 (texture, bind_group)
303 }
304
305 pub fn update_texture(&self, texture: &wgpu::Texture, rgba: &[u8], width: u32, height: u32) {
306 self.queue.write_texture(
307 wgpu::TexelCopyTextureInfo {
308 texture,
309 mip_level: 0,
310 origin: wgpu::Origin3d::ZERO,
311 aspect: wgpu::TextureAspect::All,
312 },
313 rgba,
314 wgpu::TexelCopyBufferLayout {
315 offset: 0,
316 bytes_per_row: Some(4 * width),
317 rows_per_image: Some(height),
318 },
319 wgpu::Extent3d {
320 width,
321 height,
322 depth_or_array_layers: 1,
323 },
324 );
325 }
326
327 pub fn load_texture(
328 &self,
329 image: &image::DynamicImage,
330 ) -> anyhow::Result<(wgpu::Texture, wgpu::BindGroup)> {
331 let rgba = image.to_rgba8();
332 let (width, height) = image.dimensions();
333 let (texture, bind_group) = self.create_texture(width, height);
334 self.update_texture(&texture, &rgba, width, height);
335 Ok((texture, bind_group))
336 }
337
338 pub fn update_uniforms(&self, buffer: &wgpu::Buffer, uniforms: Uniforms) {
339 self.queue
340 .write_buffer(buffer, 0, bytemuck::cast_slice(&[uniforms]));
341 }
342
343 pub fn render_frame(
344 &self,
345 request: FrameRequest,
346 per_output: &PerOutputUniforms,
347 ) -> anyhow::Result<FrameStatus> {
348 let FrameRequest {
349 surface,
350 format,
351 bg_bind,
352 new_bind,
353 effect,
354 width,
355 height,
356 img_width,
357 img_height,
358 old_img_width,
359 old_img_height,
360 scaling_mode,
361 } = request;
362 let mut uniforms = Uniforms::from_effect(effect);
363 uniforms.resolution = [width as f32, height as f32];
364 uniforms.image_resolution = [img_width as f32, img_height as f32];
365 uniforms.old_image_resolution = [old_img_width as f32, old_img_height as f32];
366 uniforms.scaling_mode = scaling_mode;
367 self.update_uniforms(&per_output.buffer, uniforms);
368
369 let pipeline = self.get_pipeline(format);
370 let output = match surface.get_current_texture() {
375 Ok(texture) => texture,
376 Err(wgpu::SurfaceError::Timeout) => return Ok(FrameStatus::TimedOut),
377 Err(err) => {
378 return Err(anyhow::anyhow!(
379 "failed to acquire swapchain texture: {err:?}"
380 ));
381 }
382 };
383 let view = output
384 .texture
385 .create_view(&wgpu::TextureViewDescriptor::default());
386
387 let mut encoder = self
388 .device
389 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
390 label: Some("Render Encoder"),
391 });
392
393 {
394 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
395 label: Some("Wallpaper Render Pass"),
396 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
397 view: &view,
398 resolve_target: None,
399 ops: wgpu::Operations {
400 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
401 store: wgpu::StoreOp::Store,
402 },
403 })],
404 depth_stencil_attachment: None,
405 occlusion_query_set: None,
406 timestamp_writes: None,
407 });
408
409 render_pass.set_pipeline(&pipeline);
410 render_pass.set_bind_group(0, bg_bind, &[]);
411 render_pass.set_bind_group(1, new_bind, &[]);
412 render_pass.set_bind_group(2, &per_output.bind_group, &[]);
413 render_pass.draw(0..3, 0..1);
415 }
416
417 self.queue.submit(std::iter::once(encoder.finish()));
418 output.present();
419
420 Ok(FrameStatus::Presented)
421 }
422}
423
424#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428pub enum FrameStatus {
429 Presented,
430 TimedOut,
431}
432
433pub struct FrameRequest<'a> {
435 pub surface: &'a wgpu::Surface<'a>,
436 pub format: wgpu::TextureFormat,
437 pub bg_bind: &'a wgpu::BindGroup,
439 pub new_bind: &'a wgpu::BindGroup,
441 pub effect: &'a crate::animation::EffectUniforms,
443 pub width: u32,
444 pub height: u32,
445 pub img_width: u32,
446 pub img_height: u32,
447 pub old_img_width: u32,
448 pub old_img_height: u32,
449 pub scaling_mode: u32,
451}