//
// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty.  In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//

// This code was adapted from the original NanoVG OpenGL renderer to work with the FreeSpace Open graphics API

#include "NanoVGRenderer.h"
#include "tracing/tracing.h"


// That is a wrapper function for log prints to be availiable for nanovg components. For now it is stb_truetype.h
// Planted by ksotar with blessing from asarium
extern "C" {
	void nvgOldCPrintf(SCP_FORMAT_STRING const char *message, ...) {
		if (LoggingEnabled) {
			SCP_string buf;
			va_list args;
			va_start(args, message);
			vsprintf(buf, message, args);
			va_end(args);
			outwnd_printf2("%s", message);
		}
	}
}


namespace {
using namespace graphics::paths;

int nvgRenderCreate(void* userptr) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);

	try {
		renderer->initialize();
		return 1;
	} catch (...) {
		return 0;
	}
}

void nvgRenderDelete(void* userptr) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	delete renderer;
}

void nvgViewport(void* userptr, int width, int height) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->setViewport(width, height);
}

void nvgRenderTriangles(void* userptr, NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->renderTriangles(paint, scissor, verts, nverts);
}
void nvgRenderFill(void* userptr,
				   NVGpaint* paint,
				   NVGscissor* scissor,
				   float fringe,
				   const float* bounds,
				   const NVGpath* paths,
				   int npaths) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->renderFill(paint, scissor, fringe, bounds, paths, npaths);
}
void nvgRenderStroke(void* userptr,
					 NVGpaint* paint,
					 NVGscissor* scissor,
					 float fringe,
					 float strokeWidth,
					 const NVGpath* paths,
					 int npaths) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->renderStroke(paint, scissor, fringe, strokeWidth, paths, npaths);
}
void nvgRenderFlush(void* userptr) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->renderFlush();

}
void nvgRenderCancel(void* userptr) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	renderer->renderCancel();
}
int nvgCreateTexture(void* userptr, int type, int w, int h, int imageFlags, const unsigned char* data) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	return renderer->createTexture(type, w, h, imageFlags, data);
}
int nvgUpdateTexture(void* userptr, int image, int x, int y, int w, int h, const unsigned char* data) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	return renderer->updateTexture(image, x, y, w, h, data);
}
int nvgDeleteTexture(void* userptr, int image) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	return renderer->deleteTexture(image);
}
int nvgGetTextureSize(void* userptr, int image, int* w, int* h) {
	auto renderer = static_cast<NanoVGRenderer*>(userptr);
	return renderer->getTextureSize(image, w, h);
}

NVGcolor premulColor(NVGcolor c) {
	c.r *= c.a;
	c.g *= c.a;
	c.b *= c.a;
	return c;
}
vec4 colorToVec4(NVGcolor c) {
	vec4 out;
	out.xyzw.x = c.r;
	out.xyzw.y = c.g;
	out.xyzw.z = c.b;
	out.xyzw.w = c.a;
	return out;
}
void xformToMat3x4(float m3[12], float* t) {
	m3[0] = t[0];
	m3[1] = t[1];
	m3[2] = 0.0f;
	m3[3] = 0.0f;
	m3[4] = t[2];
	m3[5] = t[3];
	m3[6] = 0.0f;
	m3[7] = 0.0f;
	m3[8] = t[4];
	m3[9] = t[5];
	m3[10] = 1.0f;
	m3[11] = 0.0f;
}

size_t maxVertCount(const NVGpath* paths, size_t npaths) {
	size_t count = 0;
	for (size_t i = 0; i < npaths; i++) {
		count += paths[i].nfill;
		count += paths[i].nstroke;
	}
	return count;
}

void vertexSet(NVGvertex* vtx, float x, float y, float u, float v) {
	vtx->x = x;
	vtx->y = y;
	vtx->u = u;
	vtx->v = v;
}

