#include #include FT_FREETYPE_H #include "Graphics.h" #include #include #include #define STB_IMAGE_IMPLEMENTATION #include namespace { using namespace ADVect::Graphics; bgfx::ShaderHandle loadShader(const std::string& FILENAME) { std::string shaderPath{}; switch (bgfx::getRendererType()) { case bgfx::RendererType::Noop: case bgfx::RendererType::Direct3D9: shaderPath = "shaders/dx9/"; break; case bgfx::RendererType::Direct3D11: case bgfx::RendererType::Direct3D12: shaderPath = "shaders/dx11/"; break; case bgfx::RendererType::Gnm: shaderPath = "shaders/pssl/"; break; case bgfx::RendererType::Metal: shaderPath = "shaders/metal/"; break; case bgfx::RendererType::OpenGL: shaderPath = "shaders/glsl/"; break; case bgfx::RendererType::OpenGLES: shaderPath = "shaders/essl/"; break; case bgfx::RendererType::Vulkan: shaderPath = "shaders/spirv/"; break; } std::string filePath = shaderPath + FILENAME; FILE* file = fopen(filePath.c_str(), "rb"); fseek(file, 0, SEEK_END); long fileSize = ftell(file); fseek(file, 0, SEEK_SET); const bgfx::Memory* mem = bgfx::alloc(fileSize + 1); fread(mem->data, 1, fileSize, file); mem->data[mem->size - 1] = '\0'; fclose(file); return bgfx::createShader(mem); } struct PosUVVertex { f32 x; f32 y; f32 z; f32 u; f32 v; }; static PosUVVertex quad_vert[] = { { 0.f, 0.f, 0.0f, 0.0f, 0.0f }, { 0.f, 1.f, 0.0f, 0.0f, 1.0f }, { 1.f, 0.f, 0.0f, 1.0f, 0.0f }, { 1.f, 1.f, 0.0f, 1.0f, 1.0f } }; static const uint16_t quad_indices[] = { 0, 2, 1, 1, 2, 3 }; FT_Library library; FT_Error error; struct Font { FT_Face face = nullptr; std::unordered_map cache{}; void cache_char(NVL::Char c) { FT_GlyphSlot slot = face->glyph; error = FT_Load_Char(face, c, FT_LOAD_RENDER); if (error) { std::cerr << "ADV: Failed to load character" << std::endl; } if (cache.find(c) == cache.end() && slot->bitmap.width != 0) { const bgfx::Memory* buf = bgfx::copy(slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows); cache.emplace(c, bgfx::createTexture2D( slot->bitmap.width, slot->bitmap.rows, false, 1, bgfx::TextureFormat::A8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP, buf )); } } }; std::optional init_font(const char* path) { Font f; error = FT_New_Face(library, path, 0, &f.face); if (error == FT_Err_Unknown_File_Format) { std::cerr << "ADV: FreeType Unknown_File_Format: " << error << std::endl; return std::nullopt; } else if (error) { std::cerr << "ADV: FreeType font loading error: " << error << std::endl; return std::nullopt; } error = FT_Set_Char_Size(f.face, 0, 20 * 64, 72, 72); if (error) { std::cerr << "ADV: FreeType Set_Char_Size error: " << error << std::endl; return std::nullopt; } return f; } void destroy_font(Font& f) { FT_Done_Face(f.face); for (auto it = f.cache.begin(); it != f.cache.end(); it++) { bgfx::destroy(it->second); } } inline bgfx::ProgramHandle load_shader_program(const std::string& shader) { return bgfx::createProgram(loadShader(shader + ".vert.bin"), loadShader(shader + ".frag.bin"), true); } std::optional get_image_texture(const std::string& file) { ImageTexture i; i.buffer = stbi_load(file.c_str(), &i.w, &i.h, &i.channels, 0); if (i.buffer == NULL) { std::cerr << "ADV: STB IMAGE loading failed" << std::endl; return std::nullopt; } const bgfx::Memory* buf = bgfx::makeRef(i.buffer, i.w * i.h * i.channels * sizeof(u8)); i.tx = bgfx::createTexture2D( i.w, i.h, false, 1, i.channels == 4 ? bgfx::TextureFormat::RGBA8 : bgfx::TextureFormat::RGB8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP, buf ); return i; } void destroy_image_texture(ImageTexture& i) { stbi_image_free(i.buffer); bgfx::destroy(i.tx); } Font regular, bold; bgfx::ProgramHandle a_program, img_program; bgfx::UniformHandle s_texColor; bgfx::IndexBufferHandle ibh; bgfx::VertexBufferHandle vbh; bgfx::VertexLayout pcvDecl; std::unordered_map imgs; } namespace ADVect::Graphics { bool Init(u16 width, u16 height) { error = FT_Init_FreeType(&library); if (error) { std::cerr << "ADV: FreeType init error: " << error << std::endl; return false; } if (auto f = init_font("SourceHanSerif-Regular.ttc")) regular = *f; else return false; if (auto f = init_font("SourceHanSerif-Heavy.ttc")) bold = *f; else return false; pcvDecl.begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); ibh = bgfx::createIndexBuffer(bgfx::makeRef(quad_indices, sizeof(quad_indices))); vbh = bgfx::createVertexBuffer(bgfx::makeRef(quad_vert, sizeof(quad_vert)), pcvDecl); img_program = load_shader_program("test"); a_program = load_shader_program("swizzle_aaaa"); s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Sampler); return true; } void Shutdown() { destroy_font(regular); destroy_font(bold); FT_Done_FreeType(library); for (auto it = imgs.begin(); it != imgs.end(); it++) { destroy_image_texture(it->second); } bgfx::destroy(a_program); bgfx::destroy(img_program); bgfx::destroy(ibh); bgfx::destroy(vbh); bgfx::destroy(s_texColor); } // Draws quad with texture on Z = 0 void DrawTexture(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h, u64 state, const bgfx::ProgramHandle& pg) { float mtx1[16], mtx2[16], mtx3[16]; bx::mtxTranslate(mtx1, pos_x, pos_y, 0.0f); bx::mtxScale(mtx2, w, h, 1.0f); bx::mtxMul(mtx3, mtx2, mtx1); bgfx::setTransform(mtx3); bgfx::setVertexBuffer(0, vbh); bgfx::setIndexBuffer(ibh); bgfx::setTexture(0, s_texColor, tex); bgfx::setState(state); bgfx::submit(0, pg); } void DrawTextureImage(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h) { DrawTexture(tex, pos_x, pos_y, w, h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), img_program); } void DrawTextureImage(const ImageTexture& img, i32 pos_x, i32 pos_y) { DrawTextureImage(img.tx, pos_x, pos_y, img.w, img.h); } ImageTexture* GetImageTextureFromFile(const std::string& file) { if (imgs.find(file) == imgs.end()) { ImageTexture img; if (auto i = get_image_texture(file)) { img = *i; imgs[file] = std::move(img); return &imgs[file]; } } return nullptr; } void DrawTextureStencilAlpha(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h) { DrawTexture(tex, pos_x, pos_y, w, h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), a_program); } void RenderString(const NVL::String& s, u32& pos_x, u32& pos_y, u32 col, u32 style_flags) { Font* f = ®ular; switch (style_flags) { case TEXT_NONE: break; case TEXT_BOLD: f = &bold; break; case TEXT_ITALIC: break; case TEXT_ITALIC | TEXT_BOLD: break; } FT_GlyphSlot& slot = f->face->glyph; for (const auto& c : s) { f->cache_char(c); pos_x += slot->bitmap_left; float ypos = pos_y - (slot->bitmap.rows - slot->bitmap_top); if (slot->bitmap.width != 0) { DrawTextureStencilAlpha((f->cache)[c], pos_x, ypos, slot->bitmap.width, slot->bitmap.rows); } pos_x += slot->advance.x >> 6; pos_y += slot->advance.y >> 6; } } void RenderString(const NVL::String& s, u32&& pos_x, u32&& pos_y, u32 col, u32 style_flags) { u32 x = pos_x, y = pos_y; RenderString(s, x, y, col, style_flags); } void RenderStringMarkup(const std::vector& s, u32 pos_x, u32 pos_y, u32 col) { const NVL::String& str = std::get(s[0].value); std::vector ms{}; for (const NVL::Environment::Variable& markup : std::get>(s[1].value)) { ms.push_back(NVL::Environment::UnpackMarkupVariable(markup)); } if (ms.empty()) { RenderString(str, pos_x, pos_y, col, TEXT_NONE); return; } if (ms[0].begin != 0) RenderString(str.substr(0, ms[0].begin), pos_x, pos_y, col, TEXT_NONE); // We assume markups are already sorted for (size_t i = 0; i < ms.size(); i++) { u32 style_flags = TEXT_NONE; const NVL::String* ruby = nullptr; for (const auto& e : ms[i].efs) { if (e.first == u"b") { style_flags |= TEXT_BOLD; } else if (e.first == u"i") { style_flags |= TEXT_ITALIC; } else if (e.first == u"u") { style_flags |= TEXT_UNDERLINE; } else if (e.first == u"s") { style_flags |= TEXT_STRIKETHROUGH; } else if (e.first == u"o") { style_flags |= TEXT_OVERLINE; } else if (e.first == u"rb") { ruby = &e.second[0]; } else { std::cerr << "ADV: Unrecognized text markup: " << NVL::to_std_string(e.first) << std::endl; } } if (ruby != nullptr) { u32 temp_x = pos_x, temp_y = pos_y + 20; RenderString(*ruby, temp_x, temp_y, col, TEXT_NONE); } RenderString(str.substr(ms[i].begin, ms[i].end - ms[i].begin), pos_x, pos_y, col, style_flags); if (i != ms.size() - 1 && ms[i + 1].begin - 1 != ms[i].end) RenderString(str.substr(ms[i].end, ms[i + 1].begin - ms[i].end), pos_x, pos_y, col, TEXT_NONE); } if (ms.back().end != str.length()) RenderString(str.substr(ms.back().end), pos_x, pos_y, col, TEXT_NONE); } }