From c28f8e610984a192d3dcc9a4ef076cdd4a4143cf Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 12 Jun 2025 01:27:27 -0400 Subject: [PATCH] partial move --- CMakeLists.txt | 2 +- include/blt/gfx/vao.h | 198 ++++++++++++++++++++++++++++++++++++++ include/blt/gfx/vbo.h | 214 ++++++++++++++++++++++++++++++++++++++++++ libraries/BLT | 2 +- src/blt/gfx/vao.cpp | 100 ++++++++++++++++++++ src/blt/gfx/vbo.cpp | 103 ++++++++++++++++++++ 6 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 include/blt/gfx/vao.h create mode 100644 include/blt/gfx/vbo.h create mode 100644 src/blt/gfx/vao.cpp create mode 100644 src/blt/gfx/vbo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5876df0..1fe21e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) include(FetchContent) -set(BLT_GRAPHICS_VERSION 2.0.10) +set(BLT_GRAPHICS_VERSION 2.0.11) set(BLT_GRAPHICS_TEST_VERSION 0.0.1) project(BLT_WITH_GRAPHICS VERSION ${BLT_GRAPHICS_VERSION}) diff --git a/include/blt/gfx/vao.h b/include/blt/gfx/vao.h new file mode 100644 index 0000000..64ebb0e --- /dev/null +++ b/include/blt/gfx/vao.h @@ -0,0 +1,198 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_GFX_VAO_H +#define BLT_GFX_VAO_H + +#include +#include +#include +#include + +namespace blt::gfx +{ + class unique_vao_t; + + namespace detail + { + struct vao_vbo_storage_t + { + std::unique_ptr vbo; + std::optional> attribute_numbers; + + [[nodiscard]] bool is_element() const + { + return vbo->get_buffer_type() == GL_ELEMENT_ARRAY_BUFFER; + } + + explicit vao_vbo_storage_t(unique_vbo_t&& vbo): vbo(std::make_unique(std::move(vbo))) + {} + }; + + class vao_vbo_context_t + { + friend class vao_context_t; + public: + vao_vbo_context_t(const vao_vbo_context_t& copy) = delete; + vao_vbo_context_t(vao_vbo_context_t&& move) = delete; + vao_vbo_context_t& operator=(const vao_vbo_context_t& copy) = delete; + vao_vbo_context_t& operator=(vao_vbo_context_t&& move) = delete; + /** + * This function takes ownership of the underlying VBO (GPU side). It will be freed when the basic vertex array is deleted + * @param attribute_number attribute number to bind to + * @param coordinate_size size of the data (number of elements, not the number of bytes) + * @param type GL_TYPE type of data + * @param stride how many bytes this data takes (for the entire per-vertex data structure) 0 will assume packed data + * This is in effect how many bytes until the next block of data + * @param offset offset into the data structure to where the data is stored + */ + vao_vbo_context_t& attribute_ptr(int attribute_number, int coordinate_size, GLenum type, int stride, long offset); + + vao_vbo_context_t& silence() + { + attributed = true; + return *this; + } + + /** + * Useless function, but if it makes you feel better, feel free to use it. + */ + vao_vbo_context_t& as_element() + { + return *this; + } + + ~vao_vbo_context_t(); + private: + vao_vbo_context_t(unique_vao_t& vao, vao_vbo_storage_t& vbo): vbo(vbo), vao(vao) + {} + + vao_vbo_storage_t& vbo; + unique_vao_t& vao; + bool attributed = false; + }; + + class vao_context_t + { + friend vao_vbo_context_t; + friend unique_vao_t; + public: + vao_context_t(const vao_context_t& copy) = delete; + vao_context_t(vao_context_t&& move) = delete; + vao_context_t& operator=(const vao_context_t& copy) = delete; + vao_context_t& operator=(vao_context_t&& move) = delete; + + vao_context_t& bind(); + + vao_context_t& unbind(); + + vao_vbo_context_t attach_vbo(unique_vbo_t&& vbo) const; + + ~vao_context_t(); + private: + [[nodiscard]] bool is_bound() const; + + explicit vao_context_t(unique_vao_t& vao): vao(vao) + { + bind(); + } + + unique_vao_t& vao; + }; + } + + class unique_vao_t + { + friend detail::vao_vbo_context_t; + friend detail::vao_context_t; + + public: + unique_vao_t(): vaoID(0) + { + glGenVertexArrays(1, &*vaoID); + } + + unique_vao_t(const unique_vao_t&) = delete; + + unique_vao_t& operator=(const unique_vao_t&) = delete; + + unique_vao_t(unique_vao_t&& other) noexcept: vaoID(std::exchange(other.vaoID, std::nullopt)) + {} + + unique_vao_t& operator=(unique_vao_t&& other) noexcept + { + vaoID = std::exchange(other.vaoID, vaoID); + return *this; + } + + /** + * This function is used for configuring the internals of VAO it will automatically unbind the VAO at the end of scope + */ + detail::vao_context_t configure(); + + /** + * This function is used to bind the VAO for usage during rendering. + */ + void bind() const; + + [[nodiscard]] std::optional> get_attribute(const u32 attribute) const + { + for (const auto& vbo_obj : vbo_list) + { + if (const auto attrs = vbo_obj.attribute_numbers) + { + if (attrs->contains(attribute)) + return *vbo_obj.vbo; + } + } + return {}; + } + + [[nodiscard]] std::optional> get_buffer_type(const GLuint buffer_type) const + { + for (const auto& vbo_obj : vbo_list) + { + if (vbo_obj.vbo->get_buffer_type() == buffer_type) + return *vbo_obj.vbo; + } + return {}; + } + + [[nodiscard]] std::optional> get_element() const + { + for (const auto& vbo_obj : vbo_list) + { + if (vbo_obj.is_element()) + return *vbo_obj.vbo; + } + return {}; + } + + ~unique_vao_t() + { + if (vaoID) + glDeleteVertexArrays(1, &*vaoID); + } + + private: + std::optional vaoID; + std::vector vbo_list; + }; +} + +#endif //BLT_GFX_VAO_H diff --git a/include/blt/gfx/vbo.h b/include/blt/gfx/vbo.h new file mode 100644 index 0000000..9f58d3e --- /dev/null +++ b/include/blt/gfx/vbo.h @@ -0,0 +1,214 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_GFX_VBO_H +#define BLT_GFX_VBO_H + +#include +#include + +namespace blt::gfx +{ + class unique_vbo_t; + + namespace detail + { + /** + * So long as this class is called from vbo.bind(), it is always valid to chain any internal functions. + * This system is designed to be foolproof, so don't get too clever + */ + class vbo_context_t + { + friend unique_vbo_t; + public: + vbo_context_t(const vbo_context_t& copy) = delete; + vbo_context_t(vbo_context_t&& move) = delete; + vbo_context_t& operator=(const vbo_context_t& copy) = delete; + vbo_context_t& operator=(vbo_context_t&& move) = delete; + /** + * By default, the VBO is bound when this class is constructed (this class should only be constructed through the VBO bind() method) + * + * It is very unlikely that you need this method! + */ + vbo_context_t& bind(); + + vbo_context_t& unbind(); + + /** + * Reserves a chunk of GPU memory for future use + */ + vbo_context_t& resize(GLsizeiptr size, GLint mem_type); + + /** + * Reserves a chunk of GPU memory for future use + */ + vbo_context_t& resize(const size_t size, const GLint mem_type) + { + return resize(static_cast(size), mem_type); + } + + /** + * Uploads a chunk of memory to the GPU. If the existing VBO has enough space, the memory will be reused. + */ + vbo_context_t& upload(size_t size, const void* ptr, GLint mem_type); + + /** + * Uploads a chunk of memory to the GPU. If the existing VBO has enough space, the memory will be reused. + */ + template , bool> = true> + vbo_context_t& upload(const size_t size, T* ptr, const GLint mem_type) + { + return upload(size, static_cast(ptr), mem_type); + } + + /** + * Updates an internal segment of the VBO. This function will never reallocate. + */ + vbo_context_t& update(size_t offset, size_t size, const void* ptr); + + /** + * Updates an internal segment of the VBO. This function will never reallocate. + */ + template , bool> = true> + vbo_context_t& update(const size_t offset, const size_t size, T* ptr) + { + return update(offset, size, static_cast(ptr)); + } + + private: + [[nodiscard]] bool is_bound() const; + + explicit vbo_context_t(unique_vbo_t& vbo): vbo(vbo) + { + bind(); + } + + unique_vbo_t& vbo; + }; + } + + class unique_vbo_t + { + friend class detail::vbo_context_t; + + public: + explicit unique_vbo_t(const GLuint type): vboID(0), buffer_type(type) + { + glGenBuffers(1, &*vboID); + } + + unique_vbo_t(const unique_vbo_t&) = delete; + + unique_vbo_t& operator=(const unique_vbo_t&) = delete; + + unique_vbo_t(unique_vbo_t&& other) noexcept: vboID(std::exchange(other.vboID, std::nullopt)), buffer_type(other.buffer_type), + size(other.size), memory_type(other.memory_type) + {} + + unique_vbo_t& operator=(unique_vbo_t&& other) noexcept + { + vboID = std::exchange(other.vboID, vboID); + buffer_type = std::exchange(other.buffer_type, buffer_type); + size = std::exchange(other.size, size); + memory_type = std::exchange(other.memory_type, memory_type); + return *this; + } + + /** + * Changes the internal buffer type of this VBO + */ + GLuint change_type(const GLuint type) + { + return std::exchange(this->buffer_type, type); + } + + /** + * This function binds the VBO to the current buffer_type slot and returns an object which allows you to modify or use this VBO. + * This allows you to use the VBO without worrying about whether an operation is valid in this context. + * As so long as you use this object in line, or without binding other VBOs to the same buffer_type + * (violating the contracts this function attempts to create) then all functions on the associated object are valid to call. + * + * You can enable the flag BLT_DEBUG_CONTRACTS which will validate VBO bind state making most of ^ irrelevant + */ + detail::vbo_context_t bind(); + + [[nodiscard]] auto native_handle() const + { + return vboID; + } + + [[nodiscard]] GLsizeiptr get_size() const + { + return size; + } + + [[nodiscard]] GLint get_memory_type() const + { + return memory_type; + } + + [[nodiscard]] GLuint get_buffer_type() const + { + return buffer_type; + } + + ~unique_vbo_t() + { + if (vboID) + glDeleteBuffers(1, &*vboID); + } + + private: + std::optional vboID; + GLuint buffer_type; + GLsizeiptr size = 0; + GLint memory_type = 0; + }; + + class unique_ssbo_t : public unique_vbo_t + { + public: + unique_ssbo_t(): unique_vbo_t{GL_SHADER_STORAGE_BUFFER} + {} + }; + + class unique_ebo_t : public unique_vbo_t + { + public: + unique_ebo_t(): unique_vbo_t{GL_ELEMENT_ARRAY_BUFFER} + {} + }; + + class unique_ubo_t : public unique_vbo_t + { + public: + explicit unique_ubo_t(const i32 location): unique_vbo_t{GL_UNIFORM_BUFFER} + {set_location(location);} + + void set_location(i32 new_location); + + [[nodiscard]] i32 get_location() const + { + return location; + } + private: + i32 location = 0; + }; +} + +#endif //BLT_GFX_VBO_H diff --git a/libraries/BLT b/libraries/BLT index e2dc35f..2e1bdf9 160000 --- a/libraries/BLT +++ b/libraries/BLT @@ -1 +1 @@ -Subproject commit e2dc35fea98cc62897169cfc50dbf59fd820cd0e +Subproject commit 2e1bdf945e820348f7ae59922ff41355b9dd3a9c diff --git a/src/blt/gfx/vao.cpp b/src/blt/gfx/vao.cpp new file mode 100644 index 0000000..cea1af9 --- /dev/null +++ b/src/blt/gfx/vao.cpp @@ -0,0 +1,100 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +namespace blt::gfx +{ + #define ENSURE_CONTEXT_BOUND BLT_CONTRACT(glfwGetCurrentContext() != nullptr, "Expected active OpenGL context!") + + detail::vao_vbo_context_t& detail::vao_vbo_context_t::attribute_ptr(const int attribute_number, const int coordinate_size, const GLenum type, + const int stride, const long offset) + { + if (!vbo.attribute_numbers) + vbo.attribute_numbers = hashset_t(); + if (!vbo.attribute_numbers->contains(attribute_number)) + vbo.attribute_numbers->insert(attribute_number); + glEnableVertexAttribArray(attribute_number); + glVertexAttribPointer(attribute_number, coordinate_size, type, GL_FALSE, stride < 0 ? 0 : stride, reinterpret_cast(offset)); + attributed = true; + return *this; + } + + detail::vao_vbo_context_t::~vao_vbo_context_t() + { + #if blt_debug_has_flag(BLT_DEBUG_CONTRACTS) + if (!(vbo.is_element() || attributed)) + { + BLT_WARN("VBO is not an element array buffer or been assigned to an attribute, are you sure this is what you want?"); + BLT_WARN("You can silence this warning by calling .silence()"); + } + #endif + } + + detail::vao_context_t& detail::vao_context_t::bind() + { + vao.bind(); + return *this; + } + + detail::vao_context_t& detail::vao_context_t::unbind() // NOLINT + { + ENSURE_CONTEXT_BOUND; + glBindVertexArray(0); + return *this; + } + + detail::vao_vbo_context_t detail::vao_context_t::attach_vbo(unique_vbo_t&& vbo) const + { + ENSURE_CONTEXT_BOUND; + BLT_CONTRACT(vao.vaoID, "Expected VAO to have an associated VAO ID!"); + BLT_CONTRACT(is_bound(), "Expected VAO to be bound before attaching VBO! (If you are using this API correctly, this has been done for you!)"); + + auto& vbo_storage = vao.vbo_list.emplace_back(std::move(vbo)); + vbo_storage.vbo->bind(); + return vao_vbo_context_t{vao, vbo_storage}; + } + + detail::vao_context_t::~vao_context_t() + { + if (is_bound()) + unbind(); + } + + bool detail::vao_context_t::is_bound() const + { + GLint current_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, ¤t_vao); + return *vao.vaoID == static_cast(current_vao); + } + + detail::vao_context_t unique_vao_t::configure() + { + ENSURE_CONTEXT_BOUND; + return detail::vao_context_t{*this}; + } + + void unique_vao_t::bind() const + { + ENSURE_CONTEXT_BOUND; + BLT_CONTRACT(vaoID, "Expected VAO to have an associated VAO ID!"); + glBindVertexArray(*vaoID); + } +} diff --git a/src/blt/gfx/vbo.cpp b/src/blt/gfx/vbo.cpp new file mode 100644 index 0000000..af0c5fc --- /dev/null +++ b/src/blt/gfx/vbo.cpp @@ -0,0 +1,103 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +namespace blt::gfx +{ + #if blt_debug_has_flag(BLT_DEBUG_CONTRACTS) + static hashmap_t bound_vbo_ids; + #endif + + namespace detail + { + vbo_context_t& vbo_context_t::bind() + { + BLT_CONTRACT(vbo.vboID, "Expected VBO to have an associated VBO ID!"); + glBindBuffer(vbo.buffer_type, *vbo.vboID); + #if blt_debug_has_flag(BLT_DEBUG_CONTRACTS) + bound_vbo_ids[vbo.buffer_type] = *vbo.vboID; + #endif + return *this; + } + + vbo_context_t& vbo_context_t::unbind() + { + glBindBuffer(vbo.buffer_type, 0); + #if blt_debug_has_flag(BLT_DEBUG_CONTRACTS) + bound_vbo_ids[vbo.buffer_type] = 0; + #endif + return *this; + } + + vbo_context_t& vbo_context_t::resize(const GLsizeiptr size, const GLint mem_type) + { + BLT_CONTRACT(is_bound(), "Expected VBO to be bound before resizing!"); + vbo.size = size; + vbo.memory_type = mem_type; + glBufferData(vbo.buffer_type, size, nullptr, mem_type); + return *this; + } + + vbo_context_t& vbo_context_t::upload(const size_t size, const void* ptr, const GLint mem_type) + { + BLT_CONTRACT(is_bound(), "Expected VBO to be bound before uploading!"); + if (mem_type != vbo.memory_type || static_cast(vbo.size) < size) + { + vbo.size = static_cast(size); + vbo.memory_type = mem_type; + glBufferData(vbo.buffer_type, vbo.size, ptr, mem_type); + } else + { + update(0, size, ptr); + } + return *this; + } + + vbo_context_t& vbo_context_t::update(const size_t offset, const size_t size, const void* ptr) + { + BLT_CONTRACT(is_bound(), "Expected VBO to be bound before updating!"); + glBufferSubData(vbo.buffer_type, static_cast(offset), static_cast(size), ptr); + return *this; + } + + bool vbo_context_t::is_bound() const + { + #if blt_debug_has_flag(BLT_DEBUG_CONTRACTS) + return bound_vbo_ids[vbo.buffer_type] == *vbo.vboID; + #else + return true; + #endif + } + } + + detail::vbo_context_t unique_vbo_t::bind() + { + BLT_CONTRACT(glfwGetCurrentContext() != nullptr, "Expected active OpenGL context!"); + return detail::vbo_context_t{*this}; + } + + void unique_ubo_t::set_location(const i32 new_location) + { + BLT_CONTRACT(native_handle().has_value(), "Expected UBO to have an associated buffer! (You are probably calling this on a moved-from value)"); + location = new_location; + glBindBufferBase(GL_UNIFORM_BUFFER, location, *native_handle()); + } +}