#pragma once #include "glArrays.h" #include #include #include #include #include template class InstanceVertices { public: InstanceVertices(size_t initialSize = 16) { allocBuffer(initialSize); } class [[nodiscard]] 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(index); } } InstanceProxy & operator=(InstanceProxy && other) { if (instances) { instances->release(index); } instances = std::exchange(other.instances, nullptr); index = other.index; return *this; } template T & operator=(U && v) { return instances->at(index) = std::forward(v); } [[nodiscard]] operator T &() { return instances->at(index); } [[nodiscard]] operator const T &() const { return instances->at(index); } [[nodiscard]] T * get() { return &instances->at(index); } [[nodiscard]] const T * get() const { return &instances->at(index); } [[nodiscard]] T * operator->() { return get(); } [[nodiscard]] const T * operator->() const { return get(); } [[nodiscard]] T & operator*() { return instances->at(index); } [[nodiscard]] const T & operator*() const { return instances->at(index); } private: InstanceVertices * instances; std::size_t index; }; template [[nodiscard]] InstanceProxy acquire(Params &&... params) { map(); if (!unused.empty()) { auto idx = unused.back(); unused.pop_back(); index[idx] = next++; new (&at(idx)) T(std::forward(params)...); return InstanceProxy {this, idx}; } if (next >= capacity) { resize(capacity * 2); } index.emplace_back(next++); new (data + index.back()) T(std::forward(params)...); return InstanceProxy {this, index.size() - 1}; } [[nodiscard]] const auto & bufferName() const { return buffer; } [[nodiscard]] auto count() const { unmap(); return next; } protected: friend InstanceProxy; void release(const size_t pidx) { // Destroy p's object at(pidx).~T(); if (next-- > index[pidx]) { // Remember p.index is free index now unused.push_back(pidx); // Move last object into p's slot new (&at(pidx)) T {std::move(data[next])}; (data[next]).~T(); *std::find(index.begin(), index.end(), next) = index[pidx]; // Not strictly required, but needed for uniqueness unit test assertion index[pidx] = static_cast(-1); } else { index.pop_back(); } } void allocBuffer(std::size_t newCapacity) { 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 resize(size_t newCapacity) { const auto maintain = std::min(newCapacity, capacity); std::vector existing; const auto maintaind = static_cast(maintain); existing.reserve(maintain); map(); std::move(data, data + maintain, std::back_inserter(existing)); allocBuffer(newCapacity); map(); std::move(existing.begin(), existing.begin() + maintaind, data); capacity = newCapacity; } [[nodiscard]] T & at(size_t pindex) { map(); return data[index[pindex]]; } void map() const { if (!data) { data = static_cast(glMapNamedBuffer(buffer, GL_READ_WRITE)); } } void unmap() const { if (data) { glUnmapNamedBuffer(buffer); data = nullptr; } } glBuffer buffer; mutable T * data {}; // Size of buffer std::size_t capacity {}; // # used of capacity std::size_t next {}; // Index into buffer given to nth proxy std::vector index; // List of free spaces in index std::vector unused; };