// ===================================================================================================================== // glw, an open-source library that wraps OpenGL structures into classes. // Copyright (C) 2024 Medusa Slockbower // // 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 GLW_BUFFER_H #define GLW_BUFFER_H #include "common.h" #include #include namespace ocu = open_cpp_utils; namespace glw { // ===================================================================================================================== // Definitions // ===================================================================================================================== // Typedefs ------------------------------------------------------------------------------------------------------------ using buffer_t = GLenum; template class buffer; // Enums --------------------------------------------------------------------------------------------------------------- enum buffer_type { buf_vertex = GL_ARRAY_BUFFER , buf_element = GL_ELEMENT_ARRAY_BUFFER , buf_copy_read = GL_COPY_READ_BUFFER , buf_copy_write = GL_COPY_WRITE_BUFFER , buf_pixel_pack = GL_PIXEL_PACK_BUFFER , buf_pixel_unpack = GL_PIXEL_UNPACK_BUFFER , buf_query = GL_QUERY_BUFFER , buf_texture = GL_TEXTURE_BUFFER , buf_transform_feedback = GL_TRANSFORM_FEEDBACK_BUFFER , buf_uniform = GL_UNIFORM_BUFFER , buf_indirect_draw = GL_DRAW_INDIRECT_BUFFER , buf_atomic_counter = GL_ATOMIC_COUNTER_BUFFER , buf_indirect_dispatch = GL_DISPATCH_INDIRECT_BUFFER , buf_shader_storage = GL_SHADER_STORAGE_BUFFER , buf_parameter = GL_PARAMETER_BUFFER }; enum buffer_flags { buf_flags_read = GL_MAP_READ_BIT , buf_flags_write = GL_MAP_WRITE_BIT , buf_flags_read_write = buf_flags_read | buf_flags_write , buf_flags_dynamic = GL_DYNAMIC_STORAGE_BIT , buf_flags_persistent = GL_MAP_PERSISTENT_BIT , buf_flags_coherent = GL_MAP_COHERENT_BIT | bf_persistent , buf_flags_client = GL_CLIENT_STORAGE_BIT }; enum mapping_flags { buf_map_read = GL_MAP_READ_BIT , buf_map_write = GL_MAP_WRITE_BIT , buf_map_read_write = buf_map_read | buf_map_write , buf_map_persistent = GL_MAP_PERSISTENT_BIT , buf_map_coherent = GL_MAP_COHERENT_BIT | buf_map_persistent , buf_map_invalidate_range = GL_MAP_INVALIDATE_RANGE_BIT , buf_map_invalidate_buffer = GL_MAP_INVALIDATE_BUFFER_BIT , buf_map_flush_explicity = GL_MAP_FLUSH_EXPLICIT_BIT | buf_map_write , buf_map_unsynchronized = GL_MAP_UNSYNCHRONIZED_BIT , buf_map_optional = buf_map_invalidate_range | buf_map_invalidate_buffer | buf_map_flush_explicity | buf_map_unsynchronized }; // Buffer Aliases template using vertex_buffer = buffer; template using element_buffer = buffer; template using copy_read_buffer = buffer; template using copy_write_buffer = buffer; template using pixel_pack_buffer = buffer; template using pixel_unpack_buffer = buffer; template using query_buffer = buffer; template using texture_buffer = buffer; template using transform_feedback_buffer = buffer; template using uniform_buffer = buffer; template using indirect_draw_buffer = buffer; template using atomic_counter_buffer = buffer; template using indirect_dispatch_buffer = buffer; template using shader_storage_buffer = buffer; template using parameter_buffer = buffer; // Buffer Definition --------------------------------------------------------------------------------------------------- template class buffer { // Constants =========================================================================================================== public: static constexpr buffer_t type = T_; static constexpr flags_t flags = Fs_; // Functions =========================================================================================================== public: // Constructors -------------------------------------------------------------------------------------------------------- /** * \brief Construct a buffer filled with the provided data * \param size Size of the buffer in bytes * \param data Pointer to the data to initialize the buffer with, if null, initialized with 0 */ buffer(size_t size, const void* data = nullptr); buffer(const buffer&); buffer(buffer&&) = default; ~buffer(); // Access -------------------------------------------------------------------------------------------------------------- /** * \brief Map the buffer to the CPU * \param size Size of the region to map * \param offset Offset into the buffer to map * \param map_flags Flags to map the buffer with * \return Whether the buffer was successfully mapped */ bool map(size_t size = -1, offset_t offset = 0, flags_t map_flags = mapping_flags::read); void unmap(); template T* data() { static_assert(flags | buffer_flags::write); return map_; } /** * \brief * \tparam T * \return */ template const T* data() const { static_assert(flags | buffer_flags::read); return map_; } /** * \brief Bind the buffer for usage with gl functions */ void bind(); /** * \brief Attach the buffer to a specific base location for usage in a shader program * \param location Location to bind the buffer to */ void attach(location_t location); // Status -------------------------------------------------------------------------------------------------------------- [[nodiscard]] bool mapped() const { return map_ != nullptr; } [[nodiscard]] bool valid() const { return handle_ != NULL; } // Capacity ------------------------------------------------------------------------------------------------------------ [[nodiscard]] size_t size() const { return size_; } template [[nodiscard]] size_t capacity() const { return size_ / sizeof(T); } // Modifiers ----------------------------------------------------------------------------------------------------------- /** * \brief Read a section of the buffer from the gpu into a location on the cpu * \param buffer Pointer to the buffer to read into * \param size Size of the buffer * \param offset Offset into the gpu memory */ void read(void* buffer, size_t size, offset_t offset = 0); /** * \brief Write a section to the buffer on the gpu from the cpu * \param buffer Pointer to the buffer to write from * \param size Size of the buffer * \param offset Offset into the gpu memory */ void write(const void* buffer, size_t size, offset_t offset = 0); /** * \brief Clear a portion of the buffer * \param size Size of the section to clear, if negative, size will be automatically calculated using the offset. * \param offset Offset into the buffer * \param data Value to clear the buffer with * \param layout Layout of the provided data * \param type Type of the provided data * \param format Format of the provided data */ void clear(size_t size = -1, offset_t offset = 0, const void* data = nullptr, enum_t layout = r_i, enum_t type = uint8, enum_t format = r8_ui); /** * \brief Copy data from another buffer * \tparam oType Type of the other buffer * \tparam oFlags Flags of the other buffer * \param src Source buffer to copy from * \param size Size of the section of data to copy * \param src_offset Offset into the source buffer * \param dst_offset Offset into the destination buffer */ template void copy(const buffer& src, size_t size = -1, offset_t src_offset = 0, offset_t dst_offset = 0); /** * \brief Resize the buffer * \param size New size of the data in bytes */ void resize(size_t size); // Variables =========================================================================================================== private: handle_t handle_; size_t size_; void* map_; offset_t map_start_, map_end_; flags_t map_flags_; }; // ===================================================================================================================== // Implementations // ===================================================================================================================== // Constructors -------------------------------------------------------------------------------------------------------- template buffer::buffer(size_t size, const void *data) : handle_(NULL) , size_(size) , map_(nullptr) , map_start_(0), map_end_(0) , map_flags_(0) { glCreateBuffers(1, &handle_); glNamedBufferStorage(handle_, size_, data, flags); } template buffer::buffer(const buffer& other) : handle_(NULL) , size_(other.size_) , map_(nullptr) , map_start_(0), map_end_(0) , map_flags_(0) { glCreateBuffers(1, &handle_); glNamedBufferData(handle_, size_, nullptr, flags); copy(other, size_, 0, 0); } // Access -------------------------------------------------------------------------------------------------------------- template buffer::~buffer() { glDeleteBuffers(1, &handle_); } template bool buffer::map(size_t size, offset_t offset, flags_t map_flags) { static_assert(flags & read_write); if(map_) return false; map_flags &= flags | optional; if(size < 0) size = size_ - offset; map_flags_ = map_flags; map_start_ = offset; map_end_ = map_start_ + size; return map_ = glMapNamedBufferRange(handle_, offset, size, map_flags_); } template void buffer::unmap() { glUnmapNamedBuffer(handle_); map_ = nullptr; } template void buffer::bind() { glBindBuffer(type, handle_); } template void buffer::attach(location_t location) { glBindBufferBase(type, location, handle_); } template void buffer::read(void* buffer, size_t size, offset_t offset) { // If not mapped, use glGetBufferSubData to avoid mapping the buffer if(map_ == nullptr) { glGetNamedBufferSubData(handle_, offset, size, buffer); return; } // Check if we need to remap the buffer offset_t start = offset, end = offset + size; if(start < map_start_ || end > map_end_ || !(map_flags_ & mapping_flags::read)) { flags_t map_flags = map_flags_ | mapping_flags::read; start = glm::min(start, map_start_); end = glm::max(end, map_end_); unmap(); map(end - start, start, map_flags); } // Copy from the mapping offset = offset - map_start_; memcpy(buffer, map_ + offset, size); } template void buffer::write(const void *buffer, size_t size, offset_t offset) { // if the buffer isn't mapped and is dynamic, use glBufferSubData to avoid mapping the buffer if(map_ == nullptr && flags | dynamic) { glNamedBufferSubData(handle_, offset, size, buffer); } // Check if we need to remap the buffer offset_t start = offset, end = offset + size; if(start < map_start_ || end > map_end_ || !(map_flags_ & mapping_flags::write)) { flags_t map_flags = map_flags_ | mapping_flags::write; start = glm::min(start, map_start_); end = glm::max(end, map_end_); unmap(); map(end - start, start, map_flags); } // Copy to the mapping offset = offset - map_start_; memcpy(map_ + offset, buffer, size); } template void buffer::clear(size_t size, offset_t offset, const void* data, layout_t layout, type_t type, format_t format) { glClearNamedBufferSubData(handle_, format, offset, size, format, type, data); } template template void buffer::copy(const buffer& src, size_t size, offset_t src_offset, offset_t dst_offset) { glCopyNamedBufferSubData(src.handle_, handle_, src_offset, dst_offset, size); } template void buffer::resize(size_t size) { if(map_) unmap(); handle_t handle; glCreateBuffers(1, &handle); glNamedBufferStorage(handle, size, nullptr, flags); glCopyNamedBufferSubData(handle_, handle, 0, 0, glm::min(size_, size)); glDeleteBuffers(1, &handle_); handle_ = handle; size_ = size; } } #endif //BUFFER_H