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.

162 lines
4.2KB

  1. #include "widget/FramebufferWidget.hpp"
  2. #include "app.hpp"
  3. #include "random.hpp"
  4. namespace rack {
  5. namespace widget {
  6. FramebufferWidget::FramebufferWidget() {
  7. oversample = 1.0;
  8. }
  9. FramebufferWidget::~FramebufferWidget() {
  10. if (fb)
  11. nvgluDeleteFramebuffer(fb);
  12. }
  13. void FramebufferWidget::step() {
  14. Widget::step();
  15. // if (random::uniform() > 0.01)
  16. // return;
  17. // Render to framebuffer if dirty.
  18. // Also check that scale has been set by `draw()` yet.
  19. if (dirty && !scale.isZero()) {
  20. // In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here.
  21. dirty = false;
  22. fbScale = scale;
  23. // Get subpixel offset in range [0, 1)
  24. math::Vec offsetI = offset.floor();
  25. fbOffset = offset.minus(offsetI);
  26. math::Rect localBox;
  27. if (children.empty()) {
  28. localBox = box.zeroPos();
  29. }
  30. else {
  31. localBox = getChildrenBoundingBox();
  32. }
  33. // DEBUG("%g %g %g %g, %g %g, %g %g", RECT_ARGS(localBox), VEC_ARGS(fbOffset), VEC_ARGS(fbScale));
  34. // Transform to world coordinates, then expand to nearest integer coordinates
  35. math::Vec min = localBox.getTopLeft().mult(fbScale).plus(fbOffset).floor();
  36. math::Vec max = localBox.getBottomRight().mult(fbScale).plus(fbOffset).ceil();
  37. fbBox = math::Rect::fromMinMax(min, max);
  38. // DEBUG("%g %g %g %g", RECT_ARGS(fbBox));
  39. math::Vec newFbSize = fbBox.size.mult(APP->window->pixelRatio * oversample);
  40. // Create framebuffer if a new size is needed
  41. if (!fb || !newFbSize.isEqual(fbSize)) {
  42. fbSize = newFbSize;
  43. // Delete old framebuffer
  44. if (fb)
  45. nvgluDeleteFramebuffer(fb);
  46. // Create a framebuffer from the main nanovg context. We will draw to this in the secondary nanovg context.
  47. if (fbSize.isFinite() && !fbSize.isZero())
  48. fb = nvgluCreateFramebuffer(APP->window->vg, fbSize.x, fbSize.y, 0);
  49. }
  50. if (!fb)
  51. return;
  52. nvgluBindFramebuffer(fb);
  53. drawFramebuffer();
  54. nvgluBindFramebuffer(NULL);
  55. }
  56. }
  57. void FramebufferWidget::draw(const DrawArgs &args) {
  58. // Draw directly if already drawing in a framebuffer
  59. if (args.fb) {
  60. Widget::draw(args);
  61. return;
  62. }
  63. // Get world transform
  64. float xform[6];
  65. nvgCurrentTransform(args.vg, xform);
  66. // Skew and rotate is not supported
  67. if (!math::isNear(xform[1], 0.f) || !math::isNear(xform[2], 0.f))
  68. return;
  69. // Extract scale and offset from world transform
  70. scale = math::Vec(xform[0], xform[3]);
  71. offset = math::Vec(xform[4], xform[5]);
  72. math::Vec offsetI = offset.floor();
  73. math::Vec scaleRatio = math::Vec(1, 1);
  74. if (!fbScale.isZero() && !scale.isEqual(fbScale)) {
  75. dirty = true;
  76. // Continue to draw but at the wrong scale. In the next frame, the framebuffer will be redrawn.
  77. scaleRatio = scale.div(fbScale);
  78. }
  79. if (!fb)
  80. return;
  81. // Draw framebuffer image, using world coordinates
  82. nvgSave(args.vg);
  83. nvgResetTransform(args.vg);
  84. nvgBeginPath(args.vg);
  85. nvgRect(args.vg,
  86. offsetI.x + fbBox.pos.x,
  87. offsetI.y + fbBox.pos.y,
  88. fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y);
  89. NVGpaint paint = nvgImagePattern(args.vg,
  90. offsetI.x + fbBox.pos.x,
  91. offsetI.y + fbBox.pos.y,
  92. fbBox.size.x * scaleRatio.x, fbBox.size.y * scaleRatio.y,
  93. 0.0, fb->image, 1.0);
  94. nvgFillPaint(args.vg, paint);
  95. nvgFill(args.vg);
  96. // For debugging the bounding box of the framebuffer
  97. // nvgStrokeWidth(args.vg, 2.0);
  98. // nvgStrokeColor(args.vg, nvgRGBAf(1, 1, 0, 0.5));
  99. // nvgStroke(args.vg);
  100. nvgRestore(args.vg);
  101. }
  102. void FramebufferWidget::drawFramebuffer() {
  103. NVGcontext *vg = APP->window->vg;
  104. float pixelRatio = fbSize.x / fbBox.size.x;
  105. nvgBeginFrame(vg, fbBox.size.x, fbBox.size.y, pixelRatio);
  106. // Use local scaling
  107. nvgTranslate(vg, -fbBox.pos.x, -fbBox.pos.y);
  108. nvgTranslate(vg, fbOffset.x, fbOffset.y);
  109. nvgScale(vg, fbScale.x, fbScale.y);
  110. DrawArgs args;
  111. args.vg = vg;
  112. args.clipBox = box.zeroPos();
  113. args.fb = fb;
  114. Widget::draw(args);
  115. glViewport(0.0, 0.0, fbSize.x, fbSize.y);
  116. glClearColor(0.0, 0.0, 0.0, 0.0);
  117. // glClearColor(0.0, 1.0, 1.0, 0.5);
  118. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  119. nvgEndFrame(vg);
  120. // Clean up the NanoVG state so that calls to nvgTextBounds() etc during step() don't use a dirty state.
  121. nvgReset(vg);
  122. }
  123. int FramebufferWidget::getImageHandle() {
  124. if (!fb)
  125. return -1;
  126. return fb->image;
  127. }
  128. } // namespace widget
  129. } // namespace rack