diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bc8d3b..454290c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,9 +46,10 @@ set(PD_SOURCES source/drivers/gfx.cpp # Lithium - source/lithium/pools.cpp - source/lithium/math.cpp source/lithium/drawlist.cpp + source/lithium/font.cpp + source/lithium/math.cpp + source/lithium/pools.cpp ) if(${PD_BUILD_SHARED}) diff --git a/backends/source/gfx_citro3d.cpp b/backends/source/gfx_citro3d.cpp index 10de327..09c3962 100644 --- a/backends/source/gfx_citro3d.cpp +++ b/backends/source/gfx_citro3d.cpp @@ -119,6 +119,7 @@ void GfxCitro3D::SysInit() { if (impl) return; PDLOG("GfxCitro3D::SysInit();"); impl = new Impl(); + Flags |= PDBackendFlags_FlipUV_Y; impl->pShaderRaw = Pica::AssembleCode(LIShaderCTR); impl->pCode = DVLB_ParseFile((uint32_t*)impl->pShaderRaw.data(), impl->pShaderRaw.size()); diff --git a/include/pd/core/strings.hpp b/include/pd/core/strings.hpp index 383472e..91dc310 100644 --- a/include/pd/core/strings.hpp +++ b/include/pd/core/strings.hpp @@ -149,7 +149,7 @@ class U8Iterator { } bool PeekNext32(u32& ret) { - // if ((ptr + 1) == 0 || *(ptr + 1) == 0) return false; + if (ptr == nullptr || *(ptr + 1) == 0) return false; u8 c = *ptr; if (c < 0x80) { ret = c; diff --git a/include/pd/drivers/gfx.hpp b/include/pd/drivers/gfx.hpp index 49c5a59..657e1a4 100755 --- a/include/pd/drivers/gfx.hpp +++ b/include/pd/drivers/gfx.hpp @@ -40,6 +40,7 @@ class PD_API GfxDriver : public DriverInterface { virtual void DeleteTexture(const Li::Texture& tex) {} virtual void Draw(const Pool& commands) {} Li::Texture::Ptr GetWhiteTexture() { return &pWhite; } + PDBackendFlags GetFlags() { return Flags; } protected: virtual void SysDeinit() {} @@ -62,6 +63,7 @@ class PD_API GfxDriver : public DriverInterface { ivec2 ViewPort; std::unordered_map pTextureRegestry; Li::Texture pWhite; + PDBackendFlags Flags = 0; }; struct DefaultGfxConfig { @@ -166,6 +168,8 @@ class PD_API Gfx { return driver->GetWhiteTexture(); } + static PDBackendFlags GetFlags() { return driver->GetFlags(); } + static const char* GetDriverName() { return driver->GetName(); } private: diff --git a/include/pd/lithium/drawlist.hpp b/include/pd/lithium/drawlist.hpp index d8d0754..d9bc470 100644 --- a/include/pd/lithium/drawlist.hpp +++ b/include/pd/lithium/drawlist.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include using LiPathRectFlags = PD::u32; @@ -27,18 +28,6 @@ enum LiDrawFlags_ : PD::u32 { LiDrawFlags_Close = 1 << 0, }; -using LiTextFlags = PD::u32; -enum LiTextFlags_ : PD::u32 { - LiTextFlags_None = 0, ///< Do nothing - LiTextFlags_AlignRight = 1 << 0, ///< Align Right of position - LiTextFlags_AlignMid = 1 << 1, ///< Align in the middle of pos and box - LiTextFlags_Shaddow = 1 << 2, ///< Draws the text twice to create shaddow - LiTextFlags_Wrap = 1 << 3, ///< Wrap Text: May be runs better with TMS - LiTextFlags_Short = 1 << 4, ///< Short Text: May be runs better with TMS - LiTextFlags_Scroll = 1 << 5, ///< Not implemented [scoll text if to long] - LiTextFlags_NoOOS = 1 << 6, ///< No Out of Screen Rendering -}; - namespace PD { namespace Li { class PD_API Drawlist { @@ -78,6 +67,10 @@ class PD_API Drawlist { void BindTexture(const Texture& tex); void UnbindTexture() { pCurrentTexture = Texture(); } + /** Font Handling */ + void SetFont(Font* font) { pFont = font; } + void SetFontscale(float fontscale = 1.f) { pFontScale = fontscale; } + /** Data geters */ const Pool& Data() const { return pCommands; } operator const Pool&() const { return pCommands; } @@ -111,6 +104,8 @@ class PD_API Drawlist { Pool pPath; Pool pVertices; Pool pIndices; + Font* pFont = nullptr; + float pFontScale = 1.f; }; } // namespace Li } // namespace PD \ No newline at end of file diff --git a/include/pd/lithium/font.hpp b/include/pd/lithium/font.hpp new file mode 100644 index 0000000..9928949 --- /dev/null +++ b/include/pd/lithium/font.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include + +using LiTextFlags = PD::u32; +enum LiTextFlags_ : PD::u32 { + LiTextFlags_None = 0, ///< Do nothing + LiTextFlags_AlignRight = 1 << 0, ///< Align Right of position + LiTextFlags_AlignMid = 1 << 1, ///< Align in the middle of pos and box + LiTextFlags_Shaddow = 1 << 2, ///< Draws the text twice to create shaddow + LiTextFlags_Wrap = 1 << 3, ///< Wrap Text: May be runs better with TMS + LiTextFlags_Short = 1 << 4, ///< Short Text: May be runs better with TMS + LiTextFlags_Scroll = 1 << 5, ///< Not implemented [scoll text if to long] + LiTextFlags_NoOOS = 1 << 6, ///< No Out of Screen Rendering +}; + +namespace PD { +namespace Li { +class Drawlist; +class PD_API Font { + public: + struct Codepoint { + u32 pCodepoint; + fvec4 SimpleUV; + size_t Tex; + fvec2 Size; + float Offset = 0.f; + bool pInvalid; + }; + Font() {} + ~Font() {} + + /** + * Load a TTF File + * @param path Path to the TTF file + * @param px_height Pixelheight of the codepoints (limit by 64) + */ + void LoadTTF(const std::string& path, int px_height = 32); + /** + * Load a TTF File from Memory + * @param data File data + * @param px_height Pixelheight of the codepoints (limit by 64) + */ + void LoadTTF(const std::vector& data, int px_height = 32); + /** + * Function that loads a default integrated font... + * This will only work if PD_LI_INCLUDE_FONTS was set + * on lithium build cause otherwise the font data is not included + */ + void LoadDefaultFont(int id = 0, int pixel_height = 32); + /** + * Getter for Codepoint reference + * @return codepoint dataholder reference + */ + Codepoint& GetCodepoint(u32 c); + + /** + * Get Text Bounding Box + */ + fvec2 GetTextBounds(const char* text, float scale); + /** + * Extended Draw Text Function that vreates a Command List + */ + void CmdTextEx(Drawlist& dl, const fvec2& pos, u32 color, float scale, + const char* text, LiTextFlags flags = 0, const fvec2& box = 0); + + /** + * Garbage collection for TextMapSystem + */ + void CleanupTMS(); + /** + * Utility function to create a font atlas + * During TTF loading (Internal and should not be called) + */ + void BakeAndPush(bool final, std::vector& font_tex, int texszs); + std::string pWrapText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim); + std::string pShortText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim); + + /** Data Section */ + int PixelHeight = 0; + int DefaultPixelHeight = 24; + std::vector Textures; + /** + * 32Bit Codepoint Dataholder reference map + * **Now using unordered map** + */ + std::unordered_map CodeMap; + /** TMS */ + struct TMELEM { + PD::u32 ID; + PD::fvec2 Size; + std::string Text; + u64 TimeStamp; + }; + std::unordered_map pTMS; + + private: + size_t pCurrentTex = 0; +}; +} // namespace Li +} // namespace PD \ No newline at end of file diff --git a/include/pd/lithium/lithium.hpp b/include/pd/lithium/lithium.hpp index 0fc3cd3..9cde277 100644 --- a/include/pd/lithium/lithium.hpp +++ b/include/pd/lithium/lithium.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/source/lithium/drawlist.cpp b/source/lithium/drawlist.cpp index c79e862..5362fff 100644 --- a/source/lithium/drawlist.cpp +++ b/source/lithium/drawlist.cpp @@ -203,10 +203,16 @@ PD_API void Drawlist::DrawCircleFilled(const fvec2& center, float rad, PathFill(color); } -PD_API void Drawlist::DrawText(const fvec2& p, const char* text, u32 color) {} +PD_API void Drawlist::DrawText(const fvec2& p, const char* text, u32 color) { + if (!pFont) return; + pFont->CmdTextEx(*this, p, color, pFontScale, text); +} PD_API void Drawlist::DrawTextEx(const fvec2& p, const char* text, u32 color, - LiTextFlags flags, const fvec2& box) {} + LiTextFlags flags, const fvec2& box) { + if (!pFont) return; + pFont->CmdTextEx(*this, p, color, pFontScale, text, flags, box); +} PD_API void Drawlist::DrawPolyLine(const Pool& points, u32 color, LiDrawFlags flags, int t) { @@ -270,6 +276,7 @@ PD_API void Drawlist::DrawConvexPolyFilled(const Pool& points, PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, u32 color) { + cmd.Reserve(4, 6); cmd.Add(2, 1, 0); cmd.Add(3, 2, 0); cmd.Add(Vertex(quad.TopLeft(), uv.TopLeft(), color)); @@ -280,6 +287,7 @@ PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, PD_API void Drawlist::PrimTriangle(Command& cmd, const fvec2& a, const fvec2& b, const fvec2& c, u32 color) { + cmd.Reserve(3, 3); cmd.Add(2, 1, 0); cmd.Add(Vertex(a, vec2(0.f, 1.f), color)); cmd.Add(Vertex(b, vec2(1.f, 1.f), color)); diff --git a/source/lithium/font.cpp b/source/lithium/font.cpp new file mode 100644 index 0000000..ac89aa3 --- /dev/null +++ b/source/lithium/font.cpp @@ -0,0 +1,275 @@ +#include +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +#include + +namespace PD { +namespace Li { +PD_API void Font::LoadTTF(const std::string& path, int px_height) { + /** + * Just use LoadFile2Mem which looks way cleaner + * and helps not having the font loading code twice + * when adding LoadTTF with mem support + */ + PDLOG("Font: Loading {}...", path); + TT::Scope st("LI_LoadTTF_" + path); + auto font = PD::IO::LoadFile2Mem(path); + LoadTTF(font, px_height); +} + +PD_API void Font::LoadTTF(const std::vector& data, int px_height) { + /** + * Some additional Info: + * Removed the stbtt get bitmapbox as we dont need to place + * the glyps nicely in the tex. next step would be using the free + * space on the y axis to get mor glyphs inside + */ + PixelHeight = px_height; + int texszs = PD::Bits::GetPow2(px_height * 16); + if (texszs > 1024) { + texszs = 1024; // Max size + } + + stbtt_fontinfo inf; + if (!stbtt_InitFont(&inf, data.data(), 0)) { + return; + } + + float scale = stbtt_ScaleForPixelHeight(&inf, PixelHeight); + + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&inf, &ascent, &descent, &lineGap); + int baseline = static_cast(ascent * scale); + + // Cache to not render same codepoint tex twice + std::map buf_cache; + + std::vector font_tex(texszs * texszs * 4, 0); + fvec2 off; + + bool empty = true; + + for (u32 ii = 0x0000; ii <= 0xFFFF; ii++) { + int gi = stbtt_FindGlyphIndex(&inf, ii); + if (gi == 0) continue; + if (stbtt_IsGlyphEmpty(&inf, gi)) continue; + + int w = 0, h = 0, xo = 0, yo = 0; + unsigned char* bitmap = + stbtt_GetCodepointBitmap(&inf, scale, scale, ii, &w, &h, &xo, &yo); + if (!bitmap || w <= 0 || h <= 0) { + if (bitmap) free(bitmap); + continue; + } + + u32 hashed_map = IO::HashMemory(std::vector(bitmap, bitmap + (w * h))); + if (buf_cache.find(hashed_map) != buf_cache.end()) { + Codepoint c = GetCodepoint(buf_cache[hashed_map]); + c.pCodepoint = ii; + CodeMap[ii] = c; + free(bitmap); + continue; + } else { + buf_cache[hashed_map] = ii; + } + + // Next row + if (off.x + w > texszs) { + off.y += PixelHeight; + off.x = 0.0f; + } + // Bake cause we go out of the tex + if (off.y + PixelHeight > texszs) { + BakeAndPush(false, font_tex, texszs); + off = 0; + std::fill(font_tex.begin(), font_tex.end(), 0); + empty = true; + } + + // UVs & Codepoint + Codepoint c; + fvec4 uvs; + // cast the ints to floats and not the floats... + // dont know where my mind was when creating the code + uvs.x = off.x / static_cast(texszs); + uvs.y = off.y / static_cast(texszs); + uvs.z = (off.x + w) / static_cast(texszs); + uvs.w = (off.y + h) / static_cast(texszs); + // Flip if needed + if (PD::Gfx::GetFlags() & PDBackendFlags_FlipUV_Y) { + uvs.y = 1.f - uvs.y; + uvs.w = 1.f - uvs.w; + } + c.SimpleUV = uvs; + c.Tex = pCurrentTex; + c.Size = fvec2(w, h); + c.Offset = baseline + yo; + c.pCodepoint = ii; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + int map_pos = ((static_cast(off.y) + y) * texszs + + (static_cast(off.x) + x)) * + 4; + font_tex[map_pos + 0] = 255; + font_tex[map_pos + 1] = 255; + font_tex[map_pos + 2] = 255; + font_tex[map_pos + 3] = bitmap[x + y * w]; + } + } + + empty = false; + CodeMap[ii] = c; + free(bitmap); + + // offset by 1 (prevents visual glitches i had) + off.x += w + 1; + } + + if (!empty) { + BakeAndPush(true, font_tex, texszs); + } +} + +PD_API void Font::LoadDefaultFont(int id, int pixel_height) {} + +PD_API Font::Codepoint& Font::GetCodepoint(u32 c) { + // Check if codepoijt exist or return a static invalid one + auto res = CodeMap.find(c); + if (res == CodeMap.end()) { + static Codepoint invalid; + invalid.pInvalid = true; + return invalid; + } + return res->second; +} + +PD_API fvec2 Font::GetTextBounds(const char* text, float scale) { + // Create a temp position and offset as [0, 0] + fvec2 res; + float x = 0; + // Curent Font Scale + float cfs = (DefaultPixelHeight * scale) / (float)PixelHeight; + float lh = (float)PixelHeight * cfs; + U8Iterator it(text); + u32 c; + while (it.Decode32(c)) { + auto cp = GetCodepoint(c); + if (!cp.pInvalid && c != '\n' && c != '\t' && c != ' ') { + continue; + } + switch (c) { + case L'\n': + res.y += lh; + res.x = std::max(res.x, x); + x = 0.f; + break; + case L'\t': + x += 16 * cfs; + break; + case L' ': + x += 4 * cfs; + // Fall trough here to get the same result as in + // TextCommand if/else Section + default: + x += cp.Size.x * cfs; + // Omly passing c cause i know its not used + if (it.PeekNext32(c)) { + x += 2 * cfs; + } + break; + } + } + res.x = std::max(res.x, x); + res.y += lh; + return res; +} + +PD_API void Font::CmdTextEx(Drawlist& dl, const fvec2& pos, u32 color, + float scale, const char* text, LiTextFlags flags, + const fvec2& box) { + fvec2 off; + float cfs = (DefaultPixelHeight * scale) / (float)PixelHeight; + float lh = (float)PixelHeight * cfs; + fvec2 td; + fvec2 rpos = pos; + fvec2 rbox = box; + + if (flags & (LiTextFlags_AlignMid | LiTextFlags_AlignRight)) { + td = GetTextBounds(text, scale); + } + if (flags & LiTextFlags_AlignMid) rpos = rbox * 0.5 - td * 0.5 + pos; + if (flags & LiTextFlags_AlignRight) rpos.x -= td.x; + + U8Iterator it(text); + u32 c; + Command* cmd = nullptr; + + while (it.Decode32(c)) { + auto cp = GetCodepoint(c); + if ((!cp.pInvalid && c != '\n' && c != '\t' && c != ' ') && c != '\r') + continue; + + if (c == L'\n') { + off.y += lh; + off.x = 0.f; + continue; + } + + if (c == L'\t') { + off.x += 16 * cfs; + continue; + } + if (c == L' ') { + off.x += 4 * cfs; + continue; + } + + if (cmd == nullptr || cmd->Tex != Textures[cp.Tex]) { + if (cp.Tex >= Textures.size()) continue; + cmd = &dl.NewCommand(); + cmd->Tex = Textures[cp.Tex]; + } + + if (flags & LiTextFlags_Shaddow) { + Rect rec = Math::PrimRect(rpos + off + fvec2(1, cp.Offset * cfs + 1), + cp.Size * cfs, 0.f); + dl.PrimQuad(*cmd, rec, cp.SimpleUV, 0xff111111); + } + + Rect rec = Math::PrimRect(rpos + off + fvec2(0, cp.Offset * cfs), + cp.Size * cfs, 0.f); + dl.PrimQuad(*cmd, rec, cp.SimpleUV, color); + + off.x += cp.Size.x * cfs + 2 * cfs; + } +} + +PD_API void Font::CleanupTMS() {} + +PD_API void Font::BakeAndPush(bool final, std::vector& font_tex, + int texszs) { + auto t = PD::Gfx::LoadTexture(font_tex, texszs, texszs); + PDLOG("Font: Texture backed as 0x{:X} at {}", t.GetID(), pCurrentTex); + Textures.push_back(t.GetID()); + pCurrentTex = Textures.size(); +} + +PD_API std::string Font::pWrapText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim) { + return ""; +} + +PD_API std::string Font::pShortText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim) { + return ""; +} + +} // namespace Li +} // namespace PD \ No newline at end of file diff --git a/tests/assets/default.ttf b/tests/assets/default.ttf new file mode 100644 index 0000000..dc9a6d5 Binary files /dev/null and b/tests/assets/default.ttf differ diff --git a/tests/gfx/source/main.cpp b/tests/gfx/source/main.cpp index e43de3c..7c4fb3b 100644 --- a/tests/gfx/source/main.cpp +++ b/tests/gfx/source/main.cpp @@ -16,7 +16,7 @@ const char* ResourcePath(const char* in) { } int main(int argc, char** argv) { - PD::LogFilter(PD::LogLevel::Warning); + // PD::LogFilter(PD::LogLevel::Warning); Driver drv = Driver::OpenGL3; if (argc == 2) { if (std::string(argv[1]) == "gl2") { @@ -39,6 +39,10 @@ int main(int argc, char** argv) { PD::Li::Drawlist pList; PD::Image img(ResourcePath("icon.png")); auto pTex = PD::Gfx::LoadTexture(img, img.Width(), img.Height()); + PD::Li::Font font; + font.LoadTTF(ResourcePath("default.ttf")); + pList.SetFont(&font); + pList.SetFontscale(0.7f); while (pOs->Mainloop()) { pOs->ClearViewPort(); PD::Li::ResetPools(); @@ -48,6 +52,7 @@ int main(int argc, char** argv) { pList.BindTexture(pTex); pList.DrawRectFilled(pOs->PositionTranslate(PD::fvec2(0.02f, 0.5f)), pOs->SizeTranslate(PD::fvec2(0.3)), 0xffffffff); + pList.DrawText(5, "Hello World!", 0xff0000ff); PD::Gfx::Reset(); PD::Gfx::Draw(pList); pList.Clear(); diff --git a/tests/gfx/source/os/horizon-ctr.cpp b/tests/gfx/source/os/horizon-ctr.cpp index 25455e6..a955534 100644 --- a/tests/gfx/source/os/horizon-ctr.cpp +++ b/tests/gfx/source/os/horizon-ctr.cpp @@ -15,11 +15,21 @@ namespace PD { struct HorizonCtr::Impl { C3D_RenderTarget* Top = nullptr; C3D_RenderTarget* Bottom = nullptr; + uint32_t* pSocBuf = nullptr; }; void HorizonCtr::Init() { if (impl) return; impl = new Impl(); + impl->pSocBuf = (uint32_t*)std::aligned_alloc(0x1000, 0x100000); + if (impl->pSocBuf) { + Result ret = socInit(impl->pSocBuf, 0x100000); + if (R_FAILED(ret)) { + free(impl->pSocBuf); + impl->pSocBuf = nullptr; + } + } + link3dsStdio(); romfsInit(); gfxInitDefault(); consoleInit(GFX_BOTTOM, nullptr); @@ -41,6 +51,7 @@ void HorizonCtr::Deinit() { C3D_Fini(); gfxExit(); romfsExit(); + socExit(); delete impl; impl = nullptr; } @@ -48,7 +59,9 @@ void HorizonCtr::Deinit() { bool HorizonCtr::Mainloop() { pViewPort = ivec2(400, 240); - return aptMainLoop(); + hidScanInput(); + bool _kill = hidKeysUp() & KEY_START; + return aptMainLoop() && !_kill; } void HorizonCtr::ClearViewPort() { diff --git a/tests/gfx/source/os/horizon-nx.cpp b/tests/gfx/source/os/horizon-nx.cpp index 5621517..d552fed 100644 --- a/tests/gfx/source/os/horizon-nx.cpp +++ b/tests/gfx/source/os/horizon-nx.cpp @@ -16,6 +16,8 @@ struct HorizonNX::Impl { void HorizonNX::Init() { if (impl) return; impl = new Impl(); + socketInitializeDefault(); + nxlinkStdio(); romfsInit(); glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);