Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
SpriteRenderer.cpp
Go to the documentation of this file.
2
3#include <cstddef>
4#include <filesystem>
5#include <vector>
6
7#include <GL/glew.h>
8#include <glm/gtc/matrix_transform.hpp>
9
10#include "Engine.h"
11#include "Log.h"
12#include "graphics/Texture.h"
13#include "io/FileSystem.h"
14
15#define STB_TRUETYPE_IMPLEMENTATION
16#include "imstb_truetype.h"
17
18namespace mnd
19{
20namespace
21{
22GLuint CompileShader(GLenum type, const char *source)
23{
24 GLuint shader = glCreateShader(type);
25 glShaderSource(shader, 1, &source, nullptr);
26 glCompileShader(shader);
27
28 GLint ok = 0;
29 glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
30 if (!ok)
31 {
32 char info[1024];
33 glGetShaderInfoLog(shader, sizeof(info), nullptr, info);
34 LOG_ERROR("SpriteRenderer shader compile failed: %s", info);
35 glDeleteShader(shader);
36 return 0;
37 }
38 return shader;
39}
40
41GLuint LinkProgram(const char *vertexSource, const char *fragmentSource)
42{
43 GLuint vs = CompileShader(GL_VERTEX_SHADER, vertexSource);
44 GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragmentSource);
45 if (vs == 0 || fs == 0)
46 {
47 if (vs != 0) glDeleteShader(vs);
48 if (fs != 0) glDeleteShader(fs);
49 return 0;
50 }
51
52 GLuint program = glCreateProgram();
53 glAttachShader(program, vs);
54 glAttachShader(program, fs);
55 glLinkProgram(program);
56 glDeleteShader(vs);
57 glDeleteShader(fs);
58
59 GLint ok = 0;
60 glGetProgramiv(program, GL_LINK_STATUS, &ok);
61 if (!ok)
62 {
63 char info[1024];
64 glGetProgramInfoLog(program, sizeof(info), nullptr, info);
65 LOG_ERROR("SpriteRenderer shader link failed: %s", info);
66 glDeleteProgram(program);
67 return 0;
68 }
69 return program;
70}
71} // namespace
72
74{
75 const char *vertexSource = R"(
76 #version 330 core
77 layout(location = 0) in vec2 aPosition;
78 layout(location = 1) in vec2 aUV;
79 layout(location = 2) in vec4 aColor;
80
81 uniform mat4 uProjection;
82
83 out vec2 vUV;
84 out vec4 vColor;
85
86 void main()
87 {
88 vUV = aUV;
89 vColor = aColor;
90 gl_Position = uProjection * vec4(aPosition, 0.0, 1.0);
91 }
92 )";
93
94 const char *fragmentSource = R"(
95 #version 330 core
96 uniform sampler2D uTexture;
97 uniform bool uRedOnly;
98
99 in vec2 vUV;
100 in vec4 vColor;
101 out vec4 FragColor;
102
103 void main()
104 {
105 vec4 tex = texture(uTexture, vUV);
106 if (uRedOnly) {
107 tex = vec4(1.0, 1.0, 1.0, tex.r);
108 }
109 FragColor = tex * vColor;
110 }
111 )";
112
113 m_shader = LinkProgram(vertexSource, fragmentSource);
114 if (m_shader == 0)
115 {
116 return false;
117 }
118
119 glGenVertexArrays(1, &m_vao);
120 glGenBuffers(1, &m_vbo);
121
122 glBindVertexArray(m_vao);
123 glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
124 glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 6, nullptr, GL_DYNAMIC_DRAW);
125
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)));
132
133 glBindVertexArray(0);
134 glBindBuffer(GL_ARRAY_BUFFER, 0);
135
136 {
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);
147 }
148
149 m_fontReady = LoadDefaultFont();
150 if (!m_fontReady)
151 {
152 LOG_WARN("SpriteRenderer: default font unavailable; DrawText calls will be ignored");
153 }
154 return true;
156
158{
159 if (m_whiteTexture != 0) glDeleteTextures(1, &m_whiteTexture);
160 m_whiteTexture = 0;
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);
165 m_fontTexture = 0;
166 m_vbo = 0;
167 m_vao = 0;
168 m_shader = 0;
169 m_fontReady = false;
170 m_commands.clear();
171}
172
173bool SpriteRenderer::LoadDefaultFont()
174{
175 const auto cwd = std::filesystem::current_path();
177 const std::filesystem::path relative = "engine/thirdparty/imgui/misc/fonts/ProggyClean.ttf";
178
179 std::vector<std::filesystem::path> candidates = {
180 cwd / relative,
181 exe / relative,
182 exe / ".." / ".." / relative,
183 };
184
185 std::vector<char> fontData;
186 for (const auto &candidate : candidates)
187 {
188 if (std::filesystem::exists(candidate))
189 {
190 fontData = Engine::GetInstance().GetFileSystem().LoadFile(candidate);
191 break;
192 }
193 }
194 if (fontData.empty())
195 {
196 return false;
197 }
198
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()),
202 0,
203 m_fontBakeHeight,
204 m_fontBitmap.data(),
205 512,
206 512,
207 32,
208 static_cast<int>(baked.size()),
209 baked.data());
210 if (bottom <= 0)
211 {
212 LOG_ERROR("SpriteRenderer: failed to bake default font atlas");
213 return false;
214 }
215
216 for (usize i = 0; i < baked.size(); ++i)
217 {
218 m_glyphs[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),
223 baked[i].xoff,
224 baked[i].yoff,
225 baked[i].xadvance,
226 };
227 }
228
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);
238 return true;
240
241void SpriteRenderer::DrawSprite(Texture *texture, const vec2 &position, const vec2 &size, const vec4 &color)
242{
243 if (texture == nullptr || texture->GetID() == 0)
244 {
245 return;
246 }
247 QueueQuad(texture->GetID(), false, position, size, vec2(0.0F), vec2(1.0F), color);
249
250void SpriteRenderer::DrawRect(const vec2 &position, const vec2 &size, const vec4 &color)
251{
252 if (m_whiteTexture == 0)
253 {
254 return;
255 }
256 QueueQuad(m_whiteTexture, false, position, size, vec2(0.0F), vec2(1.0F), color);
258
259void SpriteRenderer::DrawSprite(const std::string &assetPath, const vec2 &position, const vec2 &size, const vec4 &color)
260{
261 auto texture = Engine::GetInstance().GetTextureManager().GetOrLoadTexture(assetPath);
262 DrawSprite(texture.get(), position, size, color);
264
265void SpriteRenderer::DrawText(const std::string &text, const vec2 &position, f32 pixelHeight, const vec4 &color)
266{
267 if (!m_fontReady || text.empty())
268 {
269 return;
270 }
271
272 const f32 scale = pixelHeight / m_fontBakeHeight;
273 f32 x = position.x;
274 f32 y = position.y;
275
276 for (char c : text)
277 {
278 if (c == '\n')
279 {
280 x = position.x;
281 y += pixelHeight;
282 continue;
283 }
284 if (c < 32 || c > 127)
285 {
286 c = '?';
287 }
288
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;
296 }
297}
298
299void SpriteRenderer::QueueQuad(GLuint texture,
300 bool redOnly,
301 const vec2 &position,
302 const vec2 &size,
303 const vec2 &uvMin,
304 const vec2 &uvMax,
305 const vec4 &color)
306{
307 if (texture == 0 || size.x == 0.0F || size.y == 0.0F)
308 {
309 return;
310 }
311
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;
316
317 DrawCommand cmd;
318 cmd.texture = texture;
319 cmd.redOnly = redOnly;
320 cmd.vertices = {
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},
327 };
328 m_commands.push_back(cmd);
330
331void SpriteRenderer::Flush(int viewportWidth, int viewportHeight)
332{
333 if (m_commands.empty() || m_shader == 0 || viewportWidth <= 0 || viewportHeight <= 0)
334 {
335 m_commands.clear();
336 return;
337 }
338
339 GLboolean oldDepth = glIsEnabled(GL_DEPTH_TEST);
340 GLboolean oldBlend = glIsEnabled(GL_BLEND);
341 GLint oldProgram = 0;
342 GLint oldTexture = 0;
343 GLint oldVao = 0;
344 glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram);
345 glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTexture);
346 glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldVao);
347
348 glDisable(GL_DEPTH_TEST);
349 glEnable(GL_BLEND);
350 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
351
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);
356
357 glBindVertexArray(m_vao);
358 glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
359 glActiveTexture(GL_TEXTURE0);
360
361 for (const auto &cmd : m_commands)
362 {
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()));
367 }
368
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);
375
376 m_commands.clear();
377}
378
379} // namespace mnd
int GLint
Definition GLForward.h:13
unsigned int GLenum
Definition GLForward.h:12
int GLsizei
Definition GLForward.h:14
unsigned int GLuint
Definition GLForward.h:11
Console logging macros for all engine and game code.
#define LOG_WARN(fmt,...)
Definition Log.h:80
#define LOG_ERROR(fmt,...)
Definition Log.h:81
FileSystem & GetFileSystem()
Returns the file system helper for asset-relative path resolution.
Definition Engine.cpp:453
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
Definition Engine.cpp:59
TextureManager & GetTextureManager()
Returns the TextureManager that caches loaded textures by path.
Definition Engine.cpp:433
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.
Definition Texture.cpp:58
2D OpenGL texture object wrapping a GL_TEXTURE_2D handle.
Definition Texture.h:41
GLuint GetID() const
Returns the underlying OpenGL texture handle (GL_TEXTURE_2D target).
Definition Texture.cpp:71
glm::vec2 vec2
2-component float vector (e.g. UV coordinates, mouse position).
Definition Types.h:50
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
Definition Types.h:63
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
Definition Types.h:52
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
size_t usize
Platform-native unsigned size type.
Definition Types.h:43