Skip to main content

app_surface/
lib.rs

1use wgpu::{Instance, Surface};
2
3mod touch;
4pub use touch::*;
5
6#[cfg(any(target_os = "ios", all(feature = "mac_catalyst", target_os = "macos")))]
7#[path = "ios.rs"]
8mod app_surface;
9
10#[cfg(target_os = "android")]
11#[path = "android.rs"]
12mod app_surface;
13
14#[cfg(all(target_arch = "wasm32", feature = "web_rwh"))]
15#[path = "web_rwh/mod.rs"]
16mod app_surface;
17
18#[cfg(all(
19    feature = "winit",
20    any(
21        all(not(feature = "mac_catalyst"), target_os = "macos"),
22        target_os = "windows",
23        target_os = "linux",
24        all(target_arch = "wasm32", not(feature = "web_rwh")),
25    )
26))]
27#[path = "app_surface_use_winit.rs"]
28mod app_surface;
29
30#[cfg(any(
31    all(
32        not(feature = "winit"),
33        any(
34            all(not(feature = "mac_catalyst"), target_os = "macos"),
35            target_os = "windows",
36            target_os = "linux",
37        ),
38    ),
39    all(
40        target_arch = "wasm32",
41        not(feature = "winit"),
42        not(feature = "web_rwh")
43    ),
44))]
45#[path = "unsupported.rs"]
46mod app_surface;
47#[cfg(not(any(
48    all(
49        not(feature = "winit"),
50        any(
51            all(not(feature = "mac_catalyst"), target_os = "macos"),
52            target_os = "windows",
53            target_os = "linux",
54        ),
55    ),
56    all(
57        target_arch = "wasm32",
58        not(feature = "winit"),
59        not(feature = "web_rwh")
60    ),
61)))]
62pub use app_surface::*;
63
64#[repr(C)]
65#[derive(Debug)]
66pub struct ViewSize {
67    pub width: u32,
68    pub height: u32,
69}
70
71pub(crate) fn normalize_view_size(size: (u32, u32)) -> (u32, u32) {
72    (size.0.max(1), size.1.max(1))
73}
74
75pub(crate) fn normalize_scale_factor(scale_factor: f32) -> f32 {
76    if scale_factor.is_finite() && scale_factor > 0.0 {
77        scale_factor
78    } else {
79        1.0
80    }
81}
82
83#[allow(dead_code)]
84pub(crate) fn physical_size_from_logical_size(
85    width: f32,
86    height: f32,
87    scale_factor: f32,
88) -> (u32, u32) {
89    let scale_factor = normalize_scale_factor(scale_factor);
90    let width = if width.is_finite() && width > 0.0 {
91        (width * scale_factor) as u32
92    } else {
93        0
94    };
95    let height = if height.is_finite() && height > 0.0 {
96        (height * scale_factor) as u32
97    } else {
98        0
99    };
100
101    normalize_view_size((width, height))
102}
103
104#[allow(dead_code)]
105pub(crate) fn logical_size_from_physical_size(
106    physical_size: (u32, u32),
107    scale_factor: f32,
108) -> (f32, f32) {
109    let physical_size = normalize_view_size(physical_size);
110    let scale_factor = normalize_scale_factor(scale_factor);
111
112    (
113        physical_size.0 as f32 / scale_factor,
114        physical_size.1 as f32 / scale_factor,
115    )
116}
117
118pub(crate) fn normalize_touch_point(
119    touch_point_x: f32,
120    touch_point_y: f32,
121    physical_size: (u32, u32),
122    scale_factor: f32,
123) -> (f32, f32) {
124    let physical_size = normalize_view_size(physical_size);
125    let scale_factor = normalize_scale_factor(scale_factor);
126
127    (
128        touch_point_x * scale_factor / physical_size.0 as f32,
129        touch_point_y * scale_factor / physical_size.1 as f32,
130    )
131}
132
133pub(crate) fn resize_surface_config(
134    config: &mut wgpu::SurfaceConfiguration,
135    size: (u32, u32),
136) -> bool {
137    let size = normalize_view_size(size);
138    if config.width == size.0 && config.height == size.1 {
139        return false;
140    }
141
142    config.width = size.0;
143    config.height = size.1;
144    true
145}
146
147#[cfg(target_arch = "wasm32")]
148use std::rc::Rc as SharedPtr;
149#[cfg(not(target_arch = "wasm32"))]
150use std::sync::Arc as SharedPtr;
151/// wgpu v24 开始,Instance、Adapter、Device 和 Queue 都是可 `Clone` 的
152#[derive(Clone)]
153pub struct IASDQContext {
154    pub instance: wgpu::Instance,
155    pub surface: SharedPtr<wgpu::Surface<'static>>,
156    pub config: wgpu::SurfaceConfiguration,
157    pub adapter: wgpu::Adapter,
158    pub device: wgpu::Device,
159    pub queue: wgpu::Queue,
160}
161
162impl IASDQContext {
163    pub fn update_config_format(&mut self, format: wgpu::TextureFormat) {
164        self.config.format = format;
165        if cfg!(feature = "webgl") {
166            // webgl 后端不支持 view_formats
167        } else if format == format.remove_srgb_suffix() {
168            self.config.view_formats = vec![format.add_srgb_suffix()];
169        } else {
170            self.config.view_formats = vec![format];
171        }
172        self.surface.configure(&self.device, &self.config);
173    }
174}
175
176#[cfg(not(any(
177    all(
178        not(feature = "winit"),
179        any(
180            all(not(feature = "mac_catalyst"), target_os = "macos"),
181            target_os = "windows",
182            target_os = "linux",
183        ),
184    ),
185    all(
186        target_arch = "wasm32",
187        not(feature = "winit"),
188        not(feature = "web_rwh")
189    ),
190)))]
191impl core::ops::Deref for AppSurface {
192    type Target = IASDQContext;
193    fn deref(&self) -> &Self::Target {
194        &self.ctx
195    }
196}
197
198pub trait SurfaceFrame {
199    fn view_size(&self) -> ViewSize;
200    // After App view's size or orientation changed, need to resize surface.
201    fn resize_surface(&mut self);
202    fn resize_surface_by_size(&mut self, size: (u32, u32));
203    fn pintch(&mut self, _touch: Touch, _scale: f32) {}
204    fn touch(&mut self, _touch: Touch) {}
205    fn normalize_touch_point(&self, _touch_point_x: f32, _touch_point_y: f32) -> (f32, f32) {
206        unimplemented!()
207    }
208    fn enter_frame(&mut self) {}
209    fn get_current_frame_view(
210        &self,
211        _view_format: Option<wgpu::TextureFormat>,
212    ) -> Option<(wgpu::SurfaceTexture, wgpu::TextureView)> {
213        unimplemented!()
214    }
215    fn create_current_frame_view(
216        &self,
217        device: &wgpu::Device,
218        surface: &wgpu::Surface,
219        config: &wgpu::SurfaceConfiguration,
220        view_format: Option<wgpu::TextureFormat>,
221    ) -> Option<(wgpu::SurfaceTexture, wgpu::TextureView)> {
222        let frame = match surface.get_current_texture() {
223            wgpu::CurrentSurfaceTexture::Success(frame)
224            | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
225            wgpu::CurrentSurfaceTexture::Timeout
226            | wgpu::CurrentSurfaceTexture::Outdated
227            | wgpu::CurrentSurfaceTexture::Lost => {
228                surface.configure(device, config);
229                match surface.get_current_texture() {
230                    wgpu::CurrentSurfaceTexture::Success(frame)
231                    | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
232                    _ => panic!("Failed to acquire next swap chain texture!"),
233                }
234            }
235            wgpu::CurrentSurfaceTexture::Occluded => return None,
236            wgpu::CurrentSurfaceTexture::Validation => panic!("Validation error acquiring texture"),
237        };
238        let view = frame.texture.create_view(&wgpu::TextureViewDescriptor {
239            label: Some("frame texture view"),
240            format: if view_format.is_none() {
241                // frame buffer's view format prefer to use sRGB.
242                Some(config.format.add_srgb_suffix())
243            } else {
244                view_format
245            },
246            ..Default::default()
247        });
248        Some((frame, view))
249    }
250}
251
252#[cfg(not(any(
253    all(
254        not(feature = "winit"),
255        any(
256            all(not(feature = "mac_catalyst"), target_os = "macos"),
257            target_os = "windows",
258            target_os = "linux",
259        ),
260    ),
261    all(
262        target_arch = "wasm32",
263        not(feature = "winit"),
264        not(feature = "web_rwh")
265    ),
266)))]
267impl SurfaceFrame for AppSurface {
268    fn view_size(&self) -> ViewSize {
269        let size = self.get_view_size();
270        ViewSize {
271            width: size.0,
272            height: size.1,
273        }
274    }
275
276    fn resize_surface(&mut self) {
277        let size = self.get_view_size();
278        if resize_surface_config(&mut self.ctx.config, size) {
279            self.surface.configure(&self.device, &self.config);
280        }
281    }
282
283    fn resize_surface_by_size(&mut self, size: (u32, u32)) {
284        if resize_surface_config(&mut self.ctx.config, size) {
285            self.surface.configure(&self.device, &self.config);
286        }
287    }
288
289    fn normalize_touch_point(&self, touch_point_x: f32, touch_point_y: f32) -> (f32, f32) {
290        normalize_touch_point(
291            touch_point_x,
292            touch_point_y,
293            self.get_view_size(),
294            self.scale_factor,
295        )
296    }
297
298    fn get_current_frame_view(
299        &self,
300        view_format: Option<wgpu::TextureFormat>,
301    ) -> Option<(wgpu::SurfaceTexture, wgpu::TextureView)> {
302        self.create_current_frame_view(&self.device, &self.surface, &self.config, view_format)
303    }
304}
305
306async fn create_iasdq_context(
307    instance: Instance,
308    surface: Surface<'static>,
309    physical_size: (u32, u32),
310) -> IASDQContext {
311    let physical_size = normalize_view_size(physical_size);
312    let (adapter, device, queue) = crate::request_device(&instance, &surface).await;
313
314    let caps = surface.get_capabilities(&adapter);
315    let prefered = caps.formats[0];
316
317    let format = if cfg!(all(target_arch = "wasm32", not(feature = "webgl"))) {
318        // Chrome WebGPU doesn't support sRGB:
319        // unsupported swap chain format "xxxx8unorm-srgb"
320        prefered.remove_srgb_suffix()
321    } else {
322        prefered
323    };
324    let view_formats = if cfg!(feature = "webgl") {
325        // panicked at 'Error in Surface::configure: Validation Error
326        // Caused by:
327        // Downlevel flags DownlevelFlags(SURFACE_VIEW_FORMATS) are required but not supported on the device.
328        vec![]
329    } else if cfg!(target_os = "android") {
330        // TODO:HarmonyOS 不支持 view_formats 格式
331        // format 的值与 view_formats 的值一致时,configure 内部会自动忽略 view_formats 的值
332        //
333        // Android 不支持 view_formats:
334        // Downlevel flags DownlevelFlags(SURFACE_VIEW_FORMATS) are required but not supported on the device.
335        // This is not an invalid use of WebGPU: the underlying API or device does not support enough features
336        // to be a fully compliant implementation. A subset of the features can still be used.
337        // If you are running this program on native and not in a browser and wish to work around this issue,
338        // call Adapter::downlevel_properties or Device::downlevel_properties to get a listing of the features the current platform supports.
339        vec![format]
340    } else if format.is_srgb() {
341        vec![format, format.remove_srgb_suffix()]
342    } else {
343        vec![format.add_srgb_suffix(), format.remove_srgb_suffix()]
344    };
345
346    let mut config = surface
347        .get_default_config(&adapter, physical_size.0, physical_size.1)
348        .expect("Surface isn't supported by the adapter.");
349
350    config.view_formats = view_formats;
351    config.format = format;
352
353    surface.configure(&device, &config);
354
355    IASDQContext {
356        instance,
357        surface: SharedPtr::new(surface),
358        config,
359        adapter,
360        device,
361        queue,
362    }
363}
364
365async fn request_device(
366    instance: &Instance,
367    surface: &Surface<'static>,
368) -> (wgpu::Adapter, wgpu::Device, wgpu::Queue) {
369    let adapter = instance
370        .request_adapter(&wgpu::RequestAdapterOptions {
371            power_preference: wgpu::PowerPreference::from_env()
372                .unwrap_or(wgpu::PowerPreference::HighPerformance),
373            force_fallback_adapter: false,
374            compatible_surface: Some(surface),
375        })
376        .await
377        .expect("No suitable GPU adapters found on the system!");
378
379    let adapter_info = adapter.get_info();
380    println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
381
382    let base_dir = std::env::var("CARGO_MANIFEST_DIR");
383    let _trace_path = if let Ok(base_dir) = base_dir {
384        Some(std::path::PathBuf::from(&base_dir).join("WGPU_TRACE_ERROR"))
385    } else {
386        None
387    };
388
389    // remove raytracing features from acquired features under unix like os on nvidia discrete cards
390    // this might be related to wgpu issue, need to keep tracing.
391    let adp_features = adapter.features();
392    #[cfg(target_family = "unix")]
393    let adp_features = {
394        let mut adp_features = adp_features;
395        if adapter_info.name.contains("NVIDIA") {
396            adp_features.remove(wgpu::Features::EXPERIMENTAL_RAY_QUERY);
397        }
398        adp_features
399    };
400    // test features
401    // let adp_features = wgpu::Features::from_bits(0b0011111111011100110111111111111111111111110111000000111111001111).unwrap();
402
403    let res = adapter
404        .request_device(&wgpu::DeviceDescriptor {
405            label: None,
406            required_features: adp_features,
407            required_limits: adapter.limits(),
408            experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
409            memory_hints: wgpu::MemoryHints::Performance,
410            trace: wgpu::Trace::Off,
411        })
412        .await;
413
414    match res {
415        Err(err) => {
416            panic!("request_device failed: {err:?}");
417        }
418        Ok(tuple) => (adapter, tuple.0, tuple.1),
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn normalize_view_size_clamps_zero_axes() {
428        assert_eq!(normalize_view_size((0, 720)), (1, 720));
429        assert_eq!(normalize_view_size((1280, 0)), (1280, 1));
430        assert_eq!(normalize_view_size((0, 0)), (1, 1));
431    }
432
433    #[test]
434    fn normalize_scale_factor_falls_back_to_one_for_invalid_values() {
435        assert_eq!(normalize_scale_factor(0.0), 1.0);
436        assert_eq!(normalize_scale_factor(-2.0), 1.0);
437        assert_eq!(normalize_scale_factor(f32::NAN), 1.0);
438        assert_eq!(normalize_scale_factor(f32::INFINITY), 1.0);
439        assert_eq!(normalize_scale_factor(2.0), 2.0);
440    }
441
442    #[test]
443    fn physical_size_from_logical_size_normalizes_scale_and_size() {
444        assert_eq!(
445            physical_size_from_logical_size(100.0, 50.0, 2.0),
446            (200, 100)
447        );
448        assert_eq!(physical_size_from_logical_size(0.0, 0.0, 2.0), (1, 1));
449        assert_eq!(physical_size_from_logical_size(12.0, 8.0, 0.0), (12, 8));
450    }
451
452    #[test]
453    fn logical_size_from_physical_size_normalizes_scale_and_size() {
454        assert_eq!(
455            logical_size_from_physical_size((200, 100), 2.0),
456            (100.0, 50.0)
457        );
458        assert_eq!(logical_size_from_physical_size((0, 0), 0.0), (1.0, 1.0));
459    }
460
461    #[test]
462    fn normalize_touch_point_uses_normalized_size_and_scale() {
463        assert_eq!(
464            normalize_touch_point(50.0, 25.0, (200, 100), 2.0),
465            (0.5, 0.5)
466        );
467        assert_eq!(normalize_touch_point(1.0, 1.0, (0, 0), 0.0), (1.0, 1.0));
468    }
469
470    #[test]
471    fn resize_surface_config_updates_only_when_size_changes() {
472        let mut config = wgpu::SurfaceConfiguration {
473            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
474            format: wgpu::TextureFormat::Bgra8Unorm,
475            width: 640,
476            height: 480,
477            present_mode: wgpu::PresentMode::Fifo,
478            desired_maximum_frame_latency: 2,
479            alpha_mode: wgpu::CompositeAlphaMode::Auto,
480            view_formats: vec![],
481        };
482
483        assert!(!resize_surface_config(&mut config, (640, 480)));
484        assert!(resize_surface_config(&mut config, (0, 720)));
485        assert_eq!((config.width, config.height), (1, 720));
486    }
487}