← Build a viewer

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