diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 5e0c59b63e..3707034100 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -26,6 +26,7 @@ #include "Common/Thread/ThreadManager.h" #include "Common/Data/Text/StringWriter.h" #include "Common/TimeUtil.h" +#include "Core/Config.h" #include "Core/MemMap.h" #include "Core/System.h" #include "GPU/Common/TextureDecoder.h" @@ -342,7 +343,7 @@ void BinManager::UpdateState() { void BinManager::CacheStep(BinItem &item) { item.cacheStepped = true; const RasterizerState &state = states_[item.stateIndex]; - const bool stale = texCache_.Access(item, state); + const bool reads = texCache_.Prepare(item, state); bool selfWrite = false; if (state.serial != overlapSerial_) { // Whether this state's texture can be in its color or depth buffer at all: then each primitive is @@ -354,24 +355,190 @@ void BinManager::CacheStep(BinItem &item) { constexpr uint32_t mirrorMask = 0x041FFFFF; const DrawingCoords tl = TransformUnit::ScreenToDrawing(item.range.x1, item.range.y1); const DrawingCoords br = TransformUnit::ScreenToDrawing(item.range.x2, item.range.y2); + const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; + const bool depthWrites = state.pixelID.depthWrite || (state.pixelID.clearMode && state.pixelID.DepthClear()); + if (reads && mayOverlap_) { + selfWrite = texCache_.FootprintReads(drawTargetAddr_ & mirrorMask, bpp, state.pixelID.cached.framebufStride, tl.x, tl.y, br.x, br.y); + if (!selfWrite && depthWrites) + selfWrite = texCache_.FootprintReads(gstate.getDepthBufAddress() & mirrorMask, 2, state.pixelID.cached.depthbufStride, tl.x, tl.y, br.x, br.y); + } + serialDraw_ = selfWrite && CanDrawSerial(item, state); + bool stale = false; + if (serialDraw_) { + texCache_.Defer(); + serialFb_ = drawTargetAddr_ & mirrorMask; + } else if (reads) { + stale = texCache_.Access(); + } auto writes = [&](uint32_t base, uint32_t bpp, uint32_t stride) { base &= mirrorMask; const uint32_t start = base + ((uint32_t)tl.y * stride + (uint32_t)tl.x) * bpp; const uint32_t end = base + ((uint32_t)br.y * stride + (uint32_t)br.x + 1) * bpp; - if (mayOverlap_ && !selfWrite) - selfWrite = texCache_.FootprintReads(base, bpp, stride, tl.x, tl.y, br.x, br.y); if (texCache_.MayHold(start, end)) texCache_.BeforeWrite(start, end); }; if (!texCache_.IsEmpty()) { - const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; writes(drawTargetAddr_, bpp, state.pixelID.cached.framebufStride); - if (state.pixelID.depthWrite || (state.pixelID.clearMode && state.pixelID.DepthClear())) + if (depthWrites) writes(gstate.getDepthBufAddress(), 2, state.pixelID.cached.depthbufStride); } cacheImage_ = stale || selfWrite; } +// The serial draw's samplers: a sample's texels go through the texture cache, then the sampler reads them +// from its copies of the levels. +static struct { + TexCache *cache; + Sampler::NearestFunc nearest; + Sampler::LinearFunc linear; + Sampler::FetchFunc fetch; + // Forced by the texture filtering setting. + bool nearestIsLinear, linearIsNearest; +} serialSampler; + +static void SerialReads(float s, float t, int level, int levelFrac, bool linear, const SamplerID &samplerID) { + int u[8], v[8], levels[8]; + const int n = Sampler::SampleTexels(s, t, level, levelFrac, linear, samplerID, u, v, levels); + for (int i = 0; i < n; ++i) + serialSampler.cache->SerialRead(levels[i], u[i], v[i]); +} + +static Vec4IntResult SOFTRAST_CALL SerialNearest(float s, float t, Vec4IntArg prim_color, const u8 *const *tptr, const uint16_t *bufw, int level, int levelFrac, const SamplerID &samplerID) { + SerialReads(s, t, level, levelFrac, serialSampler.nearestIsLinear, samplerID); + return serialSampler.nearest(s, t, prim_color, tptr, bufw, level, levelFrac, samplerID); +} + +static Vec4IntResult SOFTRAST_CALL SerialLinear(float s, float t, Vec4IntArg prim_color, const u8 *const *tptr, const uint16_t *bufw, int level, int levelFrac, const SamplerID &samplerID) { + SerialReads(s, t, level, levelFrac, !serialSampler.linearIsNearest, samplerID); + return serialSampler.linear(s, t, prim_color, tptr, bufw, level, levelFrac, samplerID); +} + +static Vec4IntResult SOFTRAST_CALL SerialFetch(int u, int v, const u8 *tptr, int bufw, int level, const SamplerID &samplerID) { + serialSampler.cache->SerialRead(level, u, v); + return serialSampler.fetch(u, v, tptr, bufw, level, samplerID); +} + +bool BinManager::CanDrawSerial(const BinItem &item, const RasterizerState &state) { + if (item.type != BinItemType::SPRITE && item.type != BinItemType::RECT) + return false; + const GETextureFormat fmt = state.samplerID.TexFmt(); + return fmt != GE_TFMT_DXT1 && fmt != GE_TFMT_DXT3 && fmt != GE_TFMT_DXT5; +} + +// A primitive that textures from what it draws, as the GE draws it (ppsspp-re's geprobe exp204, exp205 and +// exp81): a sprite's rows top down, each left to right, its texture lines loaded as samples first read +// them, and its pixels reaching memory a 16-byte block at a time, a pixel after the next block starts (four +// with bilinear filtering, whose reads run further ahead). +void BinManager::DrawSerial(const BinItem &item, const RasterizerState &state) { + cacheState_ = item.type == BinItemType::RECT ? OptimizeFlatRasterizerState(state, item.v1) : state; + texCache_.BeginSerial(cacheState_); + serialSampler.cache = &texCache_; + serialSampler.nearest = cacheState_.nearest; + serialSampler.linear = cacheState_.linear; + serialSampler.fetch = Sampler::GetFetchFunc(cacheState_.samplerID, nullptr); + serialSampler.nearestIsLinear = g_Config.iTexFiltering == TEX_FILTER_FORCE_LINEAR; + serialSampler.linearIsNearest = g_Config.iTexFiltering == TEX_FILTER_FORCE_NEAREST; + cacheState_.nearest = &SerialNearest; + cacheState_.linear = &SerialLinear; + cacheState_.linearQuad = nullptr; + cacheState_.fetch = &SerialFetch; + + // The sprite's pixels (as DrawRectangle finds them), within the range. + const int x1 = std::min(item.v0.screenpos.x, item.v1.screenpos.x), x2 = std::max(item.v0.screenpos.x, item.v1.screenpos.x); + const int y1 = std::min(item.v0.screenpos.y, item.v1.screenpos.y), y2 = std::max(item.v0.screenpos.y, item.v1.screenpos.y); + const int px0 = std::max((x1 + 6) / SCREEN_SCALE_FACTOR, item.range.x1 / SCREEN_SCALE_FACTOR); + const int px1 = std::min((x2 + 7) / SCREEN_SCALE_FACTOR - 1, item.range.x2 / SCREEN_SCALE_FACTOR); + const int py0 = std::max((y1 + 7) / SCREEN_SCALE_FACTOR, item.range.y1 / SCREEN_SCALE_FACTOR); + const int py1 = std::min((y2 + 7) / SCREEN_SCALE_FACTOR - 1, item.range.y2 / SCREEN_SCALE_FACTOR); + + const int bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; + const int stride = state.pixelID.cached.framebufStride; + const int64_t latency = state.minFilt || state.magFilt ? 4 : 1; + // Rows that start and end inside 64-byte groups and span at most four (exp207-exp209): there the first + // block of the row above reads as it was before this primitive to a row's first pixel (though in its own + // row it reached memory as usual, exp204), and the last block of the row above reaches memory a block + // into the row (exp174). + const int64_t rowStart = (int64_t)serialFb_ + (int64_t)px0 * bpp, rowEnd = (int64_t)serialFb_ + (int64_t)(px1 + 1) * bpp; + const bool holdFirst = (rowStart & 63) != 0 && (rowEnd & 63) != 0 && ((rowEnd + 63) >> 6) - (rowStart >> 6) <= 4; + auto draw = [&](int xa, int xb, int y) { + BinCoords r{ xa * SCREEN_SCALE_FACTOR, y * SCREEN_SCALE_FACTOR, xb * SCREEN_SCALE_FACTOR + SCREEN_SCALE_FACTOR - 1, y * SCREEN_SCALE_FACTOR + SCREEN_SCALE_FACTOR - 1 }; + DrawBinItem(item, r.Intersect(item.range), cacheState_); + }; + // The pixels drawn so far, and when each block in the output buffer (as texCache_ holds them) reaches + // memory, never yet for the one being drawn. + constexpr int64_t NOT_YET = INT64_MAX; + int64_t drawn = 0; + int64_t visibleAt[TexCache::MAX_UNFLUSHED]; + int count = 0; + uint32_t firstAddr = 0; + uint8_t firstOld[16]; + bool haveFirst = false; + auto reachMemory = [&]() { + for (int k = 0; k < count;) { + if (visibleAt[k] <= drawn) { + texCache_.RemoveUnflushed(k); + memmove(visibleAt + k, visibleAt + k + 1, sizeof(visibleAt[0]) * (count - k - 1)); + count--; + } else { + k++; + } + } + }; + for (int y = py0; y <= py1; ++y) { + const int64_t rowAddr = (int64_t)serialFb_ + (int64_t)y * stride * bpp; + int x = px0; + while (x <= px1) { + const uint32_t blockAddr = (uint32_t)(rowAddr + x * bpp) & ~15; + const int blockStart = (int)(((int64_t)blockAddr - rowAddr) / bpp); + const int hi = std::min(px1, blockStart + 16 / bpp - 1); + // The previous block is finished now. + if (count > 0 && visibleAt[count - 1] == NOT_YET) + visibleAt[count - 1] = drawn + (x == px0 && holdFirst ? std::max(latency, 16 / bpp) : latency); + // Room for two more (a row's held first block and this one). A sprite a pixel or two wide can outrun + // its blocks reaching memory: then the oldest go now. + while (count >= TexCache::MAX_UNFLUSHED - 1) { + drawn = std::max(drawn, *std::min_element(visibleAt, visibleAt + count)); + reachMemory(); + } + uint8_t old[16]{}; + if (Memory::IsValidRange(blockAddr, 16)) + memcpy(old, Memory::GetPointerUnchecked(blockAddr), 16); + // The row above's first block again, but not the primitive's first row's (exp206). + if (x == px0 && holdFirst && y > py0) { + if (haveFirst) { + texCache_.PushUnflushed(firstAddr, firstOld); + visibleAt[count++] = drawn + 1; + } + firstAddr = blockAddr; + memcpy(firstOld, old, 16); + haveFirst = true; + } + texCache_.PushUnflushed(blockAddr, old); + visibleAt[count++] = NOT_YET; + // Its pixels, in runs between the times blocks reach memory. + while (x <= hi) { + reachMemory(); + int64_t next = NOT_YET; + for (int k = 0; k < count; ++k) + next = std::min(next, visibleAt[k]); + const int end = next != NOT_YET ? (int)std::min(hi, x + next - drawn - 1) : hi; + draw(x, end, y); + drawn += end - x + 1; + x = end + 1; + } + } + } +} + +void BinManager::DrawStepped(const BinItem &item, const RasterizerState &state) { + if (serialDraw_) { + serialDraw_ = false; + DrawSerial(item, state); + } else { + DrawSplit(item, CacheView(item, state)); + } +} + // The state to draw the primitive last stepped with: with its texture as the GE's texture cache gives it, when // that differs from memory or the primitive draws over what it reads. const RasterizerState &BinManager::CacheView(const BinItem &item, const RasterizerState &state) { @@ -639,11 +806,13 @@ void BinManager::Drain() { while (!queue_.Empty()) { BinItem &item = queue_[queue_.head_]; const RasterizerState &state = states_[item.stateIndex]; - if (!item.cacheStepped) + if (!item.cacheStepped) { CacheStep(item); - else + } else { cacheImage_ = false; - DrawSplit(item, CacheView(item, state)); + serialDraw_ = false; + } + DrawStepped(item, state); queue_.SkipNext(); } distributePos_ = queue_.tail_; @@ -1283,7 +1452,7 @@ void BinManager::DrainDependent() { const RasterizerState &state = states_[item.stateIndex]; if (!item.cacheStepped) CacheStep(item); - DrawSplit(item, CacheView(item, state)); + DrawStepped(item, state); queue_.Reset(); distributePos_ = 0; undistributed_ = 0; diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index ae4302b143..6bf9e2f7f3 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -349,6 +349,10 @@ private: // cacheState_, whose texture is memory with the cached lines over it. TexCache texCache_; bool cacheImage_ = false; + // It draws over what it reads, and is drawn in the GE's pixel order (DrawSerial) to the framebuffer at + // serialFb_ (stride and bytes per pixel as for its state). + bool serialDraw_ = false; + uint32_t serialFb_ = 0; uint32_t stateSerial_ = 0; uint32_t overlapSerial_ = 0; bool mayOverlap_ = false; @@ -380,6 +384,10 @@ private: void CacheStep(BinItem &item); bool TextureOverlapsTarget(const Rasterizer::RasterizerState &state) const; const Rasterizer::RasterizerState &CacheView(const BinItem &item, const Rasterizer::RasterizerState &state); + static bool CanDrawSerial(const BinItem &item, const Rasterizer::RasterizerState &state); + void DrawSerial(const BinItem &item, const Rasterizer::RasterizerState &state); + // The primitive last stepped through the texture cache, as that says. + void DrawStepped(const BinItem &item, const Rasterizer::RasterizerState &state); void OptimizePendingStates(uint16_t first, uint16_t last); void PushState(); void DeswizzleMirrorTextures(Rasterizer::RasterizerState &state); diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index 6a8c3c4bc7..76a53e92bd 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -75,6 +75,8 @@ struct RasterizerState { Sampler::NearestFunc nearest; // Four pixels at once, where linear is the C++ fallback. Sampler::LinearQuadFunc linearQuad = nullptr; + // Replaces the fetch function DrawSprite gets for samplerID (the texture cache simulation's, BinManager::DrawSerial). + Sampler::FetchFunc fetch = nullptr; uint32_t texaddr[8]{}; uint16_t texbufw[8]{}; const u8 *texptr[8]{}; @@ -116,6 +118,8 @@ void CalculateRasterStateFlags(RasterizerState *state, const VertexData &v0); void CalculateRasterStateFlags(RasterizerState *state, const VertexData &v0, const VertexData &v1, bool forceFlat); void CalculateRasterStateFlags(RasterizerState *state, const VertexData &v0, const VertexData &v1, const VertexData &v2); bool OptimizeRasterState(RasterizerState *state); +// The state DrawRectangle draws a flat sprite with (from its last vertex). +RasterizerState OptimizeFlatRasterizerState(const RasterizerState &origState, const VertexData &v1); // Draws a triangle if its vertices are specified in counter-clockwise order void DrawTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const BinCoords &range, const RasterizerState &state); diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index d42f1057ad..0fbaaee2e8 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -342,7 +342,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran uint16_t texbufw = state.texbufw[0]; // We won't flush, since we compile all samplers together. - Sampler::FetchFunc fetchFunc = Sampler::GetFetchFunc(state.samplerID, nullptr); + Sampler::FetchFunc fetchFunc = state.fetch ? state.fetch : Sampler::GetFetchFunc(state.samplerID, nullptr); _dbg_assert_msg_(fetchFunc != nullptr, "Failed to get precompiled fetch func"); auto &pixelID = state.pixelID; auto &samplerID = state.samplerID; diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index 068a7879e1..05bf1b423a 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -1181,6 +1181,29 @@ static LinearQuadFunc PickLinearQuad(const SamplerID &id) { } } +int SampleTexels(float s, float t, int level, int levelFrac, bool linear, const SamplerID &id, int u[8], int v[8], int levels[8]) { + int n = 0; + for (int l = level; l <= level + (levelFrac ? 1 : 0); ++l) { + const int w = id.cached.sizes[l].w, h = id.cached.sizes[l].h; + if (linear) { + int u0, u1, v0, v1, fracU, fracV; + TexelPairT(s, w, id.clampS, u0, u1, fracU); + TexelPairT(t, h, id.clampT, v0, v1, fracV); + const int us[4] = { u0, u1, u0, u1 }, vs[4] = { v0, v0, v1, v1 }; + for (int i = 0; i < 4; ++i) { + u[n] = us[i]; + v[n] = vs[i]; + levels[n] = l; + ++n; + } + } else { + GetTexelCoordinates(l, s, t, u[n], v[n], id); + levels[n++] = l; + } + } + return n; +} + LinearQuadFunc GetLinearQuadFunc(const SamplerID &id, LinearFunc linear) { if (linear != GetLinearFallback(id)) return nullptr; diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h index b4124559a7..df7465257d 100644 --- a/GPU/Software/Sampler.h +++ b/GPU/Software/Sampler.h @@ -53,6 +53,10 @@ typedef void (SOFTRAST_CALL *LinearQuadFunc)(const float *s, const float *t, con // Only for the samplers without a JIT (linear is what GetLinearFunc returned), nullptr otherwise. LinearQuadFunc GetLinearQuadFunc(const SamplerID &id, LinearFunc linear); +// The texels a sample reads, after clamping or wrapping: nearest or the bilinear 2x2, at level and (with +// levelFrac) level + 1. Returns how many (at most 8). +int SampleTexels(float s, float t, int level, int levelFrac, bool linear, const SamplerID &id, int u[8], int v[8], int levels[8]); + void Init(); void FlushJit(); int JitClearGeneration(); diff --git a/GPU/Software/TexCache.cpp b/GPU/Software/TexCache.cpp index 340d85bbd2..6a99b25cb6 100644 --- a/GPU/Software/TexCache.cpp +++ b/GPU/Software/TexCache.cpp @@ -19,6 +19,7 @@ #include #include +#include "Common/Log.h" #include "Core/Config.h" #include "Core/MemMap.h" #include "GPU/Common/TextureDecoder.h" @@ -193,7 +194,11 @@ void TexCache::Touch(int bits, int level, int col, int band, const Source &src) } return; } - // A miss loads from memory, over the least recently used way. + Load(bits, level, col, band, src); +} + +// A miss loads from memory, over the least recently used way. +TexCache::Line *TexCache::Load(int bits, int level, int col, int band, const Source &src) { const int s = SetIndex(bits, col, band); Line *set = lines_[s]; int way = 0; @@ -220,6 +225,7 @@ void TexCache::Touch(int bits, int level, int col, int band, const Source &src) MemoryRange(src, col, band, start, end); uncapturedLo_ = std::min(uncapturedLo_, start); uncapturedHi_ = std::max(uncapturedHi_, end); + return victim; } bool TexCache::Config::SameSources(const Config &o) const { @@ -455,7 +461,7 @@ void TexCache::ReplayLog() { loggedSerial_ = 0; } -bool TexCache::Access(const BinItem &item, const Rasterizer::RasterizerState &state) { +bool TexCache::Prepare(const BinItem &item, const Rasterizer::RasterizerState &state) { delivered_.clear(); footprintValid_ = false; currentValid_ = false; @@ -468,6 +474,12 @@ bool TexCache::Access(const BinItem &item, const Rasterizer::RasterizerState &st if (!configOk_ || !MakeBounds(item, state, bounds_)) return false; currentValid_ = true; + return true; +} + +bool TexCache::Access() { + if (!currentValid_) + return false; if (coherent_) { if (!epochStarted_) { epoch_ = current_; @@ -562,6 +574,12 @@ void TexCache::Image(Rasterizer::RasterizerState &state) { DecodedImage(state); return; } + CopyLevels(state); + for (const Delivered &line : delivered_) + PutLine(line.level, line.col, line.band, line.data); +} + +void TexCache::CopyLevels(Rasterizer::RasterizerState &state) { for (int level = 0; level <= state.maxTexLevel; ++level) { const Source &cur = current_.src[level]; const int w = state.samplerID.cached.sizes[level].w; @@ -572,27 +590,101 @@ void TexCache::Image(Rasterizer::RasterizerState &state) { std::vector &img = images_[level]; img.resize(bytes); CopyFrom(cur, cur.addr, img.data(), bytes); - for (const Delivered &line : delivered_) { - if (line.level != level) - continue; - uint32_t start, end; - LineRange(cur, line.col, line.band, start, end); - if (cur.swizzled) { - const uint32_t off = start - cur.addr; - if (off + 128 <= bytes) - memcpy(img.data() + off, line.data, 128); - } else { - for (int r = 0; r < 8; ++r) { - const uint32_t off = start - cur.addr + r * cur.strideBytes; - if (off + 16 <= bytes) - memcpy(img.data() + off, line.data + r * 16, 16); - } - } - } state.texptr[level] = img.data(); } } +void TexCache::PutLine(int level, int col, int band, const uint8_t data[128]) { + const Source &cur = current_.src[level]; + std::vector &img = images_[level]; + uint32_t start, end; + LineRange(cur, col, band, start, end); + if (cur.swizzled) { + const uint32_t off = start - cur.addr; + if (off + 128 <= img.size()) + memcpy(img.data() + off, data, 128); + return; + } + for (int r = 0; r < 8; ++r) { + const uint32_t off = start - cur.addr + r * cur.strideBytes; + if (off + 16 <= img.size()) + memcpy(img.data() + off, data + r * 16, 16); + } +} + +void TexCache::BeginSerial(Rasterizer::RasterizerState &state) { + CopyLevels(state); + unflushedCount_ = 0; + // A new generation: no line is in the copies yet. + if (++serialGen_ == 0) { + for (auto &set : lines_) { + for (Line &line : set) + line.viewGen = 0; + } + serialGen_ = 1; + } +} + +void TexCache::Defer() { + ReplayLog(); + delivered_.clear(); +} + +void TexCache::PushUnflushed(uint32_t addr, const uint8_t old[16]) { + _dbg_assert_(unflushedCount_ < MAX_UNFLUSHED); + Unflushed &u = unflushed_[unflushedCount_++]; + u.addr = MaskAddress(addr); + memcpy(u.old, old, 16); +} + +void TexCache::RemoveUnflushed(int index) { + if (index < 0 || index >= unflushedCount_) + return; + memmove(unflushed_ + index, unflushed_ + index + 1, sizeof(Unflushed) * (unflushedCount_ - index - 1)); + unflushedCount_--; +} + +void TexCache::SerialRead(int level, int u, int v) { + const int col = u * current_.bits / 128, band = v >> 3; + Line *line = Find(current_.bits, level, col, band); + if (line) { + line->lru = ++clock_; + if (line->viewGen != serialGen_) { + uint8_t data[128]; + Bytes(*line, data); + PutLine(level, col, band, data); + line->viewGen = serialGen_; + } + return; + } + // Loaded now: memory as it is, but for the pixels still in the output block. + const Source &src = current_.src[level]; + line = Load(current_.bits, level, col, band, src); + ReadLine(src, col, band, line->data); + uint32_t start, end; + LineRange(src, col, band, start, end); + for (int k = 0; k < unflushedCount_; ++k) { + const Unflushed &u = unflushed_[k]; + if (end <= u.addr || start >= u.addr + 16) + continue; + auto patch = [&](uint32_t addr, uint8_t *dst, uint32_t n) { + const uint32_t lo = std::max(addr, u.addr), hi = std::min(addr + n, u.addr + 16); + if (lo < hi) + memcpy(dst + (lo - addr), u.old + (lo - u.addr), hi - lo); + }; + if (src.swizzled) { + patch(start, line->data, 128); + } else { + for (int r = 0; r < 8; ++r) + patch(start + r * src.strideBytes, line->data + r * 16, 16); + } + } + line->captured = true; + coherent_ = false; + PutLine(level, col, band, line->data); + line->viewGen = serialGen_; +} + // The cache holds DXT decoded, so a stale line is 8888 texels: the levels are decoded to 8888, the lines laid // over that, and the state switched to sample 8888. void TexCache::DecodedImage(Rasterizer::RasterizerState &state) { diff --git a/GPU/Software/TexCache.h b/GPU/Software/TexCache.h index 22388d327b..fd894b468c 100644 --- a/GPU/Software/TexCache.h +++ b/GPU/Software/TexCache.h @@ -45,9 +45,23 @@ public: // TEXFLUSH. void Clear(); - // Simulates the reads of a textured primitive. Returns true when it would read cached bytes that differ + // What a primitive reads (for FootprintReads, Access or Defer). False when it reads nothing. + bool Prepare(const BinItem &item, const Rasterizer::RasterizerState &state); + // Simulates the reads of the prepared primitive. Returns true when it would read cached bytes that differ // from what its texture holds in memory now (then draw it from Image()). - bool Access(const BinItem &item, const Rasterizer::RasterizerState &state); + bool Access(); + // Instead of Access, for a primitive drawn in the GE's pixel order (one that reads what it draws): its + // lines go through the cache as its samples first read them, loading memory as it is then. Defer when + // it's stepped, BeginSerial when it's drawn (pointing state's texptr at copies of its levels), then + // SerialRead each texel before it's sampled. + void Defer(); + void BeginSerial(Rasterizer::RasterizerState &state); + void SerialRead(int level, int u, int v); + // The output buffer: memory doesn't have its blocks' new bytes yet (still old). Push a block as drawing it + // starts, remove it (by its index in push order) when it reaches memory. + static constexpr int MAX_UNFLUSHED = 6; + void PushUnflushed(uint32_t addr, const uint8_t old[16]); + void RemoveUnflushed(int index); // The bytes from start to end (masked addresses) are about to change: lines loaded from them keep the old. void BeforeWrite(uint32_t start, uint32_t end); // The CPU is about to run, and may write anything: every line keeps its bytes. @@ -71,6 +85,9 @@ public: private: void DecodedImage(Rasterizer::RasterizerState &state); + void CopyLevels(Rasterizer::RasterizerState &state); + // Lays a line's bytes over the copy of its level. + void PutLine(int level, int col, int band, const uint8_t data[128]); struct Source { // Where the line's bytes came from: the level's address, its row stride in bytes, swizzled or not. @@ -116,6 +133,8 @@ private: uint16_t col = 0; uint16_t band = 0; uint32_t lru = 0; + // The serial draw whose level copies have its bytes. + uint32_t viewGen = 0; Source src; uint8_t data[128]; }; @@ -143,6 +162,7 @@ private: static void MakeFootprint(const Config &cfg, const Bounds &b, std::vector &out); Line *Find(int bits, int level, int col, int band); void Touch(int bits, int level, int col, int band, const Source &src); + Line *Load(int bits, int level, int col, int band, const Source &src); void Step(const Config &cfg, const std::vector &fp); void ReplayLog(); // The line's bytes as the GE holds them. @@ -188,4 +208,12 @@ private: std::vector footprint_; std::vector delivered_; std::vector images_[8]; + + uint32_t serialGen_ = 0; + struct Unflushed { + uint32_t addr; + uint8_t old[16]; + }; + Unflushed unflushed_[MAX_UNFLUSHED]; + int unflushedCount_ = 0; };