diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index ac13f62277..b5164318ae 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -673,6 +673,7 @@ void BinManager::DistributeItems() { void BinManager::DistributeItems(size_t end) { undistributed_ = 0; + bool any = false; while (distributePos_ != end) { const size_t index = distributePos_; const BinItem &item = queue_[index]; @@ -701,7 +702,10 @@ void BinManager::DistributeItems(size_t end) { } } distributePos_ = index + 1 == QUEUED_PRIMS ? 0 : index + 1; + any = true; } + if (any) + distributed_.store(distributed_.load(std::memory_order_relaxed) + 1, std::memory_order_release); } void BinManager::ResetTiles() { @@ -784,17 +788,23 @@ void BinManager::MakeRoom() { // A drawing thread out of work stays a moment for more before its task ends: waking it again costs the // emulation thread a system call (about 15 us under WSL2, where lingering made God of War and Wipeout 4-8% // faster). Not while that thread waits for the drawing to finish. +// It looks at the tiles again only when distributed_ says there's new work: reading their heads and tails over +// and over took cache lines from the emulation thread, which writes the tails. void BinManager::LingerForWork(int start) { double until = time_now_d() + LINGER_SECONDS; while (!waitingForIdle_.load(std::memory_order_relaxed)) { + // Read before the look at the tiles, so work distributed during it isn't missed. + const uint32_t seen = distributed_.load(std::memory_order_acquire); if (ProcessTiles(start)) { until = time_now_d() + LINGER_SECONDS; continue; } - if (time_now_d() >= until) - break; - for (int i = 0; i < 64; ++i) - std::this_thread::yield(); + while (distributed_.load(std::memory_order_acquire) == seen && !waitingForIdle_.load(std::memory_order_relaxed)) { + if (time_now_d() >= until) + return; + for (int i = 0; i < 64; ++i) + std::this_thread::yield(); + } } } diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index 6cf2dfe277..0749026489 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -313,10 +313,11 @@ private: int tilesX_ = TILES_X; int tilesY_ = TILES_Y; struct Tile { - // Indices into queue_, as a ring: head_ is how many have been drawn, tail_ how many were pushed. - std::atomic head; - std::atomic tail; + // Indices into queue_, as a ring: head_ is how many have been drawn, tail_ how many were pushed. The + // drawing threads write head and busy, this thread tail, so they're on separate cache lines. + alignas(64) std::atomic head; std::atomic busy; + alignas(64) std::atomic tail; uint16_t items[QUEUED_PRIMS]; }; Tile *tiles_ = nullptr; @@ -325,6 +326,8 @@ private: // The tiles given work since the last flush, for the threads to look through. uint16_t activeTiles_[TILES_X * TILES_Y]; std::atomic activeCount_{ 0 }; + // Counts DistributeItems calls that gave the tiles work, for threads waiting for some (LingerForWork). + std::atomic distributed_{ 0 }; // The emulation thread is waiting for the drawing threads to finish (LingerForWork). std::atomic waitingForIdle_{ false }; bool tileActive_[TILES_X * TILES_Y]{};