examples/workshop.rs
Repository source snapshot · Plain text · GitHub
1//! cargo run --example workshop -- 62//! 0..7 choose a milestone; arrows orbit; Space pauses; Home resets; Escape exits.3//! --smoke creates a hidden window and presents all eight stages, then exits.4#[cfg(target_arch = "wasm32")]5fn main() {}67#[cfg(not(target_arch = "wasm32"))]8fn main() -> Result<(), Box<dyn std::error::Error>> {9 desktop::run()10}1112#[cfg(not(target_arch = "wasm32"))]13mod desktop {14 use flate2::read::GzDecoder;15 use pajama_gaussian_lab::{workshop::Frame, workshop_surface::WorkshopSurface};16 use std::{io::Read, sync::Arc, time::Instant};17 use winit::{18 application::ApplicationHandler,19 event::{ElementState, WindowEvent},20 event_loop::{ActiveEventLoop, EventLoop},21 keyboard::{Key, NamedKey},22 window::{Window, WindowId},23 };2425 struct App {26 window: Option<Arc<Window>>,27 gpu: Option<WorkshopSurface>,28 frame: Frame,29 last: Instant,30 playing: bool,31 smoke: bool,32 presented: u32,33 smoke_attempts: u32,34 error: Option<String>,35 }36 impl App {37 fn load_model(&mut self) -> Result<(), String> {38 let raw = std::fs::read("public/data/n3d-sear-steak-stg-lite.ply.gz")39 .map_err(|e| e.to_string())?;40 let mut ply = Vec::new();41 GzDecoder::new(raw.as_slice())42 .read_to_end(&mut ply)43 .map_err(|e| e.to_string())?;44 let gpu = self.gpu.as_mut().ok_or("GPU not initialized")?;45 gpu.pass.load_model(&gpu.device, &ply)46 }47 fn fail(&mut self, event_loop: &ActiveEventLoop, error: String) {48 eprintln!("{error}");49 self.error = Some(error);50 event_loop.exit();51 }52 }53 impl ApplicationHandler for App {54 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {55 if !self.smoke || self.presented >= 8 {56 return;57 }58 self.smoke_attempts += 1;59 if self.smoke_attempts > 600 {60 self.fail(61 event_loop,62 "Hidden surface did not present all stages within the smoke budget".into(),63 );64 return;65 }66 // Hidden windows need not receive OS redraw events. Drive only this67 // test's frame callback explicitly, with a bounded retry interval.68 if let Some(window) = &self.window {69 let id = window.id();70 self.window_event(event_loop, id, WindowEvent::RedrawRequested);71 }72 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(73 Instant::now() + std::time::Duration::from_millis(16),74 ));75 }76 // ANCHOR: native-create77 fn resumed(&mut self, event_loop: &ActiveEventLoop) {78 if self.window.is_some() {79 return;80 }81 let attributes = Window::default_attributes()82 .with_title("4DGS Workshop · 0–7 stages · arrows orbit · Space pause · Home reset")83 .with_inner_size(winit::dpi::LogicalSize::new(960, 640))84 .with_visible(!self.smoke);85 let window = match event_loop.create_window(attributes) {86 Ok(window) => Arc::new(window),87 Err(e) => {88 self.fail(event_loop, e.to_string());89 return;90 }91 };92 let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();93 if cfg!(target_os = "windows") {94 descriptor.backends = wgpu::Backends::DX12;95 }96 let instance = wgpu::Instance::new(descriptor);97 let surface = match instance.create_surface(window.clone()) {98 Ok(surface) => surface,99 Err(e) => {100 self.fail(event_loop, e.to_string());101 return;102 }103 };104 let size = window.inner_size();105 // Blocking is confined to desktop startup. The browser host awaits.106 self.gpu = match pollster::block_on(WorkshopSurface::new(107 instance,108 surface,109 size.width,110 size.height,111 )) {112 Ok(gpu) => {113 println!("{}", gpu.adapter_label);114 Some(gpu)115 }116 Err(e) => {117 self.fail(event_loop, e);118 return;119 }120 };121 self.window = Some(window);122 if self.frame.stage == 7 || self.smoke {123 if let Err(e) = self.load_model() {124 self.fail(event_loop, e);125 return;126 }127 }128 self.last = Instant::now();129 self.window.as_ref().unwrap().request_redraw();130 }131 // END: native-create132 fn suspended(&mut self, _: &ActiveEventLoop) {133 self.gpu = None;134 self.window = None;135 }136 // ANCHOR: native-events137 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {138 let Some(window) = self.window.clone() else {139 return;140 };141 if id != window.id() {142 return;143 }144 match event {145 WindowEvent::CloseRequested => event_loop.exit(),146 WindowEvent::Resized(size) => {147 if let Some(gpu) = &mut self.gpu {148 gpu.resize(size.width, size.height);149 }150 window.request_redraw();151 }152 WindowEvent::KeyboardInput { event, .. }153 if event.state == ElementState::Pressed =>154 {155 match event.logical_key {156 Key::Named(NamedKey::Escape) => event_loop.exit(),157 Key::Named(NamedKey::Space) => self.playing = !self.playing,158 Key::Named(NamedKey::ArrowLeft) => self.frame.yaw -= 0.15,159 Key::Named(NamedKey::ArrowRight) => self.frame.yaw += 0.15,160 Key::Named(NamedKey::ArrowUp) => {161 self.frame.pitch = (self.frame.pitch + 0.1).min(1.4)162 }163 Key::Named(NamedKey::ArrowDown) => {164 self.frame.pitch = (self.frame.pitch - 0.1).max(-1.4)165 }166 Key::Named(NamedKey::Home) => {167 self.frame = Frame {168 stage: self.frame.stage,169 ..Frame::default()170 }171 }172 Key::Character(text) => {173 if let Ok(stage @ 0..=7) = text.parse::<u32>() {174 if stage == 7 {175 if let Err(e) = self.load_model() {176 self.fail(event_loop, e);177 return;178 }179 }180 self.frame.stage = stage;181 }182 }183 _ => {}184 }185 window.request_redraw();186 }187 WindowEvent::RedrawRequested => {188 if self.smoke && self.presented >= 8 {189 return;190 }191 let now = Instant::now();192 let dt = now.duration_since(self.last).as_secs_f32().min(0.1);193 self.last = now;194 if self.playing {195 self.frame.time = (self.frame.time + dt / 10.0).fract();196 }197 let size = window.inner_size();198 if size.width == 0 || size.height == 0 {199 return;200 }201 if let Some(gpu) = &mut self.gpu {202 match gpu.render(self.frame) {203 Ok(Some(count)) if self.smoke => {204 println!("presented stage={} visible={count}", self.frame.stage);205 self.presented += 1;206 if self.presented == 8 {207 event_loop.exit();208 return;209 }210 self.frame.stage = self.presented;211 }212 Ok(_) => {}213 Err(e) => {214 self.fail(event_loop, e);215 return;216 }217 }218 }219 if self.playing || self.smoke {220 window.request_redraw();221 }222 }223 _ => {}224 }225 }226 // END: native-events227 }228 pub fn run() -> Result<(), Box<dyn std::error::Error>> {229 let args: Vec<_> = std::env::args().skip(1).collect();230 let smoke = args.iter().any(|s| s == "--smoke");231 let stage = if smoke {232 0233 } else {234 args.first().map(|s| s.parse()).transpose()?.unwrap_or(6)235 };236 if stage > 7 {237 return Err("Stage must be 0..7".into());238 }239 let mut app = App {240 window: None,241 gpu: None,242 frame: Frame {243 stage,244 ..Frame::default()245 },246 last: Instant::now(),247 playing: !smoke,248 smoke,249 presented: 0,250 smoke_attempts: 0,251 error: None,252 };253 EventLoop::new()?.run_app(&mut app)?;254 if let Some(error) = app.error {255 return Err(error.into());256 }257 Ok(())258 }259}260