mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
GPU: Track video frames in GPUCommon, so they expire for the blit cost too
The blit cost remembered only the last buffer a decoder wrote into, forever. Move the texture cache's video list (with its ageing out a few flips after the last write) into GPUCommon, so the texture cache, the blit cost and SoftGPU all share one. That also counts both of a double-buffered player's frames, which exposed that a clear drawn with texturing still enabled was being charged as a blit - skip clears and draws without texture coordinates. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
8f70003cce
commit
242ef0a984
7 files changed
+80
-82
No files matched your search
@@ -1561,7 +1561,7 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig
|
||||
constexpr float v0 = 0.0f, v1 = 1.0f;
|
||||
|
||||
if (useBufferedRendering_) {
|
||||
presentation_->UpdateUniforms(textureCache_->VideoIsPlaying());
|
||||
presentation_->UpdateUniforms(gpu->VideoIsPlaying());
|
||||
presentation_->SourceTexture(pixelsTex, 512, 272);
|
||||
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
|
||||
}
|
||||
@@ -1728,7 +1728,7 @@ void FramebufferManagerCommon::PrepareCopyDisplayToOutput(const DisplayLayoutCon
|
||||
|
||||
int actualWidth = (vfb->bufferWidth * vfb->renderWidth) / vfb->width;
|
||||
int actualHeight = (vfb->bufferHeight * vfb->renderHeight) / vfb->height;
|
||||
presentation_->UpdateUniforms(textureCache_->VideoIsPlaying());
|
||||
presentation_->UpdateUniforms(gpu->VideoIsPlaying());
|
||||
presentation_->SourceFramebuffer(vfb->fbo, actualWidth, actualHeight);
|
||||
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
#include "GPU/Common/GPUStateUtils.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
#include "GPU/Debugger/Record.h"
|
||||
#include "GPU/GPUCommon.h"
|
||||
#include "GPU/GPUState.h"
|
||||
#include "Core/Util/PPGeDraw.h"
|
||||
|
||||
// Videos should be updated every few frames, so we forget quickly.
|
||||
#define VIDEO_DECIMATE_AGE 4
|
||||
|
||||
// If a texture hasn't been seen for this many frames, get rid of it.
|
||||
#define TEXTURE_KILL_AGE 200
|
||||
@@ -153,7 +153,7 @@ void TextureCacheCommon::StartFrame() {
|
||||
gpuStats.perFrame.numReplacerTrackedTex = replacer_.GetNumTrackedTextures();
|
||||
gpuStats.perFrame.numCachedReplacedTextures = replacer_.GetNumCachedReplacedTextures();
|
||||
}
|
||||
gpuStats.perFrame.numVideoTextures = (int)videos_.size();
|
||||
gpuStats.perFrame.numVideoTextures = (int)gpu->Videos().size();
|
||||
|
||||
if (texelsScaledThisFrame_) {
|
||||
VERBOSE_LOG(Log::TexCache, "Scaled %d texels", texelsScaledThisFrame_);
|
||||
@@ -1134,26 +1134,11 @@ void TextureCacheCommon::Decimate(const TexCacheEntry *const exceptThisOne, bool
|
||||
}
|
||||
}
|
||||
|
||||
// Decimate known videos (so the list doesn't grow unboundedly or we start to misidentify textures as video).
|
||||
for (auto iter = videos_.begin(); iter != videos_.end(); ) {
|
||||
if (iter->flips + VIDEO_DECIMATE_AGE < gpuStats.totals.numFlips) {
|
||||
iter = videos_.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
replacer_.Decimate(forcePressure ? ReplacerDecimateMode::FORCE_PRESSURE : ReplacerDecimateMode::NEW_FRAME);
|
||||
}
|
||||
|
||||
bool TextureCacheCommon::IsVideo(u32 texaddr) const {
|
||||
texaddr &= 0x3FFFFFFF;
|
||||
for (const VideoInfo &info : videos_) {
|
||||
if (texaddr >= info.addr && texaddr < info.addr + info.size) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return gpu->IsVideo(texaddr);
|
||||
}
|
||||
|
||||
void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, FramebufferNotification msg) {
|
||||
@@ -1509,37 +1494,6 @@ void TextureCacheCommon::NotifyConfigChanged() {
|
||||
replacer_.NotifyConfigChanged();
|
||||
}
|
||||
|
||||
void TextureCacheCommon::NoteVideoRange(u32 addr, u32 size) {
|
||||
addr &= 0x3FFFFFFF;
|
||||
// A game blits its video frame every displayed frame while waiting for the next one, so the
|
||||
// same few buffers arrive over and over. Refresh the one we already have rather than stacking
|
||||
// a duplicate per frame - IsVideo() scans this linearly.
|
||||
for (VideoInfo &info : videos_) {
|
||||
if (info.addr == addr) {
|
||||
info.size = size;
|
||||
info.flips = gpuStats.totals.numFlips;
|
||||
return;
|
||||
}
|
||||
}
|
||||
videos_.push_back({ addr, size, gpuStats.totals.numFlips });
|
||||
}
|
||||
|
||||
void TextureCacheCommon::NotifyWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt) {
|
||||
NoteVideoRange(addr, (u32)size);
|
||||
}
|
||||
|
||||
// A block copy of a video frame is still a video frame, and games do move them around: Dragon Ball
|
||||
// Z - Shin Budokai: Another Road colour-converts into RAM, sceDmacMemcpy's the result into VRAM and
|
||||
// textures from there, never sampling the converted buffer itself. Without carrying the status
|
||||
// across the copy, what we actually sample looks like an ordinary texture that happens to have new
|
||||
// contents every frame, so we hash it, miss, and rebuild it - forever.
|
||||
void TextureCacheCommon::NotifyVideoCopy(u32 dst, u32 src, int size) {
|
||||
if (size <= 0 || !IsVideo(src)) {
|
||||
return;
|
||||
}
|
||||
NoteVideoRange(dst, (u32)size);
|
||||
}
|
||||
|
||||
void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Recorder *recorder) {
|
||||
if (loadBytes == 0) {
|
||||
// Don't accidentally overwrite clutTotalBytes_ with a zero.
|
||||
@@ -2769,7 +2723,6 @@ void TextureCacheCommon::Clear(bool delete_them) {
|
||||
cache_.clear();
|
||||
secondCache_.clear();
|
||||
}
|
||||
videos_.clear();
|
||||
|
||||
if (dynamicClutFbo_) {
|
||||
dynamicClutFbo_->Release();
|
||||
|
||||
@@ -362,8 +362,6 @@ public:
|
||||
// FramebufferManager keeps TextureCache updated about what regions of memory are being rendered to,
|
||||
// so that it can invalidate TexCacheEntries pointed at those addresses.
|
||||
void NotifyFramebuffer(VirtualFramebuffer *framebuffer, FramebufferNotification msg);
|
||||
void NotifyWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt);
|
||||
void NotifyVideoCopy(u32 dst, u32 src, int size);
|
||||
|
||||
int NumLoadedTextures() const {
|
||||
return (int)cache_.size();
|
||||
@@ -374,9 +372,6 @@ public:
|
||||
bool IsFakeMipmapChange() {
|
||||
return PSP_CoreParameter().compat.flags().FakeMipmapChange && gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST;
|
||||
}
|
||||
bool VideoIsPlaying() {
|
||||
return !videos_.empty();
|
||||
}
|
||||
virtual bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { return false; }
|
||||
|
||||
virtual void StartFrame();
|
||||
@@ -393,15 +388,6 @@ public:
|
||||
const size_t CacheSizeEstimate() const;
|
||||
const size_t SecondCacheSizeEstimate() const;
|
||||
|
||||
struct VideoInfo {
|
||||
u32 addr;
|
||||
u32 size;
|
||||
int flips;
|
||||
};
|
||||
|
||||
const std::vector<VideoInfo> &Videos() const {
|
||||
return videos_;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry);
|
||||
@@ -446,7 +432,6 @@ protected:
|
||||
virtual void BoundFramebufferTexture() {}
|
||||
|
||||
bool IsVideo(u32 texaddr) const;
|
||||
void NoteVideoRange(u32 addr, u32 size);
|
||||
|
||||
static TextureAlpha CheckCLUTAlpha(const uint8_t *pixelData, GEPaletteFormat clutFmt, int w);
|
||||
|
||||
@@ -473,7 +458,6 @@ protected:
|
||||
// The secondary cache uses the texture hash and clut hash as the key.
|
||||
TexCache secondCache_;
|
||||
|
||||
std::vector<VideoInfo> videos_;
|
||||
|
||||
AlignedVector<u32, 16> tmpTexBuf32_;
|
||||
AlignedVector<u32, 16> tmpTexBufRearrange_;
|
||||
|
||||
+54
-7
@@ -149,11 +149,14 @@ int GPUCommon::EstimatePerVertexCost() {
|
||||
}
|
||||
|
||||
int GPUCommon::EstimateVideoBlitCycles(GEPrimitiveType prim, const void *verts, const void *inds, int count, const VertexDecoder *dec, u32 vertType) const {
|
||||
if (prim != GE_PRIM_RECTANGLES || !gstate.isModeThrough() || !gstate.isTextureMapEnabled() || videoFrameSize_ == 0) {
|
||||
// A clear, or a draw without texture coordinates, doesn't sample the texture even if texturing
|
||||
// is still enabled from an earlier draw.
|
||||
if (prim != GE_PRIM_RECTANGLES || !gstate.isModeThrough() || !gstate.isTextureMapEnabled() ||
|
||||
gstate.isModeClear() || (vertType & GE_VTYPE_TC_MASK) == 0) {
|
||||
return 0;
|
||||
}
|
||||
const u32 texAddr = gstate.getTextureAddress(0) & 0x3FFFFFFF;
|
||||
if (texAddr < (videoFrameAddr_ & 0x3FFFFFFF) || texAddr >= (videoFrameAddr_ & 0x3FFFFFFF) + videoFrameSize_) {
|
||||
if (!IsVideo(texAddr)) {
|
||||
return 0;
|
||||
}
|
||||
if ((vertType & GE_VTYPE_POS_MASK) != GE_VTYPE_POS_16BIT) {
|
||||
@@ -205,6 +208,46 @@ int GPUCommon::EstimateVideoBlitCycles(GEPrimitiveType prim, const void *verts,
|
||||
return (int)((float)usToCycles(PowerScaleFromDefaultClock(1000)) * ns / 1000000.0f);
|
||||
}
|
||||
|
||||
// How many flips a video range stays known after the last frame written to it.
|
||||
static const int VIDEO_DECIMATE_AGE = 4;
|
||||
|
||||
bool GPUCommon::IsVideo(u32 addr) const {
|
||||
addr &= 0x3FFFFFFF;
|
||||
for (const VideoInfo &info : videos_) {
|
||||
if (addr >= info.addr && addr < info.addr + info.size) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GPUCommon::NoteVideoRange(u32 addr, u32 size) {
|
||||
addr &= 0x3FFFFFFF;
|
||||
// A game blits its video frame every displayed frame while waiting for the next one, so the
|
||||
// same few buffers arrive over and over. Refresh the one we already have rather than stacking
|
||||
// a duplicate per frame - IsVideo() scans this linearly.
|
||||
for (VideoInfo &info : videos_) {
|
||||
if (info.addr == addr) {
|
||||
info.size = size;
|
||||
info.flips = gpuStats.totals.numFlips;
|
||||
return;
|
||||
}
|
||||
}
|
||||
videos_.push_back({ addr, size, gpuStats.totals.numFlips });
|
||||
}
|
||||
|
||||
// So the list doesn't grow unboundedly, and a buffer reused for something else once the movie is
|
||||
// over stops being treated as video.
|
||||
void GPUCommon::DecimateVideos() {
|
||||
for (auto iter = videos_.begin(); iter != videos_.end(); ) {
|
||||
if (iter->flips + VIDEO_DECIMATE_AGE < gpuStats.totals.numFlips) {
|
||||
iter = videos_.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommon::PopDLQueue() {
|
||||
if(!dlQueue.empty()) {
|
||||
dlQueue.pop_front();
|
||||
@@ -711,6 +754,7 @@ u32 GPUCommon::Break(int mode) {
|
||||
|
||||
void GPUCommon::PSPFrame() {
|
||||
immCount_ = 0;
|
||||
DecimateVideos();
|
||||
if (dumpNextFrame_) {
|
||||
NOTICE_LOG(Log::G3D, "DUMPING THIS FRAME");
|
||||
dumpThisFrame_ = true;
|
||||
@@ -2065,9 +2109,14 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) {
|
||||
cyclesExecuted += ((height * width * bpp) * 16) / 10;
|
||||
}
|
||||
|
||||
// A block copy of a video frame is still a video frame, and games do move them around: Dragon Ball
|
||||
// Z - Shin Budokai: Another Road colour-converts into RAM, sceDmacMemcpy's the result into VRAM and
|
||||
// textures from there, never sampling the converted buffer itself. Without carrying the status
|
||||
// across the copy, what we actually sample looks like an ordinary texture that happens to have new
|
||||
// contents every frame, so we hash it, miss, and rebuild it - forever.
|
||||
void GPUCommon::NotifyVideoCopy(u32 dest, u32 src, int size) {
|
||||
if (textureCache_) {
|
||||
textureCache_->NotifyVideoCopy(dest, src, size);
|
||||
if (size > 0 && IsVideo(src)) {
|
||||
NoteVideoRange(dest, (u32)size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2166,12 +2215,10 @@ bool GPUCommon::PerformWriteColorFromMemory(u32 dest, int size) {
|
||||
}
|
||||
|
||||
void GPUCommon::PerformWriteFormattedFromMemory(u32 addr, int size, int frameWidth, GEBufferFormat format) {
|
||||
videoFrameAddr_ = addr;
|
||||
videoFrameSize_ = size;
|
||||
NoteVideoRange(addr, (u32)size);
|
||||
if (Memory::IsVRAMAddress(addr)) {
|
||||
framebufferManager_->PerformWriteFormattedFromMemory(addr, size, frameWidth, format);
|
||||
}
|
||||
textureCache_->NotifyWriteFormattedFromMemory(addr, size, frameWidth, format);
|
||||
InvalidateCache(addr, size, GPU_INVALIDATE_SAFE);
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -192,6 +192,21 @@ public:
|
||||
// anything else. We don't model fill rate in general, but a movie player can be paced by it.
|
||||
int EstimateVideoBlitCycles(GEPrimitiveType prim, const void *verts, const void *inds, int count, const VertexDecoder *dec, u32 vertType) const;
|
||||
|
||||
// Memory a video decoder has written a frame into recently (PerformWriteFormattedFromMemory),
|
||||
// or a copy of such a frame (NotifyVideoCopy). An entry ages out a few flips after its last write.
|
||||
struct VideoInfo {
|
||||
u32 addr;
|
||||
u32 size;
|
||||
int flips;
|
||||
};
|
||||
bool IsVideo(u32 addr) const;
|
||||
bool VideoIsPlaying() const {
|
||||
return !videos_.empty();
|
||||
}
|
||||
const std::vector<VideoInfo> &Videos() const {
|
||||
return videos_;
|
||||
}
|
||||
|
||||
virtual void Flush();
|
||||
|
||||
#ifdef USE_CRT_DBG
|
||||
@@ -360,9 +375,9 @@ protected:
|
||||
u64 drawCompleteTicks;
|
||||
u64 busyTicks;
|
||||
|
||||
// The last buffer a video decoder wrote a frame into, see PerformWriteFormattedFromMemory.
|
||||
u32 videoFrameAddr_ = 0;
|
||||
u32 videoFrameSize_ = 0;
|
||||
void NoteVideoRange(u32 addr, u32 size);
|
||||
void DecimateVideos();
|
||||
std::vector<VideoInfo> videos_;
|
||||
|
||||
int downcount;
|
||||
u64 startingTicks;
|
||||
|
||||
@@ -1285,8 +1285,7 @@ void SoftGPU::InvalidateCache(u32 addr, int size, GPUInvalidationType type)
|
||||
void SoftGPU::PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format)
|
||||
{
|
||||
// Nothing to update, but remember it for EstimateVideoBlitCycles.
|
||||
videoFrameAddr_ = addr;
|
||||
videoFrameSize_ = size;
|
||||
NoteVideoRange(addr, (u32)size);
|
||||
}
|
||||
|
||||
bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) {
|
||||
|
||||
@@ -243,9 +243,9 @@ void DrawTexturesWindow(ImConfig &cfg, TextureCacheCommon *textureCache) {
|
||||
replacementStateCounts[(int)ReplacementState::ACTIVE],
|
||||
replacementStateCounts[(int)ReplacementState::CANCEL_INIT]);
|
||||
}
|
||||
if (textureCache->Videos().size()) {
|
||||
if (gpu->Videos().size()) {
|
||||
if (ImGui::CollapsingHeader("Tracked video playback memory")) {
|
||||
for (auto &video : textureCache->Videos()) {
|
||||
for (auto &video : gpu->Videos()) {
|
||||
ImGui::Text("%08x: %d flips, size = %d", video.addr, video.flips, video.size);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user