mod app;
mod default_shaders;
mod input;
mod recorder;
mod render_bundle;
mod shader_compiler;
mod utils;
#[allow(dead_code)]
mod audio;
use std::{
error::Error,
path::{Path, PathBuf},
time::{Duration, Instant},
};
use pilka_types::{PushConstant, ShaderInfo};
use recorder::{RecordEvent, RecordTimer};
use render_bundle::Renderer;
use utils::{parse_args, print_help, save_screenshot, save_shaders, Args};
use eyre::*;
use notify::{RecursiveMode, Watcher};
use winit::{
dpi::{LogicalSize, PhysicalPosition, PhysicalSize},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use app::App;
pub const SCREENSHOTS_FOLDER: &str = "screenshots";
pub const SHADER_DUMP_FOLDER: &str = "shader_dump";
pub const VIDEO_FOLDER: &str = "recordings";
pub const SHADER_PATH: &str = "shaders";
fn main() -> Result<(), Box<dyn Error>> {
color_eyre::install()?;
env_logger::init();
puffin::set_scopes_on(true);
let Args {
record_time,
inner_size,
wgsl_mode,
} = parse_args();
let event_loop = EventLoop::new();
let main_window = {
let mut window_builder = WindowBuilder::new().with_title("Pilka");
#[cfg(unix)]
{
use winit::platform::unix::WindowBuilderExtUnix;
window_builder =
window_builder.with_resize_increments(LogicalSize::<u32>::from((8, 2)));
}
if let Some(size) = inner_size {
window_builder = window_builder
.with_resizable(false)
.with_inner_size(LogicalSize::<u32>::from(size));
} else {
window_builder = window_builder.with_inner_size(LogicalSize::new(1280, 720));
}
window_builder.build(&event_loop)?
};
let shader_dir = PathBuf::new().join(SHADER_PATH);
if !shader_dir.is_dir() {
default_shaders::create_default_shaders(&shader_dir, wgsl_mode)?;
}
let (folder_tx, folder_rx) = crossbeam_channel::unbounded();
let mut watcher = notify::recommended_watcher(move |res| match res {
Ok(event) => {
folder_tx.send(event).unwrap();
}
Err(e) => eprintln!("watch error: {:?}", e),
})?;
watcher.watch(Path::new(SHADER_PATH), RecursiveMode::Recursive)?;
let mut app = App::new(&main_window, folder_rx)?;
let mut video_recording = false;
let (video_tx, video_rx) = crossbeam_channel::unbounded();
std::thread::spawn(move || recorder::record_thread(video_rx));
let mut input = input::Input::new();
let mut pause = false;
let mut timeline = Instant::now();
let mut prev_time = timeline.elapsed();
let mut backup_time = timeline.elapsed();
let mut dt = Duration::from_secs_f32(1. / 60.);
let mut last_update_inst = Instant::now();
let (mut timer, start_event) = RecordTimer::new(record_time, video_tx.clone());
if let Some(period) = record_time {
app.push_constant.record_period = period.as_secs_f32();
}
event_loop.run(move |event, _event_loop, control_flow| {
*control_flow = winit::event_loop::ControlFlow::Poll;
match event {
Event::RedrawEventsCleared => {
puffin::profile_scope!("Redraw Timeout");
let target_frametime = Duration::from_secs_f64(1.0 / 60.0);
let time_since_last_frame = last_update_inst.elapsed();
if time_since_last_frame >= target_frametime {
main_window.request_redraw();
last_update_inst = Instant::now();
} else {
*control_flow = ControlFlow::WaitUntil(
Instant::now() + target_frametime - time_since_last_frame,
);
}
}
Event::NewEvents(_) => {
puffin::profile_scope!("Frame setup");
app.setup_frame();
app.push_constant.time = if pause {
backup_time.as_secs_f32()
} else if let Some(recording_time) = timer.counter {
recording_time.elapsed().as_secs_f32()
} else {
timeline.elapsed().as_secs_f32()
};
app.push_constant.wh = main_window.inner_size().into();
input.process_position(&mut app.push_constant);
if !pause {
dt = timeline.elapsed().saturating_sub(prev_time);
app.push_constant.time_delta = dt.as_secs_f32();
prev_time = timeline.elapsed();
}
timer
.update(&mut video_recording, app.render.captured_frame_dimentions())
.unwrap();
}
Event::WindowEvent {
event:
WindowEvent::Resized(size)
| WindowEvent::ScaleFactorChanged {
new_inner_size: &mut size,
..
},
window_id,
} => {
puffin::profile_scope!("Resize");
let PhysicalSize { width, height } = size;
if main_window.id() == window_id {
app.resize(width.max(1), height.max(1)).unwrap();
}
if video_recording {
println!("Stop recording. Resolution has been changed.",);
video_recording = false;
video_tx.send(RecordEvent::Finish).unwrap();
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id: _window_id,
} => {
}
Event::WindowEvent { event, window_id } if main_window.id() == window_id => match event
{
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(keycode),
state,
..
},
..
} => {
puffin::profile_scope!("Keyboard Events");
input.update(&keycode, &state);
if VirtualKeyCode::Escape == keycode {
*control_flow = ControlFlow::Exit;
}
if ElementState::Pressed == state {
if VirtualKeyCode::F1 == keycode {
print_help();
}
if VirtualKeyCode::F2 == keycode {
if !pause {
backup_time = timeline.elapsed();
pause = true;
} else {
timeline = Instant::now() - backup_time;
pause = false;
}
}
if VirtualKeyCode::F3 == keycode {
if !pause {
backup_time = timeline.elapsed();
pause = true;
}
backup_time = backup_time.saturating_sub(dt);
}
if VirtualKeyCode::F4 == keycode {
if !pause {
backup_time = timeline.elapsed();
pause = true;
}
backup_time += dt;
}
if VirtualKeyCode::F5 == keycode {
app.push_constant.pos = [0.; 3];
app.push_constant.time = 0.;
app.push_constant.frame = 0;
timeline = Instant::now();
backup_time = timeline.elapsed();
}
if VirtualKeyCode::F6 == keycode {
eprintln!("{}", app.push_constant);
}
if VirtualKeyCode::F7 == keycode {
}
if VirtualKeyCode::F8 == keycode {
app.render.switch(&main_window, &mut app.compiler).unwrap();
}
if VirtualKeyCode::F10 == keycode {
save_shaders(&app.render.shader_list()).unwrap();
}
if VirtualKeyCode::F11 == keycode {
let now = Instant::now();
let (frame, image_dimentions) = app.render.capture_frame().unwrap();
eprintln!("Capture image: {:#.2?}", now.elapsed());
save_screenshot(frame, image_dimentions);
}
if app.has_ffmpeg && VirtualKeyCode::F12 == keycode {
if video_recording {
video_tx.send(RecordEvent::Finish).unwrap()
} else {
let (_, image_dimentions) = app.render.capture_frame().unwrap();
video_tx.send(RecordEvent::Start(image_dimentions)).unwrap()
}
video_recording = !video_recording;
}
}
}
WindowEvent::CursorMoved {
position: PhysicalPosition { x, y },
..
} => {
if !pause {
let PhysicalSize { width, height } = main_window.inner_size();
let x = (x as f32 / width as f32 - 0.5) * 2.;
let y = -(y as f32 / height as f32 - 0.5) * 2.;
app.push_constant.mouse = [x, y];
}
}
WindowEvent::MouseInput {
button: winit::event::MouseButton::Left,
state,
..
} => match state {
ElementState::Pressed => app.push_constant.mouse_pressed = true as _,
ElementState::Released => app.push_constant.mouse_pressed = false as _,
},
_ => {}
},
Event::RedrawRequested(_) => {
puffin::GlobalProfiler::lock().new_frame();
puffin::profile_scope!("Rendering");
app.render();
start_event.try_send(()).ok();
if video_recording {
let (frame, _image_dimentions) = app.render.capture_frame().unwrap();
video_tx.send(RecordEvent::Record(frame)).unwrap()
}
}
Event::LoopDestroyed => {
app.shut_down();
println!("// End from the loop. Bye bye~⏎ ");
}
_ => {}
}
});
}