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 padding2: 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 padding2: 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 padding2: 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 {
194 let mut cache = self.pipeline.lock().unwrap();
195 if let Some((cached_fmt, ref pipeline)) = *cache
196 && cached_fmt == format
197 {
198 return pipeline.clone();
199 }
200
201 let pipeline = self
202 .device
203 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
204 label: Some("Render Pipeline"),
205 layout: Some(&self.pipeline_layout),
206 vertex: wgpu::VertexState {
207 module: &self.shader,
208 entry_point: Some("vs_main"),
209 buffers: &[],
210 compilation_options: wgpu::PipelineCompilationOptions::default(),
211 },
212 fragment: Some(wgpu::FragmentState {
213 module: &self.shader,
214 entry_point: Some("fs_main"),
215 targets: &[Some(wgpu::ColorTargetState {
216 format,
217 blend: Some(wgpu::BlendState::REPLACE),
218 write_mask: wgpu::ColorWrites::ALL,
219 })],
220 compilation_options: wgpu::PipelineCompilationOptions::default(),
221 }),
222 primitive: wgpu::PrimitiveState {
223 topology: wgpu::PrimitiveTopology::TriangleList,
224 strip_index_format: None,
225 front_face: wgpu::FrontFace::Ccw,
226 cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
228 unclipped_depth: false,
229 conservative: false,
230 },
231 depth_stencil: None,
232 multisample: wgpu::MultisampleState {
233 count: 1,
234 mask: !0,
235 alpha_to_coverage_enabled: false,
236 },
237 multiview: None,
238 cache: None,
239 });
240
241 *cache = Some((format, pipeline.clone()));
242 pipeline
243 }
244
245 pub fn create_texture(&self, width: u32, height: u32) -> (wgpu::Texture, wgpu::BindGroup) {
248 let size = wgpu::Extent3d {
249 width,
250 height,
251 depth_or_array_layers: 1,
252 };
253
254 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
255 label: None,
256 size,
257 mip_level_count: 1,
258 sample_count: 1,
259 dimension: wgpu::TextureDimension::D2,
260 format: wgpu::TextureFormat::Rgba8UnormSrgb,
261 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
262 view_formats: &[],
263 });
264
265 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
266 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
267 address_mode_u: wgpu::AddressMode::ClampToEdge,
268 address_mode_v: wgpu::AddressMode::ClampToEdge,
269 address_mode_w: wgpu::AddressMode::ClampToEdge,
270 mag_filter: wgpu::FilterMode::Linear,
271 min_filter: wgpu::FilterMode::Nearest,
272 mipmap_filter: wgpu::FilterMode::Nearest,
273 ..Default::default()
274 });
275
276 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
277 layout: &self.bind_group_layout_tex,
278 entries: &[
279 wgpu::BindGroupEntry {
280 binding: 0,
281 resource: wgpu::BindingResource::TextureView(&view),
282 },
283 wgpu::BindGroupEntry {
284 binding: 1,
285 resource: wgpu::BindingResource::Sampler(&sampler),
286 },
287 ],
288 label: None,
289 });
290
291 (texture, bind_group)
292 }
293
294 pub fn update_texture(&self, texture: &wgpu::Texture, rgba: &[u8], width: u32, height: u32) {
296 self.queue.write_texture(
297 wgpu::TexelCopyTextureInfo {
298 texture,
299 mip_level: 0,
300 origin: wgpu::Origin3d::ZERO,
301 aspect: wgpu::TextureAspect::All,
302 },
303 rgba,
304 wgpu::TexelCopyBufferLayout {
305 offset: 0,
306 bytes_per_row: Some(4 * width),
307 rows_per_image: Some(height),
308 },
309 wgpu::Extent3d {
310 width,
311 height,
312 depth_or_array_layers: 1,
313 },
314 );
315 }
316
317 pub fn load_texture(
318 &self,
319 image: &image::DynamicImage,
320 ) -> anyhow::Result<(wgpu::Texture, wgpu::BindGroup)> {
321 let rgba = image.to_rgba8();
322 let (width, height) = image.dimensions();
323 let (texture, bind_group) = self.create_texture(width, height);
324 self.update_texture(&texture, &rgba, width, height);
325 Ok((texture, bind_group))
326 }
327
328 pub fn update_uniforms(&self, uniforms: Uniforms) {
329 self.queue
330 .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
331 }
332
333 pub fn render_frame(&self, request: FrameRequest) -> anyhow::Result<FrameStatus> {
336 let FrameRequest {
337 surface,
338 format,
339 bg_bind,
340 new_bind,
341 effect,
342 width,
343 height,
344 img_width,
345 img_height,
346 old_img_width,
347 old_img_height,
348 } = request;
349 let mut uniforms = Uniforms::from_effect(effect);
350 uniforms.resolution = [width as f32, height as f32];
351 uniforms.image_resolution = [img_width as f32, img_height as f32];
352 uniforms.old_image_resolution = [old_img_width as f32, old_img_height as f32];
353 self.update_uniforms(uniforms);
354
355 let pipeline = self.get_pipeline(format);
356 let output = match surface.get_current_texture() {
361 Ok(texture) => texture,
362 Err(wgpu::SurfaceError::Timeout) => return Ok(FrameStatus::TimedOut),
363 Err(err) => {
364 return Err(anyhow::anyhow!(
365 "failed to acquire swapchain texture: {err:?}"
366 ));
367 }
368 };
369 let view = output
370 .texture
371 .create_view(&wgpu::TextureViewDescriptor::default());
372
373 let mut encoder = self
374 .device
375 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
376 label: Some("Render Encoder"),
377 });
378
379 {
380 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
381 label: Some("Wallpaper Render Pass"),
382 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
383 view: &view,
384 resolve_target: None,
385 ops: wgpu::Operations {
386 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
387 store: wgpu::StoreOp::Store,
388 },
389 })],
390 depth_stencil_attachment: None,
391 occlusion_query_set: None,
392 timestamp_writes: None,
393 });
394
395 render_pass.set_pipeline(&pipeline);
396 render_pass.set_bind_group(0, bg_bind, &[]);
397 render_pass.set_bind_group(1, new_bind, &[]);
398 render_pass.set_bind_group(2, &self.uniform_bind_group, &[]);
399 render_pass.draw(0..3, 0..1);
401 }
402
403 self.queue.submit(std::iter::once(encoder.finish()));
404 output.present();
405
406 Ok(FrameStatus::Presented)
407 }
408}
409
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub enum FrameStatus {
415 Presented,
416 TimedOut,
417}
418
419pub struct FrameRequest<'a> {
421 pub surface: &'a wgpu::Surface<'a>,
422 pub format: wgpu::TextureFormat,
423 pub bg_bind: &'a wgpu::BindGroup,
425 pub new_bind: &'a wgpu::BindGroup,
427 pub effect: &'a crate::animation::EffectUniforms,
429 pub width: u32,
430 pub height: u32,
431 pub img_width: u32,
432 pub img_height: u32,
433 pub old_img_width: u32,
434 pub old_img_height: u32,
435}