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
|
//! `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
|
//! update → render → present. La scène se rend automatiquement : la méthode `render()` par défaut
|
||||||
//! (README, Roadmap étape 1) : `render()` est donc vide pour l'instant.
|
//! 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::app::AppBuilder;
|
||||||
|
use wsg_lib::resources::{Material, Mesh, Vertex};
|
||||||
use wsg_lib::utils::WsgError;
|
use wsg_lib::utils::WsgError;
|
||||||
use wsg_lib::{App, AppHandler};
|
|
||||||
|
|
||||||
struct MonQuad;
|
struct MonQuad;
|
||||||
|
|
||||||
impl AppHandler for MonQuad {
|
impl AppHandler for MonQuad {}
|
||||||
fn render(&mut self, _app: &mut App) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pollster::main]
|
#[pollster::main]
|
||||||
async fn main() -> Result<(), WsgError> {
|
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)
|
app.run(MonQuad)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -68,8 +68,8 @@ impl App {
|
|||||||
// Rendering logic
|
// Rendering logic
|
||||||
let frame = self.context.get_next_frame();
|
let frame = self.context.get_next_frame();
|
||||||
|
|
||||||
// On appelle le render() de l'utilisateur
|
// On appelle le render() de l'utilisateur (reçoit la frame courante)
|
||||||
handler.render(&mut self);
|
handler.render(&mut self, &frame);
|
||||||
// On présente automatiquement
|
// On présente automatiquement
|
||||||
self.renderer.present(frame);
|
self.renderer.present(frame);
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,14 @@ impl App {
|
|||||||
})
|
})
|
||||||
.map_err(|_| WsgError::WindowSystem)
|
.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.
|
/// 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::Context;
|
||||||
use crate::core::Frame;
|
use crate::core::Frame;
|
||||||
use crate::resources::{Material, Mesh};
|
use crate::resources::{Material, Mesh};
|
||||||
|
use crate::scene::Scene;
|
||||||
|
|
||||||
/// The Executor layer of the architecture. Holds shared references to Device and Queue from Context,
|
/// 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
|
/// plus the surface texture format. Executes WGPU rendering commands by binding Materials and Meshes
|
||||||
@@ -80,18 +81,40 @@ impl Renderer {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
render_pass.set_pipeline(&material.pipeline);
|
draw_entity(&mut render_pass, mesh, material);
|
||||||
if mesh.num_vertices > 0 {
|
}
|
||||||
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
} else {
|
}
|
||||||
// If no vertices, skip drawing entirely (nothing to render)
|
|
||||||
return;
|
/// 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
|
||||||
if let Some(index_buffer) = &mesh.index_buffer {
|
/// `render` does), minimizing GPU submissions. Called automatically each frame by the default
|
||||||
render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
/// `AppHandler::render` through `App::render_scene`.
|
||||||
render_pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
|
/// Inputs: view — the frame's texture view color attachment; scene — the scene whose entities are drawn.
|
||||||
} else {
|
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
|
||||||
render_pass.draw(0..mesh.num_vertices, 0..1);
|
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()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
@@ -116,3 +139,22 @@ impl Renderer {
|
|||||||
self.format
|
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.
|
//! the engine "brick by brick" through direct Context/PipelineCache/Renderer manipulation if needed.
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
use crate::core::Frame;
|
||||||
|
|
||||||
/// Trait defining user-provided game logic injected into the render loop at two callback points.
|
/// 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
|
/// 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 {
|
pub trait AppHandler {
|
||||||
/// Called once per frame before rendering begins. Used for physics updates, input processing,
|
/// 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.
|
/// 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.
|
/// Inputs: _app — mutable reference to the App facade providing access to all subsystems.
|
||||||
fn update(&mut self, _app: &mut App) {}
|
fn update(&mut self, _app: &mut App) {}
|
||||||
/// Called during each RedrawRequested event after frame acquisition. Used for executing draw calls
|
/// Called during each RedrawRequested event after frame acquisition, receiving the current frame.
|
||||||
/// by iterating Scene entities and calling app.renderer.render(view, mesh, material) per entity.
|
/// Used for custom draw call execution. Default implementation renders the whole scene
|
||||||
/// Must be implemented — called every frame that needs rendering.
|
/// automatically (`app.render_scene(frame.view())`), so most users don't need to override it.
|
||||||
/// Inputs: app — mutable reference to the App facade providing access to all subsystems.
|
/// Advanced users override this method to control drawing manually.
|
||||||
fn render(&mut self, app: &mut App);
|
/// 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 app;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
|
pub mod math;
|
||||||
pub mod pipeline;
|
pub mod pipeline;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod math;
|
|
||||||
|
|
||||||
/// Re-export of the high-level application facade for convenient top-level access.
|
/// 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.
|
/// Users create App instances via `AppBuilder`, then call `.run(handler)` to start the application.
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ pub struct Geometry {
|
|||||||
pub uvs: Option<Vec<[f32; 2]>>,
|
pub uvs: Option<Vec<[f32; 2]>>,
|
||||||
/// Optional indices for indexed rendering
|
/// Optional indices for indexed rendering
|
||||||
pub indices: Option<Vec<u16>>,
|
pub indices: Option<Vec<u16>>,
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -15,9 +15,9 @@
|
|||||||
//! - `geometry.rs`: Defines the `Geometry` struct for mesh data storage
|
//! - `geometry.rs`: Defines the `Geometry` struct for mesh data storage
|
||||||
//! - `camera.rs`: Defines the `Camera` struct and view/projection matrix calculations
|
//! - `camera.rs`: Defines the `Camera` struct and view/projection matrix calculations
|
||||||
|
|
||||||
pub mod transform;
|
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
pub mod transform;
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
|
pub use geometry::Geometry;
|
||||||
pub use transform::Transform;
|
pub use transform::Transform;
|
||||||
pub use geometry::Geometry;
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
//! - `Transform`: Core struct for position/rotation/scale
|
//! - `Transform`: Core struct for position/rotation/scale
|
||||||
//! - `to_matrix()`: Converts transform to a 4x4 matrix
|
//! - `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.
|
/// Represents a 3D transformation with translation, rotation, and scale.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
@@ -42,4 +42,4 @@ impl Transform {
|
|||||||
pub fn to_matrix(&self) -> Mat4 {
|
pub fn to_matrix(&self) -> Mat4 {
|
||||||
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
|
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user