1use crate::render::{CommonFrameOptions, RenderTest};
2use anyhow::anyhow;
3use image::RgbaImage;
4use librashader::runtime::wgpu::*;
5use librashader::runtime::{Size, Viewport};
6use librashader_runtime::image::{Image, UVDirection};
7use std::io::{Cursor, Write};
8use std::ops::DerefMut;
9use std::path::Path;
10use std::sync::Arc;
11use wgpu::{Adapter, Device, Instance, Queue, TexelCopyBufferInfo, Texture};
12use wgpu_types::{
13 BufferAddress, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, TexelCopyBufferLayout,
14 TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
15};
16
17use librashader::presets::ShaderPreset;
18use librashader::runtime::{FilterChainParameters, RuntimeParameters};
19use parking_lot::Mutex;
20
21pub struct Wgpu {
22 _instance: Instance,
23 _adapter: Adapter,
24 device: Arc<Device>,
25 queue: Arc<Queue>,
26 image: Image,
27 texture: Arc<Texture>,
28}
29
30struct BufferDimensions {
31 height: usize,
32 unpadded_bytes_per_row: usize,
33 padded_bytes_per_row: usize,
34}
35
36impl BufferDimensions {
37 fn new(width: usize, height: usize) -> Self {
38 let bytes_per_pixel = std::mem::size_of::<u32>();
39 let unpadded_bytes_per_row = width * bytes_per_pixel;
40 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
41 let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
42 let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
43 Self {
44 height,
45 unpadded_bytes_per_row,
46 padded_bytes_per_row,
47 }
48 }
49}
50
51impl RenderTest for Wgpu {
52 fn new(path: &Path) -> anyhow::Result<Self>
53 where
54 Self: Sized,
55 {
56 Wgpu::new(path)
57 }
58
59 fn image_size(&self) -> Size<u32> {
60 self.image.size
61 }
62
63 fn render_with_preset_and_params(
64 &mut self,
65 preset: ShaderPreset,
66 frame_count: usize,
67 output_size: Option<Size<u32>>,
68 param_setter: Option<&dyn Fn(&RuntimeParameters)>,
69 frame_options: Option<CommonFrameOptions>,
70 ) -> anyhow::Result<image::RgbaImage> {
71 let mut chain = FilterChain::load_from_preset(
72 preset,
73 &self.device,
74 &self.queue,
75 Some(&FilterChainOptions {
76 force_no_mipmaps: false,
77 enable_cache: true,
78 adapter_info: None,
79 ..Default::default()
80 }),
81 )?;
82 if let Some(setter) = param_setter {
83 setter(&chain.parameters());
84 }
85 let mut cmd = self
86 .device
87 .create_command_encoder(&CommandEncoderDescriptor { label: None });
88
89 let output_tex = self.device.create_texture(&TextureDescriptor {
90 label: None,
91 size: output_size.map_or(self.texture.size(), |size| size.into()),
92 mip_level_count: 1,
93 sample_count: 1,
94 dimension: TextureDimension::D2,
95 format: TextureFormat::Rgba8Unorm,
96 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
97 view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
98 });
99
100 let buffer_dimensions =
101 BufferDimensions::new(output_tex.width() as usize, output_tex.height() as usize);
102 let output_buf = Arc::new(self.device.create_buffer(&BufferDescriptor {
103 label: None,
104 size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height)
105 as BufferAddress, usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
107 mapped_at_creation: false,
108 }));
109
110 let view = output_tex.create_view(&wgpu::TextureViewDescriptor::default());
111 let output = WgpuOutputView::new_from_raw(
112 &view,
113 output_tex.size().into(),
114 TextureFormat::Rgba8Unorm,
115 );
116
117 let viewport = Viewport::new_render_target_sized_origin(output, None)?;
118 let options = frame_options.map(|options| FrameOptions {
119 clear_history: options.clear_history,
120 frame_direction: options.frame_direction,
121 rotation: options.rotation,
122 total_subframes: options.total_subframes,
123 current_subframe: options.current_subframe,
124 aspect_ratio: options.aspect_ratio,
125 frametime_delta: options.frametime_delta,
126 frames_per_second: options.frames_per_second,
127 ..Default::default()
128 });
129
130 for frame in 0..=frame_count {
131 chain.frame(&self.texture, &viewport, &mut cmd, frame, options.as_ref())?;
132 }
133
134 cmd.copy_texture_to_buffer(
135 output_tex.as_image_copy(),
136 TexelCopyBufferInfo {
137 buffer: &output_buf,
138 layout: TexelCopyBufferLayout {
139 offset: 0,
140 bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
141 rows_per_image: None,
142 },
143 },
144 output_tex.size(),
145 );
146
147 let si = self.queue.submit([cmd.finish()]);
148 self.device.poll(wgpu::PollType::Wait {
149 submission_index: Some(si),
150 timeout: None,
151 })?;
152
153 let capturable = Arc::clone(&output_buf);
154
155 let pixels = Arc::new(Mutex::new(Vec::new()));
156
157 let pixels_async = Arc::clone(&pixels);
158 output_buf
159 .slice(..)
160 .map_async(wgpu::MapMode::Read, move |r| {
161 if r.is_ok() {
162 let buffer = capturable.slice(..).get_mapped_range();
163 let mut pixels = pixels_async.lock();
164 pixels.resize(buffer.len(), 0);
165
166 let mut cursor = Cursor::new(pixels.deref_mut());
167 for chunk in buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
168 cursor
169 .write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row])
170 .unwrap()
171 }
172
173 cursor.into_inner();
174 }
175 capturable.unmap();
176 });
177
178 self.device.poll(wgpu::PollType::Wait {
179 submission_index: None,
180 timeout: None,
181 })?;
182
183 if pixels.lock().len() == 0 {
184 return Err(anyhow!("failed to copy pixels from buffer"));
185 }
186
187 let image = RgbaImage::from_raw(
188 output_tex.width(),
189 output_tex.height(),
190 pixels.lock().to_vec(),
191 )
192 .ok_or(anyhow!("Unable to create image from data"))?;
193
194 Ok(image)
195 }
196}
197
198impl Wgpu {
199 pub fn new(image: &Path) -> anyhow::Result<Self> {
200 pollster::block_on(async {
201 let instance = wgpu::Instance::default();
202 let adapter = instance
203 .request_adapter(&wgpu::RequestAdapterOptions {
204 power_preference: wgpu::PowerPreference::default(),
205 compatible_surface: None,
206 force_fallback_adapter: false,
207 })
208 .await?;
209
210 let (device, queue) = adapter
211 .request_device(&wgpu::DeviceDescriptor {
212 required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
213 | wgpu::Features::PIPELINE_CACHE
214 | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
215 | wgpu::Features::FLOAT32_FILTERABLE,
216 required_limits: wgpu::Limits::default(),
217 label: None,
218 memory_hints: Default::default(),
219 ..Default::default()
220 })
221 .await?;
222 let (image, texture) = Self::load_image(&device, &queue, image)?;
223
224 Ok(Self {
225 _instance: instance,
226 _adapter: adapter,
227 device: Arc::new(device),
228 queue: Arc::new(queue),
229 image,
230 texture: Arc::new(texture),
231 })
232 })
233 }
234
235 fn load_image(device: &Device, queue: &Queue, path: &Path) -> anyhow::Result<(Image, Texture)> {
236 let image = Image::load(path, UVDirection::TopLeft)?;
237 let texture = device.create_texture(&TextureDescriptor {
238 size: image.size.into(),
239 mip_level_count: 1,
240 sample_count: 1,
241 dimension: TextureDimension::D2,
242 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
243 format: wgpu::TextureFormat::Rgba8Unorm,
244 view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
245 label: None,
246 });
247
248 queue.write_texture(
249 wgpu::TexelCopyTextureInfo {
250 texture: &texture,
251 mip_level: 0,
252 origin: wgpu::Origin3d::ZERO,
253 aspect: wgpu::TextureAspect::All,
254 },
255 &image.bytes,
256 wgpu::TexelCopyBufferLayout {
257 offset: 0,
258 bytes_per_row: Some(4 * image.size.width),
259 rows_per_image: None,
260 },
261 image.size.into(),
262 );
263
264 let si = queue.submit([]);
265
266 device.poll(wgpu::PollType::Wait {
267 submission_index: Some(si),
268 timeout: None,
269 })?;
270
271 Ok((image, texture))
272 }
273}