Cache and hash data for DrawPixels.

We already had a cache to reuse texture objects so just
opportunistically reuse them when easy to do so.
This commit is contained in:
Henrik Rydgård 2023-11-11 19:51:44 +01:00
parent 3287c268ff
commit 632fa1c9d6
5 changed files with 35 additions and 6 deletions

View File

@ -1208,7 +1208,6 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int
vfb ? vfb->bufferHeight : g_display.pixel_yres, vfb ? vfb->bufferHeight : g_display.pixel_yres,
u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags);
gpuStats.numUploads++;
draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
@ -1324,6 +1323,19 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
} }
} }
int bpp = BufferFormatBytesPerPixel(srcPixelFormat);
int srcStrideInBytes = srcStride * bpp;
int widthInBytes = width * bpp;
// Compute hash of contents.
XXH3_state_t *hashState = XXH3_createState();
XXH3_64bits_reset(hashState);
for (int y = 0; y < height; y++) {
XXH3_64bits_update(hashState, srcPixels + srcStrideInBytes, widthInBytes);
}
uint64_t imageHash = XXH3_64bits_digest(hashState);
XXH3_freeState(hashState);
// TODO: We can just change the texture format and flip some bits around instead of this. // TODO: We can just change the texture format and flip some bits around instead of this.
// Could share code with the texture cache perhaps. // Could share code with the texture cache perhaps.
auto generateTexture = [&](uint8_t *data, const uint8_t *initData, uint32_t w, uint32_t h, uint32_t d, uint32_t byteStride, uint32_t sliceByteStride) { auto generateTexture = [&](uint8_t *data, const uint8_t *initData, uint32_t w, uint32_t h, uint32_t d, uint32_t byteStride, uint32_t sliceByteStride) {
@ -1396,16 +1408,28 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
int frameNumber = draw_->GetFrameCount(); int frameNumber = draw_->GetFrameCount();
// Look for a matching texture we can re-use. // First look for an exact match (including contents hash) that we can re-use.
for (auto &iter : drawPixelsCache_) {
if (iter.contentsHash == imageHash && iter.tex->Width() == width && iter.tex->Height() == height && iter.tex->Format() == texFormat) {
iter.frameNumber = frameNumber;
gpuStats.numCachedUploads++;
return iter.tex;
}
}
// Then, look for an alternative one that's not been used recently that we can overwrite.
for (auto &iter : drawPixelsCache_) { for (auto &iter : drawPixelsCache_) {
if (iter.frameNumber >= frameNumber - 3 || iter.tex->Width() != width || iter.tex->Height() != height || iter.tex->Format() != texFormat) { if (iter.frameNumber >= frameNumber - 3 || iter.tex->Width() != width || iter.tex->Height() != height || iter.tex->Format() != texFormat) {
continue; continue;
} }
// OK, current one seems good, let's use it (and mark it used). // OK, current one seems good, let's use it (and mark it used).
gpuStats.numUploads++;
draw_->UpdateTextureLevels(iter.tex, &srcPixels, generateTexture, 1); draw_->UpdateTextureLevels(iter.tex, &srcPixels, generateTexture, 1);
// NOTE: numFlips is no good - this is called every frame when paused sometimes! // NOTE: numFlips is no good - this is called every frame when paused sometimes!
iter.frameNumber = frameNumber; iter.frameNumber = frameNumber;
// We need to update the hash for future matching.
iter.contentsHash = imageHash;
return iter.tex; return iter.tex;
} }
@ -1435,8 +1459,9 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
// INFO_LOG(G3D, "Creating drawPixelsCache texture: %dx%d", tex->Width(), tex->Height()); // INFO_LOG(G3D, "Creating drawPixelsCache texture: %dx%d", tex->Width(), tex->Height());
DrawPixelsEntry entry{ tex, frameNumber }; DrawPixelsEntry entry{ tex, imageHash, frameNumber };
drawPixelsCache_.push_back(entry); drawPixelsCache_.push_back(entry);
gpuStats.numUploads++;
return tex; return tex;
} }

View File

@ -269,6 +269,7 @@ class DrawContext;
struct DrawPixelsEntry { struct DrawPixelsEntry {
Draw::Texture *tex; Draw::Texture *tex;
uint64_t contentsHash;
int frameNumber; int frameNumber;
}; };

View File

@ -92,6 +92,7 @@ struct GPUStatistics {
numBlockingReadbacks = 0; numBlockingReadbacks = 0;
numReadbacks = 0; numReadbacks = 0;
numUploads = 0; numUploads = 0;
numCachedUploads = 0;
numDepal = 0; numDepal = 0;
numClears = 0; numClears = 0;
numDepthCopies = 0; numDepthCopies = 0;
@ -126,6 +127,7 @@ struct GPUStatistics {
int numBlockingReadbacks; int numBlockingReadbacks;
int numReadbacks; int numReadbacks;
int numUploads; int numUploads;
int numCachedUploads;
int numDepal; int numDepal;
int numClears; int numClears;
int numDepthCopies; int numDepthCopies;

View File

@ -1688,7 +1688,7 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) {
"Vertices: %d drawn: %d\n" "Vertices: %d drawn: %d\n"
"FBOs active: %d (evaluations: %d)\n" "FBOs active: %d (evaluations: %d)\n"
"Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n" "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n"
"readbacks %d (%d non-block), uploads %d, depal %d\n" "readbacks %d (%d non-block), upload %d (cached %d), depal %d\n"
"block transfers: %d\n" "block transfers: %d\n"
"replacer: tracks %d references, %d unique textures\n" "replacer: tracks %d references, %d unique textures\n"
"Cpy: depth %d, color %d, reint %d, blend %d, self %d\n" "Cpy: depth %d, color %d, reint %d, blend %d, self %d\n"
@ -1713,6 +1713,7 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) {
gpuStats.numBlockingReadbacks, gpuStats.numBlockingReadbacks,
gpuStats.numReadbacks, gpuStats.numReadbacks,
gpuStats.numUploads, gpuStats.numUploads,
gpuStats.numCachedUploads,
gpuStats.numDepal, gpuStats.numDepal,
gpuStats.numBlockTransfers, gpuStats.numBlockTransfers,
gpuStats.numReplacerTrackedTex, gpuStats.numReplacerTrackedTex,

View File

@ -460,10 +460,10 @@ inline bool IsTextureFormat16Bit(GETextureFormat tfmt) {
inline int BufferFormatBytesPerPixel(GEBufferFormat format) { inline int BufferFormatBytesPerPixel(GEBufferFormat format) {
switch (format) { switch (format) {
case GE_FORMAT_8888: return 4; // applies to depth as well. case GE_FORMAT_8888: return 4;
case GE_FORMAT_CLUT8: return 1; case GE_FORMAT_CLUT8: return 1;
default: default:
return 2; return 2; // works for depth as well as the 16-bit color formats.
} }
} }