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 = 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 if (mMainFrameBuffer != nullptr)
484 nvgDeleteFramebuffer(mMainFrameBuffer);
485
486 if (mVG)
487 {
488 mMainFrameBuffer = nvgCreateFramebuffer(mVG, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale(), 0);
489
490 if (mMainFrameBuffer == nullptr)
491 DBGMSG("Could not init FBO.\n");
492 }
493}
494
496{
497 mInDraw = true;
498 IGraphics::BeginFrame(); // start perf graph timing
499
500#ifdef IGRAPHICS_GL
501 glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
502 glClearColor(0.f, 0.f, 0.f, 0.f);
503 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
504 #if defined OS_MAC
505 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mInitialFBO); // stash apple fbo
506 #endif
507#endif
508
509 nvgBindFramebuffer(mMainFrameBuffer); // begin main frame buffer update
510 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
511}
512
514{
515 nvgEndFrame(mVG); // end main frame buffer update
516 nvgBindFramebuffer(nullptr);
517 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
518
519 NVGpaint img = nvgImagePattern(mVG, 0, 0, WindowWidth(), WindowHeight(), 0, mMainFrameBuffer->image, 1.0f);
520
521 nvgSave(mVG);
522 nvgResetTransform(mVG);
523 nvgTranslate(mVG, mXTranslation, mYTranslation);
524 nvgBeginPath(mVG);
525 nvgRect(mVG, 0, 0, WindowWidth(), WindowHeight());
526 nvgFillPaint(mVG, img);
527 nvgFill(mVG);
528 nvgRestore(mVG);
529
530#if defined OS_MAC && defined IGRAPHICS_GL
531 glBindFramebuffer(GL_FRAMEBUFFER, mInitialFBO); // restore apple fbo
532#endif
533
534 nvgEndFrame(mVG);
535
536 mInDraw = false;
537 ClearFBOStack();
538}
539
540void IGraphicsNanoVG::DrawBitmap(const IBitmap& bitmap, const IRECT& dest, int srcX, int srcY, const IBlend* pBlend)
541{
542 APIBitmap* pAPIBitmap = bitmap.GetAPIBitmap();
543
544 assert(pAPIBitmap);
545
546 // First generate a scaled image paint
547 NVGpaint imgPaint;
548 double scale = 1.0 / (pAPIBitmap->GetScale() * pAPIBitmap->GetDrawScale());
549
550 nvgTransformScale(imgPaint.xform, scale, scale);
551
552 imgPaint.xform[4] = dest.L - srcX;
553 imgPaint.xform[5] = dest.T - srcY;
554 imgPaint.extent[0] = bitmap.W() * bitmap.GetScale();
555 imgPaint.extent[1] = bitmap.H() * bitmap.GetScale();
556 imgPaint.image = pAPIBitmap->GetBitmap();
557 imgPaint.radius = imgPaint.feather = 0.f;
558 imgPaint.innerColor = imgPaint.outerColor = nvgRGBAf(1, 1, 1, BlendWeight(pBlend));
559
560 // Now draw
561
562 nvgBeginPath(mVG); // Clears any existing path
563 nvgRect(mVG, dest.L, dest.T, dest.W(), dest.H());
564 nvgFillPaint(mVG, imgPaint);
565 NanoVGSetBlendMode(mVG, pBlend);
566 nvgFill(mVG);
567 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
568 nvgBeginPath(mVG); // Clears the bitmap rect from the path state
569}
570
572{
573 nvgBeginPath(mVG);
574}
575
577{
578 nvgClosePath(mVG);
579}
580
581void IGraphicsNanoVG::PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding)
582{
583 nvgArc(mVG, cx, cy, r, DegToRad(a1 - 90.f), DegToRad(a2 - 90.f), winding == EWinding::CW ? NVG_CW : NVG_CCW);
584}
585
586void IGraphicsNanoVG::PathMoveTo(float x, float y)
587{
588 nvgMoveTo(mVG, x, y);
589}
590
591void IGraphicsNanoVG::PathLineTo(float x, float y)
592{
593 nvgLineTo(mVG, x, y);
594}
595
596void IGraphicsNanoVG::PathCubicBezierTo(float c1x, float c1y, float c2x, float c2y, float x2, float y2)
597{
598 nvgBezierTo(mVG, c1x, c1y, c2x, c2y, x2, y2);
599}
600
601void IGraphicsNanoVG::PathQuadraticBezierTo(float cx, float cy, float x2, float y2)
602{
603 nvgQuadTo(mVG, cx, cy, x2, y2);
604}
605
607{
608 nvgPathWinding(mVG, clockwise ? NVG_CW : NVG_CCW);
609}
610
612{
613 return COLOR_BLACK; //TODO:
614}
615
616void IGraphicsNanoVG::PrepareAndMeasureText(const IText& text, const char* str, IRECT& r, double& x, double & y) const
617{
618 float fbounds[4];
619
620 assert(nvgFindFont(mVG, text.mFont) != -1 && "No font found - did you forget to load it?");
621
622 nvgFontBlur(mVG, 0);
623 nvgFontSize(mVG, text.mSize);
624 nvgFontFace(mVG, text.mFont);
625
626 int align = 0;
627
628 switch (text.mAlign)
629 {
630 case EAlign::Near: align = NVG_ALIGN_LEFT; x = r.L; break;
631 case EAlign::Center: align = NVG_ALIGN_CENTER; x = r.MW(); break;
632 case EAlign::Far: align = NVG_ALIGN_RIGHT; x = r.R; break;
633 }
634
635 switch (text.mVAlign)
636 {
637 case EVAlign::Top: align |= NVG_ALIGN_TOP; y = r.T; break;
638 case EVAlign::Middle: align |= NVG_ALIGN_MIDDLE; y = r.MH(); break;
639 case EVAlign::Bottom: align |= NVG_ALIGN_BOTTOM; y = r.B; break;
640 }
641
642 nvgTextAlign(mVG, align);
643 nvgTextBounds(mVG, x, y, str, NULL, fbounds);
644
645 r = IRECT(fbounds[0], fbounds[1], fbounds[2], fbounds[3]);
646}
647
648float IGraphicsNanoVG::DoMeasureText(const IText& text, const char* str, IRECT& bounds) const
649{
650 IRECT r = bounds;
651 double x, y;
652 PrepareAndMeasureText(text, str, bounds, x, y);
653 DoMeasureTextRotation(text, r, bounds);
654
655 return bounds.W();
656}
657
658void IGraphicsNanoVG::DoDrawText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
659{
660 IRECT measured = bounds;
661 double x, y;
662
663 PrepareAndMeasureText(text, str, measured, x, y);
665 DoTextRotation(text, bounds, measured);
666 nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
667 NanoVGSetBlendMode(mVG, pBlend);
668 nvgText(mVG, x, y, str, NULL);
669 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
671}
672
673void IGraphicsNanoVG::PathStroke(const IPattern& pattern, float thickness, const IStrokeOptions& options, const IBlend* pBlend)
674{
675 // First set options
676 switch (options.mCapOption)
677 {
678 case ELineCap::Butt: nvgLineCap(mVG, NVG_BUTT); break;
679 case ELineCap::Round: nvgLineCap(mVG, NVG_ROUND); break;
680 case ELineCap::Square: nvgLineCap(mVG, NVG_SQUARE); break;
681 }
682
683 switch (options.mJoinOption)
684 {
685 case ELineJoin::Miter: nvgLineJoin(mVG, NVG_MITER); break;
686 case ELineJoin::Round: nvgLineJoin(mVG, NVG_ROUND); break;
687 case ELineJoin::Bevel: nvgLineJoin(mVG, NVG_BEVEL); break;
688 }
689
690 nvgMiterLimit(mVG, options.mMiterLimit);
691 nvgStrokeWidth(mVG, thickness);
692
693 // NanoVG does not support dashed paths
694 if (pattern.mType == EPatternType::Solid)
695 nvgStrokeColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
696 else
697 nvgStrokePaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
698
699 nvgPathWinding(mVG, NVG_CCW);
700 NanoVGSetBlendMode(mVG, pBlend);
701 nvgStroke(mVG);
702 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
703
704 if (!options.mPreserve)
705 nvgBeginPath(mVG); // Clears the path state
706}
707
708void IGraphicsNanoVG::PathFill(const IPattern& pattern, const IFillOptions& options, const IBlend* pBlend)
709{
710 switch(options.mFillRule)
711 {
712 // This concept of fill vs. even/odd winding does not really translate to nanovg.
713 // Instead the caller is responsible for settting winding correctly for each subpath
714 // based on whether it's a solid (NVG_CCW) or hole (NVG_CW).
715 case EFillRule::Winding:
716 nvgPathWinding(mVG, NVG_CCW);
717 break;
718 case EFillRule::EvenOdd:
719 nvgPathWinding(mVG, NVG_CW);
720 break;
721 case EFillRule::Preserve:
722 // don't set a winding rule for the path, to preserve individual windings on subpaths
723 default:
724 break;
725 }
726
727 if (pattern.mType == EPatternType::Solid)
728 nvgFillColor(mVG, NanoVGColor(pattern.GetStop(0).mColor, pBlend));
729 else
730 nvgFillPaint(mVG, NanoVGPaint(mVG, pattern, pBlend));
731
732 NanoVGSetBlendMode(mVG, pBlend);
733 nvgFill(mVG);
734 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
735
736 if (!options.mPreserve)
737 nvgBeginPath(mVG); // Clears the path state
738}
739
740bool IGraphicsNanoVG::LoadAPIFont(const char* fontID, const PlatformFontPtr& font)
741{
742 StaticStorage<IFontData>::Accessor storage(sFontCache);
743 IFontData* cached = storage.Find(fontID);
744
745 if (cached)
746 {
747 nvgCreateFontFaceMem(mVG, fontID, cached->Get(), cached->GetSize(), cached->GetFaceIdx(), 0);
748 return true;
749 }
750
751 IFontDataPtr data = font->GetFontData();
752
753 if (data->IsValid() && nvgCreateFontFaceMem(mVG, fontID, data->Get(), data->GetSize(), data->GetFaceIdx(), 0) != -1)
754 {
755 storage.Add(data.release(), fontID);
756 return true;
757 }
758
759 return false;
760}
761
762void IGraphicsNanoVG::UpdateLayer()
763{
764 if (mLayers.empty())
765 {
766 nvgEndFrame(mVG);
767#ifdef IGRAPHICS_GL
768 glViewport(0, 0, WindowWidth() * GetScreenScale(), WindowHeight() * GetScreenScale());
769#endif
770 nvgBindFramebuffer(mMainFrameBuffer);
771 nvgBeginFrame(mVG, WindowWidth(), WindowHeight(), GetScreenScale());
772 }
773 else
774 {
775 nvgEndFrame(mVG);
776#ifdef IGRAPHICS_GL
777 const double scale = GetBackingPixelScale();
778 glViewport(0, 0, mLayers.top()->Bounds().W() * scale, mLayers.top()->Bounds().H() * scale);
779#endif
780 nvgBindFramebuffer(dynamic_cast<const Bitmap*>(mLayers.top()->GetAPIBitmap())->GetFBO());
781 nvgBeginFrame(mVG, mLayers.top()->Bounds().W() * GetDrawScale(), mLayers.top()->Bounds().H() * GetDrawScale(), GetScreenScale());
782 }
783}
784
785void IGraphicsNanoVG::PathTransformSetMatrix(const IMatrix& m)
786{
787 double xTranslate = 0.0;
788 double yTranslate = 0.0;
789
790 if (!mLayers.empty())
791 {
792 IRECT bounds = mLayers.top()->Bounds();
793
794 xTranslate = -bounds.L;
795 yTranslate = -bounds.T;
796 }
797
798 nvgResetTransform(mVG);
799 nvgScale(mVG, GetDrawScale(), GetDrawScale());
800 nvgTranslate(mVG, xTranslate, yTranslate);
801 nvgTransform(mVG, m.mXX, m.mYX, m.mXY, m.mYY, m.mTX, m.mTY);
802}
803
804void IGraphicsNanoVG::SetClipRegion(const IRECT& r)
805{
806 nvgScissor(mVG, r.L, r.T, r.W(), r.H());
807}
808
809void IGraphicsNanoVG::DrawDottedLine(const IColor& color, float x1, float y1, float x2, float y2, const IBlend* pBlend, float thickness, float dashLen)
810{
811 const float xd = x1 - x2;
812 const float yd = y1 - y2;
813 const float len = std::sqrt(xd * xd + yd * yd);
814
815 const float segs = std::round(len / dashLen);
816 const float incr = 1.f / segs;
817
818 float xs = x1;
819 float ys = y1;
820
821 PathMoveTo(xs, ys);
822
823 for (int i = 1; i < static_cast<int>(segs); i+=2)
824 {
825 float progress = incr * static_cast<float>(i);
826
827 float xe = x1 + progress * (x2 - x1);
828 float ye = y1 + progress * (y2 - y1);
829
830 PathLineTo(xe, ye);
831
832 progress += incr;
833
834 xs = x1 + progress * (x2 - x1);
835 ys = y1 + progress * (y2 - y1);
836
837 PathMoveTo(xs, ys);
838 }
839
840 PathStroke(color, thickness, IStrokeOptions(), pBlend);
841}
842
843void IGraphicsNanoVG::DrawDottedRect(const IColor& color, const IRECT& bounds, const IBlend* pBlend, float thickness, float dashLen)
844{
845 const int xsegs = static_cast<int>(std::ceil(bounds.W() / (dashLen * 2.f)));
846 const int ysegs = static_cast<int>(std::ceil(bounds.H() / (dashLen * 2.f)));
847
848 float x1 = bounds.L;
849 float y1 = bounds.T;
850
851 float x2 = x1;
852 float y2 = y1;
853
854 PathMoveTo(x1, y1);
855
856 for(int j = 0; j < 2; j++)
857 {
858 for (int i = 0; i < xsegs; i++)
859 {
860 x2 = Clip(x1 + dashLen, bounds.L, bounds.R);
861 PathLineTo(x2, y2);
862 x1 = Clip(x2 + dashLen, bounds.L, bounds.R);
863 PathMoveTo(x1, y1);
864 }
865
866 x2 = x1;
867
868 for (int i = 0; i < ysegs; i++)
869 {
870 y2 = Clip(y1 + dashLen, bounds.T, bounds.B);
871 PathLineTo(x2, y2);
872 y1 = Clip(y2 + dashLen, bounds.T, bounds.B);
873 PathMoveTo(x1, y1);
874 }
875
876 y2 = y1;
877
878 dashLen = -dashLen;
879 }
880
881 PathStroke(color, thickness, IStrokeOptions(), pBlend);
882}
883
884void IGraphicsNanoVG::DeleteFBO(NVGframebuffer* pBuffer)
885{
886 if (!mInDraw)
887 nvgDeleteFramebuffer(pBuffer);
888 else
889 {
890 WDL_MutexLock lock(&mFBOMutex);
891 mFBOStack.push(pBuffer);
892 }
893}
894
895void IGraphicsNanoVG::ClearFBOStack()
896{
897 WDL_MutexLock lock(&mFBOMutex);
898 while (!mFBOStack.empty())
899 {
900 nvgDeleteFramebuffer(mFBOStack.top());
901 mFBOStack.pop();
902 }
903}
904
905void IGraphicsNanoVG::DrawFastDropShadow(const IRECT& innerBounds, const IRECT& outerBounds, float xyDrop, float roundness, float blur, IBlend* pBlend)
906{
907 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));
908 nvgBeginPath(mVG);
909 nvgRect(mVG, outerBounds.L, outerBounds.T, outerBounds.W(), outerBounds.H());
910 nvgFillPaint(mVG, shadowPaint);
911 nvgGlobalCompositeOperation(mVG, NVG_SOURCE_OVER);
912 nvgFill(mVG);
913 nvgBeginPath(mVG);
914}
915
916void IGraphicsNanoVG::DrawMultiLineText(const IText& text, const char* str, IRECT& bounds, const IBlend* pBlend)
917{
918 nvgSave(mVG);
919 nvgFontSize(mVG, text.mSize);
920 nvgFontFace(mVG, text.mFont);
921
922 float x = 0.0, y = 0.0;
923 const float width = bounds.W();
924 int align = 0;
925 float yOffsetScale = 0.0;
926
927 switch (text.mAlign)
928 {
929 case EAlign::Near: align = NVG_ALIGN_LEFT; x = bounds.L; break;
930 case EAlign::Center: align = NVG_ALIGN_CENTER; x = bounds.MW(); break;
931 case EAlign::Far: align = NVG_ALIGN_RIGHT; x = bounds.R; break;
932 }
933
934 switch (text.mVAlign)
935 {
936 case EVAlign::Top:
937 {
938 align |= NVG_ALIGN_TOP;
939 y = bounds.T;
940 yOffsetScale = 0.0;
941 break;
942 }
943 case EVAlign::Middle:
944 {
945 align |= NVG_ALIGN_MIDDLE;
946 y = bounds.MH();
947 yOffsetScale = 0.5;
948 break;
949 }
950 case EVAlign::Bottom:
951 {
952 align |= NVG_ALIGN_BOTTOM;
953 y = bounds.B;
954 yOffsetScale = 1.0;
955 break;
956 }
957 }
958
959 nvgTextAlign(mVG, align);
960
961 NVGtextRow rows[3];
962 const char* start;
963 const char* end;
964 int nRows = 0;
965 int lines = 0;
966 float lineHeight;
967 nvgTextMetrics(mVG, NULL, NULL, &lineHeight);
968 nvgFillColor(mVG, NanoVGColor(text.mFGColor, pBlend));
969
970 for (auto run : {0, 1})
971 {
972 start = str;
973 end = str + strlen(str);
974 while ((nRows = nvgTextBreakLines(mVG, start, end, width, rows, 3))) {
975 for (int i = 0; i < nRows; i++) {
976 if (run == 0)
977 {
978 lines++;
979 }
980 else
981 {
982 NVGtextRow* row = &rows[i];
983 nvgText(mVG, x, y - ((lines*lineHeight)*yOffsetScale), row->start, row->end);
984 y += lineHeight;
985 }
986 }
987 start = rows[nRows-1].next;
988 }
989 }
990
991 nvgRestore(mVG);
992}
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 for a SOMETHING that uses IGraphics for it's 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:2014
void PathTransformTranslate(float x, float y)
Apply a translation transform to the current path.
Definition: IGraphics.cpp:2730
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
Definition: IGraphics.cpp:2635
virtual float GetBackingPixelScale() const
Definition: IGraphics.h:1778
void DoMeasureTextRotation(const IText &text, const IRECT &bounds, IRECT &rect) const
Definition: IGraphics.cpp:2227
int WindowWidth() const
Gets the width of the graphics context including draw scaling.
Definition: IGraphics.h:1089
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:1876
void PathTransformRestore()
Restore the affine transform of the current path, to the previously saved state.
Definition: IGraphics.cpp:2708
void PushLayer(ILayer *pLayer)
Push a layer on to the stack.
Definition: IGraphics.cpp:2005
int GetRoundedScreenScale() const
Gets the screen/display scaling factor, rounded up.
Definition: IGraphics.h:1109
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:1105
void PathTransformSave()
Save the current affine transform of the current path.
Definition: IGraphics.cpp:2703
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1101
int WindowHeight() const
Gets the height of the graphics context including draw scaling.
Definition: IGraphics.h:1093
virtual void BeginFrame()
Called at the beginning of drawing.
Definition: IGraphics.cpp:867
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 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.
void DrawMultiLineText(const IText &text, const char *str, IRECT &bounds, const IBlend *pBlend) override
Draw some multi-line text to the graphics context in a specific rectangle (NanoVG only)
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,...