iPlug2 - C++ Audio Plug-in Framework
IGraphicsNanoVG.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5 
6  See LICENSE.txt for more info.
7 
8  ==============================================================================
9 */
10 
11 #include <cmath>
12 
13 #include "IGraphicsNanoVG.h"
14 #include "ITextEntryControl.h"
15 
16 #if defined IGRAPHICS_GL
17  #if defined OS_MAC
18  #if defined IGRAPHICS_GL2
19  #define NANOVG_GL2_IMPLEMENTATION
20  #elif defined IGRAPHICS_GL3
21  #include <OpenGL/gl3.h>
22  #define NANOVG_GL3_IMPLEMENTATION
23  #else
24  #error Define either IGRAPHICS_GL2 or IGRAPHICS_GL3 for IGRAPHICS_NANOVG with OS_MAC
25  #endif
26  #elif defined OS_IOS
27 // #if defined IGRAPHICS_GLES2
28 // #define NANOVG_GLES2_IMPLEMENTATION
29 // #elif defined IGRAPHICS_GLES3
30 // #define NANOVG_GLES2_IMPLEMENTATION
31 // #else
32 // #error Define either IGRAPHICS_GLES2 or IGRAPHICS_GLES3 when using IGRAPHICS_GL and IGRAPHICS_NANOVG with OS_IOS
33 // #endif
34  #error NOT IMPLEMENTED
35  #elif defined OS_WIN
36  #pragma comment(lib, "opengl32.lib")
37  #if defined IGRAPHICS_GL2
38  #define NANOVG_GL2_IMPLEMENTATION
39  #elif defined IGRAPHICS_GL3
40  #define NANOVG_GL3_IMPLEMENTATION
41  #else
42  #error Define either IGRAPHICS_GL2 or IGRAPHICS_GL3 when using IGRAPHICS_GL and IGRAPHICS_NANOVG with OS_WIN
43  #endif
44  #elif defined OS_LINUX
45  #error NOT IMPLEMENTED
46  #elif defined OS_WEB
47  #if defined IGRAPHICS_GLES2
48  #define NANOVG_GLES2_IMPLEMENTATION
49  #elif defined IGRAPHICS_GLES3
50  #define NANOVG_GLES3_IMPLEMENTATION
51  #else
52  #error Define either IGRAPHICS_GLES2 or IGRAPHICS_GLES3 when using IGRAPHICS_GL and IGRAPHICS_NANOVG with OS_WEB
53  #endif
54  #endif
55  #include "nanovg_gl.h"
56  #include "nanovg_gl_utils.h"
57 #elif defined IGRAPHICS_METAL
58  #include "nanovg_mtl.h"
59  #if defined OS_MAC
60  //even though this is a .cpp we are in an objc(pp) compilation unit
61  #import <Metal/Metal.h>
62  #endif
63 #else
64  #error you must define either IGRAPHICS_GL2, IGRAPHICS_GLES2 etc or IGRAPHICS_METAL when using IGRAPHICS_NANOVG
65 #endif
66 
67 #include <string>
68 #include <map>
69 
70 using namespace iplug;
71 using namespace igraphics;
72 
73 #pragma mark - Private Classes and Structs
74 
76 {
77 public:
78  Bitmap(NVGcontext* pContext, const char* path, double sourceScale, int nvgImageID, bool shared = false);
79  Bitmap(IGraphicsNanoVG* pGraphics, NVGcontext* pContext, int width, int height, int scale, float drawScale);
80  Bitmap(NVGcontext* pContext, int width, int height, const uint8_t* pData, int scale, float drawScale);
81  virtual ~Bitmap();
82  NVGframebuffer* GetFBO() const { return mFBO; }
83 private:
84  IGraphicsNanoVG *mGraphics = nullptr;
85  NVGcontext* mVG;
86  NVGframebuffer* mFBO = nullptr;
87  bool mSharedTexture = false;
88 };
89 
90 IGraphicsNanoVG::Bitmap::Bitmap(NVGcontext* pContext, const char* path, double sourceScale, int nvgImageID, bool shared)
91 {
92  assert(nvgImageID > 0);
93 
94  mVG = pContext;
95  mSharedTexture = shared;
96  int w = 0, h = 0;
97  nvgImageSize(mVG, nvgImageID, &w, &h);
98 
99  SetBitmap(nvgImageID, w, h, sourceScale, 1.f);
100 }
101 
102 IGraphicsNanoVG::Bitmap::Bitmap(IGraphicsNanoVG* pGraphics, NVGcontext* pContext, int width, int height, int scale, float drawScale)
103 {
104  mGraphics = pGraphics;
105  mVG = pContext;
106  mFBO = nvgCreateFramebuffer(pContext, width, height, 0);
107 
108  nvgBindFramebuffer(mFBO);
109 
110 #ifdef IGRAPHICS_METAL
111  mnvgClearWithColor(mVG, nvgRGBAf(0, 0, 0, 0));
112 #else
113  glViewport(0, 0, width, height);
114  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
115  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
116 #endif
117  nvgBeginFrame(mVG, width, height, 1.f);
118  nvgEndFrame(mVG);
119 
120  SetBitmap(mFBO->image, width, height, scale, drawScale);
121 }
122 
123 IGraphicsNanoVG::Bitmap::Bitmap(NVGcontext* pContext, int width, int height, const uint8_t* pData, int scale, float drawScale)
124 {
125  int idx = nvgCreateImageRGBA(pContext, width, height, 0, pData);
126  mVG = pContext;
127  SetBitmap(idx, width, height, scale, drawScale);
128 }
129 
130 IGraphicsNanoVG::Bitmap::~Bitmap()
131 {
132  if(!mSharedTexture)
133  {
134  if(mFBO)
135  mGraphics->DeleteFBO(mFBO);
136  else
137  nvgDeleteImage(mVG, GetBitmap());
138  }
139 }
140 
141 // Fonts
142 static StaticStorage<IFontData> sFontCache;
143 
144 extern std::map<std::string, MTLTexturePtr> gTextureMap;
145 
146 // Retrieving pixels
147 static void nvgReadPixels(NVGcontext* pContext, int image, int x, int y, int width, int height, void* pData)
148 {
149 #if defined(IGRAPHICS_GL)
150  glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
151 #elif defined(IGRAPHICS_METAL)
152  mnvgReadPixels(pContext, image, x, y, width, height, pData);
153 #endif
154 }
155 
156 #pragma mark - Utilities
157 
158 BEGIN_IPLUG_NAMESPACE
159 BEGIN_IGRAPHICS_NAMESPACE
160 
161 NVGcolor NanoVGColor(const IColor& color, const IBlend* pBlend)
162 {
163  NVGcolor c;
164  c.r = (float) color.R / 255.0f;
165  c.g = (float) color.G / 255.0f;
166  c.b = (float) color.B / 255.0f;
167  c.a = (BlendWeight(pBlend) * color.A) / 255.0f;
168  return c;
169 }
170 
171 void NanoVGRect(NVGcontext* pContext, const IRECT& r)
172 {
173  nvgRect(pContext, r.L, r.T, r.W(), r.H());
174 }
175 
176 void NanoVGSetBlendMode(NVGcontext* pContext, const IBlend* pBlend)
177 {
178  if (!pBlend)
179  {
180  nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OVER);
181  return;
182  }
183 
184  switch (pBlend->mMethod)
185  {
186  case EBlend::SrcOver: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OVER); break;
187  case EBlend::SrcIn: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_IN); break;
188  case EBlend::SrcOut: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OUT); break;
189  case EBlend::SrcAtop: nvgGlobalCompositeOperation(pContext, NVG_ATOP); break;
190  case EBlend::DstOver: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_OVER); break;
191  case EBlend::DstIn: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_IN); break;
192  case EBlend::DstOut: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_OUT); break;
193  case EBlend::DstAtop: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_ATOP); break;
194  case EBlend::Add: nvgGlobalCompositeBlendFunc(pContext, NVG_SRC_ALPHA, NVG_DST_ALPHA); break;
195  case EBlend::XOR: nvgGlobalCompositeOperation(pContext, NVG_XOR); break;
196  }
197 }
198 
199 NVGpaint NanoVGPaint(NVGcontext* pContext, const IPattern& pattern, const IBlend* pBlend)
200 {
201  assert(pattern.NStops() > 0);
202 
203  double s[2], e[2];
204 
205  NVGcolor icol = NanoVGColor(pattern.GetStop(0).mColor, pBlend);
206  NVGcolor ocol = NanoVGColor(pattern.GetStop(pattern.NStops() - 1).mColor, pBlend);
207 
208  // Invert transform
209  IMatrix inverse = IMatrix(pattern.mTransform).Invert();
210  inverse.TransformPoint(s[0], s[1], 0.0, 0.0);
211 
212  if (pattern.mType == EPatternType::Radial)
213  {
214  return nvgRadialGradient(pContext, s[0], s[1], inverse.mXX * pattern.GetStop(0).mOffset, inverse.mXX, icol, ocol);
215  }
216  else
217  {
218  inverse.TransformPoint(e[0], e[1], 0.0, 1.0);
219 
220  return nvgLinearGradient(pContext, s[0], s[1], e[0], e[1], icol, ocol);
221  }
222 }
223 
224 END_IGRAPHICS_NAMESPACE
225 END_IPLUG_NAMESPACE
226 
227 #pragma mark -
228 
229 IGraphicsNanoVG::IGraphicsNanoVG(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
230 : IGraphics(dlg, w, h, fps, scale)
231 {
232  DBGMSG("IGraphics NanoVG @ %i FPS\n", fps);
233  StaticStorage<IFontData>::Accessor storage(sFontCache);
234  storage.Retain();
235 }
236 
237 IGraphicsNanoVG::~IGraphicsNanoVG()
238 {
239  StaticStorage<IFontData>::Accessor storage(sFontCache);
240  storage.Release();
241  ClearFBOStack();
242 }
243 
245 {
246 #if defined IGRAPHICS_METAL
247  return "NanoVG | Metal";
248 #else
249  #if defined OS_WEB
250  return "NanoVG | WebGL";
251  #else
252  #if defined IGRAPHICS_GL2
253  return "NanoVG | GL2";
254  #elif defined IGRAPHICS_GL3
255  return "NanoVG | GL3";
256  #elif defined IGRAPHICS_GLES2
257  return "NanoVG | GLES2";
258  #elif defined IGRAPHICS_GLES3
259  return "NanoVG | GLES3";
260  #endif
261  #endif
262 #endif
263 }
264 
266 {
267  char extLower[32];
268  ToLower(extLower, ext);
269  return (strstr(extLower, "png") != nullptr) || (strstr(extLower, "jpg") != nullptr) || (strstr(extLower, "jpeg") != nullptr);
270 }
271 
272 IBitmap IGraphicsNanoVG::LoadBitmap(const char* name, int nStates, bool framesAreHorizontal, int targetScale)
273 {
274  if (targetScale == 0)
275  targetScale = GetRoundedScreenScale();
276 
277  // NanoVG does not use the global static cache, since bitmaps are textures linked to a context
278  StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
279  APIBitmap* pAPIBitmap = storage.Find(name, targetScale);
280 
281  // If the bitmap is not already cached at the targetScale
282  if (!pAPIBitmap)
283  {
284  const char* ext = name + strlen(name) - 1;
285  while (ext >= name && *ext != '.') --ext;
286  ++ext;
287 
288  WDL_String fullPathOrResourceID;
289  int sourceScale = 0;
290  EResourceLocation resourceFound = SearchImageResource(name, ext, fullPathOrResourceID, targetScale, sourceScale);
291 
292  bool bitmapTypeSupported = BitmapExtSupported(ext); // KTX textures pass this test (if ext is .ktx.png)
293 
294  if(resourceFound == EResourceLocation::kNotFound || !bitmapTypeSupported)
295  {
296  assert(0 && "Bitmap not found");
297  return IBitmap(); // return invalid IBitmap
298  }
299 
300  pAPIBitmap = LoadAPIBitmap(fullPathOrResourceID.Get(), sourceScale, resourceFound, ext);
301 
302  storage.Add(pAPIBitmap, name, sourceScale);
303 
304  assert(pAPIBitmap && "Bitmap not loaded");
305  }
306 
307  return IBitmap(pAPIBitmap, nStates, framesAreHorizontal, name);
308 }
309 
310 APIBitmap* IGraphicsNanoVG::LoadAPIBitmap(const char* fileNameOrResID, int scale, EResourceLocation location, const char* ext)
311 {
312  int idx = 0;
313  int nvgImageFlags = 0;
314 
315 #ifdef OS_IOS
316  if (location == EResourceLocation::kPreloadedTexture)
317  {
318  idx = mnvgCreateImageFromHandle(mVG, gTextureMap[fileNameOrResID], nvgImageFlags);
319  }
320  else
321 #endif
322 
323 #ifdef OS_WIN
324  if (location == EResourceLocation::kWinBinary)
325  {
326  const void* pResData = nullptr;
327 
328  int size = 0;
329  pResData = LoadWinResource(fileNameOrResID, ext, size, GetWinModuleHandle());
330 
331  if (pResData)
332  {
333  ActivateGLContext(); // no-op on non WIN/GL
334  idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*) pResData, size);
335  DeactivateGLContext(); // no-op on non WIN/GL
336  }
337  }
338  else
339 #endif
340  if (location == EResourceLocation::kAbsolutePath)
341  {
342  ActivateGLContext(); // no-op on non WIN/GL
343  idx = nvgCreateImage(mVG, fileNameOrResID, nvgImageFlags);
344  DeactivateGLContext(); // no-op on non WIN/GL
345  }
346 
347  return new Bitmap(mVG, fileNameOrResID, scale, idx, location == EResourceLocation::kPreloadedTexture);
348 }
349 
350 APIBitmap* IGraphicsNanoVG::LoadAPIBitmap(const char* name, const void* pData, int dataSize, int scale)
351 {
352  StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
353  APIBitmap* pBitmap = storage.Find(name, scale);
354 
355  if (!pBitmap)
356  {
357  int idx = 0;
358  int nvgImageFlags = 0;
359 
360  ActivateGLContext();
361  idx = idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*)pData, dataSize);
362  DeactivateGLContext();
363 
364  pBitmap = new Bitmap(mVG, name, scale, idx, false);
365 
366  storage.Add(pBitmap, name, scale);
367  }
368 
369  return pBitmap;
370 }
371 
372 APIBitmap* IGraphicsNanoVG::CreateAPIBitmap(int width, int height, int scale, double drawScale, bool cacheable)
373 {
374  if (mInDraw)
375  {
376  nvgEndFrame(mVG);
377  }
378 
379  APIBitmap* pAPIBitmap = new Bitmap(this, mVG, width, height, scale, drawScale);
380 
381  if (mInDraw)
382  {
383  nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
384  nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
385  }
386 
387  return pAPIBitmap;
388 }
389 
390 void IGraphicsNanoVG::GetLayerBitmapData(const ILayerPtr& layer, RawBitmapData& data)
391 {
392  const APIBitmap* pBitmap = layer->GetAPIBitmap();
393  int size = pBitmap->GetWidth() * pBitmap->GetHeight() * 4;
394 
395  data.Resize(size);
396 
397  if (data.GetSize() >= size)
398  {
399  PushLayer(layer.get());
400  nvgReadPixels(mVG, pBitmap->GetBitmap(), 0, 0, pBitmap->GetWidth(), pBitmap->GetHeight(), data.Get());
401  PopLayer();
402  }
403 }
404 
405 void IGraphicsNanoVG::ApplyShadowMask(ILayerPtr& layer, RawBitmapData& mask, const IShadow& shadow)
406 {
407  const APIBitmap* pBitmap = layer->GetAPIBitmap();
408  int width = pBitmap->GetWidth();
409  int height = pBitmap->GetHeight();
410  int size = width * height * 4;
411 
412  if (mask.GetSize() >= size)
413  {
414  if (!shadow.mDrawForeground)
415  {
416  PushLayer(layer.get());
417  nvgGlobalCompositeBlendFunc(mVG, NVG_ZERO, NVG_ZERO);
418  PathRect(layer->Bounds());
419  nvgFillColor(mVG, NanoVGColor(COLOR_TRANSPARENT));
420  nvgFill(mVG);
421  PopLayer();
422  }
423 
424  IRECT bounds(layer->Bounds());
425 
426  Bitmap maskRawBitmap(mVG, width, height, mask.Get(), pBitmap->GetScale(), pBitmap->GetDrawScale());
427  APIBitmap* shadowBitmap = CreateAPIBitmap(width, height, pBitmap->GetScale(), pBitmap->GetDrawScale());
428  IBitmap tempLayerBitmap(shadowBitmap, 1, false);
429  IBitmap maskBitmap(&maskRawBitmap, 1, false);
430  ILayer shadowLayer(shadowBitmap, layer->Bounds(), nullptr, IRECT());
431 
432  PathTransformSave();
433  PushLayer(layer.get());
434  PushLayer(&shadowLayer);
435  DrawBitmap(maskBitmap, bounds, 0, 0, nullptr);
436  IBlend blend1(EBlend::SrcIn, 1.0);
437  PathRect(layer->Bounds());
438  PathTransformTranslate(-shadow.mXOffset, -shadow.mYOffset);
439  PathFill(shadow.mPattern, IFillOptions(), &blend1);
440  PopLayer();
441  IBlend blend2(EBlend::DstOver, shadow.mOpacity);
442  bounds.Translate(shadow.mXOffset, shadow.mYOffset);
443  DrawBitmap(tempLayerBitmap, bounds, 0, 0, &blend2);
444  PopLayer();
445  PathTransformRestore();
446  }
447 }
448 
450 {
451 #if defined IGRAPHICS_METAL
452  mVG = nvgCreateContext(pContext, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER); //TODO: NVG_STENCIL_STROKES currently has issues
453 #else
454  mVG = nvgCreateContext(NVG_ANTIALIAS /*| NVG_STENCIL_STROKES*/);
455 #endif
456 
457  if (mVG == nullptr)
458  DBGMSG("Could not init nanovg.\n");
459 }
460 
462 {
463  // need to remove all the controls to free framebuffers, before deleting context
464  RemoveAllControls();
465 
466  StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
467  storage.Clear();
468 
469  if(mMainFrameBuffer != nullptr)
470  nvgDeleteFramebuffer(mMainFrameBuffer);
471 
472  mMainFrameBuffer = nullptr;
473 
474  if(mVG)
475  nvgDeleteContext(mVG);
476 
477  mVG = nullptr;
478 }
479 
481 {
482  if (mMainFrameBuffer != nullptr)
483  nvgDeleteFramebuffer(mMainFrameBuffer);
484 
485  if (mVG)
486  {
487  mMainFrameBuffer = nvgCreateFramebuffer(mVG, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale(), 0);
488 
489  if (mMainFrameBuffer == nullptr)
490  DBGMSG("Could not init FBO.\n");
491  }
492 }
493 
495 {
496  mInDraw = true;
497  IGraphics::BeginFrame(); // start perf graph timing
498 
499 #ifdef IGRAPHICS_GL
500  glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
501  glClearColor(0.f, 0.f, 0.f, 0.f);
502  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
503  #if defined OS_MAC
504  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mInitialFBO); // stash apple fbo
505  #endif
506 #endif
507 
508  nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
509  nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
510 }
511 
513 {
514  nvgEndFrame(mVG); // end main frame buffer update
515  nvgBindFramebuffer(nullptr);
516  nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
517 
518  NVGpaint img = nvgImagePattern(mVG, 0, 0, WindowWidth(), WindowHeight(), 0, mMainFrameBuffer->image, 1.0f);
519 
520  nvgSave(mVG);
521  nvgResetTransform(mVG);
522  nvgTranslate(mVG, mXTranslation, mYTranslation);
523  nvgBeginPath(mVG);
524  nvgRect(mVG, 0, 0, WindowWidth(), WindowHeight());
525  nvgFillPaint(mVG, img);
526  nvgFill(mVG);
527  nvgRestore(mVG);
528 
529 #if defined OS_MAC && defined IGRAPHICS_GL
530  glBindFramebuffer(GL_FRAMEBUFFER, mInitialFBO); // restore apple fbo
531 #endif
532 
533  nvgEndFrame(mVG);
534 
535 #if defined IGRAPHICS_IMGUI && defined IGRAPHICS_GL
536  if(mImGuiRenderer)
537  mImGuiRenderer->NewFrame();
538 #endif
539 
540  mInDraw = false;
541  ClearFBOStack();
542 }
543 
544 void IGraphicsNanoVG::DrawBitmap(const IBitmap& bitmap, const IRECT& dest, int srcX, int srcY, const IBlend* pBlend)
545 {
546  APIBitmap* pAPIBitmap = bitmap.GetAPIBitmap();
547 
548  assert(pAPIBitmap);
549 
550  // First generate a scaled image paint
551  NVGpaint imgPaint;
552  double scale = 1.0 / (pAPIBitmap->GetScale() * pAPIBitmap->GetDrawScale());
553 
554  nvgTransformScale(imgPaint.xform, scale, scale);
555 
556  imgPaint.xform[4] = dest.L - srcX;
557  imgPaint.xform[5] = dest.T - srcY;
558  imgPaint.extent[0] = bitmap.W() * bitmap.GetScale();
559  imgPaint.extent[1] = bitmap.H() * bitmap.GetScale();
560  imgPaint.image = pAPIBitmap->GetBitmap();
561  imgPaint.radius = imgPaint.feather = 0.f;
562  imgPaint.innerColor = imgPaint.outerColor = nvgRGBAf(1, 1, 1, BlendWeight(pBlend));
563 
564  // Now draw
565 
566  nvgBeginPath(mVG); // Clears any existing path
567  nvgRect(mVG, dest.L, dest.T, dest.W(), dest.H());
568  nvgFillPaint(mVG, imgPaint);
569  NanoVGSetBlendMode(mVG, pBlend);
570  nvgFill(mVG);
571  nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
572  nvgBeginPath(mVG); // Clears the bitmap rect from the path state
573 }
574 
576 {
577  nvgBeginPath(mVG);
578 }
579 
581 {
582  nvgClosePath(mVG);
583 }
584 
585 void IGraphicsNanoVG::PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding)
586 {
587  nvgArc(mVG, cx, cy, r, DegToRad(a1 - 90.f), DegToRad(a2 - 90.f), winding == EWinding::CW ? NVG_CW : NVG_CCW);
588 }
589 
590 void IGraphicsNanoVG::PathMoveTo(float x, float y)
591 {
592  nvgMoveTo(mVG, x, y);
593 }
594 
595 void IGraphicsNanoVG::PathLineTo(float x, float y)
596 {
597  nvgLineTo(mVG, x, y);
598 }
599 
600 void IGraphicsNanoVG::PathCubicBezierTo(float c1x, float c1y, float c2x, float c2y, float x2, float y2)
601 {
602  nvgBezierTo(mVG, c1x, c1y, c2x, c2y, x2, y2);
603 }
604 
605 void IGraphicsNanoVG::PathQuadraticBezierTo(float cx, float cy, float x2, float y2)
606 {
607  nvgQuadTo(mVG, cx, cy, x2, y2);
608 }
609 
611 {
612  nvgPathWinding(mVG, clockwise ? NVG_CW : NVG_CCW);
613 }
614 
616 {
617  return COLOR_BLACK; //TODO:
618 }
619 
620 void IGraphicsNanoVG::PrepareAndMeasureText(const IText& text, const char* str, IRECT& r, double& x, double & y) const
621 {
622  float fbounds[4];
623 
624  assert(nvgFindFont(mVG, text.mFont) != -1 && "No font found - did you forget to load it?");
625 
626  nvgFontBlur(mVG, 0);
627  nvgFontSize(mVG, text.mSize);
628  nvgFontFace(mVG, text.mFont);
629 
630  int align = 0;
631 
632  switch (text.mAlign)
633  {
634  case EAlign::Near: align = NVG_ALIGN_LEFT; x = r.L; break;
635  case EAlign::Center: align = NVG_ALIGN_CENTER; x = r.MW(); break;
636  case EAlign::Far: align = NVG_ALIGN_RIGHT; x = r.R; break;
637  }
638 
639  switch (text.mVAlign)
640  {
641  case EVAlign::Top: align |= NVG_ALIGN_TOP; y = r.T; break;
642  case EVAlign::Middle: align |= NVG_ALIGN_MIDDLE; y = r.MH(); break;
643  case EVAlign::Bottom: align |= NVG_ALIGN_BOTTOM; y = r.B; break;
644  }
645 
646  nvgTextAlign(mVG, align);
647  nvgTextBounds(mVG, x, y, str, NULL, fbounds);
648 
649  r = IRECT(fbounds[0], fbounds[1], fbounds[2], fbounds[3]);
650 }
651 
652 float IGraphicsNanoVG::DoMeasureText(const IText& text, const char* str, IRECT& bounds) const
653 {
654  IRECT r = bounds;
655  double x, y;
656  PrepareAndMeasureText(text, str, bounds, x, y);
657  DoMeasureTextRotation(text, r, bounds);
658 
659  return bounds.W();
660 }
661 
662 void IGraphicsNanoVG::DoDrawText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
663 {
664  IRECT measured = bounds;
665  double x, y;
666 
667  PrepareAndMeasureText(text, str, measured, x, y);
668  PathTransformSave();
669  DoTextRotation(text, bounds, measured);
670  nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
671  NanoVGSetBlendMode(mVG, pBlend);
672  nvgText(mVG, x, y, str, NULL);
673  nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
674  PathTransformRestore();
675 }
676 
677 void IGraphicsNanoVG::PathStroke(const IPattern& pattern, float thickness, const IStrokeOptions& options, const IBlend* pBlend)
678 {
679  // First set options
680  switch (options.mCapOption)
681  {
682  case ELineCap::Butt: nvgLineCap(mVG, NVG_BUTT); break;
683  case ELineCap::Round: nvgLineCap(mVG, NVG_ROUND); break;
684  case ELineCap::Square: nvgLineCap(mVG, NVG_SQUARE); break;
685  }
686 
687  switch (options.mJoinOption)
688  {
689  case ELineJoin::Miter: nvgLineJoin(mVG, NVG_MITER); break;
690  case ELineJoin::Round: nvgLineJoin(mVG, NVG_ROUND); break;
691  case ELineJoin::Bevel: nvgLineJoin(mVG, NVG_BEVEL); break;
692  }
693 
694  nvgMiterLimit(mVG, options.mMiterLimit);
695  nvgStrokeWidth(mVG, thickness);
696 
697  // NanoVG does not support dashed paths
698  if (pattern.mType == EPatternType::Solid)
699  nvgStrokeColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
700  else
701  nvgStrokePaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
702 
703  nvgPathWinding(mVG, NVG_CCW);
704  NanoVGSetBlendMode(mVG, pBlend);
705  nvgStroke(mVG);
706  nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
707 
708  if (!options.mPreserve)
709  nvgBeginPath(mVG); // Clears the path state
710 }
711 
712 void IGraphicsNanoVG::PathFill(const IPattern& pattern, const IFillOptions& options, const IBlend* pBlend)
713 {
714  switch(options.mFillRule)
715  {
716  // This concept of fill vs. even/odd winding does not really translate to nanovg.
717  // Instead the caller is responsible for settting winding correctly for each subpath
718  // based on whether it's a solid (NVG_CCW) or hole (NVG_CW).
719  case EFillRule::Winding:
720  nvgPathWinding(mVG, NVG_CCW);
721  break;
722  case EFillRule::EvenOdd:
723  nvgPathWinding(mVG, NVG_CW);
724  break;
725  case EFillRule::Preserve:
726  // don't set a winding rule for the path, to preserve individual windings on subpaths
727  default:
728  break;
729  }
730 
731  if (pattern.mType == EPatternType::Solid)
732  nvgFillColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
733  else
734  nvgFillPaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
735 
736  NanoVGSetBlendMode(mVG, pBlend);
737  nvgFill(mVG);
738  nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
739 
740  if (!options.mPreserve)
741  nvgBeginPath(mVG); // Clears the path state
742 }
743 
744 bool IGraphicsNanoVG::LoadAPIFont(const char* fontID, const PlatformFontPtr& font)
745 {
746  StaticStorage<IFontData>::Accessor storage(sFontCache);
747  IFontData* cached = storage.Find(fontID);
748 
749  if (cached)
750  {
751  nvgCreateFontFaceMem(mVG, fontID, cached->Get(), cached->GetSize(), cached->GetFaceIdx(), 0);
752  return true;
753  }
754 
755  IFontDataPtr data = font->GetFontData();
756 
757  if (data->IsValid() && nvgCreateFontFaceMem(mVG, fontID, data->Get(), data->GetSize(), data->GetFaceIdx(), 0) != -1)
758  {
759  storage.Add(data.release(), fontID);
760  return true;
761  }
762 
763  return false;
764 }
765 
766 void IGraphicsNanoVG::UpdateLayer()
767 {
768  if (mLayers.empty())
769  {
770  nvgEndFrame(mVG);
771 #ifdef IGRAPHICS_GL
772  glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
773 #endif
774  nvgBindFramebuffer(mMainFrameBuffer);
775  nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
776  }
777  else
778  {
779  nvgEndFrame(mVG);
780 #ifdef IGRAPHICS_GL
781  const double scale = GetBackingPixelScale();
782  glViewport(0, 0, mLayers.top()->Bounds().W() * scale, mLayers.top()->Bounds().H() * scale);
783 #endif
784  nvgBindFramebuffer(dynamic_cast<const Bitmap*>(mLayers.top()->GetAPIBitmap())->GetFBO());
785  nvgBeginFrame(mVG, mLayers.top()->Bounds().W() * GetDrawScale(), mLayers.top()->Bounds().H() * GetDrawScale(), GetScreenScale());
786  }
787 }
788 
789 void IGraphicsNanoVG::PathTransformSetMatrix(const IMatrix& m)
790 {
791  double xTranslate = 0.0;
792  double yTranslate = 0.0;
793 
794  if (!mLayers.empty())
795  {
796  IRECT bounds = mLayers.top()->Bounds();
797 
798  xTranslate = -bounds.L;
799  yTranslate = -bounds.T;
800  }
801 
802  nvgResetTransform(mVG);
803  nvgScale(mVG, GetDrawScale(), GetDrawScale());
804  nvgTranslate(mVG, xTranslate, yTranslate);
805  nvgTransform(mVG, m.mXX, m.mYX, m.mXY, m.mYY, m.mTX, m.mTY);
806 }
807 
808 void IGraphicsNanoVG::SetClipRegion(const IRECT& r)
809 {
810  nvgScissor(mVG, r.L, r.T, r.W(), r.H());
811 }
812 
813 void IGraphicsNanoVG::DrawDottedLine(const IColor& color, float x1, float y1, float x2, float y2, const IBlend* pBlend, float thickness, float dashLen)
814 {
815  const float xd = x1 - x2;
816  const float yd = y1 - y2;
817  const float len = std::sqrt(xd * xd + yd * yd);
818 
819  const float segs = std::round(len / dashLen);
820  const float incr = 1.f / segs;
821 
822  float xs = x1;
823  float ys = y1;
824 
825  PathMoveTo(xs, ys);
826 
827  for (int i = 1; i < static_cast<int>(segs); i+=2)
828  {
829  float progress = incr * static_cast<float>(i);
830 
831  float xe = x1 + progress * (x2 - x1);
832  float ye = y1 + progress * (y2 - y1);
833 
834  PathLineTo(xe, ye);
835 
836  progress += incr;
837 
838  xs = x1 + progress * (x2 - x1);
839  ys = y1 + progress * (y2 - y1);
840 
841  PathMoveTo(xs, ys);
842  }
843 
844  PathStroke(color, thickness, IStrokeOptions(), pBlend);
845 }
846 
847 void IGraphicsNanoVG::DrawDottedRect(const IColor& color, const IRECT& bounds, const IBlend* pBlend, float thickness, float dashLen)
848 {
849  const int xsegs = static_cast<int>(std::ceil(bounds.W() / (dashLen * 2.f)));
850  const int ysegs = static_cast<int>(std::ceil(bounds.H() / (dashLen * 2.f)));
851 
852  float x1 = bounds.L;
853  float y1 = bounds.T;
854 
855  float x2 = x1;
856  float y2 = y1;
857 
858  PathMoveTo(x1, y1);
859 
860  for(int j = 0; j < 2; j++)
861  {
862  for (int i = 0; i < xsegs; i++)
863  {
864  x2 = Clip(x1 + dashLen, bounds.L, bounds.R);
865  PathLineTo(x2, y2);
866  x1 = Clip(x2 + dashLen, bounds.L, bounds.R);
867  PathMoveTo(x1, y1);
868  }
869 
870  x2 = x1;
871 
872  for (int i = 0; i < ysegs; i++)
873  {
874  y2 = Clip(y1 + dashLen, bounds.T, bounds.B);
875  PathLineTo(x2, y2);
876  y1 = Clip(y2 + dashLen, bounds.T, bounds.B);
877  PathMoveTo(x1, y1);
878  }
879 
880  y2 = y1;
881 
882  dashLen = -dashLen;
883  }
884 
885  PathStroke(color, thickness, IStrokeOptions(), pBlend);
886 }
887 
888 void IGraphicsNanoVG::DeleteFBO(NVGframebuffer* pBuffer)
889 {
890  if (!mInDraw)
891  nvgDeleteFramebuffer(pBuffer);
892  else
893  {
894  WDL_MutexLock lock(&mFBOMutex);
895  mFBOStack.push(pBuffer);
896  }
897 }
898 
899 void IGraphicsNanoVG::ClearFBOStack()
900 {
901  WDL_MutexLock lock(&mFBOMutex);
902  while (!mFBOStack.empty())
903  {
904  nvgDeleteFramebuffer(mFBOStack.top());
905  mFBOStack.pop();
906  }
907 }
908 
909 void IGraphicsNanoVG::DrawFastDropShadow(const IRECT& innerBounds, const IRECT& outerBounds, float xyDrop, float roundness, float blur, IBlend* pBlend)
910 {
911  NVGpaint shadowPaint = nvgBoxGradient(mVG, innerBounds.L + xyDrop, innerBounds.T + xyDrop, innerBounds.W(), innerBounds.H(), roundness, blur, NanoVGColor(COLOR_BLACK_DROP_SHADOW, pBlend), NanoVGColor(COLOR_TRANSPARENT, nullptr));
912  nvgBeginPath(mVG);
913  nvgRect(mVG, outerBounds.L, outerBounds.T, outerBounds.W(), outerBounds.H());
914  nvgFillPaint(mVG, shadowPaint);
915  nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
916  nvgFill(mVG);
917  nvgBeginPath(mVG);
918 }
void DoDrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
IColor GetPoint(int x, int y) override
Get the color at an X, Y location in the graphics context.
float MW() const
float MH() const
void DrawDottedLine(const IColor &color, float x1, float y1, float x2, float y2, const IBlend *pBlend, float thickness, float dashLen) override
Draw a dotted line to the graphics context.
Used to manage a rectangular area, independent of draw class/platform.
void PathCubicBezierTo(float c1x, float c1y, float c2x, float c2y, float x2, float y2) override
Add a cubic bezier to the current path from the current point to the specified location.
void PathFill(const IPattern &pattern, const IFillOptions &options, const IBlend *pBlend) override
Fill the current current path.
void DrawResize() override
Used to manage composite/blend operations, independent of draw class/platform.
User-facing bitmap abstraction that you use to manage bitmap data, independant of draw class/platform...
Used to manage fill behaviour for path based drawing back ends.
const char * GetDrawingAPIStr() override
IMatrix & Invert()
Changes the matrix to be the inverse of its original value.
A Text entry widget drawn by IGraphics to optionally override platform text entries.
int GetWidth() const
virtual void BeginFrame()
Called at the beginning of drawing.
Definition: IGraphics.cpp:823
const void * LoadWinResource(const char *resID, const char *type, int &sizeInBytes, void *pHInstance)
Load a resource from the binary (windows only).
void DrawDottedRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend, float thickness, float dashLen) override
Draw a dotted rectangle to the graphics context.
void EndFrame() override
Called by some drawing API classes to finally blit the draw bitmap onto the screen or perform other c...
Used to manage color data, independent of draw class/platform.
Used to manage stroke behaviour for path based drawing back ends.
int GetScale() const
void PathQuadraticBezierTo(float cx, float cy, float x2, float y2) override
Add a quadratic bezier to the current path from the current point to the specified location...
void PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding) override
Add an arc to the current path.
float DoMeasureText(const IText &text, const char *str, IRECT &bounds) const override
int GetScale() const
float R
Right side of the rectangle (X + W)
void PathLineTo(float x, float y) override
Add a line to the current path from the current point to the specified location.
BitmapData GetBitmap() const
float H() const
An editor delegate base class for a SOMETHING that uses IGraphics for it&#39;s UI.
void OnViewInitialized(void *pContext) override
Called after platform view initialization, so that drawing classes can e.g.
float W() const
int NStops() const
IText is used to manage font and text/text entry style for a piece of text on the UI...
int W() const
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
void ApplyShadowMask(ILayerPtr &layer, RawBitmapData &mask, const IShadow &shadow) override
Implemented by a graphics backend to apply a calculated shadow mask to a layer, according to the shad...
int H() const
float GetDrawScale() const
APIBitmap * CreateAPIBitmap(int width, int height, int scale, double drawScale, bool cacheable=false) override
Creates a new API bitmap, either in memory or as a GPU texture.
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
float BlendWeight(const IBlend *pBlend)
Helper function to extract the blend weight value from an IBlend ptr if it is valid.
Used to specify properties of a drop-shadow to a layer.
int GetHeight() const
BEGIN_IPLUG_NAMESPACE T Clip(T x, T lo, T hi)
Clips the value x between lo and hi.
void PathClose() override
Close the path that is being specified.
void TransformPoint(double &x, double &y, double x0, double y0) const
Transforms the point x, y.
void OnViewDestroyed() override
Called after a platform view is destroyed, so that drawing classes can e.g.
void DrawBitmap(const IBitmap &bitmap, const IRECT &dest, int srcX, int srcY, const IBlend *pBlend) override
Draw a bitmap (raster) image to the graphics context.
IBitmap LoadBitmap(const char *name, int nStates, bool framesAreHorizontal, int targetScale) override
Load a bitmap image from disk or from windows resource.
A base class interface for a bitmap abstraction around the different drawing back end bitmap represen...
float L
Left side of the rectangle (X)
void PathSetWinding(bool clockwise) override
NanoVG only.
void GetLayerBitmapData(const ILayerPtr &layer, RawBitmapData &data) override
Get the contents of a layers pixels as bitmap data.
APIBitmap * LoadAPIBitmap(const char *fileNameOrResID, int scale, EResourceLocation location, const char *ext) override
Drawing API method to load a bitmap, called internally.
void PathStroke(const IPattern &pattern, float thickness, const IStrokeOptions &options, const IBlend *pBlend) override
Stroke the current current path.
void PathMoveTo(float x, float y) override
Move the current point in the current path.
void DrawFastDropShadow(const IRECT &innerBounds, const IRECT &outerBounds, float xyDrop=5.f, float roundness=0.f, float blur=10.f, IBlend *pBlend=nullptr) override
NanoVG only.
static void ToLower(char *cDest, const char *cSrc)
IGraphics draw class using NanoVG.
bool BitmapExtSupported(const char *ext) override
Checks a file extension and reports whether this drawing API supports loading that extension...
void PathClear() override
Clear the stack of path drawing commands.
APIBitmap * GetAPIBitmap() const
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
Used to store pattern information for gradients.
bool LoadAPIFont(const char *fontID, const PlatformFontPtr &font) override
Drawing API method to load a font from a PlatformFontPtr, called internally.
Used to store transformation matrices.
void BeginFrame() override
Called at the beginning of drawing.
float T
Top of the rectangle (Y)
float B
Bottom of the rectangle (Y + H)
An abstraction that is used to store a temporary raster image/framebuffer.