Add font batch renderer

This commit is contained in:
Riyyi
2021-01-13 14:03:00 +01:00
parent 0425db291f
commit e95d3a560e
8 changed files with 639 additions and 23 deletions
+73 -1
View File
@@ -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<CharacterVertex, Renderer::vertexPerQuad> 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<Transform>(cube), colors);
Renderer2D::the().drawQuad(std::make_shared<Transform>(cube2), { 0.5f, 0.6f, 0.8f, 1.0f }, m_texture);
Renderer2D::the().drawQuad(std::make_shared<Transform>(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();
+2
View File
@@ -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<Texture> m_texture;
std::shared_ptr<Texture> m_texture2;
std::shared_ptr<Font> m_font;
//
static Application* s_instance;
+155
View File
@@ -0,0 +1,155 @@
#include <bits/stdint-uintn.h>
#include <string> // 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<Texture>(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<std::string> 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>(character));
}
}
const std::string Font::findAction(const std::string& line)
{
return line.substr(0, line.find(" "));
}
const std::vector<std::string> Font::findColumns(const std::string& line)
{
std::vector<std::string> 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<std::string>& 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>& font)
{
// Construct (key, value) pair and insert it into the unordered_map
m_fontList.emplace(name, font);
}
std::shared_ptr<Font> FontManager::load(const std::string& name)
{
if (exists(name)) {
return get(name);
}
std::shared_ptr<Font> font = std::make_shared<Font>(name);
add(name, font);
return get(name);
}
std::shared_ptr<Font> 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>& 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 << " }";
}
}
+87
View File
@@ -0,0 +1,87 @@
#ifndef FONT_H
#define FONT_H
#include <cstdint> // int32_t
#include <memory> // std::shared_ptr
#include <string> // std::string
#include <unordered_map> // std::unordered_map
#include <vector> // std::vector
#include <glm/ext/vector_int2.hpp> // 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<Character>& get(unsigned char c) const { return m_characterList.at(c); }
inline const std::shared_ptr<Character>& operator[](unsigned char c) const { return m_characterList.at(c); }
inline std::string name() const { return m_name; }
inline const std::shared_ptr<Texture>& texture() const { return m_texture; }
private:
void parseFont(const std::string& font);
const std::string findAction(const std::string& line);
const std::vector<std::string> findColumns(const std::string& line);
const std::string findValue(const std::string& key, const std::vector<std::string>& columns);
std::string m_name;
std::shared_ptr<Texture> m_texture;
std::unordered_map<unsigned char, std::shared_ptr<Character>> m_characterList;
};
// -----------------------------------------
class FontManager {
public:
void initialize();
void destroy();
void add(const std::string& name, const std::shared_ptr<Font>& font);
std::shared_ptr<Font> load(const std::string& name);
std::shared_ptr<Font> get(const std::string& name);
bool exists(const std::string& name);
void remove(const std::string& name);
void remove(const std::shared_ptr<Font>& shader);
static inline FontManager& the() { return *s_instance; }
private:
std::unordered_map<std::string, std::shared_ptr<Font>> 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
+175 -22
View File
@@ -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<VertexArray>();
}
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<QuadVertex[]>(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<VertexArray>();
// Create vertex buffer
auto vertexBuffer = std::make_shared<VertexBuffer>(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<VertexBuffer>(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<IndexBuffer>(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<CharacterVertex[]>(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<VertexBuffer>(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<IndexBuffer>(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<CharacterVertex, vertexPerQuad>& characterQuad, glm::vec4 color, std::shared_ptr<Texture> texture)
{
drawCharacter(characterQuad, glm::mat4 { color, color, color, color }, texture);
}
void RendererCharacter::drawCharacter(std::array<CharacterVertex, vertexPerQuad>& characterQuad, glm::mat4 color, std::shared_ptr<Texture> 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();
}
}
+46
View File
@@ -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<CharacterVertex, vertexPerQuad>& characterQuad, glm::vec4 color, std::shared_ptr<Texture> texture);
void drawCharacter(std::array<CharacterVertex, vertexPerQuad>& characterQuad, glm::mat4 color, std::shared_ptr<Texture> 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<CharacterVertex[]> m_vertexBufferBase = nullptr;
CharacterVertex* m_vertexBufferPtr = nullptr;
static RendererCharacter* s_instance;
};
}
#endif // RENDERER_H