//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et //! `Material`, contournant la façade `App`. Rendu d'un quad plat (shader `standard` **unlit**) via la //! boucle winit 0.30 (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés //! dans `resumed()`, comme l'exigent winit 0.30 et la migration faite dans `app.rs`. Depuis l'Étape 8 //! (DRAFT 8.5) le mesh est construit via `Mesh::from_geometry(device, Arc, None)` à partir //! d'une `Geometry` (positions + couleurs par sommet) au lieu de `Mesh::new(device, &[Vertex], ..)`. use std::sync::Arc; use winit::application::ApplicationHandler; use winit::dpi::LogicalSize; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::{Window, WindowAttributes}; use wsg_lib::core::Context; use wsg_lib::core::Frame; use wsg_lib::core::Renderer; use wsg_lib::pipeline::PipelineCache; use wsg_lib::resources::{Geometry, Material, Mesh}; use wsg_lib::utils; /// Application bas-niveau : détient les objets GPU + window, tous créés dans `resumed`. struct App { /// Fenêtre système, partagée via Arc (comme dans app.rs). window: Option>, /// Contexte GPU (Instance, Surface, Adapter, Device, Queue). context: Option, /// Couche d'exécution qui soumet les draw calls. renderer: Option, /// Cache de shaders/pipelines. cache: Option, /// Matériau (pipeline) du quad. material: Option, /// Mesh du quad (sommets + indices). mesh: Option, } impl ApplicationHandler for App { /// Crée la fenêtre puis le GPU, et construit le mesh/matériau. Exécuté une fois au démarrage. /// Redondant `resumed` pour créer à nouveau ? double protection par `self.context.is_some()`. fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.context.is_some() { return; } event_loop.set_control_flow(ControlFlow::Poll); let attrs = WindowAttributes::default() .with_title("WSG Manual") .with_inner_size(LogicalSize::new(800.0, 600.0)); let window = Arc::new(event_loop.create_window(attrs).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("standard", utils::STANDARD_SHADER_PATH) .unwrap(); // Rendu 2D plat : `standard` en mode unlit (les bind groups frame+object sont posés par // draw_entity, la matrice frame par défaut est l'identité → positions NDC inchangées). let mut renderer = Renderer::new(&context, format); renderer.set_unlit(true); // 3. Material : On utilise renderer.device() et renderer.format() let material = Material::new(renderer.format(), "standard", &mut cache); // Mesh : on utilise le device du renderer. Depuis l'Étape 8 le mesh est construit depuis une // `Geometry` (positions + couleurs par sommet) via `Mesh::from_geometry` — le mesh garde aussi // l'`Arc` côté CPU (rétention D5). let geometry = Geometry::new(vec![ // Position (x,y,z) | Couleur (r,g,b,a) — normales/UV par défaut via to_vertices [-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0], ]) .with_colors(vec![ [1.0, 0.0, 0.0, 1.0], // Haut-Gauche (Rouge) [0.0, 1.0, 0.0, 1.0], // Haut-Droite (Vert) [0.0, 0.0, 1.0, 1.0], // Bas-Droite (Bleu) [1.0, 1.0, 0.0, 1.0], // Bas-Gauche (Jaune) ]) .with_indices(vec![0, 1, 2, 0, 2, 3]); let mesh = Mesh::from_geometry(renderer.device(), Arc::new(geometry), None); self.window = Some(window); self.context = Some(context); self.renderer = Some(renderer); self.cache = Some(cache); self.material = Some(material); self.mesh = Some(mesh); } /// À chaque frame, demande un redessin pour un rendu continu (animation). fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { if let Some(window) = &self.window { window.request_redraw(); } } /// Dispatch des événements de fenêtre : RedrawRequested rend puis présente, CloseRequested quitte. fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { match event { winit::event::WindowEvent::RedrawRequested => { if let (Some(context), Some(renderer), Some(mesh), Some(material)) = (&self.context, &self.renderer, &self.mesh, &self.material) { 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::WindowEvent::CloseRequested => { event_loop.exit(); // C'est ici que tu demandes à la boucle de s'arrêter } _ => (), } } } fn main() { println!( "Répertoire courant : {:?}", std::env::current_dir().unwrap() ); let event_loop = EventLoop::new().unwrap(); let mut app = App { window: None, context: None, renderer: None, cache: None, material: None, mesh: None, }; event_loop.run_app(&mut app).unwrap(); }