diff --git a/assets/glsl/batch-3d.vert b/assets/glsl/batch-3d.vert index ef14251..1af2246 100644 --- a/assets/glsl/batch-3d.vert +++ b/assets/glsl/batch-3d.vert @@ -9,7 +9,10 @@ out vec3 v_normal; out vec2 v_textureCoordinates; out flat uint v_textureIndex; -uniform mat4 u_projectionView; +layout(std140, binding = 0) uniform Camera +{ + mat4 u_projectionView; +}; void main() { diff --git a/assets/glsl/batch-quad.vert b/assets/glsl/batch-quad.vert index 0ce7896..840a032 100644 --- a/assets/glsl/batch-quad.vert +++ b/assets/glsl/batch-quad.vert @@ -9,7 +9,10 @@ out vec4 v_color; out vec2 v_textureCoordinates; out flat uint v_textureIndex; -uniform mat4 u_projectionView; +layout(std140, binding = 0) uniform Camera +{ + mat4 u_projectionView; +}; void main() { diff --git a/src/inferno/application.cpp b/src/inferno/application.cpp index 27040ef..d569b57 100644 --- a/src/inferno/application.cpp +++ b/src/inferno/application.cpp @@ -23,6 +23,7 @@ #include "inferno/render/buffer.h" #include "inferno/render/context.h" #include "inferno/render/framebuffer.h" +#include "inferno/render/uniformbuffer.h" // #include "inferno/render/gltf.h" #include "inferno/asset/shader.h" #include "inferno/asset/texture.h" @@ -67,6 +68,13 @@ Application::Application() .clearBit = GL_COLOR_BUFFER_BIT, }); + Uniformbuffer::the().setLayout( + "Camera", 0, + { + { BufferElementType::Mat4, "u_projectionView" }, + }); + Uniformbuffer::the().create("Camera"); + m_scene = std::make_shared(); m_scene->initialize(); @@ -90,6 +98,7 @@ Application::Application() Application::~Application() { m_scene->destroy(); + Uniformbuffer::destroy(); RendererFont::destroy(); Renderer2D::destroy(); @@ -189,20 +198,9 @@ int Application::run() render(); - std::pair projectionView = m_scene->cameraProjectionView(); - RendererCubemap::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment - Renderer3D::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment - Renderer2D::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment - RendererFont::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment - m_scene->render(); // RendererCharacter::the().drawCharacter(character, f->texture()); - RendererCubemap::the().endScene(); - Renderer3D::the().endScene(); - Renderer2D::the().endScene(); - RendererFont::the().endScene(); - m_framebuffer->unbind(); // --------------------------------- @@ -214,7 +212,7 @@ int Application::run() RenderCommand::clearBit(m_screenFramebuffer->clearBit()); Renderer2D::the().setEnableDepthBuffer(false); - Renderer2D::the().beginScene(matIdentity, matIdentity); + Uniformbuffer::the().setFloat("Camera", "u_projectionView", matIdentity); Renderer2D::the().drawQuad(transformIdentity, vectorOne, m_framebuffer->texture(0)); Renderer2D::the().endScene(); Renderer2D::the().setEnableDepthBuffer(true); diff --git a/src/inferno/asset/shader.cpp b/src/inferno/asset/shader.cpp index 65e7f72..fb41268 100644 --- a/src/inferno/asset/shader.cpp +++ b/src/inferno/asset/shader.cpp @@ -50,16 +50,18 @@ Shader::~Shader() } } -int32_t Shader::findUniformLocation(std::string_view name) +// ----------------------------------------- + +uint32_t Shader::findUniformLocation(std::string_view name) { // Cache uniform locations, prevent going to the GPU every call - if (m_uniformLocation.find(name) != m_uniformLocation.end()) { - return m_uniformLocation[name]; + if (m_uniformLocations.find(name) != m_uniformLocations.end()) { + return m_uniformLocations[name]; } int32_t location = glGetUniformLocation(m_id, name.data()); VERIFY(location != -1, "Shader could not find uniform '{}'", name); - m_uniformLocation[name] = location; + m_uniformLocations[name] = static_cast(location); return location; } @@ -128,6 +130,8 @@ void Shader::unbind() const glUseProgram(0); } +// ----------------------------------------- + uint32_t Shader::compileShader(int32_t type, const char* source) const { // Create new shader diff --git a/src/inferno/asset/shader.h b/src/inferno/asset/shader.h index d0aada6..c14f95f 100644 --- a/src/inferno/asset/shader.h +++ b/src/inferno/asset/shader.h @@ -21,7 +21,7 @@ public: // Factory function static std::shared_ptr create(std::string_view path); - int32_t findUniformLocation(std::string_view name); + uint32_t findUniformLocation(std::string_view name); void setInt(std::string_view name, int value); void setInt(std::string_view name, int* values, uint32_t count); @@ -53,7 +53,7 @@ private: private: uint32_t m_id { 0 }; - std::unordered_map m_uniformLocation; + std::unordered_map m_uniformLocations; }; // ----------------------------------------- diff --git a/src/inferno/render/buffer.cpp b/src/inferno/render/buffer.cpp index bb99abe..ec1b816 100644 --- a/src/inferno/render/buffer.cpp +++ b/src/inferno/render/buffer.cpp @@ -4,6 +4,12 @@ * SPDX-License-Identifier: MIT */ +#include // size_t +#include // int32_t, uint8_t, uint32_t +#include // std::shared_ptr +#include +#include // std::pair + #include "glad/glad.h" #include "ruc/meta/assert.h" @@ -61,10 +67,10 @@ uint32_t BufferElement::getTypeSize(const BufferElementType type) case BufferElementType::Vec3: case BufferElementType::Vec4: return sizeof(float) * getTypeCount(type); - case BufferElementType::VecDouble: - case BufferElementType::VecDouble2: - case BufferElementType::VecDouble3: - case BufferElementType::VecDouble4: + case BufferElementType::Double: + case BufferElementType::Vec2Double: + case BufferElementType::Vec3Double: + case BufferElementType::Vec4Double: return sizeof(double) * getTypeCount(type); case BufferElementType::Mat2: case BufferElementType::Mat3: @@ -89,25 +95,25 @@ uint32_t BufferElement::getTypeCount(const BufferElementType type) case BufferElementType::Int: case BufferElementType::Uint: case BufferElementType::Float: - case BufferElementType::VecDouble: + case BufferElementType::Double: return 1; case BufferElementType::Bool2: case BufferElementType::Int2: case BufferElementType::Uint2: case BufferElementType::Vec2: - case BufferElementType::VecDouble2: + case BufferElementType::Vec2Double: return 2; case BufferElementType::Bool3: case BufferElementType::Int3: case BufferElementType::Uint3: case BufferElementType::Vec3: - case BufferElementType::VecDouble3: + case BufferElementType::Vec3Double: return 3; case BufferElementType::Bool4: case BufferElementType::Int4: case BufferElementType::Uint4: case BufferElementType::Vec4: - case BufferElementType::VecDouble4: + case BufferElementType::Vec4Double: return 4; case BufferElementType::Mat2: return 2 * 2; @@ -152,10 +158,10 @@ uint32_t BufferElement::getTypeGL(const BufferElementType type) case BufferElementType::Vec3: case BufferElementType::Vec4: return GL_FLOAT; - case BufferElementType::VecDouble: - case BufferElementType::VecDouble2: - case BufferElementType::VecDouble3: - case BufferElementType::VecDouble4: + case BufferElementType::Double: + case BufferElementType::Vec2Double: + case BufferElementType::Vec3Double: + case BufferElementType::Vec4Double: return GL_DOUBLE; case BufferElementType::Mat2: case BufferElementType::Mat3: @@ -184,7 +190,7 @@ void BufferLayout::calculateOffsetsAndStride() m_stride = 0; for (auto& element : m_elements) { element.setOffset(m_stride); - m_stride += element.getSize(); + m_stride += element.size(); } } @@ -202,7 +208,7 @@ VertexBuffer::VertexBuffer(size_t size, float* vertices) bind(); // Upload data to the GPU - glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_DYNAMIC_DRAW); unbind(); } @@ -242,7 +248,7 @@ IndexBuffer::IndexBuffer(uint32_t* indices, size_t size) bind(); // Upload data to the GPU - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, indices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, indices, GL_DYNAMIC_DRAW); unbind(); } @@ -298,7 +304,7 @@ void VertexArray::unbind() const void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) { const auto& layout = vertexBuffer->getLayout(); - VERIFY(layout.getElements().size(), "VertexBuffer has no layout"); + VERIFY(layout.elements().size(), "VertexBuffer has no layout"); bind(); vertexBuffer->bind(); @@ -306,7 +312,7 @@ void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) uint32_t index = 0; for (const auto& element : layout) { glEnableVertexAttribArray(index); - switch (element.getType()) { + switch (element.type()) { case BufferElementType::None: break; case BufferElementType::Int: @@ -321,8 +327,8 @@ void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) index, element.getTypeCount(), element.getTypeGL(), - layout.getStride(), - reinterpret_cast(element.getOffset())); + layout.stride(), + reinterpret_cast(element.offset())); break; } case BufferElementType::Bool: @@ -340,15 +346,15 @@ void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) index, element.getTypeCount(), element.getTypeGL(), - element.getNormalized() ? GL_TRUE : GL_FALSE, - layout.getStride(), - reinterpret_cast(element.getOffset())); + element.normalized() ? GL_TRUE : GL_FALSE, + layout.stride(), + reinterpret_cast(element.offset())); break; } - case BufferElementType::VecDouble: - case BufferElementType::VecDouble2: - case BufferElementType::VecDouble3: - case BufferElementType::VecDouble4: + case BufferElementType::Double: + case BufferElementType::Vec2Double: + case BufferElementType::Vec3Double: + case BufferElementType::Vec4Double: case BufferElementType::MatDouble2: case BufferElementType::MatDouble3: case BufferElementType::MatDouble4: { @@ -356,8 +362,8 @@ void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) index, element.getTypeCount(), element.getTypeGL(), - layout.getStride(), - reinterpret_cast(element.getOffset())); + layout.stride(), + reinterpret_cast(element.offset())); break; } default: diff --git a/src/inferno/render/buffer.h b/src/inferno/render/buffer.h index 6cc01f3..720de26 100644 --- a/src/inferno/render/buffer.h +++ b/src/inferno/render/buffer.h @@ -7,10 +7,11 @@ #pragma once #include // size_t -#include // int32_t, uint32_t +#include // int32_t, uint8_t, uint32_t #include // std::shared_ptr -#include // std::string -#include // std::vector +#include +#include // std::pair +#include namespace Inferno { @@ -22,7 +23,7 @@ enum class BufferElementType { Int, Int2, Int3, Int4, // ivec Uint, Uint2, Uint3, Uint4, // uvec Float, Vec2, Vec3, Vec4, // vec - VecDouble, VecDouble2, VecDouble3, VecDouble4, // dvec + Double, Vec2Double, Vec3Double, Vec4Double, // dvec Mat2, Mat3, Mat4, // mat MatDouble2, MatDouble3, MatDouble4, // dmat }; @@ -43,11 +44,11 @@ public: static uint32_t getTypeCount(const BufferElementType type); static uint32_t getTypeGL(const BufferElementType type); - BufferElementType getType() const { return m_type; } - std::string getName() const { return m_name; } - uint32_t getSize() const { return m_size; } - uint32_t getOffset() const { return m_offset; } - bool getNormalized() const { return m_normalized; } + BufferElementType type() const { return m_type; } + std::string name() const { return m_name; } + uint32_t size() const { return m_size; } + uint32_t offset() const { return m_offset; } + bool normalized() const { return m_normalized; } void setType(const BufferElementType& type) { m_type = type; } void setName(const std::string& name) { m_name = name; } @@ -72,8 +73,8 @@ public: BufferLayout(const std::initializer_list& elements); ~BufferLayout() = default; - const std::vector& getElements() const { return m_elements; } - uint32_t getStride() const { return m_stride; } + const std::vector& elements() const { return m_elements; } + uint32_t stride() const { return m_stride; } // Iterators std::vector::iterator begin() { return m_elements.begin(); } @@ -105,7 +106,7 @@ public: const BufferLayout& getLayout() const { return m_layout; } - inline void setLayout(const BufferLayout& layout) { m_layout = layout; } + void setLayout(const BufferLayout& layout) { m_layout = layout; } private: uint32_t m_id { 0 }; diff --git a/src/inferno/render/renderer.cpp b/src/inferno/render/renderer.cpp index 3fecf26..0f78727 100644 --- a/src/inferno/render/renderer.cpp +++ b/src/inferno/render/renderer.cpp @@ -228,13 +228,6 @@ Renderer2D::Renderer2D(s) ruc::info("Renderer2D initialized"); } -void Renderer2D::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) -{ - m_shader->bind(); - m_shader->setFloat("u_projectionView", cameraProjection * cameraView); - m_shader->unbind(); -} - void Renderer2D::drawQuad(const TransformComponent& transform, glm::vec4 color) { drawQuad(transform, color, nullptr); @@ -536,13 +529,6 @@ void Renderer3D::drawModel(std::span vertices, std::spanbind(); - m_shader->setFloat("u_projectionView", cameraProjection * cameraView); - m_shader->unbind(); -} - void Renderer3D::createElementBuffer() { // --------------------------------- diff --git a/src/inferno/render/renderer.h b/src/inferno/render/renderer.h index e680115..022741f 100644 --- a/src/inferno/render/renderer.h +++ b/src/inferno/render/renderer.h @@ -132,8 +132,6 @@ public: using Singleton::destroy; - virtual void beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) override; - void drawQuad(const TransformComponent& transform, glm::vec4 color); void drawQuad(const TransformComponent& transform, glm::mat4 color); void drawQuad(const TransformComponent& transform, glm::vec4 color, std::shared_ptr texture); @@ -199,8 +197,6 @@ public: using Singleton::destroy; - virtual void beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) override; - void drawModel(std::span vertices, std::span indices, const TransformComponent& transform, std::shared_ptr texture); private: diff --git a/src/inferno/render/uniformbuffer.cpp b/src/inferno/render/uniformbuffer.cpp new file mode 100644 index 0000000..1f3b2d3 --- /dev/null +++ b/src/inferno/render/uniformbuffer.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // size_t +#include // int32_t, uint8_t +#include + +#include "glad/glad.h" +#include "glm/gtc/type_ptr.hpp" // glm::value_ptr +#include "ruc/meta/assert.h" + +#include "inferno/render/buffer.h" +#include "inferno/render/uniformbuffer.h" + +namespace Inferno { + +Uniformbuffer::Uniformbuffer(s) +{ + // Get maximum uniformbuffer bindings the GPU supports + int32_t maxBindingPoints = 0; + glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxBindingPoints); + m_maxBindingPoints = static_cast(maxBindingPoints); +} + +Uniformbuffer::~Uniformbuffer() +{ + for (const auto& [_, block] : m_blocks) { + glDeleteBuffers(1, &block.id); + } + m_blocks.clear(); +} + +// ----------------------------------------- + +void Uniformbuffer::setLayout(std::string_view blockName, uint8_t bindingPoint, const BufferLayout& layout) +{ + VERIFY(bindingPoint < m_maxBindingPoints, "{} < {}", bindingPoint, m_maxBindingPoints); + + if (!exists(blockName)) { + m_blocks[blockName] = {}; + } + + UniformbufferBlock& block = m_blocks[blockName]; + + // Example block layout: + // - mat3 + // - float + // - vec2 + // - vec2 + // - float + + // Chunks, 4 slots, 4 bytes per slot + // [x][x][x][ ] #1 + // [x][x][x][ ] #2 + // [x][x][x][ ] #3 + // [x][ ][x][x] #4 + // [x][x][x][ ] #5 + + size_t chunk = 0; + uint8_t offset = 0; + + for (auto it = layout.begin(); it != layout.end(); ++it) { + BufferElementType type = it->type(); + const std::string& name = it->name(); + + // Calculate offset + switch (type) { + // Scalar 1 + case BufferElementType::Bool: + case BufferElementType::Int: + case BufferElementType::Uint: + case BufferElementType::Float: { + // Offset + block.uniformLocations[name] = (chunk * 16) + (offset * 4); + // Jump + offset += 1; + break; + } + // Scalar 2 + case BufferElementType::Bool2: + case BufferElementType::Int2: + case BufferElementType::Uint2: + case BufferElementType::Vec2: { + // Add padding + if (offset == 1) { + offset++; + } + if (offset == 3) { + offset = 0; + chunk++; + } + // Offset + block.uniformLocations[name] = (chunk * 16) + (offset * 4); + // Jump + offset += 2; + break; + } + // Scalar 3 + case BufferElementType::Bool3: + case BufferElementType::Int3: + case BufferElementType::Uint3: + case BufferElementType::Vec3: { + // Add padding + if (offset != 0) { + offset = 0; + chunk++; + } + // Offset + block.uniformLocations[name] = (chunk * 16) + (offset * 4); + // Jump + offset += 3; + break; + } + // Scalar 4 + case BufferElementType::Bool4: + case BufferElementType::Int4: + case BufferElementType::Uint4: + case BufferElementType::Vec4: { + // Add padding + if (offset != 0) { + offset = 0; + chunk++; + } + // Offset + block.uniformLocations[name] = (chunk * 16) + (offset * 4); + // Jump + offset += 4; + break; + } + // Array types + case BufferElementType::Mat2: + case BufferElementType::Mat3: + case BufferElementType::Mat4: { + // Add padding + if (offset != 0) { + offset = 0; + chunk++; + } + // Offset + block.uniformLocations[name] = (chunk * 16) + (offset * 4); + + // Additional rows + if (type == BufferElementType::Mat2) { + chunk += 1; + } + else if (type == BufferElementType::Mat3) { + chunk += 2; + } + else { + chunk += 3; + } + + // Jump + offset += 4; + break; + } + // TODO: Implement these types + case BufferElementType::Double: + case BufferElementType::Vec2Double: + case BufferElementType::Vec3Double: + case BufferElementType::Vec4Double: + case BufferElementType::MatDouble2: + case BufferElementType::MatDouble3: + case BufferElementType::MatDouble4: + VERIFY_NOT_REACHED(); + case BufferElementType::None: + VERIFY_NOT_REACHED(); + }; + + // Overflow slots to next chunk + if (offset > 3) { + offset = 0; + chunk++; + } + } + + // Pad the end of the buffer + if (offset != 0) { + offset = 0; + chunk++; + } + + block.size = chunk * 16; +} + +void Uniformbuffer::create(std::string_view blockName) +{ + VERIFY(exists(blockName), "uniformbuffer block doesnt exist"); + + UniformbufferBlock& block = m_blocks[blockName]; + + if (block.id != 0) { + glDeleteBuffers(1, &block.id); + } + + // Allocate buffer + block.id = UINT_MAX; + glGenBuffers(1, &block.id); + glBindBuffer(GL_UNIFORM_BUFFER, block.id); + glBufferData(GL_UNIFORM_BUFFER, block.size, NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // Bind buffer to binding point + glBindBufferBase(GL_UNIFORM_BUFFER, block.bindingPoint, block.id); +} + +void Uniformbuffer::setFloat(std::string_view blockName, std::string_view member, glm::mat4 matrix) +{ + VERIFY(exists(blockName), "uniformbuffer block doesnt exist"); + + const UniformbufferBlock& block = m_blocks[blockName]; + + VERIFY(block.uniformLocations.find(member.data()) != block.uniformLocations.end(), + "uniformbuffer block member doesnt exist"); + + // Note: Uniformbuffers are bound to a binding point for the entire pipeline, + // it remains accessible without needing to rebind it every time + glBufferSubData(GL_UNIFORM_BUFFER, block.uniformLocations.at(member.data()), sizeof(glm::mat4), glm::value_ptr(matrix)); +} + +} // namespace Inferno diff --git a/src/inferno/render/uniformbuffer.h b/src/inferno/render/uniformbuffer.h new file mode 100644 index 0000000..503bd19 --- /dev/null +++ b/src/inferno/render/uniformbuffer.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // uint8_t, uint32_t +#include +#include +#include + +#include "glm/ext/matrix_float4x4.hpp" // glm::mat4 +#include "ruc/singleton.h" + +#include "inferno/render/buffer.h" + +namespace Inferno { + +struct UniformbufferBlock { + uint32_t id { 0 }; + uint32_t size { 0 }; + uint8_t bindingPoint { 0 }; + std::unordered_map uniformLocations {}; +}; + +class Uniformbuffer final : public ruc::Singleton { // Uniform Buffer Object, UBO +public: + Uniformbuffer(s); + ~Uniformbuffer(); + + void setLayout(std::string_view blockName, uint8_t bindingPoint, const BufferLayout& layout); + void create(std::string_view blockName); + + void setFloat(std::string_view blockName, std::string_view member, glm::mat4 matrix); + + bool exists(std::string_view blockName) const { return m_blocks.find(blockName) != m_blocks.end(); } + +private: + uint8_t m_maxBindingPoints { 0 }; + std::unordered_map m_blocks; +}; + +} // namespace Inferno + +#if 0 + +// ----------------------------------------- +// Example usage: + +Uniformbuffer::the().setLayout( + "ExampleBlock", 0, + { + { BufferElementType::Mat3, "a" }, + { BufferElementType::Float, "b" }, + { BufferElementType::Vec2, "c" }, + { BufferElementType::Vec2, "d" }, + { BufferElementType::Float, "e" }, + }); +Uniformbuffer::the().create("ExampleBlock"); + +#endif + +// ----------------------------------------- +// Memory alignment of uniform blocks using std140 +// +// Main points: +// - Memory is organized into chunks. +// - A block is at least the size of 1 chunk. +// - One chunk has 4 slots, 4 bytes per slot. +// - Can't fit? Move to next chunk. +// +// The rules: +// 1. Scalar (bool, int, uint, float) takes up 1 slot, can appear after anything +// 2. Vec2 takes up 2 slots, in first or last half of a chunk +// 3. Vec3 takes up 3 slots, only at the start of a chunk +// 4. Everything else: +// - Take up maxumum room +// - As often as needed +// - Add padding as needed +// 5. Mat3 (or any matrix) are treated like arrays +// 6. Each member of *any* array gets its own chunk + +// TODO: How do double types work? + +// ----------------------------------------- +// References: +// - https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL +// - https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout +// - https://www.oreilly.com/library/view/opengl-programming-guide/9780132748445/app09lev1sec2.html (The std140 Layout Rules) +// - https://www.youtube.com/watch?v=JPvbRko9lBg (WebGL 2: Uniform Buffer Objects) diff --git a/src/inferno/scene/scene.cpp b/src/inferno/scene/scene.cpp index 2fe096a..2a637cf 100644 --- a/src/inferno/scene/scene.cpp +++ b/src/inferno/scene/scene.cpp @@ -25,6 +25,7 @@ #include "inferno/component/tagcomponent.h" #include "inferno/component/textareacomponent.h" #include "inferno/component/transformcomponent.h" +#include "inferno/render/uniformbuffer.h" #include "inferno/scene/scene.h" #include "inferno/script/nativescript.h" #include "inferno/system/camerasystem.h" @@ -83,8 +84,17 @@ void Scene::update(float deltaTime) void Scene::render() { + auto [projection, view] = CameraSystem::the().projectionView(); + Uniformbuffer::the().setFloat("Camera", "u_projectionView", projection * view); + RendererCubemap::the().beginScene(projection, view); // camera, lights, environment + RenderSystem::the().render(); TextAreaSystem::the().render(); + + RendererCubemap::the().endScene(); + Renderer3D::the().endScene(); + Renderer2D::the().endScene(); + RendererFont::the().endScene(); } void Scene::destroy()