veloren_voxygen/render/renderer/
screenshot.rs1use super::super::pipelines::blit;
2use common_base::prof_span;
3use crossbeam_channel;
4use tracing::error;
5
6pub type ScreenshotFn = Box<dyn FnOnce(Result<image::RgbImage, String>) + Send>;
7
8pub struct TakeScreenshot {
9 bind_group: blit::BindGroup,
10 view: wgpu::TextureView,
11 texture: wgpu::Texture,
12 buffer: wgpu::Buffer,
13 screenshot_fn: ScreenshotFn,
14 width: u32,
16 height: u32,
17 bytes_per_pixel: u32,
18 tex_format: wgpu::TextureFormat,
20}
21
22impl TakeScreenshot {
23 pub fn new(
24 device: &wgpu::Device,
25 blit_layout: &blit::BlitLayout,
26 sampler: &wgpu::Sampler,
27 surface_config: &wgpu::SurfaceConfiguration,
29 screenshot_fn: ScreenshotFn,
32 ) -> Self {
33 let texture = device.create_texture(&wgpu::TextureDescriptor {
34 label: Some("screenshot tex"),
35 size: wgpu::Extent3d {
36 width: surface_config.width,
37 height: surface_config.height,
38 depth_or_array_layers: 1,
39 },
40 mip_level_count: 1,
41 sample_count: 1,
42 dimension: wgpu::TextureDimension::D2,
43 format: surface_config.format,
44 usage: wgpu::TextureUsages::COPY_SRC
45 | wgpu::TextureUsages::TEXTURE_BINDING
46 | wgpu::TextureUsages::RENDER_ATTACHMENT,
47 view_formats: &[],
48 });
49
50 let view = texture.create_view(&wgpu::TextureViewDescriptor {
51 label: Some("screenshot tex view"),
52 format: Some(surface_config.format),
53 dimension: Some(wgpu::TextureViewDimension::D2),
54 usage: None,
55 aspect: wgpu::TextureAspect::All,
56 base_mip_level: 0,
57 mip_level_count: None,
58 base_array_layer: 0,
59 array_layer_count: None,
60 });
61
62 let bind_group = blit_layout.bind(device, &view, sampler);
63
64 let bytes_per_pixel = surface_config.format.block_copy_size(None).unwrap();
65 let padded_bytes_per_row = padded_bytes_per_row(surface_config.width, bytes_per_pixel);
66
67 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
68 label: Some("screenshot download buffer"),
69 size: (padded_bytes_per_row * surface_config.height) as u64,
70 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
71 mapped_at_creation: false,
72 });
73
74 Self {
75 bind_group,
76 texture,
77 view,
78 buffer,
79 screenshot_fn,
80 width: surface_config.width,
81 height: surface_config.height,
82 bytes_per_pixel,
83 tex_format: surface_config.format,
84 }
85 }
86
87 pub fn texture_view(&self) -> &wgpu::TextureView { &self.view }
90
91 pub fn bind_group(&self) -> &wgpu::BindGroup { &self.bind_group.bind_group }
94
95 pub fn copy_to_buffer(self, encoder: &mut wgpu::CommandEncoder) -> impl FnOnce() + use<> {
102 let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel);
104 encoder.copy_texture_to_buffer(
106 wgpu::TexelCopyTextureInfo {
107 texture: &self.texture,
108 mip_level: 0,
109 origin: wgpu::Origin3d::ZERO,
110 aspect: wgpu::TextureAspect::All,
111 },
112 wgpu::TexelCopyBufferInfo {
113 buffer: &self.buffer,
114 layout: wgpu::TexelCopyBufferLayout {
115 offset: 0,
116 bytes_per_row: Some(padded_bytes_per_row),
117 rows_per_image: None,
118 },
119 },
120 wgpu::Extent3d {
121 width: self.width,
122 height: self.height,
123 depth_or_array_layers: 1,
124 },
125 );
126
127 move || {
128 std::thread::Builder::new()
132 .name("screenshot".into())
133 .spawn(move || {
134 self.download_and_handle_internal();
135 })
136 .expect("Failed to spawn screenshot thread");
137 }
138 }
139
140 fn download_and_handle_internal(self) {
142 prof_span!("download_and_handle_internal");
143 let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel);
145
146 let buffer = std::sync::Arc::new(self.buffer);
148 let buffer2 = std::sync::Arc::clone(&buffer);
149 let buffer_slice = buffer.slice(..);
150 let (map_result_sender, map_result_receiver) = crossbeam_channel::bounded(1);
151 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
152 map_result_sender
153 .send(result)
154 .expect("seems like the receiver broke, which should not happen");
155 });
156 let result = match map_result_receiver.recv() {
157 Ok(result) => result,
158 Err(e) => {
159 error!(
160 ?e,
161 "map_async never send the result for the screenshot mapping"
162 );
163 return;
164 },
165 };
166 let padded_buffer;
167 let buffer_slice = buffer2.slice(..);
168 let rows = match result {
169 Ok(()) => {
170 padded_buffer = buffer_slice.get_mapped_range();
172 padded_buffer
173 .chunks(padded_bytes_per_row as usize)
174 .map(|padded_chunk| {
175 &padded_chunk[..self.width as usize * self.bytes_per_pixel as usize]
176 })
177 },
178 Err(err) => {
180 error!(
181 ?err,
182 "Failed to map buffer for downloading a screenshot from the GPU"
183 );
184 return;
185 },
186 };
187
188 let bytes_per_rgb = 3;
191 let mut pixel_bytes =
192 Vec::with_capacity(self.width as usize * self.height as usize * bytes_per_rgb);
193 let image = match self.tex_format {
195 wgpu::TextureFormat::Bgra8UnormSrgb => {
196 prof_span!("copy image");
197 rows.for_each(|row| {
198 let (pixels, rest) = row.as_chunks();
199 assert!(
200 rest.is_empty(),
201 "Always valid because each pixel uses four bytes"
202 );
203 for &[b, g, r, _a] in pixels {
205 pixel_bytes.extend_from_slice(&[r, g, b])
206 }
207 });
208
209 Ok(pixel_bytes)
210 },
211 wgpu::TextureFormat::Rgba8UnormSrgb => {
212 prof_span!("copy image");
213 rows.for_each(|row| {
214 let (pixels, rest) = row.as_chunks();
215 assert!(
216 rest.is_empty(),
217 "Always valid because each pixel uses four bytes"
218 );
219 for &[r, g, b, _a] in pixels {
221 pixel_bytes.extend_from_slice(&[r, g, b])
222 }
223 });
224
225 Ok(pixel_bytes)
226 },
227 format => Err(format!(
228 "Unhandled format for screenshot texture: {:?}",
229 format,
230 )),
231 }
232 .map(|pixel_bytes| {
233 image::RgbImage::from_vec(self.width, self.height, pixel_bytes).expect(
234 "Failed to create ImageBuffer! Buffer was not large enough. This should not occur",
235 )
236 });
237 (self.screenshot_fn)(image);
239 }
240}
241
242fn padded_bytes_per_row(width: u32, bytes_per_pixel: u32) -> u32 {
244 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
245 let unpadded_bytes_per_row = width * bytes_per_pixel;
246 let padding = (align - unpadded_bytes_per_row % align) % align;
247 unpadded_bytes_per_row + padding
248}