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 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 gfx/gl/instanceVertices.h (limited to 'gfx/gl/instanceVertices.h') 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; +}; -- cgit v1.2.3 From e9dd8fafcb4ea073fce9910f7cb139afc52e7c9a Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 20 Apr 2023 20:11:05 +0100 Subject: Expose bufferName and count from InstanceVertices --- gfx/gl/instanceVertices.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index cf3b75b..dc28a4d 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -115,6 +115,18 @@ public: unused.push_back(p.index); } + const auto & + bufferName() const + { + return buffer; + } + + auto + count() const + { + return next; + } + protected: friend InstanceProxy; -- cgit v1.2.3 From ab5b46dbe58ae0401c24ead55a2627b9a577fc1a Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Fri, 21 Apr 2023 23:52:56 +0100 Subject: Instances buffer data needs to be unmapped before use Here we change quite a bit to support mapping and unmapping the buffer as required. The instance/free referencing is still broken though. We add several instances of tree in the render. --- gfx/gl/instanceVertices.h | 66 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 20 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index dc28a4d..c2ddb1d 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -9,11 +9,9 @@ template class InstanceVertices { public: - InstanceVertices(size_t initialSize = 16) : data {allocBuffer(buffer, initialSize)}, next {} { } - - ~InstanceVertices() + InstanceVertices(size_t initialSize = 16) { - glUnmapNamedBuffer(buffer); + allocBuffer(initialSize); } class InstanceProxy { @@ -50,17 +48,20 @@ public: T & operator=(U && v) { + instances->map(); return instances->data[index] = std::forward(v); } T * get() { + instances->map(); return &instances->data[index]; } const T * get() const { + instances->map(); return &instances->data[index]; } T * @@ -95,22 +96,24 @@ public: InstanceProxy acquire(Params &&... params) { + map(); if (!unused.empty()) { auto idx = unused.back(); unused.pop_back(); - new (&data[idx]) T(std::forward(params)...); + new (data + idx) T(std::forward(params)...); return InstanceProxy {this, idx}; } - if (next >= data.size()) { - resize(data.size() * 2); + if (next >= capacity) { + resize(capacity * 2); } - new (&data[next]) T(std::forward(params)...); + new (data + next) T(std::forward(params)...); return InstanceProxy {this, next++}; } void release(const InstanceProxy & p) { + map(); data[p.index].~T(); unused.push_back(p.index); } @@ -118,42 +121,65 @@ public: const auto & bufferName() const { + unmap(); return buffer; } auto count() const { + unmap(); return next; } protected: friend InstanceProxy; - static std::span - allocBuffer(const glBuffer & buffer, std::size_t count) + void + allocBuffer(std::size_t newCapacity) { 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)); + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); - return {data, count}; + capacity = newCapacity; + data = nullptr; } void resize(size_t newCapacity) { - const auto maintain = std::min(newCapacity, data.size()); - const auto maintaind = static_cast(maintain); + const auto maintain = std::min(newCapacity, capacity); std::vector existing; + const auto maintaind = static_cast(maintain); 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()); + map(); + std::move(data, data + maintain, std::back_inserter(existing)); + allocBuffer(newCapacity); + map(); + std::move(existing.begin(), existing.begin() + maintaind, data); + capacity = newCapacity; + } + + void + map() const + { + if (!data) { + data = static_cast(glMapNamedBuffer(buffer, GL_READ_WRITE)); + } + } + + void + unmap() const + { + if (data) { + glUnmapNamedBuffer(buffer); + data = nullptr; + } } glBuffer buffer; - std::span data; - std::size_t next; + mutable T * data {}; + std::size_t capacity {}; + std::size_t next {}; std::vector unused; }; -- cgit v1.2.3 From b8889129c6c2b747ff771b711cfbbf91bca93bb6 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 22 Apr 2023 11:44:38 +0100 Subject: Fix the instancing maintenance --- gfx/gl/instanceVertices.h | 83 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 26 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index c2ddb1d..036b48e 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -32,37 +32,38 @@ public: InstanceProxy & operator=(InstanceProxy && other) { + if (instances) { + instances->release(*this); + } instances = std::exchange(other.instances, nullptr); index = other.index; + return *this; + } + template + T & + operator=(U && v) + { + return instances->at(index) = std::forward(v); } operator T &() { - return *get(); + return instances->at(index); } operator const T &() const { - return *get(); - } - template - T & - operator=(U && v) - { - instances->map(); - return instances->data[index] = std::forward(v); + return instances->at(index); } T * get() { - instances->map(); - return &instances->data[index]; + return &instances->at(index); } const T * get() const { - instances->map(); - return &instances->data[index]; + return &instances->at(index); } T * operator->() @@ -77,12 +78,12 @@ public: T & operator*() { - return *get(); + return instances->at(index); } const T & operator*() const { - return *get(); + return instances->at(index); } private: @@ -100,22 +101,16 @@ public: if (!unused.empty()) { auto idx = unused.back(); unused.pop_back(); - new (data + idx) T(std::forward(params)...); + index[idx] = next++; + new (&at(idx)) T(std::forward(params)...); return InstanceProxy {this, idx}; } if (next >= capacity) { resize(capacity * 2); } - new (data + next) T(std::forward(params)...); - return InstanceProxy {this, next++}; - } - - void - release(const InstanceProxy & p) - { - map(); - data[p.index].~T(); - unused.push_back(p.index); + index.emplace_back(next++); + new (data + index.back()) T(std::forward(params)...); + return InstanceProxy {this, index.size() - 1}; } const auto & @@ -135,6 +130,30 @@ public: protected: friend InstanceProxy; + void + release(const InstanceProxy & p) + { + // Destroy p's object + at(p.index).~T(); + if (next-- > index[p.index]) { + // Remember p.index is free index now + unused.push_back(p.index); + // Move last object into p's slot + new (&at(p.index)) T {std::move(data[next])}; + (data[next]).~T(); + for (auto & i : index) { + if (i == next) { + i = index[p.index]; + break; + } + } + index[p.index] = 100000; + } + else { + index.pop_back(); + } + } + void allocBuffer(std::size_t newCapacity) { @@ -160,6 +179,13 @@ protected: capacity = newCapacity; } + T & + at(size_t pindex) + { + map(); + return data[index[pindex]]; + } + void map() const { @@ -179,7 +205,12 @@ protected: 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; }; -- cgit v1.2.3 From c26d3ecfe9779e9cce13edfd981246c146c6f3a1 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 22 Apr 2023 12:14:26 +0100 Subject: Streamline the instancing maintenance --- gfx/gl/instanceVertices.h | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index 036b48e..cf4f0ae 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -25,7 +25,7 @@ public: ~InstanceProxy() { if (instances) { - instances->release(*this); + instances->release(index); } } @@ -33,7 +33,7 @@ public: operator=(InstanceProxy && other) { if (instances) { - instances->release(*this); + instances->release(index); } instances = std::exchange(other.instances, nullptr); index = other.index; @@ -87,8 +87,6 @@ public: } private: - friend InstanceVertices; - InstanceVertices * instances; std::size_t index; }; @@ -131,23 +129,19 @@ protected: friend InstanceProxy; void - release(const InstanceProxy & p) + release(const size_t pidx) { // Destroy p's object - at(p.index).~T(); - if (next-- > index[p.index]) { + at(pidx).~T(); + if (next-- > index[pidx]) { // Remember p.index is free index now - unused.push_back(p.index); + unused.push_back(pidx); // Move last object into p's slot - new (&at(p.index)) T {std::move(data[next])}; + new (&at(pidx)) T {std::move(data[next])}; (data[next]).~T(); - for (auto & i : index) { - if (i == next) { - i = index[p.index]; - break; - } - } - index[p.index] = 100000; + *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(); -- cgit v1.2.3 From bcd0e98739974248e6d29f72e8b281e21fd59657 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 22 Apr 2023 13:17:05 +0100 Subject: Test instancing automatic unmap when count is called, add some nodiscard --- gfx/gl/instanceVertices.h | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index cf4f0ae..edcaef4 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -14,7 +14,7 @@ public: allocBuffer(initialSize); } - class InstanceProxy { + 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} @@ -46,41 +46,41 @@ public: return instances->at(index) = std::forward(v); } + [[nodiscard]] operator T &() { return instances->at(index); } - operator const T &() const + [[nodiscard]] operator const T &() const { return instances->at(index); } - - T * + [[nodiscard]] T * get() { return &instances->at(index); } - const T * + [[nodiscard]] const T * get() const { return &instances->at(index); } - T * + [[nodiscard]] T * operator->() { return get(); } - const T * + [[nodiscard]] const T * operator->() const { return get(); } - T & + [[nodiscard]] T & operator*() { return instances->at(index); } - const T & + [[nodiscard]] const T & operator*() const { return instances->at(index); @@ -92,7 +92,7 @@ public: }; template - InstanceProxy + [[nodiscard]] InstanceProxy acquire(Params &&... params) { map(); @@ -111,14 +111,13 @@ public: return InstanceProxy {this, index.size() - 1}; } - const auto & + [[nodiscard]] const auto & bufferName() const { - unmap(); return buffer; } - auto + [[nodiscard]] auto count() const { unmap(); @@ -173,7 +172,7 @@ protected: capacity = newCapacity; } - T & + [[nodiscard]] T & at(size_t pindex) { map(); -- cgit v1.2.3 From 59d85f9b87fc82b52f2c7438e38768aeddc3cc87 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 22 Apr 2023 14:56:08 +0100 Subject: Don't fill the instances unused vector unnecessarily --- gfx/gl/instanceVertices.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index edcaef4..51bf4bc 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -132,19 +132,22 @@ protected: { // Destroy p's object at(pidx).~T(); - if (next-- > index[pidx]) { - // Remember p.index is free index now - unused.push_back(pidx); + if (--next != index[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); + *std::find_if(index.begin(), index.end(), [this](const auto & i) { + const auto n = &i - index.data(); + return i == next && std::find(unused.begin(), unused.end(), n) == unused.end(); + }) = index[pidx]; } - else { + if (pidx == index.size() - 1) { index.pop_back(); } + else { + // Remember p.index is free index now + unused.push_back(pidx); + } } void -- cgit v1.2.3 From 6737c8b4c5f804e23be38212295e789ff534822b Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 22 Apr 2023 15:19:40 +0100 Subject: Keep the instance unused vector sorted and binary search it --- gfx/gl/instanceVertices.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'gfx/gl/instanceVertices.h') diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index 51bf4bc..228020d 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -137,16 +137,15 @@ protected: new (&at(pidx)) T {std::move(data[next])}; (data[next]).~T(); *std::find_if(index.begin(), index.end(), [this](const auto & i) { - const auto n = &i - index.data(); - return i == next && std::find(unused.begin(), unused.end(), n) == unused.end(); + return i == next && !std::binary_search(unused.begin(), unused.end(), &i - index.data()); }) = index[pidx]; } if (pidx == index.size() - 1) { index.pop_back(); } else { - // Remember p.index is free index now - unused.push_back(pidx); + // Remember p.index is free index now, keeping it sorted + unused.insert(std::upper_bound(unused.begin(), unused.end(), pidx), pidx); } } -- cgit v1.2.3