Skip to content
Permalink
Browse files

Re-write main loop for power efficiency

This change gets rid of the `input/delay` setting, improves
input latency and greatly reduces CPU utilization, even with
animations running.
  • Loading branch information
cloudhead committed Jan 12, 2020
1 parent b31237a commit 2789fa0c6a23c53c3328accdc408a471124519aa
Showing with 189 additions and 189 deletions.
  1. +0 −1 config/init.rx
  2. +8 −0 src/execution.rs
  3. +2 −2 src/gl/mod.rs
  4. +128 −106 src/lib.rs
  5. +26 −56 src/platform/glfw.rs
  6. +2 −18 src/platform/mod.rs
  7. +2 −2 src/renderer.rs
  8. +21 −4 src/session.rs
@@ -53,7 +53,6 @@ set scale = 1.0 -- Set interface
set checker = off -- Turn off alpha checker
set debug = off -- Turn off debug mode
set vsync = off -- Turn off Vsync
set input/delay = 8.0 -- Set frame delay to 8 milliseconds
set background = #000000 -- Set background appearance to black

brush/set perfect -- Set brush to "pixel-perfect" mode
@@ -259,6 +259,14 @@ impl Execution {
}
}

pub fn is_recording(&self) -> bool {
if let Execution::Recording { .. } = self {
true
} else {
false
}
}

