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