Merge pull request #8720 from unknownbrackets/tex-range

Move texture decode/hash to after vertex decode
This commit is contained in:
Henrik Rydgård 2016-05-07 22:15:19 +02:00
commit 2c84411426
9 changed files with 633 additions and 433 deletions

View file

@ -125,25 +125,25 @@ void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sCl
}
}
void TextureCacheCommon::UpdateMaxSeenV(bool throughMode) {
void TextureCacheCommon::UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode) {
// If the texture is >= 512 pixels tall...
if (nextTexture_->dim >= 0x900) {
if (entry->dim >= 0x900) {
// Texture scale/offset and gen modes don't apply in through.
// So we can optimize how much of the texture we look at.
if (throughMode) {
if (nextTexture_->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) {
if (entry->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) {
// Let's not hash less than 272, we might use more later and have to rehash. 272 is very common.
nextTexture_->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV);
} else if (gstate_c.vertBounds.maxV > nextTexture_->maxSeenV) {
entry->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV);
} else if (gstate_c.vertBounds.maxV > entry->maxSeenV) {
// The max height changed, so we're better off hashing the entire thing.
nextTexture_->maxSeenV = 512;
nextTexture_->status |= TexCacheEntry::STATUS_FREE_CHANGE;
entry->maxSeenV = 512;
entry->status |= TexCacheEntry::STATUS_FREE_CHANGE;
}
} else {
// Otherwise, we need to reset to ensure we use the whole thing.
// Can't tell how much is used.
// TODO: We could tell for texcoord UV gen, and apply scale to max?
nextTexture_->maxSeenV = 512;
entry->maxSeenV = 512;
}
}
}

View file

@ -150,7 +150,7 @@ protected:
u32 EstimateTexMemoryUsage(const TexCacheEntry *entry);
void GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel, u32 addr);
void UpdateMaxSeenV(bool throughMode);
void UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode);
virtual bool AttachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer, u32 texaddrOffset = 0) = 0;

View file

