@@ -6,9 +6,8 @@ namespace rack { | |||||
namespace widget { | namespace widget { | ||||
/** Caches a widget's draw() result to a framebuffer so it is called less frequently. | |||||
When `dirty` is true, its children will be re-rendered on the next call to step(). | |||||
Events are not passed to the underlying scene. | |||||
/** Caches its children's draw() result to a framebuffer image. | |||||
When dirty, its children will be re-rendered on the next call to step(). | |||||
*/ | */ | ||||
struct FramebufferWidget : Widget { | struct FramebufferWidget : Widget { | ||||
struct Internal; | struct Internal; | ||||
@@ -82,7 +82,7 @@ struct Window { | |||||
void setFullScreen(bool fullScreen); | void setFullScreen(bool fullScreen); | ||||
bool isFullScreen(); | bool isFullScreen(); | ||||
double getMonitorRefreshRate(); | double getMonitorRefreshRate(); | ||||
double getLastFrameTime(); | |||||
double getFrameTime(); | |||||
double getLastFrameDuration(); | double getLastFrameDuration(); | ||||
double getFrameTimeOverdue(); | double getFrameTimeOverdue(); | ||||
@@ -73,17 +73,17 @@ struct CableContainer : widget::TransparentWidget { | |||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
railFb = new widget::FramebufferWidget; | |||||
railFb->box.size = math::Vec(); | |||||
railFb->oversample = 1.0; | |||||
// Don't redraw when the world offset of the rail FramebufferWidget changes its fractional value. | |||||
railFb->dirtyOnSubpixelChange = false; | |||||
{ | |||||
RackRail* rail = new RackRail; | |||||
rail->box.size = math::Vec(); | |||||
railFb->addChild(rail); | |||||
} | |||||
addChild(railFb); | |||||
// railFb = new widget::FramebufferWidget; | |||||
// railFb->box.size = math::Vec(); | |||||
// railFb->oversample = 1.0; | |||||
// // Don't redraw when the world offset of the rail FramebufferWidget changes its fractional value. | |||||
// railFb->dirtyOnSubpixelChange = false; | |||||
// { | |||||
// RackRail* rail = new RackRail; | |||||
// rail->box.size = math::Vec(); | |||||
// railFb->addChild(rail); | |||||
// } | |||||
// addChild(railFb); | |||||
moduleContainer = new ModuleContainer; | moduleContainer = new ModuleContainer; | ||||
addChild(moduleContainer); | addChild(moduleContainer); | ||||
@@ -106,16 +106,16 @@ void RackWidget::draw(const DrawArgs& args) { | |||||
nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1)); | nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1)); | ||||
// Resize and reposition the RackRail to align on the grid. | // Resize and reposition the RackRail to align on the grid. | ||||
math::Rect railBox; | |||||
railBox.pos = args.clipBox.pos.div(BUS_BOARD_GRID_SIZE).floor().mult(BUS_BOARD_GRID_SIZE); | |||||
railBox.size = args.clipBox.size.div(BUS_BOARD_GRID_SIZE).ceil().plus(math::Vec(1, 1)).mult(BUS_BOARD_GRID_SIZE); | |||||
if (!railFb->box.size.equals(railBox.size)) { | |||||
railFb->dirty = true; | |||||
} | |||||
railFb->box = railBox; | |||||
RackRail* rail = railFb->getFirstDescendantOfType<RackRail>(); | |||||
rail->box.size = railFb->box.size; | |||||
// math::Rect railBox; | |||||
// railBox.pos = args.clipBox.pos.div(BUS_BOARD_GRID_SIZE).floor().mult(BUS_BOARD_GRID_SIZE); | |||||
// railBox.size = args.clipBox.size.div(BUS_BOARD_GRID_SIZE).ceil().plus(math::Vec(1, 1)).mult(BUS_BOARD_GRID_SIZE); | |||||
// if (!railFb->box.size.equals(railBox.size)) { | |||||
// railFb->dirty = true; | |||||
// } | |||||
// railFb->box = railBox; | |||||
// RackRail* rail = railFb->getFirstDescendantOfType<RackRail>(); | |||||
// rail->box.size = railFb->box.size; | |||||
Widget::draw(args); | Widget::draw(args); | ||||
} | } | ||||
@@ -111,7 +111,7 @@ void Scene::step() { | |||||
// Autosave periodically | // Autosave periodically | ||||
if (settings::autosaveInterval > 0.0) { | if (settings::autosaveInterval > 0.0) { | ||||
double time = glfwGetTime(); | |||||
double time = system::getTime(); | |||||
if (time - lastAutosaveTime >= settings::autosaveInterval) { | if (time - lastAutosaveTime >= settings::autosaveInterval) { | ||||
lastAutosaveTime = time; | lastAutosaveTime = time; | ||||
APP->patch->saveAutosave(); | APP->patch->saveAutosave(); | ||||
@@ -95,10 +95,6 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
if (APP->window->getFrameTimeOverdue() > 0.0) | if (APP->window->getFrameTimeOverdue() > 0.0) | ||||
return; | return; | ||||
// Check that scale has been set by `draw()` yet. | |||||
if (scale.isZero()) | |||||
return; | |||||
// In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here. | // In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here. | ||||
dirty = false; | dirty = false; | ||||
NVGcontext* vg = APP->window->vg; | NVGcontext* vg = APP->window->vg; | ||||
@@ -134,8 +130,8 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
} | } | ||||
// Create a framebuffer | // Create a framebuffer | ||||
if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) { | if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) { | ||||
// DEBUG("Creating framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize)); | |||||
internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0); | internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0); | ||||
// DEBUG("Created framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize)); | |||||
} | } | ||||
} | } | ||||
if (!internal->fb) { | if (!internal->fb) { | ||||
@@ -143,7 +139,7 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
return; | return; | ||||
} | } | ||||
DEBUG("Drawing to framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize)); | |||||
// DEBUG("Drawing to framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize)); | |||||
// Render to framebuffer | // Render to framebuffer | ||||
if (oversample == 1.0) { | if (oversample == 1.0) { | ||||
@@ -156,6 +152,7 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
NVGLUframebuffer* fb = internal->fb; | NVGLUframebuffer* fb = internal->fb; | ||||
// If oversampling, create another framebuffer and copy it to actual size. | // If oversampling, create another framebuffer and copy it to actual size. | ||||
math::Vec oversampledFbSize = internal->fbSize.mult(oversample).ceil(); | math::Vec oversampledFbSize = internal->fbSize.mult(oversample).ceil(); | ||||
// DEBUG("Creating %0.fx oversampled framebuffer of size (%f, %f)", oversample, VEC_ARGS(internal->fbSize)); | |||||
NVGLUframebuffer* oversampledFb = nvgluCreateFramebuffer(fbVg, oversampledFbSize.x, oversampledFbSize.y, 0); | NVGLUframebuffer* oversampledFb = nvgluCreateFramebuffer(fbVg, oversampledFbSize.x, oversampledFbSize.y, 0); | ||||
if (!oversampledFb) { | if (!oversampledFb) { | ||||
@@ -170,9 +167,8 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
internal->fb = fb; | internal->fb = fb; | ||||
nvgluBindFramebuffer(NULL); | nvgluBindFramebuffer(NULL); | ||||
// Use NanoVG for resizing framebuffers | |||||
// Use NanoVG for copying oversampled framebuffer to normal framebuffer | |||||
nvgluBindFramebuffer(internal->fb); | nvgluBindFramebuffer(internal->fb); | ||||
nvgBeginFrame(fbVg, internal->fbBox.size.x, internal->fbBox.size.y, 1.0); | nvgBeginFrame(fbVg, internal->fbBox.size.x, internal->fbBox.size.y, 1.0); | ||||
// Draw oversampled framebuffer | // Draw oversampled framebuffer | ||||
@@ -205,11 +201,7 @@ void FramebufferWidget::draw(const DrawArgs& args) { | |||||
nvgSave(args.vg); | nvgSave(args.vg); | ||||
nvgResetTransform(args.vg); | nvgResetTransform(args.vg); | ||||
math::Vec scaleRatio = math::Vec(1, 1); | |||||
if (!internal->fbScale.isZero() && !scale.equals(internal->fbScale)) { | |||||
// Continue to draw with the last framebuffer, but stretch it to rescale. | |||||
scaleRatio = scale.div(internal->fbScale); | |||||
} | |||||
math::Vec scaleRatio = scale.div(internal->fbScale); | |||||
// DEBUG("%f %f %f %f", scaleRatio.x, scaleRatio.y, offsetF.x, offsetF.y); | // DEBUG("%f %f %f %f", scaleRatio.x, scaleRatio.y, offsetF.x, offsetF.y); | ||||
// DEBUG("%f %f %f %f, %f %f", RECT_ARGS(internal->fbBox), VEC_ARGS(internal->fbSize)); | // DEBUG("%f %f %f %f, %f %f", RECT_ARGS(internal->fbBox), VEC_ARGS(internal->fbSize)); | ||||
@@ -2,6 +2,7 @@ | |||||
#include <widget/Widget.hpp> | #include <widget/Widget.hpp> | ||||
#include <context.hpp> | #include <context.hpp> | ||||
#include <window.hpp> | #include <window.hpp> | ||||
#include <system.hpp> | |||||
namespace rack { | namespace rack { | ||||
@@ -159,7 +160,7 @@ bool EventState::handleButton(math::Vec pos, int button, int action, int mods) { | |||||
if (action == GLFW_PRESS) { | if (action == GLFW_PRESS) { | ||||
const double doubleClickDuration = 0.3; | const double doubleClickDuration = 0.3; | ||||
double clickTime = glfwGetTime(); | |||||
double clickTime = system::getTime(); | |||||
if (clickedWidget | if (clickedWidget | ||||
&& clickTime - lastClickTime <= doubleClickDuration | && clickTime - lastClickTime <= doubleClickDuration | ||||
&& lastClickedWidget == clickedWidget) { | && lastClickedWidget == clickedWidget) { | ||||
@@ -81,8 +81,8 @@ struct Window::Internal { | |||||
bool ignoreNextMouseDelta = false; | bool ignoreNextMouseDelta = false; | ||||
int frameSwapInterval = -1; | int frameSwapInterval = -1; | ||||
double monitorRefreshRate = 0.0; | double monitorRefreshRate = 0.0; | ||||
double frameTime = 0.0; | |||||
double lastFrameDuration = 0.0; | double lastFrameDuration = 0.0; | ||||
double lastFrameTime = 0.0; | |||||
math::Vec lastMousePos; | math::Vec lastMousePos; | ||||
@@ -387,10 +387,10 @@ void Window::run() { | |||||
void Window::step() { | void Window::step() { | ||||
double frameTime = glfwGetTime(); | |||||
internal->lastFrameDuration = frameTime - internal->lastFrameTime; | |||||
double lastFrameTime = internal->frameTime; | |||||
internal->frameTime = system::getTime(); | |||||
internal->lastFrameDuration = internal->frameTime - lastFrameTime; | |||||
// DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration); | // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration); | ||||
internal->lastFrameTime = frameTime; | |||||
// Make event handlers and step() have a clean NanoVG context | // Make event handlers and step() have a clean NanoVG context | ||||
nvgReset(vg); | nvgReset(vg); | ||||
@@ -540,7 +540,7 @@ void Window::screenshotModules(const std::string& screenshotsDir, float zoom) { | |||||
zw->addChild(mw); | zw->addChild(mw); | ||||
// HACK: Set the frame time so FramebufferWidgets are never overdue and therefore guaranteed to draw | // HACK: Set the frame time so FramebufferWidgets are never overdue and therefore guaranteed to draw | ||||
internal->lastFrameTime = INFINITY; | |||||
internal->frameTime = INFINITY; | |||||
// Draw to framebuffer | // Draw to framebuffer | ||||
fbw->step(); | fbw->step(); | ||||
@@ -636,8 +636,8 @@ double Window::getMonitorRefreshRate() { | |||||
} | } | ||||
double Window::getLastFrameTime() { | |||||
return internal->lastFrameTime; | |||||
double Window::getFrameTime() { | |||||
return internal->frameTime; | |||||
} | } | ||||
@@ -648,7 +648,7 @@ double Window::getLastFrameDuration() { | |||||
double Window::getFrameTimeOverdue() { | double Window::getFrameTimeOverdue() { | ||||
double desiredFrameDuration = internal->frameSwapInterval / internal->monitorRefreshRate; | double desiredFrameDuration = internal->frameSwapInterval / internal->monitorRefreshRate; | ||||
double frameDuration = glfwGetTime() - internal->lastFrameTime; | |||||
double frameDuration = system::getTime() - internal->frameTime; | |||||
return frameDuration - desiredFrameDuration; | return frameDuration - desiredFrameDuration; | ||||
} | } | ||||