nanovg_material getDefaultMaterial() {
	nanovg_material mat;

	material_set_nanovg(&mat, -1);

	return mat;
}
}

namespace graphics {
namespace paths {

NVGcontext* createNanoVGContext() {
	NVGparams params;
	NVGcontext* ctx = nullptr;
	auto nvgRenderer = new NanoVGRenderer();

	memset(&params, 0, sizeof(params));
	params.renderCreate = nvgRenderCreate;
	params.renderDelete = nvgRenderDelete;
	params.renderViewport = nvgViewport;

	params.renderTriangles = nvgRenderTriangles;
	params.renderFill = nvgRenderFill;
	params.renderStroke = nvgRenderStroke;
	params.renderFlush = nvgRenderFlush;
	params.renderCancel = nvgRenderCancel;

	params.renderCreateTexture = nvgCreateTexture;
	params.renderUpdateTexture = nvgUpdateTexture;
	params.renderDeleteTexture = nvgDeleteTexture;
	params.renderGetTextureSize = nvgGetTextureSize;

	params.userPtr = nvgRenderer;
	params.edgeAntiAlias = 1;

	ctx = nvgCreateInternal(&params);
	if (ctx == nullptr) {
		return nullptr;
	}

	return ctx;
}

void deleteNanoVGContext(NVGcontext* context) {
	Assertion(context != nullptr, "Invalid context passed!");

	nvgDeleteInternal(context);
}

NanoVGRenderer::NanoVGRenderer() {
}

void NanoVGRenderer::initialize() {
	_vertexBuffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Streaming);

	_vertexLayout.add_vertex_component(vertex_format_data::POSITION2, sizeof(NVGvertex), (int) offsetof(NVGvertex, x));
	_vertexLayout.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(NVGvertex), (int) offsetof(NVGvertex, u));

	// Now create all the materials that we are going to need
	{
		auto material = getDefaultMaterial();

		// Material for simple triangle draws. These use different primitive types so if that is moved into the
		// material struct at some point this can be easily adjusted
		_trianglesMaterial = material;
		_triangleFillMaterial = material;
		_triangleStrokeMaterial = material;
	}
	{
		// Materials for the fill shader
		auto material = getDefaultMaterial();
		material.set_stencil_test(true);
		material.set_stencil_mask(0xFF);
		material.set_stencil_func(ComparisionFunction::Always, 0, 0xFF);
		material.set_color_mask(false, false, false, false);

		material.set_front_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::IncrementWrap);
		material.set_back_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::DecrementWrap);

		_fillShapeMaterial = material;

		material.set_color(true, true, true, true);
		material.set_stencil_func(ComparisionFunction::Equal, 0x00, 0xFF);

		material.set_front_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);
		material.set_back_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);

		_fillAntiAliasMaterial = material;

		material.set_stencil_func(ComparisionFunction::NotEqual, 0x00, 0xFF);

		material.set_front_stencil_op(StencilOperation::Zero, StencilOperation::Zero, StencilOperation::Zero);
		material.set_back_stencil_op(StencilOperation::Zero, StencilOperation::Zero, StencilOperation::Zero);

		_fillFillMaterial = material;
	}
	{
		// Pipeline states for the stroke shader
		auto material = getDefaultMaterial();
		material.set_stencil_test(true);
		material.set_stencil_mask(0xFF);
		material.set_stencil_func(ComparisionFunction::Equal, 0x00, 0xFF);
		material.set_front_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Increment);
		material.set_back_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Increment);
		_strokeFillMaterial = material;

		material.set_front_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);
		material.set_back_stencil_op(StencilOperation::Keep, StencilOperation::Keep, StencilOperation::Keep);
		_strokeAntiaiasMaterial = material;

		material.set_color_mask(false, false, false, false);
		material.set_stencil_func(ComparisionFunction::Always, 0x00, 0xFF);
		material.set_front_stencil_op(StencilOperation::Zero, StencilOperation::Zero, StencilOperation::Zero);
		material.set_back_stencil_op(StencilOperation::Zero, StencilOperation::Zero, StencilOperation::Zero);
		_strokeClearStencilMaterial = material;
	}
}

