diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index e195fbce02..c5a2ec06ce 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -273,8 +273,51 @@ void BinManager::UpdateState() { ClearDirty(SoftDirty::BINNER_RANGE); } pendingOverlap_ = pendingOverlap_ || selfRender; + selfRender_ = selfRender; ClearDirty(SoftDirty::BINNER_OVERLAP); } + states_[stateIndex_].selfTexture = selfRender_; +} + +// The GE samples through its texture cache, so a primitive that textures from the buffer it draws to +// mostly sees that buffer as it was before the primitive (exp81, exp159, exp160). The cache actually +// fills 8-row blocks as the primitive first reads them, so rows drawn before then show through; this +// doesn't model that. +const RasterizerState &BinManager::SelfTextureSnapshot(const BinItem &item, const RasterizerState &state) { + constexpr uint32_t mirrorMask = 0x041FFFFF; + const uint32_t bits = textureBitsPerPixel[state.samplerID.texfmt]; + const uint32_t fbBpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; + const uint32_t fbStrideBytes = state.pixelID.cached.framebufStride * fbBpp; + + selfTexState_ = state; + for (int i = 0; i <= state.maxTexLevel; ++i) { + const u8 *src = state.texptr[i]; + const uint32_t bytes = state.texbufw[i] * bits / 8 * state.samplerID.cached.sizes[i].h; + if (!src || !Memory::IsValidRange(state.texaddr[i], bytes)) + continue; + std::vector &buf = selfTexBuf_[i]; + if (!selfTexValid_ || selfTexAddr_[i] != state.texaddr[i] || buf.size() != bytes) { + buf.assign(src, src + bytes); + selfTexAddr_[i] = state.texaddr[i]; + } else { + // Only the rows the previous primitive drew have changed. + const DrawingCoords tl = TransformUnit::ScreenToDrawing(selfTexLastRange_.x1, selfTexLastRange_.y1); + const DrawingCoords br = TransformUnit::ScreenToDrawing(selfTexLastRange_.x2, selfTexLastRange_.y2); + const int64_t rowsStart = (int64_t)(drawTargetAddr_ & mirrorMask) + tl.y * fbStrideBytes; + const int64_t rowsEnd = (int64_t)(drawTargetAddr_ & mirrorMask) + (br.y + 1) * fbStrideBytes; + const int64_t texStart = state.texaddr[i] & mirrorMask; + const int64_t start = std::max(rowsStart, texStart) - texStart; + const int64_t end = std::min(rowsEnd, texStart + bytes) - texStart; + if (start < end) + memcpy(buf.data() + start, src + start, (size_t)(end - start)); + } + selfTexState_.texptr[i] = buf.data(); + } + + // A depth write could change the texture outside the color rows, so then copy all of it each time. + selfTexValid_ = !state.pixelID.depthWrite; + selfTexLastRange_ = item.range; + return selfTexState_; } bool BinManager::HasTextureWrite(const RasterizerState &state) { @@ -536,9 +579,17 @@ void BinManager::Drain(bool flushing) { if (taskRanges_.size() <= 1) { PROFILE_THIS_SCOPE("bin_drain_single"); + // Anything (transfers, the CPU) may have written memory since the last drain. + selfTexValid_ = false; while (!queue_.Empty()) { const BinItem &item = queue_.PeekNext(); - DrawBinItem(item, states_[item.stateIndex]); + const RasterizerState &state = states_[item.stateIndex]; + if (state.selfTexture) { + DrawBinItem(item, SelfTextureSnapshot(item, state)); + } else { + selfTexValid_ = false; + DrawBinItem(item, state); + } queue_.SkipNext(); } } else { diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index afff6b7e66..f54d18e5b9 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -268,6 +268,14 @@ private: std::unordered_map pendingReads_; bool pendingOverlap_ = false; + // Whether the current state textures from what it draws to, and the texture as it was before the + // primitive being drawn for one that does. + bool selfRender_ = false; + Rasterizer::RasterizerState selfTexState_; + std::vector selfTexBuf_[8]; + uint32_t selfTexAddr_[8]{}; + bool selfTexValid_ = false; + BinCoords selfTexLastRange_{}; bool creatingState_ = false; // JIT clear generations when the current state was computed. int jitGen_ = -1; @@ -288,6 +296,7 @@ private: void MarkPendingWrites(const Rasterizer::RasterizerState &state); bool HasTextureWrite(const Rasterizer::RasterizerState &state); bool IsExactSelfRender(const Rasterizer::RasterizerState &state, const BinItem &item) const; + const Rasterizer::RasterizerState &SelfTextureSnapshot(const BinItem &item, const Rasterizer::RasterizerState &state); void OptimizePendingStates(uint16_t first, uint16_t last); BinCoords Scissor(BinCoords range); BinCoords Range(const VertexData &v0, const VertexData &v1, const VertexData &v2); diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index d4e4ebe17c..e24c253b4c 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -90,6 +90,8 @@ struct RasterizerState { bool magFilt : 1; bool antialiasLines : 1; bool textureProj : 1; + // Textures from the buffer it draws to (BinManager::SelfTextureSnapshot). + bool selfTexture : 1; }; #if defined(SOFTGPU_MEMORY_TAGGING_DETAILED) || defined(SOFTGPU_MEMORY_TAGGING_BASIC)