diff --git a/assets/glsl/post-process.frag b/assets/glsl/post-process.frag index 012b9a7..5a33bac 100644 --- a/assets/glsl/post-process.frag +++ b/assets/glsl/post-process.frag @@ -25,8 +25,8 @@ struct DirectionalLight { vec3 specular; }; -const int MAX_DIRECTIONAL_LIGHTS = 32; -layout(std140, binding = 1) uniform DirectionalLights { +const int MAX_DIRECTIONAL_LIGHTS = 4; +layout(std430, binding = 0) buffer DirectionalLights { DirectionalLight u_directionalLight[MAX_DIRECTIONAL_LIGHTS]; }; diff --git a/src/inferno/asset/shader.h b/src/inferno/asset/shader.h index c14f95f..a10ca8d 100644 --- a/src/inferno/asset/shader.h +++ b/src/inferno/asset/shader.h @@ -4,6 +4,8 @@ * SPDX-License-Identifier: MIT */ +#pragma once + #include // int32_t, uint32_t #include #include diff --git a/src/inferno/render/buffer.cpp b/src/inferno/render/buffer.cpp index 01796b2..7639776 100644 --- a/src/inferno/render/buffer.cpp +++ b/src/inferno/render/buffer.cpp @@ -42,7 +42,7 @@ uint32_t BufferElement::getTypeGL() const return BufferElement::getTypeGL(m_type); } -uint32_t BufferElement::getTypeSize(const BufferElementType type) +uint32_t BufferElement::getTypeSize(BufferElementType type) { switch (type) { case BufferElementType::None: @@ -86,7 +86,7 @@ uint32_t BufferElement::getTypeSize(const BufferElementType type) return 0; } -uint32_t BufferElement::getTypeCount(const BufferElementType type) +uint32_t BufferElement::getTypeCount(BufferElementType type) { switch (type) { case BufferElementType::None: @@ -133,7 +133,7 @@ uint32_t BufferElement::getTypeCount(const BufferElementType type) return 0; } -uint32_t BufferElement::getTypeGL(const BufferElementType type) +uint32_t BufferElement::getTypeGL(BufferElementType type) { switch (type) { case BufferElementType::None: @@ -177,6 +177,58 @@ uint32_t BufferElement::getTypeGL(const BufferElementType type) return 0; } +uint32_t BufferElement::getGLTypeSize(uint32_t type) +{ + switch (type) { + case GL_BOOL: + case GL_INT: + case GL_UNSIGNED_INT: + case GL_FLOAT: + return 4; + case GL_BOOL_VEC2: + case GL_INT_VEC2: + case GL_UNSIGNED_INT_VEC2: + case GL_FLOAT_VEC2: + return 4 * 2; + case GL_BOOL_VEC3: + case GL_INT_VEC3: + case GL_UNSIGNED_INT_VEC3: + case GL_FLOAT_VEC3: + return 4 * 3; + case GL_BOOL_VEC4: + case GL_INT_VEC4: + case GL_UNSIGNED_INT_VEC4: + case GL_FLOAT_VEC4: + return 4 * 4; + case GL_FLOAT_MAT2: + return 4 * 2 * 2; + case GL_FLOAT_MAT3: + return 4 * 3 * 3; + case GL_FLOAT_MAT4: + return 4 * 4 * 4; + + case GL_DOUBLE: + return 8; + case GL_DOUBLE_VEC2: + return 8 * 2; + case GL_DOUBLE_VEC3: + return 8 * 3; + case GL_DOUBLE_VEC4: + return 8 * 4; + case GL_DOUBLE_MAT2: + return 8 * 2 * 2; + case GL_DOUBLE_MAT3: + return 8 * 3 * 3; + case GL_DOUBLE_MAT4: + return 8 * 4 * 4; + + default: + VERIFY_NOT_REACHED(); + }; + + return 0; +} + // ----------------------------------------- BufferLayout::BufferLayout(const std::initializer_list& elements) diff --git a/src/inferno/render/buffer.h b/src/inferno/render/buffer.h index aa52f71..93441e8 100644 --- a/src/inferno/render/buffer.h +++ b/src/inferno/render/buffer.h @@ -16,7 +16,7 @@ namespace Inferno { // clang-format off // https://www.khronos.org/opengl/wiki/Data_Type_(GLSL) -enum class BufferElementType { +enum class BufferElementType : uint8_t { None = 0, Bool, Bool2, Bool3, Bool4, // bvec Int, Int2, Int3, Int4, // ivec @@ -39,9 +39,10 @@ public: uint32_t getTypeSize() const; uint32_t getTypeCount() const; uint32_t getTypeGL() const; - static uint32_t getTypeSize(const BufferElementType type); - static uint32_t getTypeCount(const BufferElementType type); - static uint32_t getTypeGL(const BufferElementType type); + static uint32_t getTypeSize(BufferElementType type); + static uint32_t getTypeCount(BufferElementType type); + static uint32_t getTypeGL(BufferElementType type); + static uint32_t getGLTypeSize(uint32_t type); BufferElementType type() const { return m_type; } std::string name() const { return m_name; } @@ -49,11 +50,11 @@ public: uint32_t offset() const { return m_offset; } bool normalized() const { return m_normalized; } - void setType(const BufferElementType& type) { m_type = type; } + void setType(BufferElementType type) { m_type = type; } void setName(const std::string& name) { m_name = name; } - void setSize(const uint32_t& size) { m_size = size; } - void setOffset(const uint32_t& offset) { m_offset = offset; } - void setNormalized(const bool& normalized) { m_normalized = normalized; } + void setSize(uint32_t size) { m_size = size; } + void setOffset(uint32_t offset) { m_offset = offset; } + void setNormalized(bool normalized) { m_normalized = normalized; } private: BufferElementType m_type; diff --git a/src/inferno/render/renderer.cpp b/src/inferno/render/renderer.cpp index d59c8b1..a02459f 100644 --- a/src/inferno/render/renderer.cpp +++ b/src/inferno/render/renderer.cpp @@ -34,9 +34,6 @@ void Renderer::endScene() // ----------------------------------------- -template -uint32_t Renderer::m_maxSupportedTextureSlots = 0; - template void Renderer::initialize() { diff --git a/src/inferno/render/renderer.h b/src/inferno/render/renderer.h index f0e4936..b65ff15 100644 --- a/src/inferno/render/renderer.h +++ b/src/inferno/render/renderer.h @@ -16,9 +16,10 @@ #include "glm/ext/vector_float4.hpp" // glm::vec4 #include "ruc/singleton.h" +#include "inferno/asset/shader.h" + namespace Inferno { -class Shader; class Texture; class TransformComponent; class VertexArray; @@ -79,6 +80,8 @@ public: void setEnableDepthBuffer(bool state) { m_enableDepthBuffer = state; } + uint32_t shaderID() const { return m_shader->id(); } + protected: Renderer() {} virtual ~Renderer() { destroy(); }; @@ -106,7 +109,7 @@ protected: T* m_vertexBufferPtr { nullptr }; // Texture units - static uint32_t m_maxSupportedTextureSlots; + static inline uint32_t m_maxSupportedTextureSlots { 0 }; uint32_t m_textureSlotIndex { 1 }; std::array, maxTextureSlots> m_textureSlots; diff --git a/src/inferno/render/shader-storage-buffer.cpp b/src/inferno/render/shader-storage-buffer.cpp new file mode 100644 index 0000000..e724322 --- /dev/null +++ b/src/inferno/render/shader-storage-buffer.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // int32_t, uint8_t, uintt32_t + +#include "glad/glad.h" +#include "glm/ext/matrix_float2x2.hpp" // glm::mat2 +#include "glm/ext/matrix_float3x3.hpp" // glm::mat3 +#include "glm/ext/vector_float4.hpp" // glm::vec4 + +#include "inferno/render/buffer.h" +#include "inferno/render/shader-storage-buffer.h" + +namespace Inferno { + +ShaderStorageBuffer::ShaderStorageBuffer(s) +{ + // Get maximum uniformbuffer bindings the GPU supports + int32_t maxBindingPoints = 0; + glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxBindingPoints); + m_maxBindingPoints = static_cast(maxBindingPoints); +} + +ShaderStorageBuffer::~ShaderStorageBuffer() +{ +} + +// ----------------------------------------- + +// https://stackoverflow.com/questions/56512216#answer-56513136 +void ShaderStorageBuffer::setLayout(std::string_view blockName, uint8_t bindingPoint, uint32_t shaderID) +{ + VERIFY(bindingPoint < m_maxBindingPoints, + "shader storage buffer exceeded binding points: {}/{}", bindingPoint, m_maxBindingPoints); + + if (!exists(blockName)) { + m_blocks[blockName.data()] = {}; + } + + BufferBlock& block = m_blocks[blockName.data()]; + block.bindingPoint = bindingPoint; + block.memberOffsets.clear(); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, block.id); + + // Get the shader block index + uint32_t resourceIndex = glGetProgramResourceIndex(shaderID, GL_SHADER_STORAGE_BLOCK, blockName.data()); + VERIFY(resourceIndex != GL_INVALID_INDEX, "block doesnt exist in shader: {}::{}", blockName, shaderID); + + // Get the amount of member variables + uint32_t prop = GL_NUM_ACTIVE_VARIABLES; + int32_t memberCount; + glGetProgramResourceiv(shaderID, GL_SHADER_STORAGE_BLOCK, resourceIndex, 1, &prop, 1, nullptr, &memberCount); + + // Get the indices of the members + prop = GL_ACTIVE_VARIABLES; + std::vector members(memberCount); + glGetProgramResourceiv(shaderID, GL_SHADER_STORAGE_BLOCK, resourceIndex, 1, &prop, (int32_t)members.size(), nullptr, members.data()); + + // Reserve memory for the names of the members + int32_t memberNameSize; + glGetProgramInterfaceiv(shaderID, GL_BUFFER_VARIABLE, GL_MAX_NAME_LENGTH, &memberNameSize); + std::vector memberNameBuffer(memberNameSize); + + int32_t lastOffset = 0; + int32_t lastType = 0; + for (int32_t i = 0; i < memberCount; i++) { + + // Get name of buffer variable + int32_t stringLength; + glGetProgramResourceName(shaderID, GL_BUFFER_VARIABLE, members[i], memberNameSize, &stringLength, memberNameBuffer.data()); + std::string memberName = std::string(memberNameBuffer.begin(), memberNameBuffer.begin() + stringLength); + + // Get the other data needed for computing + + uint32_t props[7] = { + GL_OFFSET, // 0 + GL_TYPE, // 1 + GL_ARRAY_SIZE, // 2 + GL_ARRAY_STRIDE, // 3 + GL_MATRIX_STRIDE, // 4 + GL_TOP_LEVEL_ARRAY_SIZE, // 5 + GL_TOP_LEVEL_ARRAY_STRIDE, // 6 + }; + int32_t params[7]; + glGetProgramResourceiv(shaderID, GL_BUFFER_VARIABLE, members[i], 7, props, 7, nullptr, params); + + // ruc::error("{}", memberName); + // ruc::error("\n Offset: {}, type: {:#x}, arraySize: {}, arrayStride: {},\n matrixStride: {}, top level size: {}, top level stride: {}\n", + // params[0], params[1], params[2], params[3], params[4], params[5], params[6]); + + // Array of structs + if (params[5] != 1 && params[6 != 0]) { + size_t bracketOpen = memberName.find_first_of('['); + size_t bracketClose = memberName.find_first_of(']'); + std::string memberNameBegin = memberName.substr(0, bracketOpen); // name: myArray[0].member -> myArray + std::string memberNameMember = memberName.substr(bracketClose + 1); // name: myArray[0].member -> .member + for (int32_t j = 0; j < params[5]; ++j) { + lastOffset = params[0] + (j * params[6]); // calc offset + lastType = params[1]; + + // Only add the member variant the first time its encountered + if (j == 0 && block.memberOffsets.find(memberNameBegin) == block.memberOffsets.end()) { + block.memberOffsets.emplace(memberNameBegin, lastOffset); // name: myArray + } + + // Only add the index variant the first time its encountered + std::string memberNameIndex = memberNameBegin + "[" + std::to_string(j) + "]"; // name: myArray -> myArray[i] + if (block.memberOffsets.find(memberNameIndex) == block.memberOffsets.end()) { + block.memberOffsets.emplace(memberNameIndex, lastOffset); + } + + block.memberOffsets.emplace(memberNameIndex + memberNameMember, // name: myArray -> myArray[i].member + lastOffset); + } + } + // Array of primitives + else if (params[2] != 1 && params[3] != 0) { + std::string memberNameBegin = memberName.substr(0, memberName.find_first_of('[')); // name: myArray[0] -> myArray + for (int32_t j = 0; j < params[2]; ++j) { + lastOffset = params[0] + (j * params[3]); // calc offset + lastType = params[1]; + + // Only add the member variant the first time its encountered + if (j == 0) { + block.memberOffsets.emplace(memberNameBegin, lastOffset); // name: myArray + } + + block.memberOffsets.emplace(memberNameBegin + "[" + std::to_string(j) + "]", // name: myArray -> myArray[i] + lastOffset); + } + } + // Matrix case + else if (params[4] != 0) { + lastType = params[1]; + lastOffset = params[0]; + block.memberOffsets.emplace(memberName, params[0]); + } + else { + lastType = params[1]; + lastOffset = params[0]; + block.memberOffsets.emplace(memberName, params[0]); + } + } + + block.size = lastOffset + BufferElement::getGLTypeSize(lastType); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + // Results + // for (auto [k, v] : block.memberOffsets) { + // ruc::error("{}:{}", k, v); + // } +} + +void ShaderStorageBuffer::create(std::string_view blockName) +{ + VERIFY(exists(blockName), "shader storage buffer block doesnt exist"); + + BufferBlock& block = m_blocks[blockName.data()]; + + if (block.id != 0) { + glDeleteBuffers(1, &block.id); + } + + // Allocate buffer + block.id = UINT_MAX; + glGenBuffers(1, &block.id); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, block.id); + glBufferData(GL_SHADER_STORAGE_BUFFER, block.size, NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + // Bind buffer to binding point + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, block.bindingPoint, block.id); +} + +void ShaderStorageBuffer::setValue(std::string_view blockName, std::string_view member, bool value) +{ + setValue(blockName, member, static_cast(value), sizeof(uint32_t)); +} + +void ShaderStorageBuffer::setValue(std::string_view blockName, std::string_view member, glm::mat2 value) +{ + setValue(blockName, member, static_cast(value), sizeof(glm::vec4) * 2); +} + +void ShaderStorageBuffer::setValue(std::string_view blockName, std::string_view member, glm::mat3 value) +{ + setValue(blockName, member, static_cast(value), sizeof(glm::vec4) * 3); +} + +} // namespace Inferno diff --git a/src/inferno/render/shader-storage-buffer.h b/src/inferno/render/shader-storage-buffer.h new file mode 100644 index 0000000..f2cad8b --- /dev/null +++ b/src/inferno/render/shader-storage-buffer.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include // size_t +#include // uint8_t, uint32_t +#include +#include + +#include "glad/glad.h" +#include "glm/ext/matrix_float2x2.hpp" // glm::mat2 +#include "glm/ext/matrix_float3x3.hpp" // glm::mat3 +#include "ruc/singleton.h" + +#define CHECK_SET_CALL(blockName, member) \ + VERIFY(exists(blockName), "shader storage buffer block doesnt exist: {}", blockName); \ + const BufferBlock& block = m_blocks[blockName.data()]; \ + VERIFY(block.memberOffsets.find(member.data()) != block.memberOffsets.end(), \ + "shader storage buffer member doesnt exist: {}", member); + +namespace Inferno { + +struct BufferBlock { + uint32_t id { 0 }; + uint32_t size { 0 }; + uint8_t bindingPoint { 0 }; + std::unordered_map memberOffsets {}; +}; + +class ShaderStorageBuffer final : public ruc::Singleton { // Shader Storage Buffer Object, SSBO +public: + ShaderStorageBuffer(s); + virtual ~ShaderStorageBuffer(); + + void setLayout(std::string_view blockName, uint8_t bindingPoint, uint32_t shaderID); + void create(std::string_view blockName); + + bool exists(std::string_view blockName) const { return m_blocks.find(blockName.data()) != m_blocks.end(); } + + template // Capture value by reference, instead of decaying to pointer + void setValue(std::string_view blockName, std::string_view member, T&& value, size_t size = 0) + { + CHECK_SET_CALL(blockName, member); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, block.id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, block.memberOffsets.at(member.data()), (size) ? size : sizeof(T), &value); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } + // Exceptions: + void setValue(std::string_view blockName, std::string_view member, bool value); + void setValue(std::string_view blockName, std::string_view member, glm::mat2 value); + void setValue(std::string_view blockName, std::string_view member, glm::mat3 value); + +private: + uint8_t m_maxBindingPoints { 0 }; + std::unordered_map m_blocks; +}; + +} // namespace Inferno diff --git a/src/inferno/render/shader-structs.h b/src/inferno/render/shader-structs.h new file mode 100644 index 0000000..785f727 --- /dev/null +++ b/src/inferno/render/shader-structs.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "glm/ext/vector_float3.hpp" // glm::vec3 + +namespace Inferno { + +// Shader storage block layouts, using std430 memory layout rules + +#define MAX_DIRECTIONAL_LIGHTS 4 +struct alignas(16) DirectionalLightBlock { + alignas(16) glm::vec3 direction { 0 }; + + alignas(16) glm::vec3 ambient { 0 }; + alignas(16) glm::vec3 diffuse { 0 }; + alignas(16) glm::vec3 specular { 0 }; +}; + +} // namespace Inferno diff --git a/src/inferno/render/uniformbuffer.cpp b/src/inferno/render/uniformbuffer.cpp index 918c6e9..1d6688e 100644 --- a/src/inferno/render/uniformbuffer.cpp +++ b/src/inferno/render/uniformbuffer.cpp @@ -59,6 +59,7 @@ void Uniformbuffer::setLayout(std::string_view blockName, uint8_t bindingPoint, UniformbufferBlock& block = m_blocks[blockName]; block.bindingPoint = bindingPoint; + block.uniformLocations.clear(); // Example block layout: // - mat3 diff --git a/src/inferno/render/uniformbuffer.h b/src/inferno/render/uniformbuffer.h index 8eb3c9a..3a1548e 100644 --- a/src/inferno/render/uniformbuffer.h +++ b/src/inferno/render/uniformbuffer.h @@ -4,6 +4,8 @@ * SPDX-License-Identifier: MIT */ +#pragma once + #include // size_t #include // uint8_t, uint32_t #include @@ -13,36 +15,18 @@ #include "glad/glad.h" #include "glm/ext/matrix_float2x2.hpp" // glm::mat2 #include "glm/ext/matrix_float3x3.hpp" // glm::mat3 -#include "glm/ext/vector_float3.hpp" // glm::vec3 #include "ruc/singleton.h" #include "inferno/render/buffer.h" -#define CHECK_SET_CALL(blockName, member) \ - VERIFY(exists(blockName), "uniformbuffer block doesnt exist"); \ +#define CHECK_UNIFORM_SET_CALL(blockName, member) \ + VERIFY(exists(blockName), "uniformbuffer block doesnt exist: {}", blockName); \ const UniformbufferBlock& block = m_blocks[blockName]; \ VERIFY(block.uniformLocations.find(member.data()) != block.uniformLocations.end(), \ - "uniformbuffer block member doesnt exist"); + "uniformbuffer member doesnt exist: {}", member); namespace Inferno { -// Uniform block layouts, using std140 memory layout rules - -#define MAX_DIRECTIONAL_LIGHTS 32 -struct UniformDirectionalLight { - glm::vec3 direction { 0 }; - float __padding0 { 0 }; - - glm::vec3 ambient { 0 }; - float __padding1 { 0 }; - glm::vec3 diffuse { 0 }; - float __padding2 { 0 }; - glm::vec3 specular { 0 }; - float __padding3 { 0 }; -}; - -// ----------------------------------------- - struct UniformbufferBlock { uint32_t id { 0 }; uint32_t size { 0 }; @@ -62,7 +46,7 @@ public: template // Capture value by reference, instead of decaying to pointer void setValue(std::string_view blockName, std::string_view member, T&& value, size_t size = 0) { - CHECK_SET_CALL(blockName, member); + CHECK_UNIFORM_SET_CALL(blockName, member); glBindBuffer(GL_UNIFORM_BUFFER, block.id); glBufferSubData(GL_UNIFORM_BUFFER, block.uniformLocations.at(member.data()), (size) ? size : sizeof(T), &value); diff --git a/src/inferno/system/rendersystem.cpp b/src/inferno/system/rendersystem.cpp index e7c45b7..f07f50f 100644 --- a/src/inferno/system/rendersystem.cpp +++ b/src/inferno/system/rendersystem.cpp @@ -16,6 +16,8 @@ #include "inferno/render/framebuffer.h" #include "inferno/render/render-command.h" #include "inferno/render/renderer.h" +#include "inferno/render/shader-storage-buffer.h" +#include "inferno/render/shader-structs.h" #include "inferno/render/uniformbuffer.h" #include "inferno/system/camerasystem.h" #include "inferno/system/rendersystem.h" @@ -57,16 +59,8 @@ void RenderSystem::initialize(uint32_t width, uint32_t height) }); Uniformbuffer::the().create("Camera"); - Uniformbuffer::the().setLayout( - "DirectionalLights", - { - .size = sizeof(UniformDirectionalLight) * MAX_DIRECTIONAL_LIGHTS, - .bindingPoint = 1, - .uniformLocations = { - { "u_directionalLight", 0 }, - }, - }); - Uniformbuffer::the().create("DirectionalLights"); + ShaderStorageBuffer::the().setLayout("DirectionalLights", 0, RendererPostProcess::the().shaderID()); + ShaderStorageBuffer::the().create("DirectionalLights"); ruc::info("RenderSystem initialized"); } @@ -137,15 +131,21 @@ void RenderSystem::renderGeometry() Uniformbuffer::the().setValue("Camera", "u_projectionView", projection * view); Uniformbuffer::the().setValue("Camera", "u_position", translate); - static UniformDirectionalLight directionalLights[1] = { + static DirectionalLightBlock directionalLights[2] = { { .direction = { -8.0f, -8.0f, -8.0f }, .ambient = { 0.1f, 0.1f, 0.1f }, .diffuse = { 1.0f, 1.0f, 1.0f }, .specular = { 1.0f, 1.0f, 1.0f }, }, + { + .direction = { 8.0f, 8.0f, 8.0f }, + .ambient = { 0.1f, 0.1f, 0.1f }, + .diffuse = { 1.0f, 1.0f, 1.0f }, + .specular = { 1.0f, 0.0f, 0.0f }, + }, }; - Uniformbuffer::the().setValue("DirectionalLights", "u_directionalLight", directionalLights); + ShaderStorageBuffer::the().setValue("DirectionalLights", "u_directionalLight", directionalLights); auto modelView = m_registry->view();