SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
OpenGLRenderer.cpp
Go to the documentation of this file.
13#include <Camera/Camera.hpp>
14#include <Core/Application.hpp>
15#include <Core/SceneBase.hpp>
16#include <Runtime/Skybox.hpp>
17#include <Runtime/Material.hpp>
18#include <Core/GameBase.hpp>
19#include <SDL3/SDL.h>
20#include <vector>
21#include <cstdint>
22#include <cstring>
23
24namespace Sleak {
25namespace RenderEngine {
26
27/// Registers this backend's buffer/shader/texture factories with ResourceManager.
50
54
56 if (m_Initialized) {
57 return true;
58 }
59
60 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
61 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
62 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
63 SDL_GL_CONTEXT_PROFILE_CORE);
64 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
65 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
66
67 m_GLContext = SDL_GL_CreateContext(m_Window->GetSDLWindow());
68 if (!m_GLContext) {
69 SDL_Log("Failed to create OpenGL context: %s", SDL_GetError());
70 return false;
71 }
72
73 if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
74 SDL_Log("Failed to initialize Glad (OpenGL loader)");
75 return false;
76 }
77
78 int width, height;
79 SDL_GetWindowSize(m_Window->GetSDLWindow(), &width, &height);
80 glViewport(0, 0, width, height);
81
82 glEnable(GL_DEPTH_TEST);
83 glEnable(GL_BLEND);
84 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
85
86 // Apply initial VSync setting
87 SDL_GL_SetSwapInterval(m_vsync ? 1 : 0);
88
89 // Query max MSAA samples
90 GLint maxSamples = 1;
91 glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
93 if (maxSamples >= 8) m_maxMsaaSampleCount = 8;
94 else if (maxSamples >= 4) m_maxMsaaSampleCount = 4;
95 else if (maxSamples >= 2) m_maxMsaaSampleCount = 2;
96 SLEAK_INFO("OpenGL max MSAA samples: {}", m_maxMsaaSampleCount);
97
98 SetupVertexLayout();
99
100 m_Initialized = true;
102
103 // Create GBuffer resources (deferred rendering)
104 if (m_deferredEnabled) {
105 CreateGBufferResources();
106 }
107
108 SLEAK_INFO("OpenGL renderer has been initialized successfully!");
109 SLEAK_INFO("OpenGL Version: {}",
110 (const char*)glGetString(GL_VERSION));
111
112 return true;
113}
114
115void OpenGLRenderer::SetupVertexLayout() {
116 glGenVertexArrays(1, &m_VAO);
117}
118
124
125 // Commit staged lightVP before shadow pass so shadow + main agree.
126 if (m_hasPendingLightVP) {
127 memcpy(m_lightVP, m_pendingLightVP, sizeof(m_lightVP));
128 }
129
130 // Shadow pass before main rendering
132 RenderShadowPass();
133 }
134
135 if (m_gbufferCreated && m_deferredEnabled) {
136 // Deferred path: geometry pass writes to GBuffer
137 glBindFramebuffer(GL_FRAMEBUFFER, m_gbufferFBO);
138 GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
139 glDrawBuffers(3, drawBuffers);
140 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
141 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
142 // Geometry pass must NOT blend — each pixel writes final GBuffer values
143 glDisable(GL_BLEND);
144 m_inGeometryPass = true;
145 } else {
146 // Forward path: bind MSAA FBO if active, else default FBO
147 if (m_msaaFBO != 0)
148 glBindFramebuffer(GL_FRAMEBUFFER, m_msaaFBO);
149 else
150 glBindFramebuffer(GL_FRAMEBUFFER, 0);
151
152 glClearColor(0.39f, 0.58f, 0.93f, 1.0f);
153 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
154 }
155
156 glBindVertexArray(m_VAO);
157
158 if (bImInitialized) {
159 ImGui_ImplOpenGL3_NewFrame();
160 ImGui_ImplSDL3_NewFrame();
161 ImGui::NewFrame();
162 }
163}
164
166 // Resolve MSAA FBO to default framebuffer before ImGui.
167 // Skip in deferred mode — we rendered directly to FBO 0.
168 if (m_msaaFBO != 0 && !IsDeferredEnabled()) {
169 int width, height;
170 SDL_GetWindowSize(m_Window->GetSDLWindow(), &width, &height);
171 glBindFramebuffer(GL_READ_FRAMEBUFFER, m_msaaFBO);
172 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
173 glBlitFramebuffer(0, 0, width, height,
174 0, 0, width, height,
175 GL_COLOR_BUFFER_BIT, GL_NEAREST);
176 glBindFramebuffer(GL_FRAMEBUFFER, 0);
177 }
178
179 if (bImInitialized) {
180 ImGui::Render();
181 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
182 }
183
184 glBindVertexArray(0);
185 SDL_GL_SwapWindow(m_Window->GetSDLWindow());
186
188}
189
191 if (m_Initialized) {
192 CleanupGBufferResources();
193 if (m_shadowTransformUBO) { glDeleteBuffers(1, &m_shadowTransformUBO); m_shadowTransformUBO = 0; }
194 if (m_shadowUBO) { glDeleteBuffers(1, &m_shadowUBO); m_shadowUBO = 0; }
195 if (m_shadowDepthTex) { glDeleteTextures(1, &m_shadowDepthTex); m_shadowDepthTex = 0; }
196 if (m_shadowFBO) { glDeleteFramebuffers(1, &m_shadowFBO); m_shadowFBO = 0; }
197 m_shadowMapCreated = false;
198 m_shadowUBOCreated = false;
199 CleanupMSAAFramebuffer();
200 if (bImInitialized) {
201 ImGui_ImplOpenGL3_Shutdown();
202 ImGui_ImplSDL3_Shutdown();
203 ImGui::DestroyContext();
204 bImInitialized = false;
205 }
206 if (m_VAO != 0) {
207 glDeleteVertexArrays(1, &m_VAO);
208 m_VAO = 0;
209 }
210 if (m_GLContext) {
211 SDL_GL_DestroyContext(m_GLContext);
212 m_GLContext = nullptr;
213 }
214 m_Initialized = false;
215 }
216}
217
218void OpenGLRenderer::Resize(uint32_t width, uint32_t height) {
219 glViewport(0, 0, width, height);
220 // Recreate MSAA FBO at new size
221 if (m_msaaFBO != 0) {
222 CleanupMSAAFramebuffer();
223 CreateMSAAFramebuffer();
224 }
225 // Resize GBuffer textures
226 if (m_gbufferCreated) {
227 RecreateGBufferOnResize(static_cast<int>(width), static_cast<int>(height));
228 }
229}
230
231void OpenGLRenderer::Draw(uint32_t vertexCount) { Draw(vertexCount, 0); }
232
233void OpenGLRenderer::Draw(uint32_t vertexCount, uint32_t firstVertex) {
234 GLenum mode = m_debugLineMode ? GL_LINES : GL_TRIANGLES;
235 glDrawArrays(mode, static_cast<GLint>(firstVertex), vertexCount);
236 DrawnVertices += vertexCount;
237 DrawnTriangles += vertexCount / 3;
238}
239
240void OpenGLRenderer::DrawIndexed(uint32_t indexCount) {
241 DrawIndexed(indexCount, 0, 0);
242}
243
244void OpenGLRenderer::DrawIndexed(uint32_t indexCount, uint32_t firstIndex,
245 int32_t baseVertex) {
246 GLenum mode = m_debugLineMode ? GL_LINES : GL_TRIANGLES;
247 const void* offset = reinterpret_cast<const void*>(
248 static_cast<uintptr_t>(firstIndex) * sizeof(uint32_t));
249 glDrawElementsBaseVertex(mode, indexCount, GL_UNSIGNED_INT, offset,
250 baseVertex);
251 DrawnVertices += indexCount;
252 DrawnTriangles += indexCount / 3;
253}
254
255void OpenGLRenderer::DrawInstance(uint32_t instanceCount,
256 uint32_t vertexPerInstance) {
257 GLenum mode = m_debugLineMode ? GL_LINES : GL_TRIANGLES;
258 glDrawArraysInstanced(mode, 0, vertexPerInstance,
259 instanceCount);
260 DrawnVertices += vertexPerInstance * instanceCount;
261 DrawnTriangles += (vertexPerInstance / 3) * instanceCount;
262}
263
264void OpenGLRenderer::DrawIndexedInstance(uint32_t instanceCount,
265 uint32_t indexPerInstance) {
266 DrawIndexedInstance(instanceCount, indexPerInstance, 0, 0);
267}
268
269void OpenGLRenderer::DrawIndexedInstance(uint32_t instanceCount,
270 uint32_t indexPerInstance,
271 uint32_t firstIndex,
272 int32_t baseVertex) {
273 GLenum mode = m_debugLineMode ? GL_LINES : GL_TRIANGLES;
274 const void* offset = reinterpret_cast<const void*>(
275 static_cast<uintptr_t>(firstIndex) * sizeof(uint32_t));
276 glDrawElementsInstancedBaseVertex(mode, indexPerInstance, GL_UNSIGNED_INT,
277 offset, instanceCount, baseVertex);
278 DrawnVertices += indexPerInstance * instanceCount;
279 DrawnTriangles += (indexPerInstance / 3) * instanceCount;
280}
281
283 m_debugLineMode = true;
284}
285
287 m_debugLineMode = false;
288}
289
291 Face = face;
292 ConfigureRenderFace();
293}
294
296 Mode = mode;
297 ConfigureRenderMode();
298}
299
300void OpenGLRenderer::SetViewport(float x, float y, float width,
301 float height, float minDepth,
302 float maxDepth) {
303 glViewport(static_cast<GLint>(x), static_cast<GLint>(y),
304 static_cast<GLsizei>(width),
305 static_cast<GLsizei>(height));
306 glDepthRangef(minDepth, maxDepth);
307}
308
309void OpenGLRenderer::ClearRenderTarget(float r, float g, float b,
310 float a) {
311 glClearColor(r, g, b, a);
312 glClear(GL_COLOR_BUFFER_BIT);
313}
314
315void OpenGLRenderer::ClearDepthStencil(bool clearDepth, bool clearStencil,
316 float depth, uint8_t stencil) {
317 GLbitfield mask = 0;
318 if (clearDepth) {
319 glClearDepthf(depth);
320 mask |= GL_DEPTH_BUFFER_BIT;
321 }
322 if (clearStencil) {
323 glClearStencil(stencil);
324 mask |= GL_STENCIL_BUFFER_BIT;
325 }
326 if (mask) glClear(mask);
327}
328
330 glDepthMask(enabled ? GL_TRUE : GL_FALSE);
331}
332
334 GLenum glFunc = GL_LESS;
335 switch (compare) {
336 case DepthCompare::Less: glFunc = GL_LESS; break;
337 case DepthCompare::LessEqual: glFunc = GL_LEQUAL; break;
338 case DepthCompare::Greater: glFunc = GL_GREATER; break;
339 case DepthCompare::GreaterEqual: glFunc = GL_GEQUAL; break;
340 case DepthCompare::Equal: glFunc = GL_EQUAL; break;
341 case DepthCompare::NotEqual: glFunc = GL_NOTEQUAL; break;
342 case DepthCompare::Always: glFunc = GL_ALWAYS; break;
343 case DepthCompare::Never: glFunc = GL_NEVER; break;
344 }
345 glDepthFunc(glFunc);
346}
347
349 if (enabled)
350 glEnable(GL_CULL_FACE);
351 else
352 glDisable(GL_CULL_FACE);
353}
354
356 if (texture.IsValid())
357 texture->Bind(slot);
358}
359
361 uint32_t slot) {
362 auto* glBuf = dynamic_cast<OpenGLBuffer*>(buffer.get());
363 if (!glBuf) return;
364
365 glBindBuffer(GL_ARRAY_BUFFER, glBuf->GetGLBuffer());
366
367 // A registered layout drives the attribute pointers; 0 means default Vertex
368 const uint32_t customFormat = buffer->GetVertexFormat();
369 const VertexLayoutDesc* customDesc =
370 customFormat != 0 ? VertexFormatRegistry::Get(customFormat) : nullptr;
371
372 if (customDesc) {
373 // One attribute pointer per registry entry, tracking which slots stay live
374 const GLsizei stride = static_cast<GLsizei>(customDesc->stride);
375 uint32_t enabledMask = 0;
376 for (const auto& attr : customDesc->attributes) {
377 const void* off = reinterpret_cast<const void*>(
378 static_cast<uintptr_t>(attr.offset));
379 glEnableVertexAttribArray(attr.location);
380 switch (attr.format) {
382 glVertexAttribPointer(attr.location, 1, GL_FLOAT, GL_FALSE, stride, off);
383 break;
385 glVertexAttribPointer(attr.location, 2, GL_FLOAT, GL_FALSE, stride, off);
386 break;
388 glVertexAttribPointer(attr.location, 3, GL_FLOAT, GL_FALSE, stride, off);
389 break;
391 glVertexAttribPointer(attr.location, 4, GL_FLOAT, GL_FALSE, stride, off);
392 break;
394 glVertexAttribIPointer(attr.location, 1, GL_UNSIGNED_INT, stride, off);
395 break;
397 glVertexAttribIPointer(attr.location, 1, GL_INT, stride, off);
398 break;
399 }
400 if (attr.location < 32) enabledMask |= (1u << attr.location);
401 }
402 // Disable attribute slots left enabled by a previous bind
403 constexpr uint32_t TRACKED_ATTRIB_SLOTS = 8;
404 for (uint32_t loc = 0; loc < TRACKED_ATTRIB_SLOTS; ++loc) {
405 if (!(enabledMask & (1u << loc))) glDisableVertexAttribArray(loc);
406 }
407 } else {
408 // Standard 96-byte Vertex layout: 7 attributes
409 // Position: 3 floats at offset 0
410 glEnableVertexAttribArray(0);
411 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
412 (void*)offsetof(Vertex, px));
413
414 // Normal: 3 floats at offset 12
415 glEnableVertexAttribArray(1);
416 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
417 (void*)offsetof(Vertex, nx));
418
419 // Tangent: 4 floats at offset 24
420 glEnableVertexAttribArray(2);
421 glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
422 (void*)offsetof(Vertex, tx));
423
424 // Color: 4 floats at offset 40
425 glEnableVertexAttribArray(3);
426 glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
427 (void*)offsetof(Vertex, r));
428
429 // UV: 2 floats at offset 56
430 glEnableVertexAttribArray(4);
431 glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
432 (void*)offsetof(Vertex, u));
433
434 // BoneIDs: 4 ints (must use IPointer for integer attributes)
435 glEnableVertexAttribArray(5);
436 glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex),
437 (void*)offsetof(Vertex, boneIDs));
438
439 // BoneWeights: 4 floats
440 glEnableVertexAttribArray(6);
441 glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
442 (void*)offsetof(Vertex, boneWeights));
443 }
444}
445
447 uint32_t slot) {
448 auto* glBuf = dynamic_cast<OpenGLBuffer*>(buffer.get());
449 if (!glBuf) return;
450
451 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glBuf->GetGLBuffer());
452}
453
455 uint32_t slot) {
456 auto* glBuf = dynamic_cast<OpenGLBuffer*>(buffer.get());
457 if (!glBuf) return;
458
459 // Shadow pass: use dedicated shadow CB with LightVP*World for slot 0
460 if (m_inShadowPass && slot == 0 && glBuf->GetCPUShadowCopySize() >= 128) {
461 const float* srcWorld = reinterpret_cast<const float*>(
462 static_cast<const char*>(glBuf->GetCPUShadowCopy()) + 64);
463
464 float shadowPC[32];
465 for (int r = 0; r < 4; ++r) {
466 for (int c = 0; c < 4; ++c) {
467 float sum = 0.0f;
468 for (int k = 0; k < 4; ++k) {
469 sum += srcWorld[r * 4 + k] * m_lightVP[k * 4 + c];
470 }
471 shadowPC[r * 4 + c] = sum;
472 }
473 }
474 memcpy(&shadowPC[16], srcWorld, sizeof(float) * 16);
475
476 // Create dedicated shadow transform UBO on first use
477 if (m_shadowTransformUBO == 0) {
478 glGenBuffers(1, &m_shadowTransformUBO);
479 glBindBuffer(GL_UNIFORM_BUFFER, m_shadowTransformUBO);
480 glBufferData(GL_UNIFORM_BUFFER, 128, nullptr, GL_DYNAMIC_DRAW);
481 glBindBuffer(GL_UNIFORM_BUFFER, 0);
482 }
483
484 glBindBuffer(GL_UNIFORM_BUFFER, m_shadowTransformUBO);
485 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(shadowPC), shadowPC);
486 glBindBuffer(GL_UNIFORM_BUFFER, 0);
487 glBindBufferBase(GL_UNIFORM_BUFFER, slot, m_shadowTransformUBO);
488 return;
489 }
490
491 glBindBufferBase(GL_UNIFORM_BUFFER, slot, glBuf->GetGLBuffer());
492}
493
497
499 void* data) {
500 auto* buffer = new OpenGLBuffer(size, type);
501 if (!buffer->Initialize(data)) {
502 delete buffer;
503 return nullptr;
504 }
505 return buffer;
506}
507
508Shader* OpenGLRenderer::CreateShader(const std::string& shaderSource) {
509 auto* shader = new OpenGLShader();
510 if (shader->compile(shaderSource)) {
511 return shader;
512 }
513 delete shader;
514 return nullptr;
515}
516
517Texture* OpenGLRenderer::CreateTexture(const std::string& TexturePath) {
518 auto* texture = new OpenGLTexture();
519 if (texture->LoadFromFile(TexturePath)) {
520 return texture;
521 }
522 delete texture;
523 return nullptr;
524}
525
527 uint32_t height,
528 void* data) {
529 auto* texture = new OpenGLTexture();
530 if (texture->LoadFromMemory(data, width, height,
532 return texture;
533 }
534 delete texture;
535 return nullptr;
536}
537
538void OpenGLRenderer::CreateMSAAFramebuffer() {
539 if (m_msaaSampleCount <= 1)
540 return;
541
542 int width, height;
543 SDL_GetWindowSize(m_Window->GetSDLWindow(), &width, &height);
544
545 glGenFramebuffers(1, &m_msaaFBO);
546 glBindFramebuffer(GL_FRAMEBUFFER, m_msaaFBO);
547
548 // Multisampled color renderbuffer
549 glGenRenderbuffers(1, &m_msaaColorRBO);
550 glBindRenderbuffer(GL_RENDERBUFFER, m_msaaColorRBO);
551 glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_msaaSampleCount,
552 GL_RGBA8, width, height);
553 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
554 GL_RENDERBUFFER, m_msaaColorRBO);
555
556 // Multisampled depth renderbuffer
557 glGenRenderbuffers(1, &m_msaaDepthRBO);
558 glBindRenderbuffer(GL_RENDERBUFFER, m_msaaDepthRBO);
559 glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_msaaSampleCount,
560 GL_DEPTH24_STENCIL8, width, height);
561 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
562 GL_RENDERBUFFER, m_msaaDepthRBO);
563
564 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
565 SLEAK_ERROR("OpenGL MSAA framebuffer is not complete!");
566
567 glBindFramebuffer(GL_FRAMEBUFFER, 0);
568}
569
570void OpenGLRenderer::CleanupMSAAFramebuffer() {
571 if (m_msaaDepthRBO) { glDeleteRenderbuffers(1, &m_msaaDepthRBO); m_msaaDepthRBO = 0; }
572 if (m_msaaColorRBO) { glDeleteRenderbuffers(1, &m_msaaColorRBO); m_msaaColorRBO = 0; }
573 if (m_msaaFBO) { glDeleteFramebuffers(1, &m_msaaFBO); m_msaaFBO = 0; }
574}
575
578 return;
579 m_msaaChangeRequested = false;
581
582 CleanupMSAAFramebuffer();
583 CreateMSAAFramebuffer();
584
585 SLEAK_INFO("OpenGL MSAA changed to {}x", m_msaaSampleCount);
586}
587
590 return;
592 SDL_GL_SetSwapInterval(m_vsync ? 1 : 0);
593 SLEAK_INFO("OpenGL VSync {}", m_vsync ? "enabled" : "disabled");
594}
595
597 switch (Mode) {
598 case RenderMode::Fill:
599 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
600 break;
602 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
603 break;
605 glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
606 break;
607 default:
608 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
609 break;
610 }
611}
612
614 switch (Face) {
615 case RenderFace::None:
616 glDisable(GL_CULL_FACE);
617 break;
618 case RenderFace::Back:
619 glEnable(GL_CULL_FACE);
620 glCullFace(GL_BACK);
621 break;
623 glEnable(GL_CULL_FACE);
624 glCullFace(GL_FRONT);
625 break;
626 default:
627 glDisable(GL_CULL_FACE);
628 break;
629 }
630}
631
632Texture* OpenGLRenderer::CreateCubemapTexture(const std::array<std::string, 6>& facePaths) {
633 auto* texture = new OpenGLCubemapTexture();
634 if (texture->LoadCubemap(facePaths)) {
635 return texture;
636 }
637 delete texture;
638 return nullptr;
639}
640
642 auto* texture = new OpenGLCubemapTexture();
643 if (texture->LoadEquirectangular(panoramaPath)) {
644 return texture;
645 }
646 delete texture;
647 return nullptr;
648}
649
651 IMGUI_CHECKVERSION();
652 ImGui::CreateContext();
653 ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
654 ImGui::StyleColorsDark();
655
656 if (!ImGui_ImplSDL3_InitForOpenGL(m_Window->GetSDLWindow(),
657 m_GLContext))
658 return false;
659
660 if (!ImGui_ImplOpenGL3_Init("#version 450"))
661 return false;
662
663 bImInitialized = true;
664 return true;
665}
666
667void OpenGLRenderer::SetLightVP(const float* mat) {
668 if (mat) {
669 memcpy(m_pendingLightVP, mat, sizeof(m_pendingLightVP));
670 m_hasPendingLightVP = true;
671 }
672}
673
674bool OpenGLRenderer::CreateShadowUBO() {
675 if (m_shadowUBOCreated) return true;
676 glGenBuffers(1, &m_shadowUBO);
677 glBindBuffer(GL_UNIFORM_BUFFER, m_shadowUBO);
678 glBufferData(GL_UNIFORM_BUFFER, sizeof(RenderEngine::PCSSShadowGPUData), nullptr, GL_DYNAMIC_DRAW);
679 glBindBuffer(GL_UNIFORM_BUFFER, 0);
680 m_shadowUBOCreated = true;
681 return true;
682}
683
684void OpenGLRenderer::UpdateShadowLightUBO(const void* data, uint32_t size) {
685 if (!m_shadowUBOCreated && !CreateShadowUBO()) return;
686 if (!data) return;
687
688 const auto* ubo = static_cast<const RenderEngine::ShadowLightUBO*>(data);
689
690 RenderEngine::PCSSShadowGPUData shadowData{};
691 memcpy(shadowData.LightVP, ubo->LightVP, sizeof(float) * 16);
692 memcpy(shadowData.NdcToShadow, ubo->NdcToShadow, sizeof(float) * 16);
693 shadowData.ShadowBias = ubo->ShadowBias;
694 shadowData.ShadowStrength = ubo->ShadowStrength;
695 shadowData.ShadowTexelSize = ubo->ShadowTexelSize;
696 shadowData.ShadowLightSize = ubo->LightSize;
697 shadowData.PCSSEnabled = m_pcssEnabled ? 1 : 0;
698 shadowData.ShadowMapEnabled = m_shadowPassEnabled ? 1 : 0;
699
700 glBindBuffer(GL_UNIFORM_BUFFER, m_shadowUBO);
701 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(shadowData), &shadowData);
702 glBindBuffer(GL_UNIFORM_BUFFER, 0);
703
704 // Bind shadow UBO at binding 5 (matches shader)
705 glBindBufferBase(GL_UNIFORM_BUFFER, 5, m_shadowUBO);
706}
707
708bool OpenGLRenderer::CreateShadowMapResources() {
709 if (m_shadowMapCreated) return true;
710
711 // Create depth texture
712 glGenTextures(1, &m_shadowDepthTex);
713 glBindTexture(GL_TEXTURE_2D, m_shadowDepthTex);
714 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F,
716 GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
717 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
718 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
719 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
720 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
721 float borderColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
722 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
723 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
724 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
725 glBindTexture(GL_TEXTURE_2D, 0);
726
727 // Create FBO
728 glGenFramebuffers(1, &m_shadowFBO);
729 glBindFramebuffer(GL_FRAMEBUFFER, m_shadowFBO);
730 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowDepthTex, 0);
731 glDrawBuffer(GL_NONE);
732 glReadBuffer(GL_NONE);
733 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
734 SLEAK_ERROR("OpenGL shadow FBO is not complete!");
735 glBindFramebuffer(GL_FRAMEBUFFER, 0);
736 return false;
737 }
738 glBindFramebuffer(GL_FRAMEBUFFER, 0);
739
740 m_shadowMapCreated = true;
742 SLEAK_INFO("OpenGL shadow map resources created ({}x{})", m_shadowMapResolution, m_shadowMapResolution);
743 return true;
744}
745
746void OpenGLRenderer::RenderShadowPass() {
747 // Skip if no cached draws — preserve previous frame's shadow map
748 auto* queue = RenderCommandQueue::GetInstance();
749 if (!queue || !queue->HasCachedShadowDraws()) return;
750
751 if (!m_shadowMapCreated) {
752 if (!CreateShadowMapResources()) return;
753 }
754
755 // Save current viewport
756 GLint savedViewport[4];
757 glGetIntegerv(GL_VIEWPORT, savedViewport);
758
759 // Bind shadow FBO
760 glBindFramebuffer(GL_FRAMEBUFFER, m_shadowFBO);
762 glClear(GL_DEPTH_BUFFER_BIT);
763
764 // Enable polygon offset for slope-scale depth bias
765 glEnable(GL_POLYGON_OFFSET_FILL);
766 glPolygonOffset(2.0f, 4.0f);
767
768 // Back-face cull: closed solid volumes always have the light-facing
769 // surface win — same depth result, half the raster.
770 // (FRONT culling stays wrong: it removed the outward-facing surfaces.)
771 glEnable(GL_CULL_FACE);
772 glCullFace(GL_BACK);
773
774 // Ensure VAO is bound for shadow pass draws
775 glBindVertexArray(m_VAO);
776
777 // Execute shadow draw commands
778 m_inShadowPass = true;
779 if (queue) {
780 queue->ExecuteShadowPass(this);
781 }
782 m_inShadowPass = false;
783
784 // Restore state
785 glDisable(GL_POLYGON_OFFSET_FILL);
786 glEnable(GL_CULL_FACE); // re-enable culling for main pass
787 ConfigureRenderFace();
788
789 // Restore framebuffer
790 if (m_msaaFBO != 0)
791 glBindFramebuffer(GL_FRAMEBUFFER, m_msaaFBO);
792 else
793 glBindFramebuffer(GL_FRAMEBUFFER, 0);
794
795 glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]);
796
797 // Bind shadow map texture at unit 3
798 glActiveTexture(GL_TEXTURE3);
799 glBindTexture(GL_TEXTURE_2D, m_shadowDepthTex);
800 glActiveTexture(GL_TEXTURE0);
801}
802
803// ============================================================
804// Deferred rendering — GBuffer implementation
805// ============================================================
806
808 return m_deferredEnabled && m_gbufferCreated;
809}
810
811bool OpenGLRenderer::CreateGBufferResources() {
812 if (m_gbufferCreated) return true;
813
814 int w, h;
815 SDL_GetWindowSize(m_Window->GetSDLWindow(), &w, &h);
816 m_gbufferWidth = w;
817 m_gbufferHeight = h;
818
819 // ---- GBuffer FBO ----
820 glGenFramebuffers(1, &m_gbufferFBO);
821 glBindFramebuffer(GL_FRAMEBUFFER, m_gbufferFBO);
822
823 auto makeColorTex = [](GLuint& tex, GLenum internalFmt, int w, int h) {
824 glGenTextures(1, &tex);
825 glBindTexture(GL_TEXTURE_2D, tex);
826 GLenum fmt = GL_RGBA;
827 GLenum type = GL_UNSIGNED_BYTE;
828 if (internalFmt == GL_RGBA16F || internalFmt == GL_RGBA32F) {
829 type = GL_FLOAT;
830 }
831 glTexImage2D(GL_TEXTURE_2D, 0, internalFmt, w, h, 0, fmt, type, nullptr);
832 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
833 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
834 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
835 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
836 };
837
838 // RT0: Albedo + AO (RGBA8)
839 makeColorTex(m_gbufferAlbedoAO, GL_RGBA8, w, h);
840 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_gbufferAlbedoAO, 0);
841
842 // RT1: Normal + Roughness (RGBA16F)
843 makeColorTex(m_gbufferNormalRough, GL_RGBA16F, w, h);
844 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, m_gbufferNormalRough, 0);
845
846 // RT2: Metallic + Emissive (RGBA8)
847 makeColorTex(m_gbufferMetalEmit, GL_RGBA8, w, h);
848 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, m_gbufferMetalEmit, 0);
849
850 // World position is reconstructed from depth + InvViewProj in the
851 // lighting/SSAO passes (no RGBA32F worldpos RT) to cut GBuffer bandwidth.
852
853 // Depth texture (read back in lighting pass for world pos reconstruction)
854 glGenTextures(1, &m_gbufferDepth);
855 glBindTexture(GL_TEXTURE_2D, m_gbufferDepth);
856 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0,
857 GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
858 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
859 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
860 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
861 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
862 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_gbufferDepth, 0);
863
864 GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1,
865 GL_COLOR_ATTACHMENT2 };
866 glDrawBuffers(3, drawBuffers);
867
868 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
869 SLEAK_ERROR("OpenGL GBuffer FBO is not complete!");
870 glBindFramebuffer(GL_FRAMEBUFFER, 0);
871 return false;
872 }
873 glBindFramebuffer(GL_FRAMEBUFFER, 0);
874
875 // ---- GBuffer geometry shader ----
876 m_gbufferShader = new OpenGLShader();
877 if (!m_gbufferShader->compile("assets/shaders/gbuffer_gl.vert",
878 "assets/shaders/gbuffer_gl.frag")) {
879 SLEAK_ERROR("Failed to compile GBuffer geometry shader!");
880 delete m_gbufferShader;
881 m_gbufferShader = nullptr;
882 CleanupGBufferResources();
883 return false;
884 }
885
886 // ---- Lighting pass shader ----
887 m_lightingShader = new OpenGLShader();
888 if (!m_lightingShader->compile("assets/shaders/lighting_pass_gl.vert",
889 "assets/shaders/lighting_pass_gl.frag")) {
890 SLEAK_ERROR("Failed to compile deferred lighting pass shader!");
891 delete m_lightingShader;
892 m_lightingShader = nullptr;
893 CleanupGBufferResources();
894 return false;
895 }
896
897 // ---- Empty VAO for fullscreen triangle ----
898 glGenVertexArrays(1, &m_lightingVAO);
899
900 // ---- Deferred CB UBO (binding 6) ----
901 glGenBuffers(1, &m_deferredCBUBO);
902 glBindBuffer(GL_UNIFORM_BUFFER, m_deferredCBUBO);
903 glBufferData(GL_UNIFORM_BUFFER, sizeof(RenderEngine::DeferredCBData), nullptr, GL_DYNAMIC_DRAW);
904 glBindBuffer(GL_UNIFORM_BUFFER, 0);
905 glBindBufferBase(GL_UNIFORM_BUFFER, 6, m_deferredCBUBO);
906 m_deferredCBCreated = true;
907
908 m_gbufferCreated = true;
909 SLEAK_INFO("OpenGL GBuffer created ({}x{}) — deferred rendering active", w, h);
910
911 if (!CreateSSAOResources()) {
912 SLEAK_WARN("OpenGL SSAO resources failed to initialize — SSAO disabled");
913 }
914 return true;
915}
916
917void OpenGLRenderer::CleanupGBufferResources() {
918 CleanupSSAOResources();
919 CleanupIBL();
920 if (m_gbufferAlbedoAO) { glDeleteTextures(1, &m_gbufferAlbedoAO); m_gbufferAlbedoAO = 0; }
921 if (m_gbufferNormalRough) { glDeleteTextures(1, &m_gbufferNormalRough); m_gbufferNormalRough = 0; }
922 if (m_gbufferMetalEmit) { glDeleteTextures(1, &m_gbufferMetalEmit); m_gbufferMetalEmit = 0; }
923 if (m_gbufferDepth) { glDeleteTextures(1, &m_gbufferDepth); m_gbufferDepth = 0; }
924 if (m_gbufferFBO) { glDeleteFramebuffers(1, &m_gbufferFBO); m_gbufferFBO = 0; }
925 if (m_lightingVAO) { glDeleteVertexArrays(1, &m_lightingVAO); m_lightingVAO = 0; }
926 if (m_deferredCBUBO) { glDeleteBuffers(1, &m_deferredCBUBO); m_deferredCBUBO = 0; }
927 delete m_gbufferShader; m_gbufferShader = nullptr;
928 delete m_lightingShader; m_lightingShader = nullptr;
929 m_gbufferCreated = false;
930 m_deferredCBCreated = false;
931}
932
933// IBL — lazy bake from active scene's skybox cubemap.
934// First call (or when the skybox cubemap changes) reruns the precompute.
935void OpenGLRenderer::EnsureIBL() {
936 auto* app = Application::GetInstance();
937 if (!app) return;
938 auto* game = app->GetGame();
939 if (!game) return;
940 auto* scene = game->GetActiveScene();
941 if (!scene) return;
942 auto* skybox = scene->GetSkybox();
943 if (!skybox || !skybox->IsInitialized()) return;
944 auto* cube = skybox->GetCubemapTexture();
945 if (!cube) return;
946 auto* glCube = dynamic_cast<OpenGLCubemapTexture*>(cube);
947 if (!glCube) return;
948
949 GLuint cubeHandle = glCube->GetGLTexture();
950 if (cubeHandle == 0) return;
951
952 if (!m_ibl) m_ibl = new OpenGLIBL();
953 if (cubeHandle != m_iblBoundCubemap) {
954 m_ibl->Cleanup();
955 if (m_ibl->Initialize(cubeHandle)) {
956 m_iblBoundCubemap = cubeHandle;
957 } else {
958 m_iblBoundCubemap = 0;
959 }
960 }
961
962 if (m_iblSettingsUBO == 0) {
963 glGenBuffers(1, &m_iblSettingsUBO);
964 glBindBuffer(GL_UNIFORM_BUFFER, m_iblSettingsUBO);
965 glBufferData(GL_UNIFORM_BUFFER,
966 sizeof(IBLSettingsGPUData),
967 nullptr, GL_DYNAMIC_DRAW);
968 glBindBuffer(GL_UNIFORM_BUFFER, 0);
969 }
970}
971
972void OpenGLRenderer::CleanupIBL() {
973 if (m_ibl) { delete m_ibl; m_ibl = nullptr; }
974 if (m_iblSettingsUBO) {
975 glDeleteBuffers(1, &m_iblSettingsUBO);
976 m_iblSettingsUBO = 0;
977 }
978 m_iblBoundCubemap = 0;
979}
980
981void OpenGLRenderer::RecreateGBufferOnResize(int width, int height) {
982 if (!m_gbufferCreated) return;
983
984 m_gbufferWidth = width;
985 m_gbufferHeight = height;
986
987 // Resize AlbedoAO
988 glBindTexture(GL_TEXTURE_2D, m_gbufferAlbedoAO);
989 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
990
991 // Resize NormalRough
992 glBindTexture(GL_TEXTURE_2D, m_gbufferNormalRough);
993 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
994
995 // Resize MetalEmit
996 glBindTexture(GL_TEXTURE_2D, m_gbufferMetalEmit);
997 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
998
999 // Resize depth
1000 glBindTexture(GL_TEXTURE_2D, m_gbufferDepth);
1001 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0,
1002 GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
1003
1004 glBindTexture(GL_TEXTURE_2D, 0);
1005
1006 RecreateSSAOOnResize(width, height);
1007
1008 SLEAK_INFO("OpenGL GBuffer resized to {}x{}", width, height);
1009}
1010
1012 if (m_gbufferShader) {
1013 // Ensure GBuffer FBO is active with all 3 color attachments
1014 // (RT0=AlbedoAO, RT1=NormalRough, RT2=MetalEmit)
1015 glBindFramebuffer(GL_FRAMEBUFFER, m_gbufferFBO);
1016 GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1,
1017 GL_COLOR_ATTACHMENT2 };
1018 glDrawBuffers(3, drawBuffers);
1019 // Ensure depth writes are enabled for geometry pass
1020 glEnable(GL_DEPTH_TEST);
1021 glDepthMask(GL_TRUE);
1022
1023 // Clear stale shadow sampler from unit 3 — the shadow depth texture
1024 // has GL_TEXTURE_COMPARE_MODE enabled which conflicts with the GBuffer
1025 // shader's sampler2D declaration at binding 3 (roughnessTexture).
1026 glActiveTexture(GL_TEXTURE3);
1027 glBindTexture(GL_TEXTURE_2D, 0);
1028 glActiveTexture(GL_TEXTURE0);
1029
1030 m_gbufferShader->bind();
1031 glBindVertexArray(m_VAO);
1032 } else {
1033 static bool warned = false;
1034 if (!warned) {
1035 SLEAK_ERROR("BindGBufferShader: m_gbufferShader is NULL!");
1036 warned = true;
1037 }
1038 }
1039}
1040
1041void OpenGLRenderer::BindPBRMaterial(Sleak::Material* material) {
1042 // Deferred geometry-pass material bind (restores pre-rewrite OpenGL path):
1043 // bind the GBuffer shader + the material textures/CB. Without this, terrain
1044 // is drawn with the leftover water program (all-blue bug).
1045 BindGBufferShader();
1046 if (material) material->BindTexturesAndCB();
1047}
1048
1049void OpenGLRenderer::UpdateDeferredCB(const void* data, uint32_t size) {
1050 if (!m_deferredCBCreated) return;
1051 glBindBuffer(GL_UNIFORM_BUFFER, m_deferredCBUBO);
1052 glBufferSubData(GL_UNIFORM_BUFFER, 0, size, data);
1053 glBindBuffer(GL_UNIFORM_BUFFER, 0);
1054 glBindBufferBase(GL_UNIFORM_BUFFER, 6, m_deferredCBUBO);
1055}
1056
1058 m_inGeometryPass = false;
1059
1060 if (!m_gbufferCreated || !m_lightingShader) return;
1061
1062 // SSAO must run BEFORE the lighting pass so its blurred output is
1063 // available for the lighting shader to multiply against vertex AO.
1064 ExecuteSSAOPass();
1065
1066 // IBL precompute (cheap when already baked) — must happen before the
1067 // lighting shader binds units 14/15/16 and UBO 10.
1068 EnsureIBL();
1069
1070 // Deferred rendering always outputs to the default FBO (framebuffer 0).
1071 // MSAA is incompatible with deferred (multisampled GBuffer is too expensive),
1072 // so we skip the MSAA FBO and render directly to the default framebuffer.
1073 constexpr GLuint targetFBO = 0;
1074 glBindFramebuffer(GL_FRAMEBUFFER, targetFBO);
1075
1076 // Keep the sky color cleared
1077 glClearColor(0.39f, 0.58f, 0.93f, 1.0f);
1078 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1079
1080 // Enable depth writes so the lighting shader can output gl_FragDepth.
1081 // This replaces the old glBlitFramebuffer approach which silently failed
1082 // on some drivers when GBuffer depth (32F) differs from default FBO depth (24-bit).
1083 glEnable(GL_DEPTH_TEST);
1084 glDepthFunc(GL_ALWAYS); // fullscreen triangle must always pass
1085 glDepthMask(GL_TRUE); // allow depth writes
1086 glDisable(GL_BLEND);
1087
1088 // Bind lighting shader
1089 m_lightingShader->bind();
1090
1091 // Bind GBuffer textures at units 8-11
1092 glActiveTexture(GL_TEXTURE8); glBindTexture(GL_TEXTURE_2D, m_gbufferAlbedoAO);
1093 glActiveTexture(GL_TEXTURE9); glBindTexture(GL_TEXTURE_2D, m_gbufferNormalRough);
1094 glActiveTexture(GL_TEXTURE10); glBindTexture(GL_TEXTURE_2D, m_gbufferMetalEmit);
1095 glActiveTexture(GL_TEXTURE11); glBindTexture(GL_TEXTURE_2D, m_gbufferDepth);
1096 // SSAO result at unit 13 (default to 0 = "fully lit" if SSAO disabled)
1097 glActiveTexture(GL_TEXTURE13);
1098 glBindTexture(GL_TEXTURE_2D, m_ssaoCreated ? m_ssaoBlurTex : 0);
1099 // SSAO enable flag via plain uniform — avoids growing DeferredCBData
1100 GLint ssaoLoc = glGetUniformLocation(
1101 m_lightingShader->GetProgram(), "uSSAOEnabled");
1102 if (ssaoLoc >= 0) {
1103 glUniform1i(ssaoLoc, (m_ssaoEnabled && m_ssaoCreated) ? 1 : 0);
1104 }
1105 glActiveTexture(GL_TEXTURE0);
1106
1107 // Rebind shadow map at unit 3 — BindGBufferShader cleared it to avoid
1108 // sampler2DShadow/sampler2D conflict during the geometry pass.
1109 if (m_shadowMapCreated) {
1110 glActiveTexture(GL_TEXTURE3);
1111 glBindTexture(GL_TEXTURE_2D, m_shadowDepthTex);
1112 glActiveTexture(GL_TEXTURE0);
1113 }
1114
1115 // IBL — bind irradiance/prefilter/BRDF at units 14/15/16 and upload the
1116 // settings UBO at slot 10. Disabled if precompute hasn't completed.
1117 bool iblReady = (m_ibl && m_ibl->IsInitialized());
1118 if (iblReady) m_ibl->Bind();
1119 if (m_iblSettingsUBO) {
1120 IBLSettingsGPUData ibl{};
1121 ibl.IBLEnabled = (iblReady && m_iblEnabled) ? 1u : 0u;
1122 ibl.IBLIntensity = m_iblIntensity;
1123 ibl.MaxReflectionLOD = static_cast<float>(OpenGLIBL::PREFILTER_MIPS - 1);
1124 ibl._iblPad0 = 0;
1125 glBindBuffer(GL_UNIFORM_BUFFER, m_iblSettingsUBO);
1126 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ibl), &ibl);
1127 glBindBuffer(GL_UNIFORM_BUFFER, 0);
1128 glBindBufferBase(GL_UNIFORM_BUFFER, 10, m_iblSettingsUBO);
1129 }
1130
1131 // Draw fullscreen triangle — shader writes color + gl_FragDepth
1132 glBindVertexArray(m_lightingVAO);
1133 glDrawArrays(GL_TRIANGLES, 0, 3);
1134
1135 // Restore depth state for forward pass
1136 glDepthFunc(GL_LESS);
1137 glEnable(GL_BLEND);
1138 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1139
1140 // Rebind main VAO for forward pass
1141 glBindVertexArray(m_VAO);
1142}
1143
1145 m_inForwardTransparentPass = true;
1146 // Transparent objects need alpha blending
1147 glEnable(GL_BLEND);
1148 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1149}
1150
1152 m_inForwardTransparentPass = false;
1153}
1154
1155// ============================================================
1156// SSAO pass — runs between GBuffer write and deferred lighting
1157// Output: R8 half-or-full-res AO in m_ssaoBlurTex, sampled by
1158// lighting_pass_gl.frag at texture unit 13 and multiplied into ao.
1159// ============================================================
1160
1161bool OpenGLRenderer::CreateSSAOResources() {
1162 if (m_ssaoCreated) return true;
1163 if (!m_gbufferCreated) return false;
1164
1165 int w = m_gbufferWidth;
1166 int h = m_gbufferHeight;
1167
1168 auto makeR8Tex = [](GLuint& tex, int w, int h) {
1169 glGenTextures(1, &tex);
1170 glBindTexture(GL_TEXTURE_2D, tex);
1171 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
1172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1174 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1175 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1176 };
1177
1178 // ---- Raw SSAO FBO ----
1179 makeR8Tex(m_ssaoTexture, w, h);
1180 glGenFramebuffers(1, &m_ssaoFBO);
1181 glBindFramebuffer(GL_FRAMEBUFFER, m_ssaoFBO);
1182 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
1183 GL_TEXTURE_2D, m_ssaoTexture, 0);
1184 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
1185 SLEAK_ERROR("OpenGL SSAO FBO incomplete");
1186 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1187 CleanupSSAOResources();
1188 return false;
1189 }
1190
1191 // ---- Blur FBO ----
1192 makeR8Tex(m_ssaoBlurTex, w, h);
1193 glGenFramebuffers(1, &m_ssaoBlurFBO);
1194 glBindFramebuffer(GL_FRAMEBUFFER, m_ssaoBlurFBO);
1195 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
1196 GL_TEXTURE_2D, m_ssaoBlurTex, 0);
1197 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
1198 SLEAK_ERROR("OpenGL SSAO blur FBO incomplete");
1199 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1200 CleanupSSAOResources();
1201 return false;
1202 }
1203 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1204
1205 // ---- 4x4 noise texture ----
1206 SSAOKernel kernelGen;
1207 kernelGen.Generate();
1208
1209 glGenTextures(1, &m_ssaoNoiseTex);
1210 glBindTexture(GL_TEXTURE_2D, m_ssaoNoiseTex);
1211 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F,
1213 GL_RGBA, GL_FLOAT, kernelGen.noiseData.data());
1214 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1215 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1216 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1217 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1218
1219 // ---- Shaders ----
1220 m_ssaoShader = new OpenGLShader();
1221 if (!m_ssaoShader->compile("assets/shaders/ssao_gl.vert",
1222 "assets/shaders/ssao_gl.frag")) {
1223 SLEAK_ERROR("Failed to compile SSAO shader");
1224 CleanupSSAOResources();
1225 return false;
1226 }
1227 m_ssaoBlurShader = new OpenGLShader();
1228 if (!m_ssaoBlurShader->compile("assets/shaders/ssao_blur_gl.vert",
1229 "assets/shaders/ssao_blur_gl.frag")) {
1230 SLEAK_ERROR("Failed to compile SSAO blur shader");
1231 CleanupSSAOResources();
1232 return false;
1233 }
1234
1235 // ---- UBOs ----
1236 // Settings (binding 0) — 64 bytes
1237 glGenBuffers(1, &m_ssaoSettingsUBO);
1238 glBindBuffer(GL_UNIFORM_BUFFER, m_ssaoSettingsUBO);
1239 glBufferData(GL_UNIFORM_BUFFER, sizeof(SSAOSettingsGPUData), nullptr, GL_DYNAMIC_DRAW);
1240
1241 // Kernel (binding 1) — 1024 bytes, upload once (deterministic seed)
1242 glGenBuffers(1, &m_ssaoKernelUBO);
1243 glBindBuffer(GL_UNIFORM_BUFFER, m_ssaoKernelUBO);
1244 glBufferData(GL_UNIFORM_BUFFER,
1245 sizeof(SSAOKernel::Sample) * SSAOKernel::MAX_KERNEL_SIZE,
1246 kernelGen.samples.data(), GL_STATIC_DRAW);
1247
1248 // Camera (binding 2) — Projection + View + InvViewProj = 3 * 64 = 192 bytes
1249 glGenBuffers(1, &m_ssaoCameraUBO);
1250 glBindBuffer(GL_UNIFORM_BUFFER, m_ssaoCameraUBO);
1251 glBufferData(GL_UNIFORM_BUFFER, sizeof(float) * 16 * 3, nullptr, GL_DYNAMIC_DRAW);
1252
1253 glBindBuffer(GL_UNIFORM_BUFFER, 0);
1254
1255 m_ssaoCreated = true;
1256 SLEAK_INFO("OpenGL SSAO ready ({}x{})", w, h);
1257 return true;
1258}
1259
1260void OpenGLRenderer::CleanupSSAOResources() {
1261 if (m_ssaoFBO) { glDeleteFramebuffers(1, &m_ssaoFBO); m_ssaoFBO = 0; }
1262 if (m_ssaoBlurFBO) { glDeleteFramebuffers(1, &m_ssaoBlurFBO); m_ssaoBlurFBO = 0; }
1263 if (m_ssaoTexture) { glDeleteTextures(1, &m_ssaoTexture); m_ssaoTexture = 0; }
1264 if (m_ssaoBlurTex) { glDeleteTextures(1, &m_ssaoBlurTex); m_ssaoBlurTex = 0; }
1265 if (m_ssaoNoiseTex) { glDeleteTextures(1, &m_ssaoNoiseTex); m_ssaoNoiseTex = 0; }
1266 if (m_ssaoSettingsUBO) { glDeleteBuffers(1, &m_ssaoSettingsUBO); m_ssaoSettingsUBO = 0; }
1267 if (m_ssaoKernelUBO) { glDeleteBuffers(1, &m_ssaoKernelUBO); m_ssaoKernelUBO = 0; }
1268 if (m_ssaoCameraUBO) { glDeleteBuffers(1, &m_ssaoCameraUBO); m_ssaoCameraUBO = 0; }
1269 delete m_ssaoShader; m_ssaoShader = nullptr;
1270 delete m_ssaoBlurShader; m_ssaoBlurShader = nullptr;
1271 m_ssaoCreated = false;
1272}
1273
1274void OpenGLRenderer::RecreateSSAOOnResize(int width, int height) {
1275 if (!m_ssaoCreated) return;
1276
1277 glBindTexture(GL_TEXTURE_2D, m_ssaoTexture);
1278 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
1279
1280 glBindTexture(GL_TEXTURE_2D, m_ssaoBlurTex);
1281 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
1282
1283 glBindTexture(GL_TEXTURE_2D, 0);
1284}
1285
1286void OpenGLRenderer::ExecuteSSAOPass() {
1287 if (!m_ssaoCreated || !m_ssaoShader || !m_ssaoBlurShader) return;
1288
1289 // Fast-path: if SSAO is disabled, clear the blur target to white so the
1290 // lighting shader's ao * ssao multiply is a no-op.
1291 if (!m_ssaoEnabled) {
1292 glBindFramebuffer(GL_FRAMEBUFFER, m_ssaoBlurFBO);
1293 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
1294 glClear(GL_COLOR_BUFFER_BIT);
1295 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1296 return;
1297 }
1298
1299 // ---- Upload per-frame settings UBO ----
1300 SSAOSettingsGPUData s{};
1301 s.Radius = m_ssaoRadius;
1302 s.Bias = m_ssaoBias;
1303 s.Power = m_ssaoPower;
1304 s.KernelSize = SSAOKernel::MAX_KERNEL_SIZE;
1305 s.ScreenWidth = static_cast<float>(m_gbufferWidth);
1306 s.ScreenHeight = static_cast<float>(m_gbufferHeight);
1307 s.SSAOEnabled = 1;
1308 s.NearPlane = 0.1f;
1309 s.FarPlane = 2000.0f;
1310
1311 glBindBuffer(GL_UNIFORM_BUFFER, m_ssaoSettingsUBO);
1312 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(s), &s);
1313
1314 // ---- Upload Camera UBO (Projection, View, InvViewProj) ----
1317 float camData[16 * 3];
1318 std::memcpy(camData, &P(0,0), sizeof(float) * 16);
1319 std::memcpy(camData + 16, &V(0,0), sizeof(float) * 16);
1320 // InvViewProj = inverse(View * Proj) — reconstructs world pos from depth.
1321 // Row-vector engine convention: clip = world * (View * Proj).
1322 Math::Matrix4 VP = V * P;
1323 float invP[16];
1324 {
1325 // Inline determinant-based 4x4 inverse (Cramer). Returns identity if singular.
1326 const float* m = &VP(0,0);
1327 float i0 = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15] + m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10];
1328 float i4 = -m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15] - m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10];
1329 float i8 = m[4]*m[9] *m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15] + m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9];
1330 float i12 = -m[4]*m[9] *m[14] + m[4]*m[10]*m[13] + m[8]*m[5]*m[14] - m[8]*m[6]*m[13] - m[12]*m[5]*m[10] + m[12]*m[6]*m[9];
1331 float det = m[0]*i0 + m[1]*i4 + m[2]*i8 + m[3]*i12;
1332 if (std::fabs(det) < 1e-8f) {
1333 std::memset(invP, 0, sizeof(invP));
1334 invP[0] = invP[5] = invP[10] = invP[15] = 1.0f;
1335 } else {
1336 float id = 1.0f / det;
1337 invP[0] = i0 * id; invP[4] = i4 * id; invP[8] = i8 * id; invP[12] = i12 * id;
1338 invP[1] = (-m[1]*m[10]*m[15] + m[1]*m[11]*m[14] + m[9]*m[2]*m[15] - m[9]*m[3]*m[14] - m[13]*m[2]*m[11] + m[13]*m[3]*m[10]) * id;
1339 invP[5] = ( m[0]*m[10]*m[15] - m[0]*m[11]*m[14] - m[8]*m[2]*m[15] + m[8]*m[3]*m[14] + m[12]*m[2]*m[11] - m[12]*m[3]*m[10]) * id;
1340 invP[9] = (-m[0]*m[9] *m[15] + m[0]*m[11]*m[13] + m[8]*m[1]*m[15] - m[8]*m[3]*m[13] - m[12]*m[1]*m[11] + m[12]*m[3]*m[9] ) * id;
1341 invP[13] = ( m[0]*m[9] *m[14] - m[0]*m[10]*m[13] - m[8]*m[1]*m[14] + m[8]*m[2]*m[13] + m[12]*m[1]*m[10] - m[12]*m[2]*m[9] ) * id;
1342 invP[2] = ( m[1]*m[6]*m[15] - m[1]*m[7]*m[14] - m[5]*m[2]*m[15] + m[5]*m[3]*m[14] + m[13]*m[2]*m[7] - m[13]*m[3]*m[6]) * id;
1343 invP[6] = (-m[0]*m[6]*m[15] + m[0]*m[7]*m[14] + m[4]*m[2]*m[15] - m[4]*m[3]*m[14] - m[12]*m[2]*m[7] + m[12]*m[3]*m[6]) * id;
1344 invP[10] = ( m[0]*m[5]*m[15] - m[0]*m[7]*m[13] - m[4]*m[1]*m[15] + m[4]*m[3]*m[13] + m[12]*m[1]*m[7] - m[12]*m[3]*m[5]) * id;
1345 invP[14] = (-m[0]*m[5]*m[14] + m[0]*m[6]*m[13] + m[4]*m[1]*m[14] - m[4]*m[2]*m[13] - m[12]*m[1]*m[6] + m[12]*m[2]*m[5]) * id;
1346 invP[3] = (-m[1]*m[6]*m[11] + m[1]*m[7]*m[10] + m[5]*m[2]*m[11] - m[5]*m[3]*m[10] - m[9]*m[2]*m[7] + m[9]*m[3]*m[6]) * id;
1347 invP[7] = ( m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11] + m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6]) * id;
1348 invP[11] = (-m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11] - m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5]) * id;
1349 invP[15] = ( m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10] + m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5]) * id;
1350 }
1351 }
1352 std::memcpy(camData + 32, invP, sizeof(float) * 16);
1353
1354 glBindBuffer(GL_UNIFORM_BUFFER, m_ssaoCameraUBO);
1355 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(camData), camData);
1356 glBindBuffer(GL_UNIFORM_BUFFER, 0);
1357
1358 // Bind UBOs at private slots 7/8/9 — slots 0-6 are used by the lighting
1359 // pass (LightUBO=2, ShadowUBO=5, DeferredCB=6, material/transform=0,1,3).
1360 // Binding SSAO's UBOs to those would silently nuke the lighting pass.
1361 glBindBufferBase(GL_UNIFORM_BUFFER, 7, m_ssaoSettingsUBO);
1362 glBindBufferBase(GL_UNIFORM_BUFFER, 8, m_ssaoKernelUBO);
1363 glBindBufferBase(GL_UNIFORM_BUFFER, 9, m_ssaoCameraUBO);
1364
1365 // ---- Pass 1: SSAO ----
1366 glBindFramebuffer(GL_FRAMEBUFFER, m_ssaoFBO);
1367 glViewport(0, 0, m_gbufferWidth, m_gbufferHeight);
1368 glDisable(GL_DEPTH_TEST);
1369 glDepthMask(GL_FALSE);
1370 glDisable(GL_BLEND);
1371 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
1372 glClear(GL_COLOR_BUFFER_BIT);
1373
1374 m_ssaoShader->bind();
1375 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_gbufferDepth);
1376 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, m_gbufferNormalRough);
1377 glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, m_ssaoNoiseTex);
1378 glActiveTexture(GL_TEXTURE0);
1379
1380 glBindVertexArray(m_lightingVAO);
1381 glDrawArrays(GL_TRIANGLES, 0, 3);
1382
1383 // ---- Pass 2: Blur ----
1384 glBindFramebuffer(GL_FRAMEBUFFER, m_ssaoBlurFBO);
1385 glClear(GL_COLOR_BUFFER_BIT);
1386
1387 m_ssaoBlurShader->bind();
1388 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_ssaoTexture);
1389
1390 glDrawArrays(GL_TRIANGLES, 0, 3);
1391
1392 // Restore depth/blend state for the upcoming lighting pass
1393 glEnable(GL_DEPTH_TEST);
1394 glDepthMask(GL_TRUE);
1395
1396 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1397}
1398
1399} // namespace RenderEngine
1400} // namespace Sleak
int width
int height
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
static Application * GetInstance()
The one Application for this process, or null before construction.
static const Math::Matrix4 & GetMainProjectionMatrix()
Definition Camera.hpp:107
static const Math::Matrix4 & GetMainViewMatrix()
Definition Camera.hpp:103
void BindTexturesAndCB()
Binds textures and uploads the material CB for the deferred geometry pass, skipping the shader bind.
Definition Material.cpp:114
T * get() const
Definition RefPtr.hpp:170
Backend-agnostic GPU buffer: vertex, index, constant, or resource view target.
OpenGL VBO/EBO/UBO wrapper; target is derived from BufferType at construction.
OpenGLBuffer(uint32_t size, BufferType type)
static constexpr uint32_t PREFILTER_MIPS
Definition OpenGLIBL.hpp:25
virtual void SetDepthWrite(bool enabled) override
virtual Texture * CreateTexture(const std::string &TexturePath) override
Loads a texture from disk.
virtual void SetRenderFace(RenderFace face) override
virtual void DrawInstance(uint32_t instanceCount, uint32_t vertexPerInstance) override
Non-indexed instanced draw.
virtual void BindIndexBuffer(RefPtr< BufferBase > buffer, uint32_t slot=0) override
virtual void SetCullEnabled(bool enabled) override
virtual Shader * CreateShader(const std::string &shaderSource) override
Compiles a shader program from source or a source-file path.
virtual Texture * CreateTextureFromData(uint32_t width, uint32_t height, void *data) override
Creates a texture from an in-memory pixel buffer.
virtual void SetViewport(float x, float y, float width, float height, float minDepth=0.0f, float maxDepth=1.0f) override
Sets the active viewport rect and depth range in pixel/NDC units.
OpenGLRenderer(Window *window)
Registers this backend's buffer/shader/texture factories with ResourceManager.
virtual BufferBase * CreateBuffer(BufferType Type, uint32_t size, void *data) override
Allocates a backend buffer, optionally seeded with initial data.
virtual void BeginDebugLinePass() override
void BeginRender() override
Acquires the next frame and prepares it for command recording.
virtual void DrawIndexedInstance(uint32_t instanceCount, uint32_t indexPerInstance) override
Indexed instanced draw.
void EndRender() override
Submits recorded commands and presents the frame.
virtual void BindTexture(RefPtr< Sleak::Texture > texture, uint32_t slot=0) override
virtual void Resize(uint32_t width, uint32_t height) override
Recreates swapchain-dependent resources for a new surface size.
virtual void BindConstantBuffer(RefPtr< BufferBase > buffer, uint32_t slot=0) override
virtual void ClearDepthStencil(bool clearDepth, bool clearStencil, float depth, uint8_t stencil) override
virtual bool CreateImGUI() override
Initializes ImGui integration for this backend.
virtual void DrawIndexed(uint32_t indexCount) override
Indexed draw from the currently bound vertex/index buffers.
virtual void ClearRenderTarget(float r, float g, float b, float a) override
virtual void SetDepthCompare(DepthCompare compare) override
bool Initialize() override
Creates the device, swapchain, and backend resources.
Texture * CreateCubemapTextureFromPanorama(const std::string &panoramaPath)
Loads a cubemap by converting a single equirectangular panorama.
virtual void SetRenderMode(RenderMode mode) override
virtual void BindVertexBuffer(RefPtr< BufferBase > buffer, uint32_t slot=0) override
virtual void BindBoneBuffer(RefPtr< BufferBase > buffer) override
virtual void EndDebugLinePass() override
virtual void Draw(uint32_t vertexCount) override
Non-indexed draw from the currently bound vertex buffer.
Texture * CreateCubemapTexture(const std::array< std::string, 6 > &facePaths)
Loads a cubemap from six face image paths.
static RenderCommandQueue * GetInstance()
Lazily creates and returns the process-wide singleton instance.
virtual void ConfigureRenderMode()=0
Applies the current RenderMode (fill/wireframe/points) to backend state.
virtual void ConfigureRenderFace()=0
Applies the current RenderFace (cull mode) to backend state.
void UpdateFrameMetrics()
Rolls up frame count into rate/time/triangle stats once per MetricUpdateInterval.
Definition Renderer.hpp:241
void SetPerformanceCounter(bool value)
Definition Renderer.hpp:127
static void RegisterCreateCubemapTextureFromPanorama(T *instance, Texture *(T::*method)(const std::string &))
static void RegisterCreateCubemapTexture(T *instance, Texture *(T::*method)(const std::array< std::string, 6 > &))
static void RegisterCreateBuffer(T *instance, BufferBase *(T::*method)(BufferType, uint32_t, void *))
static void RegisterCreateShader(T *instance, Shader *(T::*method)(const std::string &))
static void RegisterCreateTextureFromMemory(std::function< Texture *(const void *, uint32_t, uint32_t, TextureFormat, uint32_t)> func)
static void RegisterCreateTexture(T *instance, Texture *(T::*method)(const std::string &))
Backend-agnostic compiled shader program.
Definition Shader.hpp:11
virtual bool IsValid() const
True if the pointer is non-null.
static const VertexLayoutDesc * Get(VertexFormatHandle handle)
SDL_Window * GetSDLWindow()
Returns the underlying SDL window handle.
Definition Window.hpp:43
TextureFormat
Definition Texture.hpp:10
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Backend-facing rendering layer shared by the four graphics backends.
DepthCompare
Depth test comparison function.
RenderMode
Rasterizer fill style for a draw.
BufferType
GPU buffer usage kind, drives backend binding flags and layout.
RenderFace
Which triangle winding gets culled.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
static constexpr uint32_t MAX_KERNEL_SIZE
static constexpr uint32_t NOISE_SIZE
std::vector< VertexAttribute > attributes