diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 2986b7a6f9..06ff45d7e9 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -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); } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index ba3697f019..19bacfbb8f 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -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(); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index ae7d99f30f..96bf2c1795 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -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 &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 videos_; AlignedVector tmpTexBuf32_; AlignedVector tmpTexBufRearrange_; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 05b043b980..2d5efe5157 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -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); } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index e447a54eff..8764f8057e 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -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 &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 videos_; int downcount; u64 startingTicks; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 7316add9b1..b1af8a5523 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -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) { diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index a86566430c..09bafb1676 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -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); } }