vidya/crates/game/src/main.rs
2025-09-20 15:27:21 +02:00

303 lines
10 KiB
Rust

use std::collections::BTreeMap;
use rand::{Rng, SeedableRng};
use renderer::{Renderer2, render_graph, swapchain::WindowSurface};
use tracing::info;
use tracing_subscriber::EnvFilter;
use winit::{
application::ApplicationHandler,
dpi::{LogicalSize, PhysicalSize},
event::ElementState,
event_loop::EventLoop,
raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle},
window::{Window, WindowAttributes, WindowId},
};
struct WindowState {
window: Window,
egui_platform: egui_winit_platform::Platform,
demo_app: egui_demo_lib::DemoWindows,
scale_factor: f64,
surface: WindowSurface,
}
struct WinitState {
last_resize_events: BTreeMap<WindowId, PhysicalSize<u32>>,
window_attrs: WindowAttributes,
windows: BTreeMap<WindowId, WindowState>,
renderer: Renderer2,
last_redraw: std::time::Instant,
egui_state: renderer::EguiState,
}
impl WinitState {
const BASE_WIDTH: u32 = 800;
const BASE_HEIGHT: u32 = 600;
fn new(window_title: String, display: DisplayHandle) -> WinitState {
let renderer = Renderer2::new(display.as_raw()).expect("renderer");
Self {
windows: BTreeMap::new(),
last_resize_events: BTreeMap::new(),
window_attrs: WindowAttributes::default()
.with_title(window_title)
.with_resizable(true)
.with_inner_size(LogicalSize::new(Self::BASE_WIDTH, Self::BASE_HEIGHT)),
// TODO: pass down this error and add some kind of error handling UI or dump
egui_state: renderer::EguiState::new(renderer.device().clone()).unwrap(),
renderer,
last_redraw: std::time::Instant::now(),
}
}
fn handle_final_resize(&mut self, window_id: WindowId, new_size: PhysicalSize<u32>) {
_ = (window_id, new_size);
info!("TODO: implement resize events");
if let Some(window) = self.windows.get(&window_id) {
window
.surface
.recreate_with(Some(renderer::Extent2D {
width: new_size.width,
height: new_size.height,
}))
.expect("swapchain recreation");
}
}
fn handle_draw_request(&mut self, window_id: WindowId) {
_ = window_id;
tracing::debug!(
window_id = u64::from(window_id),
"TODO: implement draw request {}ms",
self.last_redraw.elapsed().as_millis()
);
self.last_redraw = std::time::Instant::now();
if let Some(window) = self.windows.get_mut(&window_id) {
// egui
window.egui_platform.begin_pass();
window.demo_app.ui(&window.egui_platform.context());
let output = window.egui_platform.end_pass(Some(&window.window));
let textures_to_remove = output
.textures_delta
.free
.iter()
.filter_map(|id| self.egui_state.lookup_texture(*id))
.collect::<Vec<_>>();
// rendering
let render = self.renderer.draw_graph(&window.surface, |renderer, rg| {
use renderer::rendering::{egui_pass, egui_pre_pass};
let framebuffer = rg.get_framebuffer().unwrap();
let dev = renderer.device().clone();
use renderer::ash::vk::Handle;
use renderer::device::DeviceOwned;
let [r, g, b]: [f32; 3] =
rand::prelude::StdRng::seed_from_u64(window.surface.surface.handle().as_raw())
.random();
render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer);
egui_pre_pass(
&dev,
rg,
&mut renderer.texture_manager,
&mut self.egui_state,
&output,
)?;
let _ = egui_pass(
&dev,
rg,
&mut renderer.texture_manager,
&mut renderer.samplers,
&mut self.egui_state,
&window.egui_platform.context(),
output,
framebuffer,
)?;
renderer::Result::Ok(())
});
match smol::block_on(render).flatten() {
Ok(_) => {}
Err(e) => {
tracing::error!("encountered error while rendering: {e}");
}
}
for tid in textures_to_remove {
self.renderer.texture_manager_mut().remove_texture(tid);
}
window.window.request_redraw();
}
}
fn remove_window(&mut self, window_id: WindowId) {
tracing::info!(window = u64::from(window_id), "window close requested");
self.windows.remove(&window_id);
}
fn create_window(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let window = event_loop
.create_window(
self.window_attrs
.clone()
.with_window_level(winit::window::WindowLevel::AlwaysOnTop),
)
.expect("new window");
let window_id = window.id();
tracing::info!(window = u64::from(window_id), "created new window");
let size = self
.window_attrs
.inner_size
.map(|size| size.to_physical(1.0))
.unwrap_or(PhysicalSize::new(0, 0));
let extent = renderer::Extent2D {
width: size.width,
height: size.height,
};
let surface = self
.renderer
.create_surface(
window.window_handle().expect("window handle").as_raw(),
extent,
)
.expect("surface");
let scale_factor = window.scale_factor();
self.windows.insert(
window_id,
WindowState {
window,
scale_factor,
demo_app: egui_demo_lib::DemoWindows::default(),
egui_platform: egui_winit_platform::Platform::new(
egui_winit_platform::PlatformDescriptor {
physical_width: size.width,
physical_height: size.height,
scale_factor,
..Default::default()
},
),
surface,
},
);
}
}
impl ApplicationHandler for WinitState {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
tracing::debug!("winit::resumed");
self.create_window(event_loop);
}
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
tracing::trace!("winit::about_to_wait");
for (&window, &resize) in self.last_resize_events.clone().iter() {
self.handle_final_resize(window, resize);
}
self.last_resize_events.clear();
if self.windows.is_empty() {
event_loop.exit();
}
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
// if !matches!(event, winit::event::WindowEvent::Resized(_)) {
// if let Some(resize) = self.last_resize_events.remove(&window_id) {
// self.handle_final_resize(window_id, resize);
// }
// }
if let Some(window) = self.windows.get_mut(&window_id) {
window.egui_platform.handle_event(&event);
}
match event {
winit::event::WindowEvent::Resized(physical_size) => {
_ = self.last_resize_events.insert(window_id, physical_size);
}
winit::event::WindowEvent::CloseRequested
| winit::event::WindowEvent::KeyboardInput {
event:
winit::event::KeyEvent {
physical_key:
winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::KeyQ),
state: ElementState::Pressed,
repeat: false,
..
},
..
} => {
self.remove_window(window_id);
}
winit::event::WindowEvent::KeyboardInput {
event:
winit::event::KeyEvent {
physical_key:
winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::Space),
state: ElementState::Pressed,
repeat: false,
..
},
..
} => {
self.create_window(event_loop);
}
winit::event::WindowEvent::KeyboardInput {
device_id,
event,
is_synthetic,
} => {
_ = (device_id, event, is_synthetic);
}
winit::event::WindowEvent::CursorMoved {
device_id,
position,
} => {
_ = (device_id, position);
}
winit::event::WindowEvent::MouseInput {
device_id,
state,
button,
} => {
_ = (device_id, state, button);
}
winit::event::WindowEvent::RedrawRequested => {
self.handle_draw_request(window_id);
}
winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
if let Some(window) = self.windows.get_mut(&window_id) {
window.scale_factor = scale_factor;
}
}
_ => {} // unhandled event
}
}
}
fn main() {
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
let ev = EventLoop::new().unwrap();
ev.set_control_flow(winit::event_loop::ControlFlow::Poll);
let display = ev.display_handle().expect("display handle");
let mut game = WinitState::new("Vidya".to_owned(), display);
ev.run_app(&mut game).unwrap();
}