377 lines
11 KiB
C++
377 lines
11 KiB
C++
#include "Graphics.h"
|
|
#include <Resource.h>
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
#include <bx/math.h>
|
|
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <algorithm>
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include <stb_image.h>
|
|
|
|
namespace {
|
|
struct PosUVVertex {
|
|
f32 x;
|
|
f32 y;
|
|
f32 z;
|
|
f32 u;
|
|
f32 v;
|
|
};
|
|
|
|
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 }
|
|
};
|
|
|
|
const u16 quad_indices[] = {
|
|
0, 2, 1,
|
|
1, 2, 3
|
|
};
|
|
|
|
FT_Library library;
|
|
FT_Error error;
|
|
|
|
struct Glyph {
|
|
bgfx::TextureHandle tex = { bgfx::kInvalidHandle };
|
|
u32 w = 0, h = 0;
|
|
i32 x = 0, y = 0, adv_x = 0, adv_y = 0;
|
|
};
|
|
|
|
struct Font {
|
|
FT_Face face = nullptr;
|
|
K::Dict<K::Char, Glyph> cache{};
|
|
|
|
Glyph& get_char(K::Char c) {
|
|
if (cache.find(c) == cache.end()) {
|
|
FT_GlyphSlot slot = face->glyph;
|
|
|
|
error = FT_Load_Char(face, c, FT_LOAD_RENDER);
|
|
if (error) {
|
|
K::LogError("Failed to load character");
|
|
}
|
|
|
|
if (slot->bitmap.width != 0) {
|
|
const bgfx::Memory *buf = bgfx::copy(slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows);
|
|
|
|
cache.emplace(c, Glyph{
|
|
bgfx::createTexture2D(
|
|
slot->bitmap.width,
|
|
slot->bitmap.rows,
|
|
false,
|
|
1,
|
|
bgfx::TextureFormat::A8,
|
|
BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP,
|
|
buf
|
|
),
|
|
slot->bitmap.width,
|
|
slot->bitmap.rows,
|
|
slot->bitmap_left,
|
|
slot->bitmap_top,
|
|
static_cast<i32>(slot->advance.x),
|
|
static_cast<i32>(slot->advance.y)
|
|
});
|
|
}
|
|
else cache.emplace(c, Glyph{
|
|
{ bgfx::kInvalidHandle },
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
static_cast<i32>(slot->advance.x),
|
|
static_cast<i32>(slot->advance.y)
|
|
});
|
|
}
|
|
return cache[c];
|
|
}
|
|
};
|
|
|
|
std::optional<Font> GetFont(const char *path) {
|
|
Font f;
|
|
|
|
error = FT_New_Face(library, path, 0, &f.face);
|
|
if (error == FT_Err_Unknown_File_Format) {
|
|
K::LogError("FreeType Unknown_File_Format");
|
|
return std::nullopt;
|
|
}
|
|
else if (error) {
|
|
K::LogError("FreeType font loading error");
|
|
return std::nullopt;
|
|
}
|
|
|
|
error = FT_Set_Char_Size(f.face, 0, 20 * 64, 72, 72);
|
|
if (error) {
|
|
K::LogError("FreeType Set_Char_Size error");
|
|
return std::nullopt;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
void DestroyFont(Font& f) {
|
|
FT_Done_Face(f.face);
|
|
for (auto & it : f.cache) {
|
|
if (isValid(it.second.tex))
|
|
bgfx::destroy(it.second.tex);
|
|
}
|
|
}
|
|
|
|
Font regular, bold;
|
|
bgfx::ProgramHandle imga_program;
|
|
bgfx::ProgramHandle a_program;
|
|
|
|
bgfx::UniformHandle s_texColor;
|
|
bgfx::UniformHandle imga_opacity;
|
|
|
|
bgfx::IndexBufferHandle ibh;
|
|
bgfx::VertexBufferHandle vbh;
|
|
bgfx::VertexLayout pcvDecl;
|
|
|
|
f32 composite_view[16];
|
|
bgfx::UniformHandle composite_A, composite_B, composite_mode;
|
|
bgfx::ProgramHandle composite_pg;
|
|
}
|
|
|
|
namespace K::Graphics {
|
|
bool Init(u16 width, u16 height) {
|
|
error = FT_Init_FreeType(&library);
|
|
if (error) {
|
|
LogError("FreeType init error: " + std::to_string(error));
|
|
return false;
|
|
}
|
|
|
|
if (auto f = GetFont("SourceHanSans-Regular.ttc")) regular = *f; else return false;
|
|
if (auto f = GetFont("SourceHanSans-Bold.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);
|
|
|
|
imga_program = Resource::FetchUnmanagedShaderProgram("ImageAlpha"); // RGBA + Opacity
|
|
a_program = Resource::FetchUnmanagedShaderProgram("AlphaStencil"); // A -> FFFA
|
|
|
|
imga_opacity = bgfx::createUniform("f_opacity", bgfx::UniformType::Vec4); // x -> alpha, yzw are dummies
|
|
s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Sampler);
|
|
|
|
bx::mtxTranslate(composite_view, 0.f, 0.f, 1.0f);
|
|
composite_A = bgfx::createUniform("s_A", bgfx::UniformType::Sampler);
|
|
composite_B = bgfx::createUniform("s_B", bgfx::UniformType::Sampler);
|
|
composite_pg = Resource::FetchUnmanagedShaderProgram("BinaryComposite");
|
|
composite_mode = bgfx::createUniform("u_mode", bgfx::UniformType::Vec4);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Shutdown() {
|
|
DestroyFont(regular);
|
|
DestroyFont(bold);
|
|
FT_Done_FreeType(library);
|
|
|
|
bgfx::destroy(imga_program);
|
|
bgfx::destroy(a_program);
|
|
|
|
bgfx::destroy(ibh);
|
|
bgfx::destroy(vbh);
|
|
|
|
bgfx::destroy(s_texColor);
|
|
bgfx::destroy(imga_opacity);
|
|
|
|
bgfx::destroy(composite_A);
|
|
bgfx::destroy(composite_B);
|
|
bgfx::destroy(composite_mode);
|
|
|
|
bgfx::destroy(composite_pg);
|
|
}
|
|
|
|
// Draws quad
|
|
void DrawQuad(u32 view_id, f32 mtx[16], u64 state, const bgfx::ProgramHandle& pg) {
|
|
bgfx::setTransform(mtx);
|
|
|
|
bgfx::setVertexBuffer(0, vbh);
|
|
bgfx::setIndexBuffer(ibh);
|
|
|
|
bgfx::setState(state);
|
|
bgfx::submit(view_id, pg);
|
|
}
|
|
|
|
// Draws quad with texture on Z = 0
|
|
void DrawTextureWithTransform(u32 view_id, const bgfx::TextureHandle& tex, f32 mtx[16], u64 state, const bgfx::ProgramHandle& pg) {
|
|
bgfx::setTexture(0, s_texColor, tex);
|
|
|
|
DrawQuad(view_id, mtx, state, pg);
|
|
}
|
|
|
|
void DrawTexture(u32 view_id, const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h, u64 state, const bgfx::ProgramHandle& pg) {
|
|
static f32 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);
|
|
|
|
DrawTextureWithTransform(view_id, tex, mtx3, state, pg);
|
|
}
|
|
|
|
void DrawTextureImageAlpha(u32 view_id, const Resource::Resource<Resource::K_R_Still> *img, i32 pos_x, i32 pos_y, f32 alpha) {
|
|
static f32 pack[4]{};
|
|
pack[0] = alpha;
|
|
bgfx::setUniform(imga_opacity, pack);
|
|
|
|
std::shared_lock lk{Resource::resource_lock};
|
|
DrawTexture(view_id, img->tex, pos_x, pos_y, img->w, img->h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), imga_program);
|
|
}
|
|
|
|
void DrawTextureStencilAlpha(u32 view_id, const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h) {
|
|
DrawTexture(view_id, 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 WalkGlyph(u32 view_id, Char c, i32& pos_x, i32& pos_y) {
|
|
Font *f = ®ular;
|
|
|
|
auto& [tx, w, h, l, t, x, y] = f->get_char(c);
|
|
|
|
pos_x += l;
|
|
|
|
pos_x += x >> 6;
|
|
pos_y += y >> 6;
|
|
}
|
|
|
|
void RenderWalkGlyph(u32 view_id, Char c, i32& pos_x, i32& pos_y, u32 col) {
|
|
Font *f = ®ular;
|
|
|
|
auto& [tx, w, h, l, t, x, y] = f->get_char(c);
|
|
|
|
pos_x += l;
|
|
|
|
if (isValid(tx) && w != 0) {
|
|
DrawTextureStencilAlpha(view_id, tx, pos_x, pos_y - (h - t), w, h);
|
|
}
|
|
|
|
pos_x += x >> 6;
|
|
pos_y += y >> 6;
|
|
}
|
|
|
|
void RenderGlyph(u32 view_id, Char c, i32 pos_x, i32 pos_y, u32 col) {
|
|
Font *f = ®ular;
|
|
|
|
auto& [tx, w, h, l, t, x, y] = f->get_char(c);
|
|
|
|
pos_x += l;
|
|
|
|
if (isValid(tx) && w != 0) {
|
|
DrawTextureStencilAlpha(view_id, tx, pos_x, pos_y - (h - t), w, h);
|
|
}
|
|
|
|
pos_x += x >> 6;
|
|
pos_y += y >> 6;
|
|
}
|
|
|
|
void WalkString(u32 view_id, const String& s, i32& pos_x, i32& pos_y) {
|
|
for (const auto& c : s)
|
|
WalkGlyph(view_id, c, pos_x, pos_y);
|
|
}
|
|
|
|
void RenderString(u32 view_id, const String& s, i32 pos_x, i32 pos_y, u32 col) {
|
|
for (const auto& c : s)
|
|
RenderWalkGlyph(view_id, c, pos_x, pos_y, col);
|
|
}
|
|
|
|
void WalkString(u32 view_id, String::const_iterator cbegin, String::const_iterator cend, i32& pos_x, i32& pos_y) {
|
|
while (cbegin < cend)
|
|
WalkGlyph(view_id, *cbegin++, pos_x, pos_y);
|
|
}
|
|
|
|
void RenderString(u32 view_id, String::const_iterator cbegin, String::const_iterator cend, i32 pos_x, i32 pos_y, u32 col) {
|
|
while (cbegin < cend)
|
|
RenderGlyph(view_id, *cbegin++, pos_x, pos_y, col);
|
|
}
|
|
|
|
void RenderStringCentered(u32 view_id, const String& s, i32 pos_x, i32 pos_y, u32 col) {
|
|
i32 copy_x = 0, copy_y = 0;
|
|
WalkString(view_id, s, copy_x, copy_y);
|
|
RenderString(view_id, s, pos_x - copy_x / 2, pos_y, col);
|
|
}
|
|
|
|
// I cannot reason this function, returns where the string would end if it was not halted
|
|
i32 RenderSubstringBox(u32 view_id, const String& s, i32& pos_x, i32& pos_y, i32 reset_x, u32 w, u32 col, size_t s_end) {
|
|
Font *f = ®ular;
|
|
|
|
String::const_iterator last = s.cbegin(), end = s.cend(), pos = std::find(last, end, u' '),
|
|
halt = s_end > s.length() ? end : last + s_end;
|
|
if (pos == end) { // Render the entire thing with hard break if we don't split at all
|
|
String::const_iterator last_copy = last;
|
|
i32 copy_x = pos_x, copy_y = pos_y;
|
|
while (last != std::min(pos, halt)) {
|
|
RenderGlyph(view_id, *last++, pos_x, pos_y, col);
|
|
if (pos_x - reset_x > w) {
|
|
pos_x = reset_x;
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
}
|
|
if (last >= halt) {
|
|
WalkString(view_id, last_copy, pos, copy_x, copy_y);
|
|
return copy_x;
|
|
};
|
|
}
|
|
}
|
|
else while (last != end) { // Loop through all segments
|
|
i32 copy_x = pos_x, old_x = pos_x, copy_y = pos_y;
|
|
WalkString(view_id, last, pos, copy_x, copy_y);
|
|
if (copy_x - reset_x > w) { // if walk exceeded max w
|
|
copy_x = reset_x, copy_y = pos_y; // try walk again from leftmost of box, see if a soft break works; reset y from last walk
|
|
WalkString(view_id, last, pos, copy_x, copy_y);
|
|
if (copy_x - reset_x > w) { // soft break won't work, go back to old x and hard break
|
|
pos_x = old_x;
|
|
while (last != pos) {
|
|
RenderGlyph(view_id, *last++, pos_x, pos_y, col);
|
|
if (pos_x - reset_x > w) {
|
|
pos_x = reset_x;
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
}
|
|
if (last >= halt) return copy_x;
|
|
}
|
|
}
|
|
else { // soft break and kill beginning space
|
|
pos_x = reset_x;
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
last++;
|
|
RenderString(view_id, last, std::min(pos, halt), pos_x, pos_y, col);
|
|
if (last >= halt) return copy_x;
|
|
}
|
|
}
|
|
else {
|
|
RenderString(view_id, last, std::min(pos, halt), pos_x, pos_y, col);
|
|
if (last >= halt) return copy_x;
|
|
}
|
|
|
|
last = pos;
|
|
if (pos != end)
|
|
pos = std::find(std::next(pos), end, ' ');
|
|
}
|
|
return pos_x;
|
|
}
|
|
|
|
void Composite(u32 view_id, bgfx::FrameBufferHandle fb, bgfx::TextureHandle composite, bgfx::TextureHandle from, Blending mode, u16 w, u16 h, f32 proj[16], f32 transform[16]) {
|
|
static f32 pack[4]{};
|
|
pack[3] = pack[2] = pack[1] = pack[0] = static_cast<f32>(mode);
|
|
|
|
bgfx::setViewMode(view_id, bgfx::ViewMode::Sequential);
|
|
bgfx::setViewClear(view_id, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL);
|
|
bgfx::setViewFrameBuffer(view_id, fb);
|
|
bgfx::setViewTransform(view_id, composite_view, proj);
|
|
bgfx::setViewRect(view_id, 0, 0, w, h);
|
|
|
|
bgfx::setTransform(transform);
|
|
bgfx::setVertexBuffer(0, vbh);
|
|
bgfx::setIndexBuffer(ibh);
|
|
bgfx::setTexture(0, composite_A, from); // A
|
|
bgfx::setTexture(1, composite_B, composite); // B
|
|
bgfx::setUniform(composite_mode, pack); // A over B
|
|
bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A);
|
|
bgfx::submit(view_id, composite_pg);
|
|
}
|
|
}
|