From 914fd31f5ce90d0660efe2c418586d9c77c26f66 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 30 Apr 2023 13:27:39 +0100 Subject: Initial commit of glContainer A std::vector like container backed by an OpenGL buffer. --- lib/glContainer.h | 406 ++++++++++++++++++++++++++++++++++++++++++++++ test/Jamfile.jam | 1 + test/test-glContainer.cpp | 223 +++++++++++++++++++++++++ 3 files changed, 630 insertions(+) create mode 100644 lib/glContainer.h create mode 100644 test/test-glContainer.cpp diff --git a/lib/glContainer.h b/lib/glContainer.h new file mode 100644 index 0000000..9e942b4 --- /dev/null +++ b/lib/glContainer.h @@ -0,0 +1,406 @@ +#pragma once + +#include "glArrays.h" +#include +#include +#include + +template class basic_glContainer_iterator { +public: + explicit basic_glContainer_iterator(I * i) : i {i} { } + + auto & + operator++() noexcept + { + i = Direction {}(i, 1); + return *this; + } + auto + operator++(int) noexcept + { + return basic_glContainer_iterator {std::exchange(i, Direction {}(i, 1))}; + } + auto & + operator--() noexcept + { + i = Direction {}(i, -1); + return *this; + } + auto + operator--(int) noexcept + { + return basic_glContainer_iterator {std::exchange(i, Direction {}(i, -1))}; + } + + [[nodiscard]] auto + operator-(const basic_glContainer_iterator & other) const noexcept + { + if constexpr (std::is_same_v>) { + return this->i - other.i; + } + else { + return other.i - this->i; + } + } + + [[nodiscard]] bool + operator==(const basic_glContainer_iterator & other) const noexcept + { + return this->i == other.i; + } + [[nodiscard]] bool + operator!=(const basic_glContainer_iterator & other) const noexcept + { + return this->i != other.i; + } + + [[nodiscard]] auto + operator->() const noexcept + { + return i; + } + [[nodiscard]] auto & + operator*() const noexcept + { + return *i; + } + +private: + I * i; +}; + +template class glContainer { +public: + using value_type = T; + using reference_type = T &; + using const_reference_type = const T &; + using pointer_type = T *; + using const_pointer_type = const T *; + using size_type = std::size_t; + using iterator = basic_glContainer_iterator>; + using const_iterator = basic_glContainer_iterator>; + using reserve_iterator = basic_glContainer_iterator>; + using const_reserve_iterator = basic_glContainer_iterator>; + + glContainer() + { + allocBuffer(1); + } + + ~glContainer() + { + clear(); + } + + [[nodiscard]] iterator + begin() + { + map(); + return iterator {data_}; + } + + [[nodiscard]] iterator + end() + { + map(); + return iterator {data_ + size_}; + } + + [[nodiscard]] const_iterator + begin() const + { + map(); + return const_iterator {data_}; + } + + [[nodiscard]] const_iterator + end() const + { + map(); + return const_iterator {data_ + size_}; + } + + [[nodiscard]] const_iterator + cbegin() const + { + map(); + return const_iterator {data_}; + } + + [[nodiscard]] const_iterator + cend() const + { + map(); + return const_iterator {data_ + size_}; + } + + [[nodiscard]] reserve_iterator + rbegin() + { + map(); + return reserve_iterator {data_ + size_ - 1}; + } + + [[nodiscard]] reserve_iterator + rend() + { + map(); + return reserve_iterator {data_ - 1}; + } + + [[nodiscard]] const_reserve_iterator + rbegin() const + { + map(); + return const_reserve_iterator {data_ + size_ - 1}; + } + + [[nodiscard]] const_reserve_iterator + rend() const + { + map(); + return const_reserve_iterator {data_ - 1}; + } + + [[nodiscard]] const_reserve_iterator + crbegin() const + { + map(); + return const_reserve_iterator {data_ + size_ - 1}; + } + + [[nodiscard]] const_reserve_iterator + crend() const + { + map(); + return const_reserve_iterator {data_ - 1}; + } + + [[nodiscard]] size_type + size() const + { + return size_; + } + + [[nodiscard]] reference_type + at(size_type pos) + { + if (pos >= size()) { + throw std::out_of_range {__FUNCTION__}; + } + map(); + return data_[pos]; + } + + [[nodiscard]] const_reference_type + at(size_type pos) const + { + if (pos >= size()) { + throw std::out_of_range {__FUNCTION__}; + } + map(); + return data_[pos]; + } + + [[nodiscard]] reference_type + operator[](size_type pos) + { + map(); + return data_[pos]; + } + + [[nodiscard]] const_reference_type + operator[](size_type pos) const + { + map(); + return data_[pos]; + } + + [[nodiscard]] pointer_type + data() + { + map(); + return data_; + } + + [[nodiscard]] const_pointer_type + data() const + { + map(); + return data_; + } + + [[nodiscard]] reference_type + front() + { + map(); + return *data_; + } + + [[nodiscard]] reference_type + back() + { + map(); + return *(data_ + size_ - 1); + } + + [[nodiscard]] const_reference_type + front() const + { + map(); + return *data_; + } + + [[nodiscard]] const_reference_type + back() const + { + map(); + return *(data_ + size_ - 1); + } + + [[nodiscard]] bool + empty() const + { + return !size(); + } + + [[nodiscard]] size_type + capacity() const + { + return capacity_; + } + + void + unmap() const + { + if (data_) { + glUnmapNamedBuffer(buffer_); + data_ = nullptr; + } + } + + void + reserve(size_type newCapacity) + { + if (newCapacity <= capacity_) { + return; + } + + std::vector existing; + existing.reserve(size_); + map(); + std::move(begin(), end(), std::back_inserter(existing)); + allocBuffer(newCapacity); + map(); + std::move(existing.begin(), existing.end(), begin()); + } + + void + resize(size_type newSize) + { + if (newSize == size_) { + return; + } + + const auto maintain = std::min(newSize, capacity_); + std::vector existing; + const auto maintaind = static_cast(maintain); + existing.reserve(maintain); + map(); + std::move(data_, data_ + maintain, std::back_inserter(existing)); + for (auto uninitialised = data_ + newSize; uninitialised < data_ + size_; ++uninitialised) { + uninitialised->~T(); + } + allocBuffer(newSize); + map(); + std::move(existing.begin(), existing.begin() + maintaind, data_); + for (auto uninitialised = data_ + size_; uninitialised < data_ + newSize; ++uninitialised) { + new (uninitialised) T {}; + } + size_ = newSize; + } + + void + shrink_to_fit() + { + if (capacity_ <= size_) { + return; + } + + std::vector existing; + existing.reserve(size_); + map(); + std::move(begin(), end(), std::back_inserter(existing)); + allocBuffer(size_); + map(); + std::move(existing.begin(), existing.end(), begin()); + } + + void + clear() + { + std::for_each(begin(), end(), [](auto && v) { + v.~T(); + }); + size_ = 0; + } + + template + reference_type + emplace_back(P &&... ps) + { + auto newSize = size_ + 1; + reserve(newSize); + map(); + new (data_ + size_) T {std::forward

