mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 00:59:41 +01:00
Merge pull request #9332 from hrydgard/texcache-unify-2
Further texcache unification
This commit is contained in:
commit
4c8aa77e13
13 files changed
+728
-2083
No files matched your search
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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)
|
||||
|
||||
@@ -123,7 +123,7 @@ public:
|
||||
void DirtyAllUBOs();
|
||||
|
||||
VulkanPushBuffer *GetPushBufferForTextureData() {
|
||||
return frame_[curFrame_].pushUBO;
|
||||
return frame_[curFrame_ & 1].pushUBO;
|
||||
}
|
||||
|
||||
const DrawEngineVulkanStats &GetStats() const {
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user