Skip to content

Instantly share code, notes, and snippets.

@unvestigate
Created March 14, 2025 19:56
Show Gist options
  • Select an option

  • Save unvestigate/65e819b2cca2c732a28bd912e11091a2 to your computer and use it in GitHub Desktop.

Select an option

Save unvestigate/65e819b2cca2c732a28bd912e11091a2 to your computer and use it in GitHub Desktop.
NanoVG renderer for the internal Basis RHI
#include "external/nanovg/nanovg_gfxapi.h"
#include "external/nanovg/nanovg.h"
#include <gfxapi/BasisRenderContext.h>
#include <gfxapi/BasisRenderDevice.h>
#include <gfxapi/BasisRenderTarget.h>
#include <gfxapi/BasisViewport.h>
#include <gfxapi/BasisBuffer.h>
#include <gfxapi/BasisBufferDescription.h>
#include <gfxapi/BasisTextureDescription.h>
#include <gfxapi/BasisTexture.h>
#include <gfxapi/BasisTextureSamplerDescription.h>
#include <gfxapi/BasisTextureSampler.h>
#include <gfxapi/BasisShaderFlags.h>
#include <gfxapi/BasisShaderProgram.h>
#include <gfxapi/BasisPipelineState.h>
#include <gfxapi/BasisDynamicBindGroup.h>
#include <resources/BasisResourceManager.h>
#include <resources/BasisResourceListener.h>
#include <resourcetypes/BasisShaderProgramResource.h>
#include <renderer/BasisRenderer.h>
#define NANOVG_SHADER_AA_PATH "game/shaders/nanovg/nanovg_aa.binshader"
#define NANOVG_SHADER_NO_AA_PATH "game/shaders/nanovg/nanovg_no_aa.binshader"
using namespace Basis;
namespace
{
Mem::AllocatorHandle alloc(Mem::AllocatorRenderer);
//#pragma pack(push)
//#pragma pack(16)
struct GfxApiNVGfragUniforms
{
float scissorMat[16];
float scissorExt[4];
float scissorScale[4];
float paintMat[16];
float extent[4];
float radius[4];
float feather[4];
float innerCol[4]; //NVGcolor innerCol;
float outerCol[4]; //NVGcolor outerCol;
float strokeMult[4];
int texType;
int type;
int padding1;
int padding2;
};
//#pragma pack(pop)
struct VS_CONSTANTS
{
float dummy[16];
float viewSize[2];
};
enum GfxApiNVGshaderType
{
NSVG_SHADER_FILLGRAD = 0,
NSVG_SHADER_FILLIMG,
NSVG_SHADER_SIMPLE,
NSVG_SHADER_IMG
};
struct GfxApiNVGshader
{
GfxApi::ShaderProgram* shader;
VS_CONSTANTS vc;
};
struct GfxApiNVGtexture
{
int id;
GfxApi::Texture* tex;
int width, height;
int type;
int flags;
};
enum GfxApiNVGcallType
{
GFXAPINVG_NONE = 0,
GFXAPINVG_FILL,
GFXAPINVG_CONVEXFILL,
GFXAPINVG_STROKE,
GFXAPINVG_TRIANGLES
};
struct GfxApiNVGcall
{
int type;
int image;
int pathOffset;
int pathCount;
int triangleOffset;
int triangleCount;
int uniformOffset;
};
struct GfxApiNVGpath
{
int fillOffset;
int fillCount;
int strokeOffset;
int strokeCount;
};
struct GfxApiNVGbuffer
{
unsigned int maxBufferEntries;
unsigned int currentBufferEntry;
GfxApi::Buffer* buffer;
};
enum GfxApiNVGDepthStencilType
{
GfxApiNVGDepthStencilTypeDrawShapes = 0,
GfxApiNVGDepthStencilTypeDrawAA,
GfxApiNVGDepthStencilTypeFill,
GfxApiNVGDepthStencilTypeDefault,
GfxApiNVGDepthStencilTypeCount
};
enum GfxApiNVGBlendType
{
GfxApiNVGBlendTypeBlend = 0,
GfxApiNVGBlendTypeNoWrite,
GfxApiNVGBlendTypeCount
};
enum GfxApiNVGRasterizerType
{
GfxApiNVGRasterizerTypeNoCull = 0,
GfxApiNVGRasterizerTypeCull,
GfxApiNVGRasterizerTypeCount
};
enum GfxApiNVGPrimitiveTopology
{
GfxApiNVGPrimitiveTopologyTriangleList = 0,
GfxApiNVGPrimitiveTopologyTriangleStrip,
GfxApiNVGPrimitiveTopologyCount
};
struct GfxApiNVGpipelineState
{
GfxApi::PipelineState* pso;
uint32_t key;
};
const uint32_t PSO_COUNT = GfxApiNVGDepthStencilTypeCount * GfxApiNVGBlendTypeCount * GfxApiNVGRasterizerTypeCount * GfxApiNVGPrimitiveTopologyCount;
struct GfxApiNVGcontext;
class ShaderResourceListener : public Basis::ResourceListener
{
public:
ShaderResourceListener(GfxApiNVGcontext* c)
: ctxt(c)
{
}
void resourceWasReloaded(Resource* resource);
GfxApiNVGcontext* ctxt;
};
struct GfxApiNVGcontext
{
Basis::GfxApi::RenderDevice* device;
Basis::GfxApi::RenderContext* renderContext;
ShaderResourceListener* shaderListener;
ShaderProgramResource* shaderProgramResource = nullptr;
GfxApi::UniformMetadataList* uniformMetadataList;
GfxApiNVGshader shader;
//bool needsExtraBufferBind;
GfxApiNVGtexture* textures;
GfxApi::ShaderBindingSlot textureBindingSlot;
float view[2];
int ntextures;
int ctextures;
int textureId;
GfxApi::TextureSampler* samplers[4];
GfxApi::ShaderBindingSlot samplerBindingSlot;
int dummyTex;
int fragSize;
int flags;
// Per frame buffers
GfxApiNVGcall* calls;
int ccalls;
int ncalls;
GfxApiNVGpath* paths;
int cpaths;
int npaths;
NVGvertex* verts;
int cverts;
int nverts;
unsigned char* uniforms;
int cuniforms;
int nuniforms;
GfxApiNVGbuffer vertexBuffer;
GfxApi::Buffer* fanIndexBuffer;
GfxApi::Buffer* VSconstants;
GfxApi::ShaderBindingSlot VSconstantBindingSlot;
GfxApi::Buffer* PSconstants;
GfxApi::ShaderBindingSlot PSconstantBindingSlot;
GfxApi::DynamicBindGroup* bindGroup;
GfxApi::TextureSampler* currentSampler;
GfxApiNVGDepthStencilType currentDepthStencilType;
GfxApiNVGBlendType currentBlendType;
GfxApiNVGRasterizerType currentRasterizerType;
GfxApiNVGPrimitiveTopology currentPrimTopoType;
GfxApiNVGpipelineState pipelineStates[PSO_COUNT];
};
void initUniformMetadata(GfxApiNVGcontext* ctxt)
{
if (!ctxt->uniformMetadataList)
{
ctxt->uniformMetadataList = Mem::New<GfxApi::UniformMetadataList>();
}
(*ctxt->uniformMetadataList) = ctxt->shader.shader->getUniformMetadata();
for (GfxApi::UniformMetadata& u : *ctxt->uniformMetadataList)
{
if (u.type == GfxApi::UniformTypeConstantBuffer)
{
u.hasDynamicOffset = true;
}
}
}
void ShaderResourceListener::resourceWasReloaded(Resource* resource)
{
// Release the old shader and get a reference to the updated one.
// Then, release all the PSOs so that they get recreated on demand.
BASIS_RELEASE_AND_NULL(ctxt->shader.shader);
ctxt->shader.shader = ctxt->shaderProgramResource->getShaderProgram();
initUniformMetadata(ctxt);
for (uint32_t i = 0; i < PSO_COUNT; ++i)
{
BASIS_RELEASE_AND_NULL(ctxt->pipelineStates[i].pso);
ctxt->pipelineStates[i].key = 0xFFFFFFFF;
}
}
int GfxApiNVG__maxi(int a, int b)
{
return a > b ? a : b;
}
GfxApiNVGtexture* GfxApiNVG__allocTexture(GfxApiNVGcontext* ctxt)
{
GfxApiNVGtexture* tex = nullptr;
for (int i = 0; i < ctxt->ntextures; i++)
{
if (ctxt->textures[i].id == 0)
{
tex = &ctxt->textures[i];
break;
}
}
if (tex == nullptr)
{
if (ctxt->ntextures + 1 > ctxt->ctextures)
{
GfxApiNVGtexture* textures;
int ctextures = GfxApiNVG__maxi(ctxt->ntextures + 1, 4) + ctxt->ctextures / 2; // 1.5x Overallocate
textures = (GfxApiNVGtexture*)realloc(ctxt->textures, sizeof(GfxApiNVGtexture) * ctextures);
if (textures == NULL) return NULL;
ctxt->textures = textures;
ctxt->ctextures = ctextures;
}
tex = &ctxt->textures[ctxt->ntextures++];
}
memset(tex, 0, sizeof(*tex));
tex->id = ++ctxt->textureId;
return tex;
}
GfxApiNVGtexture* GfxApiNVG__findTexture(GfxApiNVGcontext* ctxt, int id)
{
int i;
for (i = 0; i < ctxt->ntextures; i++)
if (ctxt->textures[i].id == id)
return &ctxt->textures[i];
return NULL;
}
int GfxApiNVG__deleteTexture(GfxApiNVGcontext* ctxt, int id)
{
int i;
for (i = 0; i < ctxt->ntextures; i++)
{
if (ctxt->textures[i].id == id)
{
if (ctxt->textures[i].tex != 0) // && (ctxt->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
{
BASIS_RELEASE_AND_NULL(ctxt->textures[i].tex);
}
memset(&ctxt->textures[i], 0, sizeof(ctxt->textures[i]));
return 1;
}
}
return 0;
}
void GfxApiNVG__copyMatrix3to4(float* pDest, const float* pSource)
{
unsigned int i;
for (i = 0; i < 4; i++)
{
memcpy(&pDest[i * 4], &pSource[i * 3], sizeof(float) * 3);
}
}
/*void GfxApiNVG__buildFanIndices(GfxApiNVGcontext* ctxt)
{
uint32_t index0 = 0;
uint32_t index1 = 1;
uint32_t index2 = 2;
uint32_t current = 0;
uint32_t* indices = ctxt->fanIndexBuffer->map<uint32_t>(Basis::GfxApi::BufferCPUAccessWriteOnly);
while (current < (ctxt->vertexBuffer.maxBufferEntries - 3))
{
indices[current++] = index0;
indices[current++] = index1++;
indices[current++] = index2++;
}
ctxt->fanIndexBuffer->unmap();
}*/
void GfxApiNVG__copyVerts(NVGvertex* pDest, const NVGvertex* pSource, unsigned int num)
{
unsigned int i;
for (i = 0; i < num; i++)
{
pDest[i].x = pSource[i].x;
pDest[i].y = pSource[i].y;
pDest[i].u = pSource[i].u;
pDest[i].v = pSource[i].v;
}
}
unsigned int GfxApiNVG_updateVertexBuffer(GfxApiNVGcontext* ctxt, GfxApiNVGbuffer* buffer, const NVGvertex* verts, unsigned int nverts)
{
//unsigned int retEntry;
//void* data = nullptr;
if (nverts > buffer->maxBufferEntries)
{
BASIS_ASSERTF(false, "NanoVG vertex buffer too small. (%u > %u)", nverts, buffer->maxBufferEntries);
return 0;
}
// Old, map-based version which uses BufferCPUAccessMapWriteNoOverwrite.
/*
if ((buffer->currentBufferEntry + nverts) >= buffer->maxBufferEntries)
{
buffer->currentBufferEntry = 0;
data = buffer->buffer->map(Basis::GfxApi::BufferCPUAccessWriteOnly);
}
else
{
data = buffer->buffer->map(Basis::GfxApi::BufferCPUAccessMapWriteNoOverwrite);
}
GfxApiNVG__copyVerts((((NVGvertex*)data) + buffer->currentBufferEntry), (const NVGvertex*)verts, nverts);
buffer->buffer->unmap();
retEntry = buffer->currentBufferEntry;
buffer->currentBufferEntry += nverts;
return retEntry;
*/
// New, writeToBuffer-based version, which overwrites the whole buffer every time and returns 0 (no offset).
buffer->currentBufferEntry = 0;
ctxt->renderContext->writeToBuffer(buffer->buffer, verts, nverts * sizeof(NVGvertex));
return 0;
}
void GfxApiNVG_setBuffers(GfxApiNVGcontext* ctxt, unsigned int dynamicOffset)
{
uint32_t stride = sizeof(NVGvertex);
uint32_t offset = dynamicOffset * sizeof(NVGvertex);
ctxt->renderContext->bindIndexBuffer(ctxt->fanIndexBuffer, 0);
ctxt->renderContext->bindVertexBuffer(0, ctxt->vertexBuffer.buffer, stride, offset);
}
int GfxApiNVG__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
// Given a PSO key (constructed in GfxApiNVG__getPipelineState()) this function returns whether or not
// the PSO exists. If it does not exist, index is set to point to the first free slot (ie. where the PSO can
// be created).
int GfxApiNVG__findPipelineStateIndex(GfxApiNVGcontext* ctxt, uint32_t key, uint32_t* index)
{
BASIS_ASSERT(key != 0xFFFFFFFF);
BASIS_ASSERT(index != nullptr);
for (uint32_t i = 0; i < PSO_COUNT; ++i)
{
if (ctxt->pipelineStates[i].key == key)
{
// We found the PSO we want.
*index = i;
return 1;
}
else if (ctxt->pipelineStates[i].key == 0xFFFFFFFF)
{
// We found a free position, the PSO was not found.
*index = i;
return 0;
}
}
BASIS_ASSERTD(false, "Should not have come here.");
return 0;
}
GfxApi::PipelineState* GfxApiNVG__createPipelineState(GfxApiNVGcontext* ctxt, uint8_t depthStencil, uint8_t blend, uint8_t rasterizer, uint8_t primitiveTopology)
{
BASIS_PRINTF("Creating NanoVG PSO, d/s: %u, blend: %u, raster: %u, prim: %u\n");
GfxApi::PipelineStateDescription desc;
desc.primitiveTopology = (primitiveTopology == GfxApiNVGPrimitiveTopologyTriangleList) ? GfxApi::PrimitiveTopologyTriangleList : GfxApi::PrimitiveTopologyTriangleStrip;
desc.depthStencil.depthTestEnabled = false;
desc.depthStencil.depthWriteEnabled = false;
desc.depthStencil.depthFunc = GfxApi::ComparisonFuncLessEqual;
desc.depthStencil.stencilTestEnabled = true;
desc.depthStencil.frontFace.readMask = 0xFF;
desc.depthStencil.frontFace.writeMask = 0xFF;
desc.depthStencil.backFace.readMask = 0xFF;
desc.depthStencil.backFace.writeMask = 0xFF;
if (depthStencil == GfxApiNVGDepthStencilTypeDrawShapes)
{
//const D3D11_DEPTH_STENCILOP_DESC frontOp = { D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_INCR, D3D11_COMPARISON_ALWAYS };
//const D3D11_DEPTH_STENCILOP_DESC backOp = { D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_DECR, D3D11_COMPARISON_ALWAYS };
desc.depthStencil.frontFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.passOp = GfxApi::StencilOpIncrementWrap;
desc.depthStencil.frontFace.stencilFunc = GfxApi::ComparisonFuncAlways;
desc.depthStencil.backFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.passOp = GfxApi::StencilOpDecrementWrap;
desc.depthStencil.backFace.stencilFunc = GfxApi::ComparisonFuncAlways;
}
else if (depthStencil == GfxApiNVGDepthStencilTypeDrawAA)
{
//const D3D11_DEPTH_STENCILOP_DESC aaOp = { D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_COMPARISON_EQUAL };
desc.depthStencil.frontFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.passOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.stencilFunc = GfxApi::ComparisonFuncEqual;
desc.depthStencil.backFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.passOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.stencilFunc = GfxApi::ComparisonFuncEqual;
}
else if (depthStencil == GfxApiNVGDepthStencilTypeFill)
{
//const D3D11_DEPTH_STENCILOP_DESC fillOp = { D3D11_STENCIL_OP_ZERO, D3D11_STENCIL_OP_ZERO, D3D11_STENCIL_OP_ZERO, D3D11_COMPARISON_NOT_EQUAL };
desc.depthStencil.frontFace.stencilFailOp = GfxApi::StencilOpZero;
desc.depthStencil.frontFace.depthFailOp = GfxApi::StencilOpZero;
desc.depthStencil.frontFace.passOp = GfxApi::StencilOpZero;
desc.depthStencil.frontFace.stencilFunc = GfxApi::ComparisonFuncNotEqual;
desc.depthStencil.backFace.stencilFailOp = GfxApi::StencilOpZero;
desc.depthStencil.backFace.depthFailOp = GfxApi::StencilOpZero;
desc.depthStencil.backFace.passOp = GfxApi::StencilOpZero;
desc.depthStencil.backFace.stencilFunc = GfxApi::ComparisonFuncNotEqual;
}
else /*if (depthStencil == GfxApiNVGDepthStencilTypeDefault)*/
{
//const D3D11_DEPTH_STENCILOP_DESC defaultOp = { D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_STENCIL_OP_KEEP, D3D11_COMPARISON_ALWAYS };
desc.depthStencil.frontFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.passOp = GfxApi::StencilOpKeep;
desc.depthStencil.frontFace.stencilFunc = GfxApi::ComparisonFuncAlways;
desc.depthStencil.backFace.stencilFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.depthFailOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.passOp = GfxApi::StencilOpKeep;
desc.depthStencil.backFace.stencilFunc = GfxApi::ComparisonFuncAlways;
desc.depthStencil.stencilTestEnabled = false;
}
desc.rasterizer.fillMode = GfxApi::FillModeSolid;
desc.rasterizer.cullMode = (rasterizer == GfxApiNVGRasterizerTypeNoCull) ? GfxApi::CullModeNone : GfxApi::CullModeBack;
desc.rasterizer.frontCounterClockWise = true;
desc.rasterizer.depthBias = 0;
desc.rasterizer.depthBiasClamp = 0.0f;
desc.rasterizer.slopeScaledDepthBias = 0.0f;
desc.rasterizer.depthClipEnabled = true;
desc.rasterizer.scissorTestEnabled = false;
desc.rasterizer.antialiasedLinesEnabled = false;
desc.blend.renderTargets.resize(1);
desc.blend.renderTargets[0].blendEnabled = (blend == GfxApiNVGBlendTypeBlend);
desc.blend.renderTargets[0].srcBlend = GfxApi::BlendOne;
desc.blend.renderTargets[0].destBlend = GfxApi::BlendInvSrcAlpha;
desc.blend.renderTargets[0].blendOp = GfxApi::BlendOpAdd;
desc.blend.renderTargets[0].srcBlendAlpha = GfxApi::BlendOne;
desc.blend.renderTargets[0].destBlendAlpha = GfxApi::BlendInvSrcAlpha;
desc.blend.renderTargets[0].blendOpAlpha = GfxApi::BlendOpAdd;
for (int i = 0; i < 4; ++i)
desc.blend.renderTargets[0].writeMaskRGBA[i] = (blend == GfxApiNVGBlendTypeBlend);
desc.shaderProgram = ctxt->shader.shader;
desc.uniforms = ctxt->uniformMetadataList;
return ctxt->device->createPipelineState(desc);
}
void GfxApiNVG__applyPipelineState(GfxApiNVGcontext* ctxt)
{
uint32_t key = (ctxt->currentDepthStencilType << 24) | (ctxt->currentBlendType << 16) | (ctxt->currentRasterizerType << 8) | ctxt->currentPrimTopoType;
uint32_t index = 0;
GfxApi::PipelineState* pso = nullptr;
if (GfxApiNVG__findPipelineStateIndex(ctxt, key, &index))
{
// The PSO was already created.
pso = ctxt->pipelineStates[index].pso;
}
else
{
// The PSO was not yet created. Create and store for later use.
pso = GfxApiNVG__createPipelineState(ctxt, ctxt->currentDepthStencilType, ctxt->currentBlendType, ctxt->currentRasterizerType, ctxt->currentPrimTopoType);
ctxt->pipelineStates[index].pso = pso;
ctxt->pipelineStates[index].key = key;
}
ctxt->renderContext->bindPipelineState(pso);
}
int GfxApiNVG__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
int GfxApiNVG__renderCreate(void* uptr)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
// TODO: Need to find a good value for this, and
// Use the dynamic buffer fill technique to handle overflow
ctxt->vertexBuffer.maxBufferEntries = 100000;
ctxt->vertexBuffer.currentBufferEntry = 0;
{
// We don't specify any vertex stride here. Instead we specify
// it when binding the VB to the pipeline.
GfxApi::BufferDescription vbDesc;
vbDesc.type = GfxApi::BufferTypeVertex;
vbDesc.size = sizeof(NVGvertex) * ctxt->vertexBuffer.maxBufferEntries;
vbDesc.cpuAccess = GfxApi::BufferCPUAccessWriteOnly;
vbDesc.usage = GfxApi::BufferUsageDynamic;
ctxt->vertexBuffer.buffer = ctxt->device->createBuffer(vbDesc);
#ifndef BASIS_CONFIG_FINAL
ctxt->vertexBuffer.buffer->setDebugName("NanoVG renderer vertex buffer");
#endif
}
{
uint32_t index0 = 0;
uint32_t index1 = 1;
uint32_t index2 = 2;
uint32_t current = 0;
uint32_t* indices = Mem::NewArray<uint32_t>(ctxt->vertexBuffer.maxBufferEntries);
while (current < (ctxt->vertexBuffer.maxBufferEntries - 3))
{
indices[current++] = index0;
indices[current++] = index1++;
indices[current++] = index2++;
}
GfxApi::BufferDescription ibDesc;
ibDesc.type = GfxApi::BufferTypeIndex;
ibDesc.size = sizeof(uint32_t) * ctxt->vertexBuffer.maxBufferEntries;
ibDesc.cpuAccess = GfxApi::BufferCPUAccessNone;
ibDesc.usage = GfxApi::BufferUsageImmutable;
ibDesc.indexBuffer.type = GfxApi::IndexBufferType32Bit;
ibDesc.initialData = indices;
ctxt->fanIndexBuffer = ctxt->device->createBuffer(ibDesc);
#ifndef BASIS_CONFIG_FINAL
ctxt->fanIndexBuffer->setDebugName("NanoVG renderer fan index buffer");
#endif
Mem::DeleteArray(indices);
}
{
const uint32_t MAX_UPDATES_PER_FRAME = 10;
GfxApi::BufferDescription desc = {};
desc.type = GfxApi::BufferTypeConstant;
desc.size = sizeof(VS_CONSTANTS);
desc.cpuAccess = GfxApi::BufferCPUAccessWriteOnly;
desc.usage = GfxApi::BufferUsageDynamic;
desc.initialData = nullptr;
desc.constantBuffer.dynamicThroughOffset = true;
desc.constantBuffer.maxUpdateCount = MAX_UPDATES_PER_FRAME;
ctxt->VSconstants = ctxt->device->createBuffer(desc);
}
{
size_t size = sizeof(GfxApiNVGfragUniforms);
if ((size % 16) != 0)
{
size += 16 - (size % 16);
}
ctxt->fragSize = (int)size;
const uint32_t MAX_UPDATES_PER_FRAME = 250;
GfxApi::BufferDescription desc = {};
desc.type = GfxApi::BufferTypeConstant;
desc.size = size;
desc.cpuAccess = GfxApi::BufferCPUAccessWriteOnly;
desc.usage = GfxApi::BufferUsageDynamic;
desc.initialData = nullptr;
desc.constantBuffer.dynamicThroughOffset = true;
desc.constantBuffer.maxUpdateCount = MAX_UPDATES_PER_FRAME;
ctxt->PSconstants = ctxt->device->createBuffer(desc);
}
// Some platforms does not allow to have samples to unset textures.
// Create empty one which is bound when there's no texture specified.
ctxt->dummyTex = GfxApiNVG__renderCreateTexture(ctxt, NVG_TEXTURE_ALPHA, 1, 1, 0, NULL);
{
GfxApi::TextureSamplerDescription samplerDesc;
samplerDesc.minFilter = GfxApi::TextureFilterLinear;
samplerDesc.magFilter = GfxApi::TextureFilterLinear;
samplerDesc.mipMapFilter = GfxApi::TextureFilterLinear;
samplerDesc.addressModeW = GfxApi::TextureAddressModeWrap;
samplerDesc.comparisonFunc = GfxApi::ComparisonFuncNever;
samplerDesc.minLOD = 0.0f;
samplerDesc.addressModeU = GfxApi::TextureAddressModeClamp;
samplerDesc.addressModeV = GfxApi::TextureAddressModeClamp;
ctxt->samplers[0] = ctxt->device->createTextureSampler(samplerDesc);
samplerDesc.addressModeU = GfxApi::TextureAddressModeWrap;
samplerDesc.addressModeV = GfxApi::TextureAddressModeClamp;
ctxt->samplers[1] = ctxt->device->createTextureSampler(samplerDesc);
samplerDesc.addressModeU = GfxApi::TextureAddressModeClamp;
samplerDesc.addressModeV = GfxApi::TextureAddressModeWrap;
ctxt->samplers[2] = ctxt->device->createTextureSampler(samplerDesc);
samplerDesc.addressModeU = GfxApi::TextureAddressModeWrap;
samplerDesc.addressModeV = GfxApi::TextureAddressModeWrap;
ctxt->samplers[3] = ctxt->device->createTextureSampler(samplerDesc);
}
ctxt->bindGroup->init(ctxt->device, *ctxt->uniformMetadataList);
ctxt->bindGroup->addBuffer(ctxt->VSconstantBindingSlot, ctxt->VSconstants, 0, ctxt->VSconstants->getBindSize());
ctxt->bindGroup->addBuffer(ctxt->PSconstantBindingSlot, ctxt->PSconstants, 0, ctxt->PSconstants->getBindSize());
return 1; // 1 == success.
}
int GfxApiNVG__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGtexture* tex = GfxApiNVG__allocTexture(ctxt);
if (tex == NULL)
{
return 0;
}
if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS)
{
BASIS_ASSERTD(false, "Generating NanoVG texture mip maps not yet implemented.");
}
tex->width = w;
tex->height = h;
tex->type = type;
tex->flags = imageFlags;
GfxApi::TextureDescription texDesc;
texDesc.type = GfxApi::TextureType2D;
texDesc.format = (type == NVG_TEXTURE_RGBA) ? GfxApi::TextureFormat_R8G8B8A8_UNORM : GfxApi::TextureFormat_R8_UNORM;
texDesc.width = (uint32_t)w;
texDesc.height = (uint32_t)h;
texDesc.arrayLength = 1;
texDesc.mipLevelCount = 1;
texDesc.pixels = data;
texDesc.cpuAccess = GfxApi::TextureCPUAccessWriteOnly;
tex->tex = ctxt->device->createTexture(texDesc);
return tex->id;
}
int GfxApiNVG__renderDeleteTexture(void* uptr, int image)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
return GfxApiNVG__deleteTexture(ctxt, image);
}
int GfxApiNVG__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGtexture* tex = GfxApiNVG__findTexture(ctxt, image);
if (!tex)
{
return 0;
}
// The texture data passed in [data] is the whole texture, not only the rectangle to update.
// Because of this we need to adjust the pointer to point to the start of the rectangle and
// override the source row pitch. Otherwise the GfxApi::Texture will use the row pitch of the rect.
uint32_t pixelWidthBytes = (tex->type == NVG_TEXTURE_RGBA) ? 4 : 1;
uint32_t sourceRowPitch = tex->width * pixelWidthBytes;
const uint8_t* rectData = (const uint8_t*)(data + (y * (tex->width * pixelWidthBytes)) + (x * pixelWidthBytes));
tex->tex->writeData(rectData, 0, 0, x, y, w, h, sourceRowPitch);
return 1;
}
int GfxApiNVG__renderGetTextureSize(void* uptr, int image, int* w, int* h)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGtexture* tex = GfxApiNVG__findTexture(ctxt, image);
if (!tex)
{
return 0;
}
*w = tex->width;
*h = tex->height;
return 1;
}
void GfxApiNVG__xformToMat3x3(float* m3, float* t)
{
m3[0] = t[0];
m3[1] = t[1];
m3[2] = 0.0f;
m3[3] = t[2];
m3[4] = t[3];
m3[5] = 0.0f;
m3[6] = t[4];
m3[7] = t[5];
m3[8] = 1.0f;
}
/*NVGcolor GfxApiNVG__premulColor(NVGcolor c)
{
c.r *= c.a;
c.g *= c.a;
c.b *= c.a;
return c;
}*/
void GfxApiNVG__premulColorIntoFloat4(NVGcolor c, float* f)
{
f[0] = c.r * c.a;
f[2] = c.b * c.a;
f[1] = c.g * c.a;
f[3] = c.a;
}
int GfxApiNVG__convertPaint(GfxApiNVGcontext* ctxt, GfxApiNVGfragUniforms* frag, NVGpaint* paint, NVGscissor* scissor,
float width, float fringe, float strokeThr)
{
GfxApiNVGtexture* tex = NULL;
float invxform[6], paintMat[9], scissorMat[9];
memset(frag, 0, sizeof(*frag));
//frag->innerCol = GfxApiNVG__premulColor(paint->innerColor);
//frag->outerCol = GfxApiNVG__premulColor(paint->outerColor);
GfxApiNVG__premulColorIntoFloat4(paint->innerColor, frag->innerCol);
GfxApiNVG__premulColorIntoFloat4(paint->outerColor, frag->outerCol);
if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f)
{
memset(scissorMat, 0, sizeof(scissorMat));
frag->scissorExt[0] = 1.0f;
frag->scissorExt[1] = 1.0f;
frag->scissorScale[0] = 1.0f;
frag->scissorScale[1] = 1.0f;
}
else
{
nvgTransformInverse(invxform, scissor->xform);
GfxApiNVG__xformToMat3x3(scissorMat, invxform);
frag->scissorExt[0] = scissor->extent[0];
frag->scissorExt[1] = scissor->extent[1];
frag->scissorScale[0] = sqrtf(scissor->xform[0] * scissor->xform[0] + scissor->xform[2] * scissor->xform[2]) / fringe;
frag->scissorScale[1] = sqrtf(scissor->xform[1] * scissor->xform[1] + scissor->xform[3] * scissor->xform[3]) / fringe;
}
GfxApiNVG__copyMatrix3to4(frag->scissorMat, scissorMat);
frag->extent[0] = paint->extent[0];
frag->extent[1] = paint->extent[1];
frag->strokeMult[0] = (width * 0.5f + fringe * 0.5f) / fringe;
frag->strokeMult[1] = strokeThr;
if (paint->image != 0)
{
tex = GfxApiNVG__findTexture(ctxt, paint->image);
if (tex == NULL)
{
return 0;
}
if ((tex->flags & NVG_IMAGE_FLIPY) != 0)
{
float m1[6], m2[6];
nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f);
nvgTransformMultiply(m1, paint->xform);
nvgTransformScale(m2, 1.0f, -1.0f);
nvgTransformMultiply(m2, m1);
nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f);
nvgTransformMultiply(m1, m2);
nvgTransformInverse(invxform, m1);
}
else
{
nvgTransformInverse(invxform, paint->xform);
}
frag->type = NSVG_SHADER_FILLIMG;
#if NANOVG_GL_USE_UNIFORMBUFFER
if (tex->type == NVG_TEXTURE_RGBA)
{
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
}
else
{
frag->texType = 2;
}
#else
/*if (tex->type == NVG_TEXTURE_RGBA)
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f;
else
frag->texType = 2.0f;*/
// The original version above sets the values as floats. Why?
if (tex->type == NVG_TEXTURE_RGBA)
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
else
frag->texType = 2;
#endif
}
else
{
frag->type = NSVG_SHADER_FILLGRAD;
frag->radius[0] = paint->radius;
frag->feather[0] = paint->feather;
nvgTransformInverse(invxform, paint->xform);
}
GfxApiNVG__xformToMat3x3(paintMat, invxform);
GfxApiNVG__copyMatrix3to4(frag->paintMat, paintMat);
return 1;
}
GfxApiNVGfragUniforms* nvg__fragUniformPtr(GfxApiNVGcontext* ctxt, int i)
{
return (GfxApiNVGfragUniforms*)&ctxt->uniforms[i];
}
void GfxApiNVG__setUniforms(GfxApiNVGcontext* ctxt, int uniformOffset, int image)
{
GfxApiNVGfragUniforms* frag = nvg__fragUniformPtr(ctxt, uniformOffset);
GfxApiNVGtexture* tex = nullptr;
ctxt->renderContext->updateDynamicBuffer(ctxt->PSconstants, frag, sizeof(GfxApiNVGfragUniforms));
if (image != 0)
{
tex = GfxApiNVG__findTexture(ctxt, image);
}
if (!tex)
{
tex = GfxApiNVG__findTexture(ctxt, ctxt->dummyTex);
BASIS_ASSERT(tex != nullptr); // The dummy texture should always be available.
}
ctxt->bindGroup->begin();
ctxt->bindGroup->addTransientTextureSampler(ctxt->samplerBindingSlot, ctxt->currentSampler);
ctxt->bindGroup->addTransientTexture(ctxt->textureBindingSlot, tex->tex);
ctxt->renderContext->bindUniforms(ctxt->bindGroup->finalize());
}
void GfxApiNVG__renderViewport(void* uptr, float width, float height, float devicePixelRatio)
{
NVG_NOTUSED(devicePixelRatio);
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
ctxt->shader.vc.viewSize[0] = width;
ctxt->shader.vc.viewSize[1] = height;
ctxt->renderContext->updateDynamicBuffer(ctxt->VSconstants, &ctxt->shader.vc, sizeof(VS_CONSTANTS));
}
void GfxApiNVG__fill(GfxApiNVGcontext* ctxt, GfxApiNVGcall* call)
{
GfxApiNVGpath* paths = &ctxt->paths[call->pathOffset];
int i, npaths = call->pathCount;
// Draw shapes
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDrawShapes; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDrawShapes, 0);
ctxt->currentBlendType = GfxApiNVGBlendTypeNoWrite; //D3D_API_3(D3D->pDeviceContext, OMSetBlendState, D3D->pBSNoWrite, NULL, 0xFFFFFFFF);
ctxt->currentRasterizerType = GfxApiNVGRasterizerTypeNoCull; //D3D_API_1(D3D->pDeviceContext, RSSetState, D3D->pRSNoCull);
// set bindpoint for solid loc
GfxApiNVG__setUniforms(ctxt, call->uniformOffset, 0);
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleList; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
if (paths[i].fillCount > 0)
{
unsigned int numIndices = ((paths[i].fillCount - 2) * 3);
BASIS_ASSERT(numIndices < ctxt->vertexBuffer.maxBufferEntries);
if (numIndices < ctxt->vertexBuffer.maxBufferEntries)
{
ctxt->renderContext->drawIndexed(numIndices, 0, paths[i].fillOffset); //D3D_API_3(D3D->pDeviceContext, DrawIndexed, numIndices, 0, paths[i].fillOffset);
}
}
}
// Draw anti-aliased pixels
ctxt->currentRasterizerType = GfxApiNVGRasterizerTypeCull; //D3D_API_1(D3D->pDeviceContext, RSSetState, D3D->pRSCull);
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleStrip; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
ctxt->currentBlendType = GfxApiNVGBlendTypeBlend; //D3D_API_3(D3D->pDeviceContext, OMSetBlendState, D3D->pBSBlend, NULL, 0xFFFFFFFF);
GfxApiNVG__setUniforms(ctxt, call->uniformOffset + ctxt->fragSize, call->image);
if (ctxt->flags & NVG_ANTIALIAS)
{
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDrawAA; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDrawAA, 0);
GfxApiNVG__applyPipelineState(ctxt);
// Draw fringes
for (i = 0; i < npaths; i++)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
}
// Draw fill
ctxt->currentRasterizerType = GfxApiNVGRasterizerTypeNoCull; //D3D_API_1(D3D->pDeviceContext, RSSetState, D3D->pRSNoCull);
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeFill; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilFill, 0);
GfxApiNVG__applyPipelineState(ctxt);
ctxt->renderContext->draw(call->triangleCount, call->triangleOffset); //D3D_API_2(D3D->pDeviceContext, Draw, call->triangleCount, call->triangleOffset);
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDefault; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDefault, 0);
}
void GfxApiNVG__convexFill(GfxApiNVGcontext* ctxt, GfxApiNVGcall* call)
{
GfxApiNVGpath* paths = &ctxt->paths[call->pathOffset];
int i, npaths = call->pathCount;
GfxApiNVG__setUniforms(ctxt, call->uniformOffset, call->image);
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleList; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
// Draws a fan using indices to fake it up, since there isn't a fan primitive in D3D11.
if (paths[i].fillCount > 2)
{
unsigned int numIndices = ((paths[i].fillCount - 2) * 3);
BASIS_ASSERT(numIndices < ctxt->vertexBuffer.maxBufferEntries);
if (numIndices < ctxt->vertexBuffer.maxBufferEntries)
{
ctxt->renderContext->drawIndexed(numIndices, 0, paths[i].fillOffset); //D3D_API_3(D3D->pDeviceContext, DrawIndexed, numIndices, 0, paths[i].fillOffset);
}
}
}
// Draw fringes
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleStrip; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
if (paths[i].strokeCount > 0)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
}
}
void GfxApiNVG__stroke(GfxApiNVGcontext* ctxt, GfxApiNVGcall* call)
{
GfxApiNVGpath* paths = &ctxt->paths[call->pathOffset];
int npaths = call->pathCount, i;
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleStrip; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
if (ctxt->flags & NVG_STENCIL_STROKES)
{
// Fill the stroke base without overlap
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDefault; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDefault, 0);
GfxApiNVG__setUniforms(ctxt, call->uniformOffset + ctxt->fragSize, call->image);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
// Draw anti-aliased pixels.
GfxApiNVG__setUniforms(ctxt, call->uniformOffset, call->image);
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDrawAA; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDrawAA, 0);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
// Clear stencil buffer.
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeFill; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilFill, 0);
GfxApiNVG__applyPipelineState(ctxt);
for (i = 0; i < npaths; i++)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDefault; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDefault, 0);
}
else
{
GfxApiNVG__setUniforms(ctxt, call->uniformOffset, call->image);
GfxApiNVG__applyPipelineState(ctxt);
// Draw Strokes
for (i = 0; i < npaths; i++)
{
ctxt->renderContext->draw(paths[i].strokeCount, paths[i].strokeOffset); //D3D_API_2(D3D->pDeviceContext, Draw, paths[i].strokeCount, paths[i].strokeOffset);
}
}
}
void GfxApiNVG__triangles(GfxApiNVGcontext* ctxt, GfxApiNVGcall* call)
{
ctxt->currentPrimTopoType = GfxApiNVGPrimitiveTopologyTriangleList; //D3D_API_1(D3D->pDeviceContext, IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
GfxApiNVG__setUniforms(ctxt, call->uniformOffset, call->image);
GfxApiNVG__applyPipelineState(ctxt);
ctxt->renderContext->draw(call->triangleCount, call->triangleOffset); //D3D_API_2(D3D->pDeviceContext, Draw, call->triangleCount, call->triangleOffset);
}
void GfxApiNVG__renderCancel(void* uptr)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
ctxt->nverts = 0;
ctxt->npaths = 0;
ctxt->ncalls = 0;
ctxt->nuniforms = 0;
}
void GfxApiNVG__renderFlush(void* uptr)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
if (ctxt->ncalls > 0)
{
unsigned int buffer0Offset = GfxApiNVG_updateVertexBuffer(ctxt, &ctxt->vertexBuffer, ctxt->verts, ctxt->nverts);
GfxApiNVG_setBuffers(ctxt, buffer0Offset);
// Draw shapes
ctxt->currentDepthStencilType = GfxApiNVGDepthStencilTypeDefault; //D3D_API_2(D3D->pDeviceContext, OMSetDepthStencilState, D3D->pDepthStencilDefault, 0);
ctxt->currentBlendType = GfxApiNVGBlendTypeBlend; //D3D_API_3(D3D->pDeviceContext, OMSetBlendState, D3D->pBSBlend, NULL, 0xFFFFFFFF);
ctxt->currentRasterizerType = GfxApiNVGRasterizerTypeCull; //D3D_API_1(D3D->pDeviceContext, RSSetState, D3D->pRSCull);
for (int i = 0; i < ctxt->ncalls; i++)
{
GfxApiNVGcall* call = &ctxt->calls[i];
if (call->image != 0)
{
GfxApiNVGtexture* tex = GfxApiNVG__findTexture(ctxt, call->image);
if (tex)
{
ctxt->currentSampler = ctxt->samplers[(tex->flags & NVG_IMAGE_REPEATX ? 1 : 0) + (tex->flags & NVG_IMAGE_REPEATY ? 2 : 0)];
}
else
{
ctxt->currentSampler = ctxt->samplers[0];
}
}
else
{
// Some APIs (eg. Metal) does not like unbound samplers. Bind any of the samplers here even if it is not used.
ctxt->currentSampler = ctxt->samplers[0];
}
if (call->type == GFXAPINVG_FILL)
GfxApiNVG__fill(ctxt, call);
else if (call->type == GFXAPINVG_CONVEXFILL)
GfxApiNVG__convexFill(ctxt, call);
else if (call->type == GFXAPINVG_STROKE)
GfxApiNVG__stroke(ctxt, call);
else if (call->type == GFXAPINVG_TRIANGLES)
GfxApiNVG__triangles(ctxt, call);
}
}
// Reset calls
ctxt->nverts = 0;
ctxt->npaths = 0;
ctxt->ncalls = 0;
ctxt->nuniforms = 0;
}
int GfxApiNVG__maxVertCount(const NVGpath* paths, int npaths)
{
int i, count = 0;
for (i = 0; i < npaths; i++) {
count += paths[i].nfill;
count += paths[i].nstroke;
}
return count;
}
GfxApiNVGcall* GfxApiNVG__allocCall(GfxApiNVGcontext* ctxt)
{
GfxApiNVGcall* ret = NULL;
if (ctxt->ncalls + 1 > ctxt->ccalls)
{
GfxApiNVGcall* calls = NULL;
int ccalls = GfxApiNVG__maxi(ctxt->ncalls + 1, 128) + ctxt->ccalls / 2; // 1.5x Overallocate
calls = (GfxApiNVGcall*)realloc(ctxt->calls, sizeof(GfxApiNVGcall) * ccalls);
if (calls == NULL) return NULL;
ctxt->calls = calls;
ctxt->ccalls = ccalls;
}
ret = &ctxt->calls[ctxt->ncalls++];
memset(ret, 0, sizeof(GfxApiNVGcall));
return ret;
}
int GfxApiNVG__allocPaths(GfxApiNVGcontext* ctxt, int n)
{
int ret = 0;
if (ctxt->npaths + n > ctxt->cpaths)
{
GfxApiNVGpath* paths = NULL;
int cpaths = GfxApiNVG__maxi(ctxt->npaths + n, 128) + ctxt->cpaths / 2; // 1.5x Overallocate
paths = (GfxApiNVGpath*)realloc(ctxt->paths, sizeof(GfxApiNVGpath) * cpaths);
if (paths == NULL) return -1;
ctxt->paths = paths;
ctxt->cpaths = cpaths;
}
ret = ctxt->npaths;
ctxt->npaths += n;
return ret;
}
int GfxApiNVG__allocVerts(GfxApiNVGcontext* ctxt, int n)
{
int ret = 0;
if (ctxt->nverts + n > ctxt->cverts)
{
NVGvertex* verts = NULL;
int cverts = GfxApiNVG__maxi(ctxt->nverts + n, 4096) + ctxt->cverts / 2; // 1.5x Overallocate
verts = (NVGvertex*)realloc(ctxt->verts, sizeof(NVGvertex) * cverts);
if (verts == NULL) return -1;
ctxt->verts = verts;
ctxt->cverts = cverts;
}
ret = ctxt->nverts;
ctxt->nverts += n;
return ret;
}
int GfxApiNVG__allocFragUniforms(GfxApiNVGcontext* ctxt, int n)
{
int ret = 0, structSize = ctxt->fragSize;
if (ctxt->nuniforms + n > ctxt->cuniforms)
{
unsigned char* uniforms = NULL;
int cuniforms = GfxApiNVG__maxi(ctxt->nuniforms + n, 128) + ctxt->cuniforms / 2; // 1.5x Overallocate
uniforms = (unsigned char*)realloc(ctxt->uniforms, structSize * cuniforms);
if (uniforms == NULL) return -1;
ctxt->uniforms = uniforms;
ctxt->cuniforms = cuniforms;
}
ret = ctxt->nuniforms * structSize;
ctxt->nuniforms += n;
return ret;
}
void GfxApiNVG__vset(struct NVGvertex* vtx, float x, float y, float u, float v)
{
vtx->x = x;
vtx->y = y;
vtx->u = u;
vtx->v = v;
}
void GfxApiNVG__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation,
NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGcall* call = GfxApiNVG__allocCall(ctxt);
NVGvertex* quad;
GfxApiNVGfragUniforms* frag = NULL;
int i, maxverts, offset;
if (call == NULL) return;
call->type = GFXAPINVG_FILL;
call->triangleCount = 4;
call->pathOffset = GfxApiNVG__allocPaths(ctxt, npaths);
if (call->pathOffset == -1) goto error;
call->pathCount = npaths;
call->image = paint->image;
NVG_NOTUSED(compositeOperation);
if (npaths == 1 && paths[0].convex)
{
call->type = GFXAPINVG_CONVEXFILL;
call->triangleCount = 0; // Bounding box fill quad not needed for convex fill
}
// Allocate vertices for all the paths.
maxverts = GfxApiNVG__maxVertCount(paths, npaths) + call->triangleCount;
offset = GfxApiNVG__allocVerts(ctxt, maxverts);
if (offset == -1) goto error;
for (i = 0; i < npaths; i++)
{
GfxApiNVGpath* copy = &ctxt->paths[call->pathOffset + i];
const NVGpath* path = &paths[i];
memset(copy, 0, sizeof(GfxApiNVGpath));
if (path->nfill > 0)
{
copy->fillOffset = offset;
copy->fillCount = path->nfill;
memcpy(&ctxt->verts[offset], path->fill, sizeof(struct NVGvertex) * path->nfill);
offset += path->nfill;
}
if (path->nstroke > 0)
{
copy->strokeOffset = offset;
copy->strokeCount = path->nstroke;
memcpy(&ctxt->verts[offset], path->stroke, sizeof(struct NVGvertex) * path->nstroke);
offset += path->nstroke;
}
}
if (call->type == GFXAPINVG_FILL)
{
// Quad
call->triangleOffset = offset;
quad = &ctxt->verts[call->triangleOffset];
GfxApiNVG__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
GfxApiNVG__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
GfxApiNVG__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
GfxApiNVG__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
call->uniformOffset = GfxApiNVG__allocFragUniforms(ctxt, 2);
if (call->uniformOffset == -1) goto error;
// Simple shader for stencil
frag = nvg__fragUniformPtr(ctxt, call->uniformOffset);
memset(frag, 0, sizeof(*frag));
frag->strokeMult[1] = -1.0f;
frag->type = NSVG_SHADER_SIMPLE;
// Fill shader
GfxApiNVG__convertPaint(ctxt, nvg__fragUniformPtr(ctxt, call->uniformOffset + ctxt->fragSize), paint, scissor, fringe, fringe, -1.0f);
}
else
{
call->uniformOffset = GfxApiNVG__allocFragUniforms(ctxt, 1);
if (call->uniformOffset == -1) goto error;
// Fill shader
GfxApiNVG__convertPaint(ctxt, nvg__fragUniformPtr(ctxt, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f);
}
return;
error:
// We get here if call alloc was ok, but something else is not.
// Roll back the last call to prevent drawing it.
if (ctxt->ncalls > 0) ctxt->ncalls--;
}
void GfxApiNVG__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation,
NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGcall* call = GfxApiNVG__allocCall(ctxt);
int i, maxverts, offset;
if (call == NULL) return;
call->type = GFXAPINVG_STROKE;
call->pathOffset = GfxApiNVG__allocPaths(ctxt, npaths);
if (call->pathOffset == -1) goto error;
call->pathCount = npaths;
call->image = paint->image;
NVG_NOTUSED(compositeOperation);
// Allocate vertices for all the paths.
maxverts = GfxApiNVG__maxVertCount(paths, npaths);
offset = GfxApiNVG__allocVerts(ctxt, maxverts);
if (offset == -1) goto error;
for (i = 0; i < npaths; i++)
{
GfxApiNVGpath* copy = &ctxt->paths[call->pathOffset + i];
const NVGpath* path = &paths[i];
memset(copy, 0, sizeof(GfxApiNVGpath));
if (path->nstroke)
{
copy->strokeOffset = offset;
copy->strokeCount = path->nstroke;
memcpy(&ctxt->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
offset += path->nstroke;
}
}
if (ctxt->flags & NVG_STENCIL_STROKES)
{
// Fill shader
call->uniformOffset = GfxApiNVG__allocFragUniforms(ctxt, 2);
if (call->uniformOffset == -1) goto error;
GfxApiNVG__convertPaint(ctxt, nvg__fragUniformPtr(ctxt, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
GfxApiNVG__convertPaint(ctxt, nvg__fragUniformPtr(ctxt, call->uniformOffset + ctxt->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f / 255.0f);
}
else
{
// Fill shader
call->uniformOffset = GfxApiNVG__allocFragUniforms(ctxt, 1);
if (call->uniformOffset == -1) goto error;
GfxApiNVG__convertPaint(ctxt, nvg__fragUniformPtr(ctxt, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
}
return;
error:
// We get here if call alloc was ok, but something else is not.
// Roll back the last call to prevent drawing it.
if (ctxt->ncalls > 0) ctxt->ncalls--;
}
void GfxApiNVG__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation,
NVGscissor* scissor, const NVGvertex* verts, int nverts, float fringe)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
GfxApiNVGcall* call = GfxApiNVG__allocCall(ctxt);
GfxApiNVGfragUniforms* frag = NULL;
if (call == NULL) return;
call->type = GFXAPINVG_TRIANGLES;
call->image = paint->image;
NVG_NOTUSED(compositeOperation);
// Allocate vertices for all the paths.
call->triangleOffset = GfxApiNVG__allocVerts(ctxt, nverts);
if (call->triangleOffset == -1) goto error;
call->triangleCount = nverts;
memcpy(&ctxt->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts);
// Fill shader
call->uniformOffset = GfxApiNVG__allocFragUniforms(ctxt, 1);
if (call->uniformOffset == -1) goto error;
frag = nvg__fragUniformPtr(ctxt, call->uniformOffset);
GfxApiNVG__convertPaint(ctxt, frag, paint, scissor, 1.0f, fringe, -1.0f);
frag->type = NSVG_SHADER_IMG;
return;
error:
// We get here if call alloc was ok, but something else is not.
// Roll back the last call to prevent drawing it.
if (ctxt->ncalls > 0) ctxt->ncalls--;
}
void GfxApiNVG__renderDelete(void* uptr)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)uptr;
if (ctxt->uniformMetadataList)
{
Mem::Delete(ctxt->uniformMetadataList);
}
Mem::Delete(ctxt->bindGroup);
BASIS_RELEASE_AND_NULL(ctxt->shader.shader);
ctxt->shaderProgramResource->removeListener(ctxt->shaderListener);
ctxt->shaderProgramResource->release();
Mem::Delete(ctxt->shaderListener);
for (int i = 0; i < ctxt->ntextures; i++)
{
if (ctxt->textures[i].tex != 0 /*&& (ctxt->textures[i].flags & NVG_IMAGE_NODELETE) == 0*/)
{
BASIS_RELEASE_AND_NULL(ctxt->textures[i].tex);
}
}
for (int i = 0; i < 4; i++)
{
BASIS_RELEASE_AND_NULL(ctxt->samplers[i]);
}
BASIS_RELEASE_AND_NULL(ctxt->vertexBuffer.buffer);
BASIS_RELEASE_AND_NULL(ctxt->VSconstants);
BASIS_RELEASE_AND_NULL(ctxt->PSconstants);
BASIS_RELEASE_AND_NULL(ctxt->fanIndexBuffer);
for (uint32_t i = 0; i < PSO_COUNT; ++i)
{
BASIS_RELEASE_AND_NULL(ctxt->pipelineStates[i].pso);
}
free(ctxt->textures);
free(ctxt->paths);
free(ctxt->verts);
free(ctxt->uniforms);
free(ctxt->calls);
alloc.free(ctxt);
}
}
struct NVGcontext* nvgCreateGfxApi(Basis::ResourceManager* resMgr, Basis::GfxApi::RenderDevice* device, int flags)
{
NVGparams params;
NVGcontext* ctx = nullptr;
GfxApiNVGcontext* gfxApiCtxt = (GfxApiNVGcontext*)alloc.allocate(sizeof(GfxApiNVGcontext));
if (!gfxApiCtxt)
{
goto error;
}
memset(gfxApiCtxt, 0, sizeof(GfxApiNVGcontext));
for (uint32_t i = 0; i < PSO_COUNT; ++i)
{
// Set the keys to 0xFFFFFFFF to mark them as "free".
gfxApiCtxt->pipelineStates[i].key = 0xFFFFFFFF;
}
gfxApiCtxt->bindGroup = Mem::New<GfxApi::DynamicBindGroup>();
gfxApiCtxt->shaderProgramResource = resMgr->acquire<ShaderProgramResource>(
(flags & NVG_ANTIALIAS) ? NANOVG_SHADER_AA_PATH : NANOVG_SHADER_NO_AA_PATH);
gfxApiCtxt->shaderListener = Mem::New<ShaderResourceListener>(gfxApiCtxt);
gfxApiCtxt->shaderProgramResource->addListener(gfxApiCtxt->shaderListener);
// Needs to be scoped because of goto.
//{
// // Currently all backends except D3D11 needs the extra buffer map() call.
// // See GfxApiNVG__setUniforms() for more info.
// Renderer* renderer = Renderer::getPtr();
// GfxApi::GfxApiBackendType backendType = renderer->getBackendType();
// gfxApiCtxt->needsExtraBufferBind = (backendType != GfxApi::GfxApiBackendTypeD3D11);
//}
gfxApiCtxt->shader.shader = gfxApiCtxt->shaderProgramResource->getShaderProgram();
initUniformMetadata(gfxApiCtxt);
gfxApiCtxt->VSconstantBindingSlot = gfxApiCtxt->shaderProgramResource->getDescription().getUniformBindingSlot("VS_CONSTANTS");
gfxApiCtxt->textureBindingSlot = gfxApiCtxt->shaderProgramResource->getDescription().getUniformBindingSlot("g_texture");
gfxApiCtxt->samplerBindingSlot = gfxApiCtxt->shaderProgramResource->getDescription().getUniformBindingSlot("g_sampler");
gfxApiCtxt->PSconstantBindingSlot = gfxApiCtxt->shaderProgramResource->getDescription().getUniformBindingSlot("PS_CONSTANTS");
gfxApiCtxt->device = device;
gfxApiCtxt->renderContext = device->getImmediateContext();
gfxApiCtxt->flags = flags;
memset(&params, 0, sizeof(params));
params.renderCreate = GfxApiNVG__renderCreate;
params.renderCreateTexture = GfxApiNVG__renderCreateTexture;
params.renderDeleteTexture = GfxApiNVG__renderDeleteTexture;
params.renderUpdateTexture = GfxApiNVG__renderUpdateTexture;
params.renderGetTextureSize = GfxApiNVG__renderGetTextureSize;
params.renderViewport = GfxApiNVG__renderViewport;
params.renderCancel = GfxApiNVG__renderCancel;
params.renderFlush = GfxApiNVG__renderFlush;
params.renderFill = GfxApiNVG__renderFill;
params.renderStroke = GfxApiNVG__renderStroke;
params.renderTriangles = GfxApiNVG__renderTriangles;
params.renderDelete = GfxApiNVG__renderDelete;
params.userPtr = gfxApiCtxt;
params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0;
ctx = nvgCreateInternal(&params);
if (!ctx) goto error;
return ctx;
error:
// 'gfxApiCtxt' is freed by nvgDeleteInternal.
if (ctx) nvgDeleteInternal(ctx);
return NULL;
}
void nvgDeleteGfxApi(struct NVGcontext* ctx)
{
nvgDeleteInternal(ctx);
}
void nvGfxApiBeginFrame(NVGcontext* ctx)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)nvgInternalParams(ctx)->userPtr;
ctxt->VSconstants->resetDynamicOffset();
ctxt->PSconstants->resetDynamicOffset();
}
int nvGfxApiCreateImageFromTexture(NVGcontext* ctx, Basis::GfxApi::Texture* texture, int imageFlags)
{
GfxApiNVGcontext* ctxt = (GfxApiNVGcontext*)nvgInternalParams(ctx)->userPtr;
GfxApiNVGtexture* tex = GfxApiNVG__allocTexture(ctxt);
if (tex == NULL)
{
return 0;
}
tex->width = texture->getDescription().width;
tex->height = texture->getDescription().height;
tex->type = NVG_TEXTURE_RGBA;
tex->flags = imageFlags;
tex->tex = texture;
tex->tex->addReference();
return tex->id;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment