1use crate::core::Color;
3use crate::core::backend;
4use crate::core::renderer;
5use crate::graphics::color;
6use crate::graphics::compositor;
7use crate::graphics::{self, Antialiasing, Shell, Viewport};
8use crate::{Engine, Renderer};
9
10pub struct Compositor {
12 instance: wgpu::Instance,
13 adapter: wgpu::Adapter,
14 format: wgpu::TextureFormat,
15 alpha_mode: wgpu::CompositeAlphaMode,
16 engine: Engine,
17 settings: Settings,
18}
19
20#[derive(Debug, Clone, thiserror::Error)]
22pub enum Error {
23 #[error("the surface creation failed: {0}")]
25 SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
26 #[error("the surface is not compatible")]
28 IncompatibleSurface,
29 #[error("no adapter was found for the options requested: {0:?}")]
31 NoAdapterFound(String),
32 #[error("no device request succeeded: {0:?}")]
34 RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
35}
36
37impl From<Error> for backend::Error {
38 fn from(error: Error) -> Self {
39 Self::GraphicsAdapterNotFound {
40 backend: "wgpu",
41 reason: backend::Reason::RequestFailed(error.to_string()),
42 }
43 }
44}
45
46impl Compositor {
47 pub async fn request(
51 settings: Settings,
52 display: impl compositor::Display,
53 compatible_window: impl compositor::Window,
54 shell: Shell,
55 ) -> Result<Self, Error> {
56 let instance = wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor {
57 backends: settings.backends,
58 flags: if cfg!(feature = "strict-assertions") {
59 wgpu::InstanceFlags::debugging()
60 } else {
61 wgpu::InstanceFlags::empty()
62 },
63 ..wgpu::InstanceDescriptor::new_with_display_handle(Box::new(display))
64 })
65 .await;
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 .await
74 .iter()
75 .map(wgpu::Adapter::get_info)
76 .collect();
77 log::info!("Available adapters: {available_adapters:#?}");
78 }
79
80 #[allow(unsafe_code)]
81 let compatible_surface = instance
82 .create_surface(wgpu::SurfaceTarget::Window(Box::new(compatible_window)))
83 .ok();
84
85 let power_preference = match settings.power_preference {
86 backend::PowerPreference::None => wgpu::PowerPreference::None,
87 backend::PowerPreference::LowPower => wgpu::PowerPreference::LowPower,
88 backend::PowerPreference::HighPerformance => wgpu::PowerPreference::HighPerformance,
89 };
90
91 let adapter_options = wgpu::RequestAdapterOptions {
92 power_preference: wgpu::PowerPreference::from_env().unwrap_or(power_preference),
93 compatible_surface: compatible_surface.as_ref(),
94 force_fallback_adapter: false,
95 apply_limit_buckets: false,
96 };
97
98 let adapter = instance
99 .request_adapter(&adapter_options)
100 .await
101 .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?;
102
103 log::info!("Selected: {:#?}", adapter.get_info());
104
105 let (format, alpha_mode) = compatible_surface
106 .as_ref()
107 .and_then(|surface| {
108 let capabilities = surface.get_capabilities(&adapter);
109
110 let formats = capabilities.formats.iter().copied();
111
112 log::info!("Available formats: {formats:#?}");
113
114 const BLACKLIST: &[wgpu::TextureFormat] = &[
115 wgpu::TextureFormat::Rgb10a2Unorm,
116 wgpu::TextureFormat::Rgb10a2Uint,
117 wgpu::TextureFormat::Rgba16Float,
118 ];
119
120 let mut formats = formats.filter(|format| {
121 format.required_features() == wgpu::Features::empty()
122 && !BLACKLIST.contains(format)
123 });
124
125 let format = if color::GAMMA_CORRECTION {
126 formats.find(wgpu::TextureFormat::is_srgb)
127 } else {
128 formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
129 };
130
131 let format = format.or_else(|| {
132 log::warn!("No format found!");
133
134 capabilities.formats.first().copied()
135 });
136
137 let alpha_modes = capabilities.alpha_modes;
138
139 log::info!("Available alpha modes: {alpha_modes:#?}");
140
141 let preferred_alpha =
142 if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
143 wgpu::CompositeAlphaMode::PreMultiplied
144 } else {
145 wgpu::CompositeAlphaMode::Auto
146 };
147
148 format.zip(Some(preferred_alpha))
149 })
150 .ok_or(Error::IncompatibleSurface)?;
151
152 log::info!("Selected format: {format:?} with alpha mode: {alpha_mode:?}");
153
154 #[cfg(target_arch = "wasm32")]
155 let limits = [wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())];
156
157 #[cfg(not(target_arch = "wasm32"))]
158 let limits = [
159 wgpu::Limits::default().using_resolution(adapter.limits()),
160 wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()),
161 ];
162
163 let limits = limits.into_iter().map(|limits| wgpu::Limits {
164 max_non_sampler_bindings: 2048,
165 ..limits
166 });
167
168 let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) {
170 wgpu::Features::SHADER_F16
171 } else {
172 wgpu::Features::empty()
173 };
174
175 let mut errors = Vec::new();
176
177 for required_limits in limits {
178 let result = adapter
179 .request_device(&wgpu::DeviceDescriptor {
180 label: Some("iced_wgpu::window::compositor device descriptor"),
181 required_features,
182 required_limits: required_limits.clone(),
183 memory_hints: wgpu::MemoryHints::MemoryUsage,
184 trace: wgpu::Trace::Off,
185 experimental_features: wgpu::ExperimentalFeatures::disabled(),
186 })
187 .await;
188
189 match result {
190 Ok((device, queue)) => {
191 let engine = Engine::new(
192 &adapter,
193 device,
194 queue,
195 format,
196 settings.antialiasing,
197 shell,
198 );
199
200 return Ok(Compositor {
201 instance,
202 adapter,
203 format,
204 alpha_mode,
205 engine,
206 settings,
207 });
208 }
209 Err(error) => {
210 errors.push((required_limits, error));
211 }
212 }
213 }
214
215 Err(Error::RequestDeviceFailed(errors))
216 }
217}
218
219pub async fn new(
221 settings: Settings,
222 display: impl compositor::Display,
223 compatible_window: impl compositor::Window,
224 shell: Shell,
225) -> Result<Compositor, Error> {
226 Compositor::request(settings, display, compatible_window, shell).await
227}
228
229pub fn present(
231 renderer: &mut Renderer,
232 surface: &mut wgpu::Surface<'static>,
233 viewport: &Viewport,
234 background_color: Color,
235 on_pre_present: impl FnOnce(),
236) -> Result<(), compositor::SurfaceError> {
237 match surface.get_current_texture() {
238 wgpu::CurrentSurfaceTexture::Success(frame) => {
239 let view = &frame
240 .texture
241 .create_view(&wgpu::TextureViewDescriptor::default());
242
243 let _submission = renderer.present(
244 Some(background_color),
245 frame.texture.format(),
246 view,
247 viewport,
248 );
249
250 on_pre_present();
252 renderer.engine.queue.present(frame);
253
254 Ok(())
255 }
256 wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => {
257 Err(compositor::SurfaceError::Outdated)
258 }
259 wgpu::CurrentSurfaceTexture::Timeout => Err(compositor::SurfaceError::Timeout),
260 wgpu::CurrentSurfaceTexture::Occluded => Err(compositor::SurfaceError::Occluded),
261 wgpu::CurrentSurfaceTexture::Lost => Err(compositor::SurfaceError::Lost),
262 wgpu::CurrentSurfaceTexture::Validation => Err(compositor::SurfaceError::Other),
263 }
264}
265
266impl graphics::Compositor for Compositor {
267 type Renderer = Renderer;
268 type Surface = wgpu::Surface<'static>;
269
270 async fn new(
271 settings: backend::Settings,
272 display: impl compositor::Display,
273 compatible_window: impl compositor::Window,
274 shell: Shell,
275 ) -> Result<Self, backend::Error> {
276 if settings.backend.hardware().is_none() && !settings.backend.matches("wgpu") {
277 return Err(backend::Error::GraphicsAdapterNotFound {
278 backend: "wgpu",
279 reason: backend::Reason::DidNotMatch {
280 preferred_backend: settings.backend,
281 },
282 });
283 }
284
285 let mut settings = Settings::from(settings);
286
287 if let Some(backends) = wgpu::Backends::from_env() {
288 settings.backends = backends;
289 }
290
291 if let Some(present_mode) = present_mode_from_env() {
292 settings.present_mode = present_mode;
293 }
294
295 Ok(new(settings, display, compatible_window, shell).await?)
296 }
297
298 fn create_renderer(&self, settings: renderer::Settings) -> Self::Renderer {
299 Renderer::new(self.engine.clone(), settings)
300 }
301
302 fn create_surface(
303 &mut self,
304 window: impl compositor::Window,
305 width: u32,
306 height: u32,
307 ) -> Self::Surface {
308 let mut surface = self
309 .instance
310 .create_surface(wgpu::SurfaceTarget::Window(Box::new(window)))
311 .expect("Create surface");
312
313 if width > 0 && height > 0 {
314 self.configure_surface(&mut surface, width, height);
315 }
316
317 surface
318 }
319
320 fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) {
321 surface.configure(
322 &self.engine.device,
323 &wgpu::SurfaceConfiguration {
324 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
325 format: self.format,
326 present_mode: self.settings.present_mode,
327 width,
328 height,
329 alpha_mode: self.alpha_mode,
330 view_formats: vec![],
331 desired_maximum_frame_latency: 1,
332 color_space: wgpu::SurfaceColorSpace::Auto,
333 },
334 );
335 }
336
337 fn information(&self) -> compositor::Information {
338 let information = self.adapter.get_info();
339
340 compositor::Information {
341 adapter: information.name,
342 backend: format!("{:?}", information.backend),
343 }
344 }
345
346 fn present(
347 &mut self,
348 renderer: &mut Self::Renderer,
349 surface: &mut Self::Surface,
350 viewport: &Viewport,
351 background_color: Color,
352 on_pre_present: impl FnOnce(),
353 ) -> Result<(), compositor::SurfaceError> {
354 present(
355 renderer,
356 surface,
357 viewport,
358 background_color,
359 on_pre_present,
360 )
361 }
362
363 fn screenshot(
364 &mut self,
365 renderer: &mut Self::Renderer,
366 viewport: &Viewport,
367 background_color: Color,
368 ) -> Vec<u8> {
369 renderer.screenshot(viewport, background_color)
370 }
371}
372
373#[derive(Debug, Clone, Copy, PartialEq)]
375pub struct Settings {
376 pub present_mode: wgpu::PresentMode,
380
381 pub backends: wgpu::Backends,
383
384 pub power_preference: backend::PowerPreference,
388
389 pub antialiasing: Option<Antialiasing>,
393}
394
395impl Default for Settings {
396 fn default() -> Settings {
397 Settings {
398 present_mode: wgpu::PresentMode::AutoVsync,
399 backends: wgpu::Backends::all(),
400 power_preference: backend::PowerPreference::None,
401 antialiasing: None,
402 }
403 }
404}
405
406impl From<backend::Settings> for Settings {
407 fn from(settings: backend::Settings) -> Self {
408 let backends = settings
409 .backend
410 .hardware()
411 .map(|api| match api {
412 backend::Api::Best => wgpu::Backends::all(),
413 backend::Api::Vulkan => wgpu::Backends::VULKAN,
414 backend::Api::Metal => wgpu::Backends::METAL,
415 backend::Api::DirectX12 => wgpu::Backends::DX12,
416 backend::Api::OpenGL => wgpu::Backends::GL,
417 backend::Api::WebGPU => wgpu::Backends::BROWSER_WEBGPU,
418 })
419 .unwrap_or_else(wgpu::Backends::all);
420
421 Self {
422 present_mode: if settings.vsync {
423 wgpu::PresentMode::AutoVsync
424 } else {
425 wgpu::PresentMode::AutoNoVsync
426 },
427 antialiasing: settings.antialiasing.then_some(Antialiasing::MSAAx4),
428 backends,
429 power_preference: settings.power_preference,
430 }
431 }
432}
433
434pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
447 let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
448
449 match present_mode.to_lowercase().as_str() {
450 "vsync" => Some(wgpu::PresentMode::AutoVsync),
451 "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
452 "immediate" => Some(wgpu::PresentMode::Immediate),
453 "fifo" => Some(wgpu::PresentMode::Fifo),
454 "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
455 "mailbox" => Some(wgpu::PresentMode::Mailbox),
456 _ => None,
457 }
458}