Skip to main content

cotis_wgpu/
surface.rs

1//! Advanced extension API: swapchain surface and depth buffer.
2//!
3//! Extension API for custom [`crate::drawable::WgpuDrawable`] implementations; not re-exported in
4//! [`crate::prelude`] and may change between releases.
5//!
6//! [`SurfaceState`] manages the wgpu swapchain for the active winit window.
7//! [`DepthTexture`] provides a shared depth buffer used by both the UI geometry pass and glyphon text.
8
9use std::sync::Arc;
10
11use winit::window::Window;
12
13use crate::device::GpuDevice;
14use crate::device::choose_surface_format;
15use crate::error::WgpuBackendError;
16
17/// Depth buffer used by the UI geometry and glyphon text passes.
18pub struct DepthTexture {
19    /// GPU depth texture resource.
20    pub texture: wgpu::Texture,
21    /// View for binding as a depth attachment.
22    pub view: wgpu::TextureView,
23    /// Sampler for depth-aware text rendering.
24    pub sampler: wgpu::Sampler,
25}
26
27impl DepthTexture {
28    /// Creates a depth32float texture matching the given surface configuration dimensions.
29    pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
30        let texture = device.create_texture(&wgpu::TextureDescriptor {
31            size: wgpu::Extent3d {
32                width: config.width.max(1),
33                height: config.height.max(1),
34                depth_or_array_layers: 1,
35            },
36            mip_level_count: 1,
37            sample_count: 1,
38            dimension: wgpu::TextureDimension::D2,
39            format: wgpu::TextureFormat::Depth32Float,
40            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
41            label: Some("cotis-wgpu depth texture"),
42            view_formats: &[],
43        });
44
45        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
46
47        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
48            address_mode_u: wgpu::AddressMode::ClampToEdge,
49            address_mode_v: wgpu::AddressMode::ClampToEdge,
50            address_mode_w: wgpu::AddressMode::ClampToEdge,
51            mag_filter: wgpu::FilterMode::Linear,
52            min_filter: wgpu::FilterMode::Linear,
53            mipmap_filter: wgpu::FilterMode::Nearest,
54            compare: Some(wgpu::CompareFunction::LessEqual),
55            lod_min_clamp: 0.0,
56            lod_max_clamp: 100.0,
57            ..Default::default()
58        });
59
60        Self {
61            texture,
62            view,
63            sampler,
64        }
65    }
66}
67
68/// Swapchain surface and configuration for the active window.
69pub struct SurfaceState {
70    surface: wgpu::Surface<'static>,
71    /// Current surface configuration (dimensions, format, present mode).
72    pub config: wgpu::SurfaceConfiguration,
73    /// Depth buffer recreated on resize/reconfigure.
74    pub depth: DepthTexture,
75}
76
77impl SurfaceState {
78    /// Creates and configures the swapchain surface for the given window.
79    ///
80    /// # Errors
81    ///
82    /// Returns [`WgpuBackendError::CreateSurface`] if surface creation fails.
83    pub fn new(
84        gpu: &GpuDevice,
85        window: &Arc<Window>,
86        width: u32,
87        height: u32,
88    ) -> Result<Self, WgpuBackendError> {
89        let surface_target = unsafe { wgpu::SurfaceTargetUnsafe::from_window(window.as_ref()) }
90            .map_err(|error| WgpuBackendError::CreateSurface(error.to_string()))?;
91        let surface = unsafe { gpu.instance.create_surface_unsafe(surface_target) }
92            .map_err(|error| WgpuBackendError::CreateSurface(error.to_string()))?;
93
94        let capabilities = surface.get_capabilities(&gpu.adapter);
95        let format = choose_surface_format(&capabilities.formats);
96        let (width, height) = non_zero_size(width, height);
97
98        let config = wgpu::SurfaceConfiguration {
99            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
100            format,
101            width,
102            height,
103            present_mode: capabilities.present_modes[0],
104            desired_maximum_frame_latency: 2,
105            alpha_mode: capabilities.alpha_modes[0],
106            view_formats: vec![],
107        };
108
109        surface.configure(&gpu.device, &config);
110        let depth = DepthTexture::new(&gpu.device, &config);
111
112        Ok(Self {
113            surface,
114            config,
115            depth,
116        })
117    }
118
119    /// Resizes the swapchain and recreates the depth buffer.
120    ///
121    /// No-op if either dimension is zero.
122    pub fn resize(&mut self, gpu: &GpuDevice, width: u32, height: u32) {
123        let (width, height) = non_zero_size(width, height);
124        if width == 0 || height == 0 {
125            return;
126        }
127
128        self.config.width = width;
129        self.config.height = height;
130        self.surface.configure(&gpu.device, &self.config);
131        self.depth = DepthTexture::new(&gpu.device, &self.config);
132    }
133
134    /// Reconfigures the swapchain with the current config and recreates the depth buffer.
135    ///
136    /// Called internally when the surface is lost or outdated.
137    pub fn reconfigure(&mut self, gpu: &GpuDevice) {
138        self.surface.configure(&gpu.device, &self.config);
139        self.depth = DepthTexture::new(&gpu.device, &self.config);
140    }
141
142    /// Acquires the next swapchain texture, reconfiguring once on loss or staleness.
143    ///
144    /// # Errors
145    ///
146    /// Returns [`WgpuBackendError::Surface`] if acquisition fails after one reconfigure attempt.
147    pub fn acquire_frame(
148        &mut self,
149        gpu: &GpuDevice,
150    ) -> Result<wgpu::SurfaceTexture, WgpuBackendError> {
151        match self.surface.get_current_texture() {
152            Ok(frame) => Ok(frame),
153            Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
154                self.reconfigure(gpu);
155                self.surface
156                    .get_current_texture()
157                    .map_err(WgpuBackendError::Surface)
158            }
159            Err(error) => Err(WgpuBackendError::Surface(error)),
160        }
161    }
162
163    /// Returns the swapchain pixel format.
164    pub fn format(&self) -> wgpu::TextureFormat {
165        self.config.format
166    }
167}
168
169fn non_zero_size(width: u32, height: u32) -> (u32, u32) {
170    (width.max(1), height.max(1))
171}