Browse Source

Move most FramebufferWidget member fields to an opaque pointer.

tags/v2.0.0
Andrew Belt 5 years ago
parent
commit
c8e2b5fc4a
6 changed files with 128 additions and 84 deletions
  1. +6
    -15
      include/widget/FramebufferWidget.hpp
  2. +4
    -0
      include/widget/Widget.hpp
  3. +94
    -52
      src/widget/FramebufferWidget.cpp
  4. +1
    -0
      src/widget/OpenGlWidget.cpp
  5. +14
    -9
      src/widget/Widget.cpp
  6. +9
    -8
      src/window.cpp

+ 6
- 15
include/widget/FramebufferWidget.hpp View File

@@ -11,25 +11,13 @@ When `dirty` is true, its children will be re-rendered on the next call to step(
Events are not passed to the underlying scene. Events are not passed to the underlying scene.
*/ */
struct FramebufferWidget : Widget { struct FramebufferWidget : Widget {
struct Internal;
Internal* internal;

/** Set this to true to re-render the children to the framebuffer the next time it is drawn */ /** Set this to true to re-render the children to the framebuffer the next time it is drawn */
bool dirty = true; bool dirty = true;
bool bypass = false; bool bypass = false;
float oversample = 1.0; float oversample = 1.0;
NVGLUframebuffer* fb = NULL;
/** Scale relative to the world */
math::Vec scale;
/** Offset in world coordinates */
math::Vec offset;
/** Pixel dimensions of the allocated framebuffer */
math::Vec fbSize;
/** Bounding box in world coordinates of where the framebuffer should be painted.
Always has integer coordinates so that blitting framebuffers is pixel-perfect.
*/
math::Rect fbBox;
/** Framebuffer's scale relative to the world */
math::Vec fbScale;
/** Framebuffer's subpixel offset relative to fbBox in world coordinates */
math::Vec fbOffset;


FramebufferWidget(); FramebufferWidget();
~FramebufferWidget(); ~FramebufferWidget();
@@ -38,6 +26,9 @@ struct FramebufferWidget : Widget {
void draw(const DrawArgs& args) override; void draw(const DrawArgs& args) override;
virtual void drawFramebuffer(); virtual void drawFramebuffer();
int getImageHandle(); int getImageHandle();
NVGLUframebuffer* getFramebuffer();
math::Vec getFramebufferSize();
void setScale(math::Vec scale);
}; };






+ 4
- 0
include/widget/Widget.hpp View File

@@ -33,6 +33,10 @@ struct Widget {


virtual ~Widget(); virtual ~Widget();


void setBox(math::Rect box);
math::Rect getBox() {
return box;
}
void setPosition(math::Vec pos); void setPosition(math::Vec pos);
math::Vec getPosition() { math::Vec getPosition() {
return box.pos; return box.pos;


+ 94
- 52
src/widget/FramebufferWidget.cpp View File

@@ -7,19 +7,43 @@ namespace rack {
namespace widget { namespace widget {




struct FramebufferWidget::Internal {
NVGLUframebuffer* fb = NULL;
/** Scale relative to the world */
math::Vec scale;
/** Offset in world coordinates */
math::Vec offset;
/** Pixel dimensions of the allocated framebuffer */
math::Vec fbSize;
/** Bounding box in world coordinates of where the framebuffer should be painted.
Always has integer coordinates so that blitting framebuffers is pixel-perfect.
*/
math::Rect fbBox;
/** Framebuffer's scale relative to the world */
math::Vec fbScale;
/** Framebuffer's subpixel offset relative to fbBox in world coordinates */
math::Vec fbOffset;
};


FramebufferWidget::FramebufferWidget() { FramebufferWidget::FramebufferWidget() {
internal = new Internal;
} }



FramebufferWidget::~FramebufferWidget() { FramebufferWidget::~FramebufferWidget() {
if (fb)
nvgluDeleteFramebuffer(fb);
if (internal->fb)
nvgluDeleteFramebuffer(internal->fb);
delete internal;
} }



void FramebufferWidget::onDirty(const event::Dirty& e) { void FramebufferWidget::onDirty(const event::Dirty& e) {
dirty = true; dirty = true;
Widget::onDirty(e); Widget::onDirty(e);
} }



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


@@ -28,7 +52,7 @@ void FramebufferWidget::step() {
return; return;


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


// Only redraw if FramebufferWidget is dirty // Only redraw if FramebufferWidget is dirty
@@ -39,13 +63,13 @@ void FramebufferWidget::step() {
dirty = false; dirty = false;
NVGcontext* vg = APP->window->vg; NVGcontext* vg = APP->window->vg;


fbScale = scale;
internal->fbScale = internal->scale;
// Set scale to zero so we must wait for the next draw() call before drawing the framebuffer again. // Set scale to zero so we must wait for the next draw() call before drawing the framebuffer again.
// Otherwise, if the zoom level is changed while the FramebufferWidget is off-screen, the next draw() call will be skipped, the `dirty` flag will be true, and the framebuffer will be redrawn, but at the wrong scale, since it was not set in draw(). // Otherwise, if the zoom level is changed while the FramebufferWidget is off-screen, the next draw() call will be skipped, the `dirty` flag will be true, and the framebuffer will be redrawn, but at the wrong scale, since it was not set in draw().
scale = math::Vec();
internal->scale = math::Vec();
// Get subpixel offset in range [0, 1) // Get subpixel offset in range [0, 1)
math::Vec offsetI = offset.floor();
fbOffset = offset.minus(offsetI);
math::Vec offsetI = internal->offset.floor();
internal->fbOffset = internal->offset.minus(offsetI);


math::Rect localBox; math::Rect localBox;
if (children.empty()) { if (children.empty()) {
@@ -55,57 +79,57 @@ void FramebufferWidget::step() {
localBox = getChildrenBoundingBox(); localBox = getChildrenBoundingBox();
} }


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


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


// Create framebuffer if a new size is needed // Create framebuffer if a new size is needed
if (!fb || !newFbSize.isEqual(fbSize)) {
fbSize = newFbSize;
if (!internal->fb || !newFbSize.isEqual(internal->fbSize)) {
internal->fbSize = newFbSize;
// Delete old framebuffer // Delete old framebuffer
if (fb)
nvgluDeleteFramebuffer(fb);
if (internal->fb)
nvgluDeleteFramebuffer(internal->fb);
// Create a framebuffer at the oversampled size // Create a framebuffer at the oversampled size
if (fbSize.isFinite() && !fbSize.isZero())
fb = nvgluCreateFramebuffer(vg, fbSize.x * oversample, fbSize.y * oversample, 0);
if (internal->fbSize.isFinite() && !internal->fbSize.isZero())
internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x * oversample, internal->fbSize.y * oversample, 0);
} }


if (!fb) {
WARN("Framebuffer of size (%f, %f) * %f could not be created for FramebufferWidget.", VEC_ARGS(fbSize), oversample);
if (!internal->fb) {
WARN("Framebuffer of size (%f, %f) * %f could not be created for FramebufferWidget.", VEC_ARGS(internal->fbSize), oversample);
return; return;
} }


nvgluBindFramebuffer(fb);
nvgluBindFramebuffer(internal->fb);
drawFramebuffer(); drawFramebuffer();
nvgluBindFramebuffer(NULL); nvgluBindFramebuffer(NULL);


// If oversampling, create another framebuffer and copy it to actual size. // If oversampling, create another framebuffer and copy it to actual size.
if (oversample != 1.0) { if (oversample != 1.0) {
NVGLUframebuffer* newFb = nvgluCreateFramebuffer(vg, fbSize.x, fbSize.y, 0);
NVGLUframebuffer* newFb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0);
if (!newFb) { if (!newFb) {
WARN("Non-oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget.", VEC_ARGS(fbSize));
WARN("Non-oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget.", VEC_ARGS(internal->fbSize));
return; return;
} }


// Use NanoVG for resizing framebuffers // Use NanoVG for resizing framebuffers
nvgluBindFramebuffer(newFb); nvgluBindFramebuffer(newFb);


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


// Draw oversampled framebuffer // Draw oversampled framebuffer
nvgBeginPath(vg); nvgBeginPath(vg);
nvgRect(vg, 0.0, 0.0, fbSize.x, fbSize.y);
NVGpaint paint = nvgImagePattern(vg, 0.0, 0.0, fbSize.x, fbSize.y,
0.0, fb->image, 1.0);
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, internal->fb->image, 1.0);
nvgFillPaint(vg, paint); nvgFillPaint(vg, paint);
nvgFill(vg); nvgFill(vg);


glViewport(0.0, 0.0, fbSize.x, fbSize.y);
glViewport(0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
glClearColor(0.0, 0.0, 0.0, 0.0); glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgEndFrame(vg); nvgEndFrame(vg);
@@ -114,11 +138,12 @@ void FramebufferWidget::step() {
nvgluBindFramebuffer(NULL); nvgluBindFramebuffer(NULL);


// Swap the framebuffers // Swap the framebuffers
nvgluDeleteFramebuffer(fb);
fb = newFb;
nvgluDeleteFramebuffer(internal->fb);
internal->fb = newFb;
} }
} }



void FramebufferWidget::draw(const DrawArgs& args) { void FramebufferWidget::draw(const DrawArgs& args) {
// Draw directly if already drawing in a framebuffer // Draw directly if already drawing in a framebuffer
if (bypass || args.fb) { if (bypass || args.fb) {
@@ -135,18 +160,18 @@ void FramebufferWidget::draw(const DrawArgs& args) {
return; return;
} }
// Extract scale and offset from world transform // Extract scale and offset from world transform
scale = math::Vec(xform[0], xform[3]);
offset = math::Vec(xform[4], xform[5]);
math::Vec offsetI = offset.floor();
internal->scale = math::Vec(xform[0], xform[3]);
internal->offset = math::Vec(xform[4], xform[5]);
math::Vec offsetI = internal->offset.floor();


math::Vec scaleRatio = math::Vec(1, 1); math::Vec scaleRatio = math::Vec(1, 1);
if (!fbScale.isZero() && !scale.isEqual(fbScale)) {
if (!internal->fbScale.isZero() && !internal->scale.isEqual(internal->fbScale)) {
dirty = true; dirty = true;
// Continue to draw but at the wrong scale. In the next frame, the framebuffer will be redrawn. // Continue to draw but at the wrong scale. In the next frame, the framebuffer will be redrawn.
scaleRatio = scale.div(fbScale);
scaleRatio = internal->scale.div(internal->fbScale);
} }


if (!fb)
if (!internal->fb)
return; return;


// Draw framebuffer image, using world coordinates // Draw framebuffer image, using world coordinates
@@ -155,14 +180,14 @@ void FramebufferWidget::draw(const DrawArgs& args) {


nvgBeginPath(args.vg); nvgBeginPath(args.vg);
nvgRect(args.vg, nvgRect(args.vg,
offsetI.x + fbBox.pos.x,
offsetI.y + fbBox.pos.y,
fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y);
offsetI.x + internal->fbBox.pos.x,
offsetI.y + internal->fbBox.pos.y,
internal->fbBox.size.x * scaleRatio.x, internal->fbBox.size.y * scaleRatio.y);
NVGpaint paint = nvgImagePattern(args.vg, NVGpaint paint = nvgImagePattern(args.vg,
offsetI.x + fbBox.pos.x,
offsetI.y + fbBox.pos.y,
fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y,
0.0, fb->image, 1.0);
offsetI.x + internal->fbBox.pos.x,
offsetI.y + internal->fbBox.pos.y,
internal->fbBox.size.x * scaleRatio.x, internal->fbBox.size.y * scaleRatio.y,
0.0, internal->fb->image, 1.0);
nvgFillPaint(args.vg, paint); nvgFillPaint(args.vg, paint);
nvgFill(args.vg); nvgFill(args.vg);


@@ -174,24 +199,25 @@ void FramebufferWidget::draw(const DrawArgs& args) {
nvgRestore(args.vg); nvgRestore(args.vg);
} }



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


float pixelRatio = fbSize.x * oversample / fbBox.size.x;
nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, pixelRatio);
float pixelRatio = internal->fbSize.x * oversample / internal->fbBox.size.x;
nvgBeginFrame(vg, internal->fbBox.size.x, internal->fbBox.size.y, pixelRatio);


// Use local scaling // Use local scaling
nvgTranslate(vg, -fbBox.pos.x, -fbBox.pos.y);
nvgTranslate(vg, fbOffset.x, fbOffset.y);
nvgScale(vg, fbScale.x, fbScale.y);
nvgTranslate(vg, -internal->fbBox.pos.x, -internal->fbBox.pos.y);
nvgTranslate(vg, internal->fbOffset.x, internal->fbOffset.y);
nvgScale(vg, internal->fbScale.x, internal->fbScale.y);


DrawArgs args; DrawArgs args;
args.vg = vg; args.vg = vg;
args.clipBox = box.zeroPos(); args.clipBox = box.zeroPos();
args.fb = fb;
args.fb = internal->fb;
Widget::draw(args); Widget::draw(args);


glViewport(0.0, 0.0, fbSize.x * oversample, fbSize.y * oversample);
glViewport(0.0, 0.0, internal->fbSize.x * oversample, internal->fbSize.y * oversample);
glClearColor(0.0, 0.0, 0.0, 0.0); glClearColor(0.0, 0.0, 0.0, 0.0);
// glClearColor(0.0, 1.0, 1.0, 0.5); // glClearColor(0.0, 1.0, 1.0, 0.5);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@@ -201,10 +227,26 @@ void FramebufferWidget::drawFramebuffer() {
nvgReset(vg); nvgReset(vg);
} }



int FramebufferWidget::getImageHandle() { int FramebufferWidget::getImageHandle() {
if (!fb)
if (!internal->fb)
return -1; return -1;
return fb->image;
return internal->fb->image;
}


NVGLUframebuffer* FramebufferWidget::getFramebuffer() {
return internal->fb;
}


math::Vec FramebufferWidget::getFramebufferSize() {
return internal->fbSize;
}


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






+ 1
- 0
src/widget/OpenGlWidget.cpp View File

@@ -14,6 +14,7 @@ void OpenGlWidget::step() {




void OpenGlWidget::drawFramebuffer() { void OpenGlWidget::drawFramebuffer() {
math::Vec fbSize = getFramebufferSize();
glViewport(0.0, 0.0, fbSize.x, fbSize.y); glViewport(0.0, 0.0, fbSize.x, fbSize.y);
glClearColor(0.0, 0.0, 0.0, 1.0); glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);


+ 14
- 9
src/widget/Widget.cpp View File

@@ -14,11 +14,16 @@ Widget::~Widget() {
clearChildren(); clearChildren();
} }


void Widget::setBox(math::Rect box) {
setPosition(box.pos);
setSize(box.size);
}

void Widget::setPosition(math::Vec pos) { void Widget::setPosition(math::Vec pos) {
if (pos.isEqual(box.pos)) if (pos.isEqual(box.pos))
return; return;
box.pos = pos; box.pos = pos;
// event::Reposition
// Trigger Reposition event
event::Reposition eReposition; event::Reposition eReposition;
onReposition(eReposition); onReposition(eReposition);
} }
@@ -27,7 +32,7 @@ void Widget::setSize(math::Vec size) {
if (size.isEqual(box.size)) if (size.isEqual(box.size))
return; return;
box.size = size; box.size = size;
// event::Resize
// Trigger Resize event
event::Resize eResize; event::Resize eResize;
onResize(eResize); onResize(eResize);
} }
@@ -36,7 +41,7 @@ void Widget::show() {
if (visible) if (visible)
return; return;
visible = true; visible = true;
// event::Show
// Trigger Show event
event::Show eShow; event::Show eShow;
onShow(eShow); onShow(eShow);
} }
@@ -45,7 +50,7 @@ void Widget::hide() {
if (!visible) if (!visible)
return; return;
visible = false; visible = false;
// event::Hide
// Trigger Hide event
event::Hide eHide; event::Hide eHide;
onHide(eHide); onHide(eHide);
} }
@@ -94,7 +99,7 @@ void Widget::addChild(Widget* child) {
assert(!child->parent); assert(!child->parent);
child->parent = this; child->parent = this;
children.push_back(child); children.push_back(child);
// event::Add
// Trigger Add event
event::Add eAdd; event::Add eAdd;
child->onAdd(eAdd); child->onAdd(eAdd);
} }
@@ -104,7 +109,7 @@ void Widget::addChildBottom(Widget* child) {
assert(!child->parent); assert(!child->parent);
child->parent = this; child->parent = this;
children.push_front(child); children.push_front(child);
// event::Add
// Trigger Add event
event::Add eAdd; event::Add eAdd;
child->onAdd(eAdd); child->onAdd(eAdd);
} }
@@ -113,7 +118,7 @@ void Widget::removeChild(Widget* child) {
assert(child); assert(child);
// Make sure `this` is the child's parent // Make sure `this` is the child's parent
assert(child->parent == this); assert(child->parent == this);
// event::Remove
// Trigger Remove event
event::Remove eRemove; event::Remove eRemove;
child->onRemove(eRemove); child->onRemove(eRemove);
// Prepare to remove widget from the event state // Prepare to remove widget from the event state
@@ -128,7 +133,7 @@ void Widget::removeChild(Widget* child) {


void Widget::clearChildren() { void Widget::clearChildren() {
for (Widget* child : children) { for (Widget* child : children) {
// event::Remove
// Trigger Remove event
event::Remove eRemove; event::Remove eRemove;
child->onRemove(eRemove); child->onRemove(eRemove);
APP->event->finalizeWidget(child); APP->event->finalizeWidget(child);
@@ -143,7 +148,7 @@ void Widget::step() {
Widget* child = *it; Widget* child = *it;
// Delete children if a delete is requested // Delete children if a delete is requested
if (child->requestedDelete) { if (child->requestedDelete) {
// event::Remove
// Trigger Remove event
event::Remove eRemove; event::Remove eRemove;
child->onRemove(eRemove); child->onRemove(eRemove);
APP->event->finalizeWidget(child); APP->event->finalizeWidget(child);


+ 9
- 8
src/window.cpp View File

@@ -442,20 +442,21 @@ void Window::screenshot(float zoom) {
INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str()); INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());


// Create widgets // Create widgets
widget::FramebufferWidget* fbw = new widget::FramebufferWidget;
fbw->oversample = 2;
fbw->setScale(math::Vec(zoom, zoom));

app::ModuleWidget* mw = model->createModuleWidget(NULL); app::ModuleWidget* mw = model->createModuleWidget(NULL);
widget::FramebufferWidget* fb = new widget::FramebufferWidget;
fb->oversample = 2;
fb->addChild(mw);
fb->scale = math::Vec(zoom, zoom);
fbw->addChild(mw);


// Draw to framebuffer // Draw to framebuffer
frameTimeStart = glfwGetTime(); frameTimeStart = glfwGetTime();
fb->step();
nvgluBindFramebuffer(fb->fb);
fbw->step();
nvgluBindFramebuffer(fbw->getFramebuffer());


// Read pixels // Read pixels
int width, height; int width, height;
nvgImageSize(vg, fb->getImageHandle(), &width, &height);
nvgImageSize(vg, fbw->getImageHandle(), &width, &height);
uint8_t* data = new uint8_t[height * width * 4]; uint8_t* data = new uint8_t[height * width * 4];
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);


@@ -474,7 +475,7 @@ void Window::screenshot(float zoom) {
// Cleanup // Cleanup
delete[] data; delete[] data;
nvgluBindFramebuffer(NULL); nvgluBindFramebuffer(NULL);
delete fb;
delete fbw;
} }
} }
} }


Loading…
Cancel
Save