1use crate::core::{Color, Size};
3use crate::graphics::color;
4use crate::graphics::compositor;
5use crate::graphics::error;
6use crate::graphics::{self, Viewport};
7use crate::settings::{self, Settings};
8use crate::{Engine, Renderer};
9
10#[allow(missing_debug_implementations)]
12pub struct Compositor {
13 instance: wgpu::Instance,
14 adapter: wgpu::Adapter,
15 device: wgpu::Device,
16 queue: wgpu::Queue,
17 format: wgpu::TextureFormat,
18 alpha_mode: wgpu::CompositeAlphaMode,
19 engine: Engine,
20 settings: Settings,
21}
22
23#[derive(Debug, Clone, thiserror::Error)]
25pub enum Error {
26 #[error("the surface creation failed: {0}")]
28 SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
29 #[error("the surface is not compatible")]
31 IncompatibleSurface,
32 #[error("no adapter was found for the options requested: {0:?}")]
34 NoAdapterFound(String),
35 #[error("no device request succeeded: {0:?}")]
37 RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
38}
39
40impl From<Error> for graphics::Error {
41 fn from(error: Error) -> Self {
42 Self::GraphicsAdapterNotFound {
43 backend: "wgpu",
44 reason: error::Reason::RequestFailed(error.to_string()),
45 }
46 }
47}
48
49impl Compositor {
50 pub async fn request<W: compositor::Window>(
54 settings: Settings,
55 compatible_window: Option<W>,
56 ) -> Result<Self, Error> {
57 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
58 backends: settings.backends,
59 flags: if cfg!(feature = "strict-assertions") {
60 wgpu::InstanceFlags::debugging()
61 } else {
62 wgpu::InstanceFlags::empty()
63 },
64 ..Default::default()
65 });
66
67 log::info!("{settings:#?}");
68
69 #[cfg(not(target_arch = "wasm32"))]
70 if log::max_level() >= log::LevelFilter::Info {
71 let available_adapters: Vec<_> = instance
72 .enumerate_adapters(settings.backends)
73 .iter()
74 .map(wgpu::Adapter::get_info)
75 .collect();
76 log::info!("Available adapters: {available_adapters:#?}");
77 }
78
79 #[allow(unsafe_code)]
80 let compatible_surface = compatible_window
81 .and_then(|window| instance.create_surface(window).ok());
82
83 let adapter_options = wgpu::RequestAdapterOptions {
84 power_preference: wgpu::PowerPreference::from_env().unwrap_or(
85 if settings.antialiasing.is_none() {
86 wgpu::PowerPreference::LowPower
87 } else {
88 wgpu::PowerPreference::HighPerformance
89 },
90 ),
91 compatible_surface: compatible_surface.as_ref(),
92 force_fallback_adapter: false,
93 };
94
95 let adapter = instance
96 .request_adapter(&adapter_options)
97 .await
98 .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
99
100 log::info!("Selected: {:#?}", adapter.get_info());
101
102 let (format, alpha_mode) = compatible_surface
103 .as_ref()
104 .and_then(|surface| {
105 let capabilities = surface.get_capabilities(&adapter);
106
107 let mut formats = capabilities.formats.iter().copied();
108
109 log::info!("Available formats: {formats:#?}");
110
111 let format = if color::GAMMA_CORRECTION {
112 formats.find(wgpu::TextureFormat::is_srgb)
113 } else {
114 formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
115 };
116
117 let format = format.or_else(|| {
118 log::warn!("No format found!");
119
120 capabilities.formats.first().copied()
121 });
122
123 let alpha_modes = capabilities.alpha_modes;
124
125 log::info!("Available alpha modes: {alpha_modes:#?}");
126
127 let preferred_alpha = if alpha_modes
128 .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
129 {
130 wgpu::CompositeAlphaMode::PostMultiplied
131 } else if alpha_modes
132 .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
133 {
134 wgpu::CompositeAlphaMode::PreMultiplied
135 } else {
136 wgpu::CompositeAlphaMode::Auto
137 };
138
139 format.zip(Some(preferred_alpha))
140 })
141 .ok_or(Error::IncompatibleSurface)?;
142
143 log::info!(
144 "Selected format: {format:?} with alpha mode: {alpha_mode:?}"
145 );
146
147 #[cfg(target_arch = "wasm32")]
148 let limits = [wgpu::Limits::downlevel_webgl2_defaults()
149 .using_resolution(adapter.limits())];
150
151 #[cfg(not(target_arch = "wasm32"))]
152 let limits =
153 [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
154
155 let limits = limits.into_iter().map(|limits| wgpu::Limits {
156 max_bind_groups: 2,
157 ..limits
158 });
159
160 let mut errors = Vec::new();
161
162 for required_limits in limits {
163 let result = adapter
164 .request_device(
165 &wgpu::DeviceDescriptor {
166 label: Some(
167 "iced_wgpu::window::compositor device descriptor",
168 ),
169 required_features: wgpu::Features::empty(),
170 required_limits: required_limits.clone(),
171 memory_hints: wgpu::MemoryHints::MemoryUsage,
172 },
173 None,
174 )
175 .await;
176
177 match result {
178 Ok((device, queue)) => {
179 let engine = Engine::new(
180 &adapter,
181 &device,
182 &queue,
183 format,
184 settings.antialiasing,
185 );
186
187 return Ok(Compositor {
188 instance,
189 adapter,
190 device,
191 queue,
192 format,
193 alpha_mode,
194 engine,
195 settings,
196 });
197 }
198 Err(error) => {
199 errors.push((required_limits, error));
200 }
201 }
202 }
203
204 Err(Error::RequestDeviceFailed(errors))
205 }
206}
207
208pub async fn new<W: compositor::Window>(
210 settings: Settings,
211 compatible_window: W,
212) -> Result<Compositor, Error> {
213 Compositor::request(settings, Some(compatible_window)).await
214}
215
216pub fn present<T: AsRef<str>>(
218 compositor: &mut Compositor,
219 renderer: &mut Renderer,
220 surface: &mut wgpu::Surface<'static>,
221 viewport: &Viewport,
222 background_color: Color,
223 overlay: &[T],
224) -> Result<(), compositor::SurfaceError> {
225 match surface.get_current_texture() {
226 Ok(frame) => {
227 let mut encoder = compositor.device.create_command_encoder(
228 &wgpu::CommandEncoderDescriptor {
229 label: Some("iced_wgpu encoder"),
230 },
231 );
232
233 let view = &frame
234 .texture
235 .create_view(&wgpu::TextureViewDescriptor::default());
236
237 renderer.present(
238 &mut compositor.engine,
239 &compositor.device,
240 &compositor.queue,
241 &mut encoder,
242 Some(background_color),
243 frame.texture.format(),
244 view,
245 viewport,
246 overlay,
247 );
248
249 let _ = compositor.engine.submit(&compositor.queue, encoder);
250
251 frame.present();
253
254 Ok(())
255 }
256 Err(error) => match error {
257 wgpu::SurfaceError::Timeout => {
258 Err(compositor::SurfaceError::Timeout)
259 }
260 wgpu::SurfaceError::Outdated => {
261 Err(compositor::SurfaceError::Outdated)
262 }
263 wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
264 wgpu::SurfaceError::OutOfMemory => {
265 Err(compositor::SurfaceError::OutOfMemory)
266 }
267 wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
268 },
269 }
270}
271
272impl graphics::Compositor for Compositor {
273 type Renderer = Renderer;
274 type Surface = wgpu::Surface<'static>;
275
276 async fn with_backend<W: compositor::Window>(
277 settings: graphics::Settings,
278 compatible_window: W,
279 backend: Option<&str>,
280 ) -> Result<Self, graphics::Error> {
281 match backend {
282 None | Some("wgpu") => {
283 let mut settings = Settings::from(settings);
284
285 if let Some(backends) = wgpu::Backends::from_env() {
286 settings.backends = backends;
287 }
288
289 if let Some(present_mode) = settings::present_mode_from_env() {
290 settings.present_mode = present_mode;
291 }
292
293 Ok(new(settings, compatible_window).await?)
294 }
295 Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
296 backend: "wgpu",
297 reason: error::Reason::DidNotMatch {
298 preferred_backend: backend.to_owned(),
299 },
300 }),
301 }
302 }
303
304 fn create_renderer(&self) -> Self::Renderer {
305 Renderer::new(
306 &self.device,
307 &self.engine,
308 self.settings.default_font,
309 self.settings.default_text_size,
310 )
311 }
312
313 fn create_surface<W: compositor::Window>(
314 &mut self,
315 window: W,
316 width: u32,
317 height: u32,
318 ) -> Self::Surface {
319 let mut surface = self
320 .instance
321 .create_surface(window)
322 .expect("Create surface");
323
324 if width > 0 && height > 0 {
325 self.configure_surface(&mut surface, width, height);
326 }
327
328 surface
329 }
330
331 fn configure_surface(
332 &mut self,
333 surface: &mut Self::Surface,
334 width: u32,
335 height: u32,
336 ) {
337 surface.configure(
338 &self.device,
339 &wgpu::SurfaceConfiguration {
340 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
341 format: self.format,
342 present_mode: self.settings.present_mode,
343 width,
344 height,
345 alpha_mode: self.alpha_mode,
346 view_formats: vec![],
347 desired_maximum_frame_latency: 1,
348 },
349 );
350 }
351
352 fn fetch_information(&self) -> compositor::Information {
353 let information = self.adapter.get_info();
354
355 compositor::Information {
356 adapter: information.name,
357 backend: format!("{:?}", information.backend),
358 }
359 }
360
361 fn present<T: AsRef<str>>(
362 &mut self,
363 renderer: &mut Self::Renderer,
364 surface: &mut Self::Surface,
365 viewport: &Viewport,
366 background_color: Color,
367 overlay: &[T],
368 ) -> Result<(), compositor::SurfaceError> {
369 present(self, renderer, surface, viewport, background_color, overlay)
370 }
371
372 fn screenshot<T: AsRef<str>>(
373 &mut self,
374 renderer: &mut Self::Renderer,
375 viewport: &Viewport,
376 background_color: Color,
377 overlay: &[T],
378 ) -> Vec<u8> {
379 screenshot(self, renderer, viewport, background_color, overlay)
380 }
381}
382
383pub fn screenshot<T: AsRef<str>>(
387 compositor: &mut Compositor,
388 renderer: &mut Renderer,
389 viewport: &Viewport,
390 background_color: Color,
391 overlay: &[T],
392) -> Vec<u8> {
393 let dimensions = BufferDimensions::new(viewport.physical_size());
394
395 let texture_extent = wgpu::Extent3d {
396 width: dimensions.width,
397 height: dimensions.height,
398 depth_or_array_layers: 1,
399 };
400
401 let texture = compositor.device.create_texture(&wgpu::TextureDescriptor {
402 label: Some("iced_wgpu.offscreen.source_texture"),
403 size: texture_extent,
404 mip_level_count: 1,
405 sample_count: 1,
406 dimension: wgpu::TextureDimension::D2,
407 format: compositor.format,
408 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
409 | wgpu::TextureUsages::COPY_SRC
410 | wgpu::TextureUsages::TEXTURE_BINDING,
411 view_formats: &[],
412 });
413
414 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
415
416 let mut encoder = compositor.device.create_command_encoder(
417 &wgpu::CommandEncoderDescriptor {
418 label: Some("iced_wgpu.offscreen.encoder"),
419 },
420 );
421
422 renderer.present(
423 &mut compositor.engine,
424 &compositor.device,
425 &compositor.queue,
426 &mut encoder,
427 Some(background_color),
428 texture.format(),
429 &view,
430 viewport,
431 overlay,
432 );
433
434 let texture = crate::color::convert(
435 &compositor.device,
436 &mut encoder,
437 texture,
438 if color::GAMMA_CORRECTION {
439 wgpu::TextureFormat::Rgba8UnormSrgb
440 } else {
441 wgpu::TextureFormat::Rgba8Unorm
442 },
443 );
444
445 let output_buffer =
446 compositor.device.create_buffer(&wgpu::BufferDescriptor {
447 label: Some("iced_wgpu.offscreen.output_texture_buffer"),
448 size: (dimensions.padded_bytes_per_row * dimensions.height as usize)
449 as u64,
450 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
451 mapped_at_creation: false,
452 });
453
454 encoder.copy_texture_to_buffer(
455 texture.as_image_copy(),
456 wgpu::TexelCopyBufferInfo {
457 buffer: &output_buffer,
458 layout: wgpu::TexelCopyBufferLayout {
459 offset: 0,
460 bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
461 rows_per_image: None,
462 },
463 },
464 texture_extent,
465 );
466
467 let index = compositor.engine.submit(&compositor.queue, encoder);
468
469 let slice = output_buffer.slice(..);
470 slice.map_async(wgpu::MapMode::Read, |_| {});
471
472 let _ = compositor
473 .device
474 .poll(wgpu::Maintain::WaitForSubmissionIndex(index));
475
476 let mapped_buffer = slice.get_mapped_range();
477
478 mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
479 vec![],
480 |mut acc, row| {
481 acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
482 acc
483 },
484 )
485}
486
487#[derive(Clone, Copy, Debug)]
488struct BufferDimensions {
489 width: u32,
490 height: u32,
491 unpadded_bytes_per_row: usize,
492 padded_bytes_per_row: usize,
493}
494
495impl BufferDimensions {
496 fn new(size: Size<u32>) -> Self {
497 let unpadded_bytes_per_row = size.width as usize * 4; let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; let padded_bytes_per_row_padding =
500 (alignment - unpadded_bytes_per_row % alignment) % alignment;
501 let padded_bytes_per_row =
502 unpadded_bytes_per_row + padded_bytes_per_row_padding;
503
504 Self {
505 width: size.width,
506 height: size.height,
507 unpadded_bytes_per_row,
508 padded_bytes_per_row,
509 }
510 }
511}