diff --git a/nanovega.d b/nanovega.d index 4dd9f13..9092c03 100644 --- a/nanovega.d +++ b/nanovega.d @@ -22,6 +22,125 @@ The NanoVega API is modeled loosely on HTML5 canvas API. If you know canvas, you're up to speed with NanoVega in no time. +$(SIDE_BY_SIDE + + $(COLUMN + + D code with nanovega: + + --- + import arsd.simpledisplay; + + import arsd.nanovega; + + void main () { + NVGContext nvg; // our NanoVega context + + // we need at least OpenGL2 with GLSL to use NanoVega, + // so let's tell simpledisplay about that + setOpenGLContextVersion(2, 0); + + // now create OpenGL window + auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); + + // we need to destroy NanoVega context on window close + // stricly speaking, it is not necessary, as nothing fatal + // will happen if you'll forget it, but let's be polite. + // note that we cannot do that *after* our window was closed, + // as we need alive OpenGL context to do proper cleanup. + sdmain.onClosing = delegate () { + nvg.kill(); + }; + + // this is called just before our window will be shown for the first time. + // we must create NanoVega context here, as it needs to initialize + // internal OpenGL subsystem with valid OpenGL context. + sdmain.visibleForTheFirstTime = delegate () { + sdmain.setAsCurrentOpenGlContext(); // make this window's OpenGL context active + + // yes, that's all + nvg = nvgCreateContext(); + if (nvg is null) assert(0, "cannot initialize NanoVega"); + }; + + // this callback will be called when we will need to repaint our window + sdmain.redrawOpenGlScene = delegate () { + // fix viewport (we can do this in resize event, or here, it doesn't matter) + glViewport(0, 0, sdmain.width, sdmain.height); + + // clear window + glClearColor(0, 0, 0, 0); + glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call + + { + nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering + scope(exit) nvg.endFrame(); // and flush render queue on exit + + nvg.beginPath(); // start new path + nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) + // now set filling mode for our rectangle + // you can create colors using HTML syntax, or with convenient constants + nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); + // now fill our rect + nvg.fill(); + // and draw a nice outline + nvg.strokeColor = NVGColor.white; + nvg.strokeWidth = 2; + nvg.stroke(); + // that's all, folks! + } + }; + + sdmain.eventLoop(0, // no pulse timer required + delegate (KeyEvent event) { + if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on + }, + ); + + flushGui(); // let OS do it's cleanup + } + --- + ) + + $(COLUMN + Javascript code with HTML5 Canvas + + ```html + + + + NanoVega Simple Sample (HTML5 Translation) + + + + + + + + ``` + ) +) + Creating drawing context ======================== @@ -210,7 +329,7 @@ The following code illustrates the OpenGL state touched by the rendering code: NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. In addition you can upload your own image. - The parameter imageFlags is combination of flags defined in [NVGImageFlag]. + The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. @@ -395,7 +514,6 @@ version(nanovg_disable_vfs) { import core.stdc.stdlib : malloc, realloc, free; import core.stdc.string : memset, memcpy, strlen; import std.math : PI; -//import arsd.fontstash; version(Posix) { version = nanovg_use_freetype; @@ -771,7 +889,7 @@ public: static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// /// this (in Color c) { - pragma(inline, true); + version(aliced) pragma(inline, true); r = c.r/255.0f; g = c.g/255.0f; b = c.b/255.0f; @@ -845,16 +963,124 @@ public: } +//version = nanovega_debug_image_manager; +//version = nanovega_debug_image_manager_rc; + +/** NanoVega image handle. + * + * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. + * + * Group: images + */ +struct NVGImage { +private: + NVGContext ctx; + int id; // backend image id + +public: + /// + this() (in auto ref NVGImage src) nothrow @trusted @nogc { + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p created from %p (imgid=%d)\n", &this, src, src.id); } + ctx = cast(NVGContext)src.ctx; + id = src.id; + if (ctx !is null) ctx.nvg__imageIncRef(id); + } + + /// + ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } + + /// + this (this) nothrow @trusted @nogc { + if (ctx !is null) { + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } + ctx.nvg__imageIncRef(id); + } + } + + /// + void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc { + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p (imgid=%d) assigned from %p (imgid=%d)\n", &this, id, &src, src.id); } + if (src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); + if (ctx !is null) ctx.nvg__imageDecRef(id); + ctx = cast(NVGContext)src.ctx; + id = src.id; + } + + /// Is this image valid? + @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } + + /// Is this image valid? + @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } + + /// Returns image width, or zero for invalid image. + int width () const nothrow @trusted @nogc { + int w = 0; + if (valid) { + int h = void; + ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); + } + return w; + } + + /// Returns image height, or zero for invalid image. + int height () const nothrow @trusted @nogc { + int h = 0; + if (valid) { + int w = void; + ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); + } + return h; + } + + /// Free this image. + void clear () nothrow @trusted @nogc { + if (ctx !is null) { + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } + ctx.nvg__imageDecRef(id); + ctx = null; + id = 0; + } + } +} + + /// Paint parameters for various fills. Don't change anything here! /// Group: render_styles public struct NVGPaint { NVGMatrix xform; - float[2] extent; - float radius; - float feather; + float[2] extent = 0.0f; + float radius = 0.0f; + float feather = 0.0f; NVGColor innerColor; /// this can be used to modulate images (fill/font) NVGColor outerColor; - int image; + NVGImage image; + + this() (in auto ref NVGPaint p) nothrow @trusted @nogc { + xform = p.xform; + extent[] = p.extent[]; + radius = p.radius; + feather = p.feather; + innerColor = p.innerColor; + outerColor = p.outerColor; + image = p.image; + } + + void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc { + xform = p.xform; + extent[] = p.extent[]; + radius = p.radius; + feather = p.feather; + innerColor = p.innerColor; + outerColor = p.outerColor; + image = p.image; + } + + void clear () nothrow @trusted @nogc { + version(aliced) pragma(inline, true); + import core.stdc.string : memset; + image.clear(); + memset(&this, 0, this.sizeof); + } } /// Path winding. @@ -1019,7 +1245,7 @@ alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFla // ////////////////////////////////////////////////////////////////////////// // -package/*(arsd)*/: +private: static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { import core.stdc.stdlib : malloc; @@ -1039,7 +1265,7 @@ enum NVGtexture { struct NVGscissor { NVGMatrix xform; - float[2] extent; + float[2] extent = -1.0f; } struct NVGvertex { @@ -1139,23 +1365,28 @@ enum PointFlag : int { struct NVGstate { NVGCompositeOperationState compositeOperation; - bool shapeAntiAlias; + bool shapeAntiAlias = true; NVGPaint fill; NVGPaint stroke; - float strokeWidth; - float miterLimit; - NVGLineCap lineJoin; - NVGLineCap lineCap; - float alpha; + float strokeWidth = 1.0f; + float miterLimit = 10.0f; + NVGLineCap lineJoin = NVGLineCap.Miter; + NVGLineCap lineCap = NVGLineCap.Butt; + float alpha = 1.0f; NVGMatrix xform; NVGscissor scissor; - float fontSize; - float letterSpacing; - float lineHeight; - float fontBlur; + float fontSize = 16.0f; + float letterSpacing = 0.0f; + float lineHeight = 1.0f; + float fontBlur = 0.0f; NVGTextAlign textAlign; - int fontId; - bool evenOddMode; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill + int fontId = 0; + bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill + + void clearPaint () nothrow @trusted @nogc { + fill.clear(); + stroke.clear(); + } } struct NVGpoint { @@ -1305,7 +1536,7 @@ private: float fringeWidth; float devicePxRatio; FONScontext* fs; - int[NVG_MAX_FONTIMAGES] fontImages; + NVGImage[NVG_MAX_FONTIMAGES] fontImages; int fontImageIdx; int drawCallCount; int fillTriCount; @@ -1324,11 +1555,38 @@ private: NVGMatrix gpuAffine; int mWidth, mHeight; float mDeviceRatio; - void delegate (NVGContext ctx) nothrow @trusted @nogc cleanup; + // image manager + int imageCount; // number of alive images in this context + bool contextAlive; // context can be dead, but still contain some images @disable this (this); // no copies } +void nvg__imageIncRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { + if (ctx !is null && imgid > 0) { + ++ctx.imageCount; + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } + if (ctx.contextAlive) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); + } +} + +void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { + if (ctx !is null && imgid > 0) { + assert(ctx.imageCount > 0); + --ctx.imageCount; + version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } + if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); + version(nanovega_debug_image_manager) if (!ctx.contextAlive) { import core.stdc.stdio; printf("image[--]ref: zombie context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } + if (!ctx.contextAlive && ctx.imageCount == 0) { + // it is finally safe to free context memory + import core.stdc.stdlib : free; + version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } + free(ctx); + } + } +} + + public import core.stdc.math : nvg__sqrtf = sqrtf, nvg__modf = fmodf, @@ -1435,14 +1693,16 @@ NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { } // Constructor called by the render back-end. -package/*(arsd)*/ NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { +NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { FONSparams fontParams = void; NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); if (ctx is null) goto error; memset(ctx, 0, NVGcontextinternal.sizeof); + ctx.contextAlive = true; + ctx.params = *params; - ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; + //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); if (ctx.commands is null) goto error; @@ -1473,8 +1733,10 @@ package/*(arsd)*/ NVGContext createInternal (NVGparams* params) nothrow @trusted if (ctx.fs is null) goto error; // create font texture - ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); - if (ctx.fontImages[0] == 0) goto error; + ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); + if (ctx.fontImages[0].id == 0) goto error; + ctx.fontImages[0].ctx = ctx; + ctx.nvg__imageIncRef(ctx.fontImages[0].id); ctx.fontImageIdx = 0; ctx.pathPickId = -1; @@ -1488,33 +1750,34 @@ error: } // Called by render backend. -package/*(arsd)*/ NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { +NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { return &ctx.params; } // Destructor called by the render back-end. -package/*(arsd)*/ void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { +void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { if (ctx is null) return; - + if (ctx.contextAlive) { if (ctx.commands !is null) free(ctx.commands); nvg__deletePathCache(ctx.cache); if (ctx.fs) fonsDeleteInternal(ctx.fs); - foreach (uint i; 0..NVG_MAX_FONTIMAGES) { - if (ctx.fontImages[i] != 0) { - ctx.deleteImage(ctx.fontImages[i]); - ctx.fontImages[i] = 0; - } - } + foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); - if (ctx.cleanup !is null) ctx.cleanup(ctx); + ctx.contextAlive = false; + if (ctx.imageCount == 0) { + version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } free(ctx); + } else { + version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } + } + } } /// Delete NanoVega context. @@ -1526,6 +1789,11 @@ public void kill (ref NVGContext ctx) nothrow @trusted @nogc { } } +/// Returns `true` if the given context is not `null` and can be used for painting. +/// Group: context_management +public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } + + // ////////////////////////////////////////////////////////////////////////// // // Frame Management @@ -1562,6 +1830,7 @@ public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); + foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); ctx.nstates = 0; ctx.save(); ctx.reset(); @@ -1609,13 +1878,13 @@ public void endFrame (NVGContext ctx) nothrow @trusted @nogc { NVGstate* state = nvg__getState(ctx); ctx.params.renderFlush(ctx.params.userPtr, state.compositeOperation); if (ctx.fontImageIdx != 0) { - int fontImage = ctx.fontImages[ctx.fontImageIdx]; + auto fontImage = ctx.fontImages[ctx.fontImageIdx]; int j = 0, iw, ih; // delete images that smaller than current one - if (fontImage == 0) return; + if (!fontImage.valid) return; ctx.imageSize(fontImage, iw, ih); foreach (int i; 0..ctx.fontImageIdx) { - if (ctx.fontImages[i] != 0) { + if (ctx.fontImages[i].valid) { int nw, nh; ctx.imageSize(ctx.fontImages[i], nw, nh); if (nw < iw || nh < ih) { @@ -1630,7 +1899,7 @@ public void endFrame (NVGContext ctx) nothrow @trusted @nogc { ctx.fontImages[0] = fontImage; ctx.fontImageIdx = 0; // clear all images after j - ctx.fontImages[j..NVG_MAX_FONTIMAGES] = 0; + ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; } } @@ -1690,9 +1959,7 @@ private: void clearNode (int idx) nothrow @trusted @nogc { if (idx < 0 || idx >= nnodes) return; Node* node = &nodes[idx]; - if (svctx !is null && node.paint.image > 0) { - svctx.params.renderDeleteTexture(svctx.params.userPtr, node.paint.image); - } + if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); if (node.path !is null) node.path.clear(); } @@ -1873,7 +2140,6 @@ void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint p NVGpathCache* cc = node.path; cc.copyFrom(ctx.cache); node.paint = paint; - ctx.params.renderTextureIncRef(ctx.params.userPtr, node.paint.image); // copy path commands (we may need 'em for picking) version(all) { cc.ncommands = ctx.ncommands; @@ -2431,8 +2697,7 @@ public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inlin // ////////////////////////////////////////////////////////////////////////// // void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc { - //pragma(inline, true); - memset(&p, 0, p.sizeof); + p.clear(); p.xform.identity; p.radius = 0.0f; p.feather = 1.0f; @@ -2452,7 +2717,10 @@ void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @ */ public bool save (NVGContext ctx) nothrow @trusted @nogc { if (ctx.nstates >= NVG_MAX_STATES) return false; - if (ctx.nstates > 0) memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); + if (ctx.nstates > 0) { + //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); + ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; + } ++ctx.nstates; return true; } @@ -2461,6 +2729,7 @@ public bool save (NVGContext ctx) nothrow @trusted @nogc { /// Group: state_handling public bool restore (NVGContext ctx) nothrow @trusted @nogc { if (ctx.nstates <= 1) return false; + ctx.states[ctx.nstates-1].clearPaint(); --ctx.nstates; return true; } @@ -2469,7 +2738,7 @@ public bool restore (NVGContext ctx) nothrow @trusted @nogc { /// Group: state_handling public void reset (NVGContext ctx) nothrow @trusted @nogc { NVGstate* state = nvg__getState(ctx); - memset(state, 0, (*state).sizeof); + state.clearPaint(); nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); @@ -2482,8 +2751,7 @@ public void reset (NVGContext ctx) nothrow @trusted @nogc { state.alpha = 1.0f; state.xform.identity; - state.scissor.extent.ptr[0] = -1.0f; - state.scissor.extent.ptr[1] = -1.0f; + state.scissor.extent[] = -1.0f; state.fontSize = 16.0f; state.letterSpacing = 0.0f; @@ -2693,9 +2961,7 @@ public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nog /// Creates image by loading it from the disk from specified file name. /// Returns handle to the image or 0 on error. /// Group: images -public int createImage() (NVGContext ctx, const(char)[] filename, const(/*NVGImageFlag*/uint)[] imageFlagsList...) { - uint imageFlags = 0; - foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; +public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { static if (NanoVegaHasArsdImage) { import arsd.image; // do we have new arsd API to load images? @@ -2705,28 +2971,30 @@ public int createImage() (NVGContext ctx, const(char)[] filename, const(/*NVGIma try { auto oimg = MemoryImage.fromImageFile(filename); if (auto img = cast(TrueColorImage)oimg) { - scope(exit) { oimg.destroy; } - return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlags); + oimg = null; + scope(exit) { delete img.imageData.bytes; delete img; } + return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); } else { TrueColorImage img = oimg.getAsTrueColorImage; - oimg.destroy; - scope(exit) { img.destroy; } - return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlags); + if (auto xi = cast(IndexedImage)oimg) { delete xi.palette; delete xi.data; delete xi; } + oimg = null; + scope(exit) { delete img.imageData.bytes; delete img; } + return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); } } catch (Exception) {} - return 0; + return NVGImage.init; } else { import std.internal.cstring; ubyte* img; - int w, h, n, image; + int w, h, n; stbi_set_unpremultiply_on_load(1); stbi_convert_iphone_png_to_rgb(1); img = stbi_load(filename.tempCString, &w, &h, &n, 4); if (img is null) { //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); - return 0; + return NVGImage.init; } - image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlags); + auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); stbi_image_free(img); return image; } @@ -2736,32 +3004,28 @@ static if (NanoVegaHasArsdImage) { /// Creates image by loading it from the specified memory image. /// Returns handle to the image or 0 on error. /// Group: images - public int createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(/*NVGImageFlag*/uint)[] imageFlagsList...) { - if (img is null) return 0; - uint imageFlags = 0; - foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; + public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { + if (img is null) return NVGImage.init; if (auto tc = cast(TrueColorImage)img) { - return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlags); + return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); } else { auto tc = img.getAsTrueColorImage; - scope(exit) { tc.destroy; } - return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlags); + scope(exit) { delete tc.imageData.bytes; delete tc; } + return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); } } } else { /// Creates image by loading it from the specified chunk of memory. /// Returns handle to the image or 0 on error. /// Group: images - public int createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(/*NVGImageFlag*/uint)[] imageFlagsList...) { + public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { int w, h, n, image; ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); if (img is null) { //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); - return 0; + return NVGImage.init; } - uint imageFlags = 0; - foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; - image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlags); + image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); stbi_image_free(img); return image; } @@ -2770,34 +3034,45 @@ static if (NanoVegaHasArsdImage) { /// Creates image from specified image data. /// Returns handle to the image or 0 on error. /// Group: images -public int createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(/*NVGImageFlag*/uint)[] imageFlagsList...) nothrow @trusted @nogc { - if (w < 1 || h < 1 || data.length < w*h*4) return 0; +public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { + if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; uint imageFlags = 0; foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; - return ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); + NVGImage res; + res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); + if (res.id > 0) { + res.ctx = ctx; + ctx.nvg__imageIncRef(res.id); + } + return res; } /// Updates image data specified by image handle. /// Group: images -public void updateImage (NVGContext ctx, int image, const(void)[] data) nothrow @trusted @nogc { - if (image > 0) { +public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { + if (image.valid) { int w, h; - ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h); - ctx.params.renderUpdateTexture(ctx.params.userPtr, image, 0, 0, w, h, cast(const(ubyte)*)data.ptr); + if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); + ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); + ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); } } /// Returns the dimensions of a created image. /// Group: images -public void imageSize (NVGContext ctx, int image, out int w, out int h) nothrow @trusted @nogc { - if (image > 0) ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h); +public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { + if (image.valid) { + if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); + ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); + } } /// Deletes created image. /// Group: images -public void deleteImage (NVGContext ctx, int image) nothrow @trusted @nogc { - if (ctx is null || image <= 0) return; - ctx.params.renderDeleteTexture(ctx.params.userPtr, image); +public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { + if (ctx is null || !image.valid) return; + if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); + image.clear(); } @@ -2948,7 +3223,7 @@ public NVGPaint boxGradient (NVGContext ctx, float x, float y, float w, float h, * * Group: paints */ -public NVGPaint imagePattern (NVGContext ctx, float cx, float cy, float w, float h, float angle, int image, float alpha=1) nothrow @trusted @nogc { +public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc { NVGPaint p = void; memset(&p, 0, p.sizeof); @@ -2969,41 +3244,32 @@ public NVGPaint imagePattern (NVGContext ctx, float cx, float cy, float w, float /// Linear gradient with multiple stops. /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) /// Group: paints -public alias NVGLGS = NVGLGSdata*; - -private struct NVGLGSdata { - int imgid; // 0: invalid +public struct NVGLGS { +private: + NVGImage imgid; // [imagePattern] arguments - float cx, cy, w, h, angle; + float cx, cy, dim, angle; +public: @disable this (this); // no copies - public @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid > 0); } /// + @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return imgid.valid; } /// + void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); } /// } -/// Destroy linear gradient with stops -/// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) -/// Group: paints -public void kill (NVGContext ctx, ref NVGLGS lgs) nothrow @trusted @nogc { - if (lgs is null) return; - if (lgs.imgid > 0) { ctx.deleteImage(lgs.imgid); lgs.imgid = 0; } - free(lgs); - lgs = null; -} - -/** Sets linear gradient with stops, created with [createLinearGradientWithStops]. +/** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. * * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) * Group: paints */ -public NVGPaint linearGradient (NVGContext ctx, NVGLGS lgs) nothrow @trusted @nogc { - if (lgs is null || !lgs.valid) { +public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { + if (!lgs.valid) { NVGPaint p = void; memset(&p, 0, p.sizeof); nvg__setPaintColor(p, NVGColor.red); return p; } else { - return ctx.imagePattern(lgs.cx, lgs.cy, lgs.w, lgs.h, lgs.angle, lgs.imgid); + return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dim, lgs.dim, lgs.angle, lgs.imgid); } } @@ -3011,20 +3277,24 @@ public NVGPaint linearGradient (NVGContext ctx, NVGLGS lgs) nothrow @trusted @no /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) /// Group: paints public struct NVGGradientStop { - float offset; /// [0..1] + float offset = 0; /// [0..1] NVGColor color; /// + + /// + this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } + this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } } /// Create linear gradient data suitable to use with `linearGradient(res)`. /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) /// Group: paints -public NVGLGS createLinearGradientWithStops (NVGContext ctx, float sx, float sy, float ex, float ey, const(NVGGradientStop)[] stops) nothrow @trusted @nogc { +public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { // based on the code by Jorge Acereda enum NVG_GRADIENT_SAMPLES = 1024; static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { - float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); - float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); + immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); + immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); uint sc = 0xffffffffU; @@ -3037,11 +3307,12 @@ public NVGLGS createLinearGradientWithStops (NVGContext ctx, float sx, float sy, uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); - for (uint i = s; i < e; ++i) { + dst += s; + foreach (immutable _; s..e) { version(BigEndian) { - dst[i] = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); + *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); } else { - dst[i] = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); + *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); } r += dr; g += dg; @@ -3050,35 +3321,33 @@ public NVGLGS createLinearGradientWithStops (NVGContext ctx, float sx, float sy, } } + NVGLGS res; + uint[NVG_GRADIENT_SAMPLES] data = void; - float w = ex-sx; - float h = ey-sy; - float len = nvg__sqrtf(w*w + h*h); + immutable float w = ex-sx; + immutable float h = ey-sy; + res.dim = nvg__sqrtf(w*w+h*h); + + res.cx = sx; + res.cy = sy; + res.angle = + (/*nvg__absf(h) < 0.0001 ? 0 : + nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ + nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); + + if (stops.length > 0) { auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); - int img; if (stops.length > 64) stops = stops[0..64]; if (stops.length) { s0.color = stops[0].color; s1.color = stops[$-1].color; } gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); - if (stops.length) { foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); - } gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); - img = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[], NVGImageFlag.RepeatX, NVGImageFlag.RepeatY); - if (img <= 0) return null; - // allocate data - NVGLGS res = cast(NVGLGS)malloc((*NVGLGS).sizeof); - if (res is null) { ctx.deleteImage(img); return null; } - // fill result - res.imgid = img; - res.cx = sx; - res.cy = sy; - res.w = len; - res.h = len; - res.angle = nvg__atan2f(ey-sy, ex-sx); + res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[], NVGImageFlag.RepeatX, NVGImageFlag.RepeatY); + } return res; } @@ -6414,8 +6683,7 @@ void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { /** Creates font by loading it from the disk from specified file name. * Returns handle to the font or FONS_INVALID (aka -1) on error. - * use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). - * Maximum font name length is 63 chars, and it will be truncated. + * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). * * On POSIX systems it is possible to use fontconfig font names too. * `:noaa` in font path is still allowed, but it must be the last option. @@ -6429,7 +6697,6 @@ public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) n /** Creates font by loading it from the specified memory chunk. * Returns handle to the font or FONS_INVALID (aka -1) on error. * Won't free data on error. - * Maximum font name length is 63 chars, and it will be truncated. * * Group: text_api */ @@ -6560,8 +6827,14 @@ public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { return nvg__getState(ctx).fontId; } -/// Sets the font face based on specified name of current text style. -/// Group: text_api +/** Sets the font face based on specified name of current text style. + * + * The underlying implementation is using O(1) data structure to lookup + * font names, so you probably should use this function instead of [fontFaceId] + * to make your code more robust and less error-prone. + * + * Group: text_api + */ public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { pragma(inline, true); nvg__getState(ctx).fontId = fonsGetFontByName(ctx.fs, font); @@ -6836,16 +7109,16 @@ float nvg__getFontScale (NVGstate* state) nothrow @safe @nogc { void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { int[4] dirty = void; if (fonsValidateTexture(ctx.fs, dirty.ptr)) { - int fontImage = ctx.fontImages[ctx.fontImageIdx]; + auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; // Update texture - if (fontImage != 0) { + if (fontImage.valid) { int iw, ih; const(ubyte)* data = fonsGetTextureData(ctx.fs, &iw, &ih); int x = dirty[0]; int y = dirty[1]; int w = dirty[2]-dirty[0]; int h = dirty[3]-dirty[1]; - ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage, x, y, w, h, data); + ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); } } } @@ -6855,14 +7128,18 @@ bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { nvg__flushTextTexture(ctx); if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; // if next fontImage already have a texture - if (ctx.fontImages[ctx.fontImageIdx+1] != 0) { + if (ctx.fontImages[ctx.fontImageIdx+1].valid) { ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); } else { // calculate the new font image size and create it ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); if (iw > ih) ih *= 2; else iw *= 2; if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; - ctx.fontImages[ctx.fontImageIdx+1] = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); + ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); + if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { + ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; + ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id); + } } ++ctx.fontImageIdx; fonsResetAtlas(ctx.fs, iw, ih); @@ -10610,16 +10887,18 @@ public enum NVGContextFlag : int { /// Nothing special, i.e. empty flag. None = 0, /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). - Antialias = 1<<0, + Antialias = 1U<<0, /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ - StencilStrokes = 1<<1, + StencilStrokes = 1U<<1, /// Flag indicating that additional debug checks are done. - Debug = 1<<2, + Debug = 1U<<2, /// Filter (antialias) fonts - FontAA = 1<<7, + FontAA = 1U<<7, /// Don't filter (antialias) fonts - FontNoAA = 1<<8, + FontNoAA = 1U<<8, + /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. + Default = 1U<<31, } public enum NANOVG_GL_USE_STATE_FILTER = true; @@ -11272,8 +11551,8 @@ bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* p frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; frag.strokeThr = strokeThr; - if (paint.image != 0) { - tex = glnvg__findTexture(gl, paint.image); + if (paint.image.valid) { + tex = glnvg__findTexture(gl, paint.image.id); if (tex is null) return false; if ((tex.flags&NVGImageFlag.FlipY) != 0) { /* @@ -11465,6 +11744,7 @@ void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { GLNVGcontext* gl = cast(GLNVGcontext*)uptr; + foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); gl.nverts = 0; gl.npaths = 0; gl.ncalls = 0; @@ -11566,6 +11846,8 @@ void glnvg__renderFlush (void* uptr, NVGCompositeOperationState compositeOperati case GLNVG_AFFINE: glnvg__affine(gl, call); break; default: break; } + // and free texture, why not + glnvg__deleteTexture(gl, call.image); } glDisableVertexAttribArray(0); @@ -11678,7 +11960,8 @@ void glnvg__renderFill (void* uptr, NVGPaint* paint, NVGscissor* scissor, float call.pathOffset = glnvg__allocPaths(gl, npaths); if (call.pathOffset == -1) goto error; call.pathCount = npaths; - call.image = paint.image; + call.image = paint.image.id; + if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); if (npaths == 1 && paths[0].convex) { call.type = GLNVG_CONVEXFILL; @@ -11753,7 +12036,8 @@ void glnvg__renderStroke (void* uptr, NVGPaint* paint, NVGscissor* scissor, floa call.pathOffset = glnvg__allocPaths(gl, npaths); if (call.pathOffset == -1) goto error; call.pathCount = npaths; - call.image = paint.image; + call.image = paint.image.id; + if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); // Allocate vertices for all the paths. maxverts = glnvg__maxVertCount(paths, npaths); @@ -11801,7 +12085,8 @@ void glnvg__renderTriangles (void* uptr, NVGPaint* paint, NVGscissor* scissor, c if (call is null) return; call.type = GLNVG_TRIANGLES; - call.image = paint.image; + call.image = paint.image.id; + if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); // Allocate vertices for all the paths. call.triangleOffset = glnvg__allocVerts(gl, nverts); @@ -11850,20 +12135,28 @@ void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { } +/** Creates NanoVega contexts for OpenGL2+. + * + * Specify creation flags as additional arguments, like this: + * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` + * + * If you won't specify any flags, defaults will be used: + * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. + * + * Group: context_management + */ +public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { version(aliced) { - private enum NVGDefaultContextFlagsXX = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; + enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; } else { - private enum NVGDefaultContextFlagsXX = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; + enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; + } + uint flags = 0; + if (flagList.length != 0) { + foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); + } else { + flags = DefaultFlags; } - -/// Default flags for [nvgCreateContext]. -/// Group: context_management -public enum NVGDefaultContextFlags = NVGDefaultContextFlagsXX; - -/// Creates NanoVega contexts for OpenGL2+. -/// Flags should be combination (bitwise or) of the [NVGContextFlag] members. -/// Group: context_management -public NVGContext nvgCreateContext (int flags=NVGDefaultContextFlags) nothrow @trusted @nogc { NVGparams params = void; NVGContext ctx = null; version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not?