Fix display lists dropping draws when the staging batch fills (#288)

* test: cover display lists that overflow the staging batch

GXCallDisplayList and GXCallDisplayListLE decode a list with a single
fifo::process() call. When a draw does not fit the staging batch,
process() stops at that draw, and the rest of the list is never drawn.

The test stubs can now admit N draws, refuse the next one as a full
batch would, and count the split that follows. The new test refuses
the middle or the last of three draws, in big- and little-endian
lists, and expects one split and the last draw's vertices.

Also stub aurora::window::set_force_aspect_16_9, called since #256,
so gx_fifo_tests links again.

Fails without the next commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: submit staging and continue when a display list overflows it

drain() already loops process() -> split_staging_batch() -> retry.
Move that loop into fifo::process_all() and use it for drain() and
both GXCallDisplayList variants, so a display list that fills the
staging batch submits it and draws the rest instead of dropping it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
therenow9
2026-10-09 16:48:24 -04:00
committed by GitHub
parent 54c608ef9c
commit 196b48528e
6 changed files with 73 additions and 8 deletions
+2 -2
View File
@@ -66,7 +66,7 @@ void GXCallDisplayList(const void* data, u32 nbytes) {
// Decode the display list immediately while its borrowed resources are valid.
aurora::gx::fifo::drain();
aurora::gx::fifo::process(static_cast<const u8*>(data), nbytes, true);
aurora::gx::fifo::process_all(static_cast<const u8*>(data), nbytes, true);
}
void GXCallDisplayListLE(const void* data, u32 nbytes) {
@@ -84,6 +84,6 @@ void GXCallDisplayListLE(const void* data, u32 nbytes) {
aurora::gx::fifo::drain();
// Process the display list through the command processor (little-endian)
aurora::gx::fifo::process(static_cast<const u8*>(data), nbytes, false);
aurora::gx::fifo::process_all(static_cast<const u8*>(data), nbytes, false);
}
}
+8 -4
View File
@@ -82,19 +82,23 @@ void drain() {
if (detail::sBufferSize == 0) {
return;
}
process_all(detail::sBufferData, detail::sBufferSize, true);
detail::sBufferSize = 0;
}
void process_all(const uint8_t* data, uint32_t size, bool bigEndian) {
uint32_t consumed = 0;
bool retried = false;
while (consumed < detail::sBufferSize) {
const auto count = process(detail::sBufferData + consumed, detail::sBufferSize - consumed, true);
while (consumed < size) {
const auto count = process(data + consumed, size - consumed, bigEndian);
if (count == 0 && retried)
throw gfx::StagingCapacityError("FIFO draw does not fit after capacity submission");
consumed += count;
if (consumed == detail::sBufferSize) break;
if (consumed == size) break;
// process returned with its renderer lock released. No recursive drain.
gfx::split_staging_batch();
retried = true;
}
detail::sBufferSize = 0;
}
const uint8_t* get_buffer_data() { return detail::sBufferData; }
+4
View File
@@ -81,6 +81,10 @@ bool in_display_list();
// Drain the internal FIFO buffer through the command processor
void drain();
// Decode a whole command stream, submitting the staging batch whenever a draw does not fit.
// process() alone stops at that draw, so a caller that ignores its count drops the rest.
void process_all(const uint8_t* data, uint32_t size, bool bigEndian);
// Internal buffer inspection
const uint8_t* get_buffer_data();
uint32_t get_buffer_size();
+4
View File
@@ -50,6 +50,9 @@ void reset_uniform_allocations() noexcept;
const std::vector<uint8_t>& uniform_allocation(size_t index) noexcept;
void use_draw_command_tracking(bool enabled) noexcept;
void use_real_vertex_format_helpers(bool enabled) noexcept;
// Admit this many draws, then refuse one as if the staging batch were full.
void refuse_staging_admission_after(uint32_t admissions) noexcept;
void reset_staging_capacity() noexcept;
} // namespace aurora::gfx::testing
class GXFifoTest : public ::testing::Test {
@@ -65,6 +68,7 @@ protected:
aurora::gfx::testing::reset_resolve_pass_records();
aurora::gfx::testing::reset_vertex_push_record();
aurora::gfx::testing::use_real_vertex_format_helpers(false);
aurora::gfx::testing::reset_staging_capacity();
}
// Copy the internal FIFO buffer contents and clear it
+30 -2
View File
@@ -34,6 +34,9 @@ std::optional<aurora::gx::DrawData> s_lastGxDraw;
bool s_trackDrawCommands = false;
bool s_useRealVertexFormatHelpers = false;
std::deque<std::vector<uint8_t>> s_uniformAllocations;
std::optional<uint32_t> s_stagingAdmissionsBeforeRefusal;
bool s_stagingRefused = false;
uint64_t s_stagingSplitCount = 0;
} // namespace
// --- aurora::g_config ---
@@ -301,8 +304,23 @@ std::pair<ByteBuffer, Range> copy_uniform(Range source) {
uint32_t align_uniform(uint32_t value) { return (value + 255u) & ~255u; }
uint64_t staging_uniform_bytes(uint64_t value) { return staging_padded(value, 256); }
uint64_t staging_storage_bytes(uint64_t value) { return staging_padded(value, 256); }
bool staging_has_space(const StagingSizes&) { return true; }
void split_staging_batch() { throw StagingCapacityError("Unexpected split in FIFO unit test"); }
bool staging_has_space(const StagingSizes&) {
if (!s_stagingAdmissionsBeforeRefusal) return true;
if (*s_stagingAdmissionsBeforeRefusal > 0) {
--*s_stagingAdmissionsBeforeRefusal;
return true;
}
// Refuse once, as a full batch would; the split that follows makes room again.
s_stagingAdmissionsBeforeRefusal.reset();
s_stagingRefused = true;
return false;
}
void split_staging_batch() {
if (!s_stagingRefused) throw StagingCapacityError("Unexpected split in FIFO unit test");
s_stagingRefused = false;
++s_stagingSplitCount;
}
uint64_t staging_split_count() noexcept { return s_stagingSplitCount; }
Vec2<uint32_t> get_render_target_size() noexcept { return s_renderTargetSize; }
Vec2<uint32_t> get_frame_buffer_size() noexcept { return s_renderTargetSize; }
@@ -345,6 +363,15 @@ void use_draw_command_tracking(bool enabled) noexcept {
void use_real_vertex_format_helpers(bool enabled) noexcept {
s_useRealVertexFormatHelpers = enabled;
}
void refuse_staging_admission_after(uint32_t admissions) noexcept {
s_stagingAdmissionsBeforeRefusal = admissions;
s_stagingRefused = false;
}
void reset_staging_capacity() noexcept {
s_stagingAdmissionsBeforeRefusal.reset();
s_stagingRefused = false;
s_stagingSplitCount = 0;
}
} // namespace aurora::gfx::testing
// --- Pipeline/draw command stubs ---
@@ -525,6 +552,7 @@ std::optional<TextureHandle> find_replacement(const GXTexObj_&) noexcept { retur
namespace aurora::window {
AuroraWindowSize get_window_size() { return {640, 480, 640, 480, 640, 480, 1.0f}; }
void set_frame_buffer_aspect_fit(bool) {}
void set_force_aspect_16_9(bool) {}
} // namespace aurora::window
// --- WebGPU C API stubs (prevent linker errors from wgpu:: destructors) ---
@@ -88,6 +88,31 @@ TEST_F(GXFifoTest, SingleExpandedPrimitiveCannotMergeWithTriangles) {
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector<u16>{0, 1, 2}));
}
TEST_F(GXFifoTest, DisplayListSubmitsStagingWhenADrawDoesNotFit) {
__GXSetDirtyState();
aurora::gx::fifo::clear_buffer();
g_gxState.lastVtxFmt = GX_VTXFMT0;
g_gxState.lastVtxSize = 1;
// admitted=1 refuses the middle draw; admitted=2 refuses the last, so only a replay of the refused draw pushes 0x33.
for (const u32 admitted : {1u, 2u}) for (const bool bigEndian : {true, false}) {
std::vector<u8> list;
for (const u8 fill : {0x11, 0x22, 0x33}) {
auto bytes = draw(GX_TRIANGLES, 3);
if (!bigEndian) std::swap(bytes[1], bytes[2]);
std::fill(bytes.begin() + 3, bytes.end(), fill);
list.insert(list.end(), bytes.begin(), bytes.end());
}
const auto splitsBefore = aurora::gfx::staging_split_count();
aurora::gfx::testing::refuse_staging_admission_after(admitted);
if (bigEndian) GXCallDisplayList(list.data(), static_cast<u32>(list.size()));
else GXCallDisplayListLE(list.data(), static_cast<u32>(list.size()));
EXPECT_EQ(aurora::gfx::staging_split_count() - splitsBefore, 1u)
<< "admitted=" << admitted << " bigEndian=" << bigEndian;
EXPECT_EQ(aurora::gfx::testing::last_pushed_vertices(), (std::vector<u8>{0x33, 0x33, 0x33}))
<< "admitted=" << admitted << " bigEndian=" << bigEndian;
}
}
TEST(StagingMapping, RetiredCallbacksCannotPublishAnotherBuffersReadiness) {
using namespace aurora::gfx;
StagingMapState state;