diff --git a/Engine/source/gfx/gFont.cpp b/Engine/source/gfx/gFont.cpp index 05a3f55ac9..d2428eae9a 100644 --- a/Engine/source/gfx/gFont.cpp +++ b/Engine/source/gfx/gFont.cpp @@ -137,7 +137,7 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach } // Otherwise attempt to have the platform generate a new font - PlatformFont *platFont = createPlatformFont(faceName, size, charset); + PlatformFont *platFont = createPlatformFont(faceName, size*4, charset); if (platFont == NULL) { @@ -176,11 +176,10 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach font->mFaceName = faceName; font->mSize = size; font->mCharSet = charset; - - font->mHeight = platFont->getFontHeight(); - font->mBaseline = platFont->getFontBaseLine(); - font->mAscent = platFont->getFontBaseLine(); - font->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine(); + font->mHeight = (platFont->getFontHeight() * 0.25); + font->mAscent = (platFont->getFontBaseLine() * 0.25); + font->mBaseline = (platFont->getFontBaseLine() * 0.25); + font->mDescent = (font->mHeight - font->mBaseline); // Flag it to save when we exit font->mNeedSave = true; @@ -195,13 +194,11 @@ Resource GFont::create(const String &faceName, U32 size, const char *cach GFont::GFont() { - VECTOR_SET_ASSOCIATION(mCharInfoList); VECTOR_SET_ASSOCIATION(mTextureSheets); - std::fill_n(mRemapTable, Font_Table_MAX,-1); - - mCurX = mCurY = mCurSheet = -1; - + mCurX = mCurY = 0; + mCurSheet = -1; + mMaxRowHeight = 0; mPlatformFont = NULL; mSize = 0; mCharSet = 0; @@ -212,6 +209,7 @@ GFont::GFont() mNeedSave = false; mMutex = Mutex::createMutex(); + mCharMap.clear(); } GFont::~GFont() @@ -229,14 +227,10 @@ GFont::~GFont() stream.close(); } - S32 i; + mCharMap.clear(); - for(i = 0;i < mCharInfoList.size();i++) - { - SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData); - } - - for(i=0; imapEnd) mapEnd = i; - } + U32 charCode = entry.key; // The key (character code) + + mapCount++; // Count this character + + // Update the minimum and maximum character codes + if (charCode < mapBegin) mapBegin = charCode; + if (charCode > mapEnd) mapEnd = charCode; } + // Let's write out all the info we can on this font. Con::printf(" '%s' %dpt", mFaceName.c_str(), mSize); Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount); @@ -276,18 +272,25 @@ bool GFont::loadCharInfo(const UTF16 ch) { PROFILE_SCOPE(GFont_loadCharInfo); - if(mRemapTable[ch] != -1) - return true; // Not really an error + auto it = mCharMap.find(ch); + if (it != mCharMap.end()) + return true; - if(mPlatformFont && mPlatformFont->isValidChar(ch)) + if (mPlatformFont && mPlatformFont->isValidChar(ch)) { - Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes. - PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch); + Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes. + PlatformFont::CharInfo& ci = mPlatformFont->getCharInfo(ch); + if (ch == dT(' ')) + { + ci.yOrigin *= 0.25; + ci.xOrigin *= 0.25; + ci.xIncrement = (ci.xIncrement * 0.25); + } + if(ci.bitmapData) addBitmap(ci); - mCharInfoList.push_back(ci); - mRemapTable[ch] = mCharInfoList.size() - 1; + mCharMap[ch] = ci; mNeedSave = true; @@ -298,45 +301,130 @@ bool GFont::loadCharInfo(const UTF16 ch) return false; } -void GFont::addBitmap(PlatformFont::CharInfo &charInfo) +void GFont::generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, S32 sdfWidth, S32 sdfHeight, const F32 spreadFactor) { - U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/ - U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + for (S32 y = 0; y < sdfHeight; ++y) + { + for (S32 x = 0; x < sdfWidth; ++x) + { + // Map SDF coordinates to original bitmap space + F32 scaledX = x * (F32)width / sdfWidth; + F32 scaledY = y * (F32)height / sdfHeight; - // These are here for postmortem debugging. - bool routeA = false, routeB = false; + F32 minDistInsideSquared = F32_MAX; // Closest distance to an "inside" pixel + F32 minDistOutsideSquared = F32_MAX; // Closest distance to an "outside" pixel - if(mCurSheet == -1 || nextCurY >= TextureSheetSize) - { - routeA = true; - addSheet(); + // Iterate over the entire bitmap (can optimize to a smaller region) + for (S32 by = 0; by < height; ++by) + { + for (S32 bx = 0; bx < width; ++bx) + { + bool isInside = bitmap[by * width + bx] > 0; + F32 dx = scaledX - bx; + F32 dy = scaledY - by; + F32 distSquared = dx * dx + dy * dy; + + // Update the minimum distances for inside and outside + if (isInside) + minDistInsideSquared = mMin(minDistInsideSquared, distSquared); + else + minDistOutsideSquared = mMin(minDistOutsideSquared, distSquared); + } + } - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + // Compute the signed distance + F32 minDistInside = mSqrt(minDistInsideSquared); + F32 minDistOutside = mSqrt(minDistOutsideSquared); + F32 signedDist = minDistOutside - minDistInside; + + // Normalize the signed distance + F32 normalizedDist = 0.5f + (signedDist / spreadFactor) * 0.5f; + + // Clamp and scale to [0, 255] + normalizedDist = mClampF(normalizedDist, 0.0f, 1.0f); + sdfBitmap[y * sdfWidth + x] = (U8)(255 * normalizedDist); + } } +} + +void GFont::padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding) +{ + // Initialize padded bitmap with zeros + dMemset(padded, 0, padWidth * padHeight); - if( nextCurX >= TextureSheetSize) + // Copy the original bitmap into the center of the padded bitmap + for (S32 y = 0; y < origHeight; ++y) { - routeB = true; - mCurX = 0; - mCurY = nextCurY; + for (S32 x = 0; x < origWidth; ++x) + { + S32 srcIndex = y * origWidth + x; + S32 dstIndex = (y + padding) * padWidth + (x + padding); + padded[dstIndex] = original[srcIndex]; + } + } +} + +void GFont::addBitmap(PlatformFont::CharInfo &charInfo) +{ + const S32 padding = 8; + + // Dimensions for the padded bitmap and SDF + S32 paddedWidth = charInfo.width + (2 * (padding * 4)); + S32 paddedHeight = charInfo.height + (2 * (padding * 4)); + + // Allocate buffers + FrameTemp paddedBitmap(paddedWidth * paddedHeight); + padGlyphBitmap(charInfo.bitmapData, charInfo.width, charInfo.height, paddedBitmap, paddedWidth, paddedHeight, padding*4); + + S32 sdfWidth = paddedWidth * 0.25; + S32 sdfHeight = paddedHeight * 0.25; + + // Allocate buffer for SDF bitmap + FrameTemp sdfBitmap((sdfWidth * sdfHeight)); + + // Generate the SDF + F32 sdfSpread = 1.0f / (4.0f + (static_cast(paddedWidth / paddedHeight))); + sdfSpread = mMax(paddedWidth, paddedHeight) * sdfSpread; + generateSDF(paddedBitmap, paddedWidth, paddedHeight, sdfBitmap, sdfWidth, sdfHeight, sdfSpread); + + U32 nextCurX = U32(mCurX + sdfWidth); /*7) & ~0x3;*/ + U32 nextCurY = U32(mCurY + sdfHeight); // + 7) & ~0x3; - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + if (mCurSheet == -1) + { + addSheet(); } - // Check the Y once more - sometimes we advance to a new row and run off - // the end. - if(nextCurY >= TextureSheetSize) + // Check if the current texture sheet is full and if we need to add a new one + if (nextCurY >= TextureSheetSize) { - routeA = true; + // When nextCurY exceeds sheet height, add a new sheet addSheet(); + nextCurX = mCurX + sdfWidth; + nextCurY = mCurY + sdfHeight; + } + + // If the X position exceeds the sheet width, move to the next row + if (nextCurX >= TextureSheetSize) + { + // Move to the next row, reset mCurX and adjust mCurY + mCurX = 0; // Reset to the start of the row + + // Move down by the height of the tallest character in the current row + mCurY += mMaxRowHeight + padding; // Add the row height and padding - // Recalc our nexts. - nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; - nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + // Ensure the Y position doesn't exceed the texture sheet height + if (mCurY >= TextureSheetSize) + { + // If we've exceeded the texture sheet height, add a new sheet + addSheet(); + mCurX = 0; + mCurY = 0; // Reset to the top of the new sheet + } + + // Recalculate the position for the current character + nextCurX = mCurX + sdfWidth; + nextCurY = mCurY + sdfHeight; } charInfo.bitmapIndex = mCurSheet; @@ -345,14 +433,22 @@ void GFont::addBitmap(PlatformFont::CharInfo &charInfo) mCurX = nextCurX; - S32 x, y; GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap(); AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!"); + S32 x, y; + for(y = 0;y < sdfHeight;y++) + for(x = 0;x < sdfWidth;x++) + *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = sdfBitmap[y * sdfWidth + x]; - for(y = 0;y < charInfo.height;y++) - for(x = 0;x < charInfo.width;x++) - *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x]; + // update our width and height. + charInfo.width = sdfWidth; + charInfo.height = sdfHeight; + charInfo.yOrigin = ((F32)(charInfo.yOrigin) * 0.25) + padding; + charInfo.xOrigin = ((F32)(charInfo.xOrigin) * 0.25) - padding; + charInfo.xIncrement = ((F32)(charInfo.xIncrement) * 0.25) + 2; + + mMaxRowHeight = mMax(mMaxRowHeight, sdfHeight); mTextureSheets[mCurSheet].refresh(); } @@ -369,8 +465,6 @@ void GFont::addSheet() mTextureSheets.increment(); mTextureSheets.last() = handle; - mCurX = 0; - mCurY = 0; mCurSheet = mTextureSheets.size() - 1; } @@ -382,12 +476,11 @@ const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex) AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!"); - if(mRemapTable[in_charIndex] == -1) + auto it = mCharMap.find(in_charIndex); + if (it == mCharMap.end()) loadCharInfo(in_charIndex); - - AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character"); - return mCharInfoList[mRemapTable[in_charIndex]]; + return mCharMap[in_charIndex]; } const PlatformFont::CharInfo &GFont::getDefaultCharInfo() @@ -657,54 +750,52 @@ void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector &startLineOff bool GFont::read(Stream& io_rStream) { - // Handle versioning - U32 version; - io_rStream.read(&version); - if(version != csm_fileVersion) - return false; - - char buf[256]; - io_rStream.readString(buf); - mFaceName = buf; - - io_rStream.read(&mSize); - io_rStream.read(&mCharSet); - - io_rStream.read(&mHeight); - io_rStream.read(&mBaseline); - io_rStream.read(&mAscent); - io_rStream.read(&mDescent); - - U32 size = 0; - io_rStream.read(&size); - mCharInfoList.setSize(size); - U32 i; - for(i = 0; i < size; i++) - { - PlatformFont::CharInfo *ci = &mCharInfoList[i]; - io_rStream.read(&ci->bitmapIndex); - io_rStream.read(&ci->xOffset); - io_rStream.read(&ci->yOffset); - io_rStream.read(&ci->width); - io_rStream.read(&ci->height); - io_rStream.read(&ci->xOrigin); - io_rStream.read(&ci->yOrigin); - io_rStream.read(&ci->xIncrement); - ci->bitmapData = NULL; + // Handle versioning + U32 version; + io_rStream.read(&version); + if(version != csm_fileVersion) + return false; + + char buf[256]; + io_rStream.readString(buf); + mFaceName = buf; + + io_rStream.read(&mSize); + io_rStream.read(&mCharSet); + + io_rStream.read(&mHeight); + io_rStream.read(&mBaseline); + io_rStream.read(&mAscent); + io_rStream.read(&mDescent); + + U32 size = 0; + io_rStream.read(&size); + for(U32 i = 0; i < size; i++) + { + U32 charCode; + io_rStream.read(&charCode); + + PlatformFont::CharInfo ci; + io_rStream.read(&ci.bitmapIndex); + io_rStream.read(&ci.xOffset); + io_rStream.read(&ci.yOffset); + io_rStream.read(&ci.width); + io_rStream.read(&ci.height); + io_rStream.read(&ci.xOrigin); + io_rStream.read(&ci.yOrigin); + io_rStream.read(&ci.xIncrement); + ci.bitmapData = NULL; + + // Insert the character information into the map + mCharMap[charCode] = ci; // Insert the key-value pair into the map } U32 numSheets = 0; io_rStream.read(&numSheets); - for(i = 0; i < numSheets; i++) + for(U32 i = 0; i < numSheets; i++) { GBitmap *bmp = new GBitmap; - /*String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); - if(!bmp->readBitmap("png", path)) - { - delete bmp; - return false; - }*/ U32 len; io_rStream.read(&len); @@ -724,32 +815,6 @@ bool GFont::read(Stream& io_rStream) io_rStream.read(&mCurY); io_rStream.read(&mCurSheet); - // Read the remap table. - U32 minGlyph, maxGlyph; - io_rStream.read(&minGlyph); - io_rStream.read(&maxGlyph); - - if(maxGlyph >= minGlyph) - { - // Length of buffer.. - U32 buffLen; - io_rStream.read(&buffLen); - - // Read the buffer. - FrameTemp inBuff(buffLen); - io_rStream.read(buffLen, inBuff); - - // Decompress. - uLongf destLen = (static_cast(maxGlyph) - minGlyph + 1) * sizeof(S32); - uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen); - - AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!"); - - // Make sure we've got the right endianness. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); - } - return (io_rStream.getStatus() == Stream::Ok); } @@ -768,27 +833,34 @@ bool GFont::write(Stream& stream) stream.write(mAscent); stream.write(mDescent); - // Write char info list - stream.write(U32(mCharInfoList.size())); - U32 i; - for(i = 0; i < mCharInfoList.size(); i++) + // Write char info count + stream.write(U32(mCharMap.size())); + + // Write each character's info (iterate through the unordered_map) + for (const auto& entry : mCharMap) { - const PlatformFont::CharInfo *ci = &mCharInfoList[i]; - stream.write(ci->bitmapIndex); - stream.write(ci->xOffset); - stream.write(ci->yOffset); - stream.write(ci->width); - stream.write(ci->height); - stream.write(ci->xOrigin); - stream.write(ci->yOrigin); - stream.write(ci->xIncrement); - } + U32 charCode = entry.key; // Character code (U32) + stream.write(charCode); + const PlatformFont::CharInfo& ci = entry.value; // CharInfo associated with the character + + // Write each part of the CharInfo structure to the stream + stream.write(ci.bitmapIndex); + stream.write(ci.xOffset); + stream.write(ci.yOffset); + stream.write(ci.width); + stream.write(ci.height); + stream.write(ci.xOrigin); + stream.write(ci.yOrigin); + stream.write(ci.xIncrement); + } stream.write(mTextureSheets.size()); - for (i = 0; i < mTextureSheets.size(); i++) + for (U32 i = 0; i < mTextureSheets.size(); i++) { - /*String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); - mTextureSheets[i].getBitmap()->writeBitmap("png", path);*/ + // Debugging write out to images. + String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet)); + mTextureSheets[i].getBitmap()->writeBitmap("png", path); + mTextureSheets[i].getBitmap()->writeBitmapStream("png", stream); } @@ -797,45 +869,6 @@ bool GFont::write(Stream& stream) stream.write(mCurY); stream.write(mCurSheet); - // Get the min/max we have values for, and only write that range out. - S32 minGlyph = S32_MAX, maxGlyph = 0; - - for(i = 0; i < 65536; i++) - { - if(mRemapTable[i] != -1) - { - if(i>maxGlyph) maxGlyph = i; - if(i= minGlyph) - { - // Put everything big endian, to be consistent. Do this inplace. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertHostToBEndian(mRemapTable[i]); - - { - // Compress. - const U32 buffSize = 128 * 1024; - FrameTemp outBuff(buffSize); - uLongf destLen = buffSize * sizeof(S32); - compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (static_cast(maxGlyph) - minGlyph + 1) * sizeof(S32), 9); - - // Write out. - stream.write((U32)destLen); - stream.write(destLen, outBuff); - } - - // Put us back to normal. - for(i = minGlyph; i <= maxGlyph; i++) - mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); - } - return (stream.getStatus() == Stream::Ok); } @@ -847,11 +880,11 @@ void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning) S32 heightMin=0, heightMax=0; - for(S32 i=0; i glyphList; - glyphList.reserve(mCharInfoList.size()); + glyphList.reserve(mCharMap.size()); U32 curWidth = 0; - for(S32 i=0; igetFormat()); - lastGlyphMap.charId = i; + lastGlyphMap.bitmap = new GBitmap(entry.value.width + kerning + 2 * padding, entry.value.height + 2 * padding, false, strip->getFormat()); + lastGlyphMap.charId = entry.key; // Copy the rect. - RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight()); + RectI ri(curWidth, getBaseline() - entry.value.yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight()); Point2I outRi(0,0); lastGlyphMap.bitmap->copyRect(strip, ri, outRi); // Update glyph attributes. - mCharInfoList[i].width = lastGlyphMap.bitmap->getWidth(); - mCharInfoList[i].height = lastGlyphMap.bitmap->getHeight(); - mCharInfoList[i].xOffset -= kerning + padding; - mCharInfoList[i].xIncrement += kerning; - mCharInfoList[i].yOffset -= padding; + entry.value.width = lastGlyphMap.bitmap->getWidth(); + entry.value.height = lastGlyphMap.bitmap->getHeight(); + entry.value.xOffset -= kerning + padding; + entry.value.xIncrement += kerning; + entry.value.yOffset -= padding; // Advance. curWidth += ri.extent.x; @@ -961,7 +994,7 @@ void GFont::importStrip(const char *fileName, U32 padding, U32 kerning) S32 maxHeight = 0; for(U32 i = 0; i < glyphList.size(); i++) { - PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId]; + PlatformFont::CharInfo *ci = &mCharMap[glyphList[i].charId]; if(ci->height > maxHeight) maxHeight = ci->height; @@ -1021,7 +1054,7 @@ void GFont::importStrip(const char *fileName, U32 padding, U32 kerning) for(S32 i=0; ibitmapIndex; mTextureSheets[bi]->getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->getWidth(),glyphList[i].bitmap->getHeight()), Point2I(ci->xOffset, ci->yOffset)); } diff --git a/Engine/source/gfx/gFont.h b/Engine/source/gfx/gFont.h index 2f5274af24..8e8cf0cef6 100644 --- a/Engine/source/gfx/gFont.h +++ b/Engine/source/gfx/gFont.h @@ -39,18 +39,21 @@ #ifndef _GFXTEXTUREHANDLE_H_ #include "gfx/gfxTextureHandle.h" #endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif GFX_DeclareTextureProfile(GFXFontTextureProfile); -#define Font_Table_MAX 65536 class GFont { public: + typedef Map typeCharMap; enum Constants { TabWidthInSpaces = 3, - TextureSheetSize = 256, + TextureSheetSize = 512, }; public: @@ -128,10 +131,10 @@ class GFont protected: bool loadCharInfo(const UTF16 ch); + void generateSDF(const U8* bitmap, S32 width, S32 height, U8* sdfBitmap, S32 sdfWidth, S32 sdfHeight, const F32 spreadFactor); + void padGlyphBitmap(const U8* original, S32 origWidth, S32 origHeight, U8* padded, S32 padWidth, S32 padHeight, S32 padding); void addBitmap(PlatformFont::CharInfo &charInfo); void addSheet(void); - void assignSheet(S32 sheetNum, GBitmap *bmp); - void *mMutex; private: @@ -143,6 +146,7 @@ class GFont S32 mCurX; S32 mCurY; S32 mCurSheet; + S32 mMaxRowHeight; bool mNeedSave; Torque::Path mGFTFile; @@ -155,12 +159,8 @@ class GFont U32 mAscent; U32 mDescent; - /// List of character info structures, must be accessed through the - /// getCharInfo(U32) function to account for remapping. - Vector mCharInfoList; - - /// Index remapping - S32 mRemapTable[Font_Table_MAX]; + // Cache charinfo into a map. + typeCharMap mCharMap; }; inline U32 GFont::getCharXIncrement(const UTF16 in_charIndex) @@ -183,7 +183,8 @@ inline U32 GFont::getCharHeight(const UTF16 in_charIndex) inline bool GFont::isValidChar(const UTF16 in_charIndex) const { - if(mRemapTable[in_charIndex] != -1) + auto it = mCharMap.find(in_charIndex); + if (it != mCharMap.end()) return true; if(mPlatformFont) diff --git a/Engine/source/gfx/gfxFontRenderBatcher.cpp b/Engine/source/gfx/gfxFontRenderBatcher.cpp index 4fce36b4e1..1143bf6b6b 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.cpp +++ b/Engine/source/gfx/gfxFontRenderBatcher.cpp @@ -22,6 +22,8 @@ #include "gfx/gfxFontRenderBatcher.h" #include "gfx/gFont.h" +#include "materials/shaderData.h" +#include "gfxDrawUtil.h" FontRenderBatcher::FontRenderBatcher() : mStorage(8096) { @@ -48,6 +50,17 @@ FontRenderBatcher::FontRenderBatcher() : mStorage(8096) f.setColorWrites(true, true, true, false); // NOTE: comment this out if alpha write is needed mFontSB = GFX->createStateBlock(f); } + + // Find ShaderData + ShaderData* shaderData; + mFontShader = Sim::findObject("SDFFontRenderingGUI", shaderData) ? shaderData->getShader() : NULL; + if (!mFontShader) + { + Con::errorf("FontRenderBatcher - could not find Font shader"); + } + // Create ShaderConstBuffer and Handles + mFontShaderConsts = mFontShader->allocConstBuffer(); + } void FontRenderBatcher::render( F32 rot, const Point2F &offset ) @@ -81,7 +94,6 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) mSheets[i]->startVertex = currentPt; const GFXTextureObject *tex = mFont->getTextureHandle(i); - for( S32 j = 0; j < mSheets[i]->numChars; j++ ) { // Get some general info to proceed with... @@ -172,8 +184,11 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) AssertFatal(currentPt <= mLength * 6, "FontRenderBatcher::render - too many verts for length of string!"); GFX->setVertexBuffer(verts); - GFX->setupGenericShaders( GFXDevice::GSAddColorTexture ); - + GFX->setShader(mFontShader); + GFX->setShaderConstBuffer(mFontShaderConsts); + MatrixF tempMatrix = GFX->getProjectionMatrix() * GFX->getViewMatrix() * GFX->getWorldMatrix(); + mFontShaderConsts->set(mFontShader->getShaderConstHandle("$modelView"), tempMatrix, GFXSCT_Float4x4); + // Now do an optimal render! for( S32 i = 0; i < mSheets.size(); i++ ) { @@ -182,10 +197,39 @@ void FontRenderBatcher::render( F32 rot, const Point2F &offset ) if(!mSheets[i]->numChars ) continue; + Point2I texDim = mFont->getTextureHandle(i).getWidthHeight(); + mFontShaderConsts->setSafe(mFontShader->getShaderConstHandle("$texDim"), Point2F(texDim.x, texDim.y)); GFX->setTexture( 0, mFont->getTextureHandle(i) ); GFX->drawPrimitive(GFXTriangleList, mSheets[i]->startVertex, mSheets[i]->numChars * 2); } + +#if 0 + for (S32 i = 0; i < mSheets.size(); i++) + { + // Do some early outs... + if (!mSheets[i]) + continue; + + if (!mSheets[i]->numChars) + continue; + for (S32 j = 0; j < mSheets[i]->numChars; j++) + { + // Get some general info to proceed with... + const CharMarker& m = mSheets[i]->charIndex[j]; + const PlatformFont::CharInfo& ci = mFont->getCharInfo(m.c); + F32 yStart = offset.y + mFont->getBaseline() - ci.yOrigin * TEXT_MAG; + F32 xStart = offset.x + m.x + ci.xOrigin; + + // draw baseline + GFX->getDrawUtil()->drawLine(xStart, yStart + ci.yOrigin, xStart + ci.width, yStart + ci.yOrigin, ColorI::GREEN); + // draw origin line + GFX->getDrawUtil()->drawLine(xStart, yStart, xStart + ci.width, yStart, ColorI::RED); + // draw bounds + GFX->getDrawUtil()->drawRect(Point2F(xStart, yStart), Point2F(xStart + ci.width, yStart + ci.height), ColorI::BLUE); + } + } +#endif } void FontRenderBatcher::queueChar( UTF16 c, S32 ¤tX, GFXVertexColor ¤tColor ) diff --git a/Engine/source/gfx/gfxFontRenderBatcher.h b/Engine/source/gfx/gfxFontRenderBatcher.h index 25f707db49..85c3f8a0d1 100644 --- a/Engine/source/gfx/gfxFontRenderBatcher.h +++ b/Engine/source/gfx/gfxFontRenderBatcher.h @@ -54,6 +54,10 @@ class FontRenderBatcher SheetMarker &getSheetMarker(U32 sheetID); + // font shader. + GFXShaderRef mFontShader; + GFXShaderConstBufferRef mFontShaderConsts; + public: FontRenderBatcher(); @@ -64,4 +68,4 @@ class FontRenderBatcher void render(F32 rot, const Point2F &offset ); }; -#endif \ No newline at end of file +#endif diff --git a/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript b/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript index 239c76a80c..bb08bb9c4e 100644 --- a/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript +++ b/Templates/BaseGame/game/core/rendering/scripts/gfxData/shaders.tscript @@ -154,6 +154,17 @@ singleton ShaderData( CubemapSaveShader ) //----------------------------------------------------------------------------- // GUI shaders //----------------------------------------------------------------------------- +singleton ShaderData( SDFFontRenderingGUI ) +{ + DXVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/addColorTextureV.hlsl"; + DXPixelShaderFile = $Core::CommonShaderPath @ "/fixedFunction/sdfFontRenderP.hlsl"; + + OGLVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/gl/colorV.glsl"; + OGLPixelShaderFile = $Core::CommonShaderPath @ "/fixedFunction/gl/roundedRectangleP.glsl"; + + pixVersion = 3.0; +}; + singleton ShaderData( RoundedRectangleGUI ) { DXVertexShaderFile = $Core::CommonShaderPath @ "/fixedFunction/colorV.hlsl"; diff --git a/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl new file mode 100644 index 0000000000..7fa26cc4e4 --- /dev/null +++ b/Templates/BaseGame/game/core/rendering/shaders/fixedFunction/sdfFontRenderP.hlsl @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../shaderModel.hlsl" + +struct Conn +{ + float4 HPOS : TORQUE_POSITION; + float4 color : COLOR; + float2 texCoord : TEXCOORD0; +}; + +TORQUE_UNIFORM_SAMPLER2D(diffuseMap, 0); +uniform float2 texDim; + +// Customizable parameters +static const float sdfThreshold = 0.55; // Center threshold for the edge of the glyph + // Min: 0.4 , Max: 0.6 +static const float slopeMultiplier = 1.0; // Multiplies the slope of the transition + // Min: 0.5 (softer edges), Max: 4.0 (sharper edges) +static const float inBias = -0.05; // Shifts the threshold inward + // Min: -0.05 (slightly thinner), Max: 0.0 (default, no inward shift) +static const float outBias = 0.01; // Shifts the threshold outward + // Min: 0.0 (default, no outward shift), Max: 0.05 (slightly thicker) +static const float smoothness = 0.05; // Controls the falloff region + // Min: 0.005 (very sharp edges), Max: 0.05 (soft, anti-aliased edges) +static const int supersample = 1; // Supersampling factor + // Min: 1 (no supersampling), Max: 8 (high-quality, computationally expensive) +static const float alphaThreshold = 0.2f; // Sets the alpha threshold + +float4 main( Conn IN ) : TORQUE_TARGET0 +{ + float distance = TORQUE_TEX2D(diffuseMap, IN.texCoord).a; + + float adjustedThreshold = sdfThreshold + inBias - outBias; + + float edge = (distance - adjustedThreshold) * slopeMultiplier; + + // Smooth edges using the distance field + float alpha = smoothstep(-smoothness, smoothness, edge); + + if(supersample > 1) + { + float2 texelSize = float2(1.0f / texDim.x, 1.0f / texDim.y); + float2 offsets[4] = { + float2(-0.5, -0.5) * texelSize, + float2(0.5, -0.5) * texelSize, + float2(-0.5, 0.5) * texelSize, + float2(0.5, 0.5) * texelSize + }; + + float totalAlpha = 0.0; + for(uint i = 0; i < (uint)supersample; ++i) + { + float2 offsetCoord = IN.texCoord + offsets[i % 4]; + float sampleDist = TORQUE_TEX2D(diffuseMap, offsetCoord).a; + float sampleEdge = (sampleDist - adjustedThreshold) * slopeMultiplier; + totalAlpha += smoothstep(-smoothness, smoothness, sampleEdge); + } + alpha = totalAlpha / float(supersample); + } + + // Alpha lower than min discard. + if(alpha < alphaThreshold) + discard; + + // Debug: Uncomment to visualize alpha field + //return float4(alpha, alpha, alpha, 1.0); + + return float4(IN.color.rgb, alpha); +} \ No newline at end of file