CoriEngine
Loading...
Searching...
No Matches
Renderer2D.cpp
Go to the documentation of this file.
1#include "Renderer2D.hpp"
2#include <ska_sort.hpp>
3#include "Utility/AABB.hpp"
4#include "FontData.hpp"
7
8namespace Cori {
9 namespace Graphics {
10 Renderer2D::RendererData* Renderer2D::s_Data{ nullptr };
11
12 void Renderer2D::Init() {
14
15 s_Data = new RendererData();
16
17 constexpr auto params = Texture::Params();
18 constexpr uint32_t white[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
19 s_Data->WhiteTexture = Texture2D::Create(&white, 2, 2, params);
20
21 // quad setup
22
23 s_Data->QuadInstanceVertexArray = VertexArray::Create();
24 s_Data->QuadInstanceVertexBuffer = VertexBuffer::Create();
25 s_Data->QuadInstanceVertexBuffer->SetLayout({
26 {ShaderDataType::Mat3, "a_Transform", 1},
27 {ShaderDataType::Vec4, "a_TexturePosition", 1},
28 {ShaderDataType::Vec2, "a_Size", 1},
29 {ShaderDataType::Vec4, "a_TintColor", 1},
30 {ShaderDataType::Float, "a_Layer", 1},
31 });
32
33 s_Data->QuadInstanceVertexBuffer->Init(nullptr, RendererData::MaxInstanceCount * s_Data->QuadInstanceVertexBuffer->GetLayout().GetStride(), VertexBuffer::DRAW_TYPE::DYNAMIC);
34 s_Data->QuadInstanceVertexArray->AddVertexBuffer(s_Data->QuadInstanceVertexBuffer);
35
36 uint32_t quadIndices[6] = { 0, 1, 2, 2, 3, 0 };
37 s_Data->QuadInstanceIndexBuffer = IndexBuffer::Create(quadIndices, 6);
38 s_Data->QuadInstanceVertexArray->AddIndexBuffer(s_Data->QuadInstanceIndexBuffer);
39
40 s_Data->QuadInstanceBufferBase = new Quad[RendererData::MaxInstanceCount];
41
42 s_Data->QuadInstanceShader = ShaderProgram::Create(FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / std::filesystem::path("shaders/QuadInstancedVert.glsl"), FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / std::filesystem::path("shaders/QuadInstancedFrag.glsl"));
43
44 s_Data->WorldSpaceTransparentQuadQueue.reserve(RendererData::WorldSpaceTransparentQuadQueueInitialSize);
45 s_Data->WorldSpaceOpaqueQuadQueue.reserve(RendererData::WorldSpaceOpaqueQuadQueueInitialSize);
46 s_Data->ScreenSpaceTransparentQuadQueue.reserve(RendererData::ScreenSpaceTransparentQuadQueueInitialSize);
47 s_Data->ScreenSpaceOpaqueQuadQueue.reserve(RendererData::ScreenSpaceOpaqueQuadQueueInitialSize);
48
49 // text setup
50
51 s_Data->CharInstanceVertexArray = VertexArray::Create();
52 s_Data->CharInstanceVertexBuffer = VertexBuffer::Create();
53 s_Data->CharInstanceVertexBuffer->SetLayout({
54 {ShaderDataType::Mat3, "a_Transform", 1},
55 {ShaderDataType::Vec4, "a_TexturePosition", 1},
56 {ShaderDataType::Vec4, "a_CharQuad", 1},
57 {ShaderDataType::Vec4, "a_Color", 1},
58 {ShaderDataType::Float, "a_Layer", 1}
59 });
60
61 s_Data->CharInstanceVertexBuffer->Init(nullptr, RendererData::MaxCharInstanceCount * s_Data->CharInstanceVertexBuffer->GetLayout().GetStride(), VertexBuffer::DRAW_TYPE::DYNAMIC);
62 s_Data->CharInstanceVertexArray->AddVertexBuffer(s_Data->CharInstanceVertexBuffer);
63
64 s_Data->CharInstanceIndexBuffer = IndexBuffer::Create(quadIndices, 6);
65 s_Data->CharInstanceVertexArray->AddIndexBuffer(s_Data->CharInstanceIndexBuffer);
66
67 s_Data->CharInstanceBufferBase = new Char[RendererData::MaxCharInstanceCount];
68
69 s_Data->CharInstanceShader = ShaderProgram::Create(FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / std::filesystem::path("shaders/TextInstancedVert.glsl"), FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / std::filesystem::path("shaders/TextInstancedFrag.glsl"));
70
71 s_Data->WorldSpaceTransparentTextQueue.reserve(RendererData::WorldSpaceTransparentTextQueueInitialSize);
72
73 s_Data->ScreenSpaceTransparentTextQueue.reserve(RendererData::ScreenSpaceTransparentTextQueueInitialSize);
74
76 }
77
78 void Renderer2D::Shutdown() {
80
81 delete[] s_Data->QuadInstanceBufferBase;
82 delete[] s_Data->CharInstanceBufferBase;
83 delete s_Data;
84 }
85
88 s_Data->CurrentDrawSpace = UNSPECIFIED;
89
90 s_Data->WorldSpaceViewProjectionMatrix = camera.m_ViewProjectionMatrix;
91 s_Data->ScreenSpaceViewProjectionMatrix = camera.m_ProjectionMatrix;
92 }
93
94 bool Renderer2D::BeginWorldSpacePass() {
96 if (s_Data->CurrentDrawSpace != WORLD_SPACE) {
97 s_Data->CurrentViewProjectionMatrix = s_Data->WorldSpaceViewProjectionMatrix;
98 s_Data->CurrentDrawSpace = WORLD_SPACE;
99 return true;
100 }
101
102 return false;
103 }
104
105 bool Renderer2D::BeginScreenSpacePass() {
107 if (s_Data->CurrentDrawSpace != SCREEN_SPACE) {
108 s_Data->CurrentViewProjectionMatrix = s_Data->ScreenSpaceViewProjectionMatrix;
109 s_Data->CurrentDrawSpace = SCREEN_SPACE;
110 return true;
111 }
112
113 return false;
114 }
115
118 FlushRenderQueues();
119 }
120
121 void Renderer2D::SubmitScene(World::Scene* scene) {
123
124 const auto& camera = scene->GetContextComponent<World::Components::Scene::Camera>();
125 //Utility::AABB cameraBounds = { camera.m_CameraMinBound, camera.m_CameraMaxBound };
127 for (const auto entity : view) {
128 auto& renderer = view.Get<World::Components::Entity::QuadRenderer>(entity);
129 if (__builtin_expect(renderer.m_Visible, 1)) {
130 const auto texture = renderer.GetTexture().get();
131 if (__builtin_expect(!texture, 0)) {
132 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Renderer2D }, "DrawScene: Texture inside Quad Renderer for Entity '{}', is null, skipping it.", entity.GetDebugData());
133 }
134 else {
135 auto& transform = view.Get<World::Components::Entity::Transform>(entity);
136 Utility::AABB entityBounds = Utility::CalculateAABB(transform.m_WorldTransform, renderer.GetHalfSize());
137 //SubmitAABB(camera.m_CameraBounds, 0.2f, {0.0f, 1.0f, 0.0f});
138 if (AABBOverlapCheck(camera.m_CameraBounds, entityBounds)) {
139 //SubmitAABB(entityBounds, 0.2f, {1.0f, 0.0f, 1.0f});
140 if (!renderer.GetSemiTransparencyState()) {
141 SubmitQuad(WORLD_SPACE, OPAQUE, transform.m_WorldTransform, renderer.GetHalfSize(), renderer.GetColor(), texture, renderer.GetUVs(), transform.m_WorldDepth, renderer.m_FlipX, renderer.m_FlipY, renderer.m_FlatColored);
142 continue;
143 }
144 SubmitQuad(WORLD_SPACE, SEMI_TRANSPARENT, transform.m_WorldTransform, renderer.GetHalfSize(), renderer.GetColor(), texture, renderer.GetUVs(), transform.m_WorldDepth, renderer.m_FlipX, renderer.m_FlipY, renderer.m_FlatColored);
145 }
146 }
147 }
148 }
149 }
150
151 void Renderer2D::FlushRenderQueues() {
152
153 FlushOpaqueQueues();
154 FlushTransparentQueues();
155 }
156
157 void Renderer2D::SubmitQuad(const DrawSpace space, const ObjectTransparency transparencyMode, const glm::mat3& transform, const glm::vec2 halfSize, const glm::vec4& tintColor, Texture2D* texture, const UVs& uvs, const uint8_t depth, const bool flipX, const bool flipY, const bool flatColored) {
158 if (space == WORLD_SPACE) {
159 if (transparencyMode == OPAQUE) {
160 SubmitQuadToQueue(s_Data->WorldSpaceOpaqueQuadQueue, transform, halfSize, tintColor, texture, uvs, depth, flipX, flipY, flatColored);
161 return;
162 }
163
164 SubmitQuadToQueue(s_Data->WorldSpaceTransparentQuadQueue, transform, halfSize, tintColor, texture, uvs, depth, flipX, flipY, flatColored);
165 return;
166 }
167
168 if (transparencyMode == OPAQUE) {
169 SubmitQuadToQueue(s_Data->ScreenSpaceOpaqueQuadQueue, transform, halfSize, tintColor, texture, uvs, depth, flipX, flipY, flatColored);
170 return;
171 }
172
173 SubmitQuadToQueue(s_Data->ScreenSpaceTransparentQuadQueue, transform, halfSize, tintColor, texture, uvs, depth, flipX, flipY, flatColored);
174 }
175
176 void Renderer2D::BeginQuadInstancedSet() {
178
179 s_Data->QuadInstanceCount = 0;
180 s_Data->QuadInstanceBufferPtr = s_Data->QuadInstanceBufferBase;
181
182 }
183
184 void Renderer2D::EndQuadInstancedSet() {
185 if (s_Data->QuadInstanceCount != 0) {
186 if (s_Data->CurrentVertexArray != s_Data->QuadInstanceVertexArray.get()) {
187 s_Data->QuadInstanceVertexArray->Bind();
188 s_Data->CurrentVertexArray = s_Data->QuadInstanceVertexArray.get();
189 }
190 if (s_Data->CurrentVertexBuffer != s_Data->QuadInstanceVertexBuffer.get()) {
191 s_Data->QuadInstanceVertexBuffer->Bind();
192 s_Data->CurrentVertexBuffer = s_Data->QuadInstanceVertexBuffer.get();
193 }
194 if (s_Data->CurrentIndexBuffer != s_Data->QuadInstanceIndexBuffer.get()) {
195 s_Data->QuadInstanceIndexBuffer->Bind();
196 s_Data->CurrentIndexBuffer = s_Data->QuadInstanceIndexBuffer.get();
197 }
198 if (s_Data->CurrentShader != s_Data->QuadInstanceShader.get()) {
199 s_Data->QuadInstanceShader->Bind();
200 s_Data->CurrentShader = s_Data->QuadInstanceShader.get();
201 }
202 FlushInstancedQuads();
203 }
204 }
205
206 void Renderer2D::BeginCharInstancedSet() {
207 s_Data->CharInstanceCount = 0;
208 s_Data->CharInstanceBufferPtr = s_Data->CharInstanceBufferBase;
209 }
210
211 void Renderer2D::EndCharInstancedSet(Texture2D* atlas, const glm::mat3& modelMatrix) {
212 if (s_Data->CharInstanceCount != 0) {
213 if (s_Data->CurrentVertexArray != s_Data->CharInstanceVertexArray.get()) {
214 s_Data->CharInstanceVertexArray->Bind();
215 s_Data->CurrentVertexArray = s_Data->CharInstanceVertexArray.get();
216 }
217 if (s_Data->CurrentVertexBuffer != s_Data->CharInstanceVertexBuffer.get()) {
218 s_Data->CharInstanceVertexBuffer->Bind();
219 s_Data->CurrentVertexBuffer = s_Data->CharInstanceVertexBuffer.get();
220 }
221 if (s_Data->CurrentIndexBuffer != s_Data->CharInstanceIndexBuffer.get()) {
222 s_Data->CharInstanceIndexBuffer->Bind();
223 s_Data->CurrentIndexBuffer = s_Data->CharInstanceIndexBuffer.get();
224 }
225 if (s_Data->CurrentShader != s_Data->CharInstanceShader.get()) {
226 s_Data->CharInstanceShader->Bind();
227 s_Data->CurrentShader = s_Data->CharInstanceShader.get();
228 }
229 FlushInstancedChars(atlas, modelMatrix);
230 }
231 }
232
233 void Renderer2D::SubmitQuadToQueue(std::vector<QuadInstance>& queue, const glm::mat3& transform, const glm::vec2 halfSize, const glm::vec4& tintColor, Texture2D* texture, const UVs& uvs, const uint8_t depth, const bool flipX, const bool flipY, const bool flatColored) {
234 if (flatColored) {
235 texture = s_Data->WhiteTexture.get();
236 }
237 else if (texture->GetStatus() != AssetStatus::READY) {
238 return;
239 }
240
241 if (!flipX && !flipY) {
242 queue.emplace_back(
243 transform,
244 halfSize,
245 tintColor,
246 texture,
247 static_cast<glm::vec4>(uvs),
248 depth
249 );
250 }
251 else if (flipX && !flipY) {
252 queue.emplace_back(
253 transform,
254 halfSize,
255 tintColor,
256 texture,
257 glm::vec4{uvs.UVmax.x, uvs.UVmin.y, uvs.UVmin.x, uvs.UVmax.y},
258 depth
259 );
260 }
261 else if (!flipX) {
262 queue.emplace_back(
263 transform,
264 halfSize,
265 tintColor,
266 texture,
267 glm::vec4{uvs.UVmin.x, uvs.UVmax.y, uvs.UVmax.x, uvs.UVmin.y},
268 depth
269 );
270 }
271 else {
272 queue.emplace_back(
273 transform,
274 halfSize,
275 tintColor,
276 texture,
277 glm::vec4{uvs.UVmax.x, uvs.UVmax.y, uvs.UVmin.x, uvs.UVmin.y},
278 depth
279 );
280 }
281 }
282
283
284 void Renderer2D::SubmitColoredQuad(const DrawSpace space, const glm::vec2 position, const glm::vec2 halfSize, const glm::vec3& color) {
285 const glm::mat3 transform = glm::translate(glm::mat3(1.0f), position);
286 SubmitQuad(space, OPAQUE, transform, halfSize, glm::vec4(color, 1.0f), nullptr, {}, 255, false, false, true);
287 }
288
289 void Renderer2D::SubmitAABB(const Utility::AABB& aabb, const float lineThickness, const glm::vec3& color) {
290 const glm::vec2 size = { aabb.m_Max.x - aabb.m_Min.x, aabb.m_Max.y - aabb.m_Min.y };
291 SubmitColoredQuad(WORLD_SPACE, { aabb.m_Min.x, aabb.m_Max.y - size.y / 2.0f }, { lineThickness, size.y / 2.0f }, color);
292 SubmitColoredQuad(WORLD_SPACE, { aabb.m_Max.x, aabb.m_Max.y - size.y / 2.0f }, { lineThickness, size.y / 2.0f }, color);
293
294 SubmitColoredQuad(WORLD_SPACE, { aabb.m_Max.x - size.x / 2.0f, aabb.m_Min.y }, { size.x / 2.0f - lineThickness, lineThickness }, color);
295 SubmitColoredQuad(WORLD_SPACE, { aabb.m_Max.x - size.x / 2.0f, aabb.m_Max.y }, { size.x / 2.0f - lineThickness, lineThickness }, color);
296 }
297
298 void Renderer2D::SubmitTextToQueue(std::vector<TextInstance>& queue, const TextAlignment alignment, const glm::mat3& transform, const float fontSize, const std::u32string_view& text, const glm::vec4& color, Font* font, const uint8_t depth, const float limitX, const float lineSpacing, const float kerning) {
299 queue.emplace_back(
300 alignment,
301 transform,
302 fontSize,
303 text,
304 color,
305 font,
306 depth,
307 limitX,
308 lineSpacing,
309 kerning
310 );
311 }
312
313 void Renderer2D::SubmitTextToQueue(std::vector<TextInstance>& queue, const TextAlignment alignment, const glm::mat3& transform, const float fontSize, const std::string_view& text, const glm::vec4& color, Font* font, const uint8_t depth, const float limitX, const float lineSpacing, const float kerning) {
314 queue.emplace_back(
315 alignment,
316 transform,
317 fontSize,
318 text,
319 color,
320 font,
321 depth,
322 limitX,
323 lineSpacing,
324 kerning
325 );
326 }
327
328 void Renderer2D::SubmitText(const DrawSpace space, const TextAlignment alignment, const glm::mat3& transform, const float fontSize, const std::u32string_view& text, const glm::vec4& color, Font* font, const uint8_t depth, const float limitX, const float lineSpacing, const float kerning) {
329 if (font->GetStatus() == AssetStatus::LOADING) {
330 return;
331 }
332 if (font->GetStatus() == AssetStatus::PLACEHOLDER) {
333 const auto placeholder = AssetManager::GetPlaceholder<Font>();
334 if (placeholder) {
335 if (placeholder->GetStatus() == AssetStatus::READY) {
336 font = placeholder.get();
337 } else {
338 return;
339 }
340
341 }
342 }
343
344 if (space == WORLD_SPACE) {
345 SubmitTextToQueue(s_Data->WorldSpaceTransparentTextQueue, alignment, transform, fontSize, text, color, font, depth, limitX, lineSpacing, kerning);
346 return;
347 }
348
349 SubmitTextToQueue(s_Data->ScreenSpaceTransparentTextQueue, alignment, transform, fontSize, text, color, font, depth, limitX, lineSpacing, kerning);
350 }
351
352 void Renderer2D::SubmitText(const DrawSpace space, const TextAlignment alignment, const glm::mat3& transform, const float fontSize, const std::string_view& text, const glm::vec4& color, Font* font, const uint8_t depth, const float limitX, const float lineSpacing, const float kerning) {
353 if (font->GetStatus() == AssetStatus::LOADING) {
354 return;
355 }
356 if (font->GetStatus() == AssetStatus::PLACEHOLDER) {
357 const auto placeholder = AssetManager::GetPlaceholder<Font>();
358 if (placeholder) {
359 if (placeholder->GetStatus() == AssetStatus::READY) {
360 font = placeholder.get();
361 } else {
362 return;
363 }
364
365 }
366 }
367
368 if (space == WORLD_SPACE) {
369 SubmitTextToQueue(s_Data->WorldSpaceTransparentTextQueue, alignment, transform, fontSize, text, color, font, depth, limitX, lineSpacing, kerning);
370 return;
371 }
372
373 SubmitTextToQueue(s_Data->ScreenSpaceTransparentTextQueue, alignment, transform, fontSize, text, color, font, depth, limitX, lineSpacing, kerning);
374
375 }
376
378 return s_Data->Stats;
379 }
380
382 s_Data->Stats.DrawCalls = 0;
383 s_Data->Stats.QuadCount = 0;
384 }
385
387
388 void Renderer2D::DrawQuadInstanced(const QuadInstance& quad) {
389 if (!quad.m_Texture) {
390 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Renderer2D }, "DrawQuadInstanced: Texture is nullptr, trying to avoid read access violation");
391 return;
392 }
393
394 if (s_Data->QuadInstanceCount >= RendererData::MaxInstanceCount) {
395 StartNewQuadInstancedSet();
396 }
397
398 if (s_Data->NecessaryTexture != quad.m_Texture) {
399 if (!s_Data->NecessaryTexture) {
400 s_Data->NecessaryTexture = quad.m_Texture;
401 }
402 else {
403 StartNewQuadInstancedSet();
404 s_Data->NecessaryTexture = quad.m_Texture;
405 }
406 }
407
408 s_Data->QuadInstanceBufferPtr->m_Transform = quad.m_Transform;
409 s_Data->QuadInstanceBufferPtr->m_TexturePosition = quad.m_UVs;
410 s_Data->QuadInstanceBufferPtr->m_Size = quad.m_Size;
411 s_Data->QuadInstanceBufferPtr->m_TintColor = quad.m_TintColor;
412 s_Data->QuadInstanceBufferPtr->m_Layer = quad.m_Depth;
413 s_Data->QuadInstanceBufferPtr++;
414
415 s_Data->QuadInstanceCount++;
416 }
417
418 void Renderer2D::DrawTextInstanced(const TextInstance& text) {
420 if (!text.m_Font) {
421 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Renderer2D }, "DrawCharInstanced: Font is nullptr, trying to avoid read access violation");
422 return;
423 }
424
425 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(text.m_LimitX, 0.0f)), glm::vec2(1.0f, 50.0f), glm::vec4(1.0f), nullptr, UVs{}, 15, false, false, true);
426 //SubmitQuad(SCREEN_SPACE, OPAQUE, text.m_Transform, glm::vec2(0.5f, 0.5f), glm::vec4(1.0f, 0.0f, 1.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
427
428 BeginCharInstancedSet();
429
430 const auto& fontGeometry = text.m_Font->GetData()->m_FontGeometry;
431 const auto& metrics = fontGeometry.getMetrics();
432 const auto atlas = text.m_Font->GetData()->m_Atlas;
433
434 const float scale = 1.0f / (metrics.ascenderY - metrics.descenderY) * text.m_FontSize;
435 float x = 0.0f;
436
437 // maybe use something like ascender or smthg, some global font metric to center by Y
438 float y = 0.0f;
439
440 float totalLineLength = 0.0f;
441
442 const float spaceGlyphAdvance = fontGeometry.getGlyph(' ')->getAdvance() * scale;
443 constexpr uint8_t spacesInTab = CORI_SPACES_PER_TAB;
444
445 const std::u32string_view view(text.m_Text);
446
447 bool done = false;
448 size_t currentOffset = 0;
449
450 // this used only for tab calculation (includes spaces from tabs)
451 uint32_t currentGlobalCharIndex = 0;
452
453 constexpr std::u32string_view SEPARATORS = U" \a\b\t\n\v\f\r";
454
455 auto FindNextWord = [&](const std::u32string_view textView, const size_t startIndex) -> std::tuple<std::u32string_view, std::u32string_view, size_t> /* skippedPart, wordPart, wordEndIndex */ {
456 if (startIndex >= textView.length()) {
457 // nothing beyond startIndex, out of bounds
458 return {{}, {}, std::u32string_view::npos};
459 }
460
461 const size_t wordStart = textView.find_first_not_of(SEPARATORS, startIndex);
462
463 if (wordStart == std::u32string_view::npos) {
464 // no word after startIndex, everything beyond startIndex considered skipped
465 return {textView.substr(startIndex), {}, std::u32string_view::npos};
466 }
467
468 std::u32string_view skipped = textView.substr(startIndex, wordStart - startIndex);
469
470 size_t wordEnd = textView.find_first_of(SEPARATORS, wordStart);
471
472 // if no separator is found after the word, it extends to the end.
473 if (wordEnd == std::u32string_view::npos) {
474 std::u32string_view word = textView.substr(wordStart);
475 return {skipped, word, std::u32string_view::npos};
476 }
477
478 // a word, a skipped part and a subsequent separator were found.
479 std::u32string_view word = textView.substr(wordStart, wordEnd - wordStart);
480 return {skipped, word, wordEnd};
481 };
482
483 auto GetNextChar = [&](const uint32_t currentIndex, const std::u32string_view& localView) -> char32_t {
484 if (currentIndex + 1 < localView.size()) {
485 // not the last from localView
486 return localView[currentIndex + 1];
487 }
488 // last from localView
489 if (localView.data() + localView.size() == view.data() + view.size()) {
490 // last from globalView
491 done = true;
492 return '\0';
493 }
494
495 // access one beyond end of localView
496 return *(localView.data() + localView.size() + 1);
497 };
498
499 auto ProcessGlyph = [&](const uint32_t index, const std::u32string_view& localView) {
500 if (s_Data->CharInstanceCount > RendererData::MaxCharInstanceCount) {
501 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Renderer2D }, "Trying to render more than '{}' in one go. Aborting further rendering of this 'Text' piece.", RendererData::MaxCharInstanceCount);
502 return;
503 }
504
505 const char32_t c = localView[index];
506
507 auto glyph = fontGeometry.getGlyph(c);
508 if (!glyph) {
509 glyph = fontGeometry.getGlyph('#');
510 }
511
512 if (!glyph) {
513 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Renderer2D }, "Failed to locate glyph '#' after failing to locate glyph 'UTF-32 codepoint 0x{:08X}'. Aborting further rendering of this 'Text' piece.", static_cast<uint32_t>(c));
514 return;
515 }
516
517 double x0, y0, x1, y1;
518 glyph->getQuadPlaneBounds(x0, y0, x1, y1);
519
520 const glm::vec4 charQuad = { x0 * scale + x, y0 * scale + y, x1 * scale + x, y1 * scale + y };
521
522 double u0, v0, u1, v1;
523 glyph->getQuadAtlasBounds(u0, v0, u1, v1);
524
525 const float texelWidth = 1.0f / static_cast<float>(atlas->GetWidth());
526 const float texelHeight = 1.0f / static_cast<float>(atlas->GetHeight());
527
528 const glm::vec4 UV = { u0 * texelWidth, v0 * texelHeight, u1 * texelWidth, v1 * texelHeight };
529
530 s_Data->CharInstanceBufferPtr->m_Transform = text.m_Transform;
531 s_Data->CharInstanceBufferPtr->m_TexturePosition = UV;
532 s_Data->CharInstanceBufferPtr->m_CharQuad = charQuad;
533 s_Data->CharInstanceBufferPtr->m_Color = text.m_Color;
534 s_Data->CharInstanceBufferPtr->m_Layer = text.m_Depth;
535 s_Data->CharInstanceBufferPtr++;
536
537 s_Data->CharInstanceCount++;
538
539 const char32_t nextChar = GetNextChar(index, localView);
540 if (nextChar != '\0') {
541 double advance;
542 fontGeometry.getAdvance(advance, c, nextChar);
543 x += scale * advance + text.m_Kerning;
544 totalLineLength = x;
545 } else {
546 const float sizeX = x1 - x0;
547 totalLineLength += sizeX * scale;
548 }
549 };
550
551
552 auto PreprocessWord = [&](const std::u32string_view& word) -> float {
553 float totalWordAdvance = 0.0f;
554
555 for (uint32_t i = 0; i < word.size(); i++) {
556 const char32_t nextChar = GetNextChar(i, word);
557 if (nextChar != '\0') {
558 double advance;
559 fontGeometry.getAdvance(advance, word[i], nextChar);
560 totalWordAdvance += scale * advance + text.m_Kerning;
561 }
562 }
563
564 return totalWordAdvance;
565 };
566
567 auto GoToNewLine = [&] {
568 switch (text.m_Alignment) {
569 case LEFT:
570 {
571 const float offset = totalLineLength;
572 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 1.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
573
574 break;
575 }
576 case CENTER:
577 {
578 const float offset = -(totalLineLength / 2.0f);
579 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 1.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
580 EndCharInstancedSet(atlas.get(), glm::translate(glm::mat3(1.0f), glm::vec2(offset, 0.0f)));
581 BeginCharInstancedSet();
582 break;
583 }
584 case RIGHT:
585 {
586 const float offset = -totalLineLength;
587 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 1.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
588 EndCharInstancedSet(atlas.get(), glm::translate(glm::mat3(1.0f), glm::vec2(offset, 0.0f)));
589 BeginCharInstancedSet();
590 break;
591 }
592 }
593
594 totalLineLength = 0;
595 x = 0;
596 y -= scale * metrics.lineHeight + text.m_LineSpacing;
597 };
598
599 while (!done) {
600 auto [skippedPart, currentWord, nextOffset] = FindNextWord(view, currentOffset);
601 if (nextOffset != std::u32string_view::npos) {
602 currentOffset = nextOffset;
603 } else {
604 done = true;
605 }
606
607 bool longWord = false;
608
609 // only used for right align
610 bool ignoreSpaces = false;
611 bool globalAdvanceChanged = false;
612
613 float wordAdvance = 0.0f;
614 if (text.m_Alignment == RIGHT) {
615 wordAdvance = PreprocessWord(currentWord);
616 if (x + wordAdvance > text.m_LimitX) {
617 ignoreSpaces = true;
618 }
619 }
620
621 for (uint32_t i = 0; i < skippedPart.size(); ++i) {
622 if (skippedPart[i] == ' ') {
623 const char32_t nextChar = GetNextChar(i, view);
624 if (nextChar != '\0' && !ignoreSpaces) {
625 ++currentGlobalCharIndex;
626 double advance;
627 fontGeometry.getAdvance(advance, skippedPart[i], nextChar);
628 const float nextX = x + scale * advance + text.m_Kerning;
629 if (nextX > text.m_LimitX) {
630 GoToNewLine();
631 } else {
632 x = nextX;
633 totalLineLength = x;
634 }
635 }
636 continue;
637 }
638
639 if (skippedPart[i] == '\t') {
640
641 const uint32_t nextTabStop = ((currentGlobalCharIndex - 1) / spacesInTab + 1) * spacesInTab;
642
643 uint8_t spaces = nextTabStop - currentGlobalCharIndex;
644 if (spaces == 0) {
645 spaces = 4;
646 }
647
648 currentGlobalCharIndex += spaces;
649
650 const float advance = static_cast<float>(spaces) * spaceGlyphAdvance;
651
652 const float nextX = x + advance;
653 if (nextX > text.m_LimitX) {
654 GoToNewLine();
655 }
656 else {
657 x = nextX;
658 totalLineLength = x;
659 }
660
661 globalAdvanceChanged = true;
662 continue;
663 }
664
665 if (skippedPart[i] == '\n') {
666 GoToNewLine();
667 }
668 }
669
670 switch (text.m_Alignment) {
671 case LEFT:
672 case CENTER:
673 {
674 wordAdvance = PreprocessWord(currentWord);
675 if (wordAdvance < text.m_LimitX) {
676 if (x + wordAdvance > text.m_LimitX) {
677 GoToNewLine();
678 }
679 } else {
680 longWord = true;
681 }
682 break;
683 }
684 case RIGHT:
685 {
686 if (ignoreSpaces && !globalAdvanceChanged) {
687 if (wordAdvance < text.m_LimitX) {
688 GoToNewLine();
689 } else {
690 longWord = true;
691 }
692 } else {
693 wordAdvance = PreprocessWord(currentWord);
694 if (wordAdvance < text.m_LimitX) {
695 if (x + wordAdvance > text.m_LimitX) {
696 GoToNewLine();
697 }
698 } else {
699 longWord = true;
700 }
701 }
702 break;
703 }
704 }
705
706 for (uint32_t i = 0; i < currentWord.size(); ++i) {
707 if (longWord) {
708 if (x > text.m_LimitX) {
709 GoToNewLine();
710 }
711 }
712
713 ++currentGlobalCharIndex;
714
715 ProcessGlyph(i, currentWord);
716 }
717 }
718
719
720 switch (text.m_Alignment) {
721 case LEFT:
722 {
723 const float offset = totalLineLength;
724 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
725 EndCharInstancedSet(atlas.get(), glm::mat3(1.0f));
726 break;
727 }
728 case CENTER:
729 {
730 const float offset = -(totalLineLength / 2.0f);
731 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
732 EndCharInstancedSet(atlas.get(), glm::translate(glm::mat3(1.0f), glm::vec2(offset, 0.0f)));
733 break;
734 }
735 case RIGHT:
736 {
737 const float offset = -totalLineLength;
738 //SubmitQuad(SCREEN_SPACE, OPAQUE, glm::translate(text.m_Transform, glm::vec2(offset, y)), glm::vec2(0.5f, 0.5f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), nullptr, UVs{}, 15, false, false, true);
739 EndCharInstancedSet(atlas.get(), glm::translate(glm::mat3(1.0f), glm::vec2(offset, 0.0f)));
740 break;
741 }
742 }
743 }
744
745 void Renderer2D::StartNewQuadInstancedSet() {
746 EndQuadInstancedSet();
747 BeginQuadInstancedSet();
748 }
749
750 void Renderer2D::FlushOpaqueQueues() {
752
753 static auto SortOpaqueQuadQueue = [](std::vector<QuadInstance>& queue) {
754 if (!queue.empty()) {
756 ska_sort(
757 queue.begin(),
758 queue.end(),
759 [](const QuadInstance& quad) -> uint64_t {
760 return reinterpret_cast<uint64_t>(quad.m_Texture);
761 }
762 );
763 }
764 };
765
766 SortOpaqueQuadQueue(s_Data->WorldSpaceOpaqueQuadQueue);
767 SortOpaqueQuadQueue(s_Data->ScreenSpaceOpaqueQuadQueue);
768
769 if (!s_Data->WorldSpaceOpaqueQuadQueue.empty()) {
770 BeginWorldSpacePass();
771
772 BeginQuadInstancedSet();
773
774 for (const auto& quad : s_Data->WorldSpaceOpaqueQuadQueue) {
775 DrawQuadInstanced(quad);
776 }
777
778 EndQuadInstancedSet();
779
780
781 s_Data->WorldSpaceOpaqueQuadQueue.clear();
782 }
783
784 if (!s_Data->ScreenSpaceOpaqueQuadQueue.empty()) {
785 BeginScreenSpacePass();
786
787 BeginQuadInstancedSet();
788
789 for (const auto& quad : s_Data->ScreenSpaceOpaqueQuadQueue) {
790 DrawQuadInstanced(quad);
791 }
792
793 EndQuadInstancedSet();
794
795 s_Data->ScreenSpaceOpaqueQuadQueue.clear();
796 }
797 }
798
799 void Renderer2D::FlushTransparentQueues() {
800 static auto SortTransparentQuadQueue = [](std::vector<QuadInstance>& queue) {
802 if (!queue.empty()) {
803 ska_sort(
804 queue.begin(),
805 queue.end(),
806 [](const QuadInstance& quad) -> uint64_t {
807 return reinterpret_cast<uint64_t>(quad.m_Texture);
808 }
809 );
810
811 ska_sort(
812 queue.begin(),
813 queue.end(),
814 [](const QuadInstance& quad) -> uint8_t {
815 return quad.m_Depth;
816 }
817 );
818 }
819 };
820
821 static auto SortTextQueue = [](std::vector<TextInstance>& queue) {
823 if (!queue.empty()) {
824 ska_sort(
825 queue.begin(),
826 queue.end(),
827 [](const TextInstance& text) -> uint64_t {
828 return reinterpret_cast<uint64_t>(text.m_Font);
829 }
830 );
831
832 ska_sort(
833 queue.begin(),
834 queue.end(),
835 [](const TextInstance& text) -> uint8_t {
836 return text.m_Depth;
837 }
838 );
839 }
840 };
841
842 static auto ClearQuadQueue = [](std::vector<QuadInstance>& queue) {
844 if (!queue.empty()) {
845 queue.clear();
846 }
847 };
848
849 static auto ClearTextQueue = [](std::vector<TextInstance>& queue) {
851 if (!queue.empty()) {
852 queue.clear();
853 }
854 };
855
856 SortTransparentQuadQueue(s_Data->WorldSpaceTransparentQuadQueue);
857 SortTransparentQuadQueue(s_Data->ScreenSpaceTransparentQuadQueue);
858 SortTextQueue(s_Data->WorldSpaceTransparentTextQueue);
859 SortTextQueue(s_Data->ScreenSpaceTransparentTextQueue);
860
861 auto it_wsq = s_Data->WorldSpaceTransparentQuadQueue.begin();
862 auto it_ssq = s_Data->ScreenSpaceTransparentQuadQueue.begin();
863 auto it_wst = s_Data->WorldSpaceTransparentTextQueue.begin();
864 auto it_sst = s_Data->ScreenSpaceTransparentTextQueue.begin();
865
866 const auto end_wsq = s_Data->WorldSpaceTransparentQuadQueue.end();
867 const auto end_ssq = s_Data->ScreenSpaceTransparentQuadQueue.end();
868 const auto end_wst = s_Data->WorldSpaceTransparentTextQueue.end();
869 const auto end_sst = s_Data->ScreenSpaceTransparentTextQueue.end();
870
871 std::priority_queue<ElementView, std::vector<ElementView>, std::greater<>> pq;
872
873 if (it_wsq != end_wsq) pq.push({&*it_wsq, it_wsq->m_Depth, WORLD_SPACE_QUAD});
874 if (it_ssq != end_ssq) pq.push({&*it_ssq, it_ssq->m_Depth, SCREEN_SPACE_QUAD});
875 if (it_wst != end_wst) pq.push({&*it_wst, it_wst->m_Depth, WORLD_SPACE_TEXT});
876 if (it_sst != end_sst) pq.push({&*it_sst, it_sst->m_Depth, SCREEN_SPACE_TEXT});
877
878 if (pq.empty()) {
879 return;
880 }
881
884
885 while (!pq.empty()) {
886 ElementView top = pq.top();
887 pq.pop();
888
889 switch (top.m_QueueType) {
890 case WORLD_SPACE_QUAD:
891 {
892 const auto elem = std::get<const QuadInstance*>(top.m_ElementVariant);
893 if (s_Data->CurrentDrawSpace != WORLD_SPACE && s_Data->QuadInstanceCount != 0) {
894 StartNewQuadInstancedSet();
895 }
896
897 BeginWorldSpacePass();
898
899 DrawQuadInstanced(*elem);
900
901 if (++it_wsq != end_wsq) {
902 pq.push({&*it_wsq, it_wsq->m_Depth, WORLD_SPACE_QUAD});
903 }
904 break;
905 }
906 case SCREEN_SPACE_QUAD:
907 {
908 const auto elem = std::get<const QuadInstance*>(top.m_ElementVariant);
909 if (s_Data->CurrentDrawSpace != SCREEN_SPACE && s_Data->QuadInstanceCount != 0) {
910 StartNewQuadInstancedSet();
911 }
912
913 BeginScreenSpacePass();
914
915 DrawQuadInstanced(*elem);
916
917 if (++it_ssq != end_ssq) {
918 pq.push({&*it_ssq, it_ssq->m_Depth, SCREEN_SPACE_QUAD});
919 }
920 break;
921 }
922 case WORLD_SPACE_TEXT:
923 {
924 const auto elem = std::get<const TextInstance*>(top.m_ElementVariant);
925 if (s_Data->QuadInstanceCount != 0) {
926 EndQuadInstancedSet();
927 }
928
929 BeginWorldSpacePass();
930 DrawTextInstanced(*elem);
931
932 if (++it_wst != end_wst) {
933 pq.push({&*it_wst, it_wst->m_Depth, WORLD_SPACE_TEXT});
934 }
935 break;
936 }
937 case SCREEN_SPACE_TEXT:
938 {
939 const auto elem = std::get<const TextInstance*>(top.m_ElementVariant);
940 if (s_Data->QuadInstanceCount != 0) {
941 EndQuadInstancedSet();
942 }
943
944 BeginScreenSpacePass();
945 DrawTextInstanced(*elem);
946
947 if (++it_sst != end_sst) {
948 pq.push({&*it_sst, it_sst->m_Depth, SCREEN_SPACE_TEXT});
949 }
950 break;
951 }
952 }
953 }
954
955 if (s_Data->QuadInstanceCount != 0) {
956 EndQuadInstancedSet();
957 }
958
961
962 ClearQuadQueue(s_Data->WorldSpaceTransparentQuadQueue);
963 ClearQuadQueue(s_Data->ScreenSpaceTransparentQuadQueue);
964 ClearTextQueue(s_Data->WorldSpaceTransparentTextQueue);
965 ClearTextQueue(s_Data->ScreenSpaceTransparentTextQueue);
966 }
967
968 void Renderer2D::FlushInstancedQuads() {
970
971 const auto size = reinterpret_cast<uint8_t*>(s_Data->QuadInstanceBufferPtr) - reinterpret_cast<uint8_t*>(s_Data->QuadInstanceBufferBase);
972 s_Data->QuadInstanceVertexBuffer->SetData(s_Data->QuadInstanceBufferBase, size);
973
974 s_Data->QuadInstanceShader->SetMat4("u_ViewProjection", s_Data->CurrentViewProjectionMatrix);
975 s_Data->QuadInstanceShader->SetInt("u_Texture", 0);
976
977 if (s_Data->CurrentTexture != s_Data->NecessaryTexture) {
978 s_Data->NecessaryTexture->Bind(0);
979 s_Data->CurrentTexture = s_Data->NecessaryTexture;
980 }
981
982 Internal::API::DrawElementsInstancedTriangles(s_Data->QuadInstanceCount);
983
984 s_Data->Stats.DrawCalls++;
985 s_Data->Stats.QuadCount += s_Data->QuadInstanceCount;
986
987 s_Data->QuadInstanceCount = 0;
988 }
989
990 void Renderer2D::FlushInstancedChars(Texture2D* atlas, const glm::mat3& modelMatrix) {
992
993 const auto size = reinterpret_cast<uint8_t*>(s_Data->CharInstanceBufferPtr) - reinterpret_cast<uint8_t*>(s_Data->CharInstanceBufferBase);
994 s_Data->CharInstanceVertexBuffer->SetData(s_Data->CharInstanceBufferBase, size);
995
996 const glm::vec2 unitRange = glm::vec2(2.0f) / glm::vec2(atlas->GetWidth(), atlas->GetHeight());
997
998 s_Data->CharInstanceShader->SetMat4("u_ViewProjection", s_Data->CurrentViewProjectionMatrix);
999 s_Data->CharInstanceShader->SetMat3("u_ModelMatrix", modelMatrix);
1000 s_Data->CharInstanceShader->SetInt("u_Texture", 0);
1001 s_Data->CharInstanceShader->SetVec2("u_UnitRange", unitRange);
1002
1003 if (s_Data->CurrentTexture != atlas) {
1004 atlas->Bind(0);
1005 s_Data->CurrentTexture = atlas;
1006 }
1007
1008 Internal::API::DrawElementsInstancedTriangles(s_Data->CharInstanceCount);
1009
1010 s_Data->Stats.DrawCalls++;
1011 s_Data->Stats.CharCount += s_Data->CharInstanceCount;
1012
1013 s_Data->CharInstanceCount = 0;
1014 }
1015 }
1016}
#define CORI_CORE_ERROR_TAGGED(...)
Definition Logger.hpp:1039
#define CORI_CORE_INFO_TAGGED(...)
Definition Logger.hpp:1027
#define CORI_PROFILE_FUNCTION()
Definition Profiler.hpp:9
#define CORI_SPACES_PER_TAB
static std::shared_ptr< AssetType > GetPlaceholder()
Retrieves the instance of placeholder for a specified asset type.
static std::filesystem::path GetAliasedPath(const std::string &alias)
Retries the full aliased path defined in fsgame.json.
Font asset to be used when rendering text. Pretty expensive to create if not cached,...
Definition Font.hpp:15
AssetStatus GetStatus() const
Definition Font.cpp:266
static std::shared_ptr< IndexBuffer > Create(uint32_t *indices, uint32_t count)
Definition Buffers.cpp:29
static void EnableBlending()
Definition API.hpp:45
static void SetDepthMask(const bool mode)
Definition API.hpp:53
static void DisableBlending()
Definition API.hpp:49
static void DrawElementsInstancedTriangles(const uint32_t instanceCount)
Definition API.hpp:33
static void SubmitText(const DrawSpace space, const TextAlignment alignment, const glm::mat3 &transform, const float fontSize, const std::u32string_view &text, const glm::vec4 &color, Font *font, const uint8_t depth, const float limitX, const float lineSpacing, const float kerning)
Draws a UTF-32 fixed length encoded string.
DrawSpace
Defines where to draw the object, in what space.
static void SubmitColoredQuad(const DrawSpace space, const glm::vec2 position, const glm::vec2 halfSize, const glm::vec3 &color)
Convenience function mainly for debugging, draws a plain colored quad.
static Statistics GetStatistics()
Gives you the rendering stats of the last rendered frame.
static void SubmitAABB(const Utility::AABB &aabb, const float lineThickness, const glm::vec3 &color)
Draws the AABB, also a debug convenience function, always draws in world space.
static void SubmitQuad(const DrawSpace space, const ObjectTransparency transparencyMode, const glm::mat3 &transform, const glm::vec2 halfSize, const glm::vec4 &tintColor, Texture2D *texture, const UVs &uvs, const uint8_t depth, const bool flipX, const bool flipY, const bool flatColored)
Submits the quad to the render queue.
TextAlignment
Available text alignment options.
static void BeginScene(const World::Components::Scene::Camera &camera)
static std::shared_ptr< ShaderProgram > Create(const std::filesystem::path &vertexPath, const std::filesystem::path &fragmentPath, const std::filesystem::path &geometryPath={})
A regular 2D texture with 1 layer.
Definition Texture.hpp:99
static std::shared_ptr< Texture2D > Create(const std::shared_ptr< Image > &image)
Creates a Texture2D from the Image.
Definition Texture.cpp:7
static std::shared_ptr< VertexArray > Create()
static std::shared_ptr< VertexBuffer > Create()
Definition Buffers.cpp:7
A scene in a game world.
Definition Scene.hpp:20
T & GetContextComponent()
Definition Scene.hpp:88
auto StaticView()
Definition Scene.hpp:61
A wrapper for an EnTT compile-time view that provides an iterator to access Entity instances directly...
Almost everything connected to graphics is in this namespace.
Definition Window.hpp:7
AABB CalculateAABB(const glm::mat3 &transform, const glm::vec2 halfSize)
Calculates the AABB for the quad taking into account rotation and scale.
Definition AABB.hpp:22
Global engine namespace.
static constexpr char Renderer2D[]
Definition Logger.hpp:67
static constexpr char Self[]
Definition Logger.hpp:47
Axis-Aligned bounding box.
Definition AABB.hpp:11
glm::vec2 m_Max
Definition AABB.hpp:13
glm::vec2 m_Min
Definition AABB.hpp:12
Every Entity has a transform component by default, essential for rendering.
A Scene context component with all the graphical camera data.
Helper to exclude certain components from a static view.