pub fn record(&mut self, data: &[Bgra8]) {
match self {
// Replaying and verifying digests.
@@ -260,8 +260,8 @@ impl Error for RendererError {
}

impl renderer::Renderer for Renderer {
fn new<T>(
win: &mut platform::backend::Window<T>,
fn new(
win: &mut platform::backend::Window,
win_size: LogicalSize,
scale_factor: f64,
_present_mode: PresentMode,
@@ -6,6 +6,7 @@
clippy::useless_format,
clippy::new_without_default,
clippy::cognitive_complexity,
clippy::comparison_chain,
clippy::type_complexity,
clippy::or_fun_call,
clippy::nonminimal_bool,
@@ -64,7 +65,7 @@ use std::alloc::System;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time;
use std::time::{Duration, Instant};

/// Program version.
pub const VERSION: &str = "0.3.2";
@@ -112,7 +113,8 @@ pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()
WindowHint::Resizable(options.resizable),
WindowHint::Visible(!options.headless),
];
let (mut win, events) = platform::init("rx", options.width, options.height, hints, context)?;
let (mut win, mut events) =
platform::init("rx", options.width, options.height, hints, context)?;

let scale_factor = win.scale_factor();
let win_size = win.size();
@@ -140,10 +142,6 @@ pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()
}

if let ExecutionMode::Record(_, _, GifMode::Record) = options.exec {
session
.settings
.set("input/delay", Value::F64(0.0))
.expect("'input/delay' is a float");
session
.settings
.set("vsync", Value::Bool(true))
@@ -164,10 +162,6 @@ pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()
Execution::Replaying { digest, .. } | Execution::Recording { digest, .. }
if digest.mode != DigestMode::Ignore =>
{
session
.settings
.set("input/delay", Value::F64(0.0))
.expect("'input/delay' is a float");
session
.settings
.set("vsync", Value::Bool(false))
@@ -180,6 +174,7 @@ pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()
_ => {}
}

let wait_events = exec.is_normal() || exec.is_recording();
let execution = Rc::new(RefCell::new(exec));
let present_mode = session.settings.present_mode();

@@ -195,118 +190,145 @@ pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()
let mut render_timer = FrameTimer::new();
let mut update_timer = FrameTimer::new();
let mut session_events = Vec::with_capacity(16);
let mut last = time::Instant::now();
let mut last = Instant::now();
let mut resized = false;

let exit = platform::run(win, events, move |w, event| {
// Don't process events while the window is minimized.
if w.size().is_zero() {
return platform::ControlFlow::Wait;
}
if event.is_input() {
debug!("event: {:?}", event);
// Accumulated error from animation timeout.
let mut anim_accum = Duration::from_secs(0);

while !win.is_closing() {
if wait_events {
let start = Instant::now();

match session.animation_delay() {
Some(delay) if session.is_running() => {
if delay > anim_accum {
events.wait_timeout(delay - anim_accum);
} else {
events.poll();
}
// How much time has actually passed waiting for events.
let d = start.elapsed();

if d > delay {
// If more time has passed than the desired animation delay, then
// add the difference to our accumulated error.
anim_accum += d - delay;
} else if delay > d {
// If less time has passed than our desired delay, then
// reset the accumulator to zero, because we've overshot.
anim_accum = Duration::from_secs(0);
};
}
_ => events.wait(),
}
} else {
events.poll();
}

match event {
WindowEvent::Resized(size) => {
// It's possible that the above check for zero size is delayed
// by a frame, in which case we need to catch things here.
if size.is_zero() {
for event in events.flush() {
if event.is_input() {
debug!("event: {:?}", event);
}

match event {
WindowEvent::Resized(size) => {
if size.is_zero() {
// On certain operating systems, the window size will be set to
// zero when the window is minimized. Since a zero-sized framebuffer
// is not valid, we pause the session until the window is restored.
session.transition(State::Paused);
} else {
resized = true;
session.transition(State::Running);
}
}
WindowEvent::CursorEntered { .. } => {
win.set_cursor_visible(false);
}
WindowEvent::CursorLeft { .. } => {
win.set_cursor_visible(true);
}
WindowEvent::Minimized => {
session.transition(State::Paused);
return platform::ControlFlow::Wait;
} else {
}
WindowEvent::Restored => {
session.transition(State::Running);
resized = true;
}
}
WindowEvent::CursorEntered { .. } => {
// TODO: [winit] This doesn't fire if the cursor is already
// in the window.
w.set_cursor_visible(false);
}
WindowEvent::CursorLeft { .. } => {
w.set_cursor_visible(true);
}
WindowEvent::Ready => {
if resized {
session.handle_resized(w.size());
resized = false;
} else {
let input_delay: f64 = session.settings["input/delay"].clone().into();
std::thread::sleep(time::Duration::from_micros((input_delay * 1000.) as u64));
WindowEvent::Focused(true) => {
session.transition(State::Running);
}
WindowEvent::Focused(false) => {
session.transition(State::Paused);
}
WindowEvent::RedrawRequested => {
// TODO: On windows, this is the only thing called during
// resize.
}
WindowEvent::ScaleFactorChanged(factor) => {
renderer.handle_scale_factor_changed(factor);
}
WindowEvent::CloseRequested => {
session.quit(ExitReason::Normal);
}
WindowEvent::CursorMoved { position } => {
session_events.push(Event::CursorMoved(position));
}
WindowEvent::MouseInput { state, button, .. } => {
session_events.push(Event::MouseInput(button, state));
}
WindowEvent::MouseWheel { delta, .. } => {
session_events.push(Event::MouseWheel(delta));
}
WindowEvent::KeyboardInput(input) => {
session_events.push(Event::KeyboardInput(input));
}
WindowEvent::ReceivedCharacter(c) => {
session_events.push(Event::ReceivedCharacter(c));
}
_ => {}
};
}

let delta = last.elapsed();
last = time::Instant::now();
if resized {
// Instead of responded to each resize event by creating a new framebuffer,
// we respond to the event *once*, here.
resized = false;
session.handle_resized(win.size());
}

// If we're paused, we want to keep the timer running to not get a
// "jump" when we unpause, but skip session updates and rendering.
if session.state == State::Paused {
return platform::ControlFlow::Wait;
}
let delta = last.elapsed();
last = Instant::now();

let effects = update_timer
.run(|avg| session.update(&mut session_events, execution.clone(), delta, avg));
render_timer.run(|avg| {
renderer.frame(&session, execution.clone(), effects, &avg);
});
// If we're paused, we want to keep the timer running to not get a
// "jump" when we unpause, but skip session updates and rendering.
if session.state == State::Paused {
continue;
}

session.cleanup();
let effects = update_timer
.run(|avg| session.update(&mut session_events, execution.clone(), delta, avg));
render_timer.run(|avg| {
renderer.frame(&session, execution.clone(), effects, &avg);
});

if session.settings_changed.contains("vsync") {
renderer.handle_present_mode_changed(session.settings.present_mode());
}
}
WindowEvent::Minimized => {
session.transition(State::Paused);
return platform::ControlFlow::Wait;
}
WindowEvent::Restored => {
session.transition(State::Running);
}
WindowEvent::Focused(true) => {
session.transition(State::Running);
}
WindowEvent::Focused(false) => {
session.transition(State::Paused);
}
WindowEvent::RedrawRequested => {
// We currently don't draw in here, as it negatively
// affects resize smoothness. (╯°□°)╯︵ ┻━┻
}
WindowEvent::ScaleFactorChanged(factor) => {
renderer.handle_scale_factor_changed(factor);
}
WindowEvent::CloseRequested => {
session.quit(ExitReason::Normal);
}
WindowEvent::CursorMoved { position } => {
session_events.push(Event::CursorMoved(position));
}
WindowEvent::MouseInput { state, button, .. } => {
session_events.push(Event::MouseInput(button, state));
}
WindowEvent::MouseWheel { delta, .. } => {
session_events.push(Event::MouseWheel(delta));
}
WindowEvent::KeyboardInput(input) => {
session_events.push(Event::KeyboardInput(input));
session.cleanup();
win.present();

if session.settings_changed.contains("vsync") {
renderer.handle_present_mode_changed(session.settings.present_mode());
}

match session.state {
State::Closing(ExitReason::Normal) => {
return Ok(());
}
WindowEvent::ReceivedCharacter(c) => {
session_events.push(Event::ReceivedCharacter(c));
State::Closing(ExitReason::Error(e)) => {
return Err(io::Error::new(io::ErrorKind::Other, e));
}
_ => {}
};

if let State::Closing(reason) = &session.state {
platform::ControlFlow::Exit(reason.clone())
} else {
platform::ControlFlow::Continue
}
});

match exit {
ExitReason::Normal => Ok(()),
ExitReason::Error(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
}

Ok(())
}

0 comments on commit 2789fa0

Please sign in to comment.
You can’t perform that action at this time.