129 lines
5.4 KiB
Rust
129 lines
5.4 KiB
Rust
//! # 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<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.
|
|
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);
|
|
}
|
|
}
|