modif appbuilder
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
examples/target
|
examples/target
|
||||||
|
lib/examples/target
|
||||||
|
|||||||
Generated
+1
-9
@@ -2304,20 +2304,12 @@ version = "0.57.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wsg-examples"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"pollster",
|
|
||||||
"winit",
|
|
||||||
"wsg-lib",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wsg-lib"
|
name = "wsg-lib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"pollster",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["lib", "examples"]
|
members = ["lib"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|||||||
Generated
-2407
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "wsg-examples" # On change le nom du package pour éviter le conflit "examples"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wsg-lib = { path = "../lib" }
|
|
||||||
pollster = "0.4.0"
|
|
||||||
winit = "0.29" # Pinned — matches lib/ version exactly
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "demo" # Un nom de binaire qui ne risque pas de conflits
|
|
||||||
path = "main.rs" # On pointe directement vers le fichier à la racine du dossier
|
|
||||||
@@ -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
|
winit = "0.29" # For window management — pinned to match examples
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pollster = "0.4.0"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
use winit::window::WindowBuilder;
|
use winit::window::WindowBuilder;
|
||||||
use wsg_lib::utils;
|
|
||||||
use wsg_lib::core::Context;
|
use wsg_lib::core::Context;
|
||||||
use wsg_lib::core::Frame;
|
use wsg_lib::core::Frame;
|
||||||
|
use wsg_lib::core::Renderer;
|
||||||
|
use wsg_lib::pipeline::PipelineCache;
|
||||||
use wsg_lib::resources::Material;
|
use wsg_lib::resources::Material;
|
||||||
use wsg_lib::resources::Mesh;
|
use wsg_lib::resources::Mesh;
|
||||||
use wsg_lib::pipeline::PipelineCache;
|
|
||||||
use wsg_lib::core::Renderer;
|
|
||||||
use wsg_lib::resources::Vertex;
|
use wsg_lib::resources::Vertex;
|
||||||
|
use wsg_lib::utils;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!(
|
println!(
|
||||||
@@ -27,7 +27,8 @@ fn main() {
|
|||||||
.expect("Échec configuration");
|
.expect("Échec configuration");
|
||||||
|
|
||||||
// 2. Initialisation du Renderer (Il récupère tout ce dont il a besoin)
|
// 2. Initialisation du Renderer (Il récupère tout ce dont il a besoin)
|
||||||
let mut cache = PipelineCache::new();
|
let device = Arc::new(context.device.clone());
|
||||||
|
let mut cache = PipelineCache::new(device);
|
||||||
cache
|
cache
|
||||||
.register_shader("basic", utils::BASIC_SHADER_PATH)
|
.register_shader("basic", utils::BASIC_SHADER_PATH)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -35,7 +36,7 @@ fn main() {
|
|||||||
let renderer = Renderer::new(&context, format);
|
let renderer = Renderer::new(&context, format);
|
||||||
|
|
||||||
// 3. Material : On utilise renderer.device() et renderer.format()
|
// 3. Material : On utilise renderer.device() et renderer.format()
|
||||||
let material = Material::new(renderer.device(), renderer.format(), "basic", &mut cache);
|
let material = Material::new(renderer.format(), "basic", &mut cache);
|
||||||
|
|
||||||
// Mesh : On utilise le device du renderer
|
// Mesh : On utilise le device du renderer
|
||||||
let vertices = [
|
let vertices = [
|
||||||
@@ -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::resources::{Mesh, Material, Vertex};
|
||||||
//! use wsg_lib::utils::BASIC_SHADER;
|
//! use wsg_lib::utils::BASIC_SHADER;
|
||||||
//! ```
|
//! ```
|
||||||
|
pub mod app;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
|
pub mod handler;
|
||||||
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 use handler::AppHandler;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
//! - wgpu 30 requires `compilation_options` in VertexState/FragmentState and `depth_slice` in color attachments.
|
//! - 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.
|
//! - **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::resources::Vertex;
|
||||||
|
use crate::utils::BASIC_SHADER;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -24,6 +24,7 @@ use std::sync::Arc;
|
|||||||
/// Shader pipeline cache: maps (shader_id, format) keys to compiled RenderPipelines.
|
/// 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.
|
/// Ensures each unique shader+format combination is compiled at most once; subsequent requests return cached instances.
|
||||||
pub struct PipelineCache {
|
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.
|
/// 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>>,
|
pipelines: HashMap<String, Arc<wgpu::RenderPipeline>>,
|
||||||
/// Maps shader IDs to file paths on disk for WGSL loading in `load_shader()`.
|
/// Maps shader IDs to file paths on disk for WGSL loading in `load_shader()`.
|
||||||
@@ -33,8 +34,9 @@ pub struct PipelineCache {
|
|||||||
impl PipelineCache {
|
impl PipelineCache {
|
||||||
/// Creates an empty pipeline cache with no pre-loaded shaders or pipelines.
|
/// 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.
|
/// 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 {
|
Self {
|
||||||
|
device,
|
||||||
pipelines: HashMap::new(),
|
pipelines: HashMap::new(),
|
||||||
// Maps shader IDs to file paths on disk for WGSL loading in load_shader().
|
// 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.
|
// 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().
|
/// Returns an Arc-wrapped RenderPipeline ready for rendering. Called by Material::new().
|
||||||
pub fn get_or_create(
|
pub fn get_or_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
shader_id: &str,
|
shader_id: &str,
|
||||||
) -> Arc<wgpu::RenderPipeline> {
|
) -> Arc<wgpu::RenderPipeline> {
|
||||||
@@ -85,8 +86,8 @@ impl PipelineCache {
|
|||||||
.get(shader_id)
|
.get(shader_id)
|
||||||
.map(|s| s.as_str())
|
.map(|s| s.as_str())
|
||||||
.unwrap_or(shader_id);
|
.unwrap_or(shader_id);
|
||||||
let shader = self.load_shader(device, path);
|
let shader = self.load_shader(&self.device, path);
|
||||||
let pipeline = Self::build_pipeline(device, format, &shader);
|
let pipeline = Self::build_pipeline(&self.device, format, &shader);
|
||||||
|
|
||||||
// Step 3: Cache the new pipeline behind Arc and return it
|
// Step 3: Cache the new pipeline behind Arc and return it
|
||||||
let pipeline_arc = Arc::new(pipeline);
|
let pipeline_arc = Arc::new(pipeline);
|
||||||
|
|||||||
@@ -26,14 +26,9 @@ impl Material {
|
|||||||
/// Inputs: device (GPU command source for pipeline creation), format (surface texture format),
|
/// Inputs: device (GPU command source for pipeline creation), format (surface texture format),
|
||||||
/// shader_id (unique key into PipelineCache), cache (mutable ref for potential insertion).
|
/// 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.
|
/// Returns a Material holding the Arc-wrapped pipeline. Called at scene initialization time.
|
||||||
pub fn new(
|
pub fn new(format: wgpu::TextureFormat, shader_id: &str, cache: &mut PipelineCache) -> Self {
|
||||||
device: &wgpu::Device,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
shader_id: &str,
|
|
||||||
cache: &mut PipelineCache,
|
|
||||||
) -> Self {
|
|
||||||
// Request pipeline from cache — returns cached instance if already exists, creates new otherwise
|
// 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 {
|
Self {
|
||||||
shader_id: shader_id.to_string(),
|
shader_id: shader_id.to_string(),
|
||||||
pipeline,
|
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!`.
|
/// 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.
|
/// 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 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