mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
SoftGPU: Pick the tile size from the thread count
Every tile a triangle touches sets it up again, so larger tiles do less of that. The tile size is now picked at startup, as large as it can be with at least sixteen tiles per thread over the screen. With fewer, a scene whose drawing is concentrated on part of the screen leaves threads idle: on eight threads, 128x32 tiles (four per thread) took LocoRoco a third longer than 64x16, and God of War 5% longer. So eight threads keep 64x16, and four or fewer get larger tiles. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3c3025a087
commit
4bec735331
2 files changed
+64
-25
No files matched your search
+49
-21
@@ -164,8 +164,9 @@ BinManager::BinManager() {
|
||||
for (DrawBinItemsTask *&task : taskLists_[i].tasks)
|
||||
task = new DrawBinItemsTask(waitable_, this, i, taskStatus_[i]);
|
||||
}
|
||||
tiles_ = (Tile *)AllocateAlignedMemory(sizeof(Tile) * TILES_X * TILES_Y, 64);
|
||||
for (int i = 0; i < TILES_X * TILES_Y; ++i) {
|
||||
PickTileSize(maxInitTasks);
|
||||
tiles_ = (Tile *)AllocateAlignedMemory(sizeof(Tile) * tilesX_ * tilesY_, 64);
|
||||
for (int i = 0; i < tilesX_ * tilesY_; ++i) {
|
||||
tiles_[i].head = 0;
|
||||
tiles_[i].tail = 0;
|
||||
tiles_[i].busy = false;
|
||||
@@ -610,15 +611,15 @@ void BinManager::DistributeItems(size_t end) {
|
||||
while (distributePos_ != end) {
|
||||
const size_t index = distributePos_;
|
||||
const BinItem &item = queue_[index];
|
||||
const int tx1 = std::clamp(item.range.x1 / (SCREEN_SCALE_FACTOR * TILE_W), 0, TILES_X - 1);
|
||||
const int tx2 = std::clamp(item.range.x2 / (SCREEN_SCALE_FACTOR * TILE_W), 0, TILES_X - 1);
|
||||
const int ty1 = std::clamp(item.range.y1 / (SCREEN_SCALE_FACTOR * TILE_H), 0, TILES_Y - 1);
|
||||
const int ty2 = std::clamp(item.range.y2 / (SCREEN_SCALE_FACTOR * TILE_H), 0, TILES_Y - 1);
|
||||
const int tx1 = std::clamp(item.range.x1 >> tileShiftX_, 0, tilesX_ - 1);
|
||||
const int tx2 = std::clamp(item.range.x2 >> tileShiftX_, 0, tilesX_ - 1);
|
||||
const int ty1 = std::clamp(item.range.y1 >> tileShiftY_, 0, tilesY_ - 1);
|
||||
const int ty2 = std::clamp(item.range.y2 >> tileShiftY_, 0, tilesY_ - 1);
|
||||
// Set before any tile can draw it.
|
||||
itemRefs_[index].store((tx2 - tx1 + 1) * (ty2 - ty1 + 1), std::memory_order_relaxed);
|
||||
for (int ty = ty1; ty <= ty2; ++ty) {
|
||||
for (int tx = tx1; tx <= tx2; ++tx) {
|
||||
const int t = ty * TILES_X + tx;
|
||||
const int t = ty * tilesX_ + tx;
|
||||
Tile &tile = tiles_[t];
|
||||
const uint32_t tail = tile.tail.load(std::memory_order_relaxed);
|
||||
tile.items[tail % QUEUED_PRIMS] = (uint16_t)index;
|
||||
@@ -726,10 +727,10 @@ bool BinManager::ProcessTiles(int start) {
|
||||
if (tile.busy.load(std::memory_order_relaxed) || tile.busy.exchange(true, std::memory_order_acquire))
|
||||
continue;
|
||||
|
||||
const int tx = t % TILES_X, ty = t / TILES_X;
|
||||
const int tx = t % tilesX_, ty = t / tilesX_;
|
||||
const BinCoords tileRange{
|
||||
tx * TILE_W * SCREEN_SCALE_FACTOR, ty * TILE_H * SCREEN_SCALE_FACTOR,
|
||||
(tx + 1) * TILE_W * SCREEN_SCALE_FACTOR - 1, (ty + 1) * TILE_H * SCREEN_SCALE_FACTOR - 1,
|
||||
tx << tileShiftX_, ty << tileShiftY_,
|
||||
((tx + 1) << tileShiftX_) - 1, ((ty + 1) << tileShiftY_) - 1,
|
||||
};
|
||||
uint32_t head = tile.head.load(std::memory_order_relaxed);
|
||||
uint32_t tail;
|
||||
@@ -1002,20 +1003,20 @@ bool BinManager::NeedsOrder(const BinItem &item) {
|
||||
if (pastStride_ && item.range.x2 / SCREEN_SCALE_FACTOR >= (int)state.pixelID.cached.framebufStride)
|
||||
return true;
|
||||
|
||||
const int tx1 = std::clamp(item.range.x1 / (SCREEN_SCALE_FACTOR * TILE_W), 0, TILES_X - 1);
|
||||
const int tx2 = std::clamp(item.range.x2 / (SCREEN_SCALE_FACTOR * TILE_W), 0, TILES_X - 1);
|
||||
const int ty1 = std::clamp(item.range.y1 / (SCREEN_SCALE_FACTOR * TILE_H), 0, TILES_Y - 1);
|
||||
const int ty2 = std::clamp(item.range.y2 / (SCREEN_SCALE_FACTOR * TILE_H), 0, TILES_Y - 1);
|
||||
const int tx1 = std::clamp(item.range.x1 >> tileShiftX_, 0, tilesX_ - 1);
|
||||
const int tx2 = std::clamp(item.range.x2 >> tileShiftX_, 0, tilesX_ - 1);
|
||||
const int ty1 = std::clamp(item.range.y1 >> tileShiftY_, 0, tilesY_ - 1);
|
||||
const int ty2 = std::clamp(item.range.y2 >> tileShiftY_, 0, tilesY_ - 1);
|
||||
if (anyTileReads_) {
|
||||
for (int ty = ty1; ty <= ty2; ++ty) {
|
||||
for (int tx = tx1; tx <= tx2; ++tx) {
|
||||
if (tileReads_[ty * TILES_X + tx])
|
||||
if (tileReads_[ty * tilesX_ + tx])
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int ty = ty1; ty <= ty2; ++ty)
|
||||
memset(tileWrites_ + ty * TILES_X + tx1, 1, tx2 - tx1 + 1);
|
||||
memset(tileWrites_ + ty * tilesX_ + tx1, 1, tx2 - tx1 + 1);
|
||||
|
||||
if (!selfRender_)
|
||||
return false;
|
||||
@@ -1038,6 +1039,33 @@ bool BinManager::NeedsOrder(const BinItem &item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each extra tile a triangle touches sets it up again, so tiles are as large as they can be with plenty to
|
||||
// spare for every thread: at least sixteen per thread over a 480x272 screen. With fewer, a scene whose
|
||||
// drawing is concentrated on part of the screen leaves threads idle (LocoRoco took a third longer with four
|
||||
// per thread on eight threads).
|
||||
// The multiplier trades the repeated setup against the load balance, so the best one depends on what drawing
|
||||
// a pixel costs. When that gets cheaper, larger tiles can win: with faster span drawing, four per thread was
|
||||
// as fast in wall time and used less CPU. Measure again (Tools/headless_bench.py, wall and CPU time on
|
||||
// several games) after changes to the per-pixel cost.
|
||||
void BinManager::PickTileSize(int threads) {
|
||||
static const int sizes[][2] = { { 128, 32 }, { 64, 32 }, { 64, 16 } };
|
||||
int w = MIN_TILE_W, h = MIN_TILE_H;
|
||||
for (const auto &size : sizes) {
|
||||
const int tiles = ((480 + size[0] - 1) / size[0]) * ((272 + size[1] - 1) / size[1]);
|
||||
if (tiles >= 16 * threads) {
|
||||
w = size[0];
|
||||
h = size[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
tileW_ = w;
|
||||
tileH_ = h;
|
||||
tileShiftX_ = LeastSignificantSetBit((u32)(w * SCREEN_SCALE_FACTOR));
|
||||
tileShiftY_ = LeastSignificantSetBit((u32)(h * SCREEN_SCALE_FACTOR));
|
||||
tilesX_ = 1024 / w;
|
||||
tilesY_ = 1024 / h;
|
||||
}
|
||||
|
||||
void BinManager::ClearTileMarks() {
|
||||
memset(tileWrites_, 0, sizeof(tileWrites_));
|
||||
if (anyTileReads_)
|
||||
@@ -1063,23 +1091,23 @@ void BinManager::ForTargetTiles(const RasterizerState &state, const TexelRegion
|
||||
const int64_t off0 = std::max(a0, fbBase) - fbBase;
|
||||
const int64_t off1 = a1 - fbBase;
|
||||
const int64_t y0 = off0 / fbStrideBytes, y1 = off1 / fbStrideBytes;
|
||||
if (y0 >= TILES_Y * TILE_H)
|
||||
if (y0 >= tilesY_ * tileH_)
|
||||
break;
|
||||
int x0 = 0, x1 = 1023;
|
||||
if (y0 == y1) {
|
||||
x0 = (int)((off0 % fbStrideBytes) / bpp);
|
||||
x1 = (int)((off1 % fbStrideBytes) / bpp);
|
||||
}
|
||||
const int tx1 = std::min(x0 / TILE_W, TILES_X - 1), tx2 = std::min(x1 / TILE_W, TILES_X - 1);
|
||||
for (int64_t y = y0; y <= y1 && y < TILES_Y * TILE_H; ++y) {
|
||||
const int ty = (int)(y / TILE_H);
|
||||
const int tx1 = std::min(x0 / tileW_, tilesX_ - 1), tx2 = std::min(x1 / tileW_, tilesX_ - 1);
|
||||
for (int64_t y = y0; y <= y1 && y < tilesY_ * tileH_; ++y) {
|
||||
const int ty = (int)(y / tileH_);
|
||||
if (ty == lastTy && tx1 == lastTx1 && tx2 == lastTx2)
|
||||
continue;
|
||||
lastTy = ty;
|
||||
lastTx1 = tx1;
|
||||
lastTx2 = tx2;
|
||||
for (int tx = tx1; tx <= tx2; ++tx)
|
||||
f(ty * TILES_X + tx);
|
||||
f(ty * tilesX_ + tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +269,20 @@ private:
|
||||
|
||||
// With threads, queued items are binned into screen tiles. Any thread can take a tile with work and
|
||||
// draws its items in order; only one at a time, so each pixel still sees the primitives in order.
|
||||
static constexpr int TILE_W = 64;
|
||||
static constexpr int TILE_H = 16;
|
||||
static constexpr int TILES_X = 1024 / TILE_W;
|
||||
static constexpr int TILES_Y = 1024 / TILE_H;
|
||||
// Larger tiles set up fewer triangles more than once, smaller ones spread the work over more threads.
|
||||
// The size is picked at startup for the thread count (PickTileSize); the arrays fit the smallest.
|
||||
static constexpr int MIN_TILE_W = 64;
|
||||
static constexpr int MIN_TILE_H = 16;
|
||||
static constexpr int TILES_X = 1024 / MIN_TILE_W;
|
||||
static constexpr int TILES_Y = 1024 / MIN_TILE_H;
|
||||
// In subpixels.
|
||||
int tileShiftX_ = 0;
|
||||
int tileShiftY_ = 0;
|
||||
// Pixels.
|
||||
int tileW_ = MIN_TILE_W;
|
||||
int tileH_ = MIN_TILE_H;
|
||||
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<uint32_t> head;
|
||||
@@ -356,6 +366,7 @@ private:
|
||||
bool SelfReadRegion(const BinItem &item, TexelRegion ®ion);
|
||||
template <typename F>
|
||||
void ForTargetTiles(const Rasterizer::RasterizerState &state, const TexelRegion ®ion, F f);
|
||||
void PickTileSize(int threads);
|
||||
void ClearTileMarks();
|
||||
bool PendingWriteIn(const BinDirtyRange &range, uint32_t start, uint32_t stride, uint32_t w, uint32_t h);
|
||||
bool NeedsOrder(const BinItem &item);
|
||||
|
||||
Reference in new issue
Block a user