From e95d3a560e95a7a07dd17b29b3b4dddbcf642f35 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Wed, 13 Jan 2021 14:03:00 +0100 Subject: [PATCH] Add font batch renderer --- assets/glsl/batch-font.frag | 65 ++++++++ assets/glsl/batch-font.vert | 36 +++++ inferno/src/inferno/application.cpp | 74 ++++++++- inferno/src/inferno/application.h | 2 + inferno/src/inferno/render/font.cpp | 155 +++++++++++++++++++ inferno/src/inferno/render/font.h | 87 +++++++++++ inferno/src/inferno/render/renderer.cpp | 197 +++++++++++++++++++++--- inferno/src/inferno/render/renderer.h | 46 ++++++ 8 files changed, 639 insertions(+), 23 deletions(-) create mode 100644 assets/glsl/batch-font.frag create mode 100644 assets/glsl/batch-font.vert create mode 100644 inferno/src/inferno/render/font.cpp create mode 100644 inferno/src/inferno/render/font.h diff --git a/assets/glsl/batch-font.frag b/assets/glsl/batch-font.frag new file mode 100644 index 0000000..f990736 --- /dev/null +++ b/assets/glsl/batch-font.frag @@ -0,0 +1,65 @@ +#version 450 core + +layout(location = 0) out vec4 color; + +in vec4 v_color; +in vec2 v_textureCoordinates; +in flat float v_textureIndex; +in float v_width; +in float v_edge; +in float v_borderWidth; +in float v_borderEdge; +in vec4 v_borderColor; +in flat float v_offset; + +uniform sampler2D u_textures[32]; + +float alpha(float textureAlpha) +{ + float distance = 1.0f - textureAlpha; + float alpha = 1.0f - smoothstep(v_width, v_width + v_edge, distance); + + return alpha; +} + +void main() +{ + vec4 textureColor = v_color; + switch(int(v_textureIndex)) { + case 0: break; // Texture unit 0 is reserved for no texture + case 1: textureColor.a = alpha(texture(u_textures[1], v_textureCoordinates).a); break; + // case 1: textureColor *= texture(u_textures[1], v_textureCoordinates); break; + case 2: textureColor.a = alpha(texture(u_textures[2], v_textureCoordinates).a); break; + case 3: textureColor.a = alpha(texture(u_textures[3], v_textureCoordinates).a); break; + case 4: textureColor.a = alpha(texture(u_textures[4], v_textureCoordinates).a); break; + case 5: textureColor.a = alpha(texture(u_textures[5], v_textureCoordinates).a); break; + case 6: textureColor.a = alpha(texture(u_textures[6], v_textureCoordinates).a); break; + case 7: textureColor.a = alpha(texture(u_textures[7], v_textureCoordinates).a); break; + case 8: textureColor.a = alpha(texture(u_textures[8], v_textureCoordinates).a); break; + case 9: textureColor.a = alpha(texture(u_textures[9], v_textureCoordinates).a); break; + case 10: textureColor.a = alpha(texture(u_textures[10], v_textureCoordinates).a); break; + case 11: textureColor.a = alpha(texture(u_textures[11], v_textureCoordinates).a); break; + case 12: textureColor.a = alpha(texture(u_textures[12], v_textureCoordinates).a); break; + case 13: textureColor.a = alpha(texture(u_textures[13], v_textureCoordinates).a); break; + case 14: textureColor.a = alpha(texture(u_textures[14], v_textureCoordinates).a); break; + case 15: textureColor.a = alpha(texture(u_textures[15], v_textureCoordinates).a); break; + case 16: textureColor.a = alpha(texture(u_textures[16], v_textureCoordinates).a); break; + case 17: textureColor.a = alpha(texture(u_textures[17], v_textureCoordinates).a); break; + case 18: textureColor.a = alpha(texture(u_textures[18], v_textureCoordinates).a); break; + case 19: textureColor.a = alpha(texture(u_textures[19], v_textureCoordinates).a); break; + case 20: textureColor.a = alpha(texture(u_textures[20], v_textureCoordinates).a); break; + case 21: textureColor.a = alpha(texture(u_textures[21], v_textureCoordinates).a); break; + case 22: textureColor.a = alpha(texture(u_textures[22], v_textureCoordinates).a); break; + case 23: textureColor.a = alpha(texture(u_textures[23], v_textureCoordinates).a); break; + case 24: textureColor.a = alpha(texture(u_textures[24], v_textureCoordinates).a); break; + case 25: textureColor.a = alpha(texture(u_textures[25], v_textureCoordinates).a); break; + case 26: textureColor.a = alpha(texture(u_textures[26], v_textureCoordinates).a); break; + case 27: textureColor.a = alpha(texture(u_textures[27], v_textureCoordinates).a); break; + case 28: textureColor.a = alpha(texture(u_textures[28], v_textureCoordinates).a); break; + case 29: textureColor.a = alpha(texture(u_textures[29], v_textureCoordinates).a); break; + case 30: textureColor.a = alpha(texture(u_textures[30], v_textureCoordinates).a); break; + case 31: textureColor.a = alpha(texture(u_textures[31], v_textureCoordinates).a); break; + } + // textureColor.a = 1; // tmp + color = textureColor; +} diff --git a/assets/glsl/batch-font.vert b/assets/glsl/batch-font.vert new file mode 100644 index 0000000..372be09 --- /dev/null +++ b/assets/glsl/batch-font.vert @@ -0,0 +1,36 @@ +#version 450 core + +layout(location = 0) in vec3 a_position; +layout(location = 1) in vec4 a_color; +layout(location = 2) in vec2 a_textureCoordinates; +layout(location = 3) in float a_textureIndex; +layout(location = 4) in float a_width; +layout(location = 5) in float a_edge; +layout(location = 6) in float a_borderWidth; +layout(location = 7) in float a_borderEdge; +layout(location = 8) in vec4 a_borderColor; +layout(location = 9) in float a_offset; + +out vec4 v_color; +out vec2 v_textureCoordinates; +out flat float v_textureIndex; +out float v_width; +out float v_edge; +out float v_borderWidth; +out float v_borderEdge; +out vec4 v_borderColor; +out flat float v_offset; + +void main() +{ + v_color = a_color; + v_textureCoordinates = a_textureCoordinates; + v_textureIndex = a_textureIndex; + v_width = a_width; + v_edge = a_edge; + v_borderWidth = a_borderWidth; + v_borderEdge = a_borderEdge; + v_borderColor = a_borderColor; + v_offset = a_offset; + gl_Position = vec4(a_position, 1.0f); +} diff --git a/inferno/src/inferno/application.cpp b/inferno/src/inferno/application.cpp index 3bd2121..5c249d1 100644 --- a/inferno/src/inferno/application.cpp +++ b/inferno/src/inferno/application.cpp @@ -9,13 +9,14 @@ #include "inferno/input.h" #include "inferno/inputcodes.h" #include "inferno/log.h" -#include "inferno/settings.h" #include "inferno/render/buffer.h" #include "inferno/render/camera.h" #include "inferno/render/context.h" +#include "inferno/render/font.h" #include "inferno/render/renderer.h" #include "inferno/render/shader.h" #include "inferno/render/texture.h" +#include "inferno/settings.h" #include "inferno/time.h" #include "inferno/window.h" @@ -49,14 +50,24 @@ namespace Inferno { Renderer2D* renderer2D = new Renderer2D(); renderer2D->initialize(); + RendererCharacter* rendererCharacter = new RendererCharacter(); + rendererCharacter->initialize(); + + FontManager* fontManager = new FontManager(); + fontManager->initialize(); + // Load assets m_texture = TextureManager::the().load("assets/gfx/test.png"); m_texture2 = TextureManager::the().load("assets/gfx/test-inverted.png"); + + m_font = FontManager::the().load("assets/fnt/dejavu-sans"); } Application::~Application() { + FontManager::the().destroy(); + RendererCharacter::the().destroy(); Renderer2D::the().destroy(); TextureManager::the().destroy(); ShaderManager::the().destroy(); @@ -84,6 +95,63 @@ namespace Inferno { Transform cube3({2.2f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}); cube3.update(); + Transform cube4({0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}); + cube4.update(); + + std::array character; + + // character.at(0).quad.textureCoordinates = { 0.0f, 0.0f }; // bottom left + // character.at(1).quad.textureCoordinates = { 1.0f, 0.0f }; + // character.at(2).quad.textureCoordinates = { 1.0f, 1.0f }; // top right + // character.at(3).quad.textureCoordinates = { 0.0f, 1.0f }; + + auto f = FontManager::the().get("assets/fnt/dejavu-sans"); + auto c = f->get('5'); + dbg() << c->position << " " << c->size; + + uint32_t textureWidth = f->texture()->width(); + uint32_t textureHeight = f->texture()->height(); + ASSERT(textureWidth == textureHeight, "Invalid font texture!"); + + float quadWidth = (c->size.x / (float)textureWidth) - 0.04; // @Todo something wrong with the width + float quadHeight = c->size.y / (float)textureHeight; + + character.at(0).quad.position = { -quadWidth, -quadHeight, 0.0f }; // bottom left + character.at(1).quad.position = { quadWidth, -quadHeight, 0.0f }; // bottom right + character.at(2).quad.position = { quadWidth, quadHeight, 0.0f }; // top right + character.at(3).quad.position = { -quadWidth, quadHeight, 0.0f }; // top left + + glm::vec2 x { + 1 - (textureWidth - c->position.x) / (float)textureWidth, + 1 - (textureWidth - c->position.x - c->size.x) / (float)textureWidth + }; + glm::vec2 y { + (textureHeight - c->position.y - c->size.y) / (float)textureHeight, + (textureHeight - c->position.y) / (float)textureHeight + }; + + dbg() << y; + + character.at(0).quad.textureCoordinates = { x.x, y.x }; + character.at(1).quad.textureCoordinates = { x.y, y.x }; + character.at(2).quad.textureCoordinates = { x.y, y.y }; + character.at(3).quad.textureCoordinates = { x.x, y.y }; + + character.at(0).quad.textureIndex = 1.0f; + character.at(1).quad.textureIndex = 1.0f; + character.at(2).quad.textureIndex = 1.0f; + character.at(3).quad.textureIndex = 1.0f; + + // pos + // texcoords + // + // width + // edge + // borderwidth + // borderedge + // bordercolor + // offse + while(!m_window->shouldClose()) { float time = Time::time(); @@ -104,12 +172,16 @@ namespace Inferno { RenderCommand::clear(); Renderer2D::the().beginScene(m_cameraP); // camera, lights, environment + RendererCharacter::the().beginScene(); Renderer2D::the().drawQuad(std::make_shared(cube), colors); Renderer2D::the().drawQuad(std::make_shared(cube2), { 0.5f, 0.6f, 0.8f, 1.0f }, m_texture); Renderer2D::the().drawQuad(std::make_shared(cube3), { 1.0f, 1.0f, 1.0f, 1.0f }, m_texture2); + RendererCharacter::the().drawCharacter(character, {1,1,1,1}, f->texture()); + Renderer2D::the().endScene(); + RendererCharacter::the().endScene(); m_window->render(); diff --git a/inferno/src/inferno/application.h b/inferno/src/inferno/application.h index 455e6a4..62462c8 100644 --- a/inferno/src/inferno/application.h +++ b/inferno/src/inferno/application.h @@ -6,6 +6,7 @@ namespace Inferno { class Event; + class Font; class KeyPressEvent; class MousePositionEvent; class OrthographicCamera; @@ -49,6 +50,7 @@ namespace Inferno { // std::shared_ptr m_texture; std::shared_ptr m_texture2; + std::shared_ptr m_font; // static Application* s_instance; diff --git a/inferno/src/inferno/render/font.cpp b/inferno/src/inferno/render/font.cpp new file mode 100644 index 0000000..8db5f86 --- /dev/null +++ b/inferno/src/inferno/render/font.cpp @@ -0,0 +1,155 @@ +#include +#include // std::getline + +#include "inferno/assertions.h" +#include "inferno/file.h" +#include "inferno/render/font.h" +#include "inferno/render/texture.h" + +namespace Inferno { + + Font::Font(const std::string& name) + { + std::string path = name + ".fnt"; + std::string image = name + ".png"; + + std::string font = File::read(path); + parseFont(font); + + m_texture = std::make_shared(image); + } + + void Font::parseFont(const std::string& font) + { + std::istringstream iss(font); + for (std::string line; std::getline(iss, line);) { + + if (findAction(line).compare("char") != 0) { + continue; + } + + const std::vector data = findColumns(line); + + unsigned char id = std::stoi(findValue("id", data)); + Character character = { + { std::stoi(findValue("x", data)), std::stoi(findValue("y", data)) }, + { std::stoi(findValue("width", data)), std::stoi(findValue("height", data)) }, + { std::stoi(findValue("xoffset", data)), std::stoi(findValue("yoffset", data)) }, + std::stoi(findValue("xadvance", data)) + }; + m_characterList.emplace(id, std::make_shared(character)); + } + } + + const std::string Font::findAction(const std::string& line) + { + return line.substr(0, line.find(" ")); + } + + const std::vector Font::findColumns(const std::string& line) + { + std::vector elements; + + size_t index = 0; + size_t find = 0; + size_t findFirstNotOf = 0; + // Loop over line characters + while (find != std::string::npos) { + find = line.find(" ", index); + findFirstNotOf = line.find_first_not_of(" ", index); + + // Add element + if (find > findFirstNotOf) { + elements.push_back(line.substr(index, find - findFirstNotOf)); + index += 1 + find - findFirstNotOf; + } + // Skip double spaces + else { + index = findFirstNotOf; + } + } + + return elements; + } + + const std::string Font::findValue(const std::string& key, const std::vector& columns) + { + size_t find = 0; + // Loop over columns + for (auto it = columns.begin(); it != columns.end(); it++) { + find = it->find(key + "="); + if (find != std::string::npos) { + return it->substr(key.length() + 1); + } + } + + return ""; + } + +// ----------------------------------------- + + FontManager* FontManager::s_instance = nullptr; + + void FontManager::initialize() + { + ASSERT(!s_instance, "FontManager already exists!"); + s_instance = this; + + dbg(Log::Info) << "FontManager initialized"; + } + + void FontManager::destroy() + { + delete s_instance; + s_instance = nullptr; + } + + void FontManager::add(const std::string& name, const std::shared_ptr& font) + { + // Construct (key, value) pair and insert it into the unordered_map + m_fontList.emplace(name, font); + } + + std::shared_ptr FontManager::load(const std::string& name) + { + if (exists(name)) { + return get(name); + } + + std::shared_ptr font = std::make_shared(name); + add(name, font); + return get(name); + } + + std::shared_ptr FontManager::get(const std::string& name) + { + return exists(name) ? m_fontList.at(name) : nullptr; + } + + bool FontManager::exists(const std::string& name) + { + return m_fontList.find(name) != m_fontList.end(); + } + + void FontManager::remove(const std::string& name) + { + if (exists(name)) { + m_fontList.erase(name); + } + } + + void FontManager::remove(const std::shared_ptr& font) + { + if (exists(font->name())) { + m_fontList.erase(font->name()); + } + } + +// ----------------------------------------- + + const LogStream& operator<<(const LogStream& stream, const glm::ivec2& value) + { + return stream << "{ " << value.x << ", " << value.y << " }"; + } + +} diff --git a/inferno/src/inferno/render/font.h b/inferno/src/inferno/render/font.h new file mode 100644 index 0000000..33e7b4f --- /dev/null +++ b/inferno/src/inferno/render/font.h @@ -0,0 +1,87 @@ +#ifndef FONT_H +#define FONT_H + +#include // int32_t +#include // std::shared_ptr +#include // std::string +#include // std::unordered_map +#include // std::vector + +#include // glm::ivec2 + +#include "inferno/log.h" + +namespace Inferno { + +class Texture; + + struct Character { + glm::ivec2 position; // Position + glm::ivec2 size; // Width/height + glm::ivec2 offset; // Offset from baseline to left / top of glyph + int32_t advance; // Amount to advance to next glyph + }; + +// ----------------------------------------- + + class Font { + public: + Font(const std::string& name); + virtual ~Font() {} + + inline const std::shared_ptr& get(unsigned char c) const { return m_characterList.at(c); } + inline const std::shared_ptr& operator[](unsigned char c) const { return m_characterList.at(c); } + + inline std::string name() const { return m_name; } + inline const std::shared_ptr& texture() const { return m_texture; } + + private: + void parseFont(const std::string& font); + const std::string findAction(const std::string& line); + const std::vector findColumns(const std::string& line); + const std::string findValue(const std::string& key, const std::vector& columns); + + std::string m_name; + std::shared_ptr m_texture; + std::unordered_map> m_characterList; + }; + +// ----------------------------------------- + + class FontManager { + public: + void initialize(); + void destroy(); + + void add(const std::string& name, const std::shared_ptr& font); + std::shared_ptr load(const std::string& name); + std::shared_ptr get(const std::string& name); + bool exists(const std::string& name); + + void remove(const std::string& name); + void remove(const std::shared_ptr& shader); + + static inline FontManager& the() { return *s_instance; } + + private: + std::unordered_map> m_fontList; + + static FontManager* s_instance; + }; + + +// ---------------------------------------- + + const LogStream& operator<<(const LogStream& stream, const glm::ivec2& value); + +} + +#endif // FONT_H + +// FontManager fm; +// Font f = fm.load("path/to/font"); +// Font f2("path/to/font"); +// Character c = f['a']; + +// Look into using signed distance fields for texture map generation ? anti-aliasing for text +// https://youtu.be/d8cfgcJR9Tk diff --git a/inferno/src/inferno/render/renderer.cpp b/inferno/src/inferno/render/renderer.cpp index 5380c0a..4aa7802 100644 --- a/inferno/src/inferno/render/renderer.cpp +++ b/inferno/src/inferno/render/renderer.cpp @@ -57,10 +57,13 @@ namespace Inferno { } // Create shader - m_shader = ShaderManager::the().load("assets/glsl/batch-quad"); + loadShader(); m_shader->bind(); m_shader->setInt("u_textures", samplers, textureUnitPerBatch); m_shader->unbind(); + + // Create vertex array + m_vertexArray = std::make_shared(); } void Renderer::destroy() @@ -121,13 +124,13 @@ namespace Inferno { void Renderer2D::initialize() { - ASSERT(!s_instance, "ShaderManager already exists!"); + ASSERT(!s_instance, "RendererCharacter already exists!"); s_instance = this; Renderer::initialize(); -// CPU -// ----------------------------------------- + // CPU + // --------------------------------- // Create array for storing quads vertices m_vertexBufferBase = std::unique_ptr(new QuadVertex[vertexCount]); @@ -139,22 +142,6 @@ namespace Inferno { m_vertexPositions[2] = { 0.5f, 0.5f, 0.0f, 1.0f }; m_vertexPositions[3] = { -0.5f, 0.5f, 0.0f, 1.0f }; -// GPU -// ----------------------------------------- - - // Create vertex array - m_vertexArray = std::make_shared(); - - // Create vertex buffer - auto vertexBuffer = std::make_shared(sizeof(QuadVertex) * vertexCount); - vertexBuffer->setLayout({ - { BufferElementType::Vec3, "a_position" }, - { BufferElementType::Vec4, "a_color" }, - { BufferElementType::Vec2, "a_textureCoordinates" }, - { BufferElementType::Float, "a_textureIndex" }, - }); - m_vertexArray->addVertexBuffer(std::move(vertexBuffer)); - // Generate indices uint32_t* indices = new uint32_t[indexCount]; @@ -171,6 +158,19 @@ namespace Inferno { offset += vertexPerQuad; } + // GPU + // --------------------------------- + + // Create vertex buffer + auto vertexBuffer = std::make_shared(sizeof(QuadVertex) * vertexCount); + vertexBuffer->setLayout({ + { BufferElementType::Vec3, "a_position" }, + { BufferElementType::Vec4, "a_color" }, + { BufferElementType::Vec2, "a_textureCoordinates" }, + { BufferElementType::Float, "a_textureIndex" }, + }); + m_vertexArray->addVertexBuffer(std::move(vertexBuffer)); + // Create index buffer auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount); m_vertexArray->setIndexBuffer(indexBuffer); @@ -235,6 +235,11 @@ namespace Inferno { m_quadIndex++; } + void Renderer2D::loadShader() + { + m_shader = ShaderManager::the().load("assets/glsl/batch-quad"); + } + void Renderer2D::flush() { if (m_quadIndex == 0) { @@ -244,13 +249,13 @@ namespace Inferno { // Upload vertex data to GPU m_vertexArray->getVertexBuffers().at(0)->uploadData( m_vertexBufferBase.get(), - quadCount * vertexPerQuad * sizeof(QuadVertex)); + m_quadIndex * vertexPerQuad * sizeof(QuadVertex)); bind(); // Render m_shader->setFloat("u_projectionView", s_camera->projection() * s_camera->transform()->transform()); - RenderCommand::drawIndexed(m_vertexArray, quadCount * indexPerQuad); + RenderCommand::drawIndexed(m_vertexArray, m_quadIndex * indexPerQuad); unbind(); } @@ -269,4 +274,152 @@ namespace Inferno { startBatch(); } +// ----------------------------------------- + + RendererCharacter* RendererCharacter::s_instance = nullptr; + + void RendererCharacter::initialize() + { + ASSERT(!s_instance, "RendererCharacter already exists!"); + s_instance = this; + + Renderer::initialize(); + + // CPU + // --------------------------------- + + // Create array for storing quads vertices + m_vertexBufferBase = std::unique_ptr(new CharacterVertex[vertexCount]); + m_vertexBufferPtr = m_vertexBufferBase.get(); + + // Generate indices + + uint32_t* indices = new uint32_t[indexCount]; + + uint32_t offset = 0; + for (uint32_t i = 0; i < indexCount; i += indexPerQuad) { + indices[i + 0] = offset + 0; + indices[i + 1] = offset + 1; + indices[i + 2] = offset + 2; + indices[i + 3] = offset + 2; + indices[i + 4] = offset + 3; + indices[i + 5] = offset + 0; + + offset += vertexPerQuad; + } + + // GPU + // --------------------------------- + + // Create vertex buffer + auto vertexBuffer = std::make_shared(sizeof(CharacterVertex) * vertexCount); + vertexBuffer->setLayout({ + { BufferElementType::Vec3, "a_position" }, + { BufferElementType::Vec4, "a_color" }, + { BufferElementType::Vec2, "a_textureCoordinates" }, + { BufferElementType::Float, "a_textureIndex" }, + { BufferElementType::Float, "a_width" }, + { BufferElementType::Float, "a_edge" }, + { BufferElementType::Float, "a_borderWidth" }, + { BufferElementType::Float, "a_borderEdge" }, + { BufferElementType::Vec4, "a_borderColor" }, + { BufferElementType::Float, "a_offset" }, + }); + m_vertexArray->addVertexBuffer(std::move(vertexBuffer)); + + // Create index buffer + auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount); + m_vertexArray->setIndexBuffer(indexBuffer); + delete[] indices; + + dbg() << "RendererCharacter initialized"; + } + + void RendererCharacter::destroy() + { + delete s_instance; + s_instance = nullptr; + } + + void RendererCharacter::beginScene() + { + } + + void RendererCharacter::endScene() + { + nextBatch(); + } + + void RendererCharacter::drawCharacter(std::array& characterQuad, glm::vec4 color, std::shared_ptr texture) + { + drawCharacter(characterQuad, glm::mat4 { color, color, color, color }, texture); + } + + void RendererCharacter::drawCharacter(std::array& characterQuad, glm::mat4 color, std::shared_ptr texture) + { + // Create a new batch if the quad limit has been reached + if (m_quadIndex >= quadCount) { + nextBatch(); + } + + uint32_t textureUnitIndex = addTextureUnit(texture); + + // Add the quads 4 vertices + for (uint32_t i = 0; i < vertexPerQuad; i++) { + m_vertexBufferPtr->quad.position = characterQuad[i].quad.position; + m_vertexBufferPtr->quad.color = characterQuad[i].quad.color; + m_vertexBufferPtr->quad.textureCoordinates = characterQuad[i].quad.textureCoordinates; + m_vertexBufferPtr->quad.textureIndex = (float)textureUnitIndex; + + m_vertexBufferPtr->width = characterQuad[i].width; + m_vertexBufferPtr->edge = characterQuad[i].edge; + m_vertexBufferPtr->borderWidth = characterQuad[i].borderWidth; + m_vertexBufferPtr->borderEdge = characterQuad[i].borderEdge; + m_vertexBufferPtr->borderColor = characterQuad[i].borderColor; + m_vertexBufferPtr->offset = characterQuad[i].offset; + + m_vertexBufferPtr++; + } + + m_quadIndex++; + } + + void RendererCharacter::loadShader() + { + m_shader = ShaderManager::the().load("assets/glsl/batch-font"); + } + + void RendererCharacter::flush() + { + if (m_quadIndex == 0) { + return; + } + + // Upload vertex data to GPU + m_vertexArray->getVertexBuffers().at(0)->uploadData( + m_vertexBufferBase.get(), + m_quadIndex * vertexPerQuad * sizeof(CharacterVertex)); + + bind(); + + // Render + RenderCommand::drawIndexed(m_vertexArray, m_quadIndex * indexPerQuad); + + unbind(); + } + + void RendererCharacter::startBatch() + { + m_quadIndex = 0; + m_vertexBufferPtr = m_vertexBufferBase.get(); + + m_textureUnitIndex = 1; + } + + void RendererCharacter::nextBatch() + { + flush(); + startBatch(); + } + } diff --git a/inferno/src/inferno/render/renderer.h b/inferno/src/inferno/render/renderer.h index a376358..075b1ea 100644 --- a/inferno/src/inferno/render/renderer.h +++ b/inferno/src/inferno/render/renderer.h @@ -24,6 +24,18 @@ namespace Inferno { float textureIndex = 0; // @Todo get int to pass to fragment correctly }; + struct CharacterVertex { + QuadVertex quad; + + float width = 0.44f; + float edge = 0.15f; + + float borderWidth = 0.7f; + float borderEdge = 0.1f; + glm::vec4 borderColor { 1.0f, 1.0f, 1.0f, 1.0f }; + float offset = 0.0f; + }; + // ----------------------------------------- class RenderCommand { @@ -52,6 +64,7 @@ namespace Inferno { void bind(); void unbind(); + virtual void loadShader() = 0; virtual void flush() = 0; virtual void startBatch() = 0; virtual void nextBatch() = 0; @@ -90,6 +103,7 @@ namespace Inferno { static inline Renderer2D& the() { return *s_instance; } private: + void loadShader() override; void flush() override; void startBatch() override; void nextBatch() override; @@ -106,6 +120,38 @@ namespace Inferno { static Renderer2D* s_instance; }; +// ----------------------------------------- + + class RendererCharacter final : public Renderer { + public: + static const uint32_t quadCount = 1000; + static const uint32_t vertexCount = quadCount * vertexPerQuad; + static const uint32_t indexCount = quadCount * indexPerQuad; + + void initialize() override; + void destroy() override; + + void beginScene(); + void endScene(); + + void drawCharacter(std::array& characterQuad, glm::vec4 color, std::shared_ptr texture); + void drawCharacter(std::array& characterQuad, glm::mat4 color, std::shared_ptr texture); + + static inline RendererCharacter& the() { return *s_instance; } + + private: + void loadShader() override; + void flush() override; + void startBatch() override; + void nextBatch() override; + + // CPU quad vertices + std::unique_ptr m_vertexBufferBase = nullptr; + CharacterVertex* m_vertexBufferPtr = nullptr; + + static RendererCharacter* s_instance; + }; + } #endif // RENDERER_H