From 85bf771ea6d8d584f1c735cc963e17571ca0a0d1 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 17 Apr 2023 11:54:07 +0100 Subject: First cut of instance vertices and proxy Untested outside unit test, allows the use of a glBuffer as a storage container. To be combined with a vertex array and/or mesh etc for massing drawing with glDrawElementsInstanced --- gfx/gl/instanceVertices.h | 147 ++++++++++++++++++++++++++++++++++++++++++++++ test/Jamfile.jam | 1 + test/test-instancing.cpp | 76 ++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 gfx/gl/instanceVertices.h create mode 100644 test/test-instancing.cpp 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 +#include +#include +#include +#include + +template 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 + T & + operator=(U && v) + { + return instances->data[index] = std::forward(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 * instances; + std::size_t index; + }; + + template + InstanceProxy + acquire(Params &&... params) + { + if (!unused.empty()) { + auto idx = unused.back(); + unused.pop_back(); + new (&data[idx]) T(std::forward(params)...); + return InstanceProxy {this, idx}; + } + if (next >= data.size()) { + resize(data.size() * 2); + } + new (&data[next]) T(std::forward(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 + allocBuffer(const glBuffer & buffer, std::size_t count) + { + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(T) * count), nullptr, GL_DYNAMIC_DRAW); + auto data = static_cast(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(maintain); + std::vector 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 data; + std::size_t next; + std::vector 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 : benchmark test ; run perf-persistence.cpp : -- : test-persistence : benchmark test ; run test-worker.cpp ; +run test-instancing.cpp : : : 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 +#include + +#include + +BOOST_GLOBAL_FIXTURE(ApplicationBase); +BOOST_GLOBAL_FIXTURE(TestMainWindow); + +BOOST_FIXTURE_TEST_SUITE(i, InstanceVertices) + +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 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 proxies; + std::vector 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() -- cgit v1.2.3