(ps)...}; + size_ = newSize; + return back(); + } + + reference_type + push_back(T p) + { + auto newSize = size_ + 1; + reserve(newSize); + map(); + new (data_ + size_) T {std::move(p)}; + size_ = newSize; + return back(); + } + +protected: + void + allocBuffer(size_type newCapacity) + { + if (newCapacity == 0) { + return allocBuffer(1); + } + glBindBuffer(GL_ARRAY_BUFFER, buffer_); + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + capacity_ = newCapacity; + data_ = nullptr; + } + + void + map() const + { + if (!data_) { + data_ = static_cast(glMapNamedBuffer(buffer_, GL_READ_WRITE)); + assert(data_); + } + } + + glBuffer buffer_; + std::size_t capacity_ {}; + std::size_t size_ {}; + mutable T * data_ {}; +}; + +template struct std::iterator_traits> { + using difference_type = ssize_t; + using value_type = T; + using pointer = T *; + using reference = T &; + using iterator_category = std::random_access_iterator_tag; +}; diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 1ce73b7..fb9a996 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -58,6 +58,7 @@ run perf-assetFactory.cpp : -- : test-assetFactory : benchmark benchmark test ; run test-worker.cpp ; run test-instancing.cpp : : : test ; +run test-glContainer.cpp : : : test ; compile test-static-enumDetails.cpp ; compile test-static-stream_support.cpp ; explicit perf-assetFactory ; diff --git a/test/test-glContainer.cpp b/test/test-glContainer.cpp new file mode 100644 index 0000000..efb8f71 --- /dev/null +++ b/test/test-glContainer.cpp @@ -0,0 +1,223 @@ +#define BOOST_TEST_MODULE glContainer + +#include "testHelpers.h" +#include "testMainWindow.h" +#include "ui/applicationBase.h" +#include +#include + +#include "glContainer.h" + +BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer::iterator); +BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer::const_iterator); +BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer::reserve_iterator); +BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer::const_reserve_iterator); + +BOOST_GLOBAL_FIXTURE(ApplicationBase); +BOOST_GLOBAL_FIXTURE(TestMainWindow); + +BOOST_FIXTURE_TEST_SUITE(i, glContainer) + +BOOST_AUTO_TEST_CASE(createDestroy, *boost::unit_test::timeout(1)) +{ + BOOST_CHECK(!data_); + BOOST_CHECK_NO_THROW(map()); + BOOST_REQUIRE(data_); + BOOST_CHECK_NO_THROW(unmap()); + BOOST_CHECK(!data_); +} + +BOOST_AUTO_TEST_CASE(push_back_test, *boost::unit_test::timeout(1)) +{ + BOOST_CHECK_EQUAL(capacity_, 1); + BOOST_CHECK_EQUAL(size_, 0); + BOOST_CHECK_NO_THROW(push_back(1)); + BOOST_CHECK_NO_THROW(push_back(2)); + BOOST_CHECK_NO_THROW(push_back(3)); + BOOST_CHECK_NO_THROW(push_back(4)); + BOOST_CHECK_EQUAL(capacity_, 4); + BOOST_CHECK_EQUAL(size_, 4); + { + std::array expected1 {1, 2, 3, 4}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected1.rbegin(), expected1.rend()); + } +} + +BOOST_AUTO_TEST_CASE(emplace_back_test, *boost::unit_test::timeout(1)) +{ + BOOST_CHECK_EQUAL(capacity_, 1); + BOOST_CHECK_EQUAL(size_, 0); + + BOOST_CHECK_NO_THROW(emplace_back(1)); + BOOST_CHECK_NO_THROW(emplace_back(2)); + BOOST_CHECK_EQUAL(capacity_, 2); + BOOST_CHECK_EQUAL(size_, 2); + + BOOST_CHECK_NO_THROW(reserve(5)); + BOOST_CHECK_EQUAL(capacity_, 5); + BOOST_CHECK_EQUAL(size_, 2); + + BOOST_CHECK_NO_THROW(emplace_back(3)); + BOOST_CHECK_NO_THROW(emplace_back(4)); + BOOST_CHECK_EQUAL(capacity_, 5); + BOOST_CHECK_EQUAL(size_, 4); + + { + std::array expected1 {1, 2, 3, 4}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected1.rbegin(), expected1.rend()); + } + + BOOST_CHECK_NO_THROW(emplace_back(5)); + BOOST_CHECK_EQUAL(capacity_, 5); + BOOST_CHECK_EQUAL(size_, 5); + BOOST_CHECK_NO_THROW(emplace_back(6)); + BOOST_CHECK_NO_THROW(emplace_back(7)); + BOOST_CHECK_EQUAL(capacity_, 7); + BOOST_CHECK_EQUAL(size_, 7); + + { + std::array expected2 {1, 2, 3, 4, 5, 6, 7}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected2.begin(), expected2.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected2.rbegin(), expected2.rend()); + } + + BOOST_CHECK_EQUAL(7, end() - begin()); + BOOST_CHECK_EQUAL(7, rend() - rbegin()); +} + +BOOST_AUTO_TEST_CASE(resize_test) +{ + BOOST_CHECK_NO_THROW(push_back(1)); + BOOST_CHECK_NO_THROW(emplace_back(2)); + BOOST_CHECK_NO_THROW(resize(4)); + BOOST_CHECK_EQUAL(capacity_, 4); + BOOST_CHECK_EQUAL(size_, 4); + { + std::array expected1 {1, 2, 0, 0}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); + } + + BOOST_CHECK_NO_THROW(resize(1)); + BOOST_CHECK_EQUAL(capacity_, 1); + BOOST_CHECK_EQUAL(size_, 1); + { + std::array expected2 {1}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected2.begin(), expected2.end()); + } + + BOOST_CHECK_NO_THROW(resize(1)); + BOOST_CHECK_EQUAL(capacity_, 1); + BOOST_CHECK_EQUAL(size_, 1); + + BOOST_CHECK_NO_THROW(resize(0)); + BOOST_CHECK_EQUAL(capacity_, 1); + BOOST_CHECK_EQUAL(size_, 0); + BOOST_CHECK_EQUAL(begin(), end()); + BOOST_CHECK_EQUAL(rbegin(), rend()); +} + +BOOST_AUTO_TEST_CASE(shrink_to_fit_test) +{ + BOOST_CHECK_NO_THROW(reserve(4)); + BOOST_CHECK_NO_THROW(emplace_back(1)); + BOOST_CHECK_NO_THROW(emplace_back(2)); + BOOST_CHECK_EQUAL(capacity_, 4); + BOOST_CHECK_EQUAL(size_, 2); + { + std::array expected1 {1, 2}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); + } + BOOST_CHECK_NO_THROW(shrink_to_fit()); + BOOST_CHECK_EQUAL(capacity_, 2); + BOOST_CHECK_EQUAL(size_, 2); + { + std::array expected1 {1, 2}; + BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); + } + + BOOST_CHECK_NO_THROW(shrink_to_fit()); + BOOST_CHECK_EQUAL(capacity(), 2); + BOOST_CHECK_EQUAL(size(), 2); + + BOOST_CHECK_NO_THROW(clear()); + BOOST_CHECK_EQUAL(capacity(), 2); + BOOST_CHECK_EQUAL(size(), 0); +} + +BOOST_AUTO_TEST_CASE(getters) +{ + BOOST_CHECK(empty()); + BOOST_CHECK_NO_THROW(emplace_back(1)); + BOOST_CHECK(!empty()); + BOOST_CHECK_NO_THROW(emplace_back(2)); + BOOST_CHECK_EQUAL(1, front()); + BOOST_CHECK_EQUAL(2, back()); + BOOST_CHECK_EQUAL(1, at(0)); + BOOST_CHECK_EQUAL(2, at(1)); + BOOST_CHECK_EQUAL(1, (*this)[0]); + BOOST_CHECK_EQUAL(2, (*this)[1]); + BOOST_CHECK_EQUAL(data_, data()); + BOOST_CHECK_THROW(std::ignore = at(2), std::out_of_range); + + const auto & constCont {*this}; + BOOST_CHECK_EQUAL(1, constCont.front()); + BOOST_CHECK_EQUAL(2, constCont.back()); + { + std::array expected1 {1, 2}; + BOOST_CHECK_EQUAL_COLLECTIONS(constCont.begin(), constCont.end(), expected1.begin(), expected1.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(constCont.rbegin(), constCont.rend(), expected1.rbegin(), expected1.rend()); + BOOST_CHECK_EQUAL_COLLECTIONS(constCont.cbegin(), constCont.cend(), expected1.cbegin(), expected1.cend()); + BOOST_CHECK_EQUAL_COLLECTIONS(constCont.crbegin(), constCont.crend(), expected1.crbegin(), expected1.crend()); + } + BOOST_CHECK_EQUAL(1, constCont.at(0)); + BOOST_CHECK_EQUAL(2, constCont.at(1)); + BOOST_CHECK_EQUAL(1, constCont[0]); + BOOST_CHECK_EQUAL(2, constCont[1]); + BOOST_CHECK_EQUAL(data_, constCont.data()); + BOOST_CHECK_THROW(std::ignore = constCont.at(2), std::out_of_range); +} + +BOOST_AUTO_TEST_CASE(random_access) +{ + BOOST_CHECK_NO_THROW(emplace_back(1)); + BOOST_CHECK_NO_THROW(emplace_back(2)); + BOOST_CHECK_NO_THROW(emplace_back(3)); + + auto i = begin(); + BOOST_CHECK_EQUAL(1, *i); + BOOST_CHECK_EQUAL(2, *++i); + BOOST_CHECK_EQUAL(2, *i++); + BOOST_CHECK_EQUAL(3, *i); + BOOST_CHECK_EQUAL(3, *i--); + BOOST_CHECK_EQUAL(2, *i); + BOOST_CHECK_EQUAL(1, *--i); + BOOST_CHECK_EQUAL(1, *i); +} + +BOOST_AUTO_TEST_SUITE_END(); + +struct C { + int x; + float y; +}; + +BOOST_FIXTURE_TEST_SUITE(c, glContainer) + +BOOST_AUTO_TEST_CASE(basic) +{ + BOOST_CHECK_NO_THROW(emplace_back(1, 2.f)); + BOOST_CHECK_EQUAL(1, begin()->x); + BOOST_CHECK_EQUAL(2.f, begin()->y); + BOOST_CHECK_NO_THROW(begin()->x = 3); + BOOST_CHECK_EQUAL(3, begin()->x); + + BOOST_CHECK_NO_THROW(push_back(C {4, 5.f})); + BOOST_CHECK_EQUAL(3, begin()->x); + BOOST_CHECK_EQUAL(2.f, begin()->y); + BOOST_CHECK_EQUAL(4, rbegin()->x); + BOOST_CHECK_EQUAL(5.f, rbegin()->y); +} + +BOOST_AUTO_TEST_SUITE_END(); -- cgit v1.2.3