Browse Source

Move most FramebufferWidget member fields to an opaque pointer.

tags/v2.0.0
Andrew Belt 4 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.
*/
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 */
bool dirty = true;
bool bypass = false;
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();
@@ -38,6 +26,9 @@ struct FramebufferWidget : Widget {
void draw(const DrawArgs& args) override;
virtual void drawFramebuffer();
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();

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


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

@@ -7,19 +7,43 @@ namespace rack {
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() {
internal = new Internal;
}


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


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


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

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

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

// Only redraw if FramebufferWidget is dirty
@@ -39,13 +63,13 @@ void FramebufferWidget::step() {
dirty = false;
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.
// 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)
math::Vec offsetI = offset.floor();
fbOffset = offset.minus(offsetI);
math::Vec offsetI = internal->offset.floor();
internal->fbOffset = internal->offset.minus(offsetI);

math::Rect localBox;
if (children.empty()) {
@@ -55,57 +79,57 @@ void FramebufferWidget::step() {
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
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));

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
if (!fb || !newFbSize.isEqual(fbSize)) {
fbSize = newFbSize;
if (!internal->fb || !newFbSize.isEqual(internal->fbSize)) {
internal->fbSize = newFbSize;
// Delete old framebuffer
if (fb)
nvgluDeleteFramebuffer(fb);
if (internal->fb)
nvgluDeleteFramebuffer(internal->fb);
// 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;
}

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

// If oversampling, create another framebuffer and copy it to actual size.
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) {
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;
}

// Use NanoVG for resizing framebuffers
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
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);
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);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgEndFrame(vg);
@@ -114,11 +138,12 @@ void FramebufferWidget::step() {
nvgluBindFramebuffer(NULL);

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


void FramebufferWidget::draw(const DrawArgs& args) {
// Draw directly if already drawing in a framebuffer
if (bypass || args.fb) {
@@ -135,18 +160,18 @@ void FramebufferWidget::draw(const DrawArgs& args) {
return;
}
// 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);
if (!fbScale.isZero() && !scale.isEqual(fbScale)) {
if (!internal->fbScale.isZero() && !internal->scale.isEqual(internal->fbScale)) {
dirty = true;
// 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;

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

nvgBeginPath(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,
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);
nvgFill(args.vg);

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


void FramebufferWidget::drawFramebuffer() {
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
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;
args.vg = vg;
args.clipBox = box.zeroPos();
args.fb = fb;
args.fb = internal->fb;
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, 1.0, 1.0, 0.5);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@@ -201,10 +227,26 @@ void FramebufferWidget::drawFramebuffer() {
nvgReset(vg);
}


int FramebufferWidget::getImageHandle() {
if (!fb)
if (!internal->fb)
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() {
math::Vec fbSize = getFramebufferSize();
glViewport(0.0, 0.0, fbSize.x, fbSize.y);
glClearColor(0.0, 0.0, 0.0, 1.0);
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();
}

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

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

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

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

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
frameTimeStart = glfwGetTime();
fb->step();
nvgluBindFramebuffer(fb->fb);
fbw->step();
nvgluBindFramebuffer(fbw->getFramebuffer());

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

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


Loading…
Cancel
Save