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.

306 lines
9.0KB

  1. #include <widget/FramebufferWidget.hpp>
  2. #include <context.hpp>
  3. #include <random.hpp>
  4. namespace rack {
  5. namespace widget {
  6. struct FramebufferWidget::Internal {
  7. NVGLUframebuffer* fb = NULL;
  8. /** Pixel dimensions of the allocated framebuffer */
  9. math::Vec fbSize;
  10. /** Bounding box in world coordinates of where the framebuffer should be painted.
  11. Always has integer coordinates so that blitting framebuffers is pixel-perfect.
  12. */
  13. math::Rect fbBox;
  14. /** Framebuffer's scale relative to the world */
  15. math::Vec fbScale;
  16. /** Framebuffer's subpixel offset relative to fbBox in world coordinates */
  17. math::Vec fbOffsetF;
  18. };
  19. FramebufferWidget::FramebufferWidget() {
  20. internal = new Internal;
  21. }
  22. FramebufferWidget::~FramebufferWidget() {
  23. if (internal->fb) {
  24. // If the framebuffer exists, the Window should exist.
  25. assert(APP->window);
  26. nvgluDeleteFramebuffer(internal->fb);
  27. }
  28. delete internal;
  29. }
  30. void FramebufferWidget::setDirty(bool dirty) {
  31. this->dirty = dirty;
  32. }
  33. void FramebufferWidget::onDirty(const DirtyEvent& e) {
  34. setDirty();
  35. Widget::onDirty(e);
  36. }
  37. void FramebufferWidget::step() {
  38. Widget::step();
  39. }
  40. void FramebufferWidget::draw(const DrawArgs& args) {
  41. // Draw directly if bypassed or already drawing in a framebuffer
  42. if (bypassed || args.fb) {
  43. Widget::draw(args);
  44. return;
  45. }
  46. // Get world transform
  47. float xform[6];
  48. nvgCurrentTransform(args.vg, xform);
  49. // Skew and rotate is not supported
  50. if (!math::isNear(xform[1], 0.f) || !math::isNear(xform[2], 0.f)) {
  51. WARN("Skew and rotation detected but not supported in FramebufferWidget.");
  52. return;
  53. }
  54. // Extract scale and offset from world transform
  55. math::Vec scale = math::Vec(xform[0], xform[3]);
  56. math::Vec offset = math::Vec(xform[4], xform[5]);
  57. math::Vec offsetI = offset.floor();
  58. math::Vec offsetF = offset.minus(offsetI);
  59. // If drawing to a new subpixel location, rerender in the next frame.
  60. // Anything less than 0.1 pixels isn't noticeable.
  61. math::Vec offsetFDelta = offsetF.minus(internal->fbOffsetF);
  62. if (offsetFDelta.square() >= std::pow(0.1f, 2) && dirtyOnSubpixelChange && APP->window->fbDirtyOnSubpixelChange()) {
  63. // DEBUG("%p dirty subpixel (%f, %f) (%f, %f)", this, VEC_ARGS(offsetF), VEC_ARGS(internal->fbOffsetF));
  64. setDirty();
  65. }
  66. if (!scale.equals(internal->fbScale)) {
  67. // If rescaled, rerender in the next frame.
  68. // DEBUG("%p dirty scale", this);
  69. setDirty();
  70. }
  71. // Draw to framebuffer if dirty
  72. auto redraw = [&]() {
  73. // It's more important to not lag the frame than to draw the framebuffer
  74. if (APP->window->getFrameTimeOverdue() > 0.0)
  75. return;
  76. // Check that scale has been set by `draw()` yet.
  77. if (scale.isZero())
  78. return;
  79. // In case we fail drawing the framebuffer, don't try again the next frame, so reset `dirty` here.
  80. dirty = false;
  81. NVGcontext* vg = APP->window->vg;
  82. NVGcontext* fbVg = APP->window->fbVg;
  83. internal->fbScale = scale;
  84. internal->fbOffsetF = offsetF;
  85. math::Rect localBox;
  86. if (children.empty()) {
  87. localBox = box.zeroPos();
  88. }
  89. else {
  90. localBox = getVisibleChildrenBoundingBox();
  91. }
  92. // 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));
  93. // Transform to world coordinates, then expand to nearest integer coordinates
  94. math::Vec min = localBox.getTopLeft().mult(internal->fbScale).plus(internal->fbOffsetF).floor();
  95. math::Vec max = localBox.getBottomRight().mult(internal->fbScale).plus(internal->fbOffsetF).ceil();
  96. internal->fbBox = math::Rect::fromMinMax(min, max);
  97. // DEBUG("%g %g %g %g", RECT_ARGS(internal->fbBox));
  98. math::Vec newFbSize = internal->fbBox.size.mult(APP->window->pixelRatio).ceil();
  99. // Create framebuffer if a new size is needed
  100. if (!internal->fb || !newFbSize.equals(internal->fbSize)) {
  101. internal->fbSize = newFbSize;
  102. // Delete old framebuffer
  103. if (internal->fb) {
  104. nvgluDeleteFramebuffer(internal->fb);
  105. internal->fb = NULL;
  106. }
  107. // Create a framebuffer
  108. if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) {
  109. internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0);
  110. // DEBUG("Created framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize));
  111. }
  112. }
  113. if (!internal->fb) {
  114. WARN("Framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(internal->fbSize), this);
  115. return;
  116. }
  117. DEBUG("Drawing to framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize));
  118. // Render to framebuffer
  119. if (oversample == 1.0) {
  120. // If not oversampling, render directly to framebuffer.
  121. nvgluBindFramebuffer(internal->fb);
  122. drawFramebuffer();
  123. nvgluBindFramebuffer(NULL);
  124. }
  125. else {
  126. NVGLUframebuffer* fb = internal->fb;
  127. // If oversampling, create another framebuffer and copy it to actual size.
  128. math::Vec oversampledFbSize = internal->fbSize.mult(oversample).ceil();
  129. NVGLUframebuffer* oversampledFb = nvgluCreateFramebuffer(fbVg, oversampledFbSize.x, oversampledFbSize.y, 0);
  130. if (!oversampledFb) {
  131. WARN("Oversampled framebuffer of size (%f, %f) could not be created for FramebufferWidget %p.", VEC_ARGS(oversampledFbSize), this);
  132. return;
  133. }
  134. // Render to oversampled framebuffer.
  135. nvgluBindFramebuffer(oversampledFb);
  136. internal->fb = oversampledFb;
  137. drawFramebuffer();
  138. internal->fb = fb;
  139. nvgluBindFramebuffer(NULL);
  140. // Use NanoVG for resizing framebuffers
  141. nvgluBindFramebuffer(internal->fb);
  142. nvgBeginFrame(fbVg, internal->fbBox.size.x, internal->fbBox.size.y, 1.0);
  143. // Draw oversampled framebuffer
  144. nvgBeginPath(fbVg);
  145. nvgRect(fbVg, 0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
  146. NVGpaint paint = nvgImagePattern(fbVg, 0.0, 0.0,
  147. internal->fbSize.x, internal->fbSize.y,
  148. 0.0, oversampledFb->image, 1.0);
  149. nvgFillPaint(fbVg, paint);
  150. nvgFill(fbVg);
  151. glViewport(0.0, 0.0, internal->fbSize.x, internal->fbSize.y);
  152. glClearColor(0.0, 0.0, 0.0, 0.0);
  153. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  154. nvgEndFrame(fbVg);
  155. nvgReset(fbVg);
  156. nvgluBindFramebuffer(NULL);
  157. nvgluDeleteFramebuffer(oversampledFb);
  158. }
  159. };
  160. if (dirty) {
  161. redraw();
  162. }
  163. if (!internal->fb)
  164. return;
  165. // Draw framebuffer image, using world coordinates
  166. nvgSave(args.vg);
  167. nvgResetTransform(args.vg);
  168. math::Vec scaleRatio = math::Vec(1, 1);
  169. if (!internal->fbScale.isZero() && !scale.equals(internal->fbScale)) {
  170. // Continue to draw with the last framebuffer, but stretch it to rescale.
  171. scaleRatio = scale.div(internal->fbScale);
  172. }
  173. // DEBUG("%f %f %f %f", scaleRatio.x, scaleRatio.y, offsetF.x, offsetF.y);
  174. // DEBUG("%f %f %f %f, %f %f", RECT_ARGS(internal->fbBox), VEC_ARGS(internal->fbSize));
  175. nvgBeginPath(args.vg);
  176. nvgRect(args.vg,
  177. offsetI.x + internal->fbBox.pos.x,
  178. offsetI.y + internal->fbBox.pos.y,
  179. internal->fbBox.size.x * scaleRatio.x,
  180. internal->fbBox.size.y * scaleRatio.y);
  181. NVGpaint paint = nvgImagePattern(args.vg,
  182. offsetI.x + internal->fbBox.pos.x,
  183. offsetI.y + internal->fbBox.pos.y,
  184. internal->fbBox.size.x * scaleRatio.x,
  185. internal->fbBox.size.y * scaleRatio.y,
  186. 0.0, internal->fb->image, 1.0);
  187. nvgFillPaint(args.vg, paint);
  188. nvgFill(args.vg);
  189. // For debugging the bounding box of the framebuffer
  190. // nvgStrokeWidth(args.vg, 2.0);
  191. // nvgStrokeColor(args.vg, nvgRGBAf(1, 1, 0, 0.5));
  192. // nvgStroke(args.vg);
  193. nvgRestore(args.vg);
  194. }
  195. void FramebufferWidget::drawFramebuffer() {
  196. NVGcontext* vg = APP->window->fbVg;
  197. nvgSave(vg);
  198. float pixelRatio = internal->fbSize.x * oversample / internal->fbBox.size.x;
  199. nvgBeginFrame(vg, internal->fbBox.size.x, internal->fbBox.size.y, pixelRatio);
  200. // Use local scaling
  201. nvgTranslate(vg, -internal->fbBox.pos.x, -internal->fbBox.pos.y);
  202. nvgTranslate(vg, internal->fbOffsetF.x, internal->fbOffsetF.y);
  203. nvgScale(vg, internal->fbScale.x, internal->fbScale.y);
  204. // Draw children
  205. DrawArgs args;
  206. args.vg = vg;
  207. args.clipBox = box.zeroPos();
  208. args.fb = internal->fb;
  209. Widget::draw(args);
  210. glViewport(0.0, 0.0, internal->fbSize.x * oversample, internal->fbSize.y * oversample);
  211. glClearColor(0.0, 0.0, 0.0, 0.0);
  212. // glClearColor(0.0, 1.0, 1.0, 0.5);
  213. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  214. nvgEndFrame(vg);
  215. // Clean up the NanoVG state so that calls to nvgTextBounds() etc during step() don't use a dirty state.
  216. nvgReset(vg);
  217. nvgRestore(vg);
  218. }
  219. int FramebufferWidget::getImageHandle() {
  220. if (!internal->fb)
  221. return -1;
  222. return internal->fb->image;
  223. }
  224. NVGLUframebuffer* FramebufferWidget::getFramebuffer() {
  225. return internal->fb;
  226. }
  227. math::Vec FramebufferWidget::getFramebufferSize() {
  228. return internal->fbSize;
  229. }
  230. void FramebufferWidget::onContextCreate(const ContextCreateEvent& e) {
  231. setDirty();
  232. Widget::onContextCreate(e);
  233. }
  234. void FramebufferWidget::onContextDestroy(const ContextDestroyEvent& e) {
  235. if (internal->fb) {
  236. nvgluDeleteFramebuffer(internal->fb);
  237. internal->fb = NULL;
  238. }
  239. setDirty();
  240. Widget::onContextDestroy(e);
  241. }
  242. } // namespace widget
  243. } // namespace rack