| 82 | |
| 83 | |
| 84 | void FramebufferWidget::draw(const DrawArgs& args) { |
| 85 | // Draw directly if bypassed or already drawing in a framebuffer |
| 86 | if (bypassed || args.fb) { |
| 87 | Widget::draw(args); |
| 88 | return; |
| 89 | } |
| 90 | |
| 91 | // Get world transform |
| 92 | float xform[6]; |
| 93 | nvgCurrentTransform(args.vg, xform); |
| 94 | // Skew and rotate is not supported |
| 95 | if (!math::isNear(xform[1], 0.f) || !math::isNear(xform[2], 0.f)) { |
| 96 | WARN("Skew and rotation detected but not supported in FramebufferWidget."); |
| 97 | return; |
| 98 | } |
| 99 | |
| 100 | // Extract scale and offset from world transform |
| 101 | math::Vec scale = math::Vec(xform[0], xform[3]); |
| 102 | math::Vec offset = math::Vec(xform[4], xform[5]); |
| 103 | math::Vec offsetI = offset.floor(); |
| 104 | math::Vec offsetF = offset.minus(offsetI); |
| 105 | |
| 106 | // Re-render if drawing to a new subpixel location. |
| 107 | // Anything less than 0.1 pixels isn't noticeable. |
| 108 | math::Vec offsetFDelta = offsetF.minus(internal->fbOffsetF); |
| 109 | if (dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange() && offsetFDelta.square() >= std::pow(0.1f, 2)) { |
| 110 | // DEBUG("%p dirty subpixel (%f, %f) (%f, %f)", this, VEC_ARGS(offsetF), VEC_ARGS(internal->fbOffsetF)); |
| 111 | setDirty(); |
| 112 | } |
| 113 | // Re-render if rescaled. |
| 114 | else if (!scale.equals(internal->fbScale)) { |
| 115 | // DEBUG("%p dirty scale", this); |
| 116 | setDirty(); |
| 117 | } |
| 118 | // Re-render if viewport is outside framebuffer's clipbox when it was rendered. |
| 119 | else if (!internal->fbClipBox.contains(args.clipBox)) { |
| 120 | setDirty(); |
| 121 | } |
| 122 | |
| 123 | if (dirty) { |
| 124 | // Render only if there is frame time remaining (to avoid lagging frames significantly), or if it's one of the first framebuffers this frame (to avoid framebuffers from never rendering). |
| 125 | const int minCount = 1; |
| 126 | const double minRemaining = -1 / 60.0; |
| 127 | int count = ++APP->window->fbCount(); |
| 128 | double remaining = APP->window->getFrameDurationRemaining(); |
| 129 | if (count <= minCount || remaining > minRemaining) { |
| 130 | render(scale, offsetF, args.clipBox); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | if (!internal->fb) |
| 135 | return; |
| 136 | |
| 137 | // Draw framebuffer image, using world coordinates |
| 138 | nvgSave(args.vg); |
| 139 | nvgResetTransform(args.vg); |
| 140 | |
| 141 | math::Vec scaleRatio = scale.div(internal->fbScale); |