Files
wsg/lib/context.rs
T
2026-07-06 10:40:00 +02:00

138 lines
6.2 KiB
Rust

//! # Context Module
//!
//! The **Manager** layer of the architecture — owns hardware resource lifecycle (Device, Queue, Surface).
//! Initializes the GPU at startup; orchestrates frame-by-frame rendering via begin_frame() / end_frame().
//! Does not own rendering logic (that belongs to Renderer) or shader compilation (PipelineCache).
//!
//! ## Interaction with Other Modules
//! - **renderer**: receives Device/Queue references to write rendered output into the TextureView.
//! - **pipeline_cache**: requires Device reference during pipeline creation in Context::new().
//! - **error**: returns WsgError variants from all fallible methods.
use std::sync::Arc;
use wgpu::{Adapter, Device, Instance, Queue, Surface};
use winit::window::Window;
use crate::error::WsgError;
/// Represents the GPU context. Holds all WGPU objects needed for rendering.
/// Created once at startup and shared across frames via Arc.
pub struct Context {
/// The entry point to wgpu — manages connections with graphics drivers (Vulkan, Metal, DX12).
pub instance: Instance,
/// Rendering target surface linking wgpu to the window (winit).
pub surface: Surface<'static>,
/// Physical or software GPU adapter selected by the user.
pub adapter: Adapter,
/// The engine core — creates buffers, textures, pipelines.
pub device: Device,
/// Command submission queue — drawing commands are sent here for execution.
pub queue: Queue,
}
impl Context {
/// Initializes the WGPU context. Creates the surface from the window, requests a device from the adapter,
/// and stores all required objects (instance, surface, adapter, device, queue).
/// Called once at application startup. Returns an error if GPU initialization fails.
/// Internal steps: 1) create Instance → 2) create Surface bound to Window lifecycle → 3) request_adapter for compatible GPU → 4) request_device for Device + Queue.
pub async fn new(window: Arc<Window>) -> Result<Self, WsgError> {
// WGPU instance
let instance = wgpu::Instance::default();
// Surface (bound to window lifecycle, so unsafe)
let surface = instance
.create_surface(window)
.map_err(|e| WsgError::SurfaceCreation(e))?;
// Request for adapter (GPU)
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
})
.await
.map_err(|_| WsgError::NoAdapter)?;
// Request for adapter device and queue
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor::default())
.await
.map_err(|_| WsgError::DeviceCreation)?;
Ok(Self {
instance,
surface,
adapter,
device,
queue,
})
}
/// Configures the surface with a render format and alpha mode for rendering.
/// Inputs: adapter (GPU capabilities), width/height (surface resolution).
/// Returns Ok(()) on success or SurfaceIncompatible if no SRGB format + alpha mode exist.
/// Typically called by the renderer when window size changes.
/// Internal steps: 1) get_capabilities(adapter) → 2) find SRGB format (fallback to first available) → 3) select first alpha mode → 4) build SurfaceConfiguration → 5) configure() the surface.
pub fn configure(
&self,
adapter: &wgpu::Adapter,
width: u32,
height: u32,
) -> Result<(), WsgError> {
let caps = self.surface.get_capabilities(adapter);
// Prefer SRGB format for color accuracy; fall back to first available if none (technical point documented above).
let format = caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.or(caps.formats.first().copied())
.ok_or(WsgError::SurfaceIncompatible)?;
let alpha_mode = caps
.alpha_modes
.first()
.copied()
.ok_or(WsgError::SurfaceIncompatible)?;
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width,
height,
present_mode: wgpu::PresentMode::Fifo, // V-Sync activated
alpha_mode, // first supported alpha mode
view_formats: vec![],
color_space: wgpu::SurfaceColorSpace::Srgb,
desired_maximum_frame_latency: 2,
};
self.surface.configure(&self.device, &config);
Ok(())
}
/// Acquires the next surface texture for rendering this frame. Returns an error variant
/// describing why acquisition failed (timeout, occlusion, surface lost, etc.).
/// Typically called by the orchestrator (main.rs) at the start of each frame loop iteration.
pub fn begin_frame(&self) -> Result<wgpu::SurfaceTexture, WsgError> {
// In wgpu 30, get_current_texture() returns CurrentSurfaceTexture enum directly (not Result).
// All variants are matched to provide explicit error handling instead of panicking.
match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(texture) => Ok(texture),
wgpu::CurrentSurfaceTexture::Suboptimal(_texture) => Err(WsgError::SuboptimalTexture),
wgpu::CurrentSurfaceTexture::Timeout => Err(WsgError::FrameTimeout),
wgpu::CurrentSurfaceTexture::Occluded => Err(WsgError::Occluded),
wgpu::CurrentSurfaceTexture::Outdated => Err(WsgError::Outdated),
wgpu::CurrentSurfaceTexture::Lost => Err(WsgError::Lost),
wgpu::CurrentSurfaceTexture::Validation => Err(WsgError::Validation),
}
}
/// Presents the rendered frame by submitting the acquired surface texture to the GPU queue.
/// The frame must have been obtained via begin_frame(); calling present() twice on the same
/// texture is undefined behavior. Called by the orchestrator after rendering commands are complete.
pub fn end_frame(&self, frame: wgpu::SurfaceTexture) {
// In wgpu 30, present() moved from SurfaceTexture::present() to Queue::present(frame).
self.queue.present(frame);
}
}