Browse Source

Add Widget::drawChild(). Make RailWidget draw a FramebufferWidget repeatedly as a tile.

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
1f079444a2
8 changed files with 201 additions and 185 deletions
  1. +2
    -0
      include/app/RackWidget.hpp
  2. +3
    -2
      include/app/RailWidget.hpp
  3. +14
    -4
      include/widget/FramebufferWidget.hpp
  4. +3
    -0
      include/widget/Widget.hpp
  5. +6
    -26
      src/app/RackWidget.cpp
  6. +36
    -19
      src/app/RailWidget.cpp
  7. +119
    -121
      src/widget/FramebufferWidget.cpp
  8. +18
    -13
      src/widget/Widget.cpp

+ 2
- 0
include/app/RackWidget.hpp View File

@@ -4,6 +4,7 @@
#include <app/common.hpp>
#include <widget/OpaqueWidget.hpp>
#include <widget/FramebufferWidget.hpp>
#include <app/RailWidget.hpp>
#include <app/ModuleWidget.hpp>
#include <app/CableWidget.hpp>
#include <app/PortWidget.hpp>
@@ -20,6 +21,7 @@ struct RackWidget : widget::OpaqueWidget {
struct Internal;
Internal* internal;

RailWidget* rail;
widget::Widget* moduleContainer;
widget::Widget* cableContainer;
CableWidget* incompleteCable = NULL;


+ 3
- 2
include/app/RailWidget.hpp View File

@@ -8,11 +8,12 @@ namespace app {


struct RailWidget : widget::TransparentWidget {
std::shared_ptr<Svg> svg;
struct Internal;
Internal* internal;

RailWidget();
~RailWidget();
void draw(const DrawArgs& args) override;
math::Vec getTileSize();
};




+ 14
- 4
include/widget/FramebufferWidget.hpp View File

@@ -23,13 +23,23 @@ struct FramebufferWidget : Widget {
FramebufferWidget();
~FramebufferWidget();
void setDirty(bool dirty = true);
void onDirty(const DirtyEvent& e) override;
void step() override;
void draw(const DrawArgs& args) override;
virtual void drawFramebuffer();
int getImageHandle();
NVGLUframebuffer* getFramebuffer();
math::Vec getFramebufferSize();

void step() override;
/** Draws the framebuffer to the NanoVG scene, re-rendering it if necessary.
*/
void draw(const DrawArgs& args) override;
/** Re-renders the framebuffer, re-creating it if necessary.
Handles oversampling (if >1) by rendering to a temporary (larger) framebuffer and then downscaling it to the main persistent framebuffer.
*/
void render(math::Vec scale, math::Vec offsetF);
/** Initializes the current GL context and draws children to it.
*/
virtual void drawFramebuffer();

void onDirty(const DirtyEvent& e) override;
void onContextCreate(const ContextCreateEvent& e) override;
void onContextDestroy(const ContextDestroyEvent& e) override;
};


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

@@ -135,6 +135,9 @@ struct Widget : WeakBase {
/** Override draw(const DrawArgs &args) instead */
DEPRECATED virtual void draw(NVGcontext* vg) {}

/** Draws a particular child. */
void drawChild(Widget* child, const DrawArgs& args);

// Events

/** Recurses an event to all visible Widgets */


+ 6
- 26
src/app/RackWidget.cpp View File

@@ -72,24 +72,15 @@ struct CableContainer : widget::TransparentWidget {
};


struct RackWidget::Internal {
widget::FramebufferWidget* railFb;
app::RailWidget* rail;
};
// struct RackWidget::Internal {
// };


RackWidget::RackWidget() {
internal = new Internal;

internal->railFb = new widget::FramebufferWidget;
internal->railFb->box.size = math::Vec();
internal->railFb->oversample = 1.0;
// Don't redraw when the world offset of the rail FramebufferWidget changes its fractional value.
internal->railFb->dirtyOnSubpixelChange = false;
addChild(internal->railFb);
// internal = new Internal;

internal->rail = new RailWidget;
internal->railFb->addChild(internal->rail);
rail = new RailWidget;
addChild(rail);

moduleContainer = new ModuleContainer;
addChild(moduleContainer);
@@ -100,7 +91,7 @@ RackWidget::RackWidget() {

RackWidget::~RackWidget() {
clear();
delete internal;
// delete internal;
}

void RackWidget::step() {
@@ -112,17 +103,6 @@ void RackWidget::draw(const DrawArgs& args) {
float b = settings::rackBrightness;
nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1));

// Resize and reposition the RackRail to align on the grid.
math::Vec railSize = internal->rail->getTileSize();
math::Rect railBox;
railBox.pos = args.clipBox.pos.div(railSize).floor().mult(railSize);
railBox.size = args.clipBox.size.div(railSize).ceil().plus(math::Vec(1, 1)).mult(railSize);
if (!internal->railFb->box.size.equals(railBox.size)) {
internal->railFb->setDirty();
}
internal->railFb->box = railBox;
internal->rail->box.size = internal->railFb->box.size;

Widget::draw(args);
}



+ 36
- 19
src/app/RailWidget.cpp View File

@@ -2,43 +2,60 @@
#include <context.hpp>
#include <asset.hpp>
#include <svg.hpp>
#include <widget/SvgWidget.hpp>
#include <widget/FramebufferWidget.hpp>


namespace rack {
namespace app {


struct RailWidget::Internal {
widget::FramebufferWidget* railFb;
widget::SvgWidget* railSw;
};


RailWidget::RailWidget() {
svg = Svg::load(asset::system("res/ComponentLibrary/Rail.svg"));
// DEBUG("%d %d %d", svg->getNumShapes(), svg->getNumPaths(), svg->getNumPoints());
internal = new Internal;

internal->railFb = new widget::FramebufferWidget;
// The rail renders fine without oversampling, and it would be too expensive anyway.
internal->railFb->oversample = 1.0;
// Don't redraw when the world offset of the rail FramebufferWidget changes its fractional value.
internal->railFb->dirtyOnSubpixelChange = false;
addChild(internal->railFb);

internal->railSw = new widget::SvgWidget;
internal->railSw->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rail.svg")));
internal->railFb->addChild(internal->railSw);
}


RailWidget::~RailWidget() {
delete internal;
}


void RailWidget::draw(const DrawArgs& args) {
if (!svg)
if (!internal->railSw->svg)
return;

math::Vec tileSize = getTileSize();
math::Vec tileSize = internal->railSw->svg->getSize().div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE);
if (tileSize.area() == 0.f)
return;

for (float y = 0; y < box.size.y; y += tileSize.y) {
for (float x = 0; x < box.size.x; x += tileSize.x) {
nvgSave(args.vg);
nvgTranslate(args.vg, x, y);
svg->draw(args.vg);
nvgRestore(args.vg);
math::Vec min = args.clipBox.getTopLeft().div(tileSize).floor().mult(tileSize);
math::Vec max = args.clipBox.getBottomRight().div(tileSize).ceil().mult(tileSize);

// Draw the same FramebufferWidget repeatedly as a tile
math::Vec p;
for (p.y = min.y; p.y < max.y; p.y += tileSize.y) {
for (p.x = min.x; p.x < max.x; p.x += tileSize.x) {
internal->railFb->box.pos = p;
Widget::drawChild(internal->railFb, args);
}
}

Widget::draw(args);
}


math::Vec RailWidget::getTileSize() {
if (!svg)
return math::Vec();
return svg->getSize().div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE);
}




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

@@ -43,9 +43,20 @@ void FramebufferWidget::setDirty(bool dirty) {
}


void FramebufferWidget::onDirty(const DirtyEvent& e) {
setDirty();
Widget::onDirty(e);
int FramebufferWidget::getImageHandle() {
if (!internal->fb)
return -1;
return internal->fb->image;
}


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


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


@@ -79,7 +90,7 @@ void FramebufferWidget::draw(const DrawArgs& args) {
// If drawing to a new subpixel location, rerender in the next frame.
// Anything less than 0.1 pixels isn't noticeable.
math::Vec offsetFDelta = offsetF.minus(internal->fbOffsetF);
if (offsetFDelta.square() >= std::pow(0.1f, 2) && dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange()) {
if (dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange() && offsetFDelta.square() >= std::pow(0.1f, 2)) {
// DEBUG("%p dirty subpixel (%f, %f) (%f, %f)", this, VEC_ARGS(offsetF), VEC_ARGS(internal->fbOffsetF));
setDirty();
}
@@ -89,109 +100,9 @@ void FramebufferWidget::draw(const DrawArgs& args) {
setDirty();
}

// 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;

// 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()) {
// DEBUG("Creating framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize));
internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0);
}
}
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();
// DEBUG("Creating %0.fx oversampled framebuffer of size (%f, %f)", oversample, VEC_ARGS(internal->fbSize));
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 copying oversampled framebuffer to normal framebuffer
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();
// It's more important to not lag the frame than to draw the framebuffer
if (dirty && APP->window->getFrameTimeOverdue() < 0.0) {
render(scale, offsetF);
}

if (!internal->fb)
@@ -229,6 +140,104 @@ void FramebufferWidget::draw(const DrawArgs& args) {
}


void FramebufferWidget::render(math::Vec scale, math::Vec offsetF) {
// 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()) {
// DEBUG("Creating framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize));
internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0);
}
}
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();
// DEBUG("Creating %0.fx oversampled framebuffer of size (%f, %f)", oversample, VEC_ARGS(internal->fbSize));
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 copying oversampled framebuffer to normal framebuffer
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);
}
};


void FramebufferWidget::drawFramebuffer() {
NVGcontext* vg = APP->window->fbVg;
nvgSave(vg);
@@ -260,20 +269,9 @@ void FramebufferWidget::drawFramebuffer() {
}


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


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


math::Vec FramebufferWidget::getFramebufferSize() {
return internal->fbSize;
void FramebufferWidget::onDirty(const DirtyEvent& e) {
setDirty();
Widget::onDirty(e);
}




+ 18
- 13
src/widget/Widget.cpp View File

@@ -262,31 +262,36 @@ void Widget::draw(const DrawArgs& args) {
// Iterate children
for (Widget* child : children) {
// Don't draw if invisible
if (!child->visible)
if (!child->isVisible())
continue;
// Don't draw if child is outside clip box
if (!args.clipBox.intersects(child->box))
continue;

DrawArgs childArgs = args;
// Intersect child clip box with self
childArgs.clipBox = childArgs.clipBox.intersect(child->box);
// Offset clip box by child pos
childArgs.clipBox.pos = childArgs.clipBox.pos.minus(child->box.pos);
drawChild(child, args);
}
}


void Widget::drawChild(Widget* child, const DrawArgs& args) {
DrawArgs childArgs = args;
// Intersect child clip box with self
childArgs.clipBox = childArgs.clipBox.intersect(child->box);
// Offset clip box by child pos
childArgs.clipBox.pos = childArgs.clipBox.pos.minus(child->box.pos);

nvgSave(args.vg);
nvgTranslate(args.vg, child->box.pos.x, child->box.pos.y);
nvgSave(args.vg);
nvgTranslate(args.vg, child->box.pos.x, child->box.pos.y);

child->draw(childArgs);
child->draw(childArgs);

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// Call deprecated draw function, which does nothing by default
child->draw(args.vg);
// Call deprecated draw function, which does nothing by default
child->draw(args.vg);
#pragma GCC diagnostic pop

nvgRestore(args.vg);
}
nvgRestore(args.vg);
}




Loading…
Cancel
Save