//! # 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; use crate::frame::Frame; /// 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) -> 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. /// 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 { let caps = self.surface.get_capabilities(adapter); let format = caps .formats .iter() .copied() .find(|f| f.is_srgb()) .or(caps.formats.first().copied()) .ok_or(WsgError::SurfaceIncompatible)?; let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format, width, height, present_mode: wgpu::PresentMode::Fifo, alpha_mode: caps .alpha_modes .first() .copied() .ok_or(WsgError::SurfaceIncompatible)?, view_formats: vec![], color_space: wgpu::SurfaceColorSpace::Srgb, desired_maximum_frame_latency: 2, }; self.surface.configure(&self.device, &config); // On retourne le format choisi pour que le Renderer puisse le stocker Ok(format) } /// 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); } /// Returns a Frame wrapper around the current surface texture and its TextureView. /// Called by the orchestrator at frame start; equivalent to Context::begin_frame() + Frame construction. pub fn get_next_frame(&self) -> Frame { Frame::new(&self.surface) } }