Browse Source

Make FramebufferWidget draw the framebuffer in draw() instead of step().

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
241511876e
4 changed files with 136 additions and 134 deletions
  1. +0
    -1
      include/widget/FramebufferWidget.hpp
  2. +1
    -0
      include/window.hpp
  3. +121
    -130
      src/widget/FramebufferWidget.cpp
  4. +14
    -3
      src/window.cpp

+ 0
- 1
include/widget/FramebufferWidget.hpp View File

@@ -31,7 +31,6 @@ struct FramebufferWidget : Widget {
int getImageHandle(); int getImageHandle();
NVGLUframebuffer* getFramebuffer(); NVGLUframebuffer* getFramebuffer();
math::Vec getFramebufferSize(); math::Vec getFramebufferSize();
void setScale(math::Vec scale);
void onContextCreate(const ContextCreateEvent& e) override; void onContextCreate(const ContextCreateEvent& e) override;
void onContextDestroy(const ContextDestroyEvent& e) override; void onContextDestroy(const ContextDestroyEvent& e) override;
}; };


+ 1
- 0
include/window.hpp View File

@@ -50,6 +50,7 @@ struct Window {


GLFWwindow* win = NULL; GLFWwindow* win = NULL;
NVGcontext* vg = NULL; NVGcontext* vg = NULL;
NVGcontext* fbVg = NULL;
/** The scaling ratio */ /** The scaling ratio */
float pixelRatio = 1.f; float pixelRatio = 1.f;
/* The ratio between the framebuffer size and the window size reported by the OS. /* The ratio between the framebuffer size and the window size reported by the OS.


+ 121
- 130
src/widget/FramebufferWidget.cpp View File

@@ -10,15 +10,6 @@ namespace widget {
struct FramebufferWidget::Internal { struct FramebufferWidget::Internal {
NVGLUframebuffer* fb = NULL; NVGLUframebuffer* fb = NULL;


// Set by draw()

/** Scale relative to the world */
math::Vec scale;
/** Subpixel offset in world coordinates */
math::Vec offsetF;

// Set by step() and drawFramebuffer()

/** Pixel dimensions of the allocated framebuffer */ /** Pixel dimensions of the allocated framebuffer */
math::Vec fbSize; math::Vec fbSize;
/** Bounding box in world coordinates of where the framebuffer should be painted. /** Bounding box in world coordinates of where the framebuffer should be painted.
@@ -60,115 +51,11 @@ void FramebufferWidget::onDirty(const DirtyEvent& e) {


void FramebufferWidget::step() { void FramebufferWidget::step() {
Widget::step(); Widget::step();

// It's more important to not lag the frame than to draw the framebuffer
if (APP->window->getFrameTimeOverdue() > 0.0)
return;

// Check that scale has been set by `draw()` yet.
if (internal->scale.isZero())
return;

// Only redraw if FramebufferWidget is dirty
if (!dirty)
return;

// In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here.
dirty = false;
NVGcontext* vg = APP->window->vg;

internal->fbScale = internal->scale;
internal->fbOffsetF = internal->offsetF;

math::Rect localBox;
if (children.empty()) {
localBox = box.zeroPos();
}
else {
localBox = getVisibleChildrenBoundingBox();
}

// DEBUG("rendering FramebufferWidget localBox (%g %g %g %g) fbOffset (%g %g) fbScale (%g %g)", RECT_ARGS(localBox), VEC_ARGS(internal->fbOffsetF), VEC_ARGS(internal->fbScale));
// Transform to world coordinates, then expand to nearest integer coordinates
math::Vec min = localBox.getTopLeft().mult(internal->fbScale).plus(internal->fbOffsetF).floor();
math::Vec max = localBox.getBottomRight().mult(internal->fbScale).plus(internal->fbOffsetF).ceil();
internal->fbBox = math::Rect::fromMinMax(min, max);
// DEBUG("%g %g %g %g", RECT_ARGS(internal->fbBox));

math::Vec newFbSize = internal->fbBox.size.mult(APP->window->pixelRatio).ceil();

// Create framebuffer if a new size is needed
if (!internal->fb || !newFbSize.equals(internal->fbSize)) {
internal->fbSize = newFbSize;
// Delete old framebuffer
if (internal->fb) {
nvgluDeleteFramebuffer(internal->fb);
internal->fb = NULL;
}
// Create a framebuffer
if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) {
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) {
WARN("Framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(internal->fbSize), this);
return;
}

// Render to framebuffer
if (oversample == 1.0) {
// If not oversampling, render directly to framebuffer.
nvgluBindFramebuffer(internal->fb);
drawFramebuffer();
nvgluBindFramebuffer(NULL);
}
else {
NVGLUframebuffer* fb = internal->fb;
// If oversampling, create another framebuffer and copy it to actual size.
math::Vec oversampledFbSize = internal->fbSize.mult(oversample).ceil();
NVGLUframebuffer* oversampledFb = nvgluCreateFramebuffer(vg, oversampledFbSize.x, oversampledFbSize.y, 0);

if (!oversampledFb) {
WARN("Oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(oversampledFbSize), this);
return;
}

// Render to oversampled framebuffer.
nvgluBindFramebuffer(oversampledFb);
internal->fb = oversampledFb;
drawFramebuffer();
internal->fb = fb;
nvgluBindFramebuffer(NULL);

// Use NanoVG for resizing framebuffers
nvgluBindFramebuffer(internal->fb);

nvgBeginFrame(vg, internal->fbBox.size.x, internal->fbBox.size.y, 1.0);

// Draw oversampled framebuffer
nvgBeginPath(vg);
nvgRect(vg, 0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
NVGpaint paint = nvgImagePattern(vg, 0.0, 0.0,
internal->fbSize.x, internal->fbSize.y,
0.0, oversampledFb->image, 1.0);
nvgFillPaint(vg, paint);
nvgFill(vg);

glViewport(0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgEndFrame(vg);
nvgReset(vg);

nvgluBindFramebuffer(NULL);
nvgluDeleteFramebuffer(oversampledFb);
}
} }




void FramebufferWidget::draw(const DrawArgs& args) { void FramebufferWidget::draw(const DrawArgs& args) {
// Draw directly if already drawing in a framebuffer
// Draw directly if bypassed or already drawing in a framebuffer
if (bypassed || args.fb) { if (bypassed || args.fb) {
Widget::draw(args); Widget::draw(args);
return; return;
@@ -184,30 +71,132 @@ void FramebufferWidget::draw(const DrawArgs& args) {
} }


// Extract scale and offset from world transform // Extract scale and offset from world transform
internal->scale = math::Vec(xform[0], xform[3]);
math::Vec scale = math::Vec(xform[0], xform[3]);
math::Vec offset = math::Vec(xform[4], xform[5]); math::Vec offset = math::Vec(xform[4], xform[5]);
math::Vec offsetI = offset.floor(); math::Vec offsetI = offset.floor();
internal->offsetF = offset.minus(offsetI);
math::Vec offsetF = offset.minus(offsetI);


// If drawing to a new subpixel location, rerender in the next frame. // If drawing to a new subpixel location, rerender in the next frame.
// Anything less than 0.1 pixels isn't noticeable. // Anything less than 0.1 pixels isn't noticeable.
math::Vec offsetFDelta = internal->offsetF.minus(internal->fbOffsetF);
math::Vec offsetFDelta = offsetF.minus(internal->fbOffsetF);
if (offsetFDelta.square() >= std::pow(0.1f, 2) && dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange()) { if (offsetFDelta.square() >= std::pow(0.1f, 2) && dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange()) {
// DEBUG("%p dirty subpixel (%f, %f) (%f, %f)", this, VEC_ARGS(internal->offsetF), VEC_ARGS(internal->fbOffsetF));
// DEBUG("%p dirty subpixel (%f, %f) (%f, %f)", this, VEC_ARGS(offsetF), VEC_ARGS(internal->fbOffsetF));
setDirty(); setDirty();
} }
if (!internal->scale.equals(internal->fbScale)) {
if (!scale.equals(internal->fbScale)) {
// If rescaled, rerender in the next frame. // If rescaled, rerender in the next frame.
// DEBUG("%p dirty scale", this); // DEBUG("%p dirty scale", this);
setDirty(); setDirty();
} }


math::Vec scaleRatio = math::Vec(1, 1);
if (!internal->fbScale.isZero() && !internal->scale.equals(internal->fbScale)) {
// Continue to draw with the last framebuffer, but stretch it to rescale.
scaleRatio = internal->scale.div(internal->fbScale);
// Draw to framebuffer if dirty
auto redraw = [&]() {
// It's more important to not lag the frame than to draw the framebuffer
if (APP->window->getFrameTimeOverdue() > 0.0)
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.
dirty = false;
NVGcontext* vg = APP->window->vg;
NVGcontext* fbVg = APP->window->fbVg;

internal->fbScale = scale;
internal->fbOffsetF = offsetF;

math::Rect localBox;
if (children.empty()) {
localBox = box.zeroPos();
}
else {
localBox = getVisibleChildrenBoundingBox();
}

// DEBUG("rendering FramebufferWidget localBox (%g %g %g %g) fbOffset (%g %g) fbScale (%g %g)", RECT_ARGS(localBox), VEC_ARGS(internal->fbOffsetF), VEC_ARGS(internal->fbScale));
// Transform to world coordinates, then expand to nearest integer coordinates
math::Vec min = localBox.getTopLeft().mult(internal->fbScale).plus(internal->fbOffsetF).floor();
math::Vec max = localBox.getBottomRight().mult(internal->fbScale).plus(internal->fbOffsetF).ceil();
internal->fbBox = math::Rect::fromMinMax(min, max);
// DEBUG("%g %g %g %g", RECT_ARGS(internal->fbBox));

math::Vec newFbSize = internal->fbBox.size.mult(APP->window->pixelRatio).ceil();

// Create framebuffer if a new size is needed
if (!internal->fb || !newFbSize.equals(internal->fbSize)) {
internal->fbSize = newFbSize;
// Delete old framebuffer
if (internal->fb) {
nvgluDeleteFramebuffer(internal->fb);
internal->fb = NULL;
}
// Create a framebuffer
if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) {
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) {
WARN("Framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(internal->fbSize), this);
return;
}

DEBUG("Drawing to framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize));

// Render to framebuffer
if (oversample == 1.0) {
// If not oversampling, render directly to framebuffer.
nvgluBindFramebuffer(internal->fb);
drawFramebuffer();
nvgluBindFramebuffer(NULL);
}
else {
NVGLUframebuffer* fb = internal->fb;
// If oversampling, create another framebuffer and copy it to actual size.
math::Vec oversampledFbSize = internal->fbSize.mult(oversample).ceil();
NVGLUframebuffer* oversampledFb = nvgluCreateFramebuffer(fbVg, oversampledFbSize.x, oversampledFbSize.y, 0);

if (!oversampledFb) {
WARN("Oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(oversampledFbSize), this);
return;
}

// Render to oversampled framebuffer.
nvgluBindFramebuffer(oversampledFb);
internal->fb = oversampledFb;
drawFramebuffer();
internal->fb = fb;
nvgluBindFramebuffer(NULL);

// Use NanoVG for resizing framebuffers
nvgluBindFramebuffer(internal->fb);

nvgBeginFrame(fbVg, internal->fbBox.size.x, internal->fbBox.size.y, 1.0);

// Draw oversampled framebuffer
nvgBeginPath(fbVg);
nvgRect(fbVg, 0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
NVGpaint paint = nvgImagePattern(fbVg, 0.0, 0.0,
internal->fbSize.x, internal->fbSize.y,
0.0, oversampledFb->image, 1.0);
nvgFillPaint(fbVg, paint);
nvgFill(fbVg);

glViewport(0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgEndFrame(fbVg);
nvgReset(fbVg);

nvgluBindFramebuffer(NULL);
nvgluDeleteFramebuffer(oversampledFb);
}
};
if (dirty) {
redraw();
} }
// DEBUG("%f %f %f %f", scaleRatio.x, scaleRatio.y, offsetF.x, offsetF.y);


if (!internal->fb) if (!internal->fb)
return; return;
@@ -216,6 +205,13 @@ 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);
}
// 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));
nvgBeginPath(args.vg); nvgBeginPath(args.vg);
nvgRect(args.vg, nvgRect(args.vg,
@@ -242,7 +238,7 @@ void FramebufferWidget::draw(const DrawArgs& args) {




void FramebufferWidget::drawFramebuffer() { void FramebufferWidget::drawFramebuffer() {
NVGcontext* vg = APP->window->vg;
NVGcontext* vg = APP->window->fbVg;
nvgSave(vg); nvgSave(vg);


float pixelRatio = internal->fbSize.x * oversample / internal->fbBox.size.x; float pixelRatio = internal->fbSize.x * oversample / internal->fbBox.size.x;
@@ -289,11 +285,6 @@ math::Vec FramebufferWidget::getFramebufferSize() {
} }




void FramebufferWidget::setScale(math::Vec scale) {
internal->scale = scale;
}


void FramebufferWidget::onContextCreate(const ContextCreateEvent& e) { void FramebufferWidget::onContextCreate(const ContextCreateEvent& e) {
setDirty(); setDirty();
Widget::onContextCreate(e); Widget::onContextCreate(e);


+ 14
- 3
src/window.cpp View File

@@ -318,6 +318,8 @@ Window::Window() {
int nvgFlags = NVG_ANTIALIAS; int nvgFlags = NVG_ANTIALIAS;
#if defined NANOVG_GL2 #if defined NANOVG_GL2
vg = nvgCreateGL2(nvgFlags); vg = nvgCreateGL2(nvgFlags);
// TODO Instead of creating a whole other NanoVG context, create one that uses the same fonts and framebuffers as `fb`.
fbVg = nvgCreateGL2(nvgFlags);
#elif defined NANOVG_GL3 #elif defined NANOVG_GL3
vg = nvgCreateGL3(nvgFlags); vg = nvgCreateGL3(nvgFlags);
#elif defined NANOVG_GLES2 #elif defined NANOVG_GLES2
@@ -328,6 +330,8 @@ Window::Window() {
exit(1); exit(1);
} }


// fbVg = nvgClone(vg);

// Load default Blendish font // Load default Blendish font
uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf")); uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
bndSetFont(uiFont->handle); bndSetFont(uiFont->handle);
@@ -345,8 +349,11 @@ Window::~Window() {
APP->scene->onContextDestroy(e); APP->scene->onContextDestroy(e);
} }


// nvgDeleteClone(fbVg);

#if defined NANOVG_GL2 #if defined NANOVG_GL2
nvgDeleteGL2(vg); nvgDeleteGL2(vg);
nvgDeleteGL2(fbVg);
#elif defined NANOVG_GL3 #elif defined NANOVG_GL3
nvgDeleteGL3(vg); nvgDeleteGL3(vg);
#elif defined NANOVG_GLES2 #elif defined NANOVG_GLES2
@@ -523,12 +530,16 @@ void Window::screenshotModules(const std::string& screenshotsDir, float zoom) {
// Create widgets // Create widgets
widget::FramebufferWidget* fbw = new widget::FramebufferWidget; widget::FramebufferWidget* fbw = new widget::FramebufferWidget;
fbw->oversample = 2; fbw->oversample = 2;
fbw->setScale(math::Vec(zoom, zoom));

widget::ZoomWidget* zw = new widget::ZoomWidget;
zw->setZoom(zoom);
fbw->addChild(zw);


app::ModuleWidget* mw = model->createModuleWidget(NULL); app::ModuleWidget* mw = model->createModuleWidget(NULL);
fbw->addChild(mw);
zw->box.size = mw->box.size.mult(zoom);
zw->addChild(mw);


// Hack 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->lastFrameTime = INFINITY;


// Draw to framebuffer // Draw to framebuffer


Loading…
Cancel
Save