diff --git a/core/2d/FontAtlas.cpp b/core/2d/FontAtlas.cpp index c78917070b..a64c9e1201 100644 --- a/core/2d/FontAtlas.cpp +++ b/core/2d/FontAtlas.cpp @@ -38,6 +38,11 @@ #include "base/EventDispatcher.h" #include "base/EventType.h" +#include "simdjson/simdjson.h" +#include "zlib.h" +#include "fmt/format.h" +#include "base/ZipUtils.h" + NS_AX_BEGIN const int FontAtlas::CacheTextureWidth = 512; @@ -45,7 +50,101 @@ const int FontAtlas::CacheTextureHeight = 512; const char* FontAtlas::CMD_PURGE_FONTATLAS = "__cc_PURGE_FONTATLAS"; const char* FontAtlas::CMD_RESET_FONTATLAS = "__cc_RESET_FONTATLAS"; -FontAtlas::FontAtlas(Font* theFont) : _font(theFont) +void FontAtlas::loadFontAtlas(std::string_view fontatlasFile, hlookup::string_map& outAtlasMap) +{ + using namespace simdjson; + + struct PaddingString : protected yasio::sbyte_buffer + { + public: + using value_type = yasio::sbyte_buffer::value_type; + size_t size() const { return this->yasio::sbyte_buffer::size(); } + void resize(size_t size) + { + this->yasio::sbyte_buffer::resize_fit(size + SIMDJSON_PADDING); + this->yasio::sbyte_buffer::data()[size] = '\0'; + } + + char* data() { return this->yasio::sbyte_buffer::data(); }; + }; + + try + { + PaddingString strJson; + FileUtils::getInstance()->getContents(fontatlasFile, &strJson); + ondemand::parser parser; + padded_string_view json(strJson.data(), strJson.size()); + ondemand::document settings = parser.iterate(json); + std::string_view type = settings["type"]; + if (type != "fontatlas") + { + ax::print("Load fontatlas %s fail, invalid asset type: ", fontatlasFile.data(), type.data()); + return; + } + + // std::string_view version = settings["version"]; + std::string_view atlasName = settings["atlasName"]; + + auto it = outAtlasMap.find(atlasName); + if (it != outAtlasMap.end()) + { + if (it->second->getReferenceCount() != 1) + { + ax::print("Load fontatlas %s fail, due to exist fontatlas with same key %s and in used", + fontatlasFile.data(), atlasName.data()); + return; + } + else + it->second->release(); + outAtlasMap.erase(it); + } + + std::string_view sourceFont = settings["sourceFont"]; + int faceSize = static_cast(static_cast(settings["faceSize"])); + auto font = FontFreeType::create(sourceFont, faceSize, GlyphCollection::DYNAMIC, ""sv, true, 0.0f); + if (!font) + { + ax::print("Load fontatils %s fail due to create source font %s fail", fontatlasFile.data(), + sourceFont.data()); + return; + } + + int atlasDim[2]; + + auto atliasDim = settings["atlasDim"].get_array(); + int index = 0; + for (auto value : atliasDim) + { + atlasDim[index++] = static_cast(value.get_int64()); + if (index >= 2) + break; + } + + auto fontAtlas = new FontAtlas(font, atlasDim[0], atlasDim[1], AX_CONTENT_SCALE_FACTOR()); + + try + { + fontAtlas->initWithSettings(&settings); + outAtlasMap.emplace(atlasName, fontAtlas); + } + catch (std::exception& ex) + { + fontAtlas->release(); + throw ex; // rethrow + } + } + catch (std::exception& ex) + { + ax::print("Load fontatils %s fail due to exception occured: %s", fontatlasFile.data(), ex.what()); + } +} + +FontAtlas::FontAtlas(Font* theFont) + : FontAtlas(theFont, CacheTextureWidth, CacheTextureHeight, AX_CONTENT_SCALE_FACTOR()) +{} + +FontAtlas::FontAtlas(Font* theFont, int atlasWidth, int atlasHeight, float scaleFactor) + : _font(theFont), _width(atlasWidth), _height(atlasHeight), _scaleFactor(scaleFactor) { _font->retain(); @@ -61,7 +160,7 @@ FontAtlas::FontAtlas(Font* theFont) : _font(theFont) { _strideShift = 1; _pixelFormat = AX_GLES_PROFILE != 200 ? backend::PixelFormat::RG8 : backend::PixelFormat::LA8; - _currentPageDataSize = CacheTextureWidth * CacheTextureHeight << _strideShift; + _currentPageDataSize = _width * _height << _strideShift; _lineHeight += 2 * outlineSize; } @@ -69,7 +168,7 @@ FontAtlas::FontAtlas(Font* theFont) : _font(theFont) { _strideShift = 0; _pixelFormat = AX_GLES_PROFILE != 200 ? backend::PixelFormat::R8 : backend::PixelFormat::A8; - _currentPageDataSize = CacheTextureWidth * CacheTextureHeight; + _currentPageDataSize = _width * _height; } if (_fontFreeType->isDistanceFieldEnabled()) @@ -113,6 +212,54 @@ FontAtlas::~FontAtlas() AX_SAFE_DELETE_ARRAY(_currentPageData); } +void FontAtlas::initWithSettings(void* opaque /*simdjson::ondemand::document*/) +{ + if (!_currentPageData) + _currentPageData = new uint8_t[_currentPageDataSize]; + _currentPage = -1; + + simdjson::ondemand::document& settings = *(simdjson::ondemand::document*)opaque; + + // pages + for (auto page : settings["pages"].get_array()) + { + auto comprData = utils::base64Decode(page); + auto uncomprData = ZipUtils::decompressGZ(std::span{comprData}, _currentPageDataSize); + addNewPageWithData(uncomprData.data(), uncomprData.size()); + } + + _currentPageOrigX = static_cast(settings["pageX"].get_double()); + _currentPageOrigY = static_cast(settings["pageY"].get_double()); + + // letters + FontLetterDefinition tempDef; + tempDef.rotated = false; + tempDef.validDefinition = true; + + std::string strCharCode; + for (auto field : settings["letters"].get_object()) + { + strCharCode = static_cast(field.unescaped_key()); + auto letterInfo = field.value(); + tempDef.U = static_cast(letterInfo["U"].get_double()); + tempDef.V = static_cast(letterInfo["V"].get_double()); + tempDef.xAdvance = static_cast(letterInfo["advance"].get_double()); + tempDef.width = static_cast(letterInfo["width"].get_double()); + tempDef.height = static_cast(letterInfo["height"].get_double()); + tempDef.offsetX = static_cast(letterInfo["offsetX"].get_double()); + tempDef.offsetY = static_cast(letterInfo["offsetY"].get_double()); + tempDef.textureID = letterInfo["page"].get_int64(); + + auto charCode = atoi(strCharCode.c_str()); + + tempDef.U /= _scaleFactor; + tempDef.V /= _scaleFactor; + tempDef.width /= _scaleFactor; + tempDef.height /= _scaleFactor; + _letterDefinitions.emplace(charCode, tempDef); + } +} + void FontAtlas::reset() { releaseTextures(); @@ -222,7 +369,6 @@ bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text) Rect tempRect; FontLetterDefinition tempDef; - auto scaleFactor = AX_CONTENT_SCALE_FACTOR(); auto pixelFormat = _pixelFormat; int startY = (int)_currentPageOrigY; @@ -238,16 +384,17 @@ bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text) tempDef.offsetX = tempRect.origin.x - adjustForDistanceMap - adjustForExtend; tempDef.offsetY = _fontAscender + tempRect.origin.y - adjustForDistanceMap - adjustForExtend; - if (_currentPageOrigX + tempDef.width > CacheTextureWidth) + if (_currentPageOrigX + tempDef.width > _width) { _currentPageOrigY += _currLineHeight; _currLineHeight = 0; _currentPageOrigX = 0; - if (_currentPageOrigY + _lineHeight + _letterPadding + _letterEdgeExtend >= CacheTextureHeight) + if (_currentPageOrigY + _lineHeight + _letterPadding + _letterEdgeExtend >= _height) { updateTextureContent(pixelFormat, startY); startY = 0; + addNewPage(); } } @@ -257,17 +404,18 @@ bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text) _currLineHeight = glyphHeight; } _fontFreeType->renderCharAt(_currentPageData, (int)_currentPageOrigX + adjustForExtend, - (int)_currentPageOrigY + adjustForExtend, bitmap, bitmapWidth, bitmapHeight); + (int)_currentPageOrigY + adjustForExtend, bitmap, bitmapWidth, bitmapHeight, + _width, _height); tempDef.U = _currentPageOrigX; tempDef.V = _currentPageOrigY; tempDef.textureID = _currentPage; _currentPageOrigX += tempDef.width + 1; // take from pixels to points - tempDef.width = tempDef.width / scaleFactor; - tempDef.height = tempDef.height / scaleFactor; - tempDef.U = tempDef.U / scaleFactor; - tempDef.V = tempDef.V / scaleFactor; + tempDef.width = tempDef.width / _scaleFactor; + tempDef.height = tempDef.height / _scaleFactor; + tempDef.U = tempDef.U / _scaleFactor; + tempDef.V = tempDef.V / _scaleFactor; tempDef.rotated = false; } else @@ -297,18 +445,25 @@ bool FontAtlas::prepareLetterDefinitions(const std::u32string& utf32Text) void FontAtlas::updateTextureContent(backend::PixelFormat format, int startY) { - auto data = _currentPageData + (CacheTextureWidth * (int)startY << _strideShift); - _atlasTextures[_currentPage]->updateWithSubData(data, 0, startY, CacheTextureWidth, + auto data = _currentPageData + (_width * (int)startY << _strideShift); + _atlasTextures[_currentPage]->updateWithSubData(data, 0, startY, _width, (int)_currentPageOrigY - startY + _currLineHeight); } void FontAtlas::addNewPage() { - auto texture = new Texture2D(); - memset(_currentPageData, 0, _currentPageDataSize); + addNewPageWithData(_currentPageData, _currentPageDataSize); - texture->initWithData(_currentPageData, _currentPageDataSize, _pixelFormat, CacheTextureWidth, CacheTextureHeight); + _currentPageOrigY = 0; +} + +void FontAtlas::addNewPageWithData(const uint8_t* data, size_t size) +{ + assert(_currentPageDataSize == size); + + auto texture = new Texture2D(); + texture->initWithData(data, _currentPageDataSize, _pixelFormat, _width, _height); if (_antialiasEnabled) texture->setAntiAliasTexParameters(); @@ -317,8 +472,6 @@ void FontAtlas::addNewPage() setTexture(++_currentPage, texture); texture->release(); - - _currentPageOrigY = 0; } void FontAtlas::setTexture(unsigned int slot, Texture2D* texture) diff --git a/core/2d/FontAtlas.h b/core/2d/FontAtlas.h index c7c57e1fce..f91717ac9b 100644 --- a/core/2d/FontAtlas.h +++ b/core/2d/FontAtlas.h @@ -66,10 +66,12 @@ public: static const int CacheTextureHeight; static const char* CMD_PURGE_FONTATLAS; static const char* CMD_RESET_FONTATLAS; + static void loadFontAtlas(std::string_view fontatlasFile, hlookup::string_map& outAtlasMap); /** * @js ctor */ FontAtlas(Font* theFont); + FontAtlas(Font* theFont, int atlasWidth, int atlasHeight, float scaleFactor = 1.0f); /** * @js NA * @lua NA @@ -81,9 +83,13 @@ public: bool prepareLetterDefinitions(const std::u32string& utf16String); + const auto& getLetterDefinitions() const { return _letterDefinitions; } + const std::unordered_map& getTextures() const { return _atlasTextures; } - void addNewPage(); + virtual void addNewPage(); + + void addNewPageWithData(const uint8_t* data, size_t size); void setTexture(unsigned int slot, Texture2D* texture); Texture2D* getTexture(int slot); @@ -118,6 +124,8 @@ public: void setAliasTexParameters(); protected: + void initWithSettings(void* opaque /*simdjson::ondemand::document*/); + void reset(); void reinit(); @@ -137,10 +145,16 @@ protected: std::unordered_map _atlasTextures; std::unordered_map _letterDefinitions; - float _lineHeight = 0.f; + Font* _font = nullptr; FontFreeType* _fontFreeType = nullptr; + int _width = 0; // atlas width + int _height = 0; // atlas height + float _scaleFactor = 1.0f; + + float _lineHeight = 0.f; + // Dynamic GlyphCollection related stuff int _currentPage = -1; backend::PixelFormat _pixelFormat = backend::PixelFormat::NONE; diff --git a/core/2d/FontAtlasCache.cpp b/core/2d/FontAtlasCache.cpp index 99216dbe05..7675a59789 100644 --- a/core/2d/FontAtlasCache.cpp +++ b/core/2d/FontAtlasCache.cpp @@ -33,7 +33,6 @@ #include "2d/FontCharMap.h" #include "2d/Label.h" #include "platform/FileUtils.h" - #include "base/format.h" NS_AX_BEGIN @@ -53,6 +52,11 @@ void FontAtlasCache::purgeCachedData() _atlasMap.clear(); } +void FontAtlasCache::preloadFontAtlas(std::string_view fontatlasFile) +{ + FontAtlas::loadFontAtlas(fontatlasFile, _atlasMap); +} + FontAtlas* FontAtlasCache::getFontAtlasTTF(_ttfConfig* config) { auto& realFontFilename = config->fontFilePath; @@ -67,7 +71,7 @@ FontAtlas* FontAtlasCache::getFontAtlasTTF(_ttfConfig* config) std::string atlasName = config->distanceFieldEnabled - ? fmt::format("df {} {} {}", scaledFaceSize, outlineSize, realFontFilename) + ? fmt::format("df {} {}", scaledFaceSize, realFontFilename) : fmt::format("{} {} {}", scaledFaceSize, outlineSize, realFontFilename); auto it = _atlasMap.find(atlasName); diff --git a/core/2d/FontAtlasCache.h b/core/2d/FontAtlasCache.h index 91a6280233..e2a75e4e35 100644 --- a/core/2d/FontAtlasCache.h +++ b/core/2d/FontAtlasCache.h @@ -41,6 +41,11 @@ struct _ttfConfig; class AX_DLL FontAtlasCache { public: + /** + * @brief preload a SDF fontatlas + * since axmol-2.1.0, must call before creating any Label + */ + static void preloadFontAtlas(std::string_view fontatlasFile); static FontAtlas* getFontAtlasTTF(_ttfConfig* config); static FontAtlas* getFontAtlasFNT(std::string_view fontFileName); diff --git a/core/2d/FontFreeType.cpp b/core/2d/FontFreeType.cpp index 871faf14ba..b0c8e2f692 100644 --- a/core/2d/FontFreeType.cpp +++ b/core/2d/FontFreeType.cpp @@ -578,7 +578,9 @@ void FontFreeType::renderCharAt(unsigned char* dest, int posY, unsigned char* bitmap, int bitmapWidth, - int bitmapHeight) + int bitmapHeight, + int atlasWidth, + int atlasHeight) { const int iX = posX; int iY = posY; @@ -588,7 +590,7 @@ void FontFreeType::renderCharAt(unsigned char* dest, for (int32_t y = 0; y < bitmapHeight; ++y) { int32_t bitmap_y = y * bitmapWidth; - memcpy(dest + (iX + (iY * FontAtlas::CacheTextureWidth)) * 2, bitmap + bitmap_y * 2, bitmapWidth * 2); + memcpy(dest + (iX + (iY * atlasWidth)) * 2, bitmap + bitmap_y * 2, bitmapWidth * 2); ++iY; } delete[] bitmap; @@ -598,7 +600,7 @@ void FontFreeType::renderCharAt(unsigned char* dest, for (int32_t y = 0; y < bitmapHeight; ++y) { int32_t bitmap_y = y * bitmapWidth; - memcpy(dest + (iX + (iY * FontAtlas::CacheTextureWidth)), bitmap + bitmap_y, bitmapWidth); + memcpy(dest + (iX + (iY * atlasWidth)), bitmap + bitmap_y, bitmapWidth); ++iY; } } diff --git a/core/2d/FontFreeType.h b/core/2d/FontFreeType.h index 5ed59e71fa..502796aa1f 100644 --- a/core/2d/FontFreeType.h +++ b/core/2d/FontFreeType.h @@ -110,7 +110,9 @@ public: int posY, unsigned char* bitmap, int bitmapWidth, - int bitmapHeight); + int bitmapHeight, + int atlasWidth, + int atlasHeight); int* getHorizontalKerningForTextUTF32(const std::u32string& text, int& outNumLetters) const override; @@ -123,6 +125,8 @@ public: virtual FontAtlas* newFontAtlas() override; virtual int getFontMaxHeight() const override { return _lineHeight; } + std::string_view getGlyphCollection() const; + static void releaseFont(std::string_view fontName); static FT_Library getFTLibrary(); @@ -146,7 +150,6 @@ private: unsigned char* getGlyphBitmapWithOutline(unsigned int glyphIndex, FT_BBox& bbox); void setGlyphCollection(GlyphCollection glyphs, std::string_view customGlyphs); - std::string_view getGlyphCollection() const; FT_Face _fontFace; FT_Stream _fontStream; diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5c195daa7d..d8256ee1a1 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -134,6 +134,7 @@ option(AX_ENABLE_EXT_EFFEKSEER "Build extension Effekseer" OFF) cmake_dependent_option(AX_ENABLE_EXT_IMGUI "Build extension ImGui" ON "(WINDOWS AND NOT WINRT) OR MACOSX OR LINUX OR ANDROID OR WASM" OFF) cmake_dependent_option(AX_ENABLE_EXT_INSPECTOR "Build extension Inspector" ON "(WINDOWS AND NOT WINRT) OR MACOSX OR LINUX OR ANDROID OR WASM" OFF) +cmake_dependent_option(AX_ENABLE_EXT_SDFGEN "Build extension SDFGen" ON "(WINDOWS AND NOT WINRT) OR MACOSX OR LINUX OR WASM" OFF) option(AX_DISABLE_GLES2 "Whether disable GLES2 support" OFF) diff --git a/core/base/ZipUtils.cpp b/core/base/ZipUtils.cpp index 56fe537382..faf085a26b 100644 --- a/core/base/ZipUtils.cpp +++ b/core/base/ZipUtils.cpp @@ -61,6 +61,126 @@ bool ZipUtils::s_bEncryptionKeyIsValid = false; // --------------------- ZipUtils --------------------- +yasio::byte_buffer ZipUtils::compressGZ(const void* in, size_t inlen, int level) +{ + int err; + Bytef buffer[512]; + z_stream d_stream; /* compression stream */ + + d_stream.zalloc = nullptr; + d_stream.zfree = nullptr; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = (Bytef*)in; + d_stream.avail_in = inlen; + d_stream.next_out = buffer; + d_stream.avail_out = sizeof(buffer); + yasio::byte_buffer output; + err = deflateInit2(&d_stream, level, Z_DEFLATED, MAX_WBITS + 16 /*well: normaly, gzip is: 16*/, MAX_MEM_LEVEL - 1, + Z_DEFAULT_STRATEGY); + if (err != Z_OK) // + return output; + + for (;;) + { + err = deflate(&d_stream, Z_FINISH); + + if (err == Z_STREAM_END) + { + output.insert(output.end(), buffer, buffer + sizeof(buffer) - d_stream.avail_out); + break; + } + + switch (err) + { + case Z_NEED_DICT: + err = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + goto _L_end; + } + + // not enough buffer ? + if (err != Z_STREAM_END) + { + output.insert(output.end(), buffer, buffer + sizeof(buffer)); + + d_stream.next_out = buffer; + d_stream.avail_out = sizeof(buffer); + } + } + +_L_end: + deflateEnd(&d_stream); + if (err != Z_STREAM_END) + { + output.clear(); + } + + return output; +} + +yasio::byte_buffer ZipUtils::decompressGZ(const void* in, size_t inlen, int expected_size) +{ // inflate + int err; + Bytef buffer[512]; + z_stream d_stream; /* decompression stream */ + + d_stream.zalloc = nullptr; + d_stream.zfree = nullptr; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = (Bytef*)in; + d_stream.avail_in = inlen; + d_stream.next_out = buffer; + d_stream.avail_out = sizeof(buffer); + yasio::byte_buffer output; + err = inflateInit2(&d_stream, MAX_WBITS + 32 /*well: normaly, gzip is: 16*/); + if (err != Z_OK) // + return output; + + output.reserve(expected_size != -1 ? expected_size : (inlen << 2)); + + for (;;) + { + err = inflate(&d_stream, Z_NO_FLUSH); + + if (err == Z_STREAM_END) + { + output.insert(output.end(), buffer, buffer + sizeof(buffer) - d_stream.avail_out); + break; + } + + switch (err) + { + case Z_NEED_DICT: + err = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + goto _L_end; + } + + // not enough memory ? + if (err != Z_STREAM_END) + { + // *out = (unsigned char*)realloc(*out, bufferSize * BUFFER_INC_FACTOR); + output.insert(output.end(), buffer, buffer + sizeof(buffer)); + + d_stream.next_out = buffer; + d_stream.avail_out = sizeof(buffer); + } + } + +_L_end: + inflateEnd(&d_stream); + if (err != Z_STREAM_END) + { + output.clear(); + } + + return output; +} + inline void ZipUtils::decodeEncodedPvr(unsigned int* data, ssize_t len) { const int enclen = 1024; diff --git a/core/base/ZipUtils.h b/core/base/ZipUtils.h index eb3bb38042..501c16229b 100644 --- a/core/base/ZipUtils.h +++ b/core/base/ZipUtils.h @@ -40,6 +40,10 @@ THE SOFTWARE. # include "platform/StdC.h" #endif +#include "yasio/byte_buffer.hpp" + +#include + #ifndef _unz64_H struct unz_file_info_s; #endif @@ -73,6 +77,20 @@ enum class AX_DLL ZipUtils { public: + template + inline static yasio::byte_buffer compressGZ(std::span<_Ty, _Extent> in, int level = -1) + { + return compressGZ(in.data(), in.size_bytes(), level); + } + template + inline static yasio::byte_buffer decompressGZ(std::span<_Ty, _Extent> in, int expected_size = -1) + { + return decompressGZ(in.data(), in.size_bytes(), expected_size); + } + + static yasio::byte_buffer compressGZ(const void* in, size_t inlen, int level = -1); + static yasio::byte_buffer decompressGZ(const void* in, size_t inlen, int expected_size = -1); + /** * Inflates either zlib or gzip deflated memory. The inflated memory is expected to be freed by the caller. * diff --git a/core/renderer/TextureCache.cpp b/core/renderer/TextureCache.cpp index ca05c3b5cb..4915d2b765 100644 --- a/core/renderer/TextureCache.cpp +++ b/core/renderer/TextureCache.cpp @@ -387,16 +387,22 @@ void TextureCache::addImageAsyncCallBack(float /*dt*/) Texture2D* TextureCache::getWhiteTexture() { constexpr std::string_view key = "/white-texture"sv; + return getWhiteTexture(key, 0xFF); +} + +Texture2D* TextureCache::getWhiteTexture(std::string_view key, uint8_t luma) +{ // Gets the texture by key firstly. auto texture = this->getTextureForKey(key); - if (texture) return texture; + if (texture) + return texture; // If texture wasn't in cache, create it from RAW data. unsigned char texls[] = { // RGBA8888 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + luma, luma, luma, 0xFF, luma, luma, luma, 0xFF, luma, luma, luma, 0xFF, luma, luma, luma, 0xFF}; - Image* image = new Image(); // Notes: andorid: VolatileTextureMgr traits image as dynmaic object + Image* image = new Image(); // Notes: andorid: VolatileTextureMgr traits image as dynmaic object bool AX_UNUSED isOK = image->initWithRawData(texls, sizeof(texls), 2, 2, 8); AXASSERT(isOK, "The 2x2 empty texture was created unsuccessfully."); diff --git a/core/renderer/TextureCache.h b/core/renderer/TextureCache.h index d8659b4eb9..5bf864459c 100644 --- a/core/renderer/TextureCache.h +++ b/core/renderer/TextureCache.h @@ -85,9 +85,12 @@ public: */ virtual std::string getDescription() const; - /** Gets a 2x2 white texture */ + /** Gets a 2x2 white texture */ Texture2D* getWhiteTexture(); - + + /** Gets a 2x2 texture whith specify luma and key */ + Texture2D* getWhiteTexture(std::string_view key, uint8_t luma); + /** Gets 1x1 dummy texture with alpha=0 */ Texture2D* getDummyTexture(); diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 195314614f..cf393a61df 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -78,6 +78,10 @@ if(AX_ENABLE_EXT_IMGUI) add_subdirectory(Inspector) target_include_directories(Inspector PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ImGui) endif() + if (AX_ENABLE_EXT_SDFGEN) + add_subdirectory(SDFGen) + target_include_directories(SDFGen PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ImGui) + endif() endif() if(AX_ENABLE_EXT_COCOSTUDIO) diff --git a/extensions/SDFGen/CMakeLists.txt b/extensions/SDFGen/CMakeLists.txt new file mode 100644 index 0000000000..471bd981a6 --- /dev/null +++ b/extensions/SDFGen/CMakeLists.txt @@ -0,0 +1,7 @@ +set(target_name SDFGen) + +FILE(GLOB SDFGEN_SOURCES *.h;*.cpp;./**/*.h;./**/*.cpp) + +add_library(${target_name} ${SDFGEN_SOURCES}) + +setup_ax_extension_config(${target_name}) diff --git a/extensions/SDFGen/SDFGen.cpp b/extensions/SDFGen/SDFGen.cpp new file mode 100644 index 0000000000..2fda0d00cc --- /dev/null +++ b/extensions/SDFGen/SDFGen.cpp @@ -0,0 +1,356 @@ +#include "SDFGen.h" + +#include +#include +#include "base/format.h" + +#include "base/ZipUtils.h" + +#include "ImGuiPresenter.h" +#include +#include +#include "json/json.hpp" + +#include "yasio/utils.hpp" + +NS_AX_EXT_BEGIN + +struct FontAtlasGenParams +{ + std::string sourceFont; // font relative path? choose from developer machine? + std::string fontAsset; // fontAsset .xasset + std::string glyphs; // utf-8 + int faceSize = 32; + int atlasDim[2] = {512, 512}; // w,h + bool useAscii = true; + + bool saved = false; + float cost = 0.0f; // milliseconds + std::string error; // save error message if fail +}; + +/** + * scan fonts in `Content/fonts` + * kernings don't store + * axmol .xasset format spec, sdf only + * axmol .xasset binary format: \X\A\S + * { + * "version": "2.1.0", + * "type": "fontatlas", + * "atlasName": "xxx", + * "sourceFont: "xxx", + * "spread": 6, // reserved + * "faceSize": 32, + * "atlasSize": [512, 512], + * "letters": [ + * "30": { + * "page": 0, + * "xAdvance": 33, + * "width": 54, + * "height": 33, + * "offset": 5, + * "bearingY": 3, + * }, + * "20": { + * } + * ] + * "pages: [ // zip + base64 + * "", + * "" + * ] + * } + */ + +namespace xasset +{ +class FontAtlas : public ax::FontAtlas +{ +public: + FontAtlas(Font* theFont, int atlasWidth, int atlasHeight) : ax::FontAtlas(theFont, atlasWidth, atlasHeight) {} + static FontAtlas* newFontAtlas(FontAtlasGenParams* params) + { + auto font = FontFreeType::create(params->sourceFont, params->faceSize, + !params->useAscii ? ax::GlyphCollection::CUSTOM : ax::GlyphCollection::ASCII, + params->glyphs, true); + auto fontAtlas = new xasset::FontAtlas(font, params->atlasDim[0], params->atlasDim[1]); + + fontAtlas->generate(params); + + return fontAtlas; + } + + bool save() { return this->saveAs(_params->fontAsset); } + + bool saveAs(std::string_view path) + { + std::string storePath; + auto fu = FileUtils::getInstance(); + if (fu->isAbsolutePath(path)) + { + storePath = path; + } + else + { + storePath = fu->getDefaultResourceRootPath(); + storePath += path; + } + + auto start = yasio::highp_clock(); + + nlohmann::json xasset; + + xasset.emplace("version", AX_VERSION_STR "-" AX_GIT_COMMIT_HASH); + xasset.emplace("type", "fontatlas"); + xasset.emplace("sourceFont", _params->sourceFont); + xasset.emplace("atlasName", _atlasName); + xasset.emplace("spread", 6); + xasset.emplace("faceSize", _params->faceSize); + + xasset.emplace("atlasDim", _params->atlasDim); + + nlohmann::json letters; + std::string charCode; + for (auto& letterInfo : getLetterDefinitions()) + { + charCode.clear(); + fmt::format_to(charCode, "{}", (int32_t)letterInfo.first); + + nlohmann::json info; + info.emplace("U", letterInfo.second.U); + info.emplace("V", letterInfo.second.V); + info.emplace("width", letterInfo.second.width); + info.emplace("height", letterInfo.second.height); + info.emplace("offsetX", letterInfo.second.offsetX); + info.emplace("offsetY", letterInfo.second.offsetY); + info.emplace("page", letterInfo.second.textureID); + info.emplace("advance", letterInfo.second.xAdvance); + letters.emplace(charCode, std::move(info)); + } + xasset.emplace("letters", std::move(letters)); + + nlohmann::json pages; + + for (auto& data : _pageDatas) + { + auto compData = ZipUtils::compressGZ(std::span{data}); + auto pixels = utils::base64Encode(std::string_view{reinterpret_cast(compData.data()), compData.size()}); + pages.push_back(std::move(pixels)); + } + + xasset.emplace("pages", std::move(pages)); + + xasset.emplace("pageX", _currentPageOrigX); + xasset.emplace("pageY", _currentPageOrigY); + + auto str = xasset.dump(2); + + fu->writeStringToFile(str, storePath); + + _params->cost = (yasio::highp_clock() - start) / 1000.0; + + _params->error.clear(); + return true; + } + + Texture2D* testTexture() + { + static Texture2D* texture = new Texture2D(); + static bool inited = false; + if (!inited && !_pageDatas.empty()) + { + texture->initWithData(_pageDatas[0].data(), _pageDatas[0].size(), PixelFormat::R8, 512, 512, false); + texture->retain(); + inited = true; + } + return inited ? texture : nullptr; + } + +protected: + void generate(FontAtlasGenParams* params) + { + _params = params; + + // match with runtime + _atlasName = fmt::format("df {} {}", params->faceSize, params->sourceFont); + + std::u32string utf32; + if (StringUtils::UTF8ToUTF32(_fontFreeType->getGlyphCollection(), utf32)) + this->prepareLetterDefinitions(utf32); + + _pageDatas.emplace_back(_currentPageData, _currentPageData + _currentPageDataSize); + } + + void addNewPage() override + { + if (_currentPage != -1) + _pageDatas.emplace_back(_currentPageData, _currentPageData + _currentPageDataSize); + ax::FontAtlas::addNewPage(); + } + + FontAtlasGenParams* _params{nullptr}; // weak ref + std::string _atlasName; // match with runtime + std::vector _pageDatas; +}; +}; // namespace xasset + +SDFGen* SDFGen::getInstance() +{ + return yasio::singleton::instance(); +} +void SDFGen::destroyInstance() +{ + yasio::singleton::destroy(); +} + +void SDFGen::open(ax::Scene* scene) +{ + refreshFontList(); + + _atlasViewer = Sprite::create(); + _atlasViewer->setTexture(Director::getInstance()->getTextureCache()->getWhiteTexture("/black-texture", 0)); + _atlasViewer->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT); + _atlasViewer->retain(); + + auto defaultFontFile = FileUtils::getInstance()->fullPathForFilename(R"(fonts/arial.ttf)"); + + _atlasParams = new FontAtlasGenParams(); + _atlasParams->sourceFont = "fonts/arial.ttf"; + _atlasParams->fontAsset = "fonts/arial-SDF.xasset"; + + ImGuiPresenter::getInstance()->addFont(defaultFontFile); + /* For Simplified Chinese support, please use: + ImGuiPresenter::getInstance()->addFont(R"(C:\Windows\Fonts\msyh.ttc)", ImGuiPresenter::DEFAULT_FONT_SIZE, + ImGuiPresenter::CHS_GLYPH_RANGE::GENERAL); + */ + ImGuiPresenter::getInstance()->enableDPIScale(); // enable dpi scale for 4K display support, depends at least one + // valid ttf/ttc font was added. + ImGuiPresenter::getInstance()->addRenderLoop("#sdfg", AX_CALLBACK_0(SDFGen::onImGuiDraw, this), scene); +} + +void SDFGen::close() +{ + ImGuiPresenter::getInstance()->removeRenderLoop("#sdfg"); + + delete _atlasParams; + _atlasParams = nullptr; + + _atlasViewer->release(); +} + +void SDFGen::refreshFontList() +{ + _fontList.clear(); + + auto fu = FileUtils::getInstance(); + + std::vector fileList; + fu->listFilesRecursively("fonts", &fileList); + + auto& contentPath = fu->getDefaultResourceRootPath(); + for (auto& filePath : fileList) + { + if (!cxx20::ic::ends_with(filePath, ".ttf") && !cxx20::ic::ends_with(filePath, ".ttc")) + continue; + if (cxx20::starts_with(filePath, contentPath)) + _fontList.emplace_back(filePath.substr(contentPath.size())); + else + _fontList.emplace_back(std::move(filePath)); + } +} + +void SDFGen::onImGuiDraw() +{ + ImGui::StyleColorsDark(); + + auto& style = ImGui::GetStyle(); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.2f, 0.2f, 0.2f, 0.94f); + + ImGui::SetNextWindowSize(ImVec2{550, 660}, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowContentSize(ImVec2{0, 0}); + + static std::string title = fmt::format("Axmol SDF Font Creator {}", AX_VERSION_STR "-" AX_GIT_COMMIT_HASH); + if (ImGui::Begin(title.c_str(), nullptr, ImGuiWindowFlags_HorizontalScrollbar)) + { + if (ImGui::BeginCombo("Source Font File", _atlasParams->sourceFont.c_str())) + { + for (int n = 0; n < _fontList.size(); n++) + { + bool is_selected = (_atlasParams->sourceFont == _fontList[n]); + if (ImGui::Selectable(_fontList[n].c_str(), is_selected)) + _atlasParams->sourceFont = _fontList[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + + if (ImGui::Button("Refresh Font List")) + { + refreshFontList(); + } + ImGui::DragInt("Sampling Point Size", &_atlasParams->faceSize, 1, 1, 144); + ImGui::DragInt2("Atlas Resolution", _atlasParams->atlasDim, 32, 64, 4096); + + bool modified = ImGui::Checkbox("Use ASCII", &_atlasParams->useAscii); + ImGui::SameLine(); + ImGui::Text("%s", "Character Set"); + if (!_atlasParams->useAscii) + { + ImGui::InputTextMultiline("glyphs", &_atlasParams->glyphs); + } + else + { + if (modified) + _atlasParams->glyphs.clear(); + } + + if (ImGui::Button("Generate Font Atlas")) + { + AX_SAFE_RELEASE_NULL(_fontAtlas); + _fontAtlas = xasset::FontAtlas::newFontAtlas(_atlasParams); + + // TODO: display multi-pages with listview? + auto textureAtlasPage0 = _fontAtlas->getTexture(0); + _atlasViewer->setTexture(textureAtlasPage0); + + Rect rect = Rect::ZERO; + rect.size = textureAtlasPage0->getContentSize(); + _atlasViewer->setTextureRect(rect); + } + ImGui::InputText("Font Asset", &_atlasParams->fontAsset); + if (ImGui::Button("Save")) + { + if (_fontAtlas) + _fontAtlas->save(); + else + _atlasParams->error = "Please generate first!"; + + _atlasParams->saved = true; + } + + if (_atlasParams->saved) + { + ImGui::SameLine(); + if (!_atlasParams->error.empty()) + ImGui::TextColored(ImVec4{1.0, 0.0, 0.0, 1.0}, "%s", _atlasParams->error.c_str()); + else + ImGui::TextColored(ImVec4{0.0, 1.0, 0.0, 1.0}, "Save succeed, cost %.3f (ms)", _atlasParams->cost); + } + + auto viewerSize = _atlasViewer->getContentSize(); + if (viewerSize.fuzzyEquals(Vec2::ZERO, 1e-3)) + { + viewerSize.width = 512; + viewerSize.height = 512; + } + + ImGui::Separator(); + ImGui::Text("Atals View:"); + ImGuiPresenter::getInstance()->image(_atlasViewer, ImVec2(viewerSize.width, viewerSize.height)); + } + + ImGui::End(); +} + +NS_AX_EXT_END diff --git a/extensions/SDFGen/SDFGen.h b/extensions/SDFGen/SDFGen.h new file mode 100644 index 0000000000..17cbe3d6bf --- /dev/null +++ b/extensions/SDFGen/SDFGen.h @@ -0,0 +1,37 @@ +#pragma once + +#include "axmol.h" +#include "ExtensionMacros.h" + +NS_AX_EXT_BEGIN + +struct FontAtlasGenParams; + +namespace xasset +{ +class FontAtlas; +} +class SDFGen +{ +public: + static SDFGen* getInstance(); + static void destroyInstance(); + + void open(ax::Scene* = nullptr); + void close(); + +protected: + void refreshFontList(); + + void onImGuiDraw(); + +protected: + FontAtlasGenParams* _atlasParams{nullptr}; + + ax::Sprite* _atlasViewer{nullptr}; + xasset::FontAtlas* _fontAtlas{nullptr}; + + std::vector _fontList; +}; + +NS_AX_EXT_END diff --git a/tests/cpp-tests/Source/ImGuiTest/ImGuiTest.cpp b/tests/cpp-tests/Source/ImGuiTest/ImGuiTest.cpp index 1c616adacb..e1c61b7482 100644 --- a/tests/cpp-tests/Source/ImGuiTest/ImGuiTest.cpp +++ b/tests/cpp-tests/Source/ImGuiTest/ImGuiTest.cpp @@ -2,6 +2,7 @@ #include "ImGuiTest.h" #include "ImGui/ImGuiPresenter.h" +#include "SDFGen/SDFGen.h" USING_NS_AX; USING_NS_AX_EXT; @@ -22,7 +23,9 @@ ImGuiTests::ImGuiTests() void ImGuiTest::onEnter() { TestCase::onEnter(); - +# if !defined(__ANDROID__) + SDFGen::getInstance()->open(); +#endif ImGuiPresenter::getInstance()->addFont(FileUtils::getInstance()->fullPathForFilename("fonts/arial.ttf")); ImGuiPresenter::getInstance()->enableDPIScale(); ImGuiPresenter::getInstance()->addRenderLoop("#test", AX_CALLBACK_0(ImGuiTest::onDrawImGui, this), this); @@ -33,6 +36,9 @@ void ImGuiTest::onExit() ImGuiPresenter::getInstance()->removeRenderLoop("#test"); ImGuiPresenter::getInstance()->clearFonts(); +# if !defined(__ANDROID__) + SDFGen::getInstance()->close(); +# endif ImGuiPresenter::destroyInstance(); TestCase::onExit();