mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-11 19:00:39 -04:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user