vertex +normals

This commit is contained in:
Jérôme Bousquié
2026-07-06 12:31:35 +02:00
parent 6c94fc96d6
commit 47851b8f61
3 changed files with 108 additions and 12 deletions
+64 -5
View File
@@ -2,16 +2,75 @@ 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::context::Context; use wsg_lib::context::Context;
use wsg_lib::material::Material;
use wsg_lib::mesh::Mesh;
use wsg_lib::pipeline_cache::PipelineCache;
use wsg_lib::renderer::Renderer;
fn main() { fn main() {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
// Utilisation de pollster pour le bloc async // Initialisation contexte matériel (device, queue, surface) Utilisation de pollster pour le bloc async
let context = pollster::block_on(Context::new(window.clone())); let context = pollster::block_on(Context::new(window.clone()));
match context { // Initialisation des briques de rendu
Ok(_) => println!("Succès : WGPU context initialisé !"), let mut cache = PipelineCache::new();
Err(e) => eprintln!("Erreur lors de l'initialisation : {:?}", e), cache
} .register_shader("basic", "assets/shaders/basic.wgsl")
.unwrap();
let renderer = Renderer::new(&context, &cache);
// Creation d'un material (charge le basic shader via le cache)
let material = Material::new(&context.device, context.config.format, "basic", &mut cache);
// Creation d'un mesh
// 1. Définition des sommets (avec position et couleur pour l'interpolation)
let vertices = [
Vertex {
position: [-0.5, 0.5, 0.0],
color: [1.0, 0.0, 0.0],
}, // Haut-Gauche (Rouge)
Vertex {
position: [0.5, 0.5, 0.0],
color: [0.0, 1.0, 0.0],
}, // Haut-Droite (Vert)
Vertex {
position: [0.5, -0.5, 0.0],
color: [0.0, 0.0, 1.0],
}, // Bas-Droite (Bleu)
Vertex {
position: [-0.5, -0.5, 0.0],
color: [1.0, 1.0, 1.0],
}, // Bas-Gauche (Blanc)
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let mesh = Mesh::new(&context.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,
..
} => {
// Acquisition de la cible de rendu
let frame = context.surface.get_current_texture().unwrap();
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
// Orchestration du rendu
renderer.render(&context.device, &context.queue, &view, &mesh, &material);
// Presentation de l'image
frame.present();
}
_ => (),
}
})
.unwrap();
} }
+37 -4
View File
@@ -24,6 +24,7 @@ use std::sync::Arc;
pub struct PipelineCache { pub struct PipelineCache {
/// Cached pipelines keyed by their shader identifier string. /// Cached pipelines keyed by their shader identifier string.
pipelines: HashMap<String, Arc<wgpu::RenderPipeline>>, pipelines: HashMap<String, Arc<wgpu::RenderPipeline>>,
shader_paths: HashMap<String, String>,
} }
impl PipelineCache { impl PipelineCache {
@@ -31,8 +32,31 @@ impl PipelineCache {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
pipelines: HashMap::new(), pipelines: HashMap::new(),
shader_paths: HashMap::new(),
} }
} }
/// Enregistre un chemin de shader associé à un ID.
/// Renvoie Ok(id) si réussi, ou une erreur si l'ID existe déjà.
pub fn register_shader(&mut self, id: &str, path: &str) -> Result<String, String> {
if self.shader_paths.contains_key(id) {
return Err(format!("ID '{}' already exists.", id));
}
self.shader_paths.insert(id.to_string(), path.to_string());
Ok(id.to_string())
}
/// Supprime un ID et son chemin associé.
/// Renvoie Ok(id) si réussi, ou une erreur si l'ID est inconnu.
pub fn unregister_shader(&mut self, id: &str) -> Result<String, String> {
if self.shader_paths.remove(id).is_none() {
return Err(format!("ID '{}' does not exist.", id));
}
// Optionnel : tu pourrais aussi supprimer le pipeline compilé du cache
// si tu veux libérer la mémoire GPU immédiatement :
self.pipelines.remove(id);
Ok(id.to_string())
}
/// Retrieves a cached RenderPipeline by shader_id, or creates one on-demand if not present. /// Retrieves a cached RenderPipeline by shader_id, or creates one on-demand if not present.
/// Inputs: device (GPU command source), format (surface texture format for fragment output), /// Inputs: device (GPU command source), format (surface texture format for fragment output),
@@ -50,8 +74,12 @@ impl PipelineCache {
} }
// Step 2: Compile a new pipeline — loads shader and builds the GPU render pipeline // Step 2: Compile a new pipeline — loads shader and builds the GPU render pipeline
// Note: In production you would load shaders specific to shader_id from files or embedded resources. let path = self
let shader = self.load_shader(device, shader_id); .shader_paths
.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 pipeline = Self::build_pipeline(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
@@ -97,11 +125,16 @@ impl PipelineCache {
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 12, offset: 12,
shader_location: 1, shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
}, // normal
wgpu::VertexAttribute {
offset: 24,
shader_location: 2,
format: wgpu::VertexFormat::Float32x2, format: wgpu::VertexFormat::Float32x2,
}, // uv }, // uv
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 20, offset: 32,
shader_location: 2, shader_location: 3,
format: wgpu::VertexFormat::Float32x4, format: wgpu::VertexFormat::Float32x4,
}, // color }, // color
], ],
+7 -3
View File
@@ -8,15 +8,19 @@
//! - `#[repr(C)]` ensures fields are laid out contiguously without Rust padding reordering, matching C ABI. //! - `#[repr(C)]` ensures fields are laid out contiguously without Rust padding reordering, matching C ABI.
//! - `bytemuck::Pod + bytemuck::Zeroable` enables safe `cast_slice()` conversion for GPU buffer uploads. //! - `bytemuck::Pod + bytemuck::Zeroable` enables safe `cast_slice()` conversion for GPU buffer uploads.
/// Per-vertex attribute tuple: position (3D), texture coordinate (2D), color (RGBA). /// Per-vertex attribute tuple: position (3D), normal (3D), texture coordinate (2D), color (RGBA).
/// Must match the vertex buffer layout in PipelineCache::build_pipeline() byte-for-byte. /// Must match the vertex buffer layout in PipelineCache::build_pipeline() byte-for-byte.
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex { pub struct Vertex {
/// XYZ coordinates of this vertex in world space. Offset: 0 bytes. /// XYZ coordinates of this vertex in world space. Offset: 0 bytes.
pub position: [f32; 3], pub position: [f32; 3],
/// UV texture coordinates. Offset: 12 bytes (after 3 × f32 = 12 bytes). /// XYZ coordinates of the vertex normal. Offset: 12 bytes.
pub normal: [f32; 3],
/// UV texture coordinates. Offset: 24 bytes
pub uv: [f32; 2], pub uv: [f32; 2],
/// RGBA color values. Offset: 20 bytes (after 5 × f32 = 20 bytes). /// RGBA color values. Offset: 32 bytes.
pub color: [f32; 4], pub color: [f32; 4],
} }
// Default values for stability