use std::{
sync::Arc,
time::{Duration, Instant},
};
use wgpu::{
Adapter, Backends, Device, ExperimentalFeatures, Features, Instance, InstanceDescriptor,
PowerPreference, Queue, Surface, SurfaceConfiguration, TextureFormat,
};
use winit::{
dpi::PhysicalSize,
event::{DeviceEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};
use crate::{
EntityUpdate,
graphics::{GraphicsState, create_contour_bind_group, create_ssao_bind_group},
gui::GuiState,
texture::Texture,
types::{EngineUpdates, GraphicsSettings, Scene, UiSettings},
};
pub const COLOR_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb;
pub const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
pub(crate) struct RenderState {
pub size: PhysicalSize<u32>,
pub surface: Surface<'static>, pub device: Device,
pub queue: Queue,
pub surface_cfg: SurfaceConfiguration,
}
pub struct State<T: 'static, FRender, FEventDev, FEventWin, FGui>
where
FRender: FnMut(&mut T, &mut Scene, f32) -> EngineUpdates + 'static,
FEventDev: FnMut(&mut T, DeviceEvent, &mut Scene, bool, f32) -> EngineUpdates + 'static,
FEventWin: FnMut(&mut T, WindowEvent, &mut Scene, f32) -> EngineUpdates + 'static,
FGui: FnMut(&mut T, &egui::Context, &mut Scene) -> EngineUpdates + 'static,
{
pub instance: Instance,
pub render: Option<RenderState>,
pub graphics: Option<GraphicsState>,
pub gui: Option<GuiState>,
pub user_state: T,
pub render_handler: FRender,
pub event_dev_handler: FEventDev,
pub event_win_handler: FEventWin,
pub gui_handler: FGui,
pub ui_settings: UiSettings,
pub graphics_settings: GraphicsSettings,
pub scene: Scene,
pub last_render_time: Instant,
pub dt: Duration,
pub paused: bool,
}
impl<T: 'static, FRender, FEventDev, FEventWin, FGui> State<T, FRender, FEventDev, FEventWin, FGui>
where
FRender: FnMut(&mut T, &mut Scene, f32) -> EngineUpdates + 'static,
FEventDev: FnMut(&mut T, DeviceEvent, &mut Scene, bool, f32) -> EngineUpdates + 'static,
FEventWin: FnMut(&mut T, WindowEvent, &mut Scene, f32) -> EngineUpdates + 'static,
FGui: FnMut(&mut T, &egui::Context, &mut Scene) -> EngineUpdates + 'static,
{
pub(crate) fn new(
scene: Scene,
ui_settings: UiSettings,
graphics_settings: GraphicsSettings,
user_state: T,
render_handler: FRender,
event_dev_handler: FEventDev,
event_win_handler: FEventWin,
gui_handler: FGui,
) -> Self {
let last_render_time = Instant::now();
let dt = Duration::new(0, 0);
let instance = Instance::new(&InstanceDescriptor {
backends: Backends::VULKAN,
..Default::default()
});
Self {
instance,
render: None,
graphics: None,
gui: None,
user_state,
render_handler,
event_dev_handler,
event_win_handler,
gui_handler,
ui_settings,
graphics_settings,
scene,
last_render_time,
dt,
paused: false,
}
}
pub(crate) fn init(&mut self, window: Window) {
let window = Arc::new(window);
let size = window.inner_size();
let surface = self.instance.create_surface(window.clone()).unwrap();
let (_adapter, device, queue) = pollster::block_on(setup_async(&self.instance, &surface));
let surface_cfg = SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: COLOR_FORMAT,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
desired_maximum_frame_latency: 2, alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: Vec::new(),
};
surface.configure(&device, &surface_cfg);
let texture_format = surface_cfg.format;
let render = RenderState {
size,
surface,
device,
queue,
surface_cfg,
};
if let Some(strength) = self.graphics_settings.edge_cueing {
self.scene.camera.edge_cueing = strength;
}
let mut graphics = GraphicsState::new(
&render.device,
&render.surface_cfg,
self.scene.clone(), window.clone(),
self.graphics_settings.msaa_samples,
);
graphics.apply_graphics_settings(&self.graphics_settings, &render.queue);
self.gui = Some(GuiState::new(
window,
&render.device,
texture_format,
self.graphics_settings.msaa_samples,
));
self.render = Some(render);
self.graphics = Some(graphics);
}
pub(crate) fn resize(&mut self, new_size: PhysicalSize<u32>) {
if self.render.is_none() || self.graphics.is_none() {
return;
}
let sys = self.render.as_mut().unwrap();
let graphics = self.graphics.as_mut().unwrap();
if new_size.width > 0 && new_size.height > 0 {
sys.size = new_size;
sys.surface_cfg.width = new_size.width;
sys.surface_cfg.height = new_size.height;
sys.surface.configure(&sys.device, &sys.surface_cfg);
let (eff_width, eff_height) =
(sys.surface_cfg.width as f32, sys.surface_cfg.height as f32);
graphics.scene.camera.aspect = eff_width / eff_height;
graphics.scene.window_size = (new_size.width as f32, new_size.height as f32);
graphics.surface_cfg = sys.surface_cfg.clone();
graphics.depth_texture = Texture::create_depth_texture(
&sys.device,
&sys.surface_cfg,
"Depth texture",
graphics.msaa_samples,
);
graphics.depth_texture_contour = Texture::create_depth_texture(
&sys.device,
&sys.surface_cfg,
"Depth texture contour",
1,
);
graphics.bind_group_contour = create_contour_bind_group(
&sys.device,
&graphics.layout_contour,
&graphics.depth_texture_contour.view,
&graphics.contour_uniform_buf,
);
graphics.bind_group_ssao = create_ssao_bind_group(
&sys.device,
&graphics.layout_ssao,
&graphics.depth_texture_contour.view,
&graphics.ssao_uniform_buf,
);
if let Some(t) = &mut graphics.msaa_texture {
*t = GraphicsState::create_msaa_texture(
&sys.device,
&sys.surface_cfg,
graphics.msaa_samples,
);
}
graphics.scene.camera.update_proj_mat();
graphics.update_camera(&sys.queue);
}
}
}
pub fn run<T: 'static, FRender, FEventDev, FEventWin, FGui>(
user_state: T,
scene: Scene,
ui_settings: UiSettings,
graphics_settings: GraphicsSettings,
render_handler: FRender,
event_dev_handler: FEventDev,
event_win_handler: FEventWin,
gui_handler: FGui,
) where
FRender: FnMut(&mut T, &mut Scene, f32) -> EngineUpdates + 'static,
FEventDev: FnMut(&mut T, DeviceEvent, &mut Scene, bool, f32) -> EngineUpdates + 'static,
FEventWin: FnMut(&mut T, WindowEvent, &mut Scene, f32) -> EngineUpdates + 'static,
FGui: FnMut(&mut T, &egui::Context, &mut Scene) -> EngineUpdates + 'static,
{
let (_frame_count, _accum_time) = (0, 0.0);
let mut state: State<T, FRender, FEventDev, FEventWin, FGui> = State::new(
scene,
ui_settings,
graphics_settings,
user_state,
render_handler,
event_dev_handler,
event_win_handler,
gui_handler,
);
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
event_loop.run_app(&mut state).expect("Failed to run app");
}
async fn setup_async(instance: &Instance, surface: &Surface<'static>) -> (Adapter, Device, Queue) {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: PowerPreference::default(),
compatible_surface: Some(surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: None,
required_features: Features::empty(),
required_limits: Default::default(),
memory_hints: Default::default(),
trace: wgpu::Trace::Off,
experimental_features: ExperimentalFeatures::disabled(),
})
.await
.expect("Unable to find a suitable GPU adapter. :(");
(adapter, device, queue)
}
pub(crate) fn process_engine_updates(
updates: &EngineUpdates,
g_state: &mut GraphicsState,
device: &Device,
queue: &Queue,
) {
if updates.meshes {
g_state.setup_vertices_indices(device);
g_state.setup_entities(device);
}
match &updates.entities {
EntityUpdate::None => (),
EntityUpdate::All => g_state.setup_entities(device),
_ => g_state.replace_instance_entries(queue, device, &updates.entities),
}
if updates.camera {
g_state.update_camera(queue);
}
if updates.lighting {
g_state.update_lighting(queue);
}
if let Some(settings) = &updates.graphics_settings {
g_state.apply_graphics_settings(settings, queue);
if settings.msaa_samples != g_state.msaa_samples {
g_state.pending_msaa = Some(settings.msaa_samples);
}
}
}