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

79 lines
4.1 KiB
Rust

//! # Renderer Module
//!
//! The **Specialist** (Executor) layer of the architecture. Owns rendering logic — orchestrates draw calls by binding
//! Material pipelines and Mesh vertex buffers into a RenderPass, then submits commands to the GPU queue.
//! Does not own hardware resources (Device, Queue); receives references when called by the orchestrator (main.rs).
//! Does not own RenderPipelines or shaders — those are managed by PipelineCache and accessed through Material.
//! Does not own Surface/TextureView — acquired from Context::begin_frame().
//!
//! ## Interaction with Other Modules
//! - **context**: receives Device/Queue references and TextureView; does not call begin/end_frame itself.
//! - **pipeline_cache**: indirectly via Material — Renderer uses pipelines that PipelineCache compiled.
//! - **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 {}
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 {}
}
/// 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()).
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 {
label: Some("render encoder"),
});
{
// 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
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
// Pipeline call — draw with empty vertex buffers (geometry defined in shader)
render_pass.set_pipeline(&material.pipeline);
// if any indices
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);
} else {
render_pass.draw(0..mesh.num_vertices, 0..1);
}
}
// Queue submit
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()));
}
}