119 lines
4.0 KiB
C++
119 lines
4.0 KiB
C++
#include <plot/render/buffer.hpp>
|
|
|
|
#include <plot/gl/api.hpp>
|
|
|
|
#include <cstring>
|
|
|
|
namespace plot::render {
|
|
namespace detail {
|
|
|
|
bool InstanceStore::create(UploadMode mode, Status* st) noexcept {
|
|
// plot::gl::Buffer::create() already reports Unsupported while the entry
|
|
// points are unresolved and GLFailure when the driver hands out no name, so
|
|
// the wrapper only has to propagate its answer.
|
|
if (!buffer_.create(st)) {
|
|
return false;
|
|
}
|
|
capacity_ = 0;
|
|
slot_bytes_ = 0;
|
|
slot_ = kSlots - 1u; // the first upload takes slot 0
|
|
// The mapped path additionally needs its entry points, which are resolved
|
|
// best-effort (see api.hpp): without them the store would never be able to
|
|
// write anything, so it stays on the baseline path instead.
|
|
mode_ = mode == UploadMode::PersistentMapped && gl::glBufferStorage != nullptr &&
|
|
gl::glMapBufferRange != nullptr
|
|
? UploadMode::PersistentMapped
|
|
: UploadMode::Orphan;
|
|
return true;
|
|
}
|
|
|
|
void InstanceStore::release_storage() noexcept {
|
|
if (mapped_ != nullptr) {
|
|
// A persistent mapping outlives the frame that created it, so it has to be
|
|
// dropped before the buffer it belongs to is deleted.
|
|
buffer_.unmap();
|
|
mapped_ = nullptr;
|
|
}
|
|
for (gl::Fence& fence : fences_) {
|
|
fence.reset();
|
|
}
|
|
buffer_.reset();
|
|
slot_bytes_ = 0;
|
|
}
|
|
|
|
bool InstanceStore::grow_persistent(usize bytes) noexcept {
|
|
// Deleting the old buffer is safe while the GPU may still be reading it: GL
|
|
// keeps the storage alive until the last command that references it retires.
|
|
release_storage();
|
|
Status st;
|
|
if (!buffer_.create(&st)) {
|
|
mode_ = UploadMode::Orphan; // nothing left to draw with at all
|
|
capacity_ = 0;
|
|
return false;
|
|
}
|
|
const gl::GLbitfield flags =
|
|
gl::GL_MAP_WRITE_BIT | gl::GL_MAP_PERSISTENT_BIT | gl::GL_MAP_COHERENT_BIT;
|
|
buffer_.storage(kSlots * bytes, flags);
|
|
mapped_ = static_cast<u8*>(buffer_.map_range(0, kSlots * bytes, flags));
|
|
if (mapped_ == nullptr) {
|
|
// The driver refused the storage or the mapping. A frame that draws through
|
|
// the baseline path beats one that draws nothing, so re-create the empty
|
|
// buffer the orphan path needs and let the next upload use it.
|
|
release_storage();
|
|
mode_ = UploadMode::Orphan;
|
|
capacity_ = 0;
|
|
Status retry;
|
|
(void)buffer_.create(&retry); // a failed retry leaves nothing to draw with
|
|
return false;
|
|
}
|
|
slot_bytes_ = bytes;
|
|
slot_ = kSlots - 1u; // fresh storage: the first upload takes slot 0
|
|
++epoch_;
|
|
return true;
|
|
}
|
|
|
|
void InstanceStore::upload(const void* data, u32 count, usize instance_bytes) noexcept {
|
|
if (buffer_.id() == 0 || data == nullptr || count == 0 || instance_bytes == 0) {
|
|
return;
|
|
}
|
|
const usize bytes = usize(count) * instance_bytes;
|
|
if (mode_ == UploadMode::PersistentMapped && bytes > slot_bytes_ && !grow_persistent(bytes)) {
|
|
// The fallback already replaced the store with an orphan buffer.
|
|
}
|
|
|
|
if (mode_ == UploadMode::Orphan) {
|
|
if (count > capacity_) {
|
|
capacity_ = count; // grow only when the workload outgrows the store
|
|
}
|
|
buffer_.upload(nullptr, usize(capacity_) * instance_bytes, gl::GL_STREAM_DRAW);
|
|
buffer_.upload_sub(data, bytes, 0);
|
|
return;
|
|
}
|
|
|
|
// Advance the ring, then make sure the draw that last read this slot has
|
|
// retired. The non-blocking check is the common case; the bounded wait only
|
|
// costs anything when three frames' geometry is still in flight.
|
|
slot_ = (slot_ + 1u) % kSlots;
|
|
if (fences_[slot_]) {
|
|
if (!fences_[slot_].wait(0)) {
|
|
(void)fences_[slot_].wait(kFenceWaitNs);
|
|
}
|
|
fences_[slot_].reset();
|
|
}
|
|
// MAP_COHERENT storage needs no explicit flush, so this is the whole transfer.
|
|
std::memcpy(mapped_ + usize(slot_) * slot_bytes_, data, bytes);
|
|
++epoch_;
|
|
}
|
|
|
|
void InstanceStore::mark_drawn() noexcept {
|
|
if (mode_ != UploadMode::PersistentMapped || mapped_ == nullptr) {
|
|
return;
|
|
}
|
|
Status st;
|
|
fences_[slot_].reset();
|
|
(void)fences_[slot_].create(&st);
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace plot::render
|