//! # 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. 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 support. let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("render_pipeline_layout"), bind_group_layouts: &[], immediate_size: 0, // no var 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] — 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())); } }