mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 02:39:05 +02:00
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:
1 parent
a882484147
commit
fdb411dd0e
8 files changed
+359
-31
No files matched your search
+178
-9
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
Reference in new issue
Block a user