mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
SoftGPU: Self-texturing primitives sample the texture as it was before them
The GE samples through its texture cache, so a primitive that textures from its own render target mostly reads the old contents (geprobe exp81, exp159, exp160). Give each such primitive a snapshot, refreshing only the rows the previous one drew. 3rd Birthday (ULUS10567) bloom becomes exact on PSP replays, Burnout Dominator's glare 93% -> 98% and Ridge Racer's blur 40% -> 94% of pixels. The remaining differences are rows where the cache's lazily loaded 8-row blocks already see the primitive's own writes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7df47b336b
commit
74e2c8f7ee
3 files changed
+63
-1
No files matched your search
@@ -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<u8> &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 {
|
||||
|
||||
@@ -268,6 +268,14 @@ private:
|
||||
std::unordered_map<uint32_t, BinDirtyRange> 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<u8> 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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user