1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use egui_backend::WindowBackend;
use raw_window_handle::HasRawWindowHandle;
use tracing::{debug, info};
use wgpu::*;
pub struct SurfaceManager {
    /// we create a view for the swapchain image and set it to this field during the `prepare_frame` fn.
    /// users can assume that it will *always* be available during the `UserApp::run` fn. but don't keep any references as
    /// it will be taken and submitted during the `present_frame` method after rendering is done.
    /// surface is always cleared by wgpu, so no need to wipe it again.
    pub surface_view: Option<TextureView>,
    /// once we acquire a swapchain image (surface texture), we will put it here. surface_view will be created from this
    pub surface_current_image: Option<SurfaceTexture>,
    /// this is the window surface
    pub surface: Option<Surface>,
    /// this configuration needs to be updated with the latest resize
    pub surface_config: SurfaceConfiguration,
    /// Surface manager will iterate over this and find the first format that is supported by surface.
    /// if we find one, we will set surface configuration to that format.
    /// if we don't find one, we will just use the first surface format support.
    /// so, if you don't care about the surface format, just set this to an empty vector.
    surface_formats_priority: Vec<TextureFormat>,
}
impl Drop for SurfaceManager {
    fn drop(&mut self) {
        tracing::warn!("dropping wgpu surface");
    }
}
impl SurfaceManager {
    pub fn new(
        window_backend: &mut impl WindowBackend,
        instance: &Instance,
        adapter: &Adapter,
        device: &Device,
        surface: Option<Surface>,
        surface_formats_priority: Vec<TextureFormat>,
        surface_config: SurfaceConfiguration,
    ) -> Self {
        let mut surface_manager = Self {
            surface_view: None,
            surface_current_image: None,
            surface,
            surface_config,
            surface_formats_priority,
        };
        surface_manager.reconfigure_surface(window_backend, instance, adapter, device);
        surface_manager
    }
    pub fn create_current_surface_texture_view(
        &mut self,
        window_backend: &mut impl WindowBackend,
        device: &Device,
    ) {
        if let Some(surface) = self.surface.as_ref() {
            let current_surface_image = surface.get_current_texture().unwrap_or_else(|_| {
                let phy_fb_size = window_backend.get_live_physical_size_framebuffer().unwrap();
                self.surface_config.width = phy_fb_size[0];
                self.surface_config.height = phy_fb_size[1];
                surface.configure(device, &self.surface_config);
                surface.get_current_texture().unwrap_or_else(|e| {
                    panic!("failed to get surface even after reconfiguration. {e}")
                })
            });
            if current_surface_image.suboptimal {
                tracing::warn!("current surface image is suboptimal. ");
            }
            let surface_view = current_surface_image
                .texture
                .create_view(&TextureViewDescriptor {
                    label: Some("surface view"),
                    format: Some(self.surface_config.format),
                    dimension: Some(TextureViewDimension::D2),
                    aspect: TextureAspect::All,
                    base_mip_level: 0,
                    mip_level_count: None,
                    base_array_layer: 0,
                    array_layer_count: None,
                });

            self.surface_view = Some(surface_view);
            self.surface_current_image = Some(current_surface_image);
        } else {
            tracing::warn!(
                "skipping acquiring the currnet surface image because there's no surface"
            );
        }
    }
    /// This basically checks if the surface needs creating. and then if needed, creates surface if window exists.
    /// then, it does all the work of configuring the surface.
    /// this is used during resume events to create a surface.
    pub fn reconfigure_surface(
        &mut self,
        window_backend: &mut impl WindowBackend,
        instance: &Instance,
        adapter: &Adapter,
        device: &Device,
    ) {
        if let Some(window) = window_backend.get_window() {
            if self.surface.is_none() {
                self.surface = Some(unsafe {
                    tracing::debug!("creating a surface with {:?}", window.raw_window_handle());
                    instance
                        .create_surface(window)
                        .expect("failed to create surface")
                });
            }

            let capabilities = self.surface.as_ref().unwrap().get_capabilities(adapter);
            let supported_formats = capabilities.formats;
            debug!(
                "supported alpha modes: {:#?}",
                &capabilities.alpha_modes[..]
            );

            if window_backend.get_config().transparent.unwrap_or_default() {
                for alpha_mode in capabilities.alpha_modes.iter().copied() {
                    match alpha_mode {
                        CompositeAlphaMode::PreMultiplied | CompositeAlphaMode::PostMultiplied => {
                            self.surface_config.alpha_mode = alpha_mode;
                        }
                        _ => {}
                    }
                }
            }
            debug!("supported formats of the surface: {supported_formats:#?}");

            let mut compatible_format_found = false;
            for sfmt in self.surface_formats_priority.iter() {
                debug!("checking if {sfmt:?} is supported");
                if supported_formats.contains(sfmt) {
                    debug!("{sfmt:?} is supported. setting it as surface format");
                    self.surface_config.format = *sfmt;
                    compatible_format_found = true;
                    break;
                }
            }
            if !compatible_format_found {
                if !self.surface_formats_priority.is_empty() {
                    tracing::warn!(
                        "could not find compatible surface format from user provided formats. choosing first supported format instead"
                    );
                }
                self.surface_config.format = supported_formats
                    .iter()
                    .find(|f| f.is_srgb())
                    .copied()
                    .unwrap_or_else(|| {
                        supported_formats
                            .first()
                            .copied()
                            .expect("surface has zero supported texture formats")
                    })
            }
            let view_format = if self.surface_config.format.is_srgb() {
                self.surface_config.format
            } else {
                tracing::warn!(
                    "surface format is not srgb: {:?}",
                    self.surface_config.format
                );
                match self.surface_config.format {
                    TextureFormat::Rgba8Unorm => TextureFormat::Rgba8UnormSrgb,
                    TextureFormat::Bgra8Unorm => TextureFormat::Bgra8UnormSrgb,
                    _ => self.surface_config.format,
                }
            };
            self.surface_config.view_formats = vec![view_format];

            #[cfg(target_os = "emscripten")]
            {
                self.surface_config.view_formats = vec![];
            }

            debug!(
                "using format: {:#?} for surface configuration",
                self.surface_config.format
            );
            self.resize_framebuffer(device, window_backend);
        }
    }

    pub fn resize_framebuffer(&mut self, device: &Device, window_backend: &mut impl WindowBackend) {
        if let Some(size) = window_backend.get_live_physical_size_framebuffer() {
            self.surface_config.width = size[0];
            self.surface_config.height = size[1];
            info!(
                "reconfiguring surface with config: {:#?}",
                &self.surface_config
            );
            self.surface
                .as_ref()
                .unwrap()
                .configure(device, &self.surface_config);
        }
    }
    pub fn suspend(&mut self) {
        self.surface = None;
        self.surface_current_image = None;
        self.surface_view = None;
    }
}