79 lines
4.1 KiB
Rust
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()));
|
|
}
|
|
}
|