You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
6.0KB

  1. #include <widget/FramebufferWidget.hpp>
  2. #include <app.hpp>
  3. #include <random.hpp>
  4. namespace rack {
  5. namespace widget {
  6. FramebufferWidget::FramebufferWidget() {
  7. }
  8. FramebufferWidget::~FramebufferWidget() {
  9. if (fb)
  10. nvgluDeleteFramebuffer(fb);
  11. }
  12. void FramebufferWidget::step() {
  13. Widget::step();
  14. // It's more important to not lag the frame than to draw the framebuffer
  15. if (APP->window->isFrameOverdue())
  16. return;
  17. // Check that scale has been set by `draw()` yet.
  18. if (scale.isZero())
  19. return;
  20. // Only redraw if FramebufferWidget is dirty
  21. if (!dirty)
  22. return;
  23. // In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here.
  24. dirty = false;
  25. NVGcontext* vg = APP->window->vg;
  26. fbScale = scale;
  27. // Set scale to zero so we must wait for the next draw() call before drawing the framebuffer again.
  28. // 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().
  29. scale = math::Vec();
  30. // Get subpixel offset in range [0, 1)
  31. math::Vec offsetI = offset.floor();
  32. fbOffset = offset.minus(offsetI);
  33. math::Rect localBox;
  34. if (children.empty()) {
  35. localBox = box.zeroPos();
  36. }
  37. else {
  38. localBox = getChildrenBoundingBox();
  39. }
  40. // DEBUG("%g %g %g %g, %g %g, %g %g", RECT_ARGS(localBox), VEC_ARGS(fbOffset), VEC_ARGS(fbScale));
  41. // Transform to world coordinates, then expand to nearest integer coordinates
  42. math::Vec min = localBox.getTopLeft().mult(fbScale).plus(fbOffset).floor();
  43. math::Vec max = localBox.getBottomRight().mult(fbScale).plus(fbOffset).ceil();
  44. fbBox = math::Rect::fromMinMax(min, max);
  45. // DEBUG("%g %g %g %g", RECT_ARGS(fbBox));
  46. math::Vec newFbSize = fbBox.size.mult(APP->window->pixelRatio).ceil();
  47. // Create framebuffer if a new size is needed
  48. if (!fb || !newFbSize.isEqual(fbSize)) {
  49. fbSize = newFbSize;
  50. // Delete old framebuffer
  51. if (fb)
  52. nvgluDeleteFramebuffer(fb);
  53. // Create a framebuffer at the oversampled size
  54. if (fbSize.isFinite() && !fbSize.isZero())
  55. fb = nvgluCreateFramebuffer(vg, fbSize.x * oversample, fbSize.y * oversample, 0);
  56. }
  57. if (!fb) {
  58. WARN("Framebuffer of size (%f, %f) * %f could not be created for FramebufferWidget.", VEC_ARGS(fbSize), oversample);
  59. return;
  60. }
  61. nvgluBindFramebuffer(fb);
  62. drawFramebuffer();
  63. nvgluBindFramebuffer(NULL);
  64. // If oversampling, create another framebuffer and copy it to actual size.
  65. if (oversample != 1.0) {
  66. NVGLUframebuffer* newFb = nvgluCreateFramebuffer(vg, fbSize.x, fbSize.y, 0);
  67. if (!newFb) {
  68. WARN("Non-oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget.", VEC_ARGS(fbSize));
  69. return;
  70. }
  71. // Use NanoVG for resizing framebuffers
  72. nvgluBindFramebuffer(newFb);
  73. nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, 1.0);
  74. // Draw oversampled framebuffer
  75. nvgBeginPath(vg);
  76. nvgRect(vg, 0.0, 0.0, fbSize.x, fbSize.y);
  77. NVGpaint paint = nvgImagePattern(vg, 0.0, 0.0, fbSize.x, fbSize.y,
  78. 0.0, fb->image, 1.0);
  79. nvgFillPaint(vg, paint);
  80. nvgFill(vg);
  81. glViewport(0.0, 0.0, fbSize.x, fbSize.y);
  82. glClearColor(0.0, 0.0, 0.0, 0.0);
  83. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  84. nvgEndFrame(vg);
  85. nvgReset(vg);
  86. nvgluBindFramebuffer(NULL);
  87. // Swap the framebuffers
  88. nvgluDeleteFramebuffer(fb);
  89. fb = newFb;
  90. }
  91. }
  92. void FramebufferWidget::draw(const DrawArgs& args) {
  93. // Draw directly if already drawing in a framebuffer
  94. if (bypass || args.fb) {
  95. Widget::draw(args);
  96. return;
  97. }
  98. // Get world transform
  99. float xform[6];
  100. nvgCurrentTransform(args.vg, xform);
  101. // Skew and rotate is not supported
  102. if (!math::isNear(xform[1], 0.f) || !math::isNear(xform[2], 0.f)) {
  103. WARN("Skew and rotation detected but not supported in FramebufferWidget.");
  104. return;
  105. }
  106. // Extract scale and offset from world transform
  107. scale = math::Vec(xform[0], xform[3]);
  108. offset = math::Vec(xform[4], xform[5]);
  109. math::Vec offsetI = offset.floor();
  110. math::Vec scaleRatio = math::Vec(1, 1);
  111. if (!fbScale.isZero() && !scale.isEqual(fbScale)) {
  112. dirty = true;
  113. // Continue to draw but at the wrong scale. In the next frame, the framebuffer will be redrawn.
  114. scaleRatio = scale.div(fbScale);
  115. }
  116. if (!fb)
  117. return;
  118. // Draw framebuffer image, using world coordinates
  119. nvgSave(args.vg);
  120. nvgResetTransform(args.vg);
  121. nvgBeginPath(args.vg);
  122. nvgRect(args.vg,
  123. offsetI.x + fbBox.pos.x,
  124. offsetI.y + fbBox.pos.y,
  125. fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y);
  126. NVGpaint paint = nvgImagePattern(args.vg,
  127. offsetI.x + fbBox.pos.x,
  128. offsetI.y + fbBox.pos.y,
  129. fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y,
  130. 0.0, fb->image, 1.0);
  131. nvgFillPaint(args.vg, paint);
  132. nvgFill(args.vg);
  133. // For debugging the bounding box of the framebuffer
  134. // nvgStrokeWidth(args.vg, 2.0);
  135. // nvgStrokeColor(args.vg, nvgRGBAf(1, 1, 0, 0.5));
  136. // nvgStroke(args.vg);
  137. nvgRestore(args.vg);
  138. }
  139. void FramebufferWidget::drawFramebuffer() {
  140. NVGcontext* vg = APP->window->vg;
  141. float pixelRatio = fbSize.x * oversample / fbBox.size.x;
  142. nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, pixelRatio);
  143. // Use local scaling
  144. nvgTranslate(vg, -fbBox.pos.x, -fbBox.pos.y);
  145. nvgTranslate(vg, fbOffset.x, fbOffset.y);
  146. nvgScale(vg, fbScale.x, fbScale.y);
  147. DrawArgs args;
  148. args.vg = vg;
  149. args.clipBox = box.zeroPos();
  150. args.fb = fb;
  151. Widget::draw(args);
  152. glViewport(0.0, 0.0, fbSize.x * oversample, fbSize.y * oversample);
  153. glClearColor(0.0, 0.0, 0.0, 0.0);
  154. // glClearColor(0.0, 1.0, 1.0, 0.5);
  155. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  156. nvgEndFrame(vg);
  157. // Clean up the NanoVG state so that calls to nvgTextBounds() etc during step() don't use a dirty state.
  158. nvgReset(vg);
  159. }
  160. int FramebufferWidget::getImageHandle() {
  161. if (!fb)
  162. return -1;
  163. return fb->image;
  164. }
  165. } // namespace widget
  166. } // namespace rack