149 lines
5.9 KiB
Rust
149 lines
5.9 KiB
Rust
//! Low-level workflow: direct use of `Context`, `Renderer`, `PipelineCache`, `Mesh` and
|
|
//! `Material`, bypassing the `App` facade. Renders a flat quad (shader `standard` **unlit**) via the
|
|
//! winit 0.30 loop (`EventLoop::run_app` + `ApplicationHandler`). The window and the GPU are created
|
|
//! in `resumed()`, as required by winit 0.30 and the migration done in `app.rs`. Since Step 8
|
|
//! (DRAFT 8.5) the mesh is built via `Mesh::from_geometry(device, Arc<Geometry>, None)` from
|
|
//! a `Geometry` (positions + colors per vertex) instead of `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;
|
|
|
|
/// Low-level application: holds the GPU objects + window, all created in `resumed`.
|
|
struct App {
|
|
/// System window, shared via Arc (as in app.rs).
|
|
window: Option<Arc<Window>>,
|
|
/// GPU context (Instance, Surface, Adapter, Device, Queue).
|
|
context: Option<Context>,
|
|
/// Execution layer that submits draw calls.
|
|
renderer: Option<Renderer>,
|
|
/// Shader/pipeline cache.
|
|
cache: Option<PipelineCache>,
|
|
/// Quad material (pipeline).
|
|
material: Option<Material>,
|
|
/// Quad mesh (vertices + indices).
|
|
mesh: Option<Mesh>,
|
|
}
|
|
|
|
impl ApplicationHandler for App {
|
|
/// Creates the window then the GPU, and builds the mesh/material. Runs once at startup.
|
|
/// Redundant `resumed` creating again? Double protection via `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("GPU init failed");
|
|
|
|
// Surface configuration and format retrieval
|
|
let format = context
|
|
.configure(&context.adapter, 800, 600)
|
|
.expect("configuration failed");
|
|
|
|
// 2. Renderer initialization (it retrieves everything it needs)
|
|
let device = Arc::new(context.device.clone());
|
|
let mut cache = PipelineCache::new(device, context.queue.clone());
|
|
cache
|
|
.register_shader("standard", utils::STANDARD_SHADER_PATH)
|
|
.unwrap();
|
|
|
|
// Flat 2D rendering: `standard` in unlit mode (the frame+object bind groups are set by
|
|
// draw_entity, the default frame matrix is the identity → NDC positions unchanged).
|
|
let mut renderer = Renderer::new(&context, format, 800, 600);
|
|
renderer.set_unlit(true);
|
|
|
|
// 3. Material: uses renderer.device() and renderer.format()
|
|
let material = Material::new(renderer.format(), "standard", &mut cache);
|
|
|
|
// Mesh: uses the renderer's device. Since Step 8 the mesh is built from a
|
|
// `Geometry` (positions + colors per vertex) via `Mesh::from_geometry` — the mesh also keeps
|
|
// the `Arc<Geometry>` on the CPU side (retention D5).
|
|
let geometry = Geometry::new(vec![
|
|
// Position (x,y,z) | Color (r,g,b,a) — normals/UVs default 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], // top-left (red)
|
|
[0.0, 1.0, 0.0, 1.0], // top-right (green)
|
|
[0.0, 0.0, 1.0, 1.0], // bottom-right (blue)
|
|
[1.0, 1.0, 0.0, 1.0], // bottom-left (yellow)
|
|
])
|
|
.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);
|
|
}
|
|
|
|
/// Each frame, requests a redraw for continuous rendering (animation).
|
|
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
|
if let Some(window) = &self.window {
|
|
window.request_redraw();
|
|
}
|
|
}
|
|
|
|
/// Window event dispatch: RedrawRequested renders then presents, CloseRequested exits.
|
|
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. Render (no more useless device/queue arguments)
|
|
renderer.render(frame.view(), mesh, material);
|
|
|
|
// 2. Present
|
|
renderer.present(frame);
|
|
}
|
|
}
|
|
}
|
|
winit::event::WindowEvent::CloseRequested => {
|
|
event_loop.exit(); // this is where you ask the loop to stop
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
println!("Current directory: {:?}", 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();
|
|
}
|