feat(core): expose frame view and auto-render the scene
- AppHandler::render now receives the current &Frame; its default implementation renders the whole scene automatically via app.render_scene(frame.view()) (Option A). Users can simply not implement render for full auto-rendering. - Add Renderer::render_scene: batch-renders every scene entity in a single render pass. Factored per-mesh draw logic into a private draw_entity helper shared with Renderer::render. - Add App::render_scene(view) delegating to the Renderer. - Fill simple.rs with a real quad (mesh/material/entity) without importing wgpu; the scene now auto-renders via the trait default.
This commit is contained in:
+55
-8
@@ -1,19 +1,66 @@
|
||||
//! Workflow déclaratif minimal (~15 lignes), sans manipulation WGPU explicite.
|
||||
//! Workflow déclaratif minimal, sans manipulation WGPU explicite dans ce fichier.
|
||||
//! `AppBuilder` ouvre la fenêtre, construit le `Context`/`Renderer` et fait tourner la boucle
|
||||
//! update → render → present. Le rendu automatisé de la scène n'est pas encore en place
|
||||
//! (README, Roadmap étape 1) : `render()` est donc vide pour l'instant.
|
||||
//! update → render → present. La scène se rend automatiquement : la méthode `render()` par défaut
|
||||
//! du trait `AppHandler` appelle `app.render_scene(frame.view())`, donc l'utilisateur n'implémente
|
||||
//! même pas `render` ici — il ne fait que remplir `app.scene` avec un mesh, un matériau et une entité.
|
||||
use std::sync::Arc;
|
||||
use wsg_lib::AppHandler;
|
||||
use wsg_lib::app::AppBuilder;
|
||||
use wsg_lib::resources::{Material, Mesh, Vertex};
|
||||
use wsg_lib::utils::WsgError;
|
||||
use wsg_lib::{App, AppHandler};
|
||||
|
||||
struct MonQuad;
|
||||
|
||||
impl AppHandler for MonQuad {
|
||||
fn render(&mut self, _app: &mut App) {}
|
||||
}
|
||||
impl AppHandler for MonQuad {}
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> Result<(), WsgError> {
|
||||
let app = AppBuilder::new().title("WSG Simple").build().await?;
|
||||
let mut app = AppBuilder::new().title("WSG Simple").build().await?;
|
||||
|
||||
// Enregistrement du shader, création du matériau et du mesh du quad (sans importer wgpu).
|
||||
app.cache
|
||||
.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)
|
||||
.unwrap();
|
||||
let vertices = [
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, 0.0],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [0.0, 0.0],
|
||||
color: [1.0, 0.0, 0.0, 1.0],
|
||||
}, // Haut-Gauche (Rouge)
|
||||
Vertex {
|
||||
position: [0.5, 0.5, 0.0],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [1.0, 0.0],
|
||||
color: [0.0, 1.0, 0.0, 1.0],
|
||||
}, // Haut-Droite (Vert)
|
||||
Vertex {
|
||||
position: [0.5, -0.5, 0.0],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [1.0, 1.0],
|
||||
color: [0.0, 0.0, 1.0, 1.0],
|
||||
}, // Bas-Droite (Bleu)
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, 0.0],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [0.0, 1.0],
|
||||
color: [1.0, 1.0, 0.0, 1.0],
|
||||
}, // Bas-Gauche (Jaune)
|
||||
];
|
||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
||||
|
||||
let mesh = Arc::new(Mesh::new(app.renderer.device(), &vertices, Some(&indices)));
|
||||
let material = Arc::new(Material::new(
|
||||
app.renderer.format(),
|
||||
"basic",
|
||||
&mut app.cache,
|
||||
));
|
||||
|
||||
app.scene.add_mesh("quad_mesh", mesh).unwrap();
|
||||
app.scene.add_material("basic_material", material).unwrap();
|
||||
app.scene
|
||||
.add_entity("quad", "quad_mesh", "basic_material")
|
||||
.unwrap();
|
||||
|
||||
app.run(MonQuad)
|
||||
}
|
||||
|
||||
+10
-2
@@ -68,8 +68,8 @@ impl App {
|
||||
// Rendering logic
|
||||
let frame = self.context.get_next_frame();
|
||||
|
||||
// On appelle le render() de l'utilisateur
|
||||
handler.render(&mut self);
|
||||
// On appelle le render() de l'utilisateur (reçoit la frame courante)
|
||||
handler.render(&mut self, &frame);
|
||||
// On présente automatiquement
|
||||
self.renderer.present(frame);
|
||||
}
|
||||
@@ -84,6 +84,14 @@ impl App {
|
||||
})
|
||||
.map_err(|_| WsgError::WindowSystem)
|
||||
}
|
||||
|
||||
/// Renders every entity in `self.scene` into the given color view in a single batched render pass.
|
||||
/// Called automatically each frame by the default `AppHandler::render`, or manually by users
|
||||
/// who override `render` to control drawing themselves.
|
||||
/// Inputs: view — the frame's texture view acting as the color attachment target.
|
||||
pub fn render_scene(&self, view: &wgpu::TextureView) {
|
||||
self.renderer.render_scene(view, &self.scene);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for constructing a configured `App` instance with custom title and dimensions.
|
||||
|
||||
+54
-12
@@ -21,6 +21,7 @@
|
||||
use crate::core::Context;
|
||||
use crate::core::Frame;
|
||||
use crate::resources::{Material, Mesh};
|
||||
use crate::scene::Scene;
|
||||
|
||||
/// The Executor layer of the architecture. Holds shared references to Device and Queue from Context,
|
||||
/// plus the surface texture format. Executes WGPU rendering commands by binding Materials and Meshes
|
||||
@@ -80,18 +81,40 @@ impl Renderer {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&material.pipeline);
|
||||
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);
|
||||
} else {
|
||||
render_pass.draw(0..mesh.num_vertices, 0..1);
|
||||
draw_entity(&mut render_pass, mesh, material);
|
||||
}
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
||||
/// Renders every entity in `scene` into the given color view within a single batched render pass.
|
||||
/// This avoids allocating a separate encoder and render pass per entity (which the low-level
|
||||
/// `render` does), minimizing GPU submissions. Called automatically each frame by the default
|
||||
/// `AppHandler::render` through `App::render_scene`.
|
||||
/// Inputs: view — the frame's texture view color attachment; scene — the scene whose entities are drawn.
|
||||
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("scene encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("scene render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for (_label, mesh, material) in scene.iter_entities() {
|
||||
draw_entity(&mut render_pass, mesh, material);
|
||||
}
|
||||
}
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
@@ -116,3 +139,22 @@ impl Renderer {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds a Material pipeline and Mesh buffers into an active render pass and issues the draw call.
|
||||
/// Shared by `Renderer::render` and `Renderer::render_scene` to avoid duplicated draw logic.
|
||||
/// Draws indexed geometry when an index buffer exists, otherwise falls back to a non-indexed draw.
|
||||
/// Inputs: pass (active render pass), mesh (geometry to draw), material (pipeline to bind).
|
||||
fn draw_entity(pass: &mut wgpu::RenderPass<'_>, mesh: &Mesh, material: &Material) {
|
||||
if mesh.num_vertices == 0 {
|
||||
// No vertices — nothing to render.
|
||||
return;
|
||||
}
|
||||
pass.set_pipeline(&material.pipeline);
|
||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
if let Some(index_buffer) = &mesh.index_buffer {
|
||||
pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
||||
pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
|
||||
} else {
|
||||
pass.draw(0..mesh.num_vertices, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -17,18 +17,23 @@
|
||||
//! the engine "brick by brick" through direct Context/PipelineCache/Renderer manipulation if needed.
|
||||
|
||||
use crate::app::App;
|
||||
use crate::core::Frame;
|
||||
|
||||
/// Trait defining user-provided game logic injected into the render loop at two callback points.
|
||||
/// Users implement this trait to define what happens per-frame: update (pre-render logic) and
|
||||
/// render (draw call execution). Default implementations provide empty update for convenience.
|
||||
/// render (draw call execution). Default implementations provide empty update and automatic
|
||||
/// scene rendering for convenience.
|
||||
pub trait AppHandler {
|
||||
/// Called once per frame before rendering begins. Used for physics updates, input processing,
|
||||
/// entity management, and any other pre-render logic. Default implementation does nothing.
|
||||
/// Inputs: _app — mutable reference to the App facade providing access to all subsystems.
|
||||
fn update(&mut self, _app: &mut App) {}
|
||||
/// Called during each RedrawRequested event after frame acquisition. Used for executing draw calls
|
||||
/// by iterating Scene entities and calling app.renderer.render(view, mesh, material) per entity.
|
||||
/// Must be implemented — called every frame that needs rendering.
|
||||
/// Inputs: app — mutable reference to the App facade providing access to all subsystems.
|
||||
fn render(&mut self, app: &mut App);
|
||||
/// Called during each RedrawRequested event after frame acquisition, receiving the current frame.
|
||||
/// Used for custom draw call execution. Default implementation renders the whole scene
|
||||
/// automatically (`app.render_scene(frame.view())`), so most users don't need to override it.
|
||||
/// Advanced users override this method to control drawing manually.
|
||||
/// Inputs: app — mutable reference to the App facade; frame — the acquired frame exposing its view.
|
||||
fn render(&mut self, app: &mut App, frame: &Frame) {
|
||||
app.render_scene(frame.view());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,11 +31,11 @@
|
||||
pub mod app;
|
||||
pub mod core;
|
||||
pub mod handler;
|
||||
pub mod math;
|
||||
pub mod pipeline;
|
||||
pub mod resources;
|
||||
pub mod scene;
|
||||
pub mod utils;
|
||||
pub mod math;
|
||||
|
||||
/// Re-export of the high-level application facade for convenient top-level access.
|
||||
/// Users create App instances via `AppBuilder`, then call `.run(handler)` to start the application.
|
||||
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
//! - `geometry.rs`: Defines the `Geometry` struct for mesh data storage
|
||||
//! - `camera.rs`: Defines the `Camera` struct and view/projection matrix calculations
|
||||
|
||||
pub mod transform;
|
||||
pub mod geometry;
|
||||
pub mod transform;
|
||||
|
||||
// Re-exports
|
||||
pub use transform::Transform;
|
||||
pub use geometry::Geometry;
|
||||
pub use transform::Transform;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//! - `Transform`: Core struct for position/rotation/scale
|
||||
//! - `to_matrix()`: Converts transform to a 4x4 matrix
|
||||
|
||||
use glam::{Vec3, Quat, Mat4};
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
|
||||
/// Represents a 3D transformation with translation, rotation, and scale.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
||||
Reference in New Issue
Block a user