1use crate::core::Color;
3use crate::core::renderer;
4use crate::graphics::color;
5use crate::graphics::compositor;
6use crate::graphics::error;
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 graphics::Error {
38 fn from(error: Error) -> Self {
39 Self::GraphicsAdapterNotFound {
40 backend: "wgpu",
41 reason: error::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 adapter_options = wgpu::RequestAdapterOptions {
86 power_preference: wgpu::PowerPreference::from_env()
87 .unwrap_or(wgpu::PowerPreference::HighPerformance),
88 compatible_surface: compatible_surface.as_ref(),
89 force_fallback_adapter: false,
90 };
91
92 let adapter = instance
93 .request_adapter(&adapter_options)
94 .await
95 .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?;
96
97 log::info!("Selected: {:#?}", adapter.get_info());
98
99 let (format, alpha_mode) = compatible_surface
100 .as_ref()
101 .and_then(|surface| {
102 let capabilities = surface.get_capabilities(&adapter);
103
104 let formats = capabilities.formats.iter().copied();
105
106 log::info!("Available formats: {formats:#?}");
107
108 const BLACKLIST: &[wgpu::TextureFormat] = &[
109 wgpu::TextureFormat::Rgb10a2Unorm,
110 wgpu::TextureFormat::Rgb10a2Uint,
111 ];
112
113 let mut formats = formats.filter(|format| {
114 format.required_features() == wgpu::Features::empty()
115 && !BLACKLIST.contains(format)
116 });
117
118 let format = if color::GAMMA_CORRECTION {
119 formats.find(wgpu::TextureFormat::is_srgb)
120 } else {
121 formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
122 };
123
124 let format = format.or_else(|| {
125 log::warn!("No format found!");
126
127 capabilities.formats.first().copied()
128 });
129
130 let alpha_modes = capabilities.alpha_modes;
131
132 log::info!("Available alpha modes: {alpha_modes:#?}");
133
134 let preferred_alpha =
135 if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
136 wgpu::CompositeAlphaMode::PreMultiplied
137 } else {
138 wgpu::CompositeAlphaMode::Auto
139 };
140
141 format.zip(Some(preferred_alpha))
142 })
143 .ok_or(Error::IncompatibleSurface)?;
144
145 log::info!("Selected format: {format:?} with alpha mode: {alpha_mode:?}");
146
147 #[cfg(target_arch = "wasm32")]
148 let limits = [wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())];
149
150 #[cfg(not(target_arch = "wasm32"))]
151 let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
152
153 let limits = limits.into_iter().map(|limits| wgpu::Limits {
154 max_bind_groups: 2,
155 max_non_sampler_bindings: 2048,
156 ..limits
157 });
158
159 let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) {
161 wgpu::Features::SHADER_F16
162 } else {
163 wgpu::Features::empty()
164 };
165
166 let mut errors = Vec::new();
167
168 for required_limits in limits {
169 let result = adapter
170 .request_device(&wgpu::DeviceDescriptor {
171 label: Some("iced_wgpu::window::compositor device descriptor"),
172 required_features,
173 required_limits: required_limits.clone(),
174 memory_hints: wgpu::MemoryHints::MemoryUsage,
175 trace: wgpu::Trace::Off,
176 experimental_features: wgpu::ExperimentalFeatures::disabled(),
177 })
178 .await;
179
180 match result {
181 Ok((device, queue)) => {
182 let engine = Engine::new(
183 &adapter,
184 device,
185 queue,
186 format,
187 settings.antialiasing,
188 shell,
189 );
190
191 return Ok(Compositor {
192 instance,
193 adapter,
194 format,
195 alpha_mode,
196 engine,
197 settings,
198 });
199 }
200 Err(error) => {
201 errors.push((required_limits, error));
202 }
203 }
204 }
205
206 Err(Error::RequestDeviceFailed(errors))
207 }
208}
209
210pub async fn new(
212 settings: Settings,
213 display: impl compositor::Display,
214 compatible_window: impl compositor::Window,
215 shell: Shell,
216) -> Result<Compositor, Error> {
217 Compositor::request(settings, display, compatible_window, shell).await
218}
219
220pub fn present(
222 renderer: &mut Renderer,
223 surface: &mut wgpu::Surface<'static>,
224 viewport: &Viewport,
225 background_color: Color,
226 on_pre_present: impl FnOnce(),
227) -> Result<(), compositor::SurfaceError> {
228 match surface.get_current_texture() {
229 wgpu::CurrentSurfaceTexture::Success(frame) => {
230 let view = &frame
231 .texture
232 .create_view(&wgpu::TextureViewDescriptor::default());
233
234 let _submission = renderer.present(
235 Some(background_color),
236 frame.texture.format(),
237 view,
238 viewport,
239 );
240
241 on_pre_present();
243 frame.present();
244
245 Ok(())
246 }
247 wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => {
248 Err(compositor::SurfaceError::Outdated)
249 }
250 wgpu::CurrentSurfaceTexture::Timeout => Err(compositor::SurfaceError::Timeout),
251 wgpu::CurrentSurfaceTexture::Occluded => Err(compositor::SurfaceError::Occluded),
252 wgpu::CurrentSurfaceTexture::Lost => Err(compositor::SurfaceError::Lost),
253 wgpu::CurrentSurfaceTexture::Validation => Err(compositor::SurfaceError::Other),
254 }
255}
256
257impl graphics::Compositor for Compositor {
258 type Renderer = Renderer;
259 type Surface = wgpu::Surface<'static>;
260
261 async fn with_backend(
262 settings: compositor::Settings,
263 display: impl compositor::Display,
264 compatible_window: impl compositor::Window,
265 shell: Shell,
266 backend: Option<&str>,
267 ) -> Result<Self, graphics::Error> {
268 match backend {
269 None | Some("wgpu") => {
270 let mut settings = Settings::from(settings);
271
272 if let Some(backends) = wgpu::Backends::from_env() {
273 settings.backends = backends;
274 }
275
276 if let Some(present_mode) = present_mode_from_env() {
277 settings.present_mode = present_mode;
278 }
279
280 Ok(new(settings, display, compatible_window, shell).await?)
281 }
282 Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
283 backend: "wgpu",
284 reason: error::Reason::DidNotMatch {
285 preferred_backend: backend.to_owned(),
286 },
287 }),
288 }
289 }
290
291 fn create_renderer(&self, settings: renderer::Settings) -> Self::Renderer {
292 Renderer::new(self.engine.clone(), settings)
293 }
294
295 fn create_surface(
296 &mut self,
297 window: impl compositor::Window,
298 width: u32,
299 height: u32,
300 ) -> Self::Surface {
301 let mut surface = self
302 .instance
303 .create_surface(wgpu::SurfaceTarget::Window(Box::new(window)))
304 .expect("Create surface");
305
306 if width > 0 && height > 0 {
307 self.configure_surface(&mut surface, width, height);
308 }
309
310 surface
311 }
312
313 fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) {
314 surface.configure(
315 &self.engine.device,
316 &wgpu::SurfaceConfiguration {
317 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
318 format: self.format,
319 present_mode: self.settings.present_mode,
320 width,
321 height,
322 alpha_mode: self.alpha_mode,
323 view_formats: vec![],
324 desired_maximum_frame_latency: 1,
325 },
326 );
327 }
328
329 fn information(&self) -> compositor::Information {
330 let information = self.adapter.get_info();
331
332 compositor::Information {
333 adapter: information.name,
334 backend: format!("{:?}", information.backend),
335 }
336 }
337
338 fn present(
339 &mut self,
340 renderer: &mut Self::Renderer,
341 surface: &mut Self::Surface,
342 viewport: &Viewport,
343 background_color: Color,
344 on_pre_present: impl FnOnce(),
345 ) -> Result<(), compositor::SurfaceError> {
346 present(
347 renderer,
348 surface,
349 viewport,
350 background_color,
351 on_pre_present,
352 )
353 }
354
355 fn screenshot(
356 &mut self,
357 renderer: &mut Self::Renderer,
358 viewport: &Viewport,
359 background_color: Color,
360 ) -> Vec<u8> {
361 renderer.screenshot(viewport, background_color)
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq)]
367pub struct Settings {
368 pub present_mode: wgpu::PresentMode,
372
373 pub backends: wgpu::Backends,
375
376 pub antialiasing: Option<Antialiasing>,
380}
381
382impl Default for Settings {
383 fn default() -> Settings {
384 Settings {
385 present_mode: wgpu::PresentMode::AutoVsync,
386 backends: wgpu::Backends::all(),
387 antialiasing: None,
388 }
389 }
390}
391
392impl From<compositor::Settings> for Settings {
393 fn from(settings: compositor::Settings) -> Self {
394 Self {
395 present_mode: if settings.vsync {
396 wgpu::PresentMode::AutoVsync
397 } else {
398 wgpu::PresentMode::AutoNoVsync
399 },
400 antialiasing: settings.antialiasing,
401 ..Settings::default()
402 }
403 }
404}
405
406pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
419 let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
420
421 match present_mode.to_lowercase().as_str() {
422 "vsync" => Some(wgpu::PresentMode::AutoVsync),
423 "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
424 "immediate" => Some(wgpu::PresentMode::Immediate),
425 "fifo" => Some(wgpu::PresentMode::Fifo),
426 "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
427 "mailbox" => Some(wgpu::PresentMode::Mailbox),
428 _ => None,
429 }
430}