SoftGPU: Draw sprites that texture from themselves in the GE's pixel order

A sprite that reads what it draws goes through the texture cache pixel by
pixel: rows left to right, top to bottom, texture lines loaded as samples
first read them, and pixels reaching memory a 16-byte block at a time, a
pixel after the next block starts (four with bilinear filtering). Fitted to
geprobe exp81 and exp204-exp209 on hardware; Split Second's blur passes now
match the PSP.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-09 12:27:45 +02:00
1 parent a882484147
commit fdb411dd0e
8 files changed
+359 -31

No files matched your search

+178 -9
View File
@@ -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<int64_t>(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<int64_t>(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;
+8
View File
@@ -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);
+4
View File
@@ -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);
+1 -1
View File
@@ -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;
+23
View File
@@ -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;
+4
View File
@@ -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();
+111 -19
View File
@@ -19,6 +19,7 @@
#include <cmath>
#include <cstring>
#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<uint8_t> &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<uint8_t> &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) {
+30 -2
View File
@@ -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<Footprint> &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<Footprint> &fp);
void ReplayLog();
// The line's bytes as the GE holds them.
@@ -188,4 +208,12 @@ private:
std::vector<Footprint> footprint_;
std::vector<Delivered> delivered_;
std::vector<uint8_t> images_[8];
uint32_t serialGen_ = 0;
struct Unflushed {
uint32_t addr;
uint8_t old[16];
};
Unflushed unflushed_[MAX_UNFLUSHED];
int unflushedCount_ = 0;
};