modif appbuilder
This commit is contained in:
@@ -11,3 +11,6 @@ wgpu = "30.0.0" # Vérifiez la version la plus récente
|
||||
winit = "0.29" # For window management — pinned to match examples
|
||||
thiserror = "2"
|
||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pollster = "0.4.0"
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
use std::sync::Arc;
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::WindowBuilder;
|
||||
use wsg_lib::core::Context;
|
||||
use wsg_lib::core::Frame;
|
||||
use wsg_lib::core::Renderer;
|
||||
use wsg_lib::pipeline::PipelineCache;
|
||||
use wsg_lib::resources::Material;
|
||||
use wsg_lib::resources::Mesh;
|
||||
use wsg_lib::resources::Vertex;
|
||||
use wsg_lib::utils;
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"Répertoire courant : {:?}",
|
||||
std::env::current_dir().unwrap()
|
||||
);
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
|
||||
|
||||
// 1. Initialisation
|
||||
let context = pollster::block_on(Context::new(window.clone())).expect("Échec init GPU");
|
||||
|
||||
// Configuration de la surface et récupération du format
|
||||
let format = context
|
||||
.configure(&context.adapter, 800, 600)
|
||||
.expect("Échec configuration");
|
||||
|
||||
// 2. Initialisation du Renderer (Il récupère tout ce dont il a besoin)
|
||||
let device = Arc::new(context.device.clone());
|
||||
let mut cache = PipelineCache::new(device);
|
||||
cache
|
||||
.register_shader("basic", utils::BASIC_SHADER_PATH)
|
||||
.unwrap();
|
||||
|
||||
let renderer = Renderer::new(&context, format);
|
||||
|
||||
// 3. Material : On utilise renderer.device() et renderer.format()
|
||||
let material = Material::new(renderer.format(), "basic", &mut cache);
|
||||
|
||||
// Mesh : On utilise le device du renderer
|
||||
let vertices = [
|
||||
// Position (x,y,z) | Normale (x,y,z) | UV (u,v) | Couleur (r,g,b,a)
|
||||
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 = Mesh::new(renderer.device(), &vertices, Some(&indices));
|
||||
|
||||
// Render loop
|
||||
event_loop
|
||||
.run(|event, elwt| {
|
||||
match event {
|
||||
winit::event::Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
if let Some(frame) = Frame::try_new(&context.surface) {
|
||||
// 1. Rendu (plus d'arguments device/queue inutiles)
|
||||
renderer.render(frame.view(), &mesh, &material);
|
||||
|
||||
// 2. Présentation
|
||||
renderer.present(frame);
|
||||
}
|
||||
}
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
elwt.exit(); // C'est ici que tu demandes à la boucle de s'arrêter
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use wsg_lib::app::{App, AppHandler};
|
||||
use wsg_lib::resources::{Material, Mesh, Vertex};
|
||||
use wsg_lib::utils;
|
||||
|
||||
// 1. On définit notre "Jeu" qui implémente le comportement
|
||||
struct MonQuad {
|
||||
mesh: Mesh,
|
||||
material: Material,
|
||||
}
|
||||
|
||||
impl AppHandler for MonQuad {
|
||||
fn render(&mut self, app: &mut App) {
|
||||
// Le rendu devient simple : on accède aux outils via &mut app
|
||||
if let Some(frame) = app.context.get_next_frame() {
|
||||
app.renderer
|
||||
.render(frame.view(), &self.mesh, &self.material);
|
||||
app.renderer.present(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> Result<(), wsg_lib::utils::WsgError> {
|
||||
// 2. Initialisation via le Builder
|
||||
let mut app = App::builder()
|
||||
.title("Exemple Simple")
|
||||
.size(800, 600)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
// 3. Setup des ressources (déclaration)
|
||||
app.cache
|
||||
.register_shader("basic", utils::BASIC_SHADER_PATH)
|
||||
.unwrap();
|
||||
|
||||
let vertices: [Vertex; 4] = [
|
||||
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],
|
||||
},
|
||||
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],
|
||||
},
|
||||
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],
|
||||
},
|
||||
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],
|
||||
},
|
||||
];
|
||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
||||
|
||||
let mesh = Mesh::new(app.context.device(), &vertices, Some(&indices));
|
||||
let material = Material::new(app.renderer.format(), "basic", &mut app.cache);
|
||||
|
||||
// 4. Lancement de la boucle (la magie opère ici)
|
||||
let handler = MonQuad { mesh, material };
|
||||
app.run(handler)
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
use crate::AppHandler;
|
||||
use crate::core::{Context, Renderer};
|
||||
use crate::pipeline::PipelineCache;
|
||||
use crate::scene::Scene;
|
||||
use crate::utils::WsgError;
|
||||
use crate::utils::conf::{APP_DEFAULT_HEIGHT, APP_DEFAULT_TITLE, APP_DEFAULT_WIDTH};
|
||||
use std::sync::Arc;
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::Window;
|
||||
|
||||
pub struct App {
|
||||
pub context: Context,
|
||||
pub renderer: Renderer,
|
||||
pub event_loop: Option<EventLoop<()>>, // On met en Option pour pouvoir faire .take() facilement
|
||||
pub cache: PipelineCache,
|
||||
pub scene: Scene,
|
||||
pub window: Arc<Window>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run<H: AppHandler + 'static>(mut self, mut handler: H) -> Result<(), WsgError> {
|
||||
// On extrait l'event_loop de manière sûre grâce au Option
|
||||
let event_loop = self.event_loop.take().ok_or(WsgError::WindowSystem)?; // Erreur si déjà pris
|
||||
event_loop
|
||||
.run(move |event, elwt| {
|
||||
match event {
|
||||
winit::event::Event::AboutToWait => {
|
||||
// update logic
|
||||
handler.update(&mut self);
|
||||
self.window.request_redraw();
|
||||
}
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
// render logic
|
||||
handler.render(&mut self);
|
||||
}
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
elwt.exit();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.map_err(|_| WsgError::WindowSystem)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppBuilder {
|
||||
title: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl AppBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
title: APP_DEFAULT_TITLE.to_string(),
|
||||
width: APP_DEFAULT_WIDTH,
|
||||
height: APP_DEFAULT_HEIGHT,
|
||||
}
|
||||
}
|
||||
pub fn title(mut self, title: &str) -> Self {
|
||||
self.title = title.to_string();
|
||||
self
|
||||
}
|
||||
pub fn size(mut self, width: u32, height: u32) -> Self {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
pub async fn build(self) -> Result<App, WsgError> {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = Arc::new(
|
||||
winit::window::WindowBuilder::new()
|
||||
.with_title(&self.title)
|
||||
.build(&event_loop)
|
||||
.map_err(|_| WsgError::WindowSystem)?,
|
||||
);
|
||||
let context = Context::new(window.clone()).await?;
|
||||
let device = Arc::new(context.device.clone());
|
||||
let format = context
|
||||
.configure(&context.adapter, self.width, self.height)
|
||||
.map_err(|_| WsgError::SurfaceIncompatible)?;
|
||||
let renderer = Renderer::new(&context, format);
|
||||
let cache = PipelineCache::new(device);
|
||||
let scene = Scene::new();
|
||||
|
||||
Ok(App {
|
||||
context,
|
||||
renderer,
|
||||
cache,
|
||||
scene,
|
||||
event_loop: Some(event_loop),
|
||||
window,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::app::App;
|
||||
|
||||
pub trait AppHandler {
|
||||
fn update(&mut self, _app: &mut App) {}
|
||||
fn render(&mut self, app: &mut App);
|
||||
}
|
||||
@@ -16,8 +16,12 @@
|
||||
//! use wsg_lib::resources::{Mesh, Material, Vertex};
|
||||
//! use wsg_lib::utils::BASIC_SHADER;
|
||||
//! ```
|
||||
pub mod app;
|
||||
pub mod core;
|
||||
pub mod handler;
|
||||
pub mod pipeline;
|
||||
pub mod resources;
|
||||
pub mod scene;
|
||||
pub mod utils;
|
||||
|
||||
pub use handler::AppHandler;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
//! - wgpu 30 requires `compilation_options` in VertexState/FragmentState and `depth_slice` in color attachments.
|
||||
//! - **Batching**: Multiple Materials with the same shader_id share one pipeline, enabling material-level batching in Renderer.
|
||||
|
||||
use crate::utils::BASIC_SHADER;
|
||||
use crate::resources::Vertex;
|
||||
use crate::utils::BASIC_SHADER;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -24,6 +24,7 @@ use std::sync::Arc;
|
||||
/// Shader pipeline cache: maps (shader_id, format) keys to compiled RenderPipelines.
|
||||
/// Ensures each unique shader+format combination is compiled at most once; subsequent requests return cached instances.
|
||||
pub struct PipelineCache {
|
||||
device: Arc<wgpu::Device>,
|
||||
/// Cached pipelines keyed by their shader identifier string. Multiple Materials sharing the same ID share one Arc-wrapped pipeline.
|
||||
pipelines: HashMap<String, Arc<wgpu::RenderPipeline>>,
|
||||
/// Maps shader IDs to file paths on disk for WGSL loading in `load_shader()`.
|
||||
@@ -33,8 +34,9 @@ pub struct PipelineCache {
|
||||
impl PipelineCache {
|
||||
/// Creates an empty pipeline cache with no pre-loaded shaders or pipelines.
|
||||
/// Called at application startup before any Material creation. Shader paths must be registered via register_shader() first.
|
||||
pub fn new() -> Self {
|
||||
pub fn new(device: Arc<wgpu::Device>) -> Self {
|
||||
Self {
|
||||
device,
|
||||
pipelines: HashMap::new(),
|
||||
// Maps shader IDs to file paths on disk for WGSL loading in load_shader().
|
||||
// When a path exists, it reads from it; otherwise falls back to BASIC_SHADER constant.
|
||||
@@ -70,7 +72,6 @@ impl PipelineCache {
|
||||
/// Returns an Arc-wrapped RenderPipeline ready for rendering. Called by Material::new().
|
||||
pub fn get_or_create(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
shader_id: &str,
|
||||
) -> Arc<wgpu::RenderPipeline> {
|
||||
@@ -85,8 +86,8 @@ impl PipelineCache {
|
||||
.get(shader_id)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or(shader_id);
|
||||
let shader = self.load_shader(device, path);
|
||||
let pipeline = Self::build_pipeline(device, format, &shader);
|
||||
let shader = self.load_shader(&self.device, path);
|
||||
let pipeline = Self::build_pipeline(&self.device, format, &shader);
|
||||
|
||||
// Step 3: Cache the new pipeline behind Arc and return it
|
||||
let pipeline_arc = Arc::new(pipeline);
|
||||
|
||||
@@ -26,14 +26,9 @@ impl Material {
|
||||
/// Inputs: device (GPU command source for pipeline creation), format (surface texture format),
|
||||
/// shader_id (unique key into PipelineCache), cache (mutable ref for potential insertion).
|
||||
/// Returns a Material holding the Arc-wrapped pipeline. Called at scene initialization time.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
shader_id: &str,
|
||||
cache: &mut PipelineCache,
|
||||
) -> Self {
|
||||
pub fn new(format: wgpu::TextureFormat, shader_id: &str, cache: &mut PipelineCache) -> Self {
|
||||
// Request pipeline from cache — returns cached instance if already exists, creates new otherwise
|
||||
let pipeline = cache.get_or_create(device, format, shader_id);
|
||||
let pipeline = cache.get_or_create(format, shader_id);
|
||||
Self {
|
||||
shader_id: shader_id.to_string(),
|
||||
pipeline,
|
||||
|
||||
@@ -14,3 +14,7 @@ pub const BASIC_SHADER_PATH: &str = "assets/shaders/basic_shader.wgsl";
|
||||
/// The basic WGSL shader source code, embedded at compile time via `include_str!`.
|
||||
/// Serves as a fallback when `BASIC_SHADER_PATH` cannot be read at runtime.
|
||||
pub const BASIC_SHADER: &str = include_str!("../shaders/basic_shader.wgsl");
|
||||
|
||||
pub const APP_DEFAULT_TITLE: &str = "WSG App";
|
||||
pub const APP_DEFAULT_WIDTH: u32 = 800;
|
||||
pub const APP_DEFAULT_HEIGHT: u32 = 600;
|
||||
|
||||
Reference in New Issue
Block a user