void NanoVGRenderer::setViewport(int width, int height) {
	_viewport.x = width;
	_viewport.y = height;
}

void NanoVGRenderer::renderFill(NVGpaint* paint,
								NVGscissor* scissor,
								float fringe,
								const float* bounds,
								const NVGpath* paths,
								int npaths) {
	if (npaths <= 0) {
		// Ignore irrelevant render calls
		mprintf(("NanoVG asked us to render filled triangles but no paths were supplied!\n"));
		return;
	}

	auto call = addDrawCall();

	call->type = CallType::Fill;
	call->pathOffset = static_cast<uint32_t>(addPaths((size_t) npaths));
	call->pathCount = static_cast<uint32_t>((size_t) npaths);
	call->image = paint->image;

	if (npaths == 1 && paths[0].convex) {
		call->type = CallType::ConvexFill;
	}

	// Allocate vertices for all the paths.
	auto maxverts = maxVertCount(paths, (size_t) npaths) + 6;
	auto offset = addVertices(maxverts);

	for (int i = 0; i < npaths; i++) {
		Path* copy = &_paths[call->pathOffset + i];
		const NVGpath* path = &paths[i];
		memset(copy, 0, sizeof(Path));
		if (path->nfill > 0) {
			copy->fillOffset = static_cast<uint32_t>(offset);
			copy->fillCount = static_cast<uint32_t>((size_t) path->nfill);
			memcpy(&_vertices[offset], path->fill, sizeof(NVGvertex) * path->nfill);
			offset += path->nfill;
		}
		if (path->nstroke > 0) {
			copy->strokeOffset = static_cast<uint32_t>(offset);
			copy->strokeCount = static_cast<uint32_t>((size_t) path->nstroke);
			memcpy(&_vertices[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
			offset += path->nstroke;
		}
	}

	// Quad
	call->triangleOffset = static_cast<uint32_t>(offset);
	call->triangleCount = 6;
	auto quad = &_vertices[call->triangleOffset];
	vertexSet(&quad[0], bounds[0], bounds[3], 0.5f, 1.0f);
	vertexSet(&quad[1], bounds[2], bounds[3], 0.5f, 1.0f);
	vertexSet(&quad[2], bounds[2], bounds[1], 0.5f, 1.0f);

	vertexSet(&quad[3], bounds[0], bounds[3], 0.5f, 1.0f);
	vertexSet(&quad[4], bounds[2], bounds[1], 0.5f, 1.0f);
	vertexSet(&quad[5], bounds[0], bounds[1], 0.5f, 1.0f);

	// Setup uniforms for draw calls
	if (call->type == CallType::Fill) {
		call->uniformIndex = addUniformData(2);
		// Simple shader for stencil
		auto frag = &_uniformData[call->uniformIndex];
		frag->strokeThr = -1.0f;
		frag->type = NanoVGShaderType::Simple;
		// Fill shader
		convertPaint(&_uniformData[call->uniformIndex + 1], paint, scissor, fringe, fringe, -1.0f);
	} else {
		call->uniformIndex = addUniformData(1);
		// Fill shader
		convertPaint(&_uniformData[call->uniformIndex], paint, scissor, fringe, fringe, -1.0f);
	}
}
void NanoVGRenderer::renderTriangles(NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts) {
	if (nverts <= 0) {
		// Ignore irrelevant render calls
		mprintf(("NanoVG asked us to render triangles but no vertices were supplied!\n"));
		return;
	}

	_vertices.insert(_vertices.end(), verts, verts + nverts);

	auto call = addDrawCall();
	call->type = CallType::Triangles;
	call->triangleCount = static_cast<uint32_t>(nverts);
	call->triangleOffset = static_cast<uint32_t>(addVertices(verts, static_cast<size_t>(nverts)));
	call->image = paint->image;

	call->uniformIndex = addUniformData(1);

	auto uniformData = &_uniformData[call->uniformIndex];
	auto succcess = convertPaint(uniformData, paint, scissor, 1.0f, 1.0f, -1.0f);

	Assertion(succcess, "Failed to convert paint, probably caused by an invalid texture handle.");

	uniformData->type = NanoVGShaderType::Image;
}
void NanoVGRenderer::renderStroke(NVGpaint* paint,
								  NVGscissor* scissor,
								  float fringe,
								  float strokeWidth,
								  const NVGpath* paths,
								  int npaths) {
	if (npaths <= 0) {
		// Ignore irrelevant render calls
		mprintf(("NanoVG asked us to render stroke triangles but no paths were supplied!\n"));
		return;
	}

	auto call = addDrawCall();

	call->type = CallType::Stroke;
	call->pathOffset = static_cast<uint32_t>(addPaths((size_t) npaths));
	call->pathCount = static_cast<uint32_t>(npaths);
	call->image = paint->image;

	// Allocate vertices for all the paths.
	auto maxverts = maxVertCount(paths, (size_t) npaths);
	auto offset = addVertices(maxverts);

	for (int i = 0; i < npaths; i++) {
		Path* copy = &_paths[call->pathOffset + i];
		const NVGpath* path = &paths[i];
		memset(copy, 0, sizeof(*copy));
		if (path->nstroke) {
			copy->strokeOffset = static_cast<uint32_t>(offset);
			copy->strokeCount = static_cast<uint32_t>(path->nstroke);
			memcpy(&_vertices[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
			offset += path->nstroke;
		}
	}
	// Fill shader
	call->uniformIndex = addUniformData(2);

	convertPaint(&_uniformData[call->uniformIndex], paint, scissor, strokeWidth, fringe, -1.0f);
	convertPaint(&_uniformData[call->uniformIndex + 1], paint, scissor, strokeWidth, fringe, 1.0f - 0.5f / 255.0f);
}
void NanoVGRenderer::renderFlush() {
	if (_drawCalls.empty()) {
		return;
	}

	TRACE_SCOPE(tracing::NanoVGFlushFrame);
	GR_DEBUG_SCOPE("NanoVG flush");

	gr_set_viewport(0, 0, gr_screen.max_w, gr_screen.max_h);

	_uniformBuffer = gr_get_uniform_buffer(uniform_block_type::NanoVGData, _uniformData.size());
	// This copies the uniform data from our vector into the uniform buffer aligner
	for (auto& uniform : _uniformData) {
		memcpy(_uniformBuffer.aligner().addTypedElement<nanovg_draw_data>(), &uniform, sizeof(uniform));
	}

	_uniformBuffer.submitData();
	gr_update_buffer_data(_vertexBuffer, sizeof(NVGvertex) * _vertices.size(), _vertices.data());

	for (auto& drawCall : _drawCalls) {
		switch (drawCall.type) {
		case CallType::Fill:
			drawFill(drawCall);
			break;
		case CallType::ConvexFill:
			drawConvexFill(drawCall);
			break;
		case CallType::Stroke:
			drawStroke(drawCall);
			break;
		case CallType::Triangles:
			drawTriangles(drawCall);
			break;
		}
	}

	// Reset all data again
	renderCancel();
}
void NanoVGRenderer::renderCancel() {
	// Clear all data written by the render functions
	_vertices.clear();
	_uniformData.clear();
	_drawCalls.clear();
	_paths.clear();
}
int NanoVGRenderer::createTexture(int type, int w, int h, int imageFlags, const unsigned char* data) {
	int bpp;
	if (type == NVG_TEXTURE_RGBA) {
		bpp = 32;
	} else {
		bpp = 8;
	}

	Assertion(!(imageFlags & NVG_IMAGE_REPEATX), "Repeat X is not supported yet!");
	Assertion(!(imageFlags & NVG_IMAGE_REPEATY), "Repeat Y is not supported yet!");
	Assertion(!(imageFlags & NVG_IMAGE_GENERATE_MIPMAPS), "Generate Mipmaps is not supported yet!");

	std::unique_ptr<uint8_t[]> data_buffer(new uint8_t[w * h * bpp]);
	if (data != nullptr) {
		memcpy(data_buffer.get(), data, static_cast<size_t>(w * h * bpp));
	}

	Image img;
	img.data = std::move(data_buffer);
	img.bitmap = bm_create(bpp, w, h, img.data.get(), bpp == 8 ? BMP_AABITMAP : 0);
	img.type = type;
	img.flags = imageFlags;
	img.width = w;
	img.height = h;

	auto id = ++_lastImageId;
	_textureMap.insert(std::make_pair(id, std::move(img)));

	return id;
}
int NanoVGRenderer::updateTexture(int image, int  /*x*/, int  /*y*/, int  /*w*/, int  /*h*/, const unsigned char* data) {
	auto texture = getTexture(image);
	if (texture == nullptr) {
		return 0;
	}

	int bpp;
	if (texture->type == NVG_TEXTURE_RGBA) {
		bpp = 32;
	} else {
		bpp = 8;
	}

	// Copy the updated data to our internal buffer so that changes in the hardware texture detail don't discard the
	// changes NanoVG made to the texture
	memcpy(texture->data.get(), data, texture->width * texture->height * bpp / 8);

	// TODO: This could probably be done better by only uploading the changed area
	gr_update_texture(texture->bitmap, bpp, data, texture->width, texture->height);

	return 1;
}
int NanoVGRenderer::deleteTexture(int image) {
	auto iter = _textureMap.find(image);
	if (iter == _textureMap.end()) {
		return 0;
	}

	bm_release(iter->second.bitmap);
	// This will call the destructor of the class and free the resources
	_textureMap.erase(iter);

	return 1;
}
int NanoVGRenderer::getTextureSize(int image, int* w, int* h) {
	auto img = getTexture(image);
	if (img == nullptr) {
		return 0;
	}

	*w = img->width;
	*h = img->height;

	return 1;
}
NanoVGRenderer::DrawCall* NanoVGRenderer::addDrawCall() {
	_drawCalls.emplace_back();
	return &_drawCalls.back();
}

size_t NanoVGRenderer::addUniformData(size_t num) {
	nanovg_draw_data data;
	memset(&data, 0, sizeof(data));
	// Push an unitialized struct into the vector but set the viewport here since that is the same for all elements
	data.viewSize = { i2fl(_viewport.x), i2fl(_viewport.y) };

	auto current = _uniformData.size();
	_uniformData.resize(current + num, data);
	return current;
}

size_t NanoVGRenderer::addVertices(const NVGvertex* vert, size_t num) {
	auto offset = _vertices.size();
	_vertices.insert(_vertices.end(), vert, vert + num);
	return offset;
}

size_t NanoVGRenderer::addVertices(size_t num) {
	auto offset = _vertices.size();
	_vertices.resize(offset + num);
	return offset;
}

size_t NanoVGRenderer::addPaths(size_t num) {
	auto offset = _paths.size();
	_paths.resize(offset + num);
	return offset;
}

bool NanoVGRenderer::convertPaint(graphics::nanovg_draw_data* frag,
								  NVGpaint* paint,
								  NVGscissor* scissor,
								  float width,
								  float fringe,
								  float strokeThr) {
	float invxform[6];

	frag->innerCol = colorToVec4(premulColor(paint->innerColor));
	frag->outerCol = colorToVec4(premulColor(paint->outerColor));

	if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) {
		memset(&frag->scissorMat, 0, sizeof(frag->scissorMat));
		frag->scissorExt.x = 1.0f;
		frag->scissorExt.y = 1.0f;
		frag->scissorScale.x = 1.0f;
		frag->scissorScale.y = 1.0f;
	} else {
		nvgTransformInverse(invxform, scissor->xform);
		xformToMat3x4(frag->scissorMat, invxform);

		frag->scissorExt.x = scissor->extent[0];
		frag->scissorExt.y = scissor->extent[1];
		frag->scissorScale.x =
			sqrtf(scissor->xform[0] * scissor->xform[0] + scissor->xform[2] * scissor->xform[2]) / fringe;
		frag->scissorScale.y =
			sqrtf(scissor->xform[1] * scissor->xform[1] + scissor->xform[3] * scissor->xform[3]) / fringe;
	}

	memcpy(&frag->extent, paint->extent, sizeof(frag->extent));
	frag->strokeMult = (width * 0.5f + fringe * 0.5f) / fringe;
	frag->strokeThr = strokeThr;

	if (paint->image != 0) {
		auto tex = getTexture(paint->image);
		if (tex == nullptr) {
			return false;
		}
		if ((tex->flags & NVG_IMAGE_FLIPY) != 0) {
			float flipped[6];
			nvgTransformScale(flipped, 1.0f, -1.0f);
			nvgTransformMultiply(flipped, paint->xform);
			nvgTransformInverse(invxform, flipped);
		} else {
			nvgTransformInverse(invxform, paint->xform);
		}
		frag->type = NanoVGShaderType::FillImage;

		if (tex->type == NVG_TEXTURE_RGBA) {
			frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
		} else {
			frag->texType = 2;
		}
		//		printf("frag->texType = %d\n", frag->texType);
	} else {
		frag->type = NanoVGShaderType::FillGradient;
		frag->radius = paint->radius;
		frag->feather = paint->feather;
		nvgTransformInverse(invxform, paint->xform);
	}

	xformToMat3x4(frag->paintMat, invxform);

	auto tex = getTexture(paint->image);
	if (tex != nullptr) {
		// Since textures may be stored in texture arrays we need to pass the index of the image into the shader
		frag->texArrayIndex = bm_get_array_index(tex->bitmap);
	}

	return true;
}

NanoVGRenderer::Image* NanoVGRenderer::getTexture(int id) {
	auto iter = _textureMap.find(id);
	if (iter == _textureMap.end()) {
		return nullptr;
	}
	return &iter->second;
}

void NanoVGRenderer::drawTriangles(const DrawCall& call) {
	GR_DEBUG_SCOPE("Draw triangles");
	TRACE_SCOPE(tracing::NanoVGDrawTriangles);

	auto mat = _trianglesMaterial;
	materialSetTexture(mat, call.image);

	gr_bind_uniform_buffer(uniform_block_type::NanoVGData, _uniformBuffer.getAlignerElementOffset(call.uniformIndex),
	                       sizeof(nanovg_draw_data), _uniformBuffer.bufferHandle());

	gr_render_nanovg(&mat, PRIM_TYPE_TRIS, &_vertexLayout, call.triangleOffset, call.triangleCount, _vertexBuffer);
}
void NanoVGRenderer::drawFill(const DrawCall& call) {
	GR_DEBUG_SCOPE("Draw fill");
	TRACE_SCOPE(tracing::NanoVGDrawFill);

	auto mat = _fillShapeMaterial;
	mat.set_texture_map(TM_BASE_TYPE, -1);

	gr_bind_uniform_buffer(uniform_block_type::NanoVGData, _uniformBuffer.getAlignerElementOffset(call.uniformIndex),
	                       sizeof(nanovg_draw_data), _uniformBuffer.bufferHandle());

	auto pathOffset = call.pathOffset;
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].fillOffset,
						 _paths[pathOffset + i].fillCount,
						 _vertexBuffer);
	}

	mat = _fillAntiAliasMaterial;
	materialSetTexture(mat, call.image);
	gr_bind_uniform_buffer(uniform_block_type::NanoVGData,
	                       _uniformBuffer.getAlignerElementOffset(call.uniformIndex + 1), sizeof(nanovg_draw_data),
	                       _uniformBuffer.bufferHandle());

	// Draw fringes
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].strokeOffset,
						 _paths[pathOffset + i].strokeCount,
						 _vertexBuffer);
	}

	mat = _fillFillMaterial;
	materialSetTexture(mat, call.image);

	gr_render_nanovg(&mat, PRIM_TYPE_TRIS, &_vertexLayout, call.triangleOffset, call.triangleCount, _vertexBuffer);
}
void NanoVGRenderer::drawConvexFill(const DrawCall& call) {
	GR_DEBUG_SCOPE("Draw convex fill");
	TRACE_SCOPE(tracing::NanoVGDrawConvexFill);

	auto mat = _triangleFillMaterial;
	materialSetTexture(mat, call.image);
	gr_bind_uniform_buffer(uniform_block_type::NanoVGData, _uniformBuffer.getAlignerElementOffset(call.uniformIndex),
	                       sizeof(nanovg_draw_data), _uniformBuffer.bufferHandle());

	auto pathOffset = call.pathOffset;
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRIFAN,
						 &_vertexLayout,
						 _paths[pathOffset + i].fillOffset,
						 _paths[pathOffset + i].fillCount,
						 _vertexBuffer);
	}

	mat = _triangleStrokeMaterial;
	materialSetTexture(mat, call.image);
	// Draw fringes
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].strokeOffset,
						 _paths[pathOffset + i].strokeCount,
						 _vertexBuffer);
	}
}
void NanoVGRenderer::drawStroke(const DrawCall& call) {
	GR_DEBUG_SCOPE("Draw stroke");
	TRACE_SCOPE(tracing::NanoVGDrawStroke);

	auto mat = _strokeFillMaterial;
	materialSetTexture(mat, call.image);
	gr_bind_uniform_buffer(uniform_block_type::NanoVGData,
	                       _uniformBuffer.getAlignerElementOffset(call.uniformIndex + 1), sizeof(nanovg_draw_data),
	                       _uniformBuffer.bufferHandle());

	auto pathOffset = call.pathOffset;

	// Fill the stroke base without overlap
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].strokeOffset,
						 _paths[pathOffset + i].strokeCount,
						 _vertexBuffer);
	}

	// Draw anti-aliased pixels.
	gr_bind_uniform_buffer(uniform_block_type::NanoVGData, _uniformBuffer.getAlignerElementOffset(call.uniformIndex),
	                       sizeof(nanovg_draw_data), _uniformBuffer.bufferHandle());
	mat = _strokeAntiaiasMaterial;
	materialSetTexture(mat, call.image);
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].strokeOffset,
						 _paths[pathOffset + i].strokeCount,
						 _vertexBuffer);
	}

	// Clear stencil buffer.
	mat = _strokeClearStencilMaterial;
	materialSetTexture(mat, call.image);
	for (size_t i = 0; i < call.pathCount; ++i) {
		gr_render_nanovg(&mat,
						 PRIM_TYPE_TRISTRIP,
						 &_vertexLayout,
						 _paths[pathOffset + i].strokeOffset,
						 _paths[pathOffset + i].strokeCount,
						 _vertexBuffer);
	}
}
void NanoVGRenderer::materialSetTexture(nanovg_material& mat, int nvgHandle) {
	auto tex = getTexture(nvgHandle);
	if (tex != nullptr) {
		mat.set_texture_map(TM_BASE_TYPE, tex->bitmap);
		if (tex->type == NVG_TEXTURE_ALPHA) {
			mat.set_texture_type(material::TEX_TYPE_AABITMAP);
		}
	} else {
		mat.set_texture_map(TM_BASE_TYPE, -1);
	}
}

}
}
