fix(lib): migrate to winit 0.30 ApplicationHandler model

winit 0.30.13 removed WindowBuilder and deprecated EventLoop::run. Move
window/GPU creation into ApplicationHandler::resumed, expose AppHandler::setup
hook, switch App::run to run_app, and migrate both examples. pollster becomes a
regular dependency (used by app.rs).
This commit is contained in:
Jérôme Bousquié
2026-09-16 14:17:11 +02:00
parent bb7fab4911
commit 8eec38e55c
6 changed files with 748 additions and 457 deletions
+143 -84
View File
@@ -1,6 +1,13 @@
//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et
//! `Material`, contournant la façade `App`. Rendu d'un quad plat éclairé 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`.
use std::sync::Arc;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
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;
@@ -10,92 +17,144 @@ use wsg_lib::resources::Mesh;
use wsg_lib::resources::Vertex;
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<Arc<Window>>,
/// Contexte GPU (Instance, Surface, Adapter, Device, Queue).
context: Option<Context>,
/// Couche d'exécution qui soumet les draw calls.
renderer: Option<Renderer>,
/// Cache de shaders/pipelines.
cache: Option<PipelineCache>,
/// Matériau (pipeline) du quad.
material: Option<Material>,
/// Mesh du quad (sommets + indices).
mesh: Option<Mesh>,
}
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("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));
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 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();
let mut app = App {
window: None,
context: None,
renderer: None,
cache: None,
material: None,
mesh: None,
};
event_loop.run_app(&mut app).unwrap();
}