summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gfx/gl/instanceVertices.h147
-rw-r--r--test/Jamfile.jam1
-rw-r--r--test/test-instancing.cpp76
3 files changed, 224 insertions, 0 deletions
diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h
new file mode 100644
index 0000000..cf3b75b
--- /dev/null
+++ b/gfx/gl/instanceVertices.h
@@ -0,0 +1,147 @@
+#pragma once
+
+#include "glArrays.h"
+#include <iterator>
+#include <span>
+#include <special_members.hpp>
+#include <utility>
+#include <vector>
+
+template<typename T> class InstanceVertices {
+public:
+ InstanceVertices(size_t initialSize = 16) : data {allocBuffer(buffer, initialSize)}, next {} { }
+
+ ~InstanceVertices()
+ {
+ glUnmapNamedBuffer(buffer);
+ }
+
+ class InstanceProxy {
+ public:
+ InstanceProxy(InstanceVertices * iv, std::size_t idx) : instances {iv}, index {idx} { }
+ InstanceProxy(InstanceProxy && other) : instances {std::exchange(other.instances, nullptr)}, index {other.index}
+ {
+ }
+ NO_COPY(InstanceProxy);
+
+ ~InstanceProxy()
+ {
+ if (instances) {
+ instances->release(*this);
+ }
+ }
+
+ InstanceProxy &
+ operator=(InstanceProxy && other)
+ {
+ instances = std::exchange(other.instances, nullptr);
+ index = other.index;
+ }
+
+ operator T &()
+ {
+ return *get();
+ }
+ operator const T &() const
+ {
+ return *get();
+ }
+ template<typename U>
+ T &
+ operator=(U && v)
+ {
+ return instances->data[index] = std::forward<U>(v);
+ }
+
+ T *
+ get()
+ {
+ return &instances->data[index];
+ }
+ const T *
+ get() const
+ {
+ return &instances->data[index];
+ }
+ T *
+ operator->()
+ {
+ return get();
+ }
+ const T *
+ operator->() const
+ {
+ return get();
+ }
+ T &
+ operator*()
+ {
+ return *get();
+ }
+ const T &
+ operator*() const
+ {
+ return *get();
+ }
+
+ private:
+ friend InstanceVertices;
+
+ InstanceVertices<T> * instances;
+ std::size_t index;
+ };
+
+ template<typename... Params>
+ InstanceProxy
+ acquire(Params &&... params)
+ {
+ if (!unused.empty()) {
+ auto idx = unused.back();
+ unused.pop_back();
+ new (&data[idx]) T(std::forward<Params>(params)...);
+ return InstanceProxy {this, idx};
+ }
+ if (next >= data.size()) {
+ resize(data.size() * 2);
+ }
+ new (&data[next]) T(std::forward<Params>(params)...);
+ return InstanceProxy {this, next++};
+ }
+
+ void
+ release(const InstanceProxy & p)
+ {
+ data[p.index].~T();
+ unused.push_back(p.index);
+ }
+
+protected:
+ friend InstanceProxy;
+
+ static std::span<T>
+ allocBuffer(const glBuffer & buffer, std::size_t count)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(T) * count), nullptr, GL_DYNAMIC_DRAW);
+ auto data = static_cast<T *>(glMapNamedBuffer(buffer, GL_READ_WRITE));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ return {data, count};
+ }
+
+ void
+ resize(size_t newCapacity)
+ {
+ const auto maintain = std::min(newCapacity, data.size());
+ const auto maintaind = static_cast<typename decltype(data)::difference_type>(maintain);
+ std::vector<T> existing;
+ existing.reserve(maintain);
+ std::move(data.begin(), data.begin() + maintaind, std::back_inserter(existing));
+ data = allocBuffer(buffer, newCapacity);
+ std::move(existing.begin(), existing.begin() + maintaind, data.begin());
+ }
+
+ glBuffer buffer;
+ std::span<T> data;
+ std::size_t next;
+ std::vector<size_t> unused;
+};
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index b0eed5e..9cdcef4 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -57,6 +57,7 @@ run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) :
run perf-assetFactory.cpp : -- : test-assetFactory : <library>benchmark <library>test ;
run perf-persistence.cpp : -- : test-persistence : <library>benchmark <library>test ;
run test-worker.cpp ;
+run test-instancing.cpp : : : <library>test ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
explicit perf-assetFactory ;
diff --git a/test/test-instancing.cpp b/test/test-instancing.cpp
new file mode 100644
index 0000000..7191567
--- /dev/null
+++ b/test/test-instancing.cpp
@@ -0,0 +1,76 @@
+#define BOOST_TEST_MODULE instancing
+
+#include "testHelpers.h"
+#include "testMainWindow.h"
+#include "ui/applicationBase.h"
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include <gfx/gl/instanceVertices.h>
+
+BOOST_GLOBAL_FIXTURE(ApplicationBase);
+BOOST_GLOBAL_FIXTURE(TestMainWindow);
+
+BOOST_FIXTURE_TEST_SUITE(i, InstanceVertices<int>)
+
+BOOST_AUTO_TEST_CASE(createDestroy)
+{
+ BOOST_REQUIRE(data.data());
+ BOOST_CHECK_EQUAL(0, next);
+ BOOST_CHECK(unused.empty());
+}
+
+BOOST_AUTO_TEST_CASE(storeRetreive)
+{ // Read write raw buffer, not normally allowed
+ std::vector<int> test(data.size());
+ std::copy(test.begin(), test.end(), data.begin());
+ BOOST_CHECK_EQUAL_COLLECTIONS(test.begin(), test.end(), data.begin(), data.end());
+}
+
+BOOST_AUTO_TEST_CASE(acquireRelease)
+{
+ {
+ auto proxy = acquire();
+ *proxy = 20;
+ BOOST_CHECK_EQUAL(1, next);
+ }
+ BOOST_CHECK_EQUAL(1, next);
+ BOOST_CHECK_EQUAL(1, unused.size());
+ BOOST_CHECK_EQUAL(0, unused.front());
+}
+
+BOOST_AUTO_TEST_CASE(acquireReleaseMove)
+{
+ {
+ auto proxy1 = acquire();
+ *proxy1 = 20;
+ BOOST_CHECK_EQUAL(1, next);
+ auto proxy2 = std::move(proxy1);
+ *proxy2 = 40;
+ BOOST_CHECK_EQUAL(1, next);
+ }
+ BOOST_CHECK_EQUAL(1, next);
+ BOOST_CHECK_EQUAL(1, unused.size());
+ BOOST_CHECK_EQUAL(0, unused.front());
+}
+
+BOOST_AUTO_TEST_CASE(initialize)
+{
+ auto proxy = acquire(5);
+ BOOST_CHECK_EQUAL(*proxy, 5);
+}
+
+BOOST_AUTO_TEST_CASE(resize)
+{
+ constexpr auto COUNT = 500;
+ std::vector<decltype(acquire())> proxies;
+ std::vector<int> expected;
+ for (auto n = 0; n < COUNT; n++) {
+ proxies.push_back(acquire(n));
+ expected.emplace_back(n);
+ }
+ BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), data.begin(), data.begin() + COUNT);
+ BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), proxies.begin(), proxies.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END()