1use crate::core::Color;
3use crate::graphics::color;
4use crate::graphics::compositor;
5use crate::graphics::error;
6use crate::graphics::{self, Shell, Viewport};
7use crate::settings::{self, Settings};
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<W: compositor::Window>(
51 settings: Settings,
52 compatible_window: Option<W>,
53 shell: Shell,
54 ) -> Result<Self, Error> {
55 let instance = wgpu::util::new_instance_with_webgpu_detection(&wgpu::InstanceDescriptor {
56 backends: settings.backends,
57 flags: if cfg!(feature = "strict-assertions") {
58 wgpu::InstanceFlags::debugging()
59 } else {
60 wgpu::InstanceFlags::empty()
61 },
62 ..Default::default()
63 })
64 .await;
65
66 log::info!("{settings:#?}");
67
68 #[cfg(not(target_arch = "wasm32"))]
69 if log::max_level() >= log::LevelFilter::Info {
70 let available_adapters: Vec<_> = instance
71 .enumerate_adapters(settings.backends)
72 .await
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 =
81 compatible_window.and_then(|window| instance.create_surface(window).ok());
82
83 let adapter_options = wgpu::RequestAdapterOptions {
84 power_preference: wgpu::PowerPreference::from_env()
85 .unwrap_or(wgpu::PowerPreference::HighPerformance),
86 compatible_surface: compatible_surface.as_ref(),
87 force_fallback_adapter: false,
88 };
89
90 let adapter = instance
91 .request_adapter(&adapter_options)
92 .await
93 .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?;
94
95 log::info!("Selected: {:#?}", adapter.get_info());
96
97 let (format, alpha_mode) = compatible_surface
98 .as_ref()
99 .and_then(|surface| {
100 let capabilities = surface.get_capabilities(&adapter);
101
102 let formats = capabilities.formats.iter().copied();
103
104 log::info!("Available formats: {formats:#?}");
105
106 const BLACKLIST: &[wgpu::TextureFormat] = &[
107 wgpu::TextureFormat::Rgb10a2Unorm,
108 wgpu::TextureFormat::Rgb10a2Uint,
109 ];
110
111 let mut formats = formats.filter(|format| {
112 format.required_features() == wgpu::Features::empty()
113 && !BLACKLIST.contains(format)
114 });
115
116 let format = if color::GAMMA_CORRECTION {
117 formats.find(wgpu::TextureFormat::is_srgb)
118 } else {
119 formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
120 };
121
122 let format = format.or_else(|| {
123 log::warn!("No format found!");
124
125 capabilities.formats.first().copied()
126 });
127
128 let alpha_modes = capabilities.alpha_modes;
129
130 log::info!("Available alpha modes: {alpha_modes:#?}");
131
132 let preferred_alpha =
133 if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
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!("Selected format: {format:?} with alpha mode: {alpha_mode:?}");
144
145 #[cfg(target_arch = "wasm32")]
146 let limits = [wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())];
147
148 #[cfg(not(target_arch = "wasm32"))]
149 let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
150
151 let limits = limits.into_iter().map(|limits| wgpu::Limits {
152 max_bind_groups: 2,
153 max_non_sampler_bindings: 2048,
154 ..limits
155 });
156
157 let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) {
159 wgpu::Features::SHADER_F16
160 } else {
161 wgpu::Features::empty()
162 };
163
164 let mut errors = Vec::new();
165
166 for required_limits in limits {
167 let result = adapter
168 .request_device(&wgpu::DeviceDescriptor {
169 label: Some("iced_wgpu::window::compositor device descriptor"),
170 required_features,
171 required_limits: required_limits.clone(),
172 memory_hints: wgpu::MemoryHints::MemoryUsage,
173 trace: wgpu::Trace::Off,
174 experimental_features: wgpu::ExperimentalFeatures::disabled(),
175 })
176 .await;
177
178 match result {
179 Ok((device, queue)) => {
180 let engine = Engine::new(
181 &adapter,
182 device,
183 queue,
184 format,
185 settings.antialiasing,
186 shell,
187 );
188
189 return Ok(Compositor {
190 instance,
191 adapter,
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 shell: Shell,
213) -> Result<Compositor, Error> {
214 Compositor::request(settings, Some(compatible_window), shell).await
215}
216
217pub fn present(
219 renderer: &mut Renderer,
220 surface: &mut wgpu::Surface<'static>,
221 viewport: &Viewport,
222 background_color: Color,
223 on_pre_present: impl FnOnce(),
224) -> Result<(), compositor::SurfaceError> {
225 match surface.get_current_texture() {
226 Ok(frame) => {
227 let view = &frame
228 .texture
229 .create_view(&wgpu::TextureViewDescriptor::default());
230
231 let _submission = renderer.present(
232 Some(background_color),
233 frame.texture.format(),
234 view,
235 viewport,
236 );
237
238 on_pre_present();
240 frame.present();
241
242 Ok(())
243 }
244 Err(error) => match error {
245 wgpu::SurfaceError::Timeout => Err(compositor::SurfaceError::Timeout),
246 wgpu::SurfaceError::Outdated => Err(compositor::SurfaceError::Outdated),
247 wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
248 wgpu::SurfaceError::OutOfMemory => Err(compositor::SurfaceError::OutOfMemory),
249 wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
250 },
251 }
252}
253
254impl graphics::Compositor for Compositor {
255 type Renderer = Renderer;
256 type Surface = wgpu::Surface<'static>;
257
258 async fn with_backend(
259 settings: graphics::Settings,
260 _display: impl compositor::Display,
261 compatible_window: impl compositor::Window,
262 shell: Shell,
263 backend: Option<&str>,
264 ) -> Result<Self, graphics::Error> {
265 match backend {
266 None | Some("wgpu") => {
267 let mut settings = Settings::from(settings);
268
269 if let Some(backends) = wgpu::Backends::from_env() {
270 settings.backends = backends;
271 }
272
273 if let Some(present_mode) = settings::present_mode_from_env() {
274 settings.present_mode = present_mode;
275 }
276
277 Ok(new(settings, compatible_window, shell).await?)
278 }
279 Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
280 backend: "wgpu",
281 reason: error::Reason::DidNotMatch {
282 preferred_backend: backend.to_owned(),
283 },
284 }),
285 }
286 }
287
288 fn create_renderer(&self) -> Self::Renderer {
289 Renderer::new(
290 self.engine.clone(),
291 self.settings.default_font,
292 self.settings.default_text_size,
293 )
294 }
295
296 fn create_surface<W: compositor::Window>(
297 &mut self,
298 window: W,
299 width: u32,
300 height: u32,
301 ) -> Self::Surface {
302 let mut surface = self
303 .instance
304 .create_surface(window)
305 .expect("Create surface");
306
307 if width > 0 && height > 0 {
308 self.configure_surface(&mut surface, width, height);
309 }
310
311 surface
312 }
313
314 fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) {
315 surface.configure(
316 &self.engine.device,
317 &wgpu::SurfaceConfiguration {
318 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
319 format: self.format,
320 present_mode: self.settings.present_mode,
321 width,
322 height,
323 alpha_mode: self.alpha_mode,
324 view_formats: vec![],
325 desired_maximum_frame_latency: 1,
326 },
327 );
328 }
329
330 fn information(&self) -> compositor::Information {
331 let information = self.adapter.get_info();
332
333 compositor::Information {
334 adapter: information.name,
335 backend: format!("{:?}", information.backend),
336 }
337 }
338
339 fn present(
340 &mut self,
341 renderer: &mut Self::Renderer,
342 surface: &mut Self::Surface,
343 viewport: &Viewport,
344 background_color: Color,
345 on_pre_present: impl FnOnce(),
346 ) -> Result<(), compositor::SurfaceError> {
347 present(
348 renderer,
349 surface,
350 viewport,
351 background_color,
352 on_pre_present,
353 )
354 }
355
356 fn screenshot(
357 &mut self,
358 renderer: &mut Self::Renderer,
359 viewport: &Viewport,
360 background_color: Color,
361 ) -> Vec<u8> {
362 renderer.screenshot(viewport, background_color)
363 }
364}