cotis-wgpu 0.1.0-alpha

Desktop wgpu renderer backend for Cotis
Documentation
//! Advanced extension API: swapchain surface and depth buffer.
//!
//! Extension API for custom [`crate::drawable::WgpuDrawable`] implementations; not re-exported in
//! [`crate::prelude`] and may change between releases.
//!
//! [`SurfaceState`] manages the wgpu swapchain for the active winit window.
//! [`DepthTexture`] provides a shared depth buffer used by both the UI geometry pass and glyphon text.

use std::sync::Arc;

use winit::window::Window;

use crate::device::GpuDevice;
use crate::device::choose_surface_format;
use crate::error::WgpuBackendError;

/// Depth buffer used by the UI geometry and glyphon text passes.
pub struct DepthTexture {
    /// GPU depth texture resource.
    pub texture: wgpu::Texture,
    /// View for binding as a depth attachment.
    pub view: wgpu::TextureView,
    /// Sampler for depth-aware text rendering.
    pub sampler: wgpu::Sampler,
}

impl DepthTexture {
    /// Creates a depth32float texture matching the given surface configuration dimensions.
    pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
        let texture = device.create_texture(&wgpu::TextureDescriptor {
            size: wgpu::Extent3d {
                width: config.width.max(1),
                height: config.height.max(1),
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth32Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            label: Some("cotis-wgpu depth texture"),
            view_formats: &[],
        });

        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Nearest,
            compare: Some(wgpu::CompareFunction::LessEqual),
            lod_min_clamp: 0.0,
            lod_max_clamp: 100.0,
            ..Default::default()
        });

        Self {
            texture,
            view,
            sampler,
        }
    }
}

/// Swapchain surface and configuration for the active window.
pub struct SurfaceState {
    surface: wgpu::Surface<'static>,
    /// Current surface configuration (dimensions, format, present mode).
    pub config: wgpu::SurfaceConfiguration,
    /// Depth buffer recreated on resize/reconfigure.
    pub depth: DepthTexture,
}

impl SurfaceState {
    /// Creates and configures the swapchain surface for the given window.
    ///
    /// # Errors
    ///
    /// Returns [`WgpuBackendError::CreateSurface`] if surface creation fails.
    pub fn new(
        gpu: &GpuDevice,
        window: &Arc<Window>,
        width: u32,
        height: u32,
    ) -> Result<Self, WgpuBackendError> {
        let surface_target = unsafe { wgpu::SurfaceTargetUnsafe::from_window(window.as_ref()) }
            .map_err(|error| WgpuBackendError::CreateSurface(error.to_string()))?;
        let surface = unsafe { gpu.instance.create_surface_unsafe(surface_target) }
            .map_err(|error| WgpuBackendError::CreateSurface(error.to_string()))?;

        let capabilities = surface.get_capabilities(&gpu.adapter);
        let format = choose_surface_format(&capabilities.formats);
        let (width, height) = non_zero_size(width, height);

        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format,
            width,
            height,
            present_mode: capabilities.present_modes[0],
            desired_maximum_frame_latency: 2,
            alpha_mode: capabilities.alpha_modes[0],
            view_formats: vec![],
        };

        surface.configure(&gpu.device, &config);
        let depth = DepthTexture::new(&gpu.device, &config);

        Ok(Self {
            surface,
            config,
            depth,
        })
    }

    /// Resizes the swapchain and recreates the depth buffer.
    ///
    /// No-op if either dimension is zero.
    pub fn resize(&mut self, gpu: &GpuDevice, width: u32, height: u32) {
        let (width, height) = non_zero_size(width, height);
        if width == 0 || height == 0 {
            return;
        }

        self.config.width = width;
        self.config.height = height;
        self.surface.configure(&gpu.device, &self.config);
        self.depth = DepthTexture::new(&gpu.device, &self.config);
    }

    /// Reconfigures the swapchain with the current config and recreates the depth buffer.
    ///
    /// Called internally when the surface is lost or outdated.
    pub fn reconfigure(&mut self, gpu: &GpuDevice) {
        self.surface.configure(&gpu.device, &self.config);
        self.depth = DepthTexture::new(&gpu.device, &self.config);
    }

    /// Acquires the next swapchain texture, reconfiguring once on loss or staleness.
    ///
    /// # Errors
    ///
    /// Returns [`WgpuBackendError::Surface`] if acquisition fails after one reconfigure attempt.
    pub fn acquire_frame(
        &mut self,
        gpu: &GpuDevice,
    ) -> Result<wgpu::SurfaceTexture, WgpuBackendError> {
        match self.surface.get_current_texture() {
            Ok(frame) => Ok(frame),
            Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
                self.reconfigure(gpu);
                self.surface
                    .get_current_texture()
                    .map_err(WgpuBackendError::Surface)
            }
            Err(error) => Err(WgpuBackendError::Surface(error)),
        }
    }

    /// Returns the swapchain pixel format.
    pub fn format(&self) -> wgpu::TextureFormat {
        self.config.format
    }
}

fn non_zero_size(width: u32, height: u32) -> (u32, u32) {
    (width.max(1), height.max(1))
}