pub mod event;
pub mod frame_io;
pub mod settings;
pub use event::{Event, Key, Modifiers, MouseButton};
pub use frame_io::{FrameInput, FrameOutput, Viewport};
pub use settings::WindowSettings;
use crate::context::WgpuContext;
use crate::core::RenderTarget;
use crate::core::texture::DepthTexture;
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::WindowId;
pub struct Window {
settings: WindowSettings,
}
impl Window {
pub fn new(settings: WindowSettings) -> anyhow::Result<Self> {
Ok(Self { settings })
}
pub fn render_loop<F, S>(self, state_init: S, callback: F) -> anyhow::Result<()>
where
F: FnMut(&mut S, FrameInput<'_>) -> FrameOutput + 'static,
S: 'static,
{
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App {
settings: self.settings,
state: Some(state_init),
callback: Some(callback),
graphics: None,
events: Vec::new(),
start_time: std::time::Instant::now(),
last_frame_time: std::time::Instant::now(),
mouse_position: (0.0, 0.0),
};
event_loop.run_app(&mut app)?;
Ok(())
}
}
struct Graphics {
window: Arc<winit::window::Window>,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
ctx: WgpuContext,
depth_texture: DepthTexture,
}
struct App<S, F> {
settings: WindowSettings,
state: Option<S>,
callback: Option<F>,
graphics: Option<Graphics>,
events: Vec<Event>,
start_time: std::time::Instant,
last_frame_time: std::time::Instant,
mouse_position: (f32, f32),
}
impl<S, F> ApplicationHandler for App<S, F>
where
F: FnMut(&mut S, FrameInput<'_>) -> FrameOutput + 'static,
S: 'static,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.graphics.is_some() {
return;
}
let window_attrs = winit::window::WindowAttributes::default()
.with_title(&self.settings.title)
.with_inner_size(winit::dpi::LogicalSize::new(
self.settings.size.0,
self.settings.size.1,
))
.with_resizable(self.settings.resizable);
let window = Arc::new(
event_loop
.create_window(window_attrs)
.expect("Failed to create window"),
);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = instance
.create_surface(window.clone())
.expect("Failed to create surface");
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.expect("Failed to find suitable GPU adapter");
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("rein device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
},
None,
))
.expect("Failed to create device");
let ctx = WgpuContext::new(device, queue);
let size = window.inner_size();
let caps = surface.get_capabilities(&adapter);
let format = caps
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: if self.settings.vsync {
wgpu::PresentMode::AutoVsync
} else {
wgpu::PresentMode::AutoNoVsync
},
alpha_mode: caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&ctx.device, &config);
let depth_texture =
DepthTexture::new(&ctx, config.width, config.height, Some("depth texture"));
self.graphics = Some(Graphics {
window,
surface,
config,
ctx,
depth_texture,
});
self.start_time = std::time::Instant::now();
self.last_frame_time = std::time::Instant::now();
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let Some(graphics) = &mut self.graphics else {
return;
};
let modifiers = event::Modifiers::default();
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(size) => {
if size.width > 0 && size.height > 0 {
graphics.config.width = size.width;
graphics.config.height = size.height;
graphics
.surface
.configure(&graphics.ctx.device, &graphics.config);
graphics
.depth_texture
.resize(&graphics.ctx, size.width, size.height);
}
self.events.push(Event::Resize {
width: size.width,
height: size.height,
});
}
WindowEvent::CursorMoved { position, .. } => {
let old_position = self.mouse_position;
self.mouse_position = (position.x as f32, position.y as f32);
let delta = (
self.mouse_position.0 - old_position.0,
self.mouse_position.1 - old_position.1,
);
self.events.push(Event::MouseMotion {
delta,
position: self.mouse_position,
modifiers,
handled: false,
});
}
WindowEvent::MouseInput { state, button, .. } => {
let button = match button {
winit::event::MouseButton::Left => event::MouseButton::Left,
winit::event::MouseButton::Right => event::MouseButton::Right,
winit::event::MouseButton::Middle => event::MouseButton::Middle,
_ => return,
};
match state {
winit::event::ElementState::Pressed => {
self.events.push(Event::MousePress {
button,
position: self.mouse_position,
modifiers,
handled: false,
});
}
winit::event::ElementState::Released => {
self.events.push(Event::MouseRelease {
button,
position: self.mouse_position,
modifiers,
handled: false,
});
}
}
}
WindowEvent::MouseWheel { delta, .. } => {
let delta = match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => (x * 20.0, y * 20.0),
winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
};
self.events.push(Event::MouseWheel {
delta,
position: self.mouse_position,
modifiers,
handled: false,
});
}
WindowEvent::KeyboardInput {
event: key_event, ..
} => {
if let Some(key) = event::Key::from_winit(&key_event.logical_key) {
match key_event.state {
winit::event::ElementState::Pressed => {
self.events.push(Event::KeyPress {
key,
modifiers,
handled: false,
});
}
winit::event::ElementState::Released => {
self.events.push(Event::KeyRelease {
key,
modifiers,
handled: false,
});
}
}
}
}
WindowEvent::RedrawRequested => {
let now = std::time::Instant::now();
let elapsed_time = (now - self.start_time).as_secs_f64();
let delta_time = (now - self.last_frame_time).as_secs_f64();
self.last_frame_time = now;
let surface_texture = match graphics.surface.get_current_texture() {
Ok(texture) => texture,
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
graphics
.surface
.configure(&graphics.ctx.device, &graphics.config);
return;
}
Err(e) => {
tracing::error!("Surface error: {:?}", e);
return;
}
};
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let viewport = frame_io::Viewport {
x: 0,
y: 0,
width: graphics.config.width,
height: graphics.config.height,
};
let events = std::mem::take(&mut self.events);
let frame_input = FrameInput {
events,
elapsed_time,
delta_time,
viewport,
ctx: &graphics.ctx,
surface_view: &view,
depth_texture: &graphics.depth_texture,
surface_format: graphics.config.format,
};
let state = self.state.as_mut().expect("State should exist");
let callback = self.callback.as_mut().expect("Callback should exist");
let output = (callback)(state, frame_input);
surface_texture.present();
if output.exit {
event_loop.exit();
}
}
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(graphics) = &self.graphics {
graphics.window.request_redraw();
}
}
}
pub fn screen_target<'a>(input: &'a FrameInput<'a>) -> RenderTarget<'a> {
RenderTarget::from_surface(
input.ctx,
input.surface_view,
Some(input.depth_texture),
input.viewport.width,
input.viewport.height,
input.surface_format,
)
}