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