1 module nanovg.gl3;
2 
3 //
4 // NanoVG-d:
5 // Copyright (c) 2015 S.Percentage
6 //
7 // Original Source(NanoVG):
8 // Copyright (c) 2013 Mikko Mononen memon@inside.org
9 //
10 // This software is provided 'as-is', without any express or implied
11 // warranty.  In no event will the authors be held liable for any damages
12 // arising from the use of this software.
13 // Permission is granted to anyone to use this software for any purpose,
14 // including commercial applications, and to alter it and redistribute it
15 // freely, subject to the following restrictions:
16 // 1. The origin of this software must not be misrepresented; you must not
17 //    claim that you wrote the original software. If you use this software
18 //    in a product, an acknowledgment in the product documentation would be
19 //    appreciated but is not required.
20 // 2. Altered source versions must be plainly marked as such, and must not be
21 //    misrepresented as being the original software.
22 // 3. This notice may not be removed or altered from any source distribution.
23 //
24 
25 // NanoVG OpenGL3 renderer Implementation
26 // (Rewriting Original Version to Dlang)
27 
28 import nanovg.h;
29 import derelict.opengl3.gl3;
30 import std.string, std.range, std.algorithm, std.math;
31 import core.memory;
32 import std.experimental.logger;
33 
34 public import nanovg.glInterface;
35 
36 enum CommandType
37 {
38 	Fill, ConvexFill, Stroke, Triangles
39 }
40 enum ImageType
41 {
42 	Single, RGBA
43 }
44 enum ShaderType : int
45 {
46 	Gradient, Textured, StencilFilling, TexturedTris	
47 }
48 enum TexturePostProcess : int
49 {
50 	None, Multiply, Colorize
51 }
52 
53 auto premultiplied(NVGcolor color)
54 {
55 	return NVGcolor(color.r * color.a, color.g * color.a, color.b * color.a, color.a);
56 }
57 auto asFloat4(NVGcolor color)
58 {
59 	return [color.r, color.g, color.b, color.a];
60 }
61 auto asMatrix3x4(float[6] xform)
62 {
63 	return
64 	[
65 		xform[0], xform[1], 0.0f, 0.0f,
66 		xform[2], xform[3], 0.0f, 0.0f,
67 		xform[4], xform[5], 1.0f, 0.0f
68 	];
69 }
70 auto maxVertCount(const(NVGpath)[] paths)
71 {
72 	return paths.map!(a => a.nfill + a.nstroke).sum;
73 }
74 
75 class Texture
76 {
77 	GLuint texture, sampler;
78 	Size size;
79 	ImageType type;
80 	int flags;
81 	
82 	public this(int w, int h, int type, int imageFlags, const(byte)* pData)
83 	{
84 		info(false, "CreateTexture: ", w, "/", h);
85 		this.size = Size(w, h);
86 		this.flags = imageFlags;
87 		
88 		glGenTextures(1, &this.texture);
89 		GLContext.Texture2D = this.texture;
90 		scope(exit) GLContext.Texture2D = NullTexture;
91 		this.setPixelStoreState();
92 		
93 		GLTexture2D.Wrap.S = imageFlags.raised!NVG_IMAGE_REPEATX ? GL_REPEAT : GL_CLAMP_TO_EDGE;
94 		GLTexture2D.Wrap.T = imageFlags.raised!NVG_IMAGE_REPEATY ? GL_REPEAT : GL_CLAMP_TO_EDGE;
95 		GLTexture2D.Filter.Min = imageFlags.raised!NVG_IMAGE_GENERATE_MIPMAPS ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR;
96 		GLTexture2D.Filter.Mag = GL_LINEAR;
97 		switch(type)
98 		{
99 		case NVG_TEXTURE_RGBA:
100 			this.type = ImageType.RGBA;
101 			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
102 			break;
103 		default:
104 			this.type = ImageType.Single;
105 			glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, pData);
106 		}
107 		glCheckError();
108 		this.revertPixelStoreState();
109 		if(imageFlags.raised!NVG_IMAGE_GENERATE_MIPMAPS)
110 		{
111 			glGenerateMipmap(GL_TEXTURE_2D);
112 			glCheckError();
113 		}
114 	}
115 	public ~this()
116 	{
117 		glDeleteSamplers(1, &this.sampler);
118 		glDeleteTextures(1, &this.texture);
119 	}
120 	private void setPixelStoreState()
121 	{
122 		GLPixelStore.Alignment = 1;
123 		GLPixelStore.RowLength = this.size.width;
124 		GLPixelStore.SkipPixels = 0;
125 		GLPixelStore.SkipRows = 0;
126 	}
127 	private void revertPixelStoreState()
128 	{
129 		GLPixelStore.SkipRows = 0;
130 		GLPixelStore.SkipPixels = 0;
131 		GLPixelStore.RowLength = 0;
132 		GLPixelStore.Alignment = 4;
133 	}
134 	
135 	bool update(int x, int y, int w, int h, const(byte)* data)
136 	{
137 		info(false, "UpdateTexture: ", x, "/", y, "/", w, "/", h);
138 		info(false, "UpdateData: \n", data[0 .. w * h].map!(a => format("%02x", a)).chunks(16).enumerate.map!(a => format("+%04x: ", a[0] * 0x10) ~ a[1].join(" ")).join("\n"));
139 		GLContext.Texture2D = this.texture;
140 		this.setPixelStoreState();
141 		GLPixelStore.SkipPixels = x;
142 		GLPixelStore.SkipRows = y;
143 		
144 		final switch(this.type)
145 		{
146 		case ImageType.RGBA:
147 			glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data);
148 			break;
149 		case ImageType.Single:
150 			glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RED, GL_UNSIGNED_BYTE, data);
151 		}
152 		glCheckError();
153 		
154 		this.revertPixelStoreState();
155 		GLContext.Texture2D = NullTexture;
156 		return true;
157 	}
158 	bool getTextureSize(int* w, int* h)
159 	{
160 		*w = this.size.width;
161 		*h = this.size.height;
162 		return true;
163 	}
164 }
165 
166 class RenderProgram
167 {
168 	GLuint vsh, fsh, program;
169 	
170 	public this()
171 	{
172 		this.vsh = glCreateShader(GL_VERTEX_SHADER);
173 		this.fsh = glCreateShader(GL_FRAGMENT_SHADER);
174 		this.program = glCreateProgram();
175 		
176 		this.vsh.glShaderSource(1, [import("vertex.glsl").toStringz].ptr, null);
177 		this.fsh.glShaderSource(1, [import("fragment.glsl").toStringz].ptr, null);
178 		this.buildShaders();
179 		this.uniformBlocks = new UniformBlockIndexAccessor();
180 		this.uniforms = new UniformLocationAccessor();
181 		this.inputs = new AttributeLocationAccessor();
182 	}
183 	private void buildShaders()
184 	{
185 		GLint status;
186 		
187 		this.vsh.glCompileShader();
188 		this.vsh.glGetShaderiv(GLShaderInfo.CompileStatus, &status);
189 		if(status != GL_TRUE) throw new GLShaderCompileError(this.vsh, "Vertex");
190 		this.fsh.glCompileShader();
191 		this.fsh.glGetShaderiv(GLShaderInfo.CompileStatus, &status);
192 		if(status != GL_TRUE) throw new GLShaderCompileError(this.fsh, "Fragment");
193 		this.program.glAttachShader(this.vsh);
194 		this.program.glAttachShader(this.fsh);
195 		this.program.glLinkProgram();
196 		this.program.glGetProgramiv(GLProgramInfo.LinkStatus, &status);
197 		if(status != GL_TRUE) throw new GLProgramLinkError(this.program);
198 	}
199 	
200 	class UniformBlockIndexAccessor
201 	{
202 		GLint[string] cache;
203 		
204 		public this() { this.userIndexes = new UserIndexAccessor(); }
205 		GLint opDispatch(string op)() { return this[op]; }
206 		GLint opIndex(string op)
207 		{
208 			if(op in cache) return cache[op];
209 			cache[op] = this.outer.program.glGetUniformBlockIndex(op.toStringz);
210 			return cache[op];
211 		}
212 		class UserIndexAccessor
213 		{
214 			void opIndexAssign(GLint idx, string vn)
215 			{
216 				info(false, "UniformBlock Binding: ", this.outer[vn], " <=> ", idx);
217 				glUniformBlockBinding(this.outer.outer.program, this.outer[vn], idx);
218 				glCheckError();
219 			}
220 		}
221 		UserIndexAccessor userIndexes;
222 	}
223 	UniformBlockIndexAccessor uniformBlocks;
224 	class UniformLocationAccessor
225 	{
226 		GLint[string] cache;
227 		
228 		GLint opDispatch(string op)()
229 		{
230 			if(op in cache) return cache[op];
231 			cache[op] = this.outer.program.glGetUniformLocation(op.toStringz);
232 			return cache[op];
233 		}
234 	}
235 	UniformLocationAccessor uniforms;
236 	class AttributeLocationAccessor
237 	{
238 		GLint[string] cache;
239 		
240 		GLint opDispatch(string op)()
241 		{
242 			if(op in cache) return cache[op];
243 			cache[op] = this.outer.program.glGetAttribLocation(op.toStringz);
244 			return cache[op];
245 		}
246 	}
247 	AttributeLocationAccessor inputs;
248 }
249 struct FragUniformBuffer
250 {
251 	float[3*4] scissorMatr, paintMatr;
252 	float[4] innerColor, outerColor;
253 	float[2] scissorExt, scissorScale;
254 	float[2] extent;
255 	float radius, feather;
256 	float strokeMult, strokeThr;
257 	int texType, type;
258 }
259 struct InternalDrawCall
260 {
261 	CommandType type;
262 	int image;
263 	size_t pathOffset, pathCount;
264 	size_t triangleOffset, triangleCount;
265 	size_t uniformOffset;
266 }
267 struct Path
268 {
269 	size_t fillOffset, fillCount;
270 	size_t strokeOffset, strokeCount;
271 }
272 
273 class Context
274 {
275 	const FUBUserIndex = 1;
276 	
277 	RenderProgram program;
278 	UniformBufferObject fragUniformObject;
279 	VertexArrayObject varray;
280 	ArrayBufferObject vbuffer;
281 	Texture[] textureList;
282 	Path[] pathList;
283 	InternalDrawCall[] callList;
284 	NVGvertex[] vertexList;
285 	FragUniformBuffer[] uniformList;
286 	Size vport;
287 	size_t ubHardwareSize, ubHardwarePadding;
288 	
289 	GLint ublocFrag;
290 	GLint ulocTexImage, ulocViewSize;
291 	GLint alocVertex, alocTexcoord;
292 
293 	void init()
294 	{
295 		this.program = new RenderProgram();
296 		this.ublocFrag = this.program.uniformBlocks.frag;
297 		this.ulocTexImage = this.program.uniforms.texImage;
298 		this.ulocViewSize = this.program.uniforms.viewSize;
299 		this.alocVertex = this.program.inputs.vertex;
300 		this.alocTexcoord = this.program.inputs.texcoord;
301 		
302 		info(false, "viewSize Location: ", this.ulocViewSize);
303 		info(false, "texImage Location: ", this.ulocTexImage);
304 		
305 		this.program.uniformBlocks.userIndexes["frag"] = FUBUserIndex;
306 		this.fragUniformObject = new UniformBufferObject();
307 		this.varray = new VertexArrayObject();
308 		this.vbuffer = new ArrayBufferObject();
309 		int ub_align = GLUniformBuffer.OffsetAlignment;
310 		this.ubHardwareSize = (cast(int)((FragUniformBuffer.sizeof - 1) / ub_align) + 1) * ub_align;
311 		this.ubHardwarePadding = this.ubHardwareSize - FragUniformBuffer.sizeof;
312 		info(false, "GL UniformBlockAlign: ", ub_align);
313 		info(false, "HardwareUniformBlockSize: ", this.ubHardwareSize);
314 		info(false, "UniformBlockPadding: ", this.ubHardwarePadding);
315 		
316 		glFinish();
317 	}
318 	void terminate() {}
319 	int createTexture(int type, int w, int h, int imageFlags, const(byte)* data)
320 	{
321 		auto emptyIter = this.textureList.enumerate.filter!(a => a.value is null);
322 		if(!emptyIter.empty)
323 		{
324 			emptyIter.front.value = new Texture(w, h, type, imageFlags, data);
325 			return cast(int)emptyIter.front.index + 1;
326 		}
327 		else
328 		{
329 			this.textureList ~= new Texture(w, h, type, imageFlags, data);
330 			return cast(int)this.textureList.length;
331 		}
332 	}
333 	void deleteTexture(int image_id)
334 	{
335 		this.textureList[image_id - 1] = null;
336 	}
337 	Texture findTexture(int image_id)
338 	{
339 		return this.textureList[image_id - 1];
340 	}
341 
342 	void cancelRender()
343 	{
344 		this.pathList = null;
345 		this.callList = null;
346 		this.vertexList = null;
347 		this.uniformList = null;
348 	}
349 	void flush()
350 	{
351 		this.processCallList();
352 		this.cancelRender();
353 	}
354 	private void processCallList()
355 	{
356 		if(this.callList.empty) return;
357 		
358 		// render init
359 		GLContext.RenderProgram = this.program.program;
360 		GLContext.ColorMask = [true, true, true, true];
361 		// blend
362 		GLContext.Blend.Enable = true;
363 		GLContext.Blend.Func = GLBlendPresets.PremultipliedBlending;
364 		// cullface
365 		GLContext.CullFace.Enable = true;
366 		GLContext.CullFace.Direction = GLFaceDirection.Back;
367 		GLContext.CullFace.FrontFace = GLPathDirection.CounterClockwise;
368 		// stencil
369 		GLContext.Stencil.Mask = GLuint.max;
370 		GLContext.Stencil.Operations = GLStencilOpPresets.Keep;
371 		GLContext.Stencil.Func = GLStencilFuncParams(GL_ALWAYS, 0, GLuint.max);
372 		// switch
373 		GLContext.DepthTest.Enable = false;
374 		GLContext.ScissorTest.Enable = false;
375 		// activate shader resource
376 		GLContext.ActiveTexture = 0;
377 		GLContext.Texture2D = NullTexture;
378 		// uniform setup
379 		ubyte[] uniformBytes;
380 		foreach(i, ref ub; this.uniformList)
381 		{
382 			auto pBytePtr = cast(ubyte*)&ub;
383 			uniformBytes ~= pBytePtr[0 .. FragUniformBuffer.sizeof];
384 			if(this.ubHardwarePadding > 0) uniformBytes.length += this.ubHardwarePadding;
385 		}
386 		info(false, "uniforms: ", this.uniformList);
387 		info(false, "uniformBufferData: \n", uniformBytes.map!(a => format("%02x", a)).chunks(16).enumerate.map!(a => format("+%04x: ", a[0] * 0x10) ~ a[1].join(" ")).join("\n"));
388 		GLContext.UniformBuffer = this.fragUniformObject;
389 		GLUniformBuffer.ArrayData = uniformBytes;
390 		GLProgram.Uniform[this.ulocTexImage] = 0;
391 		GLProgram.Uniform[this.ulocViewSize] = [this.vport.width, this.vport.height];
392 		// vertex array setup
393 		GLContext.VertexArray = this.varray;
394 		GLContext.ArrayBuffer = this.vbuffer;
395 		GLArrayBuffer.ArrayData = this.vertexList;
396 		GLArrayBuffer.AttribPointer[this.alocVertex] = PlainFloatPointer(2, NVGvertex.sizeof, NVGvertex.x.offsetof);
397 		GLArrayBuffer.AttribPointer[this.alocTexcoord] = PlainFloatPointer(2, NVGvertex.sizeof, NVGvertex.u.offsetof);
398 		
399 		foreach(call; this.callList)
400 		{
401 			info(false, "callType: ", call.type);
402 			final switch(call.type)
403 			{
404 			case CommandType.Fill: this.processFill(call); break;
405 			case CommandType.ConvexFill: this.processConvexFill(call); break;
406 			case CommandType.Stroke: this.processStroke(call); break;
407 			case CommandType.Triangles: this.processTriangles(call);
408 			}
409 		}
410 		
411 		GLArrayBuffer.AttribPointer[this.alocTexcoord] = DisablePointer();
412 		GLArrayBuffer.AttribPointer[this.alocVertex] = DisablePointer();
413 		GLContext.ArrayBuffer = cast(ArrayBufferObject)null;
414 		GLContext.VertexArray = cast(VertexArrayObject)null;
415 		GLContext.CullFace.Enable = false;
416 		GLContext.Texture2D = NullTexture;
417 		GLContext.RenderProgram = DisableProgram;
418 	}
419 	const FillFunc = (Path a) { glDrawArrays(GL_TRIANGLE_FAN, cast(int)a.fillOffset, cast(int)a.fillCount); };
420 	const DrawStrokeFunc = (Path a) { glDrawArrays(GL_TRIANGLE_STRIP, cast(int)a.strokeOffset, cast(int)a.strokeCount); };
421 	private void processFill(InternalDrawCall call)
422 	{
423 		auto processList = this.pathList[call.pathOffset .. call.pathOffset + call.pathCount];
424 		
425 		// Draw shapes
426 		GLContext.Stencil.EnableTest = true;
427 		GLContext.Stencil.Mask = 0xff;
428 		GLContext.Stencil.Func = GLStencilFuncParams(GL_ALWAYS, 0, 0xff);
429 		GLContext.ColorMask = [false, false, false, false];
430 		this.setUniformAndTexture(call.uniformOffset, 0);
431 		GLContext.Stencil.Operations[GLFaceDirection.Front] = GLStencilOpSet(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
432 		GLContext.Stencil.Operations[GLFaceDirection.Back] = GLStencilOpSet(GL_KEEP, GL_KEEP, GL_DECR_WRAP);
433 		GLContext.CullFace.Enable = false;
434 		processList.each!FillFunc;
435 		GLContext.CullFace.Enable = true;
436 		
437 		// Draw anti-aliased pixels
438 		GLContext.ColorMask = [true, true, true, true];
439 		this.setUniformAndTexture(call.uniformOffset + 1, call.image);
440 		GLContext.Stencil.Func = GLStencilFuncParams(GL_EQUAL, 0, 0xff);
441 		GLContext.Stencil.Operations = GLStencilOpPresets.Keep;
442 		processList.each!DrawStrokeFunc;
443 		
444 		// Draw fill
445 		GLContext.Stencil.Func = GLStencilFuncParams(GL_NOTEQUAL, 0, 0xff);
446 		GLContext.Stencil.Operations = GLStencilOpPresets.Zero;
447 		glDrawArrays(GL_TRIANGLES, cast(int)call.triangleOffset, cast(int)call.triangleCount);
448 		
449 		GLContext.Stencil.EnableTest = false;
450 	}
451 	private void processConvexFill(InternalDrawCall call)
452 	{
453 		auto processList = this.pathList[call.pathOffset .. call.pathOffset + call.pathCount];
454 		
455 		this.setUniformAndTexture(call.uniformOffset, call.image);
456 		processList.each!FillFunc;
457 		processList.each!DrawStrokeFunc;
458 	}
459 	private void processStroke(InternalDrawCall call)
460 	{
461 		auto processList = this.pathList[call.pathOffset .. call.pathOffset + call.pathCount];
462 		
463 		// Uses Stencil Stroke
464 		GLContext.Stencil.EnableTest = true;
465 		GLContext.Stencil.Mask = 0xff;
466 		
467 		// Fill the stroke base without overlap
468 		GLContext.Stencil.Func = GLStencilFuncParams(GL_EQUAL, 0, 0xff);
469 		GLContext.Stencil.Operations = GLStencilOpPresets.IncrementOnSucceeded;
470 		this.setUniformAndTexture(call.uniformOffset + 1, call.image);
471 		processList.each!DrawStrokeFunc;
472 		
473 		// Draw anti-aliased pixels
474 		this.setUniformAndTexture(call.uniformOffset, call.image);
475 		GLContext.Stencil.Func = GLStencilFuncParams(GL_EQUAL, 0, 0xff);
476 		GLContext.Stencil.Operations = GLStencilOpPresets.Keep;
477 		processList.each!DrawStrokeFunc;
478 		
479 		// Clear stencil buffer
480 		GLContext.ColorMask = [false, false, false, false];
481 		GLContext.Stencil.Func = GLStencilFuncParams(GL_ALWAYS, 0, 0xff);
482 		GLContext.Stencil.Operations = GLStencilOpPresets.Zero;
483 		processList.each!DrawStrokeFunc;
484 		GLContext.ColorMask = [true, true, true, true];
485 		
486 		GLContext.Stencil.EnableTest = false;
487 	}
488 	private void processTriangles(InternalDrawCall call)
489 	{
490 		this.setUniformAndTexture(call.uniformOffset, call.image);
491 		glDrawArrays(GL_TRIANGLES, cast(int)call.triangleOffset, cast(int)call.triangleCount);
492 	}
493 	private void allocatePathList(CommandType T)(const(NVGpath)[] paths)
494 	{
495 		foreach(path; paths)
496 		{
497 			Path internal;
498 			
499 			static if(T == CommandType.Fill) if(path.nfill > 0)
500 			{
501 				internal.fillOffset = this.vertexList.length;
502 				internal.fillCount = path.nfill;
503 				this.vertexList ~= path.fill[0 .. path.nfill];
504 			}
505 			if(path.nstroke > 0)
506 			{
507 				internal.strokeOffset = this.vertexList.length;
508 				internal.strokeCount = path.nstroke;
509 				this.vertexList ~= path.stroke[0 .. path.nstroke];
510 			}
511 			this.pathList ~= internal;
512 		}
513 	}
514 	void pushCommand(CommandType T, Param)(NVGpaint* paint, NVGscissor* scissor, float fringe, const(NVGpath)[] paths, Param param)
515 	{
516 		InternalDrawCall call;
517 		with(call)
518 		{
519 			type = T;
520 			pathOffset = this.pathList.length;
521 			pathCount = paths.length;
522 			image = paint.image;
523 			uniformOffset = this.uniformList.length;
524 		}
525 		this.allocatePathList!T(paths);
526 		
527 		// Depended by CommandType
528 		static if(T == CommandType.Fill)
529 		{
530 			if(paths.length == 1 && paths.front.convex) call.type = CommandType.ConvexFill;
531 		
532 			// quad
533 			auto quad = [
534 				NVGvertex(param[0], param[3], 0.5f, 1.0f),
535 				NVGvertex(param[2], param[3], 0.5f, 1.0f),
536 				NVGvertex(param[2], param[1], 0.5f, 1.0f),
537 				NVGvertex(param[0], param[3], 0.5f, 1.0f),
538 				NVGvertex(param[2], param[1], 0.5f, 1.0f),
539 				NVGvertex(param[0], param[1], 0.5f, 1.0f)
540 			];
541 			call.triangleOffset = this.vertexList.length;
542 			call.triangleCount = 6;
543 			this.vertexList ~= quad;
544 		
545 			// Set UniformBuffer
546 			if(call.type == CommandType.Fill)
547 			{
548 				FragUniformBuffer ub_stencil;
549 				ub_stencil.strokeThr = -1.0f;
550 				ub_stencil.type = ShaderType.StencilFilling;
551 				this.uniformList ~= ub_stencil;
552 			}
553 			this.uniformList ~= this.createUniformBufferFromPaint(*paint, *scissor, fringe, fringe, -1.0f);
554 		}
555 		else static if(T == CommandType.Stroke)
556 		{
557 			this.uniformList ~= this.createUniformBufferFromPaint(*paint, *scissor, param, fringe, -1.0f);
558 			this.uniformList ~= this.createUniformBufferFromPaint(*paint, *scissor, param, fringe, 1.0f - 0.5f / 255.0f);
559 		}
560 		else static assert(false, "Invalid CommandType for pushCommand");
561 		
562 		this.callList ~= call;
563 	}
564 	void pushTrianglesCommand(NVGpaint* paint, NVGscissor* scissor, const(NVGvertex)[] verts)
565 	{
566 		InternalDrawCall call;
567 		with(call)
568 		{
569 			type = CommandType.Triangles;
570 			image = paint.image;
571 			triangleOffset = this.vertexList.length;
572 			triangleCount = verts.length;
573 			uniformOffset = this.uniformList.length;
574 		}
575 		this.vertexList ~= verts;
576 
577 		this.uniformList ~= this.createUniformBufferFromPaint(*paint, *scissor, 1.0f, 1.0f, -1.0f);
578 		this.uniformList.back.type = ShaderType.TexturedTris;
579 		
580 		this.callList ~= call;
581 	}
582 	
583 	private auto createUniformBufferFromPaint(NVGpaint paint, NVGscissor scissor, float width, float fringe, float strokeThr)
584 	{
585 		FragUniformBuffer ub;
586 		float[6] invxform;
587 		
588 		ub.innerColor = paint.innerColor.premultiplied.asFloat4;
589 		ub.outerColor = paint.outerColor.premultiplied.asFloat4;
590 		
591 		if(scissor.extent[0] < -0.5f || scissor.extent[1] < -0.5f)
592 		{
593 			ub.scissorMatr[] = 0.0;
594 			ub.scissorExt = [1.0f, 1.0f];
595 			ub.scissorScale = [1.0f, 1.0f];
596 		}
597 		else
598 		{
599 			nvgTransformInverse(invxform.ptr, scissor.xform.ptr);
600 			ub.scissorMatr = invxform.asMatrix3x4;
601 			ub.scissorExt = scissor.extent;
602 			ub.scissorScale[0] = sqrt(scissor.xform[0] ^^ 2.0f + scissor.xform[2] ^^ 2.0f);
603 			ub.scissorScale[1] = sqrt(scissor.xform[1] ^^ 2.0f + scissor.xform[3] ^^ 2.0f);
604 		}
605 		
606 		ub.extent = paint.extent;
607 		ub.strokeMult = (width * 0.5f + fringe * 0.5f) / fringe;
608 		ub.strokeThr = strokeThr;
609 		
610 		if(paint.image != 0)
611 		{
612 			auto texture = this.findTexture(paint.image);
613 			if(texture is null) throw new Exception("Texture not found");
614 			if(texture.flags.raised!NVG_IMAGE_FLIPY)
615 			{
616 				float[6] flipped;
617 				nvgTransformScale(flipped.ptr, 1.0f, -1.0f);
618 				nvgTransformMultiply(flipped.ptr, paint.xform.ptr);
619 				nvgTransformInverse(invxform.ptr, flipped.ptr);
620 			}
621 			else nvgTransformInverse(invxform.ptr, paint.xform.ptr);
622 			ub.type = ShaderType.Textured;
623 			
624 			if(texture.type == ImageType.RGBA)
625 			{
626 				if(texture.flags.raised!NVG_IMAGE_PREMULTIPLIED)
627 				{
628 					ub.texType = TexturePostProcess.None;
629 				}
630 				else ub.texType = TexturePostProcess.Multiply;
631 			}
632 			else ub.texType = TexturePostProcess.Colorize;
633 		}
634 		else
635 		{
636 			ub.type = ShaderType.Gradient;
637 			ub.radius = paint.radius;
638 			ub.feather = paint.feather;
639 			nvgTransformInverse(invxform.ptr, paint.xform.ptr);
640 		}
641 		ub.paintMatr = invxform.asMatrix3x4;
642 		return ub;
643 	}
644 	private void setUniformAndTexture(size_t uniformIndex, int image_id)
645 	{
646 		info(false, "Setting UniformOffset: ", uniformIndex * this.ubHardwareSize, "(", uniformIndex, ")");
647 		info(false, "UniformBuffer TextureType: ", this.uniformList[uniformIndex].texType);
648 		info(false, "UniformBuffer RenderType: ", this.uniformList[uniformIndex].type);
649 		GLUniformBuffer.BindRange[this.fragUniformObject.id, FUBUserIndex] = ByteRange(uniformIndex * this.ubHardwareSize, FragUniformBuffer.sizeof);
650 		if(image_id == 0) GLContext.Texture2D = NullTexture;
651 		else
652 		{
653 			const auto texture = this.findTexture(image_id);
654 			GLContext.Texture2D = texture is null ? NullTexture : texture.texture;
655 			info(false, "Texture: ", texture.texture);
656 		}
657 	}
658 
659 	@property viewport(Size sz)
660 	{
661 		this.vport = sz;
662 	}
663 }
664 auto asContext(void* p) { return cast(Context)p; }
665 
666 extern(C)
667 {
668 	// Initialize/Terminate
669 	int initContext(void* uptr)
670 	{
671 		uptr.asContext.init();
672 		return 1;
673 	}
674 	void deleteContext(void* uptr)
675 	{
676 		uptr.asContext.terminate();
677 		GC.removeRoot(uptr);
678 	}
679 	// Textures
680 	int createTexture(void* uptr, int type, int w, int h, int imageFlags, const(byte)* data)
681 	{
682 		return uptr.asContext.createTexture(type, w, h, imageFlags, data);
683 	}
684 	int deleteTexture(void* uptr, int image)
685 	{
686 		uptr.asContext.deleteTexture(image);
687 		return 1;
688 	}
689 	int updateTexture(void* uptr, int image, int x, int y, int w, int h, const(byte)* data)
690 	{
691 		return uptr.asContext.findTexture(image).update(x, y, w, h, data);
692 	}
693 	int getTextureSize(void* uptr, int image, int* w, int* h)
694 	{
695 		return uptr.asContext.findTexture(image).getTextureSize(w, h);
696 	}
697 	// Viewport
698 	void setViewport(void* uptr, int w, int h)
699 	{
700 		uptr.asContext.viewport = Size(w, h);
701 	}
702 	// Render Control
703 	void cancel(void* uptr) { uptr.asContext.cancelRender(); }
704 	void flush(void* uptr) { uptr.asContext.flush(); }
705 	// Internal Commands
706 	void pushFillCommand(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, const(float)* bounds, const(NVGpath)* pPaths, int nPaths)
707 	{
708 		uptr.asContext.pushCommand!(CommandType.Fill)(paint, scissor, fringe, pPaths[0 .. nPaths], bounds[0 .. 4]);
709 	}
710 	void pushStrokeCommand(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const(NVGpath)* pPaths, int nPaths)
711 	{
712 		uptr.asContext.pushCommand!(CommandType.Stroke)(paint, scissor, fringe, pPaths[0 .. nPaths], strokeWidth);
713 	}
714 	void pushTrianglesCommand(void* uptr, NVGpaint* paint, NVGscissor* scissor, const(NVGvertex)* verts, int nVerts)
715 	{
716 		uptr.asContext.pushTrianglesCommand(paint, scissor, verts[0 .. nVerts]);
717 	}
718 }
719 
720 // NanoVG Export
721 NVGcontext* nvgCreateGL3()
722 {
723 	auto pContext = new Context();
724 
725 	GC.addRoot(cast(void*)pContext);
726 	GC.setAttr(cast(void*)pContext, GC.BlkAttr.NO_MOVE);
727 
728 	NVGparams params;
729 	with(params)
730 	{
731 		userPtr = cast(void*)pContext;
732 		edgeAntiAlias = 1;
733 		renderCreate			= &initContext;
734 		renderCreateTexture		= &createTexture;
735 		renderDeleteTexture		= &deleteTexture;
736 		renderUpdateTexture		= &updateTexture;
737 		renderGetTextureSize	= &getTextureSize;
738 		renderViewport			= &setViewport;
739 		renderCancel			= &cancel;
740 		renderFlush				= &flush;
741 		renderFill				= &pushFillCommand;
742 		renderStroke			= &pushStrokeCommand;
743 		renderTriangles			= &pushTrianglesCommand;
744 		renderDelete			= &deleteContext;
745 	}
746 
747 	return nvgCreateInternal(&params);
748 }
749 alias nvgDeleteGL3 = nvgDeleteInternal;