Readd font support (not workign yet)

This commit is contained in:
2026-03-22 21:50:53 +01:00
parent b49c0bd3dc
commit 4777c85f9a
13 changed files with 427 additions and 19 deletions
+3 -2
View File
@@ -46,9 +46,10 @@ set(PD_SOURCES
source/drivers/gfx.cpp source/drivers/gfx.cpp
# Lithium # Lithium
source/lithium/pools.cpp
source/lithium/math.cpp
source/lithium/drawlist.cpp source/lithium/drawlist.cpp
source/lithium/font.cpp
source/lithium/math.cpp
source/lithium/pools.cpp
) )
if(${PD_BUILD_SHARED}) if(${PD_BUILD_SHARED})
+1
View File
@@ -119,6 +119,7 @@ void GfxCitro3D::SysInit() {
if (impl) return; if (impl) return;
PDLOG("GfxCitro3D::SysInit();"); PDLOG("GfxCitro3D::SysInit();");
impl = new Impl(); impl = new Impl();
Flags |= PDBackendFlags_FlipUV_Y;
impl->pShaderRaw = Pica::AssembleCode(LIShaderCTR); impl->pShaderRaw = Pica::AssembleCode(LIShaderCTR);
impl->pCode = DVLB_ParseFile((uint32_t*)impl->pShaderRaw.data(), impl->pCode = DVLB_ParseFile((uint32_t*)impl->pShaderRaw.data(),
impl->pShaderRaw.size()); impl->pShaderRaw.size());
+1 -1
View File
@@ -149,7 +149,7 @@ class U8Iterator {
} }
bool PeekNext32(u32& ret) { bool PeekNext32(u32& ret) {
// if ((ptr + 1) == 0 || *(ptr + 1) == 0) return false; if (ptr == nullptr || *(ptr + 1) == 0) return false;
u8 c = *ptr; u8 c = *ptr;
if (c < 0x80) { if (c < 0x80) {
ret = c; ret = c;
+4
View File
@@ -40,6 +40,7 @@ class PD_API GfxDriver : public DriverInterface {
virtual void DeleteTexture(const Li::Texture& tex) {} virtual void DeleteTexture(const Li::Texture& tex) {}
virtual void Draw(const Pool<Li::Command>& commands) {} virtual void Draw(const Pool<Li::Command>& commands) {}
Li::Texture::Ptr GetWhiteTexture() { return &pWhite; } Li::Texture::Ptr GetWhiteTexture() { return &pWhite; }
PDBackendFlags GetFlags() { return Flags; }
protected: protected:
virtual void SysDeinit() {} virtual void SysDeinit() {}
@@ -62,6 +63,7 @@ class PD_API GfxDriver : public DriverInterface {
ivec2 ViewPort; ivec2 ViewPort;
std::unordered_map<TextureID, Li::Texture> pTextureRegestry; std::unordered_map<TextureID, Li::Texture> pTextureRegestry;
Li::Texture pWhite; Li::Texture pWhite;
PDBackendFlags Flags = 0;
}; };
struct DefaultGfxConfig { struct DefaultGfxConfig {
@@ -166,6 +168,8 @@ class PD_API Gfx {
return driver->GetWhiteTexture(); return driver->GetWhiteTexture();
} }
static PDBackendFlags GetFlags() { return driver->GetFlags(); }
static const char* GetDriverName() { return driver->GetName(); } static const char* GetDriverName() { return driver->GetName(); }
private: private:
+7 -12
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <pd/lithium/command.hpp> #include <pd/lithium/command.hpp>
#include <pd/lithium/font.hpp>
#include <pd/lithium/texture.hpp> #include <pd/lithium/texture.hpp>
using LiPathRectFlags = PD::u32; using LiPathRectFlags = PD::u32;
@@ -27,18 +28,6 @@ enum LiDrawFlags_ : PD::u32 {
LiDrawFlags_Close = 1 << 0, 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 PD {
namespace Li { namespace Li {
class PD_API Drawlist { class PD_API Drawlist {
@@ -78,6 +67,10 @@ class PD_API Drawlist {
void BindTexture(const Texture& tex); void BindTexture(const Texture& tex);
void UnbindTexture() { pCurrentTexture = Texture(); } void UnbindTexture() { pCurrentTexture = Texture(); }
/** Font Handling */
void SetFont(Font* font) { pFont = font; }
void SetFontscale(float fontscale = 1.f) { pFontScale = fontscale; }
/** Data geters */ /** Data geters */
const Pool<Command>& Data() const { return pCommands; } const Pool<Command>& Data() const { return pCommands; }
operator const Pool<Command>&() const { return pCommands; } operator const Pool<Command>&() const { return pCommands; }
@@ -111,6 +104,8 @@ class PD_API Drawlist {
Pool<fvec2> pPath; Pool<fvec2> pPath;
Pool<Vertex> pVertices; Pool<Vertex> pVertices;
Pool<u16> pIndices; Pool<u16> pIndices;
Font* pFont = nullptr;
float pFontScale = 1.f;
}; };
} // namespace Li } // namespace Li
} // namespace PD } // namespace PD
+103
View File
@@ -0,0 +1,103 @@
#pragma once
#include <pd/lithium/texture.hpp>
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<u8>& 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<u8>& 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<TextureID> Textures;
/**
* 32Bit Codepoint Dataholder reference map
* **Now using unordered map**
*/
std::unordered_map<u32, Codepoint> CodeMap;
/** TMS */
struct TMELEM {
PD::u32 ID;
PD::fvec2 Size;
std::string Text;
u64 TimeStamp;
};
std::unordered_map<u32, TMELEM> pTMS;
private:
size_t pCurrentTex = 0;
};
} // namespace Li
} // namespace PD
+1
View File
@@ -2,6 +2,7 @@
#include <pd/lithium/command.hpp> #include <pd/lithium/command.hpp>
#include <pd/lithium/drawlist.hpp> #include <pd/lithium/drawlist.hpp>
#include <pd/lithium/font.hpp>
#include <pd/lithium/math.hpp> #include <pd/lithium/math.hpp>
#include <pd/lithium/pools.hpp> #include <pd/lithium/pools.hpp>
#include <pd/lithium/vertex.hpp> #include <pd/lithium/vertex.hpp>
+10 -2
View File
@@ -203,10 +203,16 @@ PD_API void Drawlist::DrawCircleFilled(const fvec2& center, float rad,
PathFill(color); 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, 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<fvec2>& points, u32 color, PD_API void Drawlist::DrawPolyLine(const Pool<fvec2>& points, u32 color,
LiDrawFlags flags, int t) { LiDrawFlags flags, int t) {
@@ -270,6 +276,7 @@ PD_API void Drawlist::DrawConvexPolyFilled(const Pool<fvec2>& points,
PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv,
u32 color) { u32 color) {
cmd.Reserve(4, 6);
cmd.Add(2, 1, 0); cmd.Add(2, 1, 0);
cmd.Add(3, 2, 0); cmd.Add(3, 2, 0);
cmd.Add(Vertex(quad.TopLeft(), uv.TopLeft(), color)); 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, PD_API void Drawlist::PrimTriangle(Command& cmd, const fvec2& a, const fvec2& b,
const fvec2& c, u32 color) { const fvec2& c, u32 color) {
cmd.Reserve(3, 3);
cmd.Add(2, 1, 0); cmd.Add(2, 1, 0);
cmd.Add(Vertex(a, vec2(0.f, 1.f), color)); cmd.Add(Vertex(a, vec2(0.f, 1.f), color));
cmd.Add(Vertex(b, vec2(1.f, 1.f), color)); cmd.Add(Vertex(b, vec2(1.f, 1.f), color));
+275
View File
@@ -0,0 +1,275 @@
#include <pd/core/core.hpp>
#include <pd/drivers/gfx.hpp>
#include <pd/lithium/drawlist.hpp>
#include <pd/lithium/font.hpp>
#include <pd/lithium/math.hpp>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>
#include <map>
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<u8>& 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<int>(ascent * scale);
// Cache to not render same codepoint tex twice
std::map<u32, int> buf_cache;
std::vector<u8> 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<u8>(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<float>(texszs);
uvs.y = off.y / static_cast<float>(texszs);
uvs.z = (off.x + w) / static_cast<float>(texszs);
uvs.w = (off.y + h) / static_cast<float>(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<int>(off.y) + y) * texszs +
(static_cast<int>(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<u8>& 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
Binary file not shown.
+6 -1
View File
@@ -16,7 +16,7 @@ const char* ResourcePath(const char* in) {
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
PD::LogFilter(PD::LogLevel::Warning); // PD::LogFilter(PD::LogLevel::Warning);
Driver drv = Driver::OpenGL3; Driver drv = Driver::OpenGL3;
if (argc == 2) { if (argc == 2) {
if (std::string(argv[1]) == "gl2") { if (std::string(argv[1]) == "gl2") {
@@ -39,6 +39,10 @@ int main(int argc, char** argv) {
PD::Li::Drawlist pList; PD::Li::Drawlist pList;
PD::Image img(ResourcePath("icon.png")); PD::Image img(ResourcePath("icon.png"));
auto pTex = PD::Gfx::LoadTexture(img, img.Width(), img.Height()); 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()) { while (pOs->Mainloop()) {
pOs->ClearViewPort(); pOs->ClearViewPort();
PD::Li::ResetPools(); PD::Li::ResetPools();
@@ -48,6 +52,7 @@ int main(int argc, char** argv) {
pList.BindTexture(pTex); pList.BindTexture(pTex);
pList.DrawRectFilled(pOs->PositionTranslate(PD::fvec2(0.02f, 0.5f)), pList.DrawRectFilled(pOs->PositionTranslate(PD::fvec2(0.02f, 0.5f)),
pOs->SizeTranslate(PD::fvec2(0.3)), 0xffffffff); pOs->SizeTranslate(PD::fvec2(0.3)), 0xffffffff);
pList.DrawText(5, "Hello World!", 0xff0000ff);
PD::Gfx::Reset(); PD::Gfx::Reset();
PD::Gfx::Draw(pList); PD::Gfx::Draw(pList);
pList.Clear(); pList.Clear();
+14 -1
View File
@@ -15,11 +15,21 @@ namespace PD {
struct HorizonCtr::Impl { struct HorizonCtr::Impl {
C3D_RenderTarget* Top = nullptr; C3D_RenderTarget* Top = nullptr;
C3D_RenderTarget* Bottom = nullptr; C3D_RenderTarget* Bottom = nullptr;
uint32_t* pSocBuf = nullptr;
}; };
void HorizonCtr::Init() { void HorizonCtr::Init() {
if (impl) return; if (impl) return;
impl = new Impl(); 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(); romfsInit();
gfxInitDefault(); gfxInitDefault();
consoleInit(GFX_BOTTOM, nullptr); consoleInit(GFX_BOTTOM, nullptr);
@@ -41,6 +51,7 @@ void HorizonCtr::Deinit() {
C3D_Fini(); C3D_Fini();
gfxExit(); gfxExit();
romfsExit(); romfsExit();
socExit();
delete impl; delete impl;
impl = nullptr; impl = nullptr;
} }
@@ -48,7 +59,9 @@ void HorizonCtr::Deinit() {
bool HorizonCtr::Mainloop() { bool HorizonCtr::Mainloop() {
pViewPort = ivec2(400, 240); pViewPort = ivec2(400, 240);
return aptMainLoop(); hidScanInput();
bool _kill = hidKeysUp() & KEY_START;
return aptMainLoop() && !_kill;
} }
void HorizonCtr::ClearViewPort() { void HorizonCtr::ClearViewPort() {
+2
View File
@@ -16,6 +16,8 @@ struct HorizonNX::Impl {
void HorizonNX::Init() { void HorizonNX::Init() {
if (impl) return; if (impl) return;
impl = new Impl(); impl = new Impl();
socketInitializeDefault();
nxlinkStdio();
romfsInit(); romfsInit();
glfwInit(); glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);