@ -697,22 +697,61 @@ void TextureCacheDX9::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebu
}
void TextureCacheDX9::ApplyTexture() {
if (nextTexture_ == nullptr) {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
if (nextTexture_->framebuffer) {
ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer);
} else {
UpdateMaxSeenV(gstate.isModeThrough());
UpdateMaxSeenV(entry, gstate.isModeThrough());
LPDIRECT3DTEXTURE9 texture = DxTex(nextTexture_);
pD3Ddevice->SetTexture(0, texture);
lastBoundTexture = texture;
UpdateSamplingParams(*nextTexture_, false);
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());
}
}
nextTexture_ = nullptr;
// 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;
}
}
void TextureCacheDX9::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) {
@ -909,7 +948,6 @@ bool TextureCacheDX9::SetOffsetTexture(u32 offset) {
if (success && entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return true;
}
@ -966,7 +1004,6 @@ void TextureCacheDX9::SetTexture(bool force) {
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr));
u32 fullhash = 0;
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
@ -974,7 +1011,6 @@ void TextureCacheDX9::SetTexture(bool force) {
gstate_c.bgraTexture = true;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
bool replaceImages = false;
if (iter != cache.end()) {
entry = &iter->second;
@ -990,7 +1026,6 @@ void TextureCacheDX9::SetTexture(bool force) {
}
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
} else {
// Make sure we re-evaluate framebuffers.
@ -1001,7 +1036,6 @@ void TextureCacheDX9::SetTexture(bool force) {
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
bool doDelete = true;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
@ -1038,67 +1072,10 @@ void TextureCacheDX9::SetTexture(bool force) {
rehash = true;
}
bool hashFail = false;
if (texhash != entry->hash) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry);
hashFail = true;
rehash = false;
}
if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, 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) {
match = false;
reason = "hash fail";
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)cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(dim, format, maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
entry = secondEntry;
match = true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
@ -1113,48 +1090,27 @@ void TextureCacheDX9::SetTexture(bool force) {
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
entry->lastFrame = gpuStats.numFlips;
LPDIRECT3DTEXTURE9 texture = DxTex(entry);
if (texture != lastBoundTexture) {
nextTexture_ = entry;
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
if (entry->texturePtr != lastBoundTexture) {
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
}
UpdateSamplingParams(*entry, false);
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 {
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason);
if (doDelete) {
if (entry->maxLevel == maxLevel && entry->dim == gstate.getTextureDimension(0) && entry->format == format && standardScaleFactor_ == 1) {
// 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);
}
}
// 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 (cluthash != 0) {
const u64 cachekeyMin = (u64)(texaddr & 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 != cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
@ -1171,36 +1127,136 @@ void TextureCacheDX9::SetTexture(bool force) {
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
}
if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
nextNeedsChange_ = false;
}
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->format = format;
entry->lastFrame = gpuStats.numFlips;
entry->framebuffer = 0;
entry->maxLevel = maxLevel;
entry->lodBias = 0.0f;
entry->dim = dim;
entry->bufw = bufw;
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->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash;
entry->cluthash = cluthash;
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
nextTexture_ = entry;
nextNeedsRehash_ = true;
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) {
// 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);
}
}
// 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;
// For the estimate, we assume cluts always point to 8888 for simplicity.
cacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
@ -1208,6 +1264,7 @@ void TextureCacheDX9::SetTexture(bool force) {
// Should just always create one here or something (like GLES.)
// Before we go reading the texture from memory, let's check for render-to-texture.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
@ -1216,13 +1273,19 @@ void TextureCacheDX9::SetTexture(bool force) {
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
}
if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
// Proceeding here can cause a crash.
return;
}
// Adjust maxLevel to actually present levels..
bool badMipSizes = false;
for (u32 i = 0; i <= maxLevel; i++) {
int maxLevel = entry->maxLevel;
for (int i = 0; i <= maxLevel; i++) {
// If encountering levels pointing to nothing, adjust max level.
u32 levelTexaddr = gstate.getTextureAddress(i);
if (!Memory::IsValidAddress(levelTexaddr)) {
@ -1230,7 +1293,7 @@ void TextureCacheDX9::SetTexture(bool force) {
break;
}
if (i > 0) {
if (i > 0 && gstate_c.Supports(GPU_SUPPORTS_TEXTURE_LOD_CONTROL)) {
int tw = gstate.getTextureWidth(i);
int th = gstate.getTextureHeight(i);
if (tw != 1 && tw != (gstate.getTextureWidth(i - 1) >> 1))
@ -1246,7 +1309,7 @@ void TextureCacheDX9::SetTexture(bool force) {
}
// If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient.
D3DFORMAT dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat());
D3DFORMAT dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());
int scaleFactor = standardScaleFactor_;
@ -1256,6 +1319,9 @@ void TextureCacheDX9::SetTexture(bool force) {
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
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);
if (replaced.GetSize(0, w, h)) {
// We're replacing, so we won't scale.
@ -1302,7 +1368,7 @@ void TextureCacheDX9::SetTexture(bool force) {
// Mipmapping is only enabled when texture scaling is disabled.
if (maxLevel > 0 && scaleFactor == 1) {
for (u32 i = 1; i <= maxLevel; i++) {
for (int i = 1; i <= maxLevel; i++) {
LoadTextureLevel(*entry, replaced, i, maxLevel, replaceImages, scaleFactor, dstFmt);
}
}
@ -1311,11 +1377,7 @@ void TextureCacheDX9::SetTexture(bool force) {
entry->SetAlphaStatus(TexCacheEntry::Status(replaced.AlphaStatus()));
}
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
nextTexture_ = entry;
UpdateSamplingParams(*nextTexture_, true);
UpdateSamplingParams(*entry, true);
}
D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const {

View file

@ -92,6 +92,10 @@ private:
void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
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);
LPDIRECT3DTEXTURE9 &DxTex(TexCacheEntry *entry) {
return *(LPDIRECT3DTEXTURE9 *)&entry->texturePtr;
}
@ -126,6 +130,11 @@ private:
FramebufferManagerDX9 *framebufferManager_;
DepalShaderCacheDX9 *depalShaderCache_;
ShaderManagerDX9 *shaderManager_;
const char *nextChangeReason_;
bool nextNeedsRehash_;
bool nextNeedsChange_;
bool nextNeedsRebuild_;
};
D3DFORMAT getClutDestFormat(GEPaletteFormat format);

View file

@ -766,23 +766,60 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffe
}
void TextureCache::ApplyTexture() {
if (nextTexture_ == nullptr) {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
return;
}
nextTexture_ = nullptr;
if (nextTexture_->framebuffer) {
ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer);
} else {
UpdateMaxSeenV(gstate.isModeThrough());
UpdateMaxSeenV(entry, gstate.isModeThrough());
if (nextTexture_->textureName != lastBoundTexture) {
glBindTexture(GL_TEXTURE_2D, nextTexture_->textureName);
lastBoundTexture = nextTexture_->textureName;
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());
}
UpdateSamplingParams(*nextTexture_, false);
}
nextTexture_ = nullptr;
// 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 TextureCache::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) {
@ -986,7 +1023,6 @@ bool TextureCache::SetOffsetTexture(u32 offset) {
if (success && entry->framebuffer) {
// This will not apply the texture immediately.
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return true;
}
@ -1072,13 +1108,11 @@ void TextureCache::SetTexture(bool force) {
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
u32 fullhash = 0;
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.needShaderTexClamp = false;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
bool replaceImages = false;
if (iter != cache.end()) {
entry = &iter->second;
@ -1094,7 +1128,6 @@ void TextureCache::SetTexture(bool force) {
}
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
} else {
// Make sure we re-evaluate framebuffers.
@ -1105,7 +1138,6 @@ void TextureCache::SetTexture(bool force) {
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
bool doDelete = true;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
@ -1142,67 +1174,10 @@ void TextureCache::SetTexture(bool force) {
rehash = true;
}
bool hashFail = false;
if (texhash != entry->hash) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry);
hashFail = true;
rehash = false;
}
if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, 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) {
match = false;
reason = "hash fail";
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)cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(dim, format, maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
entry = secondEntry;
match = true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
@ -1217,46 +1192,27 @@ void TextureCache::SetTexture(bool force) {
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
entry->lastFrame = gpuStats.numFlips;
if (entry->textureName != lastBoundTexture) {
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
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 {
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason);
if (doDelete) {
if (entry->maxLevel == maxLevel && entry->dim == gstate.getTextureDimension(0) && entry->format == format && standardScaleFactor_ == 1) {
// 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);
}
}
// 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 (cluthash != 0) {
const u64 cachekeyMin = (u64)(texaddr & 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 != cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
@ -1273,38 +1229,136 @@ void TextureCache::SetTexture(bool force) {
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
}
if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
// Proceeding here can cause a crash.
return;
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->lastFrame = gpuStats.numFlips;
entry->framebuffer = 0;
entry->maxLevel = maxLevel;
entry->lodBias = 0.0f;
entry->dim = gstate.getTextureDimension(0);
entry->bufw = bufw;
// 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->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash;
entry->cluthash = cluthash;
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
nextTexture_ = entry;
nextNeedsRehash_ = true;
nextNeedsRebuild_= true;
}
bool TextureCache::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 TextureCache::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) {
// 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);
}
}
// 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 TextureCache::BuildTexture(TexCacheEntry *const entry, bool replaceImages) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
// For the estimate, we assume cluts always point to 8888 for simplicity.
cacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
@ -1314,6 +1368,7 @@ void TextureCache::SetTexture(bool force) {
}
// Before we go reading the texture from memory, let's check for render-to-texture.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
@ -1322,15 +1377,22 @@ void TextureCache::SetTexture(bool force) {
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
}
if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
// Proceeding here can cause a crash.
return;
}
glBindTexture(GL_TEXTURE_2D, entry->textureName);
lastBoundTexture = entry->textureName;
// Adjust maxLevel to actually present levels..
bool badMipSizes = false;
for (u32 i = 0; i <= maxLevel; i++) {
int maxLevel = entry->maxLevel;
for (int i = 0; i <= maxLevel; i++) {
// If encountering levels pointing to nothing, adjust max level.
u32 levelTexaddr = gstate.getTextureAddress(i);
if (!Memory::IsValidAddress(levelTexaddr)) {
@ -1354,7 +1416,7 @@ void TextureCache::SetTexture(bool force) {
}
// If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient.
GLenum dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat());
GLenum dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());
int scaleFactor = standardScaleFactor_;
@ -1364,6 +1426,9 @@ void TextureCache::SetTexture(bool force) {
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
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);
if (replaced.GetSize(0, w, h)) {
// We're replacing, so we won't scale.
@ -1459,11 +1524,7 @@ void TextureCache::SetTexture(bool force) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropyLevel);
}
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
// This will rebind it, but that's okay.
nextTexture_ = entry;
UpdateSamplingParams(*entry, true);
//glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

View file

@ -107,6 +107,10 @@ private:
void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer);
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);
std::vector<u32> nameCache_;
TexCache secondCache;
u32 secondCacheSizeEstimate_;
@ -133,6 +137,11 @@ private:
DepalShaderCache *depalShaderCache_;
ShaderManager *shaderManager_;
DrawEngineGLES *transformDraw_;
const char *nextChangeReason_;
bool nextNeedsRehash_;
bool nextNeedsChange_;
bool nextNeedsRebuild_;
};
GLenum getClutDestFormat(GEPaletteFormat format);

View file

@ -591,8 +591,6 @@ void DrawEngineVulkan::DirtyAllUBOs() {
if (!gstate.isModeClear()) {
// TODO: Test texture?
textureCache_->ApplyTexture();
if (fboTexNeedBind_) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferColor(1, nullptr, BINDFBCOLOR_MAY_COPY);
@ -612,19 +610,16 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
FrameData *frame = &frame_[curFrame_ & 1];
bool textureNeedsApply = false;
if (gstate_c.textureChanged != TEXCHANGE_UNCHANGED && !gstate.isModeClear() && gstate.isTextureMapEnabled()) {
textureCache_->SetTexture(frame->pushUBO);
textureCache_->SetTexture();
textureNeedsApply = true;
gstate_c.textureChanged = TEXCHANGE_UNCHANGED;
if (gstate_c.needShaderTexClamp) {
// We will rarely need to set this, so let's do it every time on use rather than in runloop.
// Most of the time non-framebuffer textures will be used which can be clamped themselves.
shaderManager_->DirtyUniform(DIRTY_TEXCLAMP);
}
textureCache_->ApplyTexture(imageView, sampler);
if (imageView == VK_NULL_HANDLE)
imageView = nullTexture_->GetImageView();
if (sampler == VK_NULL_HANDLE)
sampler = nullSampler_;
}
GEPrimitiveType prim = prevPrim_;
@ -661,6 +656,14 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255);
}
if (textureNeedsApply) {
textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler);
if (imageView == VK_NULL_HANDLE)
imageView = nullTexture_->GetImageView();
if (sampler == VK_NULL_HANDLE)
sampler = nullSampler_;
}
VulkanPipelineRasterStateKey pipelineKey;
VulkanDynamicState dynState;
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey, dynState);
@ -753,6 +756,14 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
// Only here, where we know whether to clear or to draw primitives, should we actually set the current framebuffer! Because that gives use the opportunity
// 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);
if (imageView == VK_NULL_HANDLE)
imageView = nullTexture_->GetImageView();
if (sampler == VK_NULL_HANDLE)
sampler = nullSampler_;
}
VulkanPipelineRasterStateKey pipelineKey;
VulkanDynamicState dynState;
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey, dynState);

View file

@ -704,50 +704,68 @@ void TextureCacheVulkan::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFram
}
}
void TextureCacheVulkan::ApplyTexture(VkImageView &imageView, VkSampler &sampler) {
if (nextTexture_ == nullptr) {
void TextureCacheVulkan::ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler) {
TexCacheEntry *entry = nextTexture_;
if (entry == nullptr) {
imageView = VK_NULL_HANDLE;
sampler = VK_NULL_HANDLE;
return;
}
VkCommandBuffer cmd = nullptr;
if (nextTexture_->framebuffer) {
ApplyTextureFramebuffer(cmd, nextTexture_, nextTexture_->framebuffer, imageView, sampler);
} else if (nextTexture_->vkTex) {
// If the texture is >= 512 pixels tall...
if (nextTexture_->dim >= 0x900) {
// Texture scale/offset and gen modes don't apply in through.
// So we can optimize how much of the texture we look at.
if (gstate.isModeThrough()) {
if (nextTexture_->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) {
// Let's not hash less than 272, we might use more later and have to rehash. 272 is very common.
nextTexture_->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV);
} else if (gstate_c.vertBounds.maxV > nextTexture_->maxSeenV) {
// The max height changed, so we're better off hashing the entire thing.
nextTexture_->maxSeenV = 512;
nextTexture_->status |= TexCacheEntry::STATUS_FREE_CHANGE;
}
} else {
// Otherwise, we need to reset to ensure we use the whole thing.
// Can't tell how much is used.
// TODO: We could tell for texcoord UV gen, and apply scale to max?
nextTexture_->maxSeenV = 512;
}
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);
}
imageView = nextTexture_->vkTex->texture_->GetImageView();
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(*nextTexture_, key);
UpdateSamplingParams(*entry, key);
sampler = samplerCache_.GetOrCreateSampler(key);
lastBoundTexture = nextTexture_->vkTex;
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;
}
nextTexture_ = nullptr;
}
void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &imageView, VkSampler &sampler) {
@ -841,7 +859,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn
*/
SamplerCacheKey key;
UpdateSamplingParams(*nextTexture_, key);
UpdateSamplingParams(*entry, key);
key.mipEnable = false;
sampler = samplerCache_.GetOrCreateSampler(key);
@ -876,7 +894,6 @@ bool TextureCacheVulkan::SetOffsetTexture(u32 offset) {
if (success && entry->framebuffer) {
// This will not apply the texture immediately.
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return true;
}
@ -911,7 +928,7 @@ VkFormat ToVulkanFormat(ReplacedTextureFormat fmt) {
}
}
void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
void TextureCacheVulkan::SetTexture() {
#ifdef DEBUG_TEXTURES
if (SetDebugTexture()) {
// A different texture was bound, let's rebind next time.
@ -959,7 +976,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
u32 fullhash = 0;
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
@ -980,7 +996,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
}
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
} else {
// Make sure we re-evaluate framebuffers.
@ -991,7 +1006,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
}
bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE;
bool doDelete = true;
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) {
@ -1028,67 +1042,10 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
rehash = true;
}
bool hashFail = false;
if (texhash != entry->hash) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry);
hashFail = true;
rehash = false;
}
if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) {
fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, 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) {
match = false;
reason = "hash fail";
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)cluthash << 32;
TexCache::iterator secondIter = secondCache.find(secondKey);
if (secondIter != secondCache.end()) {
TexCacheEntry *secondEntry = &secondIter->second;
if (secondEntry->Matches(dim, format, maxLevel)) {
// Reset the numInvalidated value lower, we got a match.
if (entry->numInvalidated > 8) {
--entry->numInvalidated;
}
entry = secondEntry;
match = true;
}
} else {
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
secondCache[secondKey] = *entry;
doDelete = false;
}
}
}
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
rehash = false;
}
}
@ -1103,41 +1060,27 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
if (match) {
// TODO: Mark the entry reliable if it's been safe for long enough?
//got one!
entry->lastFrame = gpuStats.numFlips;
if (entry->vkTex != lastBoundTexture) {
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
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 {
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
gpuStats.numTextureInvalidations++;
DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason);
if (doDelete) {
if (entry->vkTex == lastBoundTexture) {
lastBoundTexture = nullptr;
}
delete entry->vkTex;
entry->vkTex = nullptr;
}
// 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 (cluthash != 0) {
const u64 cachekeyMin = (u64)(texaddr & 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 != cluthash) {
it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK;
}
}
}
nextChangeReason_ = reason;
nextNeedsChange_ = true;
}
} else {
VERBOSE_LOG(G3D, "No texture in cache, decoding...");
@ -1154,43 +1097,134 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
} else {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
}
if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
// Proceeding here can cause a crash.
nextTexture_ = nullptr;
return;
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->lastFrame = gpuStats.numFlips;
entry->framebuffer = 0;
entry->maxLevel = maxLevel;
entry->lodBias = 0.0f;
entry->dim = gstate.getTextureDimension(0);
entry->bufw = bufw;
// 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->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash;
entry->cluthash = cluthash;
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
gstate_c.curTextureWidth = w;
gstate_c.curTextureHeight = h;
nextTexture_ = entry;
nextNeedsRehash_ = true;
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;
}
// 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) {
entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK;
// For the estimate, we assume cluts always point to 8888 for simplicity.
cacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
// Before we go reading the texture from memory, let's check for render-to-texture.
entry->framebuffer = 0;
for (size_t i = 0, n = fbCache_.size(); i < n; ++i) {
auto framebuffer = fbCache_[i];
AttachFramebuffer(entry, framebuffer->fb_address, framebuffer);
@ -1199,13 +1233,19 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if (entry->framebuffer) {
SetTextureFramebuffer(entry, entry->framebuffer);
entry->lastFrame = gpuStats.numFlips;
return;
}
if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) {
ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff);
// Proceeding here can cause a crash.
return;
}
// Adjust maxLevel to actually present levels..
bool badMipSizes = false;
for (u32 i = 0; i <= maxLevel; i++) {
int maxLevel = entry->maxLevel;
for (int i = 0; i <= maxLevel; i++) {
// If encountering levels pointing to nothing, adjust max level.
u32 levelTexaddr = gstate.getTextureAddress(i);
if (!Memory::IsValidAddress(levelTexaddr)) {
@ -1228,11 +1268,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
maxLevel = 0;
}
// Disable mipmapping. Something is wrong.
// maxLevel = 0;
// If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient.
VkFormat dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat());
VkFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat());
int scaleFactor = standardScaleFactor_;
@ -1242,12 +1279,16 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1);
}
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);
if (replaced.GetSize(0, w, h)) {
// We're replacing, so we won't scale.
scaleFactor = 1;
if (g_Config.bMipMap) {
maxLevel = replaced.MaxLevel();
badMipSizes = false;
}
}
@ -1338,8 +1379,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
if (replacer.Enabled() && !replaced.Valid()) {
replacedInfo.cachekey = cachekey;
replacedInfo.hash = entry->fullhash;
replacedInfo.addr = texaddr;
replacedInfo.isVideo = videos_.find(texaddr & 0x3FFFFFFF) != videos_.end();
replacedInfo.addr = entry->addr;
replacedInfo.isVideo = videos_.find(entry->addr & 0x3FFFFFFF) != videos_.end();
replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0;
replacedInfo.scaleFactor = scaleFactor;
replacedInfo.fmt = FromVulkanFormat(actualFmt);
@ -1379,8 +1420,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN;
nextTexture_ = entry;
}
VkFormat TextureCacheVulkan::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const {

View file

@ -86,7 +86,7 @@ public:
TextureCacheVulkan(VulkanContext *vulkan);
~TextureCacheVulkan();
void SetTexture(VulkanPushBuffer *uploadBuffer);
void SetTexture();
virtual bool SetOffsetTexture(u32 offset) override;
void Clear(bool delete_them);
@ -118,7 +118,7 @@ public:
gstate_c.textureChanged |= TEXCHANGE_PARAMSONLY;
}
void ApplyTexture(VkImageView &imageView, VkSampler &sampler);
void ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler);
protected:
void DownloadFramebufferForClut(u32 clutAddr, u32 bytes);
@ -141,6 +141,10 @@ private:
void ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &image, VkSampler &sampler);
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key);
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);
VulkanContext *vulkan_;
VulkanDeviceAllocator *allocator_;
@ -170,6 +174,11 @@ private:
DepalShaderCacheVulkan *depalShaderCache_;
ShaderManagerVulkan *shaderManager_;
DrawEngineVulkan *transformDraw_;
const char *nextChangeReason_;
bool nextNeedsRehash_;
bool nextNeedsChange_;
bool nextNeedsRebuild_;
};
VkFormat getClutDestFormatVulkan(GEPaletteFormat format);