//----------------------------------------------------------------------------- // Copyright (c) 2023 tgemit contributors. // See AUTHORS file and git repository for contributor information. // // SPDX-License-Identifier: MIT //----------------------------------------------------------------------------- #pragma once #include "dgl/gFont.h" /// Helper class which turns text into a mesh for rendering class DGLFontMeshBuilder { protected: enum { SheetChunkSize = 4096, COLOR_INDEX_BITMAP_MODULATION=0, COLOR_INDEX_ANCHOR=1, COLOR_INDEX_COLOR_TABLE=2, }; struct DrawChar { Point2F pos; // NOTE: non-scaled U16 charCode; U16 colorIndex; }; struct DrawSheet { U16 bitmapIndex; U16 charsUsed; DrawChar firstChar; DrawChar* getChars() { return &firstChar; } }; /// Main font we are drawing with GFont* mFont; /// Allocator for DrawSheet DataChunker mSheetChunker; /// Sheets to output to Vector mDrawSheets; /// Sheets we have outputted Vector mOutputSheets; /// Color State /// { ColorF mBitmapModulation; ColorF mAnchorColor; const ColorI* mColorTable; U32 mColorTableSize; U32 mColorIndex; U32 mSavedColorIndex; /// } /// Offset we start at Point2F mDrawOrigin; /// Where we are Point2F mCurrentPoint; MatrixF mBaseTransform; public: DGLFontMeshBuilder() : mFont(NULL), mBitmapModulation(1.0f,1.0f,1.0f,1.0f), mAnchorColor(1.0f,1.0f,1.0f,1.0f), mColorTable(NULL), mColorTableSize(0), mColorIndex(0), mSavedColorIndex(0), mDrawOrigin(0,0), mCurrentPoint(0,0), mBaseTransform(1) { } RectI getDrawBounds() { Point2F minP = Point2F(F32_MAX, F32_MAX); Point2F maxP = Point2F(F32_MIN, F32_MIN); for (DrawSheet* outSheet : mOutputSheets) { for (S32 i=0; icharsUsed; i += std::max(outSheet->charsUsed-1, 1)) { auto info = outSheet->getChars()[i]; const PlatformFont::CharInfo &ci = mFont->getCharInfo(info.charCode); Point2F ep1 = info.pos; Point2F ep2 = info.pos + Point2F(ci.width, ci.height); minP.x = std::min(minP.x, ep1.x); minP.y = std::min(minP.y, ep1.y); maxP.x = std::max(maxP.x, ep1.x); maxP.y = std::max(maxP.y, ep1.y); minP.x = std::min(minP.x, ep2.x); minP.y = std::min(minP.y, ep2.y); maxP.x = std::max(maxP.x, ep2.x); maxP.y = std::max(maxP.y, ep2.y); } } return RectI(mDrawOrigin.x + minP.x, mDrawOrigin.y + minP.y, maxP.x - minP.x, maxP.y - minP.y); } inline void setLastDrawPoint(const Point2F point) { mCurrentPoint = point; } inline Point2F getLastDrawPoint() const { return mCurrentPoint; } inline bool getDrawSheet(DrawSheet** outSheet, U32 bitmapIndex) { if (bitmapIndex >= mDrawSheets.size()) { *outSheet = NULL; return false; } DrawSheet* sheet = mDrawSheets[bitmapIndex]; if (!sheet) { sheet = allocSheet(bitmapIndex); mDrawSheets[bitmapIndex] = sheet; } *outSheet = sheet; return true; } /// Returns color for index inline DGLPackedPointU16 getColor(U32 index) { switch (index) { case COLOR_INDEX_BITMAP_MODULATION: return mBitmapModulation; break; case COLOR_INDEX_ANCHOR: return mAnchorColor; break; default: U32 realIndex = index-COLOR_INDEX_COLOR_TABLE; return mColorTable ? mColorTable[realIndex] : ColorI(255,255,255,255); break; } } /// Allocate a sheet to draw DrawSheet* allocSheet(U16 bitmapIndex) { const U32 allocSize = sizeof(DrawSheet) + (sizeof(DrawChar) * SheetChunkSize); DrawSheet* sheet = (DrawSheet*)mSheetChunker.alloc(allocSize); sheet->bitmapIndex = bitmapIndex; memset(sheet, '\0', allocSize); mOutputSheets.push_back(sheet); return sheet; } /// Begin drawing a line at origin void begin(GFont* font, Point2I origin, F32 rot=0.0f, const ColorI* colorTable=NULL, U32 maxColorIndex=0) { clear(); mFont = font; dglGetTextAnchorColor(&mAnchorColor); dglGetBitmapModulation(&mBitmapModulation); mColorIndex = COLOR_INDEX_BITMAP_MODULATION; mSavedColorIndex = COLOR_INDEX_BITMAP_MODULATION; mColorTable = colorTable; mColorTableSize = maxColorIndex; mDrawSheets.setSize(font->mTextureSheets.size()); for (auto itr = mDrawSheets.begin(), itrEnd = mDrawSheets.end(); itr != itrEnd; itr++) { *itr = NULL; } mDrawOrigin = Point2F(origin.x, origin.y); mCurrentPoint = Point2F(0,0); // relative to mDrawOrigin mBaseTransform = MatrixF( EulerF( 0.0, 0.0, mDegToRad( rot ) ) ); } /// Add text to batch U32 addText(U32 numChars, const UTF16* chars) { const F32 invMetricScale = mFont->getInvMetricScale(); const F32 invTexScale = 1.0f / mFont->getTextureScale(); Point2F pt = mCurrentPoint; DrawSheet* currentSheet = NULL; U32 lastBitmapIndex = UINT_MAX; U32 i = 0; for (i=0; i= 1 && c <= 7) || (c >= 11 && c <= 12) || (c == 14)) { // Color code if (mColorTable) { static U8 remap[15] = { 0x0, // 0 special null terminator 0x0, // 1 ascii start-of-heading?? 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x0, // 8 special backspace 0x0, // 9 special tab 0x0, // a special \n 0x7, 0x8, 0x0, // a special \r 0x9 }; U8 remapped = remap[c]; // Ignore if the color is greater than the specified max index: if ( remapped <= mColorTableSize ) { mColorIndex = COLOR_INDEX_COLOR_TABLE + remapped; } } continue; } // reset color? if ( c == 15 ) { mColorIndex = COLOR_INDEX_ANCHOR; continue; } // push color: if ( c == 16 ) { mSavedColorIndex = mColorIndex; continue; } // pop color: if ( c == 17 ) { mColorIndex = mSavedColorIndex; continue; } // Tab character if ( c == dT('\t') ) { const PlatformFont::CharInfo &ci = mFont->getCharInfo( dT(' ') ); pt.x += ci.xIncrement * GFont::TabWidthInSpaces * invMetricScale; continue; } if( !mFont->isValidChar( c ) ) continue; const PlatformFont::CharInfo &ci = mFont->getCharInfo(c); const F32 realXOrigin = ci.xOrigin * invMetricScale; const F32 realYOrigin = ci.yOrigin * invMetricScale; if (ci.bitmapIndex == -1) { pt.x += (realXOrigin * invTexScale * invMetricScale) + (ci.xIncrement * invMetricScale); continue; } if (ci.bitmapIndex != lastBitmapIndex) { // Grab sheet if (!getDrawSheet(¤tSheet, ci.bitmapIndex)) { lastBitmapIndex = UINT_MAX; continue; } lastBitmapIndex = ci.bitmapIndex; } if (ci.width != 0 && ci.height != 0) { pt.y = mFont->getBaseline() - (realYOrigin * invTexScale); F32 charExtra = realXOrigin * invTexScale; pt.x += charExtra; DrawChar outChar = {}; outChar.pos = pt; outChar.colorIndex = mColorIndex; outChar.charCode = c; currentSheet->getChars()[currentSheet->charsUsed++] = outChar; // End sheet if full if (currentSheet->charsUsed >= SheetChunkSize) { mDrawSheets[ci.bitmapIndex] = NULL; currentSheet = NULL; lastBitmapIndex = UINT_MAX; } pt.x += (ci.xIncrement * invMetricScale) - charExtra; } else { pt.x += ci.xIncrement * invMetricScale; } } mCurrentPoint = pt; return i; } /// Fill in mesh info void fillAllocParams(DGLMeshAllocParams& params) { U32 vertCount = 0; U32 indCount = 0; U32 primCount = 0; for (DrawSheet* outSheet : mOutputSheets) { vertCount += outSheet->charsUsed * 4; indCount += outSheet->charsUsed * 6; primCount++; } params.primType = DGLPrimitiveTriangle; params.vertCode = GuiVert::getFmtCode(); params.numVerts = vertCount; params.numInds = indCount; params.numPrims = primCount; } template void fillAlloc(DGLGuiMeshDataRef &outRef, Y* pipeline) { const F32 invMetricScale = mFont->getInvMetricScale(); const F32 invTexScale = 1.0f / mFont->getTextureScale(); GuiVert* outVerts = outRef.vertPtr; U16* outInds = outRef.indexPtr; DGLPrimitive* outPrims = outRef.primPtr; U16 curInd = 0; for (DrawSheet* outSheet : mOutputSheets) { TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex); if (lastTexture == NULL) continue; DGLPrimitive prim = {}; GuiVert* startVertPtr = outVerts; U16* startIndPtr = outInds; prim.primType = DGLPrimitiveTriangle; prim.startVert = (U32)(outVerts - outRef.vertPtr); prim.startInd = (U32)(outInds - outRef.indexPtr); prim.indexed = 1; curInd = 0; Point3F points[4]; for (U32 cn=0; cn < outSheet->charsUsed; cn++) { DrawChar& dc = outSheet->getChars()[cn]; const PlatformFont::CharInfo &ci = mFont->getCharInfo(dc.charCode); Point2F pt = dc.pos; DGLPackedPointU16 currentColor = getColor(dc.colorIndex); F32 texLeft = F32(ci.xOffset) / F32(lastTexture->getTextureWidth()); F32 texRight = F32(ci.xOffset + ci.width) / F32(lastTexture->getTextureWidth()); F32 texTop = F32(ci.yOffset) / F32(lastTexture->getTextureHeight()); F32 texBottom = F32(ci.yOffset + ci.height) / F32(lastTexture->getTextureHeight()); F32 screenLeft = pt.x; F32 screenRight = pt.x + (ci.width * invTexScale); F32 screenTop = pt.y; F32 screenBottom = pt.y + (ci.height * invTexScale); points[0] = Point3F( screenLeft, screenTop, 0.0); points[1] = Point3F( screenRight, screenTop, 0.0); points[2] = Point3F( screenLeft, screenBottom, 0.0); points[3] = Point3F( screenRight, screenBottom, 0.0); for( int i=0; i<4; i++ ) { mBaseTransform.mulP( points[i] ); points[i] += Point3F(mDrawOrigin.x, mDrawOrigin.y, 0.0f); } // Verts outVerts[0].setValues(points[0], Point2F(texLeft, texTop), currentColor); outVerts[1].setValues(points[1], Point2F(texRight, texTop), currentColor); outVerts[2].setValues(points[2], Point2F(texLeft, texBottom), currentColor); outVerts[3].setValues(points[3], Point2F(texRight, texBottom), currentColor); // Indices outInds[0] = curInd; outInds[1] = curInd+1; outInds[2] = curInd+2; outInds[3] = curInd+2; outInds[4] = curInd+1; outInds[5] = curInd+3; outVerts += 4; curInd += 4; outInds += 6; } prim.numVerts = (U32)(outVerts - startVertPtr); prim.numElements = (U32)(outInds - startIndPtr); *outPrims++ = prim; outInds += prim.numElements; outVerts += prim.numVerts; } } /// Outputs to a pipeline mesh outRef template void endToPipelineMesh(T* pipeline, DGLGuiMeshDataRef& outRef) { DGLMeshAllocParams allocParams = {}; fillAllocParams(allocParams); if (pipeline->allocMesh(allocParams, outRef)) { fillAlloc(outRef, pipeline); } } void clear() { mOutputSheets.clear(); mSheetChunker.freeBlocks(true); mDrawSheets.clear(); } /// Helper function to draw output sheets to the pipeline with the correct textures void drawPipelineMesh(DGLMeshIndex outMeshIndex, DGLPipelineState* pipeline) { U32 primNum = 0; for (DrawSheet* outSheet : mOutputSheets) { TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex); if (lastTexture == NULL) continue; pipeline->quickSetTexture(lastTexture->getGLTextureName(), DGLSamplerStates::GuiText); pipeline->drawMesh(outMeshIndex, primNum++); } } static DGLFontMeshBuilder* getInstance(); };