mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-12 12:11:36 -10:00
811 lines
24 KiB
C++
811 lines
24 KiB
C++
//
|
|
// Copyright (c) 2009-2013 Mikko Mononen [email protected]
|
|
//
|
|
// 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"
|
|
|
|
|
|
#ifndef NDEBUG
|
|
// 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, ...) {
|
|
SCP_string buf;
|
|
va_list args;
|
|
va_start(args, message);
|
|
vsprintf(buf, message, args);
|
|
va_end(args);
|
|
outwnd_printf2("%s", message);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
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(¶ms, 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(¶ms);
|
|
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);
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
_uniformBuffer->finished();
|
|
|
|
// 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;
|
|
|
|
bm_get_info(img.bitmap);
|
|
|
|
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;
|
|
}
|
|
bm_get_info(texture->bitmap);
|
|
// 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->aligner().getOffset(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->aligner().getOffset(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->aligner().getOffset(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->aligner().getOffset(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->aligner().getOffset(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->aligner().getOffset(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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|