Merge pull request #9332 from hrydgard/texcache-unify-2

Further texcache unification
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2017-02-20 01:10:02 +01:00
commit 4c8aa77e13
13 files changed
+728 -2083

No files matched your search

+443 -50
View File
@@ -37,6 +37,18 @@
// Videos should be updated every few frames, so we forge 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
#define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
#define TEXTURE_SECOND_KILL_AGE 100
// Try to be prime to other decimation intervals.
#define TEXCACHE_DECIMATION_INTERVAL 13
#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM
#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024
TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw)
: draw_(draw),
clearCacheNextFrame_(false),
@@ -49,7 +61,10 @@ TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw)
clutTotalBytes_(0),
clutMaxBytes_(0),
clutRenderAddress_(0xFFFFFFFF),
clutAlphaLinear_(false) {
clutAlphaLinear_(false),
isBgraBackend_(false) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
// TODO: Clamp down to 256/1KB? Need to check mipmapShareClut and clamp loadclut.
clutBufRaw_ = (u32 *)AllocateAlignedMemory(1024 * sizeof(u32), 16); // 4KB
clutBufConverted_ = (u32 *)AllocateAlignedMemory(1024 * sizeof(u32), 16); // 4KB
@@ -60,11 +75,11 @@ TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw)
clutBuf_ = clutBufConverted_;
// These buffers will grow if necessary, but most won't need more than this.
tmpTexBuf32.resize(512 * 512); // 1MB
tmpTexBuf16.resize(512 * 512); // 0.5MB
tmpTexBufRearrange.resize(512 * 512); // 1MB
tmpTexBuf32_.resize(512 * 512); // 1MB
tmpTexBuf16_.resize(512 * 512); // 0.5MB
tmpTexBufRearrange_.resize(512 * 512); // 1MB
replacer.Init();
replacer_.Init();
}
TextureCacheCommon::~TextureCacheCommon() {
@@ -158,6 +173,264 @@ void TextureCacheCommon::UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode)
}
}
void TextureCacheCommon::SetTexture(bool force) {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
InvalidateLastTexture();
return;
}
#endif
if (force) {
InvalidateLastTexture();
}
u8 level = 0;
if (IsFakeMipmapChange())
level = (gstate.texlevel >> 20) & 0xF;
u32 texaddr = gstate.getTextureAddress(level);
if (!Memory::IsValidAddress(texaddr)) {
// Bind a null texture and return.
Unbind();
InvalidateLastTexture();
return;
}
const u16 dim = gstate.getTextureDimension(level);
int w = gstate.getTextureWidth(level);
int h = gstate.getTextureHeight(level);
GETextureFormat format = gstate.getTextureFormat();
if (format >= 11) {
ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format);
// TODO: Better assumption?
format = GE_TFMT_5650;
}
bool hasClut = gstate.isTextureFormatIndexed();
// Ignore uncached/kernel when caching.
u32 cluthash;
if (hasClut) {
if (clutLastFormat_ != gstate.clutformat) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
}
cluthash = clutHash_ ^ gstate.clutformat;
} else {
cluthash = 0;
}
u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash);
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
TexCache::iterator iter = cache_.find(cachekey);
TexCacheEntry *entry = nullptr;
gstate_c.needShaderTexClamp = false;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
gstate_c.bgraTexture = isBgraBackend_;
if (iter != cache_.end()) {
entry = iter->second.get();
// Validate the texture still matches the cache entry.
bool match = entry->Matches(dim, format, maxLevel);
const char *reason = "different params";
// Check for FBO - slow!
if (entry->framebuffer) {
if (match) {
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
SetTextureFramebuffer(entry, entry->framebuffer);
return;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer(entry, texaddr, entry->framebuffer);
reason = "detached framebuf";
match = false;
}
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true;
entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
}
if (match) {
if (entry->lastFrame != gpuStats.numFlips) {
u32 diff = gpuStats.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
// Exponential backoff up to 512 frames. Textures are often reused.
if (entry->numFrames > 32) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15);
} else {
entry->framesUntilNextFullHash = entry->numFrames;
}
rehash = true;
} else {
entry->framesUntilNextFullHash -= diff;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) {
entry->invalidHint = 0;
rehash = true;
}
if (texhash != entry->hash) {
match = false;
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) {
if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false;
reason = "scaling";
}
}
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
if (rehash) {
// Update in case any of these changed.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
}
nextTexture_ = entry;
nextNeedsRehash_ = rehash;
nextNeedsChange_ = false;
// Might need a rebuild if the hash fails.
nextNeedsRebuild_ = false;
VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr);
return; //Done!
} else {
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
TexCacheEntry *entryNew = new TexCacheEntry{};
cache_[cachekey].reset(entryNew);
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
entry = entryNew;
if (g_Config.bTextureBackoffCache) {
entry->status = TexCacheEntry::STATUS_HASHING;
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
// Before we go reading the texture from memory, let's check for render-to-texture.
// We must do this early so we have the right w/h.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
}
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
}
nextTexture_ = entry;
nextNeedsRehash_ = entry->framebuffer == nullptr;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_ = true;
}
// Removes old textures.
void TextureCacheCommon::Decimate() {
if (--decimationCounter_ <= 0) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
} else {
return;
}
if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
ForgetLastTexture();
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
for (TexCache::iterator iter = cache_.begin(); iter != cache_.end(); ) {
if (iter->second->lastFrame + killAge < gpuStats.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
}
if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) {
const u32 had = secondCacheSizeEstimate_;
for (TexCache::iterator iter = secondCache_.begin(); iter != secondCache_.end(); ) {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second->lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) {
ReleaseTexture(iter->second.get());
secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(iter->second.get());
secondCache_.erase(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
}
DecimateVideos();
}
void TextureCacheCommon::DecimateVideos() {
if (!videos_.empty()) {
for (auto iter = videos_.begin(); iter != videos_.end(); ) {
@@ -170,6 +443,43 @@ void TextureCacheCommon::DecimateVideos() {
}
}
bool TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
bool replaceImages = false;
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
if (initialMatch && standardScaleFactor_ == 1 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) {
// Actually, if size and number of levels match, let's try to avoid deleting and recreating.
// Instead, let's use glTexSubImage to replace the images.
replaceImages = true;
} else {
InvalidateLastTexture(entry);
ReleaseTexture(entry);
entry->status &= ~TexCacheEntry::STATUS_IS_SCALED;
}
}
// Clear the reliable bit if set.
if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if (entry->cluthash != 0) {
const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
for (auto it = cache_.lower_bound(cachekeyMin), end = cache_.upper_bound(cachekeyMax); it != end; ++it) {
if (it->second->cluthash != entry->cluthash) {
it->second->status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
return replaceImages;
}
void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg) {
// Must be in VRAM so | 0x04000000 it is. Also, ignore memory mirrors.
// These checks are mainly to reduce scanning all textures.
@@ -191,15 +501,15 @@ void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *fram
if (std::find(fbCache_.begin(), fbCache_.end(), framebuffer) == fbCache_.end()) {
fbCache_.push_back(framebuffer);
}
for (auto it = cache.lower_bound(cacheKey), end = cache.upper_bound(cacheKeyEnd); it != end; ++it) {
AttachFramebuffer(&it->second, addr, framebuffer);
for (auto it = cache_.lower_bound(cacheKey), end = cache_.upper_bound(cacheKeyEnd); it != end; ++it) {
AttachFramebuffer(it->second.get(), addr, framebuffer);
}
// Let's assume anything in mirrors is fair game to check.
for (auto it = cache.lower_bound(mirrorCacheKey), end = cache.upper_bound(mirrorCacheKeyEnd); it != end; ++it) {
for (auto it = cache_.lower_bound(mirrorCacheKey), end = cache_.upper_bound(mirrorCacheKeyEnd); it != end; ++it) {
const u64 mirrorlessKey = it->first & ~0x0060000000000000ULL;
// Let's still make sure it's in the cache range.
if (mirrorlessKey >= cacheKey && mirrorlessKey <= cacheKeyEnd) {
AttachFramebuffer(&it->second, addr, framebuffer);
AttachFramebuffer(it->second.get(), addr, framebuffer);
}
}
break;
@@ -214,7 +524,7 @@ void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *fram
// We might erase, so move to the next one already (which won't become invalid.)
++it;
DetachFramebuffer(&cache[cachekey], addr, framebuffer);
DetachFramebuffer(cache_[cachekey].get(), addr, framebuffer);
}
break;
}
@@ -239,7 +549,7 @@ void TextureCacheCommon::AttachFramebufferValid(TexCacheEntry *entry, VirtualFra
}
entry->framebuffer = framebuffer;
entry->invalidHint = 0;
entry->status &= ~TextureCacheCommon::TexCacheEntry::STATUS_DEPALETTIZE;
entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE;
entry->maxLevel = 0;
fbTexInfo_[cachekey] = fbInfo;
framebuffer->last_frame_attached = gpuStats.numFlips;
@@ -258,7 +568,7 @@ void TextureCacheCommon::AttachFramebufferInvalid(TexCacheEntry *entry, VirtualF
}
entry->framebuffer = framebuffer;
entry->invalidHint = -1;
entry->status &= ~TextureCacheCommon::TexCacheEntry::STATUS_DEPALETTIZE;
entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE;
entry->maxLevel = 0;
fbTexInfo_[cachekey] = fbInfo;
host->GPUNotifyTextureAttachment(entry->addr);
@@ -432,11 +742,11 @@ bool TextureCacheCommon::SetOffsetTexture(u32 offset) {
const u16 dim = gstate.getTextureDimension(0);
u64 cachekey = TexCacheEntry::CacheKey(texaddr, gstate.getTextureFormat(), dim, 0);
TexCache::iterator iter = cache.find(cachekey);
if (iter == cache.end()) {
TexCache::iterator iter = cache_.find(cachekey);
if (iter == cache_.end()) {
return false;
}
TexCacheEntry *entry = &iter->second;
TexCacheEntry *entry = iter->second.get();
bool success = false;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
@@ -494,7 +804,7 @@ void TextureCacheCommon::NotifyConfigChanged() {
standardScaleFactor_ = scaleFactor;
replacer.NotifyConfigChanged();
replacer_.NotifyConfigChanged();
}
void TextureCacheCommon::NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt) {
@@ -685,9 +995,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm
const int clutSharingOffset = mipmapShareClut ? 0 : level * 16;
if (swizzled) {
tmpTexBuf32.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32.data(), bufw / 2, texptr, bufw, h, 0);
texptr = (u8 *)tmpTexBuf32.data();
tmpTexBuf32_.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32_.data(), bufw / 2, texptr, bufw, h, 0);
texptr = (u8 *)tmpTexBuf32_.data();
}
switch (clutformat) {
@@ -770,9 +1080,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm
}
} else {
// We don't have enough space for all rows in out, so use a temp buffer.
tmpTexBuf32.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32.data(), bufw * 2, texptr, bufw, h, 2);
const u8 *unswizzled = (u8 *)tmpTexBuf32.data();
tmpTexBuf32_.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32_.data(), bufw * 2, texptr, bufw, h, 2);
const u8 *unswizzled = (u8 *)tmpTexBuf32_.data();
if (reverseColors) {
for (int y = 0; y < h; ++y) {
@@ -804,9 +1114,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm
}
} else {
// We don't have enough space for all rows in out, so use a temp buffer.
tmpTexBuf32.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32.data(), bufw * 4, texptr, bufw, h, 4);
const u8 *unswizzled = (u8 *)tmpTexBuf32.data();
tmpTexBuf32_.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32_.data(), bufw * 4, texptr, bufw, h, 4);
const u8 *unswizzled = (u8 *)tmpTexBuf32_.data();
if (reverseColors) {
for (int y = 0; y < h; ++y) {
@@ -899,9 +1209,9 @@ bool TextureCacheCommon::ReadIndexedTex(u8 *out, int outPitch, int level, const
int h = gstate.getTextureHeight(level);
if (gstate.isTextureSwizzled()) {
tmpTexBuf32.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32.data(), bufw * bytesPerIndex, texptr, bufw, h, bytesPerIndex);
texptr = (u8 *)tmpTexBuf32.data();
tmpTexBuf32_.resize(bufw * ((h + 7) & ~7));
UnswizzleFromMem(tmpTexBuf32_.data(), bufw * bytesPerIndex, texptr, bufw, h, bytesPerIndex);
texptr = (u8 *)tmpTexBuf32_.data();
}
switch (gstate.getClutPaletteFormat()) {
@@ -966,11 +1276,93 @@ bool TextureCacheCommon::ReadIndexedTex(u8 *out, int outPitch, int level, const
}
bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) {
void TextureCacheCommon::ApplyTexture() {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
bool replaceImages = false;
if (nextNeedsRebuild_) {
if (nextNeedsRehash_) {
// Update the hash on the texture.
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
entry->fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
}
if (nextNeedsChange_) {
// This texture existed previously, let's handle the change.
replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true);
}
// We actually build afterward (shared with rehash rebuild.)
} else if (nextNeedsRehash_) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true;
if (!CheckFullHash(entry, doDelete)) {
replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete);
nextNeedsRebuild_ = true;
} else if (nextTexture_ != nullptr) {
// Secondary cache picked a different texture, use it.
entry = nextTexture_;
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
}
}
// Okay, now actually rebuild the texture if needed.
if (nextNeedsRebuild_) {
BuildTexture(entry, replaceImages);
}
entry->lastFrame = gpuStats.numFlips;
if (entry->framebuffer) {
ApplyTextureFramebuffer(entry, entry->framebuffer);
} else {
BindTexture(entry);
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
}
}
void TextureCacheCommon::Clear(bool delete_them) {
ForgetLastTexture();
if (delete_them) {
for (TexCache::iterator iter = cache_.begin(); iter != cache_.end(); ++iter) {
ReleaseTexture(iter->second.get());
}
for (TexCache::iterator iter = secondCache_.begin(); iter != secondCache_.end(); ++iter) {
ReleaseTexture(iter->second.get());
}
}
if (cache_.size() + secondCache_.size()) {
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache_.size() + secondCache_.size()));
cache_.clear();
secondCache_.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
}
fbTexInfo_.clear();
videos_.clear();
}
void TextureCacheCommon::DeleteTexture(TexCache::iterator it) {
ReleaseTexture(it->second.get());
auto fbInfo = fbTexInfo_.find(it->first);
if (fbInfo != fbTexInfo_.end()) {
fbTexInfo_.erase(fbInfo);
}
cacheSizeEstimate_ -= EstimateTexMemoryUsage(it->second.get());
cache_.erase(it);
}
bool TextureCacheCommon::CheckFullHash(TexCacheEntry *entry, bool &doDelete) {
bool hashFail = false;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
u32 fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
if (fullhash != entry->fullhash) {
hashFail = true;
} else {
@@ -1001,9 +1393,9 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelet
if (g_Config.bTextureSecondaryCache) {
if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) {
u64 secondKey = fullhash | (u64)entry->cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
TexCache::iterator secondIter = secondCache_.find(secondKey);
if (secondIter != secondCache_.end()) {
TexCacheEntry *secondEntry = secondIter->second.get();
if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
@@ -1015,7 +1407,8 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelet
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
// Is this wise? We simply copy the entry.
secondCache_[secondKey].reset(new TexCacheEntry(*entry));
doDelete = false;
}
}
@@ -1046,28 +1439,28 @@ void TextureCacheCommon::Invalidate(u32 addr, int size, GPUInvalidationType type
endKey = (u64)-1;
}
for (TexCache::iterator iter = cache.lower_bound(startKey), end = cache.upper_bound(endKey); iter != end; ++iter) {
u32 texAddr = iter->second.addr;
u32 texEnd = iter->second.addr + iter->second.sizeInRAM;
for (TexCache::iterator iter = cache_.lower_bound(startKey), end = cache_.upper_bound(endKey); iter != end; ++iter) {
u32 texAddr = iter->second->addr;
u32 texEnd = iter->second->addr + iter->second->sizeInRAM;
if (texAddr < addr_end && addr < texEnd) {
if (iter->second.GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
iter->second.SetHashStatus(TexCacheEntry::STATUS_HASHING);
if (iter->second->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
iter->second->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
if (type != GPU_INVALIDATE_ALL) {
gpuStats.numTextureInvalidations++;
// Start it over from 0 (unless it's safe.)
iter->second.numFrames = type == GPU_INVALIDATE_SAFE ? 256 : 0;
iter->second->numFrames = type == GPU_INVALIDATE_SAFE ? 256 : 0;
if (type == GPU_INVALIDATE_SAFE) {
u32 diff = gpuStats.numFlips - iter->second.lastFrame;
u32 diff = gpuStats.numFlips - iter->second->lastFrame;
// We still need to mark if the texture is frequently changing, even if it's safely changing.
if (diff < TEXCACHE_FRAME_CHANGE_FREQUENT) {
iter->second.status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
iter->second->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
iter->second.framesUntilNextFullHash = 0;
} else if (!iter->second.framebuffer) {
iter->second.invalidHint++;
iter->second->framesUntilNextFullHash = 0;
} else if (!iter->second->framebuffer) {
iter->second->invalidHint++;
}
}
}
@@ -1084,12 +1477,12 @@ void TextureCacheCommon::InvalidateAll(GPUInvalidationType /*unused*/) {
}
timesInvalidatedAllThisFrame_++;
for (TexCache::iterator iter = cache.begin(), end = cache.end(); iter != end; ++iter) {
if (iter->second.GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
iter->second.SetHashStatus(TexCacheEntry::STATUS_HASHING);
for (TexCache::iterator iter = cache_.begin(), end = cache_.end(); iter != end; ++iter) {
if (iter->second->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
iter->second->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
if (!iter->second.framebuffer) {
iter->second.invalidHint++;
if (!iter->second->framebuffer) {
iter->second->invalidHint++;
}
}
}
+124 -100
View File
@@ -19,6 +19,7 @@
#include <map>
#include <vector>
#include <memory>
#include "Common/CommonTypes.h"
#include "Common/MemoryUtil.h"
@@ -45,6 +46,8 @@ enum FramebufferNotification {
// Note: only used when hash backoff is disabled.
#define TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST 33
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame
struct VirtualFramebuffer;
class CachedTextureVulkan;
@@ -76,7 +79,96 @@ struct SamplerCacheKey {
}
};
// Wow this is starting to grow big. Soon need to start looking at resizing it.
// Must stay a POD.
struct TexCacheEntry {
// After marking STATUS_UNRELIABLE, if it stays the same this many frames we'll trust it again.
const static int FRAMES_REGAIN_TRUST = 1000;
enum Status {
STATUS_HASHING = 0x00,
STATUS_RELIABLE = 0x01, // Don't bother rehashing.
STATUS_UNRELIABLE = 0x02, // Always recheck hash.
STATUS_MASK = 0x03,
STATUS_ALPHA_UNKNOWN = 0x04,
STATUS_ALPHA_FULL = 0x00, // Has no alpha channel, or always full alpha.
STATUS_ALPHA_SIMPLE = 0x08, // Like above, but also has 0 alpha (e.g. 5551.)
STATUS_ALPHA_MASK = 0x0c,
STATUS_CHANGE_FREQUENT = 0x10, // Changes often (less than 6 frames in between.)
STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail.
STATUS_DEPALETTIZE = 0x40, // Needs to go through a depalettize pass.
STATUS_TO_SCALE = 0x80, // Pending texture scaling in a later frame.
STATUS_IS_SCALED = 0x100, // Has been scaled (can't be replaceImages'd.)
STATUS_FREE_CHANGE = 0x200, // Allow one change before marking "frequent".
};
// Status, but int so we can zero initialize.
int status;
u32 addr;
u32 hash;
VirtualFramebuffer *framebuffer; // if null, not sourced from an FBO.
u32 sizeInRAM;
int lastFrame;
int numFrames;
int numInvalidated;
u32 framesUntilNextFullHash;
u8 format;
u8 maxLevel;
u16 dim;
u16 bufw;
union {
u32 textureName;
void *texturePtr;
CachedTextureVulkan *vkTex;
};
#ifdef _WIN32
void *textureView; // Used by D3D11 only for the shader resource view.
#endif
int invalidHint;
u32 fullhash;
u32 cluthash;
float lodBias;
u16 maxSeenV;
// Cache the current filter settings so we can avoid setting it again.
// (OpenGL madness where filter settings are attached to each texture. Unused in Vulkan).
u8 magFilt;
u8 minFilt;
bool sClamp;
bool tClamp;
Status GetHashStatus() {
return Status(status & STATUS_MASK);
}
void SetHashStatus(Status newStatus) {
status = (status & ~STATUS_MASK) | newStatus;
}
Status GetAlphaStatus() {
return Status(status & STATUS_ALPHA_MASK);
}
void SetAlphaStatus(Status newStatus) {
status = (status & ~STATUS_ALPHA_MASK) | newStatus;
}
void SetAlphaStatus(Status newStatus, int level) {
// For non-level zero, only set more restrictive.
if (newStatus == STATUS_ALPHA_UNKNOWN || level == 0) {
SetAlphaStatus(newStatus);
} else if (newStatus == STATUS_ALPHA_SIMPLE && GetAlphaStatus() == STATUS_ALPHA_FULL) {
SetAlphaStatus(STATUS_ALPHA_SIMPLE);
}
}
bool Matches(u16 dim2, u8 format2, u8 maxLevel2) const;
u64 CacheKey() const;
static u64 CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash);
};
class FramebufferManagerCommon;
// Can't be unordered_map, we use lower_bound ... although for some reason that compiles on MSVC.
typedef std::map<u64, std::unique_ptr<TexCacheEntry>> TexCache;
class TextureCacheCommon {
public:
@@ -86,11 +178,16 @@ public:
void LoadClut(u32 clutAddr, u32 loadBytes);
bool GetCurrentClutBuffer(GPUDebugBuffer &buffer);
void SetTexture(bool force = false);
void ApplyTexture();
bool SetOffsetTexture(u32 offset);
void Invalidate(u32 addr, int size, GPUInvalidationType type);
void InvalidateAll(GPUInvalidationType type);
void ClearNextFrame();
virtual void ForgetLastTexture() = 0;
virtual void InvalidateLastTexture(TexCacheEntry *entry = nullptr) = 0;
virtual void Clear(bool delete_them);
// FramebufferManager keeps TextureCache updated about what regions of memory are being rendered to.
void NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg);
@@ -100,106 +197,25 @@ public:
int AttachedDrawingHeight();
size_t NumLoadedTextures() const {
return cache.size();
return cache_.size();
}
bool IsFakeMipmapChange() {
return PSP_CoreParameter().compat.flags().FakeMipmapChange && gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST;
}
// Wow this is starting to grow big. Soon need to start looking at resizing it.
// Must stay a POD.
struct TexCacheEntry {
// After marking STATUS_UNRELIABLE, if it stays the same this many frames we'll trust it again.
const static int FRAMES_REGAIN_TRUST = 1000;
enum Status {
STATUS_HASHING = 0x00,
STATUS_RELIABLE = 0x01, // Don't bother rehashing.
STATUS_UNRELIABLE = 0x02, // Always recheck hash.
STATUS_MASK = 0x03,
STATUS_ALPHA_UNKNOWN = 0x04,
STATUS_ALPHA_FULL = 0x00, // Has no alpha channel, or always full alpha.
STATUS_ALPHA_SIMPLE = 0x08, // Like above, but also has 0 alpha (e.g. 5551.)
STATUS_ALPHA_MASK = 0x0c,
STATUS_CHANGE_FREQUENT = 0x10, // Changes often (less than 6 frames in between.)
STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail.
STATUS_DEPALETTIZE = 0x40, // Needs to go through a depalettize pass.
STATUS_TO_SCALE = 0x80, // Pending texture scaling in a later frame.
STATUS_IS_SCALED = 0x100, // Has been scaled (can't be replaceImages'd.)
STATUS_FREE_CHANGE = 0x200, // Allow one change before marking "frequent".
};
// Status, but int so we can zero initialize.
int status;
u32 addr;
u32 hash;
VirtualFramebuffer *framebuffer; // if null, not sourced from an FBO.
u32 sizeInRAM;
int lastFrame;
int numFrames;
int numInvalidated;
u32 framesUntilNextFullHash;
u8 format;
u8 maxLevel;
u16 dim;
u16 bufw;
union {
u32 textureName;
void *texturePtr;
CachedTextureVulkan *vkTex;
};
#ifdef _WIN32
void *textureView; // Used by D3D11 only for the shader resource view.
#endif
int invalidHint;
u32 fullhash;
u32 cluthash;
float lodBias;
u16 maxSeenV;
// Cache the current filter settings so we can avoid setting it again.
// (OpenGL madness where filter settings are attached to each texture. Unused in Vulkan).
u8 magFilt;
u8 minFilt;
bool sClamp;
bool tClamp;
Status GetHashStatus() {
return Status(status & STATUS_MASK);
}
void SetHashStatus(Status newStatus) {
status = (status & ~STATUS_MASK) | newStatus;
}
Status GetAlphaStatus() {
return Status(status & STATUS_ALPHA_MASK);
}
void SetAlphaStatus(Status newStatus) {
status = (status & ~STATUS_ALPHA_MASK) | newStatus;
}
void SetAlphaStatus(Status newStatus, int level) {
// For non-level zero, only set more restrictive.
if (newStatus == STATUS_ALPHA_UNKNOWN || level == 0) {
SetAlphaStatus(newStatus);
} else if (newStatus == STATUS_ALPHA_SIMPLE && GetAlphaStatus() == STATUS_ALPHA_FULL) {
SetAlphaStatus(STATUS_ALPHA_SIMPLE);
}
}
bool Matches(u16 dim2, u8 format2, u8 maxLevel2) const;
u64 CacheKey() const;
static u64 CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash);
};
protected:
virtual void BindTexture(TexCacheEntry *entry) = 0;
virtual void Unbind() = 0;
virtual void ReleaseTexture(TexCacheEntry *entry) = 0;
void DeleteTexture(TexCache::iterator it);
void Decimate();
bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete);
// Can't be unordered_map, we use lower_bound ... although for some reason that compiles on MSVC.
typedef std::map<u64, TexCacheEntry> TexCache;
virtual void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) = 0;
bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
virtual void BuildTexture(TexCacheEntry *const entry, bool replaceImages) = 0;
virtual void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) = 0;
bool CheckFullHash(TexCacheEntry *entry, bool &doDelete);
// Separate to keep main texture cache size down.
struct AttachedFramebufferInfo {
@@ -248,8 +264,12 @@ protected:
}
}
static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
Draw::DrawContext *draw_;
TextureReplacer replacer;
TextureReplacer replacer_;
FramebufferManagerCommon *framebufferManager_;
bool clearCacheNextFrame_;
@@ -259,10 +279,10 @@ protected:
int texelsScaledThisFrame_;
int timesInvalidatedAllThisFrame_;
TexCache cache;
TexCache cache_;
u32 cacheSizeEstimate_;
TexCache secondCache;
TexCache secondCache_;
u32 secondCacheSizeEstimate_;
std::vector<VirtualFramebuffer *> fbCache_;
@@ -270,12 +290,14 @@ protected:
std::map<u32, int> videos_;
SimpleBuf<u32> tmpTexBuf32;
SimpleBuf<u16> tmpTexBuf16;
SimpleBuf<u32> tmpTexBufRearrange;
SimpleBuf<u32> tmpTexBuf32_;
SimpleBuf<u16> tmpTexBuf16_;
SimpleBuf<u32> tmpTexBufRearrange_;
TexCacheEntry *nextTexture_;
u32 clutHash_ = 0;
// Raw is where we keep the original bytes. Converted is where we swap colors if necessary.
u32 *clutBufRaw_;
u32 *clutBufConverted_;
@@ -296,17 +318,19 @@ protected:
bool nextNeedsRehash_;
bool nextNeedsChange_;
bool nextNeedsRebuild_;
bool isBgraBackend_;
};
inline bool TextureCacheCommon::TexCacheEntry::Matches(u16 dim2, u8 format2, u8 maxLevel2) const {
inline bool TexCacheEntry::Matches(u16 dim2, u8 format2, u8 maxLevel2) const {
return dim == dim2 && format == format2 && maxLevel == maxLevel2;
}
inline u64 TextureCacheCommon::TexCacheEntry::CacheKey() const {
inline u64 TexCacheEntry::CacheKey() const {
return CacheKey(addr, format, dim, cluthash);
}
inline u64 TextureCacheCommon::TexCacheEntry::CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash) {
inline u64 TexCacheEntry::CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash) {
u64 cachekey = ((u64)(addr & 0x3FFFFFFF) << 32) | dim;
bool hasClut = (format & 4) != 0;
if (hasClut) {
+29 -476
View File
@@ -43,20 +43,6 @@
#define INVALID_TEX (ID3D11ShaderResourceView *)(-1LL)
// If a texture hasn't been seen for this many frames, get rid of it.
#define TEXTURE_KILL_AGE 200
#define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
#define TEXTURE_SECOND_KILL_AGE 100
// Try to be prime to other decimation intervals.
#define TEXCACHE_DECIMATION_INTERVAL 13
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame
#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM
#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024
static const D3D11_INPUT_ELEMENT_DESC g_QuadVertexElements[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,},
@@ -114,8 +100,8 @@ TextureCacheD3D11::TextureCacheD3D11(Draw::DrawContext *draw)
device_ = (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE);
context_ = (ID3D11DeviceContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT);
isBgraBackend_ = true;
lastBoundTexture = INVALID_TEX;
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
HRESULT result = 0;
@@ -134,40 +120,17 @@ void TextureCacheD3D11::SetFramebufferManager(FramebufferManagerD3D11 *fbManager
framebufferManager_ = fbManager;
}
void TextureCacheD3D11::Clear(bool delete_them) {
// ID3D11ShaderResourceView *srv = nullptr;
// context_->PSSetShaderResources(0, 1, &srv);
lastBoundTexture = INVALID_TEX;
if (delete_them) {
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr);
ReleaseTexture(&iter->second);
}
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr);
ReleaseTexture(&iter->second);
}
void TextureCacheD3D11::ReleaseTexture(TexCacheEntry *entry) {
ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr;
ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)entry->textureView;
if (texture) {
texture->Release();
entry->texturePtr = nullptr;
}
if (cache.size() + secondCache.size()) {
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size()));
cache.clear();
secondCache.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
if (view) {
view->Release();
entry->textureView = nullptr;
}
fbTexInfo_.clear();
videos_.clear();
}
void TextureCacheD3D11::DeleteTexture(TexCache::iterator it) {
ReleaseTexture(&it->second);
auto fbInfo = fbTexInfo_.find(it->first);
if (fbInfo != fbTexInfo_.end()) {
fbTexInfo_.erase(fbInfo);
}
cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second);
cache.erase(it);
}
void TextureCacheD3D11::ForgetLastTexture() {
@@ -177,49 +140,10 @@ void TextureCacheD3D11::ForgetLastTexture() {
context_->PSSetShaderResources(0, 1, &nullTex);
}
// Removes old textures.
void TextureCacheD3D11::Decimate() {
if (--decimationCounter_ <= 0) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
} else {
return;
}
if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
ID3D11ShaderResourceView *srv = nullptr;
context_->PSSetShaderResources(0, 1, &srv);
void TextureCacheD3D11::InvalidateLastTexture(TexCacheEntry *entry) {
if (!entry || entry->texturePtr == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) {
if (iter->second.lastFrame + killAge < gpuStats.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
}
if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) {
const u32 had = secondCacheSizeEstimate_;
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_KILL_AGE < gpuStats.numFlips) {
ReleaseTexture(&iter->second);
secondCache.erase(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
}
DecimateVideos();
}
DXGI_FORMAT getClutDestFormatD3D11(GEPaletteFormat format) {
@@ -298,10 +222,6 @@ void TextureCacheD3D11::StartFrame() {
}
}
static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
void TextureCacheD3D11::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
const u32 clutBaseBytes = clutBase * (clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16));
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
@@ -335,8 +255,16 @@ void TextureCacheD3D11::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBa
clutLastFormat_ = gstate.clutformat;
}
inline u32 TextureCacheD3D11::GetCurrentClutHash() {
return clutHash_;
void TextureCacheD3D11::BindTexture(TexCacheEntry *entry) {
ID3D11ShaderResourceView *textureView = DxView(entry);
if (textureView != lastBoundTexture) {
context_->PSSetShaderResources(0, 1, &textureView);
lastBoundTexture = textureView;
}
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
ID3D11SamplerState *state = samplerCache_.GetOrCreateSampler(device_, key);
context_->PSSetSamplers(0, 1, &state);
}
void TextureCacheD3D11::Unbind() {
@@ -344,66 +272,6 @@ void TextureCacheD3D11::Unbind() {
context_->PSSetShaderResources(0, 1, &nullView);
}
void TextureCacheD3D11::ApplyTexture() {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
bool replaceImages = false;
if (nextNeedsRebuild_) {
if (nextNeedsRehash_) {
// Update the hash on the texture.
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
}
if (nextNeedsChange_) {
// This texture existed previously, let's handle the change.
replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true);
}
// We actually build afterward (shared with rehash rebuild.)
} else if (nextNeedsRehash_) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true;
if (!CheckFullHash(entry, doDelete)) {
replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete);
nextNeedsRebuild_ = true;
} else if (nextTexture_ != nullptr) {
// Secondary cache picked a different texture, use it.
entry = nextTexture_;
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
}
}
// Okay, now actually rebuild the texture if needed.
if (nextNeedsRebuild_) {
BuildTexture(entry, replaceImages);
}
entry->lastFrame = gpuStats.numFlips;
if (entry->framebuffer) {
ApplyTextureFramebuffer(entry, entry->framebuffer);
} else {
ID3D11ShaderResourceView *textureView = DxView(entry);
if (textureView != lastBoundTexture) {
context_->PSSetShaderResources(0, 1, &textureView);
lastBoundTexture = textureView;
}
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
ID3D11SamplerState *state = samplerCache_.GetOrCreateSampler(device_, key);
context_->PSSetSamplers(0, 1, &state);
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
}
}
class TextureShaderApplierD3D11 {
public:
struct Pos {
@@ -578,321 +446,6 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra
lastBoundTexture = INVALID_TEX;
}
void TextureCacheD3D11::SetTexture(bool force) {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
lastBoundTexture = INVALID_TEX;
return;
}
#endif
if (force) {
lastBoundTexture = INVALID_TEX;
}
u32 texaddr = gstate.getTextureAddress(0);
if (!Memory::IsValidAddress(texaddr)) {
// Bind a null texture and return.
ID3D11ShaderResourceView *srv = nullptr;
context_->PSSetShaderResources(0, 1, &srv);
lastBoundTexture = INVALID_TEX;
return;
}
const u16 dim = gstate.getTextureDimension(0);
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
GETextureFormat format = gstate.getTextureFormat();
if (format >= 11) {
ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format);
// TODO: Better assumption?
format = GE_TFMT_5650;
}
bool hasClut = gstate.isTextureFormatIndexed();
// Ignore uncached/kernel when caching.
u32 cluthash;
if (hasClut) {
if (clutLastFormat_ != gstate.clutformat) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
}
cluthash = GetCurrentClutHash() ^ gstate.clutformat;
} else {
cluthash = 0;
}
u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash);
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr));
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.needShaderTexClamp = false;
gstate_c.bgraTexture = true;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (iter != cache.end()) {
entry = &iter->second;
// Validate the texture still matches the cache entry.
bool match = entry->Matches(dim, format, maxLevel);
const char *reason = "different params";
// Check for FBO - slow!
if (entry->framebuffer) {
if (match) {
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
SetTextureFramebuffer(entry, entry->framebuffer);
return;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer(entry, texaddr, entry->framebuffer);
reason = "detached framebuf";
match = false;
}
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true;
entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
}
if (match) {
if (entry->lastFrame != gpuStats.numFlips) {
u32 diff = gpuStats.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
// Exponential backoff up to 512 frames. Textures are often reused.
if (entry->numFrames > 32) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + ((intptr_t)entry->texturePtr & 15);
} else {
entry->framesUntilNextFullHash = entry->numFrames;
}
rehash = true;
} else {
entry->framesUntilNextFullHash -= diff;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) {
entry->invalidHint = 0;
rehash = true;
}
if (texhash != entry->hash) {
match = false;
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) {
if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false;
reason = "scaling";
}
}
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
if (entry->texturePtr != lastBoundTexture) {
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
}
if (rehash) {
// Update in case any of these changed.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
}
nextTexture_ = entry;
nextNeedsRehash_ = rehash;
nextNeedsChange_ = false;
// Might need a rebuild if the hash fails.
nextNeedsRebuild_ = false;
VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr);
return; //Done!
} else {
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
TexCacheEntry entryNew = { 0 };
cache[cachekey] = entryNew;
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
entry = &cache[cachekey];
if (g_Config.bTextureBackoffCache) {
entry->status = TexCacheEntry::STATUS_HASHING;
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
// Before we go reading the texture from memory, let's check for render-to-texture.
// We must do this early so we have the right w/h.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
}
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
}
nextTexture_ = entry;
nextNeedsRehash_ = entry->framebuffer == nullptr;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_ = true;
}
bool TextureCacheD3D11::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) {
bool hashFail = false;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
if (fullhash != entry->fullhash) {
hashFail = true;
} else {
if (g_Config.bTextureBackoffCache) {
if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) {
// Reset to STATUS_HASHING.
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
} else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) {
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
if (hashFail) {
entry->status |= TexCacheEntry::STATUS_UNRELIABLE;
if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) {
if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) {
entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE;
} else {
entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
entry->numFrames = 0;
// Don't give up just yet. Let's try the secondary cache if it's been invalidated before.
// If it's failed a bunch of times, then the second cache is just wasting time and VRAM.
if (g_Config.bTextureSecondaryCache) {
if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) {
u64 secondKey = fullhash | (u64)entry->cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
nextTexture_ = secondEntry;
return true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
// We know it failed, so update the full hash right away.
entry->fullhash = fullhash;
return false;
}
return true;
}
bool TextureCacheD3D11::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
bool replaceImages = false;
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
if (initialMatch && standardScaleFactor_ == 1 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) {
// Actually, if size and number of levels match, let's try to avoid deleting and recreating.
// Instead, let's use glTexSubImage to replace the images.
replaceImages = true;
} else {
if (entry->texturePtr == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
}
ReleaseTexture(entry);
entry->status &= ~TexCacheEntry::STATUS_IS_SCALED;
}
}
// Clear the reliable bit if set.
if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if (entry->cluthash != 0) {
const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) {
if (it->second.cluthash != entry->cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
return replaceImages;
}
void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry, bool replaceImages) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
@@ -951,10 +504,10 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry, bool replaceIma
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0;
u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h);
ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h);
if (replaced.GetSize(0, w, h)) {
if (replaceImages) {
// Since we're replacing the texture, we can't replace the image inside.
@@ -1041,7 +594,7 @@ DXGI_FORMAT TextureCacheD3D11::GetDestFormat(GETextureFormat format, GEPaletteFo
}
}
TextureCacheD3D11::TexCacheEntry::Status TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) {
TexCacheEntry::Status TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) {
CheckAlphaResult res;
switch (dstFmt) {
case DXGI_FORMAT_B4G4R4A4_UNORM:
@@ -1142,8 +695,8 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &
u32 *pixelData;
int decPitch;
if (scaleFactor > 1) {
tmpTexBufRearrange.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange.data();
tmpTexBufRearrange_.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange_.data();
// We want to end up with a neatly packed texture for scaling.
decPitch = w * bpp;
mapData = new u32[(w * scaleFactor) * (h * scaleFactor)];
@@ -1187,7 +740,7 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &
entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN);
}
if (replacer.Enabled()) {
if (replacer_.Enabled()) {
ReplacedTextureDecodeInfo replacedInfo;
replacedInfo.cachekey = entry.CacheKey();
replacedInfo.hash = entry.fullhash;
@@ -1197,7 +750,7 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &
replacedInfo.scaleFactor = scaleFactor;
replacedInfo.fmt = FromD3D11Format(dstFmt);
replacer.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h);
replacer_.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h);
}
}
+6 -27
View File
@@ -46,9 +46,6 @@ public:
TextureCacheD3D11(Draw::DrawContext *draw);
~TextureCacheD3D11();
void SetTexture(bool force = false);
void Clear(bool delete_them);
void StartFrame();
void SetFramebufferManager(FramebufferManagerD3D11 *fbManager);
@@ -63,28 +60,24 @@ public:
bool DecodeTexture(u8 *output, const GPUgstate &state);
void ForgetLastTexture() override;
void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override;
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key);
void ApplyTexture();
protected:
void BindTexture(TexCacheEntry *entry) override;
void Unbind() override;
void ReleaseTexture(TexCacheEntry *entry) override;
private:
void Decimate(); // Run this once per frame to get rid of old textures.
void DeleteTexture(TexCache::iterator it);
void UpdateSamplingParams(TexCacheEntry &entry, SamplerCacheKey &key);
void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt);
DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
TexCacheEntry::Status CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h);
u32 GetCurrentClutHash();
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete);
bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
void BuildTexture(TexCacheEntry *const entry, bool replaceImages);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override;
void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override;
ID3D11Device *device_;
ID3D11DeviceContext *context_;
@@ -95,25 +88,11 @@ private:
ID3D11ShaderResourceView *DxView(TexCacheEntry *entry) {
return (ID3D11ShaderResourceView *)entry->textureView;
}
void ReleaseTexture(TexCacheEntry *entry) {
ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr;
ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)entry->textureView;
if (texture) {
texture->Release();
entry->texturePtr = nullptr;
}
if (view) {
view->Release();
entry->textureView = nullptr;
}
}
TextureScalerD3D11 scaler;
SamplerCacheD3D11 samplerCache_;
u32 clutHash_;
ID3D11ShaderResourceView *lastBoundTexture;
int decimationCounter_;
+23 -476
View File
@@ -43,20 +43,6 @@ namespace DX9 {
#define INVALID_TEX (LPDIRECT3DTEXTURE9)(-1)
// If a texture hasn't been seen for this many frames, get rid of it.
#define TEXTURE_KILL_AGE 200
#define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
#define TEXTURE_SECOND_KILL_AGE 100
// Try to be prime to other decimation intervals.
#define TEXCACHE_DECIMATION_INTERVAL 13
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame
#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM
#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024
static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
@@ -66,7 +52,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
TextureCacheDX9::TextureCacheDX9(Draw::DrawContext *draw)
: TextureCacheCommon(draw) {
lastBoundTexture = INVALID_TEX;
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
isBgraBackend_ = true;
D3DCAPS9 pCaps;
ZeroMemory(&pCaps, sizeof(pCaps));
@@ -98,39 +84,13 @@ void TextureCacheDX9::SetFramebufferManager(FramebufferManagerDX9 *fbManager) {
framebufferManager_ = fbManager;
}
void TextureCacheDX9::Clear(bool delete_them) {
pD3Ddevice->SetTexture(0, NULL);
lastBoundTexture = INVALID_TEX;
if (delete_them) {
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr);
ReleaseTexture(&iter->second);
}
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr);
ReleaseTexture(&iter->second);
}
void TextureCacheDX9::ReleaseTexture(TexCacheEntry *entry) {
DEBUG_LOG(G3D, "Deleting texture %p", entry->texturePtr);
LPDIRECT3DTEXTURE9 &texture = DxTex(entry);
if (texture) {
texture->Release();
texture = nullptr;
}
if (cache.size() + secondCache.size()) {
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size()));
cache.clear();
secondCache.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
}
fbTexInfo_.clear();
videos_.clear();
}
void TextureCacheDX9::DeleteTexture(TexCache::iterator it) {
ReleaseTexture(&it->second);
auto fbInfo = fbTexInfo_.find(it->first);
if (fbInfo != fbTexInfo_.end()) {
fbTexInfo_.erase(fbInfo);
}
cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second);
cache.erase(it);
}
void TextureCacheDX9::ForgetLastTexture() {
@@ -138,48 +98,10 @@ void TextureCacheDX9::ForgetLastTexture() {
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
}
// Removes old textures.
void TextureCacheDX9::Decimate() {
if (--decimationCounter_ <= 0) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
} else {
return;
}
if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
pD3Ddevice->SetTexture(0, NULL);
void TextureCacheDX9::InvalidateLastTexture(TexCacheEntry *entry) {
if (!entry || entry->texturePtr == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) {
if (iter->second.lastFrame + killAge < gpuStats.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
}
if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) {
const u32 had = secondCacheSizeEstimate_;
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_KILL_AGE < gpuStats.numFlips) {
ReleaseTexture(&iter->second);
secondCache.erase(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
}
DecimateVideos();
}
D3DFORMAT getClutDestFormat(GEPaletteFormat format) {
@@ -311,10 +233,6 @@ void TextureCacheDX9::StartFrame() {
}
static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
void TextureCacheDX9::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
const u32 clutBaseBytes = clutBase * (clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16));
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
@@ -348,72 +266,19 @@ void TextureCacheDX9::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase
clutLastFormat_ = gstate.clutformat;
}
inline u32 TextureCacheDX9::GetCurrentClutHash() {
return clutHash_;
void TextureCacheDX9::BindTexture(TexCacheEntry *entry) {
LPDIRECT3DTEXTURE9 texture = DxTex(entry);
if (texture != lastBoundTexture) {
pD3Ddevice->SetTexture(0, texture);
lastBoundTexture = texture;
}
UpdateSamplingParams(*entry, false);
}
void TextureCacheDX9::Unbind() {
pD3Ddevice->SetTexture(0, NULL);
}
void TextureCacheDX9::ApplyTexture() {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
bool replaceImages = false;
if (nextNeedsRebuild_) {
if (nextNeedsRehash_) {
// Update the hash on the texture.
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
}
if (nextNeedsChange_) {
// This texture existed previously, let's handle the change.
replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true);
}
// We actually build afterward (shared with rehash rebuild.)
} else if (nextNeedsRehash_) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true;
if (!CheckFullHash(entry, doDelete)) {
replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete);
nextNeedsRebuild_ = true;
} else if (nextTexture_ != nullptr) {
// Secondary cache picked a different texture, use it.
entry = nextTexture_;
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
}
}
// Okay, now actually rebuild the texture if needed.
if (nextNeedsRebuild_) {
BuildTexture(entry, replaceImages);
}
entry->lastFrame = gpuStats.numFlips;
if (entry->framebuffer) {
ApplyTextureFramebuffer(entry, entry->framebuffer);
} else {
LPDIRECT3DTEXTURE9 texture = DxTex(entry);
if (texture != lastBoundTexture) {
pD3Ddevice->SetTexture(0, texture);
lastBoundTexture = texture;
}
UpdateSamplingParams(*entry, false);
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
}
}
class TextureShaderApplierDX9 {
public:
struct Pos {
@@ -591,324 +456,6 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame
lastBoundTexture = INVALID_TEX;
}
void TextureCacheDX9::SetTexture(bool force) {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
lastBoundTexture = INVALID_TEX;
return;
}
#endif
if (force) {
lastBoundTexture = INVALID_TEX;
}
u8 level = 0;
if (IsFakeMipmapChange())
level = (gstate.texlevel >> 20) & 0xF;
u32 texaddr = gstate.getTextureAddress(level);
if (!Memory::IsValidAddress(texaddr)) {
// Bind a null texture and return.
pD3Ddevice->SetTexture(0, NULL);
lastBoundTexture = INVALID_TEX;
return;
}
const u16 dim = gstate.getTextureDimension(level);
int w = gstate.getTextureWidth(level);
int h = gstate.getTextureHeight(level);
GETextureFormat format = gstate.getTextureFormat();
if (format >= 11) {
ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format);
// TODO: Better assumption?
format = GE_TFMT_5650;
}
bool hasClut = gstate.isTextureFormatIndexed();
// Ignore uncached/kernel when caching.
u32 cluthash;
if (hasClut) {
if (clutLastFormat_ != gstate.clutformat) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
}
cluthash = GetCurrentClutHash() ^ gstate.clutformat;
} else {
cluthash = 0;
}
u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash);
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr));
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.needShaderTexClamp = false;
gstate_c.bgraTexture = true;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (iter != cache.end()) {
entry = &iter->second;
// Validate the texture still matches the cache entry.
bool match = entry->Matches(dim, format, maxLevel);
const char *reason = "different params";
// Check for FBO - slow!
if (entry->framebuffer) {
if (match) {
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
SetTextureFramebuffer(entry, entry->framebuffer);
return;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer(entry, texaddr, entry->framebuffer);
reason = "detached framebuf";
match = false;
}
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true;
entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
}
if (match) {
if (entry->lastFrame != gpuStats.numFlips) {
u32 diff = gpuStats.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
// Exponential backoff up to 512 frames. Textures are often reused.
if (entry->numFrames > 32) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + ((intptr_t)entry->texturePtr & 15);
} else {
entry->framesUntilNextFullHash = entry->numFrames;
}
rehash = true;
} else {
entry->framesUntilNextFullHash -= diff;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) {
entry->invalidHint = 0;
rehash = true;
}
if (texhash != entry->hash) {
match = false;
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) {
if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false;
reason = "scaling";
}
}
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
if (entry->texturePtr != lastBoundTexture) {
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
}
if (rehash) {
// Update in case any of these changed.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
}
nextTexture_ = entry;
nextNeedsRehash_ = rehash;
nextNeedsChange_ = false;
// Might need a rebuild if the hash fails.
nextNeedsRebuild_= false;
VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr);
return; //Done!
} else {
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
TexCacheEntry entryNew = {0};
cache[cachekey] = entryNew;
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
entry = &cache[cachekey];
if (g_Config.bTextureBackoffCache) {
entry->status = TexCacheEntry::STATUS_HASHING;
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
// Before we go reading the texture from memory, let's check for render-to-texture.
// We must do this early so we have the right w/h.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
}
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
}
nextTexture_ = entry;
nextNeedsRehash_ = entry->framebuffer == nullptr;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_= true;
}
bool TextureCacheDX9::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) {
bool hashFail = false;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
if (fullhash != entry->fullhash) {
hashFail = true;
} else {
if (g_Config.bTextureBackoffCache) {
if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) {
// Reset to STATUS_HASHING.
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
} else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) {
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
if (hashFail) {
entry->status |= TexCacheEntry::STATUS_UNRELIABLE;
if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) {
if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) {
entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE;
} else {
entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
entry->numFrames = 0;
// Don't give up just yet. Let's try the secondary cache if it's been invalidated before.
// If it's failed a bunch of times, then the second cache is just wasting time and VRAM.
if (g_Config.bTextureSecondaryCache) {
if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) {
u64 secondKey = fullhash | (u64)entry->cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
nextTexture_ = secondEntry;
return true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
// We know it failed, so update the full hash right away.
entry->fullhash = fullhash;
return false;
}
return true;
}
bool TextureCacheDX9::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
bool replaceImages = false;
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
if (initialMatch && standardScaleFactor_ == 1 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) {
// Actually, if size and number of levels match, let's try to avoid deleting and recreating.
// Instead, let's use glTexSubImage to replace the images.
replaceImages = true;
} else {
if (entry->texturePtr == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
}
ReleaseTexture(entry);
entry->status &= ~TexCacheEntry::STATUS_IS_SCALED;
}
}
// Clear the reliable bit if set.
if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if (entry->cluthash != 0) {
const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) {
if (it->second.cluthash != entry->cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
return replaceImages;
}
void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry, bool replaceImages) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
@@ -966,10 +513,10 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry, bool replaceImage
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0;
u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h);
ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h);
if (replaced.GetSize(0, w, h)) {
if (replaceImages) {
// Since we're replacing the texture, we can't replace the image inside.
@@ -1058,7 +605,7 @@ D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat
}
}
TextureCacheDX9::TexCacheEntry::Status TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) {
TexCacheEntry::Status TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) {
CheckAlphaResult res;
switch (dstFmt) {
case D3DFMT_A4R4G4B4:
@@ -1154,8 +701,8 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re
u32 *pixelData = (u32 *)rect.pBits;
int decPitch = rect.Pitch;
if (scaleFactor > 1) {
tmpTexBufRearrange.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange.data();
tmpTexBufRearrange_.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange_.data();
// We want to end up with a neatly packed texture for scaling.
decPitch = w * bpp;
}
@@ -1191,7 +738,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re
entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN);
}
if (replacer.Enabled()) {
if (replacer_.Enabled()) {
ReplacedTextureDecodeInfo replacedInfo;
replacedInfo.cachekey = entry.CacheKey();
replacedInfo.hash = entry.fullhash;
@@ -1201,7 +748,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re
replacedInfo.scaleFactor = scaleFactor;
replacedInfo.fmt = FromD3D9Format(dstFmt);
replacer.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h);
replacer_.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h);
}
}
+6 -22
View File
@@ -40,9 +40,6 @@ public:
TextureCacheDX9(Draw::DrawContext *draw);
~TextureCacheDX9();
void SetTexture(bool force = false);
void Clear(bool delete_them);
void StartFrame();
void SetFramebufferManager(FramebufferManagerDX9 *fbManager);
@@ -57,44 +54,31 @@ public:
bool DecodeTexture(u8 *output, const GPUgstate &state);
void ForgetLastTexture() override;
void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override;
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight);
void ApplyTexture();
protected:
void BindTexture(TexCacheEntry *entry) override;
void Unbind() override;
void ReleaseTexture(TexCacheEntry *entry) override;
private:
void Decimate(); // Run this once per frame to get rid of old textures.
void DeleteTexture(TexCache::iterator it);
void UpdateSamplingParams(TexCacheEntry &entry, bool force);
void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt);
D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
TexCacheEntry::Status CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h);
u32 GetCurrentClutHash();
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete);
bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
void BuildTexture(TexCacheEntry *const entry, bool replaceImages);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override;
void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override;
LPDIRECT3DTEXTURE9 &DxTex(TexCacheEntry *entry) {
return *(LPDIRECT3DTEXTURE9 *)&entry->texturePtr;
}
void ReleaseTexture(TexCacheEntry *entry) {
LPDIRECT3DTEXTURE9 &texture = DxTex(entry);
if (texture) {
texture->Release();
texture = nullptr;
}
}
TextureScalerDX9 scaler;
u32 clutHash_;
LPDIRECT3DVERTEXDECLARATION9 pFramebufferVertexDecl;
LPDIRECT3DTEXTURE9 lastBoundTexture;
+27 -422
View File
@@ -43,33 +43,16 @@
#include <xmmintrin.h>
#endif
// If a texture hasn't been seen for this many frames, get rid of it.
#define TEXTURE_KILL_AGE 200
#define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
#define TEXTURE_SECOND_KILL_AGE 100
// Try to be prime to other decimation intervals.
#define TEXCACHE_DECIMATION_INTERVAL 13
#define TEXCACHE_NAME_CACHE_SIZE 16
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame
#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in GL
#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024
#ifndef GL_UNPACK_ROW_LENGTH
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#endif
#define INVALID_TEX -1
#define TEXCACHE_NAME_CACHE_SIZE 16
TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw)
: TextureCacheCommon(draw) {
timesInvalidatedAllThisFrame_ = 0;
lastBoundTexture = INVALID_TEX;
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel);
SetupTextureDecoder();
@@ -86,88 +69,19 @@ void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager)
framebufferManager_ = fbManager;
}
void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry) {
DEBUG_LOG(G3D, "Deleting texture %i", entry->textureName);
glDeleteTextures(1, &entry->textureName);
}
void TextureCacheGLES::Clear(bool delete_them) {
glBindTexture(GL_TEXTURE_2D, 0);
lastBoundTexture = INVALID_TEX;
TextureCacheCommon::Clear(delete_them);
if (delete_them) {
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %i", iter->second.textureName);
glDeleteTextures(1, &iter->second.textureName);
}
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %i", iter->second.textureName);
glDeleteTextures(1, &iter->second.textureName);
}
if (!nameCache_.empty()) {
glDeleteTextures((GLsizei)nameCache_.size(), &nameCache_[0]);
nameCache_.clear();
}
}
if (cache.size() + secondCache.size()) {
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size()));
cache.clear();
secondCache.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
}
fbTexInfo_.clear();
videos_.clear();
}
void TextureCacheGLES::DeleteTexture(TexCache::iterator it) {
glDeleteTextures(1, &it->second.textureName);
auto fbInfo = fbTexInfo_.find(it->first);
if (fbInfo != fbTexInfo_.end()) {
fbTexInfo_.erase(fbInfo);
}
cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second);
cache.erase(it);
}
// Removes old textures.
void TextureCacheGLES::Decimate() {
if (--decimationCounter_ <= 0) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
} else {
return;
}
if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
glBindTexture(GL_TEXTURE_2D, 0);
lastBoundTexture = INVALID_TEX;
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) {
if (iter->second.lastFrame + killAge < gpuStats.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
}
if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) {
const u32 had = secondCacheSizeEstimate_;
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) {
glDeleteTextures(1, &iter->second.textureName);
secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(&iter->second);
secondCache.erase(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
}
DecimateVideos();
}
GLenum getClutDestFormat(GEPaletteFormat format) {
@@ -307,7 +221,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int n
}
void TextureCacheGLES::StartFrame() {
lastBoundTexture = INVALID_TEX;
InvalidateLastTexture();
timesInvalidatedAllThisFrame_ = 0;
if (texelsScaledThisFrame_) {
@@ -322,10 +236,6 @@ void TextureCacheGLES::StartFrame() {
}
}
static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16));
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
@@ -367,10 +277,6 @@ void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBas
clutLastFormat_ = gstate.clutformat;
}
inline u32 TextureCacheGLES::GetCurrentClutHash() {
return clutHash_;
}
// #define DEBUG_TEXTURES
#ifdef DEBUG_TEXTURES
@@ -413,64 +319,16 @@ bool SetDebugTexture() {
}
#endif
void TextureCacheGLES::Unbind() {
glBindTexture(GL_TEXTURE_2D, 0);
void TextureCacheGLES::BindTexture(TexCacheEntry *entry) {
if (entry->textureName != lastBoundTexture) {
glBindTexture(GL_TEXTURE_2D, entry->textureName);
lastBoundTexture = entry->textureName;
}
UpdateSamplingParams(*entry, false);
}
void TextureCacheGLES::ApplyTexture() {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
bool replaceImages = false;
if (nextNeedsRebuild_) {
if (nextNeedsRehash_) {
// Update the hash on the texture.
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
}
if (nextNeedsChange_) {
// This texture existed previously, let's handle the change.
replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true);
}
// We actually build afterward (shared with rehash rebuild.)
} else if (nextNeedsRehash_) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true;
if (!CheckFullHash(entry, doDelete)) {
replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete);
nextNeedsRebuild_ = true;
} else if (nextTexture_ != nullptr) {
// Secondary cache picked a different texture, use it.
entry = nextTexture_;
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
}
}
// Okay, now actually rebuild the texture if needed.
if (nextNeedsRebuild_) {
BuildTexture(entry, replaceImages);
}
entry->lastFrame = gpuStats.numFlips;
if (entry->framebuffer) {
ApplyTextureFramebuffer(entry, entry->framebuffer);
} else {
if (entry->textureName != lastBoundTexture) {
glBindTexture(GL_TEXTURE_2D, entry->textureName);
lastBoundTexture = entry->textureName;
}
UpdateSamplingParams(*entry, false);
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
}
void TextureCacheGLES::Unbind() {
glBindTexture(GL_TEXTURE_2D, 0);
}
class TextureShaderApplier {
@@ -673,259 +531,6 @@ GLenum ToGLESFormat(ReplacedTextureFormat fmt) {
}
}
void TextureCacheGLES::SetTexture(bool force) {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
lastBoundTexture = INVALID_TEX;
return;
}
#endif
if (force) {
lastBoundTexture = INVALID_TEX;
}
u8 level = 0;
if (IsFakeMipmapChange())
level = (gstate.texlevel >> 20) & 0xF;
u32 texaddr = gstate.getTextureAddress(level);
if (!Memory::IsValidAddress(texaddr)) {
// Bind a null texture and return.
glBindTexture(GL_TEXTURE_2D, 0);
lastBoundTexture = INVALID_TEX;
return;
}
const u16 dim = gstate.getTextureDimension(level);
int w = gstate.getTextureWidth(level);
int h = gstate.getTextureHeight(level);
GETextureFormat format = gstate.getTextureFormat();
if (format >= 11) {
ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format);
// TODO: Better assumption?
format = GE_TFMT_5650;
}
bool hasClut = gstate.isTextureFormatIndexed();
// Ignore uncached/kernel when caching.
u32 cluthash;
if (hasClut) {
if (clutLastFormat_ != gstate.clutformat) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
}
cluthash = GetCurrentClutHash() ^ gstate.clutformat;
} else {
cluthash = 0;
}
u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash);
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.needShaderTexClamp = false;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
if (iter != cache.end()) {
entry = &iter->second;
// Validate the texture still matches the cache entry.
bool match = entry->Matches(dim, format, maxLevel);
const char *reason = "different params";
// Check for FBO - slow!
if (entry->framebuffer) {
if (match) {
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
SetTextureFramebuffer(entry, entry->framebuffer);
return;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer(entry, texaddr, entry->framebuffer);
reason = "detached framebuf";
match = false;
}
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true;
entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
}
if (match) {
if (entry->lastFrame != gpuStats.numFlips) {
u32 diff = gpuStats.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
// Exponential backoff up to 512 frames. Textures are often reused.
if (entry->numFrames > 32) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15);
} else {
entry->framesUntilNextFullHash = entry->numFrames;
}
rehash = true;
} else {
entry->framesUntilNextFullHash -= diff;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) {
entry->invalidHint = 0;
rehash = true;
}
if (texhash != entry->hash) {
match = false;
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) {
if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false;
reason = "scaling";
}
}
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
if (entry->textureName != lastBoundTexture) {
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
}
if (rehash) {
// Update in case any of these changed.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
}
nextTexture_ = entry;
nextNeedsRehash_ = rehash;
nextNeedsChange_ = false;
// Might need a rebuild if the hash fails.
nextNeedsRebuild_= false;
VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr);
return; //Done!
} else {
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
TexCacheEntry entryNew = {0};
cache[cachekey] = entryNew;
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
entry = &cache[cachekey];
if (g_Config.bTextureBackoffCache) {
entry->status = TexCacheEntry::STATUS_HASHING;
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
// Before we go reading the texture from memory, let's check for render-to-texture.
// We must do this early so we have the right w/h.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
}
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
}
nextTexture_ = entry;
nextNeedsRehash_ = entry->framebuffer == nullptr;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_= true;
}
bool TextureCacheGLES::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
bool replaceImages = false;
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
if (initialMatch && standardScaleFactor_ == 1 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) {
// Actually, if size and number of levels match, let's try to avoid deleting and recreating.
// Instead, let's use glTexSubImage to replace the images.
replaceImages = true;
} else {
if (entry->textureName == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
}
glDeleteTextures(1, &entry->textureName);
entry->status &= ~TexCacheEntry::STATUS_IS_SCALED;
}
}
// Clear the reliable bit if set.
if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if (entry->cluthash != 0) {
const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) {
if (it->second.cluthash != entry->cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
return replaceImages;
}
void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImages) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
@@ -985,10 +590,10 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0;
u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h);
ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h);
if (replaced.GetSize(0, w, h)) {
if (replaceImages) {
// Since we're replacing the texture, we can't replace the image inside.
@@ -1159,9 +764,9 @@ void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteF
decPitch = bufw * pixelSize;
}
tmpTexBufRearrange.resize(std::max(w, bufw) * h);
if (DecodeTextureLevel((u8 *)tmpTexBufRearrange.data(), decPitch, format, clutformat, texaddr, level, bufw, true, UseBGRA8888())) {
finalBuf = tmpTexBufRearrange.data();
tmpTexBufRearrange_.resize(std::max(w, bufw) * h);
if (DecodeTextureLevel((u8 *)tmpTexBufRearrange_.data(), decPitch, format, clutformat, texaddr, level, bufw, true, UseBGRA8888())) {
finalBuf = tmpTexBufRearrange_.data();
} else {
finalBuf = nullptr;
}
@@ -1173,7 +778,7 @@ void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteF
return finalBuf;
}
TextureCacheGLES::TexCacheEntry::Status TextureCacheGLES::CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h) {
TexCacheEntry::Status TextureCacheGLES::CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h) {
CheckAlphaResult res;
switch (dstFmt) {
case GL_UNSIGNED_SHORT_4_4_4_4:
@@ -1209,10 +814,10 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r
if (replaced.GetSize(level, w, h)) {
PROFILE_THIS_SCOPE("replacetex");
tmpTexBufRearrange.resize(w * h);
tmpTexBufRearrange_.resize(w * h);
int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2;
replaced.Load(level, tmpTexBufRearrange.data(), bpp * w);
pixelData = tmpTexBufRearrange.data();
replaced.Load(level, tmpTexBufRearrange_.data(), bpp * w);
pixelData = tmpTexBufRearrange_.data();
dstFmt = ToGLESFormat(replaced.Format(level));
@@ -1249,7 +854,7 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r
entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN);
}
if (replacer.Enabled()) {
if (replacer_.Enabled()) {
ReplacedTextureDecodeInfo replacedInfo;
replacedInfo.cachekey = entry.CacheKey();
replacedInfo.hash = entry.fullhash;
@@ -1260,7 +865,7 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r
replacedInfo.fmt = FromGLESFormat(dstFmt, useBGRA);
int bpp = dstFmt == GL_UNSIGNED_BYTE ? 4 : 2;
replacer.NotifyTextureDecoded(replacedInfo, pixelData, (useUnpack ? bufw : w) * bpp, level, w, h);
replacer_.NotifyTextureDecoded(replacedInfo, pixelData, (useUnpack ? bufw : w) * bpp, level, w, h);
}
}
+13 -14
View File
@@ -46,8 +46,6 @@ public:
TextureCacheGLES(Draw::DrawContext *draw);
~TextureCacheGLES();
void SetTexture(bool force = false);
void Clear(bool delete_them);
void StartFrame();
@@ -63,9 +61,14 @@ public:
}
void ForgetLastTexture() override {
lastBoundTexture = -1;
lastBoundTexture = INVALID_TEX;
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
}
void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override {
if (!entry || entry->textureName == lastBoundTexture) {
lastBoundTexture = INVALID_TEX;
}
}
u32 AllocTextureName();
@@ -74,32 +77,26 @@ public:
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight);
void ApplyTexture();
protected:
void BindTexture(TexCacheEntry *entry) override;
void Unbind() override;
void ReleaseTexture(TexCacheEntry *entry) override;
private:
void Decimate(); // Run this once per frame to get rid of old textures.
void DeleteTexture(TexCache::iterator it);
void UpdateSamplingParams(TexCacheEntry &entry, bool force);
void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, bool replaceImages, int scaleFactor, GLenum dstFmt);
GLenum GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
void *DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufw = 0);
TexCacheEntry::Status CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h);
u32 GetCurrentClutHash();
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override;
bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
void BuildTexture(TexCacheEntry *const entry, bool replaceImages);
void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override;
std::vector<u32> nameCache_;
TextureScalerGLES scaler;
u32 clutHash_;
u32 lastBoundTexture;
float maxAnisotropyLevel;
@@ -107,6 +104,8 @@ private:
DepalShaderCacheGLES *depalShaderCache_;
ShaderManagerGLES *shaderManager_;
DrawEngineGLES *drawEngine_;
enum { INVALID_TEX = -1 };
};
GLenum getClutDestFormat(GEPaletteFormat format);
+4 -2
View File
@@ -668,7 +668,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
}
if (textureNeedsApply) {
textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler);
textureCache_->ApplyTexture();
textureCache_->GetVulkanHandles(imageView, sampler);
if (imageView == VK_NULL_HANDLE)
imageView = nullTexture_->GetImageView();
if (sampler == VK_NULL_HANDLE)
@@ -768,7 +769,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
// to use a "pre-clear" render pass, for high efficiency on tilers.
if (result.action == SW_DRAW_PRIMITIVES) {
if (textureNeedsApply) {
textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler);
textureCache_->ApplyTexture();
textureCache_->GetVulkanHandles(imageView, sampler);
if (imageView == VK_NULL_HANDLE)
imageView = nullTexture_->GetImageView();
if (sampler == VK_NULL_HANDLE)
+1 -1
View File
@@ -123,7 +123,7 @@ public:
void DirtyAllUBOs();
VulkanPushBuffer *GetPushBufferForTextureData() {
return frame_[curFrame_].pushUBO;
return frame_[curFrame_ & 1].pushUBO;
}
const DrawEngineVulkanStats &GetStats() const {
+2
View File
@@ -1,3 +1,4 @@
// Copyright (c) 2015- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
@@ -411,6 +412,7 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
framebufferManagerVulkan_->Init();
framebufferManagerVulkan_->SetTextureCache(textureCacheVulkan_);
framebufferManagerVulkan_->SetDrawEngine(&drawEngine_);
framebufferManagerVulkan_->SetShaderManager(shaderManagerVulkan_);
textureCacheVulkan_->SetFramebufferManager(framebufferManagerVulkan_);
textureCacheVulkan_->SetDepalShaderCache(&depalShaderCache_);
textureCacheVulkan_->SetShaderManager(shaderManagerVulkan_);
+29 -477
View File
@@ -48,22 +48,10 @@
#include <xmmintrin.h>
#endif
// If a texture hasn't been seen for this many frames, get rid of it.
#define TEXTURE_KILL_AGE 200
#define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
#define TEXTURE_SECOND_KILL_AGE 100
// Try to be prime to other decimation intervals.
#define TEXCACHE_DECIMATION_INTERVAL 13
#define TEXCACHE_NAME_CACHE_SIZE 16
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame
#define TEXCACHE_MIN_PRESSURE (16 * 1024 * 1024) // Total in GL
#define TEXCACHE_SECOND_MIN_PRESSURE (4 * 1024 * 1024)
#define TEXCACHE_MIN_SLAB_SIZE (4 * 1024 * 1024)
#define TEXCACHE_MAX_SLAB_SIZE (32 * 1024 * 1024)
@@ -144,7 +132,6 @@ TextureCacheVulkan::TextureCacheVulkan(Draw::DrawContext *draw, VulkanContext *v
texelsScaledThisFrame_(0) {
timesInvalidatedAllThisFrame_ = 0;
lastBoundTexture = nullptr;
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
allocator_ = new VulkanDeviceAllocator(vulkan_, TEXCACHE_MIN_SLAB_SIZE, TEXCACHE_MAX_SLAB_SIZE);
SetupTextureDecoder();
@@ -192,80 +179,9 @@ void TextureCacheVulkan::DeviceRestore(VulkanContext *vulkan) {
samplerCache_.DeviceRestore(vulkan);
}
void TextureCacheVulkan::Clear(bool delete_them) {
lastBoundTexture = nullptr;
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.vkTex);
delete iter->second.vkTex;
}
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) {
DEBUG_LOG(G3D, "Deleting texture %p", iter->second.vkTex);
delete iter->second.vkTex;
}
if (cache.size() + secondCache.size()) {
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size()));
cache.clear();
secondCache.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
}
fbTexInfo_.clear();
videos_.clear();
}
void TextureCacheVulkan::DeleteTexture(TexCache::iterator it) {
delete it->second.vkTex;
auto fbInfo = fbTexInfo_.find(it->first);
if (fbInfo != fbTexInfo_.end()) {
fbTexInfo_.erase(fbInfo);
}
cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second);
cache.erase(it);
}
// Removes old textures.
void TextureCacheVulkan::Decimate() {
if (--decimationCounter_ <= 0) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
} else {
return;
}
if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
lastBoundTexture = nullptr;
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) {
if (iter->second.lastFrame + killAge < gpuStats.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
}
if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) {
const u32 had = secondCacheSizeEstimate_;
for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) {
delete iter->second.vkTex;
secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(&iter->second);
secondCache.erase(iter++);
} else {
++iter;
}
}
VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
}
DecimateVideos();
void TextureCacheVulkan::ReleaseTexture(TexCacheEntry *entry) {
DEBUG_LOG(G3D, "Deleting texture %p", entry->vkTex);
delete entry->vkTex;
}
VkFormat getClutDestFormatVulkan(GEPaletteFormat format) {
@@ -376,10 +292,6 @@ void TextureCacheVulkan::EndFrame() {
}
}
static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
void TextureCacheVulkan::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) {
const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16));
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
@@ -413,78 +325,25 @@ void TextureCacheVulkan::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutB
clutLastFormat_ = gstate.clutformat;
}
inline u32 TextureCacheVulkan::GetCurrentClutHash() {
return clutHash_;
void TextureCacheVulkan::BindTexture(TexCacheEntry *entry) {
if (!entry || !entry->vkTex) {
imageView_ = VK_NULL_HANDLE;
sampler_ = VK_NULL_HANDLE;
return;
}
imageView_ = entry->vkTex->texture_->GetImageView();
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
sampler_ = samplerCache_.GetOrCreateSampler(key);
}
void TextureCacheVulkan::Unbind() {
imageView_ = VK_NULL_HANDLE;
sampler_ = VK_NULL_HANDLE;
}
void TextureCacheVulkan::ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler) {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
imageView = VK_NULL_HANDLE;
sampler = VK_NULL_HANDLE;
return;
}
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
if (nextNeedsRebuild_) {
if (nextNeedsRehash_) {
// Update the hash on the texture.
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
}
if (nextNeedsChange_) {
// This texture existed previously, let's handle the change.
HandleTextureChange(entry, nextChangeReason_, false, true);
}
// We actually build afterward (shared with rehash rebuild.)
} else if (nextNeedsRehash_) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true;
if (!CheckFullHash(entry, doDelete)) {
HandleTextureChange(entry, "hash fail", true, doDelete);
nextNeedsRebuild_ = true;
} else if (nextTexture_ != nullptr) {
// Secondary cache picked a different texture, use it.
entry = nextTexture_;
nextTexture_ = nullptr;
UpdateMaxSeenV(entry, gstate.isModeThrough());
}
}
// Okay, now actually rebuild the texture if needed.
if (nextNeedsRebuild_) {
BuildTexture(entry, uploadBuffer);
}
entry->lastFrame = gpuStats.numFlips;
VkCommandBuffer cmd = nullptr;
if (entry->framebuffer) {
ApplyTextureFramebuffer(cmd, entry, entry->framebuffer, imageView, sampler);
} else if (entry->vkTex) {
imageView = entry->vkTex->texture_->GetImageView();
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
sampler = samplerCache_.GetOrCreateSampler(key);
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
lastBoundTexture = entry->vkTex;
} else {
imageView = VK_NULL_HANDLE;
sampler = VK_NULL_HANDLE;
}
}
void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &imageView, VkSampler &sampler) {
void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) {
DepalShaderVulkan *depal = nullptr;
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
if ((entry->status & TexCacheEntry::STATUS_DEPALETTIZE) && !g_Config.bDisableSlowFramebufEffects) {
@@ -558,7 +417,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn
uv[3] = UV(uvleft, uvtop);
}
shaderManager_->DirtyLastShader();
shaderManagerVulkan_->DirtyLastShader();
//depalFBO->EndPass(cmd);
//depalFBO->TransitionToTexture(cmd);
@@ -590,7 +449,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
key.mipEnable = false;
sampler = samplerCache_.GetOrCreateSampler(key);
sampler_ = samplerCache_.GetOrCreateSampler(key);
lastBoundTexture = nullptr;
}
@@ -614,314 +473,7 @@ VkFormat ToVulkanFormat(ReplacedTextureFormat fmt) {
}
}
void TextureCacheVulkan::SetTexture() {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
lastBoundTexture = nullptr;
return;
}
#endif
u8 level = 0;
if (IsFakeMipmapChange())
level = (gstate.texlevel >> 20) & 0xF;
u32 texaddr = gstate.getTextureAddress(level);
if (!Memory::IsValidAddress(texaddr)) {
// Bind a null texture and return.
lastBoundTexture = nullptr;
return;
}
const u16 dim = gstate.getTextureDimension(level);
int w = gstate.getTextureWidth(level);
int h = gstate.getTextureHeight(level);
if (texaddr == 0x04000000 && w == 2 && h == 2) {
// Nonsense bootup texture. Discard.
}
GETextureFormat format = gstate.getTextureFormat();
if (format >= 11) {
ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format);
// TODO: Better assumption?
format = GE_TFMT_5650;
}
bool hasClut = gstate.isTextureFormatIndexed();
// Ignore uncached/kernel when caching.
u32 cluthash;
if (hasClut) {
if (clutLastFormat_ != gstate.clutformat) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
}
cluthash = GetCurrentClutHash() ^ gstate.clutformat;
} else {
cluthash = 0;
}
u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash);
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.needShaderTexClamp = false;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
if (iter != cache.end()) {
entry = &iter->second;
// Validate the texture still matches the cache entry.
bool match = entry->Matches(dim, format, maxLevel);
const char *reason = "different params";
// Check for FBO - slow!
if (entry->framebuffer) {
if (match) {
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
SetTextureFramebuffer(entry, entry->framebuffer);
return;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer(entry, texaddr, entry->framebuffer);
reason = "detached framebuf";
match = false;
}
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true;
entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
}
if (match) {
if (entry->lastFrame != gpuStats.numFlips) {
u32 diff = gpuStats.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
// Exponential backoff up to 512 frames. Textures are often reused.
if (entry->numFrames > 32) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15);
} else {
entry->framesUntilNextFullHash = entry->numFrames;
}
rehash = true;
} else {
entry->framesUntilNextFullHash -= diff;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) {
entry->invalidHint = 0;
rehash = true;
}
if (texhash != entry->hash) {
match = false;
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) {
if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false;
reason = "scaling";
}
}
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
if (entry->vkTex != lastBoundTexture) {
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
}
if (rehash) {
// Update in case any of these changed.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
}
nextTexture_ = entry;
nextNeedsRehash_ = rehash;
nextNeedsChange_ = false;
// Might need a rebuild if the hash fails.
nextNeedsRebuild_= false;
VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr);
return; //Done!
} else {
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
TexCacheEntry entryNew = { 0 };
cache[cachekey] = entryNew;
if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
}
entry = &cache[cachekey];
if (g_Config.bTextureBackoffCache) {
entry->status = TexCacheEntry::STATUS_HASHING;
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8;
entry->bufw = bufw;
entry->cluthash = cluthash;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
// Before we go reading the texture from memory, let's check for render-to-texture.
// We must do this early so we have the right w/h.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
}
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
}
nextTexture_ = entry;
nextNeedsRehash_ = entry->framebuffer == nullptr;
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_= true;
}
bool TextureCacheVulkan::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) {
bool hashFail = false;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry);
if (fullhash != entry->fullhash) {
hashFail = true;
} else {
if (g_Config.bTextureBackoffCache) {
if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) {
// Reset to STATUS_HASHING.
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
} else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) {
entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
if (hashFail) {
entry->status |= TexCacheEntry::STATUS_UNRELIABLE;
if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) {
if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) {
entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE;
} else {
entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
}
}
entry->numFrames = 0;
// Don't give up just yet. Let's try the secondary cache if it's been invalidated before.
// If it's failed a bunch of times, then the second cache is just wasting time and VRAM.
if (g_Config.bTextureSecondaryCache) {
if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) {
u64 secondKey = fullhash | (u64)entry->cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
nextTexture_ = secondEntry;
return true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
// We know it failed, so update the full hash right away.
entry->fullhash = fullhash;
return false;
}
return true;
}
bool TextureCacheVulkan::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
if (entry->vkTex == lastBoundTexture) {
lastBoundTexture = nullptr;
}
delete entry->vkTex;
entry->vkTex = nullptr;
entry->status &= ~TexCacheEntry::STATUS_IS_SCALED;
}
// Clear the reliable bit if set.
if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
entry->SetHashStatus(TexCacheEntry::STATUS_HASHING);
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if (entry->cluthash != 0) {
const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) {
if (it->second.cluthash != entry->cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
return false;
}
void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuffer *uploadBuffer) {
void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, bool replaceImages) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
// For the estimate, we assume cluts always point to 8888 for simplicity.
@@ -975,10 +527,10 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0;
u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h);
ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h);
if (replaced.GetSize(0, w, h)) {
// We're replacing, so we won't scale.
scaleFactor = 1;
@@ -1074,7 +626,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff
lastBoundTexture = entry->vkTex;
ReplacedTextureDecodeInfo replacedInfo;
if (replacer.Enabled() && !replaced.Valid()) {
if (replacer_.Enabled() && !replaced.Valid()) {
replacedInfo.cachekey = cachekey;
replacedInfo.hash = entry->fullhash;
replacedInfo.addr = entry->addr;
@@ -1099,7 +651,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff
int size = stride * mipHeight;
uint32_t bufferOffset;
VkBuffer texBuf;
void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf);
void *data = drawEngine_->GetPushBufferForTextureData()->Push(size, &bufferOffset, &texBuf);
if (replaced.Valid()) {
replaced.Load(i, data, stride);
} else {
@@ -1109,8 +661,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff
break;
} else
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt);
if (replacer.Enabled()) {
replacer.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight);
if (replacer_.Enabled()) {
replacer_.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight);
}
}
entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
@@ -1149,7 +701,7 @@ VkFormat TextureCacheVulkan::GetDestFormat(GETextureFormat format, GEPaletteForm
}
}
TextureCacheVulkan::TexCacheEntry::Status TextureCacheVulkan::CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h) {
TexCacheEntry::Status TextureCacheVulkan::CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h) {
CheckAlphaResult res;
switch (dstFmt) {
case VULKAN_4444_FORMAT:
@@ -1187,8 +739,8 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt
u32 *pixelData = (u32 *)writePtr;
int decPitch = rowPitch;
if (scaleFactor > 1) {
tmpTexBufRearrange.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange.data();
tmpTexBufRearrange_.resize(std::max(bufw, w) * h);
pixelData = tmpTexBufRearrange_.data();
// We want to end up with a neatly packed texture for scaling.
decPitch = w * bpp;
}
+21 -16
View File
@@ -67,8 +67,6 @@ public:
TextureCacheVulkan(Draw::DrawContext *draw, VulkanContext *vulkan);
~TextureCacheVulkan();
void SetTexture();
void Clear(bool delete_them);
void StartFrame();
void EndFrame();
@@ -80,7 +78,7 @@ public:
depalShaderCache_ = dpCache;
}
void SetShaderManager(ShaderManagerVulkan *sm) {
shaderManager_ = sm;
shaderManagerVulkan_ = sm;
}
void SetDrawEngine(DrawEngineVulkan *td) {
drawEngine_ = td;
@@ -90,27 +88,32 @@ public:
lastBoundTexture = nullptr;
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
}
void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override {
if (!entry || entry->vkTex == lastBoundTexture) {
lastBoundTexture = nullptr;
}
}
void ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler);
void GetVulkanHandles(VkImageView &imageView, VkSampler &sampler) {
imageView = imageView_;
sampler = sampler_;
}
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key);
protected:
void BindTexture(TexCacheEntry *entry) override;
void Unbind() override;
void ReleaseTexture(TexCacheEntry *entry) override;
private:
void Decimate(); // Run this once per frame to get rid of old textures.
void DeleteTexture(TexCache::iterator it);
void UpdateSamplingParams(TexCacheEntry &entry, SamplerCacheKey &key);
void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt);
VkFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
TexCacheEntry::Status CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h);
u32 GetCurrentClutHash();
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple);
void ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &image, VkSampler &sampler);
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete);
bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
void BuildTexture(TexCacheEntry *const entry, VulkanPushBuffer *uploadBuffer);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override;
void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override;
VulkanContext *vulkan_;
VulkanDeviceAllocator *allocator_;
@@ -119,8 +122,6 @@ private:
TextureScalerVulkan scaler;
u32 clutHash_;
CachedTextureVulkan *lastBoundTexture;
int decimationCounter_;
@@ -129,8 +130,12 @@ private:
FramebufferManagerVulkan *framebufferManagerVulkan_;
DepalShaderCacheVulkan *depalShaderCache_;
ShaderManagerVulkan *shaderManager_;
ShaderManagerVulkan *shaderManagerVulkan_;
DrawEngineVulkan *drawEngine_;
// Bound state to emulate an API similar to the others
VkImageView imageView_ = VK_NULL_HANDLE;
VkSampler sampler_ = VK_NULL_HANDLE;
};
VkFormat getClutDestFormatVulkan(GEPaletteFormat format);