8#include <glm/gtc/matrix_transform.hpp>
12#include "graphics/Texture.h"
13#include "io/FileSystem.h"
15#define STB_TRUETYPE_IMPLEMENTATION
16#include "imstb_truetype.h"
24 GLuint shader = glCreateShader(type);
25 glShaderSource(shader, 1, &source,
nullptr);
26 glCompileShader(shader);
29 glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
33 glGetShaderInfoLog(shader,
sizeof(info),
nullptr, info);
34 LOG_ERROR(
"SpriteRenderer shader compile failed: %s", info);
35 glDeleteShader(shader);
41GLuint LinkProgram(
const char *vertexSource,
const char *fragmentSource)
43 GLuint vs = CompileShader(GL_VERTEX_SHADER, vertexSource);
44 GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragmentSource);
45 if (vs == 0 || fs == 0)
47 if (vs != 0) glDeleteShader(vs);
48 if (fs != 0) glDeleteShader(fs);
52 GLuint program = glCreateProgram();
53 glAttachShader(program, vs);
54 glAttachShader(program, fs);
55 glLinkProgram(program);
60 glGetProgramiv(program, GL_LINK_STATUS, &ok);
64 glGetProgramInfoLog(program,
sizeof(info),
nullptr, info);
65 LOG_ERROR(
"SpriteRenderer shader link failed: %s", info);
66 glDeleteProgram(program);
75 const char *vertexSource = R
"(
77 layout(location = 0) in vec2 aPosition;
78 layout(location = 1) in vec2 aUV;
79 layout(location = 2) in vec4 aColor;
81 uniform mat4 uProjection;
90 gl_Position = uProjection * vec4(aPosition, 0.0, 1.0);
94 const char *fragmentSource = R
"(
96 uniform sampler2D uTexture;
97 uniform bool uRedOnly;
105 vec4 tex = texture(uTexture, vUV);
107 tex = vec4(1.0, 1.0, 1.0, tex.r);
109 FragColor = tex * vColor;
113 m_shader = LinkProgram(vertexSource, fragmentSource);
119 glGenVertexArrays(1, &m_vao);
120 glGenBuffers(1, &m_vbo);
122 glBindVertexArray(m_vao);
123 glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
124 glBufferData(GL_ARRAY_BUFFER,
sizeof(Vertex) * 6,
nullptr, GL_DYNAMIC_DRAW);
126 glEnableVertexAttribArray(0);
127 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
reinterpret_cast<void *
>(offsetof(Vertex, position)));
128 glEnableVertexAttribArray(1);
129 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
reinterpret_cast<void *
>(offsetof(Vertex, uv)));
130 glEnableVertexAttribArray(2);
131 glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
reinterpret_cast<void *
>(offsetof(Vertex, color)));
133 glBindVertexArray(0);
134 glBindBuffer(GL_ARRAY_BUFFER, 0);
137 const unsigned char whitePixel[4] = {255, 255, 255, 255};
138 glGenTextures(1, &m_whiteTexture);
139 glBindTexture(GL_TEXTURE_2D, m_whiteTexture);
140 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
141 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
142 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
143 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
144 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
145 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
146 glBindTexture(GL_TEXTURE_2D, 0);
149 m_fontReady = LoadDefaultFont();
152 LOG_WARN(
"SpriteRenderer: default font unavailable; DrawText calls will be ignored");
159 if (m_whiteTexture != 0) glDeleteTextures(1, &m_whiteTexture);
161 if (m_fontTexture != 0) glDeleteTextures(1, &m_fontTexture);
162 if (m_vbo != 0) glDeleteBuffers(1, &m_vbo);
163 if (m_vao != 0) glDeleteVertexArrays(1, &m_vao);
164 if (m_shader != 0) glDeleteProgram(m_shader);
173bool SpriteRenderer::LoadDefaultFont()
175 const auto cwd = std::filesystem::current_path();
177 const std::filesystem::path relative =
"engine/thirdparty/imgui/misc/fonts/ProggyClean.ttf";
179 std::vector<std::filesystem::path> candidates = {
182 exe /
".." /
".." / relative,
185 std::vector<char> fontData;
186 for (
const auto &candidate : candidates)
188 if (std::filesystem::exists(candidate))
194 if (fontData.empty())
199 std::array<stbtt_bakedchar, 96> baked {};
200 m_fontBitmap.fill(0);
201 const int bottom = stbtt_BakeFontBitmap(
reinterpret_cast<const unsigned char *
>(fontData.data()),
208 static_cast<int>(baked.size()),
212 LOG_ERROR(
"SpriteRenderer: failed to bake default font atlas");
216 for (
usize i = 0; i < baked.size(); ++i)
219 static_cast<f32>(baked[i].x0),
220 static_cast<f32>(baked[i].y0),
221 static_cast<f32>(baked[i].x1),
222 static_cast<f32>(baked[i].y1),
229 glGenTextures(1, &m_fontTexture);
230 glBindTexture(GL_TEXTURE_2D, m_fontTexture);
231 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
232 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 512, 512, 0, GL_RED, GL_UNSIGNED_BYTE, m_fontBitmap.data());
233 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
234 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
235 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
236 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
237 glBindTexture(GL_TEXTURE_2D, 0);
243 if (texture ==
nullptr || texture->
GetID() == 0)
247 QueueQuad(texture->
GetID(),
false, position, size,
vec2(0.0F),
vec2(1.0F), color);
252 if (m_whiteTexture == 0)
256 QueueQuad(m_whiteTexture,
false, position, size,
vec2(0.0F),
vec2(1.0F), color);
262 DrawSprite(texture.get(), position, size, color);
267 if (!m_fontReady || text.empty())
272 const f32 scale = pixelHeight / m_fontBakeHeight;
284 if (c < 32 || c > 127)
289 const auto &g = m_glyphs[
static_cast<usize>(c - 32)];
290 const vec2 glyphPos(x + g.xoff * scale, y + g.yoff * scale);
291 const vec2 glyphSize((g.x1 - g.x0) * scale, (g.y1 - g.y0) * scale);
292 const vec2 uvMin(g.x0 / 512.0F, g.y0 / 512.0F);
293 const vec2 uvMax(g.x1 / 512.0F, g.y1 / 512.0F);
294 QueueQuad(m_fontTexture,
true, glyphPos, glyphSize, uvMin, uvMax, color);
295 x += g.xadvance * scale;
299void SpriteRenderer::QueueQuad(
GLuint texture,
301 const vec2 &position,
307 if (texture == 0 || size.x == 0.0F || size.y == 0.0F)
312 const f32 x0 = position.x;
313 const f32 y0 = position.y;
314 const f32 x1 = position.x + size.x;
315 const f32 y1 = position.y + size.y;
318 cmd.texture = texture;
319 cmd.redOnly = redOnly;
321 Vertex{{x0, y0}, {uvMin.x, uvMin.y}, color},
322 Vertex{{x1, y0}, {uvMax.x, uvMin.y}, color},
323 Vertex{{x1, y1}, {uvMax.x, uvMax.y}, color},
324 Vertex{{x0, y0}, {uvMin.x, uvMin.y}, color},
325 Vertex{{x1, y1}, {uvMax.x, uvMax.y}, color},
326 Vertex{{x0, y1}, {uvMin.x, uvMax.y}, color},
328 m_commands.push_back(cmd);
333 if (m_commands.empty() || m_shader == 0 || viewportWidth <= 0 || viewportHeight <= 0)
339 GLboolean oldDepth = glIsEnabled(GL_DEPTH_TEST);
340 GLboolean oldBlend = glIsEnabled(GL_BLEND);
341 GLint oldProgram = 0;
342 GLint oldTexture = 0;
344 glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram);
345 glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTexture);
346 glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldVao);
348 glDisable(GL_DEPTH_TEST);
350 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
352 glUseProgram(m_shader);
353 const mat4 projection = glm::ortho(0.0F,
static_cast<f32>(viewportWidth),
static_cast<f32>(viewportHeight), 0.0F);
354 glUniformMatrix4fv(glGetUniformLocation(m_shader,
"uProjection"), 1, GL_FALSE, value_ptr(projection));
355 glUniform1i(glGetUniformLocation(m_shader,
"uTexture"), 0);
357 glBindVertexArray(m_vao);
358 glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
359 glActiveTexture(GL_TEXTURE0);
361 for (
const auto &cmd : m_commands)
363 glBindTexture(GL_TEXTURE_2D, cmd.texture);
364 glUniform1i(glGetUniformLocation(m_shader,
"uRedOnly"), cmd.redOnly ? 1 : 0);
365 glBufferSubData(GL_ARRAY_BUFFER, 0,
sizeof(Vertex) * cmd.vertices.size(), cmd.vertices.data());
366 glDrawArrays(GL_TRIANGLES, 0,
static_cast<GLsizei>(cmd.vertices.size()));
369 glBindBuffer(GL_ARRAY_BUFFER, 0);
370 glBindVertexArray(
static_cast<GLuint>(oldVao));
371 glBindTexture(GL_TEXTURE_2D,
static_cast<GLuint>(oldTexture));
372 glUseProgram(
static_cast<GLuint>(oldProgram));
373 if (oldDepth) glEnable(GL_DEPTH_TEST);
else glDisable(GL_DEPTH_TEST);
374 if (oldBlend) glEnable(GL_BLEND);
else glDisable(GL_BLEND);
Console logging macros for all engine and game code.
#define LOG_WARN(fmt,...)
#define LOG_ERROR(fmt,...)
FileSystem & GetFileSystem()
Returns the file system helper for asset-relative path resolution.
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
TextureManager & GetTextureManager()
Returns the TextureManager that caches loaded textures by path.
std::filesystem::path GetExecutableFolder() const
Return the directory containing the running executable.
std::vector< char > LoadFile(const std::filesystem::path &path)
Load an arbitrary file into a byte buffer.
void DrawSprite(Texture *texture, const vec2 &position, const vec2 &size, const vec4 &color=vec4(1.0F))
void DrawText(const std::string &text, const vec2 &position, f32 pixelHeight, const vec4 &color=vec4(1.0F))
void DrawRect(const vec2 &position, const vec2 &size, const vec4 &color)
void Flush(int viewportWidth, int viewportHeight)
std::shared_ptr< Texture > GetOrLoadTexture(const std::string &path)
Return a cached texture, loading it from disk on first request.
2D OpenGL texture object wrapping a GL_TEXTURE_2D handle.
GLuint GetID() const
Returns the underlying OpenGL texture handle (GL_TEXTURE_2D target).
glm::vec2 vec2
2-component float vector (e.g. UV coordinates, mouse position).
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
float f32
32-bit IEEE float — the standard GL scalar type.
size_t usize
Platform-native unsigned size type.