refactor renderer responsable + docs + schema
This commit is contained in:
+17
-11
@@ -14,6 +14,7 @@ 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.
|
||||
@@ -78,9 +79,8 @@ impl Context {
|
||||
adapter: &wgpu::Adapter,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), WsgError> {
|
||||
) -> Result<wgpu::TextureFormat, 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()
|
||||
@@ -89,25 +89,25 @@ impl Context {
|
||||
.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
|
||||
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);
|
||||
Ok(())
|
||||
|
||||
// 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
|
||||
@@ -134,4 +134,10 @@ impl Context {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//! # Frame Module
|
||||
//!
|
||||
//! Defines `Frame`, a per-frame RAII wrapper around the surface texture and its TextureView.
|
||||
//! A Frame exists only for the duration of a single rendering pass — it is acquired at the start
|
||||
//! of each frame loop iteration via Context::begin_frame() or Frame::try_new(), used by Renderer
|
||||
//! to write draw commands into the TextureView, then dropped after Renderer::present() submits it.
|
||||
//!
|
||||
//! ## Interaction with Other Modules
|
||||
//! - **context**: provides the Surface from which Frame acquires the current texture.
|
||||
//! - **renderer**: passes Frame's TextureView to render() as the color attachment target.
|
||||
//! - **error**: does not use errors directly; Frame::new() panics on acquisition failure while
|
||||
//! Frame::try_new() returns Option<Self> for graceful recovery.
|
||||
|
||||
pub struct Frame {
|
||||
/// The GPU surface texture representing the current display buffer to be presented.
|
||||
pub surface_texture: wgpu::SurfaceTexture,
|
||||
/// A read-only view into surface_texture, used as the RenderPass color attachment during rendering.
|
||||
pub view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Acquires the next surface texture and creates a TextureView over it.
|
||||
/// Called by the orchestrator (main.rs) at the start of each frame loop iteration.
|
||||
/// Panics if the surface cannot be acquired (e.g., lost, occluded). For non-panicking
|
||||
/// alternatives, use try_new(). Internal steps: 1) get_current_texture() → 2) match Success/Suboptimal → 3) create_view.
|
||||
pub fn new(surface: &wgpu::Surface) -> Self {
|
||||
// In wgpu 30, get_current_texture() returns CurrentSurfaceTexture enum directly (not Result).
|
||||
// All variants are matched to provide explicit error handling instead of panicking —
|
||||
// see Context::begin_frame() for the detailed variant mapping.
|
||||
match surface.get_current_texture() {
|
||||
// On Success or Suboptimal, we acquire the SurfaceTexture and create its TextureView
|
||||
wgpu::CurrentSurfaceTexture::Success(frame)
|
||||
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
|
||||
let view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
Self {
|
||||
surface_texture: frame,
|
||||
view,
|
||||
}
|
||||
}
|
||||
// Panicking on failure here is intentional — Frame must exist for rendering to proceed.
|
||||
// Callers should use try_new() if they prefer Option-based error recovery.
|
||||
other => panic!("Failed to acquire texture: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Presents the rendered frame by submitting the acquired surface texture to the GPU queue.
|
||||
/// The frame must have been obtained via new() or try_new(); calling present() twice on the same
|
||||
/// texture is undefined behavior. Called after Renderer::render().
|
||||
pub fn present(self, queue: &wgpu::Queue) {
|
||||
queue.present(self.surface_texture);
|
||||
}
|
||||
|
||||
/// Attempts to acquire the next surface texture without panicking.
|
||||
/// Returns Some(Frame) on success (Success/Suboptimal) or None on any error variant.
|
||||
/// Called when graceful frame skipping is preferred over crashing.
|
||||
pub fn try_new(surface: &wgpu::Surface) -> Option<Self> {
|
||||
match surface.get_current_texture() {
|
||||
wgpu::CurrentSurfaceTexture::Success(frame)
|
||||
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
|
||||
let view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
Some(Self {
|
||||
surface_texture: frame,
|
||||
view,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the TextureView used as the RenderPass color attachment.
|
||||
/// Called by Renderer::render() to pass the view into begin_render_pass().
|
||||
pub fn view(&self) -> &wgpu::TextureView {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
pub mod conf;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod frame;
|
||||
pub mod material;
|
||||
pub mod mesh;
|
||||
pub mod pipeline_cache;
|
||||
|
||||
+12
-8
@@ -22,21 +22,26 @@ use std::sync::Arc;
|
||||
/// Shader pipeline cache: maps (shader_id, format) keys to compiled RenderPipelines.
|
||||
/// Ensures each unique shader+format combination is compiled at most once; subsequent requests return cached instances.
|
||||
pub struct PipelineCache {
|
||||
/// Cached pipelines keyed by their shader identifier string.
|
||||
/// Cached pipelines keyed by their shader identifier string. Multiple Materials sharing the same ID share one Arc-wrapped pipeline.
|
||||
pipelines: HashMap<String, Arc<wgpu::RenderPipeline>>,
|
||||
/// Maps shader IDs to file paths on disk for WGSL loading in `load_shader()`.
|
||||
shader_paths: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl PipelineCache {
|
||||
/// Creates an empty pipeline cache with no pre-loaded shaders or pipelines.
|
||||
/// Called at application startup before any Material creation. Shader paths must be registered via register_shader() first.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pipelines: HashMap::new(),
|
||||
// Maps shader IDs to file paths on disk for WGSL loading in load_shader().
|
||||
// When a path exists, it reads from it; otherwise falls back to BASIC_SHADER constant.
|
||||
shader_paths: HashMap::new(),
|
||||
}
|
||||
}
|
||||
/// Enregistre un chemin de shader associé à un ID.
|
||||
/// Renvoie Ok(id) si réussi, ou une erreur si l'ID existe déjà.
|
||||
/// Registers an external WGSL shader file path associated with a given ID.
|
||||
/// Inputs: id (unique key for this shader), path (filesystem path to .wgsl file).
|
||||
/// Returns Ok(id) on success or Err if the ID is already registered. Called during scene setup to register custom shaders.
|
||||
pub fn register_shader(&mut self, id: &str, path: &str) -> Result<String, String> {
|
||||
if self.shader_paths.contains_key(id) {
|
||||
return Err(format!("ID '{}' already exists.", id));
|
||||
@@ -45,15 +50,14 @@ impl PipelineCache {
|
||||
Ok(id.to_string())
|
||||
}
|
||||
|
||||
/// Supprime un ID et son chemin associé.
|
||||
/// Renvoie Ok(id) si réussi, ou une erreur si l'ID est inconnu.
|
||||
/// Unregisters a shader by its ID, removing both the path reference and any cached compiled pipeline.
|
||||
/// Inputs: id (the shader identifier to remove).
|
||||
/// Returns Ok(id) on success or Err if the ID does not exist. Called when a shader should be freed from GPU memory.
|
||||
pub fn unregister_shader(&mut self, id: &str) -> Result<String, String> {
|
||||
if self.shader_paths.remove(id).is_none() {
|
||||
return Err(format!("ID '{}' does not exist.", id));
|
||||
}
|
||||
// Optionnel : tu pourrais aussi supprimer le pipeline compilé du cache
|
||||
// si tu veux libérer la mémoire GPU immédiatement :
|
||||
self.pipelines.remove(id);
|
||||
// Remove cached pipeline so GPU memory is freed (wgpu drops it automatically)
|
||||
|
||||
Ok(id.to_string())
|
||||
}
|
||||
|
||||
+54
-26
@@ -12,41 +12,56 @@
|
||||
//! - **mesh**: passes vertex/index buffers into set_vertex_buffer/set_index_buffer during draw.
|
||||
//! - **material**: provides the RenderPipeline reference via set_pipeline during draw.
|
||||
|
||||
pub struct Renderer {}
|
||||
use crate::context::Context;
|
||||
|
||||
/// The Executor layer of the architecture. Owns Device, Queue, and Format after initialization from Context.
|
||||
/// Orchestrates all GPU draw calls without owning raw hardware resources externally.
|
||||
pub struct Renderer {
|
||||
/// GPU command submission queue — owned by the Renderer after initialization from Context.
|
||||
queue: wgpu::Queue,
|
||||
/// GPU device — creates buffers, textures, pipelines; owned by the Renderer after initialization.
|
||||
device: wgpu::Device,
|
||||
/// Surface texture output format — stored here so it can be passed to PipelineCache on Material creation.
|
||||
format: wgpu::TextureFormat,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Creates a new Renderer instance with no internal state — it is pure execution logic only.
|
||||
/// Called once at application startup; the same Renderer is reused for all frames.
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
/// Creates a Renderer by taking ownership of Device, Queue, and Format from the Context.
|
||||
/// Called once at application startup during scene setup. The Renderer becomes the sole owner of these resources.
|
||||
pub fn new(context: &Context, format: wgpu::TextureFormat) -> Self {
|
||||
Self {
|
||||
queue: context.queue.clone(),
|
||||
device: context.device.clone(),
|
||||
format,
|
||||
}
|
||||
}
|
||||
|
||||
/// Orchestrates a single draw call: creates an encoder, starts a render pass, binds pipeline + buffers, draws, then submits commands.
|
||||
/// Inputs: device (GPU command source), queue (command submission target), view (surface texture output), mesh (geometry to draw), material (shader/pipeline).
|
||||
/// Returns nothing — side-effect: GPU executes the draw and presents to the surface. Called by the orchestrator (main.rs) once per frame.
|
||||
/// Internal steps: 1) create_command_encoder → 2) begin_render_pass in scoped block → 3) set_pipeline/set_vertex_buffer/draw → drop(render_pass) → 4) submit(encoder.finish()).
|
||||
/// Orchestrates rendering of a single object: binds Material pipeline + Mesh vertex data into a RenderPass,
|
||||
/// then submits commands to the GPU queue for execution. Called per-frame by the orchestrator (main.rs).
|
||||
/// Internal steps: 1) create CommandEncoder → 2) begin RenderPass with color attachment →
|
||||
/// 3) set_pipeline(material.pipeline) → 4) set_vertex_buffer(mesh.vertex_buffer) →
|
||||
/// 5) draw_indexed or draw based on index buffer presence → 6) drop render_pass end scope →
|
||||
/// 7) submit encoder via queue.
|
||||
pub fn render(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
view: &wgpu::TextureView,
|
||||
mesh: &crate::mesh::Mesh,
|
||||
material: &crate::material::Material,
|
||||
) {
|
||||
// Create command encoder
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
// Create per-frame command encoder; its lifetime is scoped to this function only.
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("render encoder"),
|
||||
});
|
||||
|
||||
// RenderPass borrows encoder mutably — must end (drop) before encoder.finish() below.
|
||||
// This scope boundary enforces Rust's borrow checker rules for GPU synchronization.
|
||||
{
|
||||
// Block creation so render_pass is dropped before queue submit
|
||||
// RenderPass start — depth_slice is a new required field in wgpu 30 for multisampled textures.
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
depth_slice: None, // new field; None means no multisample resolve needed
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: wgpu::StoreOp::Store,
|
||||
@@ -55,10 +70,13 @@ impl Renderer {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Pipeline call — draw with empty vertex buffers (geometry defined in shader)
|
||||
render_pass.set_pipeline(&material.pipeline);
|
||||
|
||||
// if any indices
|
||||
if mesh.num_vertices > 0 {
|
||||
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
} else {
|
||||
// If no vertices, skip drawing entirely (nothing to render)
|
||||
return;
|
||||
}
|
||||
if let Some(index_buffer) = &mesh.index_buffer {
|
||||
render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
||||
render_pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
|
||||
@@ -66,13 +84,23 @@ impl Renderer {
|
||||
render_pass.draw(0..mesh.num_vertices, 0..1);
|
||||
}
|
||||
}
|
||||
// Queue submit
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
/// Submits a completed command encoder to the GPU queue for execution.
|
||||
/// Inputs: queue (GPU command submission target), encoder (completed command buffer).
|
||||
/// Returns nothing — side-effect: GPU executes all recorded commands. Called by renderer code after render pass completion.
|
||||
pub fn submit_commands(&self, queue: &wgpu::Queue, encoder: wgpu::CommandEncoder) {
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
/// Presents the rendered frame by submitting the acquired surface texture to the GPU queue.
|
||||
/// The frame must have been obtained via Context::begin_frame() or Frame::try_new(); calling present()
|
||||
/// twice on the same texture is undefined behavior. Called by the orchestrator after render().
|
||||
pub fn present(&self, frame: crate::frame::Frame) {
|
||||
self.queue.present(frame.surface_texture);
|
||||
}
|
||||
|
||||
/// Returns a reference to the owned Device for direct access when needed (e.g., PipelineCache creation).
|
||||
pub fn device(&self) -> &wgpu::Device {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Returns the surface texture output format used for rendering.
|
||||
pub fn format(&self) -> wgpu::TextureFormat {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -23,4 +23,21 @@ pub struct Vertex {
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
// Default values for stability
|
||||
impl Default for Vertex {
|
||||
// Default values for stability
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Default position at center (0, 0)
|
||||
position: [0.0, 0.0, 0.0],
|
||||
|
||||
// Normal pointing upward (standard for lighting calculations)
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
|
||||
// UV coordinates at the origin of the texture
|
||||
uv: [0.0, 0.0],
|
||||
|
||||
// Opaque white color by default
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user