use crate::draw;
use crate::event::{self, Event, Key, LoopEvent, Update};
use crate::frame::{Frame, RawFrame, RenderData};
use crate::geom;
use crate::state;
use crate::time::DurationF64;
use crate::ui;
use crate::vk::{self, DeviceOwned, GpuFuture};
use crate::window::{self, Window};
use find_folder;
use std;
use std::cell::{RefCell, RefMut};
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::sync::atomic::{self, AtomicBool};
use std::sync::Arc;
use std::time::{Duration, Instant};
use winit;
#[cfg(all(target_os = "macos", not(test)))]
use moltenvk_deps as mvkd;
pub type ModelFn<Model> = fn(&App) -> Model;
pub type EventFn<Model, Event> = fn(&App, &mut Model, Event);
pub type UpdateFn<Model> = fn(&App, &mut Model, Update);
pub type ViewFn<Model> = fn(&App, &Model, &Frame);
pub type SketchViewFn = fn(&App, &Frame);
pub type ExitFn<Model> = fn(&App, Model);
enum View<Model = ()> {
WithModel(ViewFn<Model>),
Sketch(SketchViewFn),
}
pub struct Builder<M = (), E = Event> {
model: ModelFn<M>,
event: Option<EventFn<M, E>>,
update: Option<UpdateFn<M>>,
default_view: Option<View<M>>,
exit: Option<ExitFn<M>>,
vk_instance: Option<Arc<vk::Instance>>,
vk_debug_callback: Option<vk::DebugCallbackBuilder>,
create_default_window: bool,
#[cfg(all(target_os = "macos", not(test)))]
moltenvk_settings: Option<mvkd::Install>,
}
fn default_model(_: &App) -> () {
()
}
pub struct App {
config: RefCell<Config>,
pub(crate) vk_instance: Arc<vk::Instance>,
pub(crate) events_loop: winit::EventsLoop,
pub(crate) windows: RefCell<HashMap<window::Id, Window>>,
draw_state: DrawState,
pub(crate) ui: ui::Arrangement,
pub(crate) focused_window: RefCell<Option<window::Id>>,
pub(crate) events_loop_is_asleep: Arc<AtomicBool>,
pub mouse: state::Mouse,
pub keys: state::Keys,
pub duration: state::Time,
pub time: DrawScalar,
}
#[derive(Debug)]
struct Config {
loop_mode: LoopMode,
exit_on_escape: bool,
fullscreen_on_shortcut: bool,
}
#[derive(Debug)]
pub struct Draw<'a> {
window_id: window::Id,
draw: RefMut<'a, draw::Draw<DrawScalar>>,
renderer: RefMut<'a, RefCell<draw::backend::vulkano::Renderer>>,
depth_format: vk::Format,
}
#[derive(Debug)]
struct DrawState {
draw: RefCell<draw::Draw<DrawScalar>>,
renderers: RefCell<HashMap<window::Id, RefCell<draw::backend::vulkano::Renderer>>>,
supported_depth_formats: RefCell<HashMap<usize, vk::Format>>,
}
pub type DrawScalar = geom::scalar::Default;
pub struct Proxy {
events_loop_proxy: winit::EventsLoopProxy,
events_loop_is_asleep: Arc<AtomicBool>,
}
struct Break<M> {
model: M,
reason: BreakReason,
}
enum BreakReason {
Exit,
NewLoopMode(LoopMode),
}
struct LoopContext<M, E> {
event_fn: Option<EventFn<M, E>>,
update_fn: Option<UpdateFn<M>>,
default_view: Option<View<M>>,
loop_start: Instant,
winit_events: Vec<winit::Event>,
last_update: Instant,
last_loop_end: Instant,
updates_remaining: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LoopMode {
Rate {
update_interval: Duration,
},
Wait {
updates_following_event: usize,
update_interval: Duration,
},
NTimes {
number_of_updates: usize,
update_interval: Duration,
},
RefreshSync {
minimum_update_interval: Duration,
windows: Option<HashSet<window::Id>>,
},
}
impl<M> Builder<M, Event>
where
M: 'static,
{
pub fn new(model: ModelFn<M>) -> Self {
Builder {
model,
event: None,
update: None,
default_view: None,
exit: None,
vk_instance: None,
vk_debug_callback: None,
create_default_window: false,
#[cfg(all(target_os = "macos", not(test)))]
moltenvk_settings: None,
}
}
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn event<E>(self, event: EventFn<M, E>) -> Builder<M, E>
where
E: LoopEvent,
{
let Builder {
model,
update,
default_view,
exit,
create_default_window,
vk_instance,
vk_debug_callback,
#[cfg(all(target_os = "macos", not(test)))]
moltenvk_settings,
..
} = self;
Builder {
model,
event: Some(event),
update,
default_view,
exit,
create_default_window,
vk_instance,
vk_debug_callback,
#[cfg(all(target_os = "macos", not(test)))]
moltenvk_settings,
}
}
}
impl<M, E> Builder<M, E>
where
M: 'static,
E: LoopEvent,
{
pub fn view(mut self, view: ViewFn<M>) -> Self {
self.default_view = Some(View::WithModel(view));
self
}
pub fn update(mut self, update: UpdateFn<M>) -> Self {
self.update = Some(update);
self
}
pub fn simple_window(mut self, view: ViewFn<M>) -> Self {
self.default_view = Some(View::WithModel(view));
self.create_default_window = true;
self
}
pub fn exit(mut self, exit: ExitFn<M>) -> Self {
self.exit = Some(exit);
self
}
pub fn vk_instance(mut self, vk_instance: Arc<vk::Instance>) -> Self {
self.vk_instance = Some(vk_instance);
self
}
pub fn vk_debug_callback(mut self, debug_cb: vk::DebugCallbackBuilder) -> Self {
self.vk_debug_callback = Some(debug_cb);
self
}
#[cfg(all(target_os = "macos", not(test)))]
pub fn macos_installer(mut self, settings: mvkd::Install) -> Self {
self.moltenvk_settings = Some(settings);
self
}
pub fn run(mut self) {
let events_loop = winit::EventsLoop::new();
let debug_callback_specified = self.vk_debug_callback.is_some();
#[cfg(all(target_os = "macos", not(test)))]
let moltenvk_settings = self.moltenvk_settings;
let vk_instance = self.vk_instance.take().unwrap_or_else(|| {
if debug_callback_specified {
let vk_builder = vk::InstanceBuilder::new();
#[cfg(all(target_os = "macos", not(test)))]
let vk_builder = vk::check_moltenvk(vk_builder, moltenvk_settings);
#[cfg(any(not(target_os = "macos"), test))]
let vk_builder = vk_builder.extensions(vk::required_windowing_extensions());
vk_builder
.add_extensions(vk::InstanceExtensions {
ext_debug_report: true,
..vk::InstanceExtensions::none()
})
.layers(vec!["VK_LAYER_LUNARG_standard_validation"])
.build()
.expect("failed to create vulkan instance")
} else {
let vk_builder = vk::InstanceBuilder::new();
#[cfg(all(target_os = "macos", not(test)))]
let vk_builder = vk::check_moltenvk(vk_builder, moltenvk_settings);
#[cfg(any(not(target_os = "macos"), test))]
let vk_builder = vk_builder.extensions(vk::required_windowing_extensions());
vk_builder
.build()
.expect("failed to create vulkan instance")
}
});
let _vk_debug_callback = self.vk_debug_callback.take().map(|builder| {
builder
.build(&vk_instance)
.expect("failed to build vulkan debug callback")
});
let app = App::new(events_loop, vk_instance).expect("failed to construct `App`");
if self.create_default_window {
let window_id = app
.new_window()
.build()
.expect("could not build default app window");
*app.focused_window.borrow_mut() = Some(window_id);
}
let model = (self.model)(&app);
if app.focused_window.borrow().is_none() {
if let Some(id) = app.windows.borrow().keys().next() {
*app.focused_window.borrow_mut() = Some(id.clone());
}
}
let loop_mode = app.loop_mode();
if loop_mode != LoopMode::default() {
let mut windows = app.windows.borrow_mut();
for window in windows.values_mut() {
let capabilities = window
.surface
.capabilities(window.swapchain.device().physical_device())
.expect("failed to get surface capabilities");
let min_image_count = capabilities.min_image_count;
let user_specified_present_mode = window.user_specified_present_mode;
let user_specified_image_count = window.user_specified_image_count;
let (present_mode, image_count) = window::preferred_present_mode_and_image_count(
&loop_mode,
min_image_count,
user_specified_present_mode,
user_specified_image_count,
&capabilities.present_modes,
);
if window.swapchain.present_mode() != present_mode
|| window.swapchain.num_images() != image_count
{
change_loop_mode_for_window(window, &loop_mode);
}
}
}
run_loop(
app,
model,
self.event,
self.update,
self.default_view,
self.exit,
);
}
}
impl Builder<(), Event> {
pub fn sketch(view: SketchViewFn) {
let builder: Self = Builder {
model: default_model,
event: None,
update: None,
default_view: Some(View::Sketch(view)),
exit: None,
create_default_window: true,
vk_instance: None,
vk_debug_callback: None,
#[cfg(all(target_os = "macos", not(test)))]
moltenvk_settings: None,
};
builder.run()
}
}
fn update_interval(fps: f64) -> Duration {
assert!(fps > 0.0);
const NANOSEC_PER_SEC: f64 = 1_000_000_000.0;
let interval_nanosecs = NANOSEC_PER_SEC / fps;
let secs = (interval_nanosecs / NANOSEC_PER_SEC) as u64;
let nanosecs = (interval_nanosecs % NANOSEC_PER_SEC) as u32;
Duration::new(secs, nanosecs)
}
impl LoopMode {
pub const DEFAULT_RATE_FPS: f64 = 60.0;
pub const DEFAULT_UPDATES_FOLLOWING_EVENT: usize = 3;
pub fn rate_fps(fps: f64) -> Self {
let update_interval = update_interval(fps);
LoopMode::Rate { update_interval }
}
pub fn wait(updates_following_event: usize) -> Self {
let update_interval = update_interval(Self::DEFAULT_RATE_FPS);
LoopMode::Wait {
updates_following_event,
update_interval,
}
}
pub fn wait_with_max_fps(updates_following_event: usize, max_fps: f64) -> Self {
let update_interval = update_interval(max_fps);
LoopMode::Wait {
updates_following_event,
update_interval,
}
}
pub fn wait_with_interval(updates_following_event: usize, update_interval: Duration) -> Self {
LoopMode::Wait {
updates_following_event,
update_interval,
}
}
pub fn loop_ntimes(number_of_updates: usize) -> Self {
let update_interval = update_interval(Self::DEFAULT_RATE_FPS);
LoopMode::NTimes {
number_of_updates,
update_interval,
}
}
pub fn loop_once() -> Self {
let update_interval = update_interval(Self::DEFAULT_RATE_FPS);
let number_of_updates = 1;
LoopMode::NTimes {
number_of_updates,
update_interval,
}
}
pub fn refresh_sync() -> Self {
LoopMode::RefreshSync {
minimum_update_interval: update_interval(Self::DEFAULT_RATE_FPS * 2.0),
windows: None,
}
}
}
impl Default for LoopMode {
fn default() -> Self {
LoopMode::refresh_sync()
}
}
impl Default for Config {
fn default() -> Self {
let loop_mode = Default::default();
let exit_on_escape = App::DEFAULT_EXIT_ON_ESCAPE;
let fullscreen_on_shortcut = App::DEFAULT_FULLSCREEN_ON_SHORTCUT;
Config {
loop_mode,
exit_on_escape,
fullscreen_on_shortcut,
}
}
}
impl App {
pub const ASSETS_DIRECTORY_NAME: &'static str = "assets";
pub const DEFAULT_EXIT_ON_ESCAPE: bool = true;
pub const DEFAULT_FULLSCREEN_ON_SHORTCUT: bool = true;
pub(super) fn new(
events_loop: winit::EventsLoop,
vk_instance: Arc<vk::Instance>,
) -> Result<Self, vk::InstanceCreationError> {
let windows = RefCell::new(HashMap::new());
let draw = RefCell::new(draw::Draw::default());
let config = RefCell::new(Default::default());
let renderers = RefCell::new(Default::default());
let supported_depth_formats = RefCell::new(HashMap::new());
let draw_state = DrawState {
draw,
renderers,
supported_depth_formats,
};
let focused_window = RefCell::new(None);
let ui = ui::Arrangement::new();
let mouse = state::Mouse::new();
let keys = state::Keys::default();
let duration = state::Time::default();
let time = duration.since_start.secs() as _;
let events_loop_is_asleep = Arc::new(AtomicBool::new(false));
let app = App {
vk_instance,
events_loop,
events_loop_is_asleep,
focused_window,
windows,
config,
draw_state,
ui,
mouse,
keys,
duration,
time,
};
Ok(app)
}
pub fn vk_instance(&self) -> &Arc<vk::instance::Instance> {
&self.vk_instance
}
pub fn vk_physical_devices(&self) -> vk::instance::PhysicalDevicesIter {
vk::instance::PhysicalDevice::enumerate(&self.vk_instance)
}
pub fn default_vk_physical_device(&self) -> Option<vk::instance::PhysicalDevice> {
self.vk_physical_devices().next()
}
pub fn assets_path(&self) -> Result<PathBuf, find_folder::Error> {
find_assets_path()
}
pub fn new_window(&self) -> window::Builder {
window::Builder::new(self)
}
pub fn window_count(&self) -> usize {
self.windows.borrow().len()
}
pub fn window(&self, id: window::Id) -> Option<std::cell::Ref<Window>> {
let windows = self.windows.borrow();
if !windows.contains_key(&id) {
None
} else {
Some(std::cell::Ref::map(windows, |ws| &ws[&id]))
}
}
pub fn window_id(&self) -> window::Id {
self.focused_window
.borrow()
.expect("called `App::window_id` but there is no window currently in focus")
}
pub fn window_ids(&self) -> Vec<window::Id> {
let windows = self.windows.borrow();
windows.keys().cloned().collect()
}
pub fn window_rect(&self) -> geom::Rect<DrawScalar> {
let (w, h) = self.main_window().inner_size_points();
geom::Rect::from_w_h(w as _, h as _)
}
pub fn main_window(&self) -> std::cell::Ref<Window> {
self.window(self.window_id())
.expect("no window for focused id")
}
pub fn exit_on_escape(&self) -> bool {
self.config.borrow().exit_on_escape
}
pub fn set_exit_on_escape(&self, b: bool) {
self.config.borrow_mut().exit_on_escape = b;
}
pub fn fullscreen_on_shortcut(&self) -> bool {
self.config.borrow().fullscreen_on_shortcut
}
pub fn set_fullscreen_on_shortcut(&self, b: bool) {
self.config.borrow_mut().fullscreen_on_shortcut = b;
}
pub fn loop_mode(&self) -> LoopMode {
self.config.borrow().loop_mode.clone()
}
pub fn set_loop_mode(&self, mode: LoopMode) {
self.config.borrow_mut().loop_mode = mode;
}
pub fn create_proxy(&self) -> Proxy {
let events_loop_proxy = self.events_loop.create_proxy();
let events_loop_is_asleep = self.events_loop_is_asleep.clone();
Proxy {
events_loop_proxy,
events_loop_is_asleep,
}
}
pub fn new_ui(&self) -> ui::Builder {
ui::Builder::new(self)
}
pub fn draw_for_window(&self, window_id: window::Id) -> Option<Draw> {
let window = match self.window(window_id) {
None => return None,
Some(window) => window,
};
let queue = window.swapchain_queue().clone();
let device = queue.device().clone();
let color_format = crate::frame::COLOR_FORMAT;
let mut supported_depth_formats = self.draw_state.supported_depth_formats.borrow_mut();
let depth_format = *supported_depth_formats
.entry(device.physical_device().index())
.or_insert_with(|| {
find_draw_depth_format(device)
.expect("no supported vulkan depth format for the App's `Draw` API")
});
let draw = self.draw_state.draw.borrow_mut();
draw.reset();
let renderers = self.draw_state.renderers.borrow_mut();
let renderer = RefMut::map(renderers, |renderers| {
renderers.entry(window_id).or_insert_with(|| {
let msaa_samples = window.msaa_samples();
let (px_w, px_h) = window.inner_size_pixels();
let glyph_cache_dims = [px_w, px_h];
let renderer = draw::backend::vulkano::Renderer::new(
queue,
color_format,
depth_format,
msaa_samples,
glyph_cache_dims,
)
.expect("failed to create `Draw` renderer for vulkano backend");
RefCell::new(renderer)
})
});
Some(Draw {
window_id,
draw,
renderer,
depth_format,
})
}
pub fn draw(&self) -> Draw {
self.draw_for_window(self.window_id())
.expect("no window open for `app.window_id`")
}
pub fn elapsed_frames(&self) -> u64 {
self.main_window().frame_count
}
pub fn fps(&self) -> f32 {
self.duration.updates_per_second()
}
}
impl Proxy {
pub fn wakeup(&self) -> Result<(), winit::EventsLoopClosed> {
if self.events_loop_is_asleep.load(atomic::Ordering::Relaxed) {
self.events_loop_proxy.wakeup()?;
self.events_loop_is_asleep
.store(false, atomic::Ordering::Relaxed);
}
Ok(())
}
}
impl<'a> Draw<'a> {
pub fn to_frame(
&self,
app: &App,
frame: &Frame,
) -> Result<(), draw::backend::vulkano::DrawError> {
let window = app
.window(self.window_id)
.expect("no window to draw to for `app::Draw`'s window_id");
assert_eq!(
self.window_id,
frame.window_id(),
"attempted to draw content intended for window {:?} in a frame \
associated with window {:?}",
self.window_id,
frame.window_id(),
);
let dpi_factor = window.hidpi_factor();
let mut renderer = self.renderer.borrow_mut();
renderer.draw_to_frame(&self.draw, dpi_factor, frame, self.depth_format)
}
}
impl<'a> Deref for Draw<'a> {
type Target = RefMut<'a, draw::Draw<DrawScalar>>;
fn deref(&self) -> &Self::Target {
&self.draw
}
}
impl<'a> DerefMut for Draw<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.draw
}
}
pub fn find_assets_path() -> Result<PathBuf, find_folder::Error> {
let exe_path = std::env::current_exe()?;
find_folder::Search::ParentsThenKids(5, 3)
.of(exe_path
.parent()
.expect("executable has no parent directory to search")
.into())
.for_folder(App::ASSETS_DIRECTORY_NAME)
}
pub fn find_draw_depth_format(device: Arc<vk::Device>) -> Option<vk::Format> {
let candidates = [
vk::Format::D32Sfloat,
vk::Format::D32Sfloat_S8Uint,
vk::Format::D24Unorm_S8Uint,
vk::Format::D16Unorm,
vk::Format::D16Unorm_S8Uint,
];
vk::find_supported_depth_image_format(device, &candidates)
}
fn run_loop<M, E>(
mut app: App,
mut model: M,
event_fn: Option<EventFn<M, E>>,
update_fn: Option<UpdateFn<M>>,
default_view: Option<View<M>>,
exit_fn: Option<ExitFn<M>>,
) where
M: 'static,
E: LoopEvent,
{
let loop_start = Instant::now();
let mut loop_ctxt = LoopContext {
event_fn,
update_fn,
default_view,
loop_start,
winit_events: vec![],
last_update: loop_start,
last_loop_end: loop_start,
updates_remaining: LoopMode::DEFAULT_UPDATES_FOLLOWING_EVENT,
};
let mut loop_mode = app.loop_mode();
'mode: loop {
let Break {
model: new_model,
reason,
} = match loop_mode {
LoopMode::Rate { update_interval } => {
run_loop_mode_rate(&mut app, model, &mut loop_ctxt, update_interval)
}
LoopMode::Wait {
updates_following_event,
update_interval,
} => {
loop_ctxt.updates_remaining = updates_following_event;
run_loop_mode_wait(
&mut app,
model,
&mut loop_ctxt,
updates_following_event,
update_interval,
)
}
LoopMode::NTimes {
number_of_updates,
update_interval,
} => {
loop_ctxt.updates_remaining = number_of_updates;
run_loop_mode_ntimes(&mut app, model, &mut loop_ctxt, update_interval)
}
LoopMode::RefreshSync {
minimum_update_interval,
windows,
} => run_loop_mode_refresh_sync(
&mut app,
model,
&mut loop_ctxt,
minimum_update_interval,
windows,
),
};
model = new_model;
match reason {
BreakReason::NewLoopMode(new_loop_mode) => {
loop_mode = new_loop_mode;
change_loop_mode(&app, &loop_mode);
}
BreakReason::Exit => {
if let Some(exit_fn) = exit_fn {
exit_fn(&app, model);
}
return;
}
}
}
}
fn run_loop_mode_rate<M, E>(
app: &mut App,
mut model: M,
loop_ctxt: &mut LoopContext<M, E>,
mut update_interval: Duration,
) -> Break<M>
where
M: 'static,
E: LoopEvent,
{
loop {
app.events_loop
.poll_events(|event| loop_ctxt.winit_events.push(event));
for winit_event in loop_ctxt.winit_events.drain(..) {
let (new_model, exit) =
process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event);
model = new_model;
if exit {
let reason = BreakReason::Exit;
return Break { model, reason };
}
}
let now = Instant::now();
let since_last = now.duration_since(loop_ctxt.last_update).into();
let since_start = now.duration_since(loop_ctxt.loop_start).into();
app.duration.since_start = since_start;
app.duration.since_prev_update = since_last;
app.time = app.duration.since_start.secs() as _;
let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update);
if let Some(event_fn) = loop_ctxt.event_fn {
let event = E::from(update.clone());
event_fn(&app, &mut model, event);
}
if let Some(update_fn) = loop_ctxt.update_fn {
update_fn(&app, &mut model, update);
}
for window_id in app.window_ids() {
acquire_image_and_view_frame(app, window_id, &model, loop_ctxt.default_view.as_ref());
}
let now = Instant::now();
let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end);
if since_last_loop_end < update_interval {
std::thread::sleep(update_interval - since_last_loop_end);
}
loop_ctxt.last_loop_end = Instant::now();
update_interval = match app.loop_mode() {
LoopMode::Rate { update_interval } => update_interval,
loop_mode => {
let reason = BreakReason::NewLoopMode(loop_mode);
return Break { model, reason };
}
};
}
}
fn run_loop_mode_wait<M, E>(
app: &mut App,
mut model: M,
loop_ctxt: &mut LoopContext<M, E>,
mut updates_following_event: usize,
mut update_interval: Duration,
) -> Break<M>
where
M: 'static,
E: LoopEvent,
{
loop {
app.events_loop
.poll_events(|event| loop_ctxt.winit_events.push(event));
if loop_ctxt.winit_events.is_empty() && loop_ctxt.updates_remaining == 0 {
let events_loop_is_asleep = app.events_loop_is_asleep.clone();
events_loop_is_asleep.store(true, atomic::Ordering::Relaxed);
app.events_loop.run_forever(|event| {
events_loop_is_asleep.store(false, atomic::Ordering::Relaxed);
loop_ctxt.winit_events.push(event);
winit::ControlFlow::Break
});
}
if !loop_ctxt.winit_events.is_empty() {
loop_ctxt.updates_remaining = updates_following_event;
}
for winit_event in loop_ctxt.winit_events.drain(..) {
let (new_model, exit) =
process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event);
model = new_model;
if exit {
let reason = BreakReason::Exit;
return Break { model, reason };
}
}
let now = Instant::now();
let since_last = now.duration_since(loop_ctxt.last_update).into();
let since_start = now.duration_since(loop_ctxt.loop_start).into();
app.duration.since_start = since_start;
app.duration.since_prev_update = since_last;
app.time = app.duration.since_start.secs() as _;
let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update);
if let Some(event_fn) = loop_ctxt.event_fn {
let event = E::from(update.clone());
event_fn(&app, &mut model, event);
}
if let Some(update_fn) = loop_ctxt.update_fn {
update_fn(&app, &mut model, update);
}
loop_ctxt.updates_remaining -= 1;
for window_id in app.window_ids() {
acquire_image_and_view_frame(app, window_id, &model, loop_ctxt.default_view.as_ref());
}
let now = Instant::now();
let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end);
if since_last_loop_end < update_interval {
std::thread::sleep(update_interval - since_last_loop_end);
}
loop_ctxt.last_loop_end = Instant::now();
match app.loop_mode() {
LoopMode::Wait {
update_interval: ui,
updates_following_event: ufe,
} => {
update_interval = ui;
updates_following_event = ufe;
}
loop_mode => {
let reason = BreakReason::NewLoopMode(loop_mode);
return Break { model, reason };
}
};
}
}
fn run_loop_mode_ntimes<M, E>(
app: &mut App,
mut model: M,
loop_ctxt: &mut LoopContext<M, E>,
mut update_interval: Duration,
) -> Break<M>
where
M: 'static,
E: LoopEvent,
{
loop {
app.events_loop
.poll_events(|event| loop_ctxt.winit_events.push(event));
if loop_ctxt.winit_events.is_empty() && loop_ctxt.updates_remaining == 0 {
let events_loop_is_asleep = app.events_loop_is_asleep.clone();
events_loop_is_asleep.store(true, atomic::Ordering::Relaxed);
app.events_loop.run_forever(|event| {
events_loop_is_asleep.store(false, atomic::Ordering::Relaxed);
loop_ctxt.winit_events.push(event);
winit::ControlFlow::Break
});
}
let now = Instant::now();
let since_last = now.duration_since(loop_ctxt.last_update).into();
let since_start = now.duration_since(loop_ctxt.loop_start).into();
app.duration.since_start = since_start;
app.duration.since_prev_update = since_last;
app.time = app.duration.since_start.secs() as _;
for winit_event in loop_ctxt.winit_events.drain(..) {
let (new_model, exit) =
process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event);
model = new_model;
if exit {
let reason = BreakReason::Exit;
return Break { model, reason };
}
}
if loop_ctxt.updates_remaining > 0 {
let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update);
if let Some(event_fn) = loop_ctxt.event_fn {
let event = E::from(update.clone());
event_fn(&app, &mut model, event);
}
if let Some(update_fn) = loop_ctxt.update_fn {
update_fn(&app, &mut model, update);
}
loop_ctxt.updates_remaining -= 1;
for window_id in app.window_ids() {
acquire_image_and_view_frame(
app,
window_id,
&model,
loop_ctxt.default_view.as_ref(),
);
}
}
let now = Instant::now();
let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end);
if since_last_loop_end < update_interval {
std::thread::sleep(update_interval - since_last_loop_end);
}
loop_ctxt.last_loop_end = Instant::now();
match app.loop_mode() {
LoopMode::NTimes {
update_interval: ui,
..
} => {
update_interval = ui;
}
loop_mode => {
let reason = BreakReason::NewLoopMode(loop_mode);
return Break { model, reason };
}
};
}
}
fn run_loop_mode_refresh_sync<M, E>(
app: &mut App,
mut model: M,
loop_ctxt: &mut LoopContext<M, E>,
mut minimum_update_interval: Duration,
mut windows: Option<HashSet<window::Id>>,
) -> Break<M>
where
M: 'static,
E: LoopEvent,
{
loop {
for window_id in app.window_ids() {
if app.window(window_id).is_none() {
continue;
}
cleanup_unused_gpu_resources_for_window(app, window_id);
loop {
if window_swapchain_needs_recreation(app, window_id) {
match recreate_window_swapchain(app, window_id) {
Ok(()) => break,
Err(vk::SwapchainCreationError::UnsupportedDimensions) => {
set_window_swapchain_needs_recreation(app, window_id, true);
continue;
}
Err(err) => panic!("{:?}", err),
}
}
break;
}
let timeout = None;
let swapchain = app.windows.borrow()[&window_id].swapchain.clone();
let next_img = vk::swapchain::acquire_next_image(swapchain.swapchain.clone(), timeout);
let (swapchain_image_index, swapchain_image_acquire_future) = match next_img {
Ok(r) => r,
Err(vk::swapchain::AcquireError::OutOfDate) => {
set_window_swapchain_needs_recreation(app, window_id, true);
continue;
}
Err(err) => panic!("{:?}", err),
};
let (new_model, exit, event_count) = poll_and_process_events(app, model, loop_ctxt);
model = new_model;
if exit {
let reason = BreakReason::Exit;
return Break { model, reason };
}
let now = Instant::now();
let since_last = now.duration_since(loop_ctxt.last_update).into();
let should_emit_update = windows
.as_ref()
.map(|ws| ws.contains(&window_id))
.unwrap_or(true)
&& (event_count > 0 || since_last > minimum_update_interval);
if should_emit_update {
let since_start = now.duration_since(loop_ctxt.loop_start).into();
app.duration.since_start = since_start;
app.duration.since_prev_update = since_last;
app.time = app.duration.since_start.secs() as _;
let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update);
if let Some(event_fn) = loop_ctxt.event_fn {
let event = E::from(update.clone());
event_fn(&app, &mut model, event);
}
if let Some(update_fn) = loop_ctxt.update_fn {
update_fn(&app, &mut model, update);
}
}
if app.window(window_id).is_none() {
continue;
}
view_frame(
app,
&model,
window_id,
swapchain_image_index,
swapchain_image_acquire_future,
loop_ctxt.default_view.as_ref(),
);
}
loop_ctxt.last_loop_end = Instant::now();
match app.loop_mode() {
LoopMode::RefreshSync {
minimum_update_interval: mli,
windows: w,
} => {
minimum_update_interval = mli;
windows = w;
}
loop_mode => {
let reason = BreakReason::NewLoopMode(loop_mode);
return Break { model, reason };
}
}
}
}
fn change_loop_mode(app: &App, loop_mode: &LoopMode) {
let mut windows = app.windows.borrow_mut();
for window in windows.values_mut() {
change_loop_mode_for_window(window, loop_mode);
}
}
fn change_loop_mode_for_window(window: &mut Window, loop_mode: &LoopMode) {
let device = window.swapchain.swapchain.device().clone();
let surface = window.surface.clone();
let queue = window.queue.clone();
let mut swapchain_builder =
window::SwapchainBuilder::from_swapchain(&window.swapchain.swapchain);
swapchain_builder.present_mode = window.user_specified_present_mode;
swapchain_builder.image_count = window.user_specified_image_count;
let (new_swapchain, new_swapchain_images) = swapchain_builder
.build(
device,
surface,
&queue,
&loop_mode,
None,
Some(&window.swapchain.swapchain),
)
.expect("failed to recreate swapchain for new `LoopMode`");
window.replace_swapchain(new_swapchain, new_swapchain_images);
}
fn cleanup_unused_gpu_resources_for_window(app: &App, window_id: window::Id) {
let windows = app.windows.borrow();
let mut guard = windows[&window_id]
.swapchain
.previous_frame_end
.lock()
.expect("failed to lock `previous_frame_end`");
if let Some(future) = guard.as_mut() {
future.cleanup_finished();
}
}
fn window_swapchain_needs_recreation(app: &App, window_id: window::Id) -> bool {
let windows = app.windows.borrow();
let window = &windows[&window_id];
window
.swapchain
.needs_recreation
.load(atomic::Ordering::Relaxed)
}
fn recreate_window_swapchain(
app: &App,
window_id: window::Id,
) -> Result<(), vk::SwapchainCreationError> {
let mut windows = app.windows.borrow_mut();
let window = windows.get_mut(&window_id).expect("no window for id");
let dimensions = window
.surface
.capabilities(window.swapchain.device().physical_device())
.expect("failed to get surface capabilities")
.current_extent
.expect("current_extent was `None`");
let (new_swapchain, new_images) = window
.swapchain
.swapchain
.recreate_with_dimension(dimensions)?;
window.replace_swapchain(new_swapchain, new_images);
Ok(())
}
fn set_window_swapchain_needs_recreation(app: &App, window_id: window::Id, b: bool) {
let windows = app.windows.borrow_mut();
let window = windows.get(&window_id).expect("no window for id");
window
.swapchain
.needs_recreation
.store(b, atomic::Ordering::Relaxed);
}
fn poll_and_process_events<M, E>(
app: &mut App,
mut model: M,
loop_ctxt: &mut LoopContext<M, E>,
) -> (M, bool, usize)
where
M: 'static,
E: LoopEvent,
{
let mut event_count = 0;
app.events_loop
.poll_events(|event| loop_ctxt.winit_events.push(event));
for winit_event in loop_ctxt.winit_events.drain(..) {
event_count += 1;
let (new_model, exit) =
process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event);
model = new_model;
if exit {
return (model, exit, event_count);
}
}
(model, false, event_count)
}
fn should_toggle_fullscreen(winit_event: &winit::WindowEvent) -> bool {
let input = match *winit_event {
winit::WindowEvent::KeyboardInput { ref input, .. } => match input.state {
event::ElementState::Pressed => input,
_ => return false,
},
_ => return false,
};
let key = match input.virtual_keycode {
None => return false,
Some(k) => k,
};
let mods = &input.modifiers;
if cfg!(target_os = "linux") {
if !mods.logo && !mods.shift && !mods.alt && !mods.ctrl {
if let Key::F11 = key {
return true;
}
}
} else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
if mods.logo && !mods.shift && !mods.alt && !mods.ctrl {
if let Key::F = key {
return true;
}
}
}
false
}
fn update_event(loop_start: Instant, last_update: &mut Instant) -> event::Update {
let now = Instant::now();
let since_last = now.duration_since(*last_update).into();
let since_start = now.duration_since(loop_start).into();
let update = event::Update {
since_last,
since_start,
};
*last_update = now;
update
}
fn process_and_emit_winit_event<M, E>(
app: &mut App,
mut model: M,
event_fn: Option<EventFn<M, E>>,
winit_event: winit::Event,
) -> (M, bool)
where
M: 'static,
E: LoopEvent,
{
let mut exit_on_escape = false;
let mut removed_window = None;
if let winit::Event::WindowEvent {
window_id,
ref event,
} = winit_event
{
if app.exit_on_escape() {
if let winit::WindowEvent::KeyboardInput { input, .. } = *event {
if let Some(Key::Escape) = input.virtual_keycode {
exit_on_escape = true;
}
}
}
fn remove_related_window_state(app: &App, window_id: &window::Id) -> Option<Window> {
app.draw_state.renderers.borrow_mut().remove(window_id);
app.windows.borrow_mut().remove(window_id)
}
if let winit::WindowEvent::Destroyed = *event {
removed_window = remove_related_window_state(app, &window_id);
} else if let winit::WindowEvent::CloseRequested = *event {
removed_window = remove_related_window_state(app, &window_id);
} else {
let (win_w, win_h) = match app.window(window_id) {
Some(win) => {
if app.fullscreen_on_shortcut() {
if should_toggle_fullscreen(event) {
if win.is_fullscreen() {
win.set_fullscreen(None);
} else {
let monitor = win.current_monitor();
win.set_fullscreen(Some(monitor));
}
}
}
win.inner_size_points()
}
None => (0.0, 0.0),
};
let tx = |x: geom::scalar::Default| x - win_w as geom::scalar::Default / 2.0;
let ty = |y: geom::scalar::Default| -(y - win_h as geom::scalar::Default / 2.0);
if *app.focused_window.borrow() != Some(window_id) {
if app.window(window_id).is_some() {
*app.focused_window.borrow_mut() = Some(window_id);
}
}
match *event {
winit::WindowEvent::CursorMoved { position, .. } => {
let (x, y): (f64, f64) = position.into();
let x = tx(x as _);
let y = ty(y as _);
app.mouse.x = x;
app.mouse.y = y;
app.mouse.window = Some(window_id);
}
winit::WindowEvent::MouseInput { state, button, .. } => {
match state {
event::ElementState::Pressed => {
let p = app.mouse.position();
app.mouse.buttons.press(button, p);
}
event::ElementState::Released => {
app.mouse.buttons.release(button);
}
}
app.mouse.window = Some(window_id);
}
winit::WindowEvent::KeyboardInput { input, .. } => {
app.keys.mods = input.modifiers;
if let Some(key) = input.virtual_keycode {
match input.state {
event::ElementState::Pressed => {
app.keys.down.keys.insert(key);
}
event::ElementState::Released => {
app.keys.down.keys.remove(&key);
}
}
}
}
_ => (),
}
if let Some(window) = app.windows.borrow().get(&window_id) {
if let Some(input) = ui::winit_window_event_to_input(event.clone(), window) {
if let Some(handles) = app.ui.windows.borrow().get(&window_id) {
for handle in handles {
if let Some(ref tx) = handle.input_tx {
tx.try_send(input.clone()).ok();
}
}
}
}
}
}
}
if let Some(event_fn) = event_fn {
if let Some(event) = E::from_winit_event(winit_event.clone(), app) {
event_fn(&app, &mut model, event);
}
}
if let winit::Event::WindowEvent { window_id, event } = winit_event {
if let Some(raw_window_event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.raw_event.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.raw_event.clone())
})
} {
let raw_window_event_fn = raw_window_event_fn
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window event function");
(*raw_window_event_fn)(&app, &mut model, event.clone());
}
let (win_w, win_h) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.surface.window().get_inner_size().map(|size| size.into()))
.unwrap_or((0f64, 0f64))
};
if let Some(simple) = event::WindowEvent::from_winit_window_event(event, win_w, win_h) {
if let Some(window_event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.event.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.event.clone())
})
} {
let window_event_fn = window_event_fn
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window event function");
(*window_event_fn)(&app, &mut model, simple.clone());
}
macro_rules! call_user_function {
($fn_name:ident $(,$arg:expr)*) => {{
if let Some(event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.$fn_name.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.$fn_name.clone())
})
} {
let event_fn = event_fn
.to_fn_ptr::<M>()
.unwrap_or_else(|| {
panic!(
"unexpected model argument given to {} function",
stringify!($fn_name),
);
});
(*event_fn)(&app, &mut model, $($arg),*);
}
}};
}
match simple {
event::WindowEvent::KeyPressed(key) => call_user_function!(key_pressed, key),
event::WindowEvent::KeyReleased(key) => call_user_function!(key_released, key),
event::WindowEvent::MouseMoved(pos) => call_user_function!(mouse_moved, pos),
event::WindowEvent::MousePressed(button) => {
call_user_function!(mouse_pressed, button)
}
event::WindowEvent::MouseReleased(button) => {
call_user_function!(mouse_released, button)
}
event::WindowEvent::MouseEntered => call_user_function!(mouse_entered),
event::WindowEvent::MouseExited => call_user_function!(mouse_exited),
event::WindowEvent::MouseWheel(amount, phase) => {
call_user_function!(mouse_wheel, amount, phase)
}
event::WindowEvent::Moved(pos) => call_user_function!(moved, pos),
event::WindowEvent::Resized(size) => call_user_function!(resized, size),
event::WindowEvent::Touch(touch) => call_user_function!(touch, touch),
event::WindowEvent::TouchPressure(pressure) => {
call_user_function!(touchpad_pressure, pressure)
}
event::WindowEvent::HoveredFile(path) => call_user_function!(hovered_file, path),
event::WindowEvent::HoveredFileCancelled => {
call_user_function!(hovered_file_cancelled)
}
event::WindowEvent::DroppedFile(path) => call_user_function!(dropped_file, path),
event::WindowEvent::Focused => call_user_function!(focused),
event::WindowEvent::Unfocused => call_user_function!(unfocused),
event::WindowEvent::Closed => call_user_function!(closed),
}
}
}
let exit = if exit_on_escape || app.windows.borrow().is_empty() {
true
} else {
false
};
(model, exit)
}
fn view_frame<M>(
app: &mut App,
model: &M,
window_id: window::Id,
swapchain_image_index: usize,
swapchain_image_acquire_future: window::SwapchainAcquireFuture,
default_view: Option<&View<M>>,
) where
M: 'static,
{
let (queue, swapchain) = {
let windows = app.windows.borrow();
let window = &windows[&window_id];
(window.queue.clone(), window.swapchain.clone())
};
let (swapchain_image, nth_frame, swapchain_frame_created) = {
let mut windows = app.windows.borrow_mut();
let window = windows.get_mut(&window_id).expect("no window for id");
let swapchain_image = window.swapchain.images[swapchain_image_index].clone();
let frame_count = window.frame_count;
let swapchain_frame_created = window.swapchain.frame_created;
window.frame_count += 1;
(swapchain_image, frame_count, swapchain_frame_created)
};
let raw_frame = RawFrame::new_empty(
queue.clone(),
window_id,
nth_frame,
swapchain_image_index,
swapchain_image,
swapchain_frame_created,
)
.expect("failed to create `Frame`");
let window_view = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.view.clone())
};
fn take_window_frame_render_data(app: &App, window_id: window::Id) -> Option<RenderData> {
let mut windows = app.windows.borrow_mut();
windows
.get_mut(&window_id)
.and_then(|w| w.frame_render_data.take())
}
fn set_window_frame_render_data(app: &App, window_id: window::Id, render_data: RenderData) {
let mut windows = app.windows.borrow_mut();
if let Some(window) = windows.get_mut(&window_id) {
window.frame_render_data = Some(render_data);
}
}
let command_buffer = match window_view {
Some(window::View::Sketch(view)) => {
let render_data = take_window_frame_render_data(app, window_id)
.expect("failed to take window's `frame_render_data`");
let frame = Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`");
view(app, &frame);
let (render_data, raw_frame) = frame
.finish()
.expect("failed to resolve frame's intermediary_image to the swapchain_image");
set_window_frame_render_data(app, window_id, render_data);
raw_frame
.finish()
.build()
.expect("failed to build command buffer")
}
Some(window::View::WithModel(view)) => {
let render_data = take_window_frame_render_data(app, window_id)
.expect("failed to take window's `frame_render_data`");
let frame = Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`");
let view = view
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window view function");
(*view)(app, model, &frame);
let (render_data, raw_frame) = frame
.finish()
.expect("failed to resolve frame's intermediary_image to the swapchain_image");
set_window_frame_render_data(app, window_id, render_data);
raw_frame
.finish()
.build()
.expect("failed to build command buffer")
}
Some(window::View::WithModelRaw(raw_view)) => {
let raw_view = raw_view
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window raw_view function");
(*raw_view)(app, model, &raw_frame);
raw_frame
.finish()
.build()
.expect("failed to build command buffer")
}
None => match default_view {
Some(View::Sketch(view)) => {
let render_data = take_window_frame_render_data(app, window_id)
.expect("failed to take window's `frame_render_data`");
let frame =
Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`");
view(app, &frame);
let (render_data, raw_frame) = frame
.finish()
.expect("failed to resolve frame's intermediary_image to the swapchain_image");
set_window_frame_render_data(app, window_id, render_data);
raw_frame
.finish()
.build()
.expect("failed to build command buffer")
}
Some(View::WithModel(view)) => {
let render_data = take_window_frame_render_data(app, window_id)
.expect("failed to take window's `frame_render_data`");
let frame =
Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`");
view(app, &model, &frame);
let (render_data, raw_frame) = frame
.finish()
.expect("failed to resolve frame's intermediary_image to the swapchain_image");
set_window_frame_render_data(app, window_id, render_data);
raw_frame
.finish()
.build()
.expect("failed to build command buffer")
}
None => raw_frame
.finish()
.build()
.expect("failed to build command buffer"),
},
};
let mut windows = app.windows.borrow_mut();
let window = windows.get_mut(&window_id).expect("no window for id");
if let Some(mut previous_frame_fence_signal_future) = window
.swapchain
.previous_frame_end
.lock()
.expect("failed to lock `previous_frame_end`")
.take()
{
previous_frame_fence_signal_future.cleanup_finished();
previous_frame_fence_signal_future
.wait(None)
.expect("failed to wait for `previous_frame_end` future to signal fence");
}
let future_result = {
let present_future = swapchain_image_acquire_future
.then_execute(queue.clone(), command_buffer)
.expect("failed to execute future")
.then_swapchain_present(
queue.clone(),
swapchain.swapchain.clone(),
swapchain_image_index,
);
(Box::new(present_future) as Box<dyn GpuFuture>).then_signal_fence_and_flush()
};
let current_frame_end = match future_result {
Ok(future) => Some(future),
Err(vk::sync::FlushError::OutOfDate) => {
window
.swapchain
.needs_recreation
.store(true, atomic::Ordering::Relaxed);
None
}
Err(e) => {
println!("{:?}", e);
None
}
};
*window
.swapchain
.previous_frame_end
.lock()
.expect("failed to acquire `previous_frame_end` lock") = current_frame_end;
}
fn acquire_image_and_view_frame<M>(
app: &mut App,
window_id: window::Id,
model: &M,
view: Option<&View<M>>,
) -> bool
where
M: 'static,
{
if app.window(window_id).is_none() {
return false;
}
cleanup_unused_gpu_resources_for_window(app, window_id);
loop {
if window_swapchain_needs_recreation(app, window_id) {
match recreate_window_swapchain(app, window_id) {
Ok(()) => break,
Err(vk::SwapchainCreationError::UnsupportedDimensions) => {
set_window_swapchain_needs_recreation(app, window_id, true);
continue;
}
Err(err) => panic!("{:?}", err),
}
}
break;
}
let timeout = None;
let swapchain = app.windows.borrow()[&window_id].swapchain.clone();
let next_img = vk::swapchain::acquire_next_image(swapchain.swapchain.clone(), timeout);
let (swapchain_image_index, swapchain_image_acquire_future) = match next_img {
Ok(r) => r,
Err(vk::swapchain::AcquireError::OutOfDate) => {
set_window_swapchain_needs_recreation(app, window_id, true);
return false;
}
Err(err) => panic!("{:?}", err),
};
view_frame(
app,
model,
window_id,
swapchain_image_index,
swapchain_image_acquire_future,
view,
);
true
}