//! # Context Module //! //! Initializes the GPU, creates the surface, and holds the Device and Queue. It is static (created once at startup). 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. pub async fn new(window: Arc) -> Result { // 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. 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 { // 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); } }