|
|
@@ -10,15 +10,6 @@ namespace widget { |
|
|
|
struct FramebufferWidget::Internal { |
|
|
|
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 */ |
|
|
|
math::Vec fbSize; |
|
|
|
/** 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() { |
|
|
|
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) { |
|
|
|
// Draw directly if already drawing in a framebuffer |
|
|
|
// Draw directly if bypassed or already drawing in a framebuffer |
|
|
|
if (bypassed || args.fb) { |
|
|
|
Widget::draw(args); |
|
|
|
return; |
|
|
@@ -184,30 +71,132 @@ void FramebufferWidget::draw(const DrawArgs& args) { |
|
|
|
} |
|
|
|
|
|
|
|
// 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 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. |
|
|
|
// 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()) { |
|
|
|
// 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(); |
|
|
|
} |
|
|
|
if (!internal->scale.equals(internal->fbScale)) { |
|
|
|
if (!scale.equals(internal->fbScale)) { |
|
|
|
// If rescaled, rerender in the next frame. |
|
|
|
// DEBUG("%p dirty scale", this); |
|
|
|
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) |
|
|
|
return; |
|
|
@@ -216,6 +205,13 @@ void FramebufferWidget::draw(const DrawArgs& args) { |
|
|
|
nvgSave(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)); |
|
|
|
nvgBeginPath(args.vg); |
|
|
|
nvgRect(args.vg, |
|
|
@@ -242,7 +238,7 @@ void FramebufferWidget::draw(const DrawArgs& args) { |
|
|
|
|
|
|
|
|
|
|
|
void FramebufferWidget::drawFramebuffer() { |
|
|
|
NVGcontext* vg = APP->window->vg; |
|
|
|
NVGcontext* vg = APP->window->fbVg; |
|
|
|
nvgSave(vg); |
|
|
|
|
|
|
|
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) { |
|
|
|
setDirty(); |
|
|
|
Widget::onContextCreate(e); |
|
|
|