Riyyi
4 months ago
12 changed files with 399 additions and 78 deletions
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // int32_t, uint8_t |
||||
#include <string_view> |
||||
|
||||
#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<uint8_t>(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
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <cstdint> // uint8_t, uint32_t |
||||
#include <string> |
||||
#include <string_view> |
||||
#include <unordered_map> |
||||
|
||||
#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<std::string, uint32_t> uniformLocations {}; |
||||
}; |
||||
|
||||
class Uniformbuffer final : public ruc::Singleton<Uniformbuffer> { // 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<std::string_view, UniformbufferBlock> 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)
|
Loading…
Reference in new issue