fix(render): bind identity into the epoch, titles into reserved strips, one layout per frame
Four review findings on the Task 7 renderer: - InstanceStore::epoch() is derived from gl::Buffer::generation() -- advanced by every allocation, deletion and move -- plus the slot the instances were written to, so the orphan fallback of grow_persistent() can no longer leave the vertex array's attribute pointers bound to a deleted buffer: the epoch reports the new identity and upload() re-points them before the draw. - PlotLayout reserves y_title_area/x_title_area outside axes (sized from the text metrics) and draw_titles() draws in them, so a title can no longer land on the data, whatever the envelope does. - draw() computes plot.layout() once and hands it to the helpers, instead of six times per frame, each of which rebuilt the ticks and the grid. - A refused fence is no longer dropped: mark_drawn() falls back to the orphan path and reports the failure rather than rotating through an unsynchronised slot. New smoke cases force both refusals through the GL entry points (glMapBufferRange, glFenceSync) and assert the pixels plus the live transfer mode; the title case asserts the title colour is absent from the bands inside axes and present in both strips. All three fail against the pre-fix sources.
This commit is contained in:
1 parent
3555ab63d7
commit
8f45108559
11 files changed
+492
-70
No files matched your search
+22
-6
@@ -58,7 +58,10 @@ bool InstanceStore::grow_persistent(usize bytes) noexcept {
|
||||
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.
|
||||
// buffer the orphan path needs and let the next upload use it. The vertex
|
||||
// array's attribute pointers went out with the buffer they captured, and the
|
||||
// epoch reports it (`gl::Buffer::generation()`), which is what re-points them
|
||||
// at this buffer before the draw.
|
||||
release_storage();
|
||||
mode_ = UploadMode::Orphan;
|
||||
capacity_ = 0;
|
||||
@@ -68,7 +71,6 @@ bool InstanceStore::grow_persistent(usize bytes) noexcept {
|
||||
}
|
||||
slot_bytes_ = bytes;
|
||||
slot_ = kSlots - 1u; // fresh storage: the first upload takes slot 0
|
||||
++epoch_;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,16 +104,30 @@ void InstanceStore::upload(const void* data, u32 count, usize instance_bytes) no
|
||||
}
|
||||
// MAP_COHERENT storage needs no explicit flush, so this is the whole transfer.
|
||||
std::memcpy(mapped_ + usize(slot_) * slot_bytes_, data, bytes);
|
||||
++epoch_;
|
||||
++slot_epoch_;
|
||||
}
|
||||
|
||||
void InstanceStore::mark_drawn() noexcept {
|
||||
bool InstanceStore::mark_drawn() noexcept {
|
||||
if (mode_ != UploadMode::PersistentMapped || mapped_ == nullptr) {
|
||||
return;
|
||||
return true; // nothing reads the store after the draw
|
||||
}
|
||||
Status st;
|
||||
fences_[slot_].reset();
|
||||
(void)fences_[slot_].create(&st);
|
||||
if (fences_[slot_].create(&st)) {
|
||||
return true;
|
||||
}
|
||||
// A slot whose fence the driver refused can never be reused safely: the next
|
||||
// upload would memcpy into storage the GPU may still be reading, which is the
|
||||
// tearing the ring exists to prevent. So the ring is abandoned rather than
|
||||
// rotated through -- the map failure's treatment, one step later -- and the
|
||||
// fresh buffer the orphan path needs is created. Deleting the mapped buffer is
|
||||
// safe while the draw still reads it: GL keeps the storage alive until then.
|
||||
release_storage();
|
||||
mode_ = UploadMode::Orphan;
|
||||
capacity_ = 0;
|
||||
Status retry;
|
||||
(void)buffer_.create(&retry); // a failed retry leaves nothing to draw with
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
Reference in new issue
Block a user