Browse Source

Render: Add and implement shader storage buffer (SSBO)

master
Riyyi 2 months ago
parent
commit
b9043dbb4b
  1. 4
      assets/glsl/post-process.frag
  2. 2
      src/inferno/asset/shader.h
  3. 58
      src/inferno/render/buffer.cpp
  4. 17
      src/inferno/render/buffer.h
  5. 3
      src/inferno/render/renderer.cpp
  6. 7
      src/inferno/render/renderer.h
  7. 195
      src/inferno/render/shader-storage-buffer.cpp
  8. 63
      src/inferno/render/shader-storage-buffer.h
  9. 24
      src/inferno/render/shader-structs.h
  10. 1
      src/inferno/render/uniformbuffer.cpp
  11. 28
      src/inferno/render/uniformbuffer.h
  12. 24
      src/inferno/system/rendersystem.cpp

4
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];
};

2
src/inferno/asset/shader.h

@ -4,6 +4,8 @@
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint> // int32_t, uint32_t
#include <string_view>
#include <unordered_map>

58
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<BufferElement>& elements)

17
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;

3
src/inferno/render/renderer.cpp

@ -34,9 +34,6 @@ void Renderer<T>::endScene()
// -----------------------------------------
template<typename T>
uint32_t Renderer<T>::m_maxSupportedTextureSlots = 0;
template<typename T>
void Renderer<T>::initialize()
{

7
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<std::shared_ptr<Texture>, maxTextureSlots> m_textureSlots;

195
src/inferno/render/shader-storage-buffer.cpp

@ -0,0 +1,195 @@
/*
* Copyright (C) 2024 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint> // 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<uint8_t>(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<int32_t> 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<char> 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<uint32_t>(value), sizeof(uint32_t));
}
void ShaderStorageBuffer::setValue(std::string_view blockName, std::string_view member, glm::mat2 value)
{
setValue(blockName, member, static_cast<glm::mat4>(value), sizeof(glm::vec4) * 2);
}
void ShaderStorageBuffer::setValue(std::string_view blockName, std::string_view member, glm::mat3 value)
{
setValue(blockName, member, static_cast<glm::mat4>(value), sizeof(glm::vec4) * 3);
}
} // namespace Inferno

63
src/inferno/render/shader-storage-buffer.h

@ -0,0 +1,63 @@
/*
* Copyright (C) 2024 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t
#include <string_view>
#include <unordered_map>
#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<std::string, uint32_t> memberOffsets {};
};
class ShaderStorageBuffer final : public ruc::Singleton<ShaderStorageBuffer> { // 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<typename T> // 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<std::string, BufferBlock> m_blocks;
};
} // namespace Inferno

24
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

1
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

28
src/inferno/render/uniformbuffer.h

@ -4,6 +4,8 @@
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t
#include <string>
@ -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<typename T> // 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);

24
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<TransformComponent, ModelComponent>();

Loading…
Cancel
Save