renderer new

This commit is contained in:
Jérôme Bousquié
2026-07-04 15:40:32 +02:00
parent 4b7a1b05e5
commit 2a3c6493fc
5 changed files with 122 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
//! # Configuration Module
//!
//! Holds shared constants for the WSG library, such as shader paths and other compilation-time values.
//! This module centralizes configuration so that it can be imported by any submodule without duplicating literals.
pub const BASIC_SHADER: &str = "assets/shaders/basic_shader.wgsl";
+1
View File
@@ -1,3 +1,4 @@
pub mod conf;
pub mod context;
pub mod error;
pub mod renderer;
+101
View File
@@ -0,0 +1,101 @@
//! # Renderer Module
//!
//! The **Specialist** layer of the architecture. Owns rendering logic — RenderPipeline, shaders, and buffers — but does not own hardware resources (Device, Queue). Receives references to those resources when called by the orchestrator (main.rs).
//! Interacts with Context via `begin_frame()` / `end_frame()`: receives a TextureView as input, writes rendered output to it.
use crate::conf::BASIC_SHADER;
/// Renders geometry using a fixed RenderPipeline. This struct owns heavy objects
/// (RenderPipeline) compiled once at creation time, avoiding per-frame allocation.
pub struct Renderer {
/// Pre-compiled pipeline defining vertex/fragment stages, layout, and blend state.
render_pipeline: wgpu::RenderPipeline,
}
impl Renderer {
/// Creates the renderer by compiling the shader module and building the render pipeline.
/// Inputs: `device` for resource creation, `format` for color attachment format.
/// Returns the initialized Renderer owning the pipeline. Called once at application startup
/// by the orchestrator (main.rs). The pipeline is compiled only here; subsequent calls are cheap.
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
// In wgpu 30, ShaderModuleDescriptor fields changed: label is Option<&str> (not Some(label)),
// and source uses Cow<'_, str> instead of [u8]. include_str! + .into() converts &str → Cow<str>.
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(BASIC_SHADER),
source: wgpu::ShaderSource::Wgsl(include_str!("../assets/shaders/basic_shader.wgsl").into()),
});
// push_constant_ranges → removed in wgpu 30; replaced by immediate_size for var<immediate> support.
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("render_pipeline_layout"),
bind_group_layouts: &[],
immediate_size: 0, // no var<immediate> used
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
// entry_point is now Option<&str> — Some to specify the entry point explicitly, None to auto-detect.
entry_point: Some("vs_main"),
compilation_options: Default::default(), // new required field in wgpu 30
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(), // new required field in wgpu 30
// targets is now &[Option<ColorTargetState>] — each target wrapped in Some.
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
// multiview → replaced by multiview_mask (NonZeroU32) and cache in wgpu 30.
multiview_mask: None,
cache: None,
});
Self { render_pipeline }
}
/// Renders a single frame by encoding commands into a temporary CommandEncoder and submitting them to the queue.
/// Inputs: `device` for command buffer creation, `queue` for submission, `view` as render target (from Context).
/// Called by the orchestrator after begin_frame() returns the TextureView. The pipeline is invoked once per call;
/// no persistent state is retained between frames.
pub fn render(&self, device: &wgpu::Device, queue: &wgpu::Queue, view: &wgpu::TextureView) {
// create command encoder
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("render encoder"),
});
// 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(&self.render_pipeline);
render_pass.draw(0..3, 0..1);
// Queue submit
drop(render_pass);
queue.submit(std::iter::once(encoder.finish()));
}
}