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 ActivateGLContext(); // no-op on non WIN/GL
335 idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*) pResData, size);
336 DeactivateGLContext(); // no-op on non WIN/GL
337 }
338 }
339 else
340#endif
341 if (location == EResourceLocation::kAbsolutePath)
342 {
343 ActivateGLContext(); // no-op on non WIN/GL
344 idx = nvgCreateImage(mVG, fileNameOrResID, nvgImageFlags);
345 DeactivateGLContext(); // no-op on non WIN/GL
346 }
347
348 return new Bitmap(mVG, fileNameOrResID, scale, idx, location == EResourceLocation::kPreloadedTexture);
349}
350
351APIBitmap* IGraphicsNanoVG::LoadAPIBitmap(const char* name, const void* pData, int dataSize, int scale)
352{
353 StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
354 APIBitmap* pBitmap = storage.Find(name, scale);
355
356 if (!pBitmap)
357 {
358 int idx = 0;
359 int nvgImageFlags = 0;
360
361 ActivateGLContext();
362 idx = nvgCreateImageMem(mVG, nvgImageFlags, (unsigned char*)pData, dataSize);
363 DeactivateGLContext();
364
365 pBitmap = new Bitmap(mVG, name, scale, idx, false);
366
367 storage.Add(pBitmap, name, scale);
368 }
369
370 return pBitmap;
371}
372
373APIBitmap* IGraphicsNanoVG::CreateAPIBitmap(int width, int height, float scale, double drawScale, bool cacheable)
374{
375 if (mInDraw)
376 {
377 nvgEndFrame(mVG);
378 }
379
380 APIBitmap* pAPIBitmap = new Bitmap(this, mVG, width, height, scale, drawScale);
381
382 if (mInDraw)
383 {
384 nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
385 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
386 }
387
388 return pAPIBitmap;
389}
390
391void IGraphicsNanoVG::GetLayerBitmapData(const ILayerPtr& layer, RawBitmapData& data)
392{
393 const APIBitmap* pBitmap = layer->GetAPIBitmap();
394 int size = pBitmap->GetWidth() * pBitmap->GetHeight() * 4;
395
396 data.Resize(size);
397
398 if (data.GetSize() >= size)
399 {
400 PushLayer(layer.get());
401 nvgReadPixels(mVG, pBitmap->GetBitmap(), 0, 0, pBitmap->GetWidth(), pBitmap->GetHeight(), data.Get());
402 PopLayer();
403 }
404}
405
406void IGraphicsNanoVG::ApplyShadowMask(ILayerPtr& layer, RawBitmapData& mask, const IShadow& shadow)
407{
408 const APIBitmap* pBitmap = layer->GetAPIBitmap();
409 int width = pBitmap->GetWidth();
410 int height = pBitmap->GetHeight();
411 int size = width * height * 4;
412
413 if (mask.GetSize() >= size)
414 {
415 if (!shadow.mDrawForeground)
416 {
417 PushLayer(layer.get());
418 nvgGlobalCompositeBlendFunc(mVG, NVG_ZERO, NVG_ZERO);
419 PathRect(layer->Bounds());
420 nvgFillColor(mVG, NanoVGColor(COLOR_TRANSPARENT));
421 nvgFill(mVG);
422 PopLayer();
423 }
424
425 IRECT bounds(layer->Bounds());
426
427 Bitmap maskRawBitmap(mVG, width, height, mask.Get(), pBitmap->GetScale(), pBitmap->GetDrawScale());
428 APIBitmap* shadowBitmap = CreateAPIBitmap(width, height, pBitmap->GetScale(), pBitmap->GetDrawScale());
429 IBitmap tempLayerBitmap(shadowBitmap, 1, false);
430 IBitmap maskBitmap(&maskRawBitmap, 1, false);
431 ILayer shadowLayer(shadowBitmap, layer->Bounds(), nullptr, IRECT());
432
434 PushLayer(layer.get());
435 PushLayer(&shadowLayer);
436 DrawBitmap(maskBitmap, bounds, 0, 0, nullptr);
437 IBlend blend1(EBlend::SrcIn, 1.0);
438 PathRect(layer->Bounds());
439 PathTransformTranslate(-shadow.mXOffset, -shadow.mYOffset);
440 PathFill(shadow.mPattern, IFillOptions(), &blend1);
441 PopLayer();
442 IBlend blend2(EBlend::DstOver, shadow.mOpacity);
443 bounds.Translate(shadow.mXOffset, shadow.mYOffset);
444 DrawBitmap(tempLayerBitmap, bounds, 0, 0, &blend2);
445 PopLayer();
447 }
448}
449
451{
452#if defined IGRAPHICS_METAL
453 mVG = nvgCreateContext(pContext, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER); //TODO: NVG_STENCIL_STROKES currently has issues
454#else
455 mVG = nvgCreateContext(NVG_ANTIALIAS /*| NVG_STENCIL_STROKES*/);
456#endif
457
458 if (mVG == nullptr)
459 DBGMSG("Could not init nanovg.\n");
460}
461
463{
464 // need to remove all the controls to free framebuffers, before deleting context
466
467 StaticStorage<APIBitmap>::Accessor storage(mBitmapCache);
468 storage.Clear();
469
470 if(mMainFrameBuffer != nullptr)
471 nvgDeleteFramebuffer(mMainFrameBuffer);
472
473 mMainFrameBuffer = nullptr;
474
475 if(mVG)
476 nvgDeleteContext(mVG);
477
478 mVG = nullptr;
479}
480
482{
483 ActivateGLContext();
484
485 if (mMainFrameBuffer != nullptr)
486 nvgDeleteFramebuffer(mMainFrameBuffer);
487
488 if (mVG)
489 {
490 mMainFrameBuffer = nvgCreateFramebuffer(mVG, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale(), 0);
491
492 if (mMainFrameBuffer == nullptr)
493 DBGMSG("Could not init FBO.\n");
494 }
495
496 DeactivateGLContext();
497}
498
500{
501 mInDraw = true;
502 IGraphics::BeginFrame(); // start perf graph timing
503
504#ifdef IGRAPHICS_GL
505 glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
506 glClearColor(0.f, 0.f, 0.f, 0.f);
507 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
508 #if defined OS_MAC
509 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mInitialFBO); // stash apple fbo
510 #endif
511#endif
512
513 nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
514 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
515}
516
518{
519 nvgEndFrame(mVG); // end main frame buffer update
520 nvgBindFramebuffer(nullptr);
521 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
522
523 NVGpaint img = nvgImagePattern(mVG, 0, 0, WindowWidth(), WindowHeight(), 0, mMainFrameBuffer->image, 1.0f);
524
525 nvgSave(mVG);
526 nvgResetTransform(mVG);
527 nvgTranslate(mVG, mXTranslation, mYTranslation);
528 nvgBeginPath(mVG);
529 nvgRect(mVG, 0, 0, WindowWidth(), WindowHeight());
530 nvgFillPaint(mVG, img);
531 nvgFill(mVG);
532 nvgRestore(mVG);
533
534#if defined OS_MAC && defined IGRAPHICS_GL
535 glBindFramebuffer(GL_FRAMEBUFFER, mInitialFBO); // restore apple fbo
536#endif
537
538 nvgEndFrame(mVG);
539
540 mInDraw = false;
541 ClearFBOStack();
542}
543
544void 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
585void 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
590void IGraphicsNanoVG::PathMoveTo(float x, float y)
591{
592 nvgMoveTo(mVG, x, y);
593}
594
595void IGraphicsNanoVG::PathLineTo(float x, float y)
596{
597 nvgLineTo(mVG, x, y);
598}
599
600void 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
605void 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
620void 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
652float 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
662void 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);
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);
675}
676
677void 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
712void 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
744bool 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
766void 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
789void 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
808void IGraphicsNanoVG::SetClipRegion(const IRECT& r)
809{
810 nvgScissor(mVG, r.L, r.T, r.W(), r.H());
811}
812
813void 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
847void 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
888void 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
899void IGraphicsNanoVG::ClearFBOStack()
900{
901 WDL_MutexLock lock(&mFBOMutex);
902 while (!mFBOStack.empty())
903 {
904 nvgDeleteFramebuffer(mFBOStack.top());
905 mFBOStack.pop();
906 }
907}
908
909void 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}
919
920void IGraphicsNanoVG::DrawMultiLineText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
921{
922 nvgSave(mVG);
923 nvgFontSize(mVG, text.mSize);
924 nvgFontFace(mVG, text.mFont);
925
926 float x = 0.0, y = 0.0;
927 const float width = bounds.W();
928 int align = 0;
929 float yOffsetScale = 0.0;
930
931 switch (text.mAlign)
932 {
933 case EAlign::Near: align = NVG_ALIGN_LEFT; x = bounds.L; break;
934 case EAlign::Center: align = NVG_ALIGN_CENTER; x = bounds.MW(); break;
935 case EAlign::Far: align = NVG_ALIGN_RIGHT; x = bounds.R; break;
936 }
937
938 switch (text.mVAlign)
939 {
940 case EVAlign::Top:
941 {
942 align |= NVG_ALIGN_TOP;
943 y = bounds.T;
944 yOffsetScale = 0.0;
945 break;
946 }
947 case EVAlign::Middle:
948 {
949 align |= NVG_ALIGN_MIDDLE;
950 y = bounds.MH();
951 yOffsetScale = 0.5;
952 break;
953 }
954 case EVAlign::Bottom:
955 {
956 align |= NVG_ALIGN_BOTTOM;
957 y = bounds.B;
958 yOffsetScale = 1.0;
959 break;
960 }
961 }
962
963 nvgTextAlign(mVG, align);
964
965 NVGtextRow rows[3];
966 const char* start;
967 const char* end;
968 int nRows = 0;
969 int lines = 0;
970 float lineHeight;
971 nvgTextMetrics(mVG, NULL, NULL, &lineHeight);
972 nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
973
974 for (auto run : {0, 1})
975 {
976 start = str;
977 end = str + strlen(str);
978 while ((nRows = nvgTextBreakLines(mVG, start, end, width, rows, 3))) {
979 for (int i = 0; i < nRows; i++) {
980 if (run == 0)
981 {
982 lines++;
983 }
984 else
985 {
986 NVGtextRow* row = &rows[i];
987 nvgText(mVG, x, y - ((lines*lineHeight)*yOffsetScale), row->start, row->end);
988 y += lineHeight;
989 }
990 }
991 start = rows[nRows-1].next;
992 }
993 }
994
995 nvgRestore(mVG);
996}
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:2025
void PathTransformTranslate(float x, float y)
Apply a translation transform to the current path.
Definition: IGraphics.cpp:2741
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
Definition: IGraphics.cpp:2646
virtual float GetBackingPixelScale() const
Definition: IGraphics.h:1784
void DoMeasureTextRotation(const IText &text, const IRECT &bounds, IRECT &rect) const
Definition: IGraphics.cpp:2238
int WindowWidth() const
Gets the width of the graphics context including draw scaling.
Definition: IGraphics.h:1094
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:1887
void PathTransformRestore()
Restore the affine transform of the current path, to the previously saved state.
Definition: IGraphics.cpp:2719
void PushLayer(ILayer *pLayer)
Push a layer on to the stack.
Definition: IGraphics.cpp:2016
int GetRoundedScreenScale() const
Gets the screen/display scaling factor, rounded up.
Definition: IGraphics.h:1114
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:1110
void PathTransformSave()
Save the current affine transform of the current path.
Definition: IGraphics.cpp:2714
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1106
int WindowHeight() const
Gets the height of the graphics context including draw scaling.
Definition: IGraphics.h:1098
virtual void BeginFrame()
Called at the beginning of drawing.
Definition: IGraphics.cpp:875
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,...