maplibre/render/resource/
surface.rs1use std::{mem::size_of, sync::Arc};
5
6use wgpu::TextureFormatFeatures;
7
8use crate::{
9 render::{
10 error::RenderError,
11 eventually::HasChanged,
12 resource::texture::TextureView,
13 settings::{Msaa, RendererSettings},
14 },
15 window::{HeadedMapWindow, MapWindow, PhysicalSize},
16};
17
18pub struct BufferDimensions {
19 pub width: u32,
20 pub height: u32,
21 pub unpadded_bytes_per_row: u32,
22 pub padded_bytes_per_row: u32,
23}
24
25impl BufferDimensions {
26 fn new(size: PhysicalSize) -> Self {
27 let bytes_per_pixel = size_of::<u32>() as u32;
28 let unpadded_bytes_per_row = size.width() * bytes_per_pixel;
29
30 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
31 let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
32 let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
33 Self {
34 width: size.width(),
35 height: size.height(),
36 unpadded_bytes_per_row,
37 padded_bytes_per_row,
38 }
39 }
40}
41
42pub struct WindowHead {
43 surface: wgpu::Surface<'static>,
44 size: PhysicalSize,
45
46 texture_format: wgpu::TextureFormat,
47 render_format: wgpu::TextureFormat,
51 present_mode: wgpu::PresentMode,
52 texture_format_features: TextureFormatFeatures,
53}
54
55fn strip_srgb(format: wgpu::TextureFormat) -> wgpu::TextureFormat {
59 match format {
60 wgpu::TextureFormat::Rgba8UnormSrgb => wgpu::TextureFormat::Rgba8Unorm,
61 wgpu::TextureFormat::Bgra8UnormSrgb => wgpu::TextureFormat::Bgra8Unorm,
62 other => other,
63 }
64}
65
66impl WindowHead {
67 pub fn resize_and_configure(&mut self, width: u32, height: u32, device: &wgpu::Device) {
68 self.size = PhysicalSize::new(width, height).unwrap();
69 self.configure(device);
70 }
71
72 pub fn configure(&self, device: &wgpu::Device) {
73 let mut view_formats = vec![self.texture_format];
74 if self.render_format != self.texture_format {
75 view_formats.push(self.render_format);
76 }
77
78 let surface_config = wgpu::SurfaceConfiguration {
79 alpha_mode: wgpu::CompositeAlphaMode::Auto,
80 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
81 format: self.texture_format,
82 width: self.size.width(),
83 height: self.size.height(),
84 present_mode: self.present_mode,
85 view_formats,
86 desired_maximum_frame_latency: 2,
87 };
88
89 self.surface.configure(device, &surface_config);
90 }
91
92 pub fn recreate_surface<MW>(
93 &mut self,
94 window: &MW,
95 instance: &wgpu::Instance,
96 ) -> Result<(), RenderError>
97 where
98 MW: MapWindow + HeadedMapWindow,
99 {
100 self.surface = unsafe {
101 instance
102 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window.handle())?)?
103 };
104 Ok(())
105 }
106
107 pub fn surface(&self) -> &wgpu::Surface {
108 &self.surface
109 }
110}
111
112pub struct BufferedTextureHead {
113 texture: wgpu::Texture,
114 texture_format: wgpu::TextureFormat,
115 output_buffer: wgpu::Buffer,
116 buffer_dimensions: BufferDimensions,
117}
118
119#[cfg(feature = "headless")]
120#[derive(thiserror::Error, Debug)]
121pub enum WriteImageError {
122 #[error("error while rendering to image")]
123 WriteImage(#[from] png::EncodingError),
124 #[error("could not create file to save as an image")]
125 CreateImageFileFailed(#[from] std::io::Error),
126}
127
128#[cfg(feature = "headless")]
129impl BufferedTextureHead {
130 pub fn map_async(&self, device: &wgpu::Device) -> wgpu::BufferSlice {
131 let buffer_slice = self.output_buffer.slice(..);
133 buffer_slice.map_async(wgpu::MapMode::Read, |_| ());
134
135 device.poll(wgpu::Maintain::Wait);
139 buffer_slice
140 }
141
142 pub fn unmap(&self) {
143 self.output_buffer.unmap();
144 }
145
146 pub fn write_png<'a>(
147 &self,
148 padded_buffer: &wgpu::BufferView<'a>,
149 png_output_path: &str,
150 ) -> Result<(), WriteImageError> {
151 use std::{fs::File, io::Write};
152 let mut png_encoder = png::Encoder::new(
153 File::create(png_output_path)?,
154 self.buffer_dimensions.width as u32,
155 self.buffer_dimensions.height as u32,
156 );
157 png_encoder.set_depth(png::BitDepth::Eight);
158 png_encoder.set_color(png::ColorType::Rgba);
159 let mut png_writer = png_encoder
160 .write_header()?
161 .into_stream_writer_with_size(self.buffer_dimensions.unpadded_bytes_per_row as usize)?;
162
163 for chunk in padded_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row as usize) {
165 png_writer
166 .write_all(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row as usize])?
167 }
168 png_writer.finish()?;
169 Ok(())
170 }
171
172 pub fn copy_texture(&self) -> wgpu::ImageCopyTexture<'_> {
173 self.texture.as_image_copy()
174 }
175
176 pub fn buffer(&self) -> &wgpu::Buffer {
177 &self.output_buffer
178 }
179
180 pub fn bytes_per_row(&self) -> u32 {
181 self.buffer_dimensions.padded_bytes_per_row
182 }
183}
184
185pub enum Head {
186 Headed(WindowHead),
187 Headless(Arc<BufferedTextureHead>),
188}
189
190pub struct Surface {
191 size: PhysicalSize,
192 head: Head,
193}
194
195impl Surface {
196 pub fn from_surface<MW>(
197 surface: wgpu::Surface<'static>,
198 adapter: &wgpu::Adapter,
199 window: &MW,
200 settings: &RendererSettings,
201 ) -> Self
202 where
203 MW: MapWindow + HeadedMapWindow,
204 {
205 let size = window.size();
206
207 let capabilities = surface.get_capabilities(adapter);
208 log::info!("adapter capabilities on surface: {capabilities:?}");
209
210 let texture_format = settings
211 .texture_format
212 .or_else(|| capabilities.formats.first().cloned())
213 .unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
214 let render_format = strip_srgb(texture_format);
215 log::info!("surface format: {texture_format:?}, render format: {render_format:?}");
216
217 let texture_format_features = adapter.get_texture_format_features(texture_format);
218 log::info!("format features: {texture_format_features:?}");
219
220 Self {
221 size,
222 head: Head::Headed(WindowHead {
223 surface,
224 size,
225 texture_format,
226 render_format,
227 texture_format_features,
228 present_mode: settings.present_mode,
229 }),
230 }
231 }
232
233 pub fn from_image<MW>(device: &wgpu::Device, window: &MW, settings: &RendererSettings) -> Self
235 where
236 MW: MapWindow,
237 {
238 let size = window.size();
239
240 let buffer_dimensions = BufferDimensions::new(size);
245
246 let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
248 label: Some("BufferedTextureHead buffer"),
249 size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
250 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
251 mapped_at_creation: false,
252 });
253
254 let format = settings
256 .texture_format
257 .unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
258
259 let texture_descriptor = wgpu::TextureDescriptor {
260 label: Some("Surface texture"),
261 size: wgpu::Extent3d {
262 width: size.width(),
263 height: size.height(),
264 depth_or_array_layers: 1,
265 },
266 mip_level_count: 1,
267 sample_count: 1,
268 dimension: wgpu::TextureDimension::D2,
269 format,
270 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
271 view_formats: &[format],
272 };
273 let texture = device.create_texture(&texture_descriptor);
274
275 Self {
276 size,
277 head: Head::Headless(Arc::new(BufferedTextureHead {
278 texture,
279 texture_format: format,
280 output_buffer,
281 buffer_dimensions,
282 })),
283 }
284 }
285
286 pub fn surface_format(&self) -> wgpu::TextureFormat {
287 match &self.head {
288 Head::Headed(headed) => headed.render_format,
289 Head::Headless(headless) => headless.texture_format,
290 }
291 }
292
293 #[tracing::instrument(name = "create_view", skip_all)]
294 pub fn create_view(&self, device: &wgpu::Device) -> TextureView {
295 match &self.head {
296 Head::Headed(window) => {
297 let WindowHead {
298 surface,
299 render_format,
300 ..
301 } = window;
302 let frame = match surface.get_current_texture() {
303 Ok(view) => view,
304 Err(wgpu::SurfaceError::Outdated) => {
305 log::warn!("surface outdated");
306 window.configure(device);
307 surface
308 .get_current_texture()
309 .expect("Error reconfiguring surface")
310 }
311 err => err.expect("Failed to acquire next swap chain texture!"),
312 };
313 let view = frame.texture.create_view(&wgpu::TextureViewDescriptor {
315 format: Some(*render_format),
316 ..Default::default()
317 });
318 TextureView::SurfaceTexture {
319 view,
320 texture: frame,
321 }
322 }
323 Head::Headless(arc) => arc
324 .texture
325 .create_view(&wgpu::TextureViewDescriptor::default())
326 .into(),
327 }
328 }
329
330 pub fn size(&self) -> PhysicalSize {
331 self.size
332 }
333
334 pub fn resize(&mut self, size: PhysicalSize) {
335 self.size = size;
336 }
337
338 pub fn reconfigure(&mut self, device: &wgpu::Device) {
339 match &mut self.head {
340 Head::Headed(window) => {
341 if window.has_changed(&(self.size.width(), self.size.height())) {
342 window.resize_and_configure(self.size.width(), self.size.height(), device);
343 }
344 }
345 Head::Headless(_) => {}
346 }
347 }
348
349 pub fn recreate<MW>(
350 &mut self,
351 window: &MW,
352 instance: &wgpu::Instance,
353 ) -> Result<(), RenderError>
354 where
355 MW: MapWindow + HeadedMapWindow,
356 {
357 match &mut self.head {
358 Head::Headed(window_head) => {
359 if window_head.has_changed(&(self.size.width(), self.size.height())) {
360 window_head.recreate_surface(window, instance)?;
361 }
362 }
363 Head::Headless(_) => {}
364 }
365 Ok(())
366 }
367
368 pub fn head(&self) -> &Head {
369 &self.head
370 }
371
372 pub fn head_mut(&mut self) -> &mut Head {
373 &mut self.head
374 }
375
376 pub fn is_multisampling_supported(&self, msaa: Msaa) -> bool {
377 match &self.head {
378 Head::Headed(headed) => {
379 let max_sample_count = {
380 let flags = headed.texture_format_features.flags;
381 if flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X8) {
382 8
383 } else if flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X4) {
384 4
385 } else if flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X2) {
386 2
387 } else {
388 1
389 }
390 };
391 let is_supported = msaa.samples <= max_sample_count;
392 if !is_supported {
393 log::debug!("Multisampling is not supported on surface");
394 }
395 is_supported
396 }
397 Head::Headless(_) => false, }
399 }
400}
401
402impl HasChanged for WindowHead {
403 type Criteria = (u32, u32);
405
406 fn has_changed(&self, criteria: &Self::Criteria) -> bool {
407 self.size.width() != criteria.0 || self.size.height() != criteria.1
408 }
409}