summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2023-04-30 13:27:39 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2023-04-30 19:13:54 +0100
commit914fd31f5ce90d0660efe2c418586d9c77c26f66 (patch)
tree105b8bdde5e13d0e30b66a3d0ecba331c6f853d8
parentRename strings.h to something that won't conflict with a system header (diff)
downloadilt-914fd31f5ce90d0660efe2c418586d9c77c26f66.tar.bz2
ilt-914fd31f5ce90d0660efe2c418586d9c77c26f66.tar.xz
ilt-914fd31f5ce90d0660efe2c418586d9c77c26f66.zip
Initial commit of glContainer
A std::vector like container backed by an OpenGL buffer.
-rw-r--r--lib/glContainer.h406
-rw-r--r--test/Jamfile.jam1
-rw-r--r--test/test-glContainer.cpp223
3 files changed, 630 insertions, 0 deletions
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 <stdexcept>
+#include <utility>
+#include <vector>
+
+template<typename I, typename Direction> 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<I, Direction> {std::exchange(i, Direction {}(i, 1))};
+ }
+ auto &
+ operator--() noexcept
+ {
+ i = Direction {}(i, -1);
+ return *this;
+ }
+ auto
+ operator--(int) noexcept
+ {
+ return basic_glContainer_iterator<I, Direction> {std::exchange(i, Direction {}(i, -1))};
+ }
+
+ [[nodiscard]] auto
+ operator-(const basic_glContainer_iterator & other) const noexcept
+ {
+ if constexpr (std::is_same_v<Direction, std::plus<>>) {
+ 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<typename T> 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<value_type, std::plus<>>;
+ using const_iterator = basic_glContainer_iterator<const value_type, std::plus<>>;
+ using reserve_iterator = basic_glContainer_iterator<value_type, std::minus<>>;
+ using const_reserve_iterator = basic_glContainer_iterator<const value_type, std::minus<>>;
+
+ 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<T> 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<T> existing;
+ const auto maintaind = static_cast<typename decltype(existing)::difference_type>(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<T> 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<typename... P>
+ reference_type
+ emplace_back(P &&... ps)
+ {
+ auto newSize = size_ + 1;
+ reserve(newSize);
+ map();
+ new (data_ + size_) T {std::forward<P>(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<GLsizeiptr>(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ capacity_ = newCapacity;
+ data_ = nullptr;
+ }
+
+ void
+ map() const
+ {
+ if (!data_) {
+ data_ = static_cast<T *>(glMapNamedBuffer(buffer_, GL_READ_WRITE));
+ assert(data_);
+ }
+ }
+
+ glBuffer buffer_;
+ std::size_t capacity_ {};
+ std::size_t size_ {};
+ mutable T * data_ {};
+};
+
+template<typename T, typename D> struct std::iterator_traits<basic_glContainer_iterator<T, D>> {
+ 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 : <library>benchmark <library
run perf-persistence.cpp : -- : test-persistence : <library>benchmark <library>test ;
run test-worker.cpp ;
run test-instancing.cpp : : : <library>test ;
+run test-glContainer.cpp : : : <library>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 <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include "glContainer.h"
+
+BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::iterator);
+BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::const_iterator);
+BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::reserve_iterator);
+BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::const_reserve_iterator);
+
+BOOST_GLOBAL_FIXTURE(ApplicationBase);
+BOOST_GLOBAL_FIXTURE(TestMainWindow);
+
+BOOST_FIXTURE_TEST_SUITE(i, glContainer<int>)
+
+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<C>)
+
+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();