diff --git a/aurora-main/lib/dolphin/gx/GXDispList.cpp b/aurora-main/lib/dolphin/gx/GXDispList.cpp index bf22212..e39cc10 100644 --- a/aurora-main/lib/dolphin/gx/GXDispList.cpp +++ b/aurora-main/lib/dolphin/gx/GXDispList.cpp @@ -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(data), nbytes, true); + aurora::gx::fifo::process_all(static_cast(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(data), nbytes, false); + aurora::gx::fifo::process_all(static_cast(data), nbytes, false); } } diff --git a/aurora-main/lib/gx/fifo.cpp b/aurora-main/lib/gx/fifo.cpp index 9818084..37065b8 100644 --- a/aurora-main/lib/gx/fifo.cpp +++ b/aurora-main/lib/gx/fifo.cpp @@ -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; } diff --git a/aurora-main/lib/gx/fifo.hpp b/aurora-main/lib/gx/fifo.hpp index b01ea45..08110dc 100644 --- a/aurora-main/lib/gx/fifo.hpp +++ b/aurora-main/lib/gx/fifo.hpp @@ -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(); diff --git a/aurora-main/tests/gx_test_common.hpp b/aurora-main/tests/gx_test_common.hpp index 7d37c52..fce6605 100644 --- a/aurora-main/tests/gx_test_common.hpp +++ b/aurora-main/tests/gx_test_common.hpp @@ -50,6 +50,9 @@ void reset_uniform_allocations() noexcept; const std::vector& 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 diff --git a/aurora-main/tests/gx_test_stubs.cpp b/aurora-main/tests/gx_test_stubs.cpp index 8ce5fb2..aaa2144 100644 --- a/aurora-main/tests/gx_test_stubs.cpp +++ b/aurora-main/tests/gx_test_stubs.cpp @@ -34,6 +34,9 @@ std::optional s_lastGxDraw; bool s_trackDrawCommands = false; bool s_useRealVertexFormatHelpers = false; std::deque> s_uniformAllocations; +std::optional s_stagingAdmissionsBeforeRefusal; +bool s_stagingRefused = false; +uint64_t s_stagingSplitCount = 0; } // namespace // --- aurora::g_config --- @@ -301,8 +304,23 @@ std::pair 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 get_render_target_size() noexcept { return s_renderTargetSize; } Vec2 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 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) --- diff --git a/aurora-main/tests/renderer_regression_test.cpp b/aurora-main/tests/renderer_regression_test.cpp index 5879d59..632cfdf 100644 --- a/aurora-main/tests/renderer_regression_test.cpp +++ b/aurora-main/tests/renderer_regression_test.cpp @@ -88,6 +88,31 @@ TEST_F(GXFifoTest, SingleExpandedPrimitiveCannotMergeWithTriangles) { EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector{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 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(list.size())); + else GXCallDisplayListLE(list.data(), static_cast(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{0x33, 0x33, 0x33})) + << "admitted=" << admitted << " bigEndian=" << bigEndian; + } +} + TEST(StagingMapping, RetiredCallbacksCannotPublishAnotherBuffersReadiness) { using namespace aurora::gfx; StagingMapState state;