iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
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
70using namespace iplug;
71using namespace igraphics;
72
73#pragma mark - Private Classes and Structs
74
76{
77public:
78 Bitmap(NVGcontext* pContext, const char* path, double sourceScale, int nvgImageID, bool shared = false);
79 Bitmap(IGraphicsNanoVG* pGraphics, NVGcontext* pContext, int width, int height, float scale, float drawScale);
80 Bitmap(NVGcontext* pContext, int width, int height, const uint8_t* pData, float scale, float drawScale);
81 virtual ~Bitmap();
82 NVGframebuffer* GetFBO() const { return mFBO; }
83private:
84 IGraphicsNanoVG *mGraphics = nullptr;
85 NVGcontext* mVG;
86 NVGframebuffer* mFBO = nullptr;
87 bool mSharedTexture = false;
88};
89
90IGraphicsNanoVG::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
102IGraphicsNanoVG::Bitmap::Bitmap(IGraphicsNanoVG* pGraphics, NVGcontext* pContext, int width, int height, float scale, float drawScale)
103{
104 assert(width > 0 && height > 0);
105 mGraphics = pGraphics;
106 mVG = pContext;
107 mFBO = nvgCreateFramebuffer(pContext, width, height, 0);
108
109 nvgBindFramebuffer(mFBO);
110
111#ifdef IGRAPHICS_METAL
112 mnvgClearWithColor(mVG, nvgRGBAf(0, 0, 0, 0));
113#else
114 glViewport(0, 0, width, height);
115 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
116 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
117#endif
118 nvgBeginFrame(mVG, width, height, 1.f);
119 nvgEndFrame(mVG);
120
121 SetBitmap(mFBO->image, width, height, scale, drawScale);
122}
123
124IGraphicsNanoVG::Bitmap::Bitmap(NVGcontext* pContext, int width, int height, const uint8_t* pData, float scale, float drawScale)
125{
126 int idx = nvgCreateImageRGBA(pContext, width, height, 0, pData);
127 mVG = pContext;
128 SetBitmap(idx, width, height, scale, drawScale);
129}
130
131IGraphicsNanoVG::Bitmap::~Bitmap()
132{
133 if(!mSharedTexture)
134 {
135 if(mFBO)
136 mGraphics->DeleteFBO(mFBO);
137 else
138 nvgDeleteImage(mVG, GetBitmap());
139 }
140}
141
142// Fonts
143static StaticStorage<IFontData> sFontCache;
144
145extern std::map<std::string, MTLTexturePtr> gTextureMap;
146
147// Retrieving pixels
148static void nvgReadPixels(NVGcontext* pContext, int image, int x, int y, int width, int height, void* pData)
149{
150#if defined(IGRAPHICS_GL)
151 glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
152#elif defined(IGRAPHICS_METAL)
153 mnvgReadPixels(pContext, image, x, y, width, height, pData);
154#endif
155}
156
157#pragma mark - Utilities
158
159BEGIN_IPLUG_NAMESPACE
160BEGIN_IGRAPHICS_NAMESPACE
161
162NVGcolor NanoVGColor(const IColor& color, const IBlend* pBlend)
163{
164 NVGcolor c;
165 c.r = (float) color.R / 255.0f;
166 c.g = (float) color.G / 255.0f;
167 c.b = (float) color.B / 255.0f;
168 c.a = (BlendWeight(pBlend) * color.A) / 255.0f;
169 return c;
170}
171
172void NanoVGRect(NVGcontext* pContext, const IRECT& r)
173{
174 nvgRect(pContext, r.L, r.T, r.W(), r.H());
175}
176
177void NanoVGSetBlendMode(NVGcontext* pContext, const IBlend* pBlend)
178{
179 if (!pBlend)
180 {
181 nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OVER);
182 return;
183 }
184
185 switch (pBlend->mMethod)
186 {
187 case EBlend::SrcOver: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OVER); break;
188 case EBlend::SrcIn: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_IN); break;
189 case EBlend::SrcOut: nvgGlobalCompositeOperation(pContext, NVG_SOURCE_OUT); break;
190 case EBlend::SrcAtop: nvgGlobalCompositeOperation(pContext, NVG_ATOP); break;
191 case EBlend::DstOver: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_OVER); break;
192 case EBlend::DstIn: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_IN); break;
193 case EBlend::DstOut: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_OUT); break;
194 case EBlend::DstAtop: nvgGlobalCompositeOperation(pContext, NVG_DESTINATION_ATOP); break;
195 case EBlend::Add: nvgGlobalCompositeBlendFunc(pContext, NVG_SRC_ALPHA, NVG_DST_ALPHA); break;
196 case EBlend::XOR: nvgGlobalCompositeOperation(pContext, NVG_XOR); break;
197 }
198}
199
200NVGpaint NanoVGPaint(NVGcontext* pContext, const IPattern& pattern, const IBlend* pBlend)
201{
202 assert(pattern.NStops() > 0);
203
204 double s[2], e[2];
205
206 NVGcolor icol = NanoVGColor(pattern.GetStop(0).mColor, pBlend);
207 NVGcolor ocol = NanoVGColor(pattern.GetStop(pattern.NStops() - 1).mColor, pBlend);
208
209 // Invert transform
210 IMatrix inverse = IMatrix(pattern.mTransform).Invert();
211 inverse.TransformPoint(s[0], s[1], 0.0, 0.0);
212
213 if (pattern.mType == EPatternType::Radial)
214 {
215 return nvgRadialGradient(pContext, s[0], s[1], inverse.mXX * pattern.GetStop(0).mOffset, inverse.mXX, icol, ocol);
216 }
217 else
218 {
219 inverse.TransformPoint(e[0], e[1], 0.0, 1.0);
220
221 return nvgLinearGradient(pContext, s[0], s[1], e[0], e[1], icol, ocol);
222 }
223}
224
225END_IGRAPHICS_NAMESPACE
226END_IPLUG_NAMESPACE
227
228#pragma mark -
229
230IGraphicsNanoVG::IGraphicsNanoVG(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
231: IGraphics(dlg, w, h, fps, scale)
232{
233 DBGMSG("IGraphics NanoVG @ %i FPS\n", fps);
234 StaticStorage<IFontData>::Accessor storage(sFontCache);
235 storage.Retain();
236}
237
238IGraphicsNanoVG::~IGraphicsNanoVG()
239{
240 StaticStorage<IFontData>::Accessor storage(sFontCache);
241 storage.Release();
242 ClearFBOStack();
243}
244
246{
247#if defined IGRAPHICS_METAL
248 return "NanoVG | Metal";
249#else
250 #if defined OS_WEB
251 return "NanoVG | WebGL";
252 #else
253 #if defined IGRAPHICS_GL2
254 return "NanoVG | GL2";
255 #elif defined IGRAPHICS_GL3
256 return "NanoVG | GL3";
257 #elif defined IGRAPHICS_GLES2
258 return "NanoVG | GLES2";
259 #elif defined IGRAPHICS_GLES3
260 return "NanoVG | GLES3";
261 #endif
262 #endif
263#endif
264}
265
267{
268 char extLower[32];
269 ToLower(extLower, ext);
270 return (strstr(extLower, "png") != nullptr) || (strstr(extLower, "jpg") != nullptr) || (strstr(extLower, "jpeg") != nullptr);
271}
272
273IBitmap IGraphicsNanoVG::LoadBitmap(const char* name, int nStates, bool framesAreHorizontal, int targetScale)
274{
275 if (targetScale == 0)
276 targetScale = GetRoundedScreenScale();
277
278 // NanoVG does not use the global static cache, since bitmaps are textures linked to a context
279 StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
280 APIBitmap* pAPIBitmap = storage.Find(name, targetScale);
281
282 // If the bitmap is not already cached at the targetScale
283 if (!pAPIBitmap)
284 {
285 const char* ext = name + strlen(name) - 1;
286 while (ext >= name && *ext != '.') --ext;
287 ++ext;
288
289 WDL_String fullPathOrResourceID;
290 int sourceScale = 0;
291 EResourceLocation resourceFound = SearchImageResource(name, ext, fullPathOrResourceID, targetScale, sourceScale);
292
293 bool bitmapTypeSupported = BitmapExtSupported(ext); // KTX textures pass this test (if ext is .ktx.png)
294
295 if(resourceFound == EResourceLocation::kNotFound || !bitmapTypeSupported)
296 {
297 assert(0 && "Bitmap not found");
298 return IBitmap(); // return invalid IBitmap
299 }
300
301 pAPIBitmap = LoadAPIBitmap(fullPathOrResourceID.Get(), sourceScale, resourceFound, ext);
302
303 storage.Add(pAPIBitmap, name, sourceScale);
304
305 assert(pAPIBitmap && "Bitmap not loaded");
306 }
307
308 return IBitmap(pAPIBitmap, nStates, framesAreHorizontal, name);
309}
310
311APIBitmap* IGraphicsNanoVG::LoadAPIBitmap(const char* fileNameOrResID, int scale, EResourceLocation location, const char* ext)
312{
313 int idx = 0;
314 int nvgImageFlags = 0;
315
316#ifdef OS_IOS
317 if (location == EResourceLocation::kPreloadedTexture)
318 {
319 idx = mnvgCreateImageFromHandle(mVG, gTextureMap[fileNameOrResID], nvgImageFlags);
320 }
321 else
322#endif
323
324#ifdef OS_WIN
325 if (location == EResourceLocation::kWinBinary)
326 {
327 const void* pResData = nullptr;
328
329 int size = 0;
330 pResData = LoadWinResource(fileNameOrResID, ext, size, GetWinModuleHandle());
331
332 if (pResData)
333 {
334 ScopedGLContext scopedGLCtx {this};
335 idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*) pResData, size);
336 }
337 }
338 else
339#endif
340 if (location == EResourceLocation::kAbsolutePath)
341 {
342 ScopedGLContext scopedGLCtx {this};
343 idx = nvgCreateImage(mVG, fileNameOrResID, nvgImageFlags);
344 }
345
346 return new Bitmap(mVG, fileNameOrResID, scale, idx, location == EResourceLocation::kPreloadedTexture);
347}
348
349APIBitmap* IGraphicsNanoVG::LoadAPIBitmap(const char* name, const void* pData, int dataSize, int scale)
350{
351 StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
352 APIBitmap* pBitmap = storage.Find(name, scale);
353
354 if (!pBitmap)
355 {
356 int idx = 0;
357 int nvgImageFlags = 0;
358
359 {
360 ScopedGLContext scopedGLCtx {this};
361 idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*)pData, dataSize);
362 }
363
364 pBitmap = new Bitmap(mVG, name, scale, idx, false);
365
366 storage.Add(pBitmap, name, scale);
367 }
368
369 return pBitmap;
370}
371
372APIBitmap* IGraphicsNanoVG::CreateAPIBitmap(int width, int height, float 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
390void 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
405void 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
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();
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
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 ScopedGLContext scopedGLCtx {this};
483
484 if (mMainFrameBuffer != nullptr)
485 nvgDeleteFramebuffer(mMainFrameBuffer);
486
487 if (mVG)
488 {
489 mMainFrameBuffer = nvgCreateFramebuffer(mVG, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale(), 0);
490
491 if (mMainFrameBuffer == nullptr)
492 DBGMSG("Could not init FBO.\n");
493 }
494}
495
497{
498 mInDraw = true;
499 IGraphics::BeginFrame(); // start perf graph timing
500
501#ifdef IGRAPHICS_GL
502 glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
503 glClearColor(0.f, 0.f, 0.f, 0.f);
504 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
505 #if defined OS_MAC
506 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mInitialFBO); // stash apple fbo
507 #endif
508#endif
509
510 nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
511 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
512}
513
515{
516 nvgEndFrame(mVG); // end main frame buffer update
517 nvgBindFramebuffer(nullptr);
518 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
519
520 NVGpaint img = nvgImagePattern(mVG, 0, 0, WindowWidth(), WindowHeight(), 0, mMainFrameBuffer->image, 1.0f);
521
522 nvgSave(mVG);
523 nvgResetTransform(mVG);
524 nvgTranslate(mVG, mXTranslation, mYTranslation);
525 nvgBeginPath(mVG);
526 nvgRect(mVG, 0, 0, WindowWidth(), WindowHeight());
527 nvgFillPaint(mVG, img);
528 nvgFill(mVG);
529 nvgRestore(mVG);
530
531#if defined OS_MAC && defined IGRAPHICS_GL
532 glBindFramebuffer(GL_FRAMEBUFFER, mInitialFBO); // restore apple fbo
533#endif
534
535 nvgEndFrame(mVG);
536
537 mInDraw = false;
538 ClearFBOStack();
539}
540
541void IGraphicsNanoVG::DrawBitmap(const IBitmap& bitmap, const IRECT& dest, int srcX, int srcY, const IBlend* pBlend)
542{
543 APIBitmap* pAPIBitmap = bitmap.GetAPIBitmap();
544
545 assert(pAPIBitmap);
546
547 // First generate a scaled image paint
548 NVGpaint imgPaint;
549 double scale = 1.0 / (pAPIBitmap->GetScale() * pAPIBitmap->GetDrawScale());
550
551 nvgTransformScale(imgPaint.xform, scale, scale);
552
553 imgPaint.xform[4] = dest.L - srcX;
554 imgPaint.xform[5] = dest.T - srcY;
555 imgPaint.extent[0] = bitmap.W() * bitmap.GetScale();
556 imgPaint.extent[1] = bitmap.H() * bitmap.GetScale();
557 imgPaint.image = pAPIBitmap->GetBitmap();
558 imgPaint.radius = imgPaint.feather = 0.f;
559 imgPaint.innerColor = imgPaint.outerColor = nvgRGBAf(1, 1, 1, BlendWeight(pBlend));
560
561 // Now draw
562
563 nvgBeginPath(mVG); // Clears any existing path
564 nvgRect(mVG, dest.L, dest.T, dest.W(), dest.H());
565 nvgFillPaint(mVG, imgPaint);
566 NanoVGSetBlendMode(mVG, pBlend);
567 nvgFill(mVG);
568 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
569 nvgBeginPath(mVG); // Clears the bitmap rect from the path state
570}
571
573{
574 nvgBeginPath(mVG);
575}
576
578{
579 nvgClosePath(mVG);
580}
581
582void IGraphicsNanoVG::PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding)
583{
584 nvgArc(mVG, cx, cy, r, DegToRad(a1 - 90.f), DegToRad(a2 - 90.f), winding == EWinding::CW ? NVG_CW : NVG_CCW);
585}
586
587void IGraphicsNanoVG::PathMoveTo(float x, float y)
588{
589 nvgMoveTo(mVG, x, y);
590}
591
592void IGraphicsNanoVG::PathLineTo(float x, float y)
593{
594 nvgLineTo(mVG, x, y);
595}
596
597void IGraphicsNanoVG::PathCubicBezierTo(float c1x, float c1y, float c2x, float c2y, float x2, float y2)
598{
599 nvgBezierTo(mVG, c1x, c1y, c2x, c2y, x2, y2);
600}
601
602void IGraphicsNanoVG::PathQuadraticBezierTo(float cx, float cy, float x2, float y2)
603{
604 nvgQuadTo(mVG, cx, cy, x2, y2);
605}
606
608{
609 nvgPathWinding(mVG, clockwise ? NVG_CW : NVG_CCW);
610}
611
613{
614 return COLOR_BLACK; //TODO:
615}
616
617void IGraphicsNanoVG::PrepareAndMeasureText(const IText& text, const char* str, IRECT& r, double& x, double & y) const
618{
619 float fbounds[4];
620
621 assert(nvgFindFont(mVG, text.mFont) != -1 && "No font found - did you forget to load it?");
622
623 nvgFontBlur(mVG, 0);
624 nvgFontSize(mVG, text.mSize);
625 nvgFontFace(mVG, text.mFont);
626
627 int align = 0;
628
629 switch (text.mAlign)
630 {
631 case EAlign::Near: align = NVG_ALIGN_LEFT; x = r.L; break;
632 case EAlign::Center: align = NVG_ALIGN_CENTER; x = r.MW(); break;
633 case EAlign::Far: align = NVG_ALIGN_RIGHT; x = r.R; break;
634 }
635
636 switch (text.mVAlign)
637 {
638 case EVAlign::Top: align |= NVG_ALIGN_TOP; y = r.T; break;
639 case EVAlign::Middle: align |= NVG_ALIGN_MIDDLE; y = r.MH(); break;
640 case EVAlign::Bottom: align |= NVG_ALIGN_BOTTOM; y = r.B; break;
641 }
642
643 nvgTextAlign(mVG, align);
644 nvgTextBounds(mVG, x, y, str, NULL, fbounds);
645
646 r = IRECT(fbounds[0], fbounds[1], fbounds[2], fbounds[3]);
647}
648
649float IGraphicsNanoVG::DoMeasureText(const IText& text, const char* str, IRECT& bounds) const
650{
651 IRECT r = bounds;
652 double x, y;
653 PrepareAndMeasureText(text, str, bounds, x, y);
654 DoMeasureTextRotation(text, r, bounds);
655
656 return bounds.W();
657}
658
659void IGraphicsNanoVG::DoDrawText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
660{
661 IRECT measured = bounds;
662 double x, y;
663
664 PrepareAndMeasureText(text, str, measured, x, y);
666 DoTextRotation(text, bounds, measured);
667 nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
668 NanoVGSetBlendMode(mVG, pBlend);
669 nvgText(mVG, x, y, str, NULL);
670 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
672}
673
674void IGraphicsNanoVG::PathStroke(const IPattern& pattern, float thickness, const IStrokeOptions& options, const IBlend* pBlend)
675{
676 // First set options
677 switch (options.mCapOption)
678 {
679 case ELineCap::Butt: nvgLineCap(mVG, NVG_BUTT); break;
680 case ELineCap::Round: nvgLineCap(mVG, NVG_ROUND); break;
681 case ELineCap::Square: nvgLineCap(mVG, NVG_SQUARE); break;
682 }
683
684 switch (options.mJoinOption)
685 {
686 case ELineJoin::Miter: nvgLineJoin(mVG, NVG_MITER); break;
687 case ELineJoin::Round: nvgLineJoin(mVG, NVG_ROUND); break;
688 case ELineJoin::Bevel: nvgLineJoin(mVG, NVG_BEVEL); break;
689 }
690
691 nvgMiterLimit(mVG, options.mMiterLimit);
692 nvgStrokeWidth(mVG, thickness);
693
694 // NanoVG does not support dashed paths
695 if (pattern.mType == EPatternType::Solid)
696 nvgStrokeColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
697 else
698 nvgStrokePaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
699
700 nvgPathWinding(mVG, NVG_CCW);
701 NanoVGSetBlendMode(mVG, pBlend);
702 nvgStroke(mVG);
703 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
704
705 if (!options.mPreserve)
706 nvgBeginPath(mVG); // Clears the path state
707}
708
709void IGraphicsNanoVG::PathFill(const IPattern& pattern, const IFillOptions& options, const IBlend* pBlend)
710{
711 switch(options.mFillRule)
712 {
713 // This concept of fill vs. even/odd winding does not really translate to nanovg.
714 // Instead the caller is responsible for settting winding correctly for each subpath
715 // based on whether it's a solid (NVG_CCW) or hole (NVG_CW).
716 case EFillRule::Winding:
717 nvgPathWinding(mVG, NVG_CCW);
718 break;
719 case EFillRule::EvenOdd:
720 nvgPathWinding(mVG, NVG_CW);
721 break;
722 case EFillRule::Preserve:
723 // don't set a winding rule for the path, to preserve individual windings on subpaths
724 default:
725 break;
726 }
727
728 if (pattern.mType == EPatternType::Solid)
729 nvgFillColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
730 else
731 nvgFillPaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
732
733 NanoVGSetBlendMode(mVG, pBlend);
734 nvgFill(mVG);
735 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
736
737 if (!options.mPreserve)
738 nvgBeginPath(mVG); // Clears the path state
739}
740
741bool IGraphicsNanoVG::LoadAPIFont(const char* fontID, const PlatformFontPtr& font)
742{
743 StaticStorage<IFontData>::Accessor storage(sFontCache);
744 IFontData* cached = storage.Find(fontID);
745
746 if (cached)
747 {
748 nvgCreateFontFaceMem(mVG, fontID, cached->Get(), cached->GetSize(), cached->GetFaceIdx(), 0);
749 return true;
750 }
751
752 IFontDataPtr data = font->GetFontData();
753
754 if (data->IsValid() && nvgCreateFontFaceMem(mVG, fontID, data->Get(), data->GetSize(), data->GetFaceIdx(), 0) != -1)
755 {
756 storage.Add(data.release(), fontID);
757 return true;
758 }
759
760 return false;
761}
762
763void IGraphicsNanoVG::UpdateLayer()
764{
765 if (mLayers.empty())
766 {
767 nvgEndFrame(mVG);
768#ifdef IGRAPHICS_GL
769 glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
770#endif
771 nvgBindFramebuffer(mMainFrameBuffer);
772 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
773 }
774 else
775 {
776 nvgEndFrame(mVG);
777#ifdef IGRAPHICS_GL
778 const double scale = GetBackingPixelScale();
779 glViewport(0, 0, mLayers.top()->Bounds().W() * scale, mLayers.top()->Bounds().H() * scale);
780#endif
781 nvgBindFramebuffer(dynamic_cast<const Bitmap*>(mLayers.top()->GetAPIBitmap())->GetFBO());
782 nvgBeginFrame(mVG, mLayers.top()->Bounds().W() * GetDrawScale(), mLayers.top()->Bounds().H() * GetDrawScale(), GetScreenScale());
783 }
784}
785
786void IGraphicsNanoVG::PathTransformSetMatrix(const IMatrix& m)
787{
788 double xTranslate = 0.0;
789 double yTranslate = 0.0;
790
791 if (!mLayers.empty())
792 {
793 IRECT bounds = mLayers.top()->Bounds();
794
795 xTranslate = -bounds.L;
796 yTranslate = -bounds.T;
797 }
798
799 nvgResetTransform(mVG);
800 nvgScale(mVG, GetDrawScale(), GetDrawScale());
801 nvgTranslate(mVG, xTranslate, yTranslate);
802 nvgTransform(mVG, m.mXX, m.mYX, m.mXY, m.mYY, m.mTX, m.mTY);
803}
804
805void IGraphicsNanoVG::SetClipRegion(const IRECT& r)
806{
807 nvgScissor(mVG, r.L, r.T, r.W(), r.H());
808}
809
810void IGraphicsNanoVG::DrawDottedLine(const IColor& color, float x1, float y1, float x2, float y2, const IBlend* pBlend, float thickness, float dashLen)
811{
812 const float xd = x1 - x2;
813 const float yd = y1 - y2;
814 const float len = std::sqrt(xd * xd + yd * yd);
815
816 const float segs = std::round(len / dashLen);
817 const float incr = 1.f / segs;
818
819 float xs = x1;
820 float ys = y1;
821
822 PathMoveTo(xs, ys);
823
824 for (int i = 1; i < static_cast<int>(segs); i+=2)
825 {
826 float progress = incr * static_cast<float>(i);
827
828 float xe = x1 + progress * (x2 - x1);
829 float ye = y1 + progress * (y2 - y1);
830
831 PathLineTo(xe, ye);
832
833 progress += incr;
834
835 xs = x1 + progress * (x2 - x1);
836 ys = y1 + progress * (y2 - y1);
837
838 PathMoveTo(xs, ys);
839 }
840
841 PathStroke(color, thickness, IStrokeOptions(), pBlend);
842}
843
844void IGraphicsNanoVG::DrawDottedRect(const IColor& color, const IRECT& bounds, const IBlend* pBlend, float thickness, float dashLen)
845{
846 const int xsegs = static_cast<int>(std::ceil(bounds.W() / (dashLen * 2.f)));
847 const int ysegs = static_cast<int>(std::ceil(bounds.H() / (dashLen * 2.f)));
848
849 float x1 = bounds.L;
850 float y1 = bounds.T;
851
852 float x2 = x1;
853 float y2 = y1;
854
855 PathMoveTo(x1, y1);
856
857 for(int j = 0; j < 2; j++)
858 {
859 for (int i = 0; i < xsegs; i++)
860 {
861 x2 = Clip(x1 + dashLen, bounds.L, bounds.R);
862 PathLineTo(x2, y2);
863 x1 = Clip(x2 + dashLen, bounds.L, bounds.R);
864 PathMoveTo(x1, y1);
865 }
866
867 x2 = x1;
868
869 for (int i = 0; i < ysegs; i++)
870 {
871 y2 = Clip(y1 + dashLen, bounds.T, bounds.B);
872 PathLineTo(x2, y2);
873 y1 = Clip(y2 + dashLen, bounds.T, bounds.B);
874 PathMoveTo(x1, y1);
875 }
876
877 y2 = y1;
878
879 dashLen = -dashLen;
880 }
881
882 PathStroke(color, thickness, IStrokeOptions(), pBlend);
883}
884
885void IGraphicsNanoVG::DeleteFBO(NVGframebuffer* pBuffer)
886{
887 if (!mInDraw)
888 nvgDeleteFramebuffer(pBuffer);
889 else
890 {
891 WDL_MutexLock lock(&mFBOMutex);
892 mFBOStack.push(pBuffer);
893 }
894}
895
896void IGraphicsNanoVG::ClearFBOStack()
897{
898 WDL_MutexLock lock(&mFBOMutex);
899 while (!mFBOStack.empty())
900 {
901 nvgDeleteFramebuffer(mFBOStack.top());
902 mFBOStack.pop();
903 }
904}
905
906void IGraphicsNanoVG::DrawFastDropShadow(const IRECT& innerBounds, const IRECT& outerBounds, float xyDrop, float roundness, float blur, IBlend* pBlend)
907{
908 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));
909 nvgBeginPath(mVG);
910 nvgRect(mVG, outerBounds.L, outerBounds.T, outerBounds.W(), outerBounds.H());
911 nvgFillPaint(mVG, shadowPaint);
912 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
913 nvgFill(mVG);
914 nvgBeginPath(mVG);
915}
916
917void IGraphicsNanoVG::DrawMultiLineText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
918{
919 nvgSave(mVG);
920 nvgFontSize(mVG, text.mSize);
921 nvgFontFace(mVG, text.mFont);
922
923 float x = 0.0, y = 0.0;
924 const float width = bounds.W();
925 int align = 0;
926 float yOffsetScale = 0.0;
927
928 switch (text.mAlign)
929 {
930 case EAlign::Near: align = NVG_ALIGN_LEFT; x = bounds.L; break;
931 case EAlign::Center: align = NVG_ALIGN_CENTER; x = bounds.MW(); break;
932 case EAlign::Far: align = NVG_ALIGN_RIGHT; x = bounds.R; break;
933 }
934
935 switch (text.mVAlign)
936 {
937 case EVAlign::Top:
938 {
939 align |= NVG_ALIGN_TOP;
940 y = bounds.T;
941 yOffsetScale = 0.0;
942 break;
943 }
944 case EVAlign::Middle:
945 {
946 align |= NVG_ALIGN_MIDDLE;
947 y = bounds.MH();
948 yOffsetScale = 0.5;
949 break;
950 }
951 case EVAlign::Bottom:
952 {
953 align |= NVG_ALIGN_BOTTOM;
954 y = bounds.B;
955 yOffsetScale = 1.0;
956 break;
957 }
958 }
959
960 nvgTextAlign(mVG, align);
961
962 NVGtextRow rows[3];
963 const char* start;
964 const char* end;
965 int nRows = 0;
966 int lines = 0;
967 float lineHeight;
968 nvgTextMetrics(mVG, NULL, NULL, &lineHeight);
969 nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
970
971 for (auto run : {0, 1})
972 {
973 start = str;
974 end = str + strlen(str);
975 while ((nRows = nvgTextBreakLines(mVG, start, end, width, rows, 3))) {
976 for (int i = 0; i < nRows; i++) {
977 if (run == 0)
978 {
979 lines++;
980 }
981 else
982 {
983 NVGtextRow* row = &rows[i];
984 nvgText(mVG, x, y - ((lines*lineHeight)*yOffsetScale), row->start, row->end);
985 y += lineHeight;
986 }
987 }
988 start = rows[nRows-1].next;
989 }
990 }
991
992 nvgRestore(mVG);
993}
const void * LoadWinResource(const char *resID, const char *type, int &sizeInBytes, void *pHInstance)
Load a resource from the binary (windows only).
A Text entry widget drawn by IGraphics to optionally override platform text entries.
A base class interface for a bitmap abstraction around the different drawing back end bitmap represen...
float GetScale() const
BitmapData GetBitmap() const
void SetBitmap(BitmapData pBitmap, int w, int h, float scale, float drawScale)
Used to initialise the members after construction.
int GetHeight() const
float GetDrawScale() const
int GetWidth() const
User-facing bitmap abstraction that you use to manage bitmap data, independant of draw class/platform...
int W() const
float GetScale() const
int H() const
APIBitmap * GetAPIBitmap() const
An editor delegate base class that uses IGraphics for the UI.
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
ILayer * PopLayer()
Pop a layer off the stack.
Definition: IGraphics.cpp:2032
void PathTransformTranslate(float x, float y)
Apply a translation transform to the current path.
Definition: IGraphics.cpp:2748
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
Definition: IGraphics.cpp:2653
virtual float GetBackingPixelScale() const
Definition: IGraphics.h:1798
void DoMeasureTextRotation(const IText &text, const IRECT &bounds, IRECT &rect) const
Definition: IGraphics.cpp:2245
int WindowWidth() const
Gets the width of the graphics context including draw scaling.
Definition: IGraphics.h:1105
EResourceLocation SearchImageResource(const char *fileName, const char *type, WDL_String &result, int targetScale, int &sourceScale)
Search for a bitmap image resource matching the target scale.
Definition: IGraphics.cpp:1892
void PathTransformRestore()
Restore the affine transform of the current path, to the previously saved state.
Definition: IGraphics.cpp:2726
void PushLayer(ILayer *pLayer)
Push a layer on to the stack.
Definition: IGraphics.cpp:2023
int GetRoundedScreenScale() const
Gets the screen/display scaling factor, rounded up.
Definition: IGraphics.h:1125
void RemoveAllControls()
Removes all regular IControls from the control list, as well as special controls (frees memory).
Definition: IGraphics.cpp:194
virtual void * GetWinModuleHandle()
Definition: IGraphics.h:926
float GetScreenScale() const
Gets the screen/display scaling factor, e.g.
Definition: IGraphics.h:1121
void PathTransformSave()
Save the current affine transform of the current path.
Definition: IGraphics.cpp:2721
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1117
int WindowHeight() const
Gets the height of the graphics context including draw scaling.
Definition: IGraphics.h:1109
virtual void BeginFrame()
Called at the beginning of drawing.
Definition: IGraphics.cpp:880
IGraphics draw class using NanoVG
const char * GetDrawingAPIStr() override
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...
void DrawResize() override
void PathClear() override
Clear the stack of path drawing commands.
IBitmap LoadBitmap(const char *name, int nStates, bool framesAreHorizontal, int targetScale) override
Load a bitmap image from disk or from windows resource.
void PathFill(const IPattern &pattern, const IFillOptions &options, const IBlend *pBlend) override
Fill the current current path.
void GetLayerBitmapData(const ILayerPtr &layer, RawBitmapData &data) override
Get the contents of a layer as Raw RGBA bitmap data NOTE: you should only call this within IControl::...
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 PathSetWinding(bool clockwise) override
NanoVG only.
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 PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding) override
Add an arc to the current path.
void PathLineTo(float x, float y) override
Add a line to the current path from the current point to the specified location.
void OnViewDestroyed() override
Called after a platform view is destroyed, so that drawing classes can e.g.
void DrawMultiLineText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
Draw some multi-line text to the graphics context in a specific rectangle (NanoVG only)
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.
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.
IColor GetPoint(int x, int y) override
Get the color at an X, Y location in the graphics context.
bool LoadAPIFont(const char *fontID, const PlatformFontPtr &font) override
Drawing API method to load a font from a PlatformFontPtr, called internally.
void PathClose() override
Close the path that is being specified.
void DoDrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
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.
void PathMoveTo(float x, float y) override
Move the current point in the current path.
void OnViewInitialized(void *pContext) override
Called after platform view initialization, so that drawing classes can e.g.
void BeginFrame() override
Called at the beginning of drawing.
void EndFrame() override
Called by some drawing API classes to finally blit the draw bitmap onto the screen or perform other c...
APIBitmap * CreateAPIBitmap(int width, int height, float scale, double drawScale, bool cacheable=false) override
Creates a new API bitmap, either in memory or as a GPU texture.
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.
bool BitmapExtSupported(const char *ext) override
Checks a file extension and reports whether this drawing API supports loading that extension.
float DoMeasureText(const IText &text, const char *str, IRECT &bounds) const override
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.
An abstraction that is used to store a temporary raster image/framebuffer.
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
float BlendWeight(const IBlend *pBlend)
Helper function to extract the blend weight value from an IBlend ptr if it is valid.
Used to manage stroke behaviour for path based drawing back ends.
BEGIN_IPLUG_NAMESPACE T Clip(T x, T lo, T hi)
Clips the value x between lo and hi.
static void ToLower(char *cDest, const char *cSrc)
Used to manage composite/blend operations, independent of draw class/platform.
Used to manage color data, independent of draw class/platform.
Used to manage fill behaviour.
Used to store transformation matrices.
IMatrix & Invert()
Changes the matrix to be the inverse of its original value.
void TransformPoint(double &x, double &y, double x0, double y0) const
Transforms the point x, y.
Used to store pattern information for gradients.
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
int NStops() const
Used to manage a rectangular area, independent of draw class/platform.
float MH() const
float W() const
float H() const
void Translate(float x, float y)
Translate this rectangle.
float MW() const
Used to specify properties of a drop-shadow to a layer.
IText is used to manage font and text/text entry style for a piece of text on the UI,...