Merge branch 'texcache-cleanup'

This commit is contained in:
NeoBrainX 2012-01-31 19:58:26 +01:00
commit 013bda74ee
20 changed files with 279 additions and 439 deletions

View File

@ -455,18 +455,14 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con
wxStaticBoxSizer* const szr_safetex = new wxStaticBoxSizer(wxHORIZONTAL, page_hacks, _("Texture Cache")); wxStaticBoxSizer* const szr_safetex = new wxStaticBoxSizer(wxHORIZONTAL, page_hacks, _("Texture Cache"));
// TODO: Use wxSL_MIN_MAX_LABELS or wxSL_VALUE_LABEL with wx 2.9.1 // TODO: Use wxSL_MIN_MAX_LABELS or wxSL_VALUE_LABEL with wx 2.9.1
wxSlider* const stc_slider = new wxSlider(page_hacks, wxID_ANY, 0, 0, 3, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_BOTTOM); wxSlider* const stc_slider = new wxSlider(page_hacks, wxID_ANY, 0, 0, 2, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_BOTTOM);
_connect_macro_(stc_slider, VideoConfigDiag::Event_Stc, wxEVT_COMMAND_SLIDER_UPDATED, this); _connect_macro_(stc_slider, VideoConfigDiag::Event_Stc, wxEVT_COMMAND_SLIDER_UPDATED, this);
RegisterControl(stc_slider, wxGetTranslation(stc_desc)); RegisterControl(stc_slider, wxGetTranslation(stc_desc));
if (vconfig.bSafeTextureCache) if (vconfig.iSafeTextureCache_ColorSamples == 0) stc_slider->SetValue(0);
{ else if (vconfig.iSafeTextureCache_ColorSamples == 512) stc_slider->SetValue(1);
if (vconfig.iSafeTextureCache_ColorSamples == 0) stc_slider->SetValue(0); else if (vconfig.iSafeTextureCache_ColorSamples == 128) stc_slider->SetValue(2);
else if (vconfig.iSafeTextureCache_ColorSamples == 512) stc_slider->SetValue(1); else stc_slider->Disable(); // Using custom number of samples; TODO: Inform the user why this is disabled..
else if (vconfig.iSafeTextureCache_ColorSamples == 128) stc_slider->SetValue(2);
else stc_slider->Disable(); // Using custom number of samples; TODO: Inform the user why this is disabled..
}
else stc_slider->SetValue(3);
szr_safetex->Add(new wxStaticText(page_hacks, wxID_ANY, _("Accuracy:")), 0, wxALL, 5); szr_safetex->Add(new wxStaticText(page_hacks, wxID_ANY, _("Accuracy:")), 0, wxALL, 5);
szr_safetex->AddStretchSpacer(1); szr_safetex->AddStretchSpacer(1);

View File

@ -120,12 +120,7 @@ protected:
void Event_Stc(wxCommandEvent &ev) void Event_Stc(wxCommandEvent &ev)
{ {
int samples[] = { 0, 512, 128 }; int samples[] = { 0, 512, 128 };
if (ev.GetInt() < 3) vconfig.iSafeTextureCache_ColorSamples = samples[ev.GetInt()];
{
vconfig.iSafeTextureCache_ColorSamples = samples[ev.GetInt()];
vconfig.bSafeTextureCache = true;
}
else vconfig.bSafeTextureCache = false;
ev.Skip(); ev.Skip();
} }

View File

@ -21,7 +21,6 @@
#include "Common.h" #include "Common.h"
bool SaveTGA(const char* filename, int width, int height, void* pdata); bool SaveTGA(const char* filename, int width, int height, void* pdata);
bool SaveTexture(const char* filename, u32 textarget, u32 tex, int width, int height);
bool SaveData(const char* filename, const char* pdata); bool SaveData(const char* filename, const char* pdata);
#endif // _IMAGEWRITE_H #endif // _IMAGEWRITE_H

View File

@ -1,3 +1,19 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "MemoryUtil.h" #include "MemoryUtil.h"
@ -23,28 +39,19 @@ enum
TextureCache *g_texture_cache; TextureCache *g_texture_cache;
GC_ALIGNED16(u8 *TextureCache::temp) = NULL; GC_ALIGNED16(u8 *TextureCache::temp) = NULL;
TextureCache::TexCache TextureCache::textures; TextureCache::TexCache TextureCache::textures;
bool TextureCache::DeferredInvalidate; bool TextureCache::DeferredInvalidate;
TextureCache::TCacheEntryBase::~TCacheEntryBase() TextureCache::TCacheEntryBase::~TCacheEntryBase()
{ {
if (0 == addr)
return;
if (!isRenderTarget && !g_ActiveConfig.bSafeTextureCache)
{
u32 *const ptr = (u32*)Memory::GetPointer(addr);
if (ptr && *ptr == hash)
*ptr = oldpixel;
}
} }
TextureCache::TextureCache() TextureCache::TextureCache()
{ {
if (!temp) if (!temp)
temp =(u8*) AllocateAlignedMemory(TEMP_SIZE,16); temp = (u8*)AllocateAlignedMemory(TEMP_SIZE,16);
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter); TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter);
if(g_ActiveConfig.bHiresTextures && !g_ActiveConfig.bDumpTextures) if(g_ActiveConfig.bHiresTextures && !g_ActiveConfig.bDumpTextures)
HiresTextures::Init(SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str()); HiresTextures::Init(SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str());
@ -88,12 +95,11 @@ TextureCache::~TextureCache()
void TextureCache::Cleanup() void TextureCache::Cleanup()
{ {
TexCache::iterator TexCache::iterator iter = textures.begin();
iter = textures.begin(), TexCache::iterator tcend = textures.end();
tcend = textures.end();
while (iter != tcend) while (iter != tcend)
{ {
if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount) if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount) // TODO: Deleting EFB copies might not be a good idea here...
{ {
delete iter->second; delete iter->second;
textures.erase(iter++); textures.erase(iter++);
@ -135,7 +141,7 @@ void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
const int rangePosition = iter->second->IntersectsMemoryRange(start_address, size); const int rangePosition = iter->second->IntersectsMemoryRange(start_address, size);
if (0 == rangePosition) if (0 == rangePosition)
{ {
iter->second->hash = 0; iter->second->SetHashes(TEXHASH_INVALID);
} }
} }
} }
@ -167,19 +173,16 @@ void TextureCache::ClearRenderTargets()
iter = textures.begin(), iter = textures.begin(),
tcend = textures.end(); tcend = textures.end();
for (; iter!=tcend; ++iter) for (; iter!=tcend; ++iter)
iter->second->isRenderTarget = false; iter->second->type = TCET_AUTOFETCH;
} }
TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage, TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
u32 address, unsigned int width, unsigned int height, int texformat, u32 address, unsigned int width, unsigned int height, int texformat,
unsigned int tlutaddr, int tlutfmt, bool UseNativeMips, unsigned int maxlevel) unsigned int tlutaddr, int tlutfmt, bool UseNativeMips, unsigned int maxlevel)
{ {
// necessary?
if (0 == address) if (0 == address)
return NULL; return NULL;
u8* ptr = Memory::GetPointer(address);
// TexelSizeInNibbles(format)*width*height/16; // TexelSizeInNibbles(format)*width*height/16;
const unsigned int bsw = TexDecoder_GetBlockWidthInTexels(texformat) - 1; const unsigned int bsw = TexDecoder_GetBlockWidthInTexels(texformat) - 1;
const unsigned int bsh = TexDecoder_GetBlockHeightInTexels(texformat) - 1; const unsigned int bsh = TexDecoder_GetBlockHeightInTexels(texformat) - 1;
@ -188,111 +191,80 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
unsigned int expandedHeight = (height + bsh) & (~bsh); unsigned int expandedHeight = (height + bsh) & (~bsh);
const unsigned int nativeW = width; const unsigned int nativeW = width;
const unsigned int nativeH = height; const unsigned int nativeH = height;
bool isPow2;
u64 hash_value = 0;
u64 texHash = 0;
u32 texID = address; u32 texID = address;
u64 tex_hash = TEXHASH_INVALID; // Hash assigned to texcache entry (also used to generate filenames used for texture dumping and custom texture lookup)
u64 tlut_hash = TEXHASH_INVALID;
u32 full_format = texformat; u32 full_format = texformat;
const u32 texture_size = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, texformat);
const u32 palette_size = TexDecoder_GetPaletteSize(texformat);
bool texture_is_dynamic = false;
unsigned int texLevels;
PC_TexFormat pcfmt = PC_TEX_FMT_NONE; PC_TexFormat pcfmt = PC_TEX_FMT_NONE;
const bool isPaletteTexture = (texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2); const bool isPaletteTexture = (texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2);
if (isPaletteTexture) if (isPaletteTexture)
full_format = texformat | (tlutfmt << 16); full_format = texformat | (tlutfmt << 16);
// hires texture loading and texture dumping require accurate hashes u8* ptr = Memory::GetPointer(address);
if (g_ActiveConfig.bSafeTextureCache || g_ActiveConfig.bHiresTextures || g_ActiveConfig.bDumpTextures) const u32 texture_size = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, texformat);
tex_hash = GetHash64(ptr, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
if (isPaletteTexture)
{ {
texHash = GetHash64(ptr, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples); const u32 palette_size = TexDecoder_GetPaletteSize(texformat);
tlut_hash = GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
if (isPaletteTexture) // NOTE: For non-paletted textures, texID is equal to the texture address.
{ // A paletted texture, however, may have multiple texIDs assigned though depending on the currently used tlut.
// WARNING! texID != address now => may break CopyRenderTargetToTexture (cf. TODO up) // This (changing texID depending on the tlut_hash) is a trick to get around
// tlut size can be up to 32768B (GX_TF_C14X2) but Safer == Slower. // an issue with Metroid Prime's fonts (it has multiple sets of fonts on each other
// This trick (to change the texID depending on the TLUT addr) is a trick to get around // stored in a single texture and uses the palette to make different characters
// an issue with metroid prime's fonts, where it has multiple sets of fonts on top of // visible or invisible. Thus, unless we want to recreate the textures for every drawn character,
// each other stored in a single texture, and uses the palette to make different characters // we must make sure that a paletted texture gets assigned multiple IDs for each tlut used.
// visible or invisible. Thus, unless we want to recreate the textures for every drawn character, //
// we must make sure that texture with different tluts get different IDs. // TODO: Because texID isn't always the same as the address now, CopyRenderTargetToTexture might be broken now
texID ^= ((u32)tlut_hash) ^(u32)(tlut_hash >> 32);
const u64 tlutHash = GetHash64(texMem + tlutaddr, palette_size, tex_hash ^= tlut_hash;
g_ActiveConfig.iSafeTextureCache_ColorSamples);
texHash ^= tlutHash;
if (g_ActiveConfig.bSafeTextureCache)
texID ^= ((u32)tlutHash) ^ (u32)(tlutHash >> 32);
}
if (g_ActiveConfig.bSafeTextureCache)
hash_value = texHash;
} }
TCacheEntryBase *entry = textures[texID]; TCacheEntryBase *entry = textures[texID];
if (entry) if (entry)
{ {
if (!g_ActiveConfig.bSafeTextureCache) // 1. Calculate reference hash:
{ // calculated from RAM texture data for normal textures. Hashes for paletted textures are modified by tlut_hash. 0 for virtual EFB copies.
if (entry->isRenderTarget || entry->isDynamic) if (g_ActiveConfig.bCopyEFBToTexture && entry->IsEfbCopy())
{ tex_hash = TEXHASH_INVALID;
if (!g_ActiveConfig.bCopyEFBToTexture)
{
hash_value = GetHash64(ptr, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
if (isPaletteTexture) // 2. a) For EFB copies, only the hash and the texture address need to match
{ if (entry->IsEfbCopy() && tex_hash == entry->hash && address == entry->addr)
hash_value ^= GetHash64(&texMem[tlutaddr], palette_size,
g_ActiveConfig.iSafeTextureCache_ColorSamples);
}
}
else
{
hash_value = 0;
}
}
else
{
hash_value = *(u32*)ptr;
}
}
else if ((entry->isRenderTarget || entry->isDynamic) && g_ActiveConfig.bCopyEFBToTexture)
{ {
hash_value = 0; // TODO: Print a warning if the format changes! In this case, we could reinterpret the internal texture object data to the new pixel format (similiar to what is already being done in Renderer::ReinterpretPixelFormat())
}
if (((entry->isRenderTarget || entry->isDynamic) && hash_value == entry->hash && address == entry->addr)
|| ((address == entry->addr) && (hash_value == entry->hash) && full_format == entry->format && entry->mipLevels == maxlevel))
{
entry->isDynamic = false;
goto return_entry; goto return_entry;
} }
// 2. b) For normal textures, all texture parameters need to match
if (address == entry->addr && tex_hash == entry->hash && full_format == entry->format &&
entry->num_mipmaps == maxlevel && entry->native_width == nativeW && entry->native_height == nativeH)
{
goto return_entry;
}
// 3. If we reach this line, we'll have to upload the new texture data to VRAM.
// If we're lucky, the texture parameters didn't change and we can reuse the internal texture object instead of destroying and recreating it.
//
// TODO: Don't we need to force texture decoding to RGBA8 for dynamic EFB copies?
// TODO: Actually, it should be enough if the internal texture format matches...
if ((entry->type == TCET_AUTOFETCH && width == entry->native_width && height == entry->native_height && full_format == entry->format && entry->num_mipmaps == maxlevel)
|| (entry->type == TCET_EC_DYNAMIC && entry->native_width == width && entry->native_height == height))
{
// reuse the texture
}
else else
{ {
// Let's reload the new texture data into the same texture, // delete the texture and make a new one
// instead of destroying it and having to create a new one. delete entry;
// Might speed up movie playback very, very slightly. entry = NULL;
texture_is_dynamic = (entry->isRenderTarget || entry->isDynamic) && !g_ActiveConfig.bCopyEFBToTexture;
if (!entry->isRenderTarget &&
((!entry->isDynamic && width == entry->realW && height == entry->realH && full_format == entry->format && entry->mipLevels == maxlevel)
|| (entry->isDynamic && entry->realW == width && entry->realH == height)))
{
// reuse the texture
}
else
{
// delete the texture and make a new one
delete entry;
entry = NULL;
}
} }
} }
if (g_ActiveConfig.bHiresTextures) if (g_ActiveConfig.bHiresTextures)
{ {
// Load Custom textures // Load Custom textures
@ -301,23 +273,23 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
unsigned int newWidth = width; unsigned int newWidth = width;
unsigned int newHeight = height; unsigned int newHeight = height;
sprintf(texPathTemp, "%s_%08x_%i", SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str(), (u32) (texHash & 0x00000000FFFFFFFFLL), texformat); sprintf(texPathTemp, "%s_%08x_%i", SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str(), (u32) (tex_hash & 0x00000000FFFFFFFFLL), texformat);
pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, texformat, temp); pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, texformat, temp);
if (pcfmt != PC_TEX_FMT_NONE) if (pcfmt != PC_TEX_FMT_NONE)
{ {
expandedWidth = width = newWidth; expandedWidth = width = newWidth;
expandedHeight = height = newHeight; expandedHeight = height = newHeight;
// TODO: shouldn't generating mips be forced on now?
// native mips with a custom texture wouldn't make sense
} }
} }
if (pcfmt == PC_TEX_FMT_NONE) if (pcfmt == PC_TEX_FMT_NONE)
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth, pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth,
expandedHeight, texformat, tlutaddr, tlutfmt, g_ActiveConfig.backend_info.bUseRGBATextures); expandedHeight, texformat, tlutaddr, tlutfmt, g_ActiveConfig.backend_info.bUseRGBATextures);
bool isPow2;
unsigned int texLevels;
UseNativeMips = UseNativeMips && (width == nativeW && height == nativeH); // Only load native mips if their dimensions fit to our virtual texture dimensions
isPow2 = !((width & (width - 1)) || (height & (height - 1))); isPow2 = !((width & (width - 1)) || (height & (height - 1)));
texLevels = (isPow2 && UseNativeMips && maxlevel) ? texLevels = (isPow2 && UseNativeMips && maxlevel) ?
GetPow2(std::max(width, height)) : !isPow2; GetPow2(std::max(width, height)) : !isPow2;
@ -332,32 +304,19 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
// Sometimes, we can get around recreating a texture if only the number of mip levels gets changes // Sometimes, we can get around recreating a texture if only the number of mip levels gets changes
// e.g. if our texture cache entry got too many mipmap levels we can limit the number of used levels by setting the appropriate render states // e.g. if our texture cache entry got too many mipmap levels we can limit the number of used levels by setting the appropriate render states
// Thus, we don't update this member for every Load, but just whenever the texture gets recreated // Thus, we don't update this member for every Load, but just whenever the texture gets recreated
entry->mipLevels = maxlevel; //
// TODO: Won't we end up recreating textures all the time because maxlevel doesn't necessarily equal texLevels?
entry->num_mipmaps = maxlevel; // TODO: Does this actually work? We can't really adjust mipmap settings per-stage...
entry->type = TCET_AUTOFETCH;
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
} }
entry->addr = address; entry->SetGeneralParameters(address, texture_size, full_format, entry->num_mipmaps);
entry->format = full_format; entry->SetDimensions(nativeW, nativeH, width, height);
entry->size_in_bytes = texture_size; entry->hash = tex_hash;
if (g_ActiveConfig.bCopyEFBToTexture) entry->type = TCET_AUTOFETCH;
entry->virtualW = width; else if (entry->IsEfbCopy()) entry->type = TCET_EC_DYNAMIC;
entry->virtualH = height;
entry->realW = nativeW;
entry->realH = nativeH;
entry->isRenderTarget = false;
entry->isNonPow2 = false;
entry->isDynamic = texture_is_dynamic;
entry->oldpixel = *(u32*)ptr;
if (g_ActiveConfig.bSafeTextureCache || entry->isDynamic)
entry->hash = hash_value;
else
// don't like rand() here
entry->hash = *(u32*)ptr = (u32)(((double)rand() / RAND_MAX) * 0xFFFFFFFF);
// load texture // load texture
entry->Load(width, height, expandedWidth, 0, (texLevels == 0)); entry->Load(width, height, expandedWidth, 0, (texLevels == 0));
@ -381,7 +340,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
expandedHeight = (currentHeight + bsh) & (~bsh); expandedHeight = (currentHeight + bsh) & (~bsh);
TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, texformat, tlutaddr, tlutfmt, g_ActiveConfig.backend_info.bUseRGBATextures); TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, texformat, tlutaddr, tlutfmt, g_ActiveConfig.backend_info.bUseRGBATextures);
entry->Load(currentWidth, currentHeight, expandedWidth, level); entry->Load(currentWidth, currentHeight, expandedWidth, level, false);
ptr += ((std::max(mipWidth, bsw) * std::max(mipHeight, bsh) * bsdepth) >> 1); ptr += ((std::max(mipWidth, bsw) * std::max(mipHeight, bsh) * bsdepth) >> 1);
mipWidth >>= 1; mipWidth >>= 1;
@ -404,7 +363,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
sprintf(szTemp, "%s/%s_%08x_%i.png", szDir.c_str(), sprintf(szTemp, "%s/%s_%08x_%i.png", szDir.c_str(),
SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str(), SConfig::GetInstance().m_LocalCoreStartupParameter.m_strUniqueID.c_str(),
(u32) (texHash & 0x00000000FFFFFFFFLL), texformat); (u32) (tex_hash & 0x00000000FFFFFFFFLL), texformat);
if (false == File::Exists(szTemp)) if (false == File::Exists(szTemp))
entry->Save(szTemp); entry->Save(szTemp);
@ -426,6 +385,45 @@ return_entry:
void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, unsigned int srcFormat, void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, unsigned int srcFormat,
const EFBRectangle& srcRect, bool isIntensity, bool scaleByHalf) const EFBRectangle& srcRect, bool isIntensity, bool scaleByHalf)
{ {
// Emulation methods:
// - EFB to RAM:
// Encodes the requested EFB data at its native resolution to the emulated RAM using shaders.
// Load() decodes the data from there again (using TextureDecoder) if the EFB copy is being used as a texture again.
// Advantage: CPU can read data from the EFB copy and we don't lose any important updates to the texture
// Disadvantage: Encoding+decoding steps often are redundant because only some games read or modify EFB copies before using them as textures.
// - EFB to texture:
// Copies the requested EFB data to a texture object in VRAM, performing any color conversion using shaders.
// Advantage: Works for many games, since in most cases EFB copies aren't read or modified at all before being used as a texture again.
// Since we don't do any further encoding or decoding here, this method is much faster.
// It also allows enhancing the visual quality by doing scaled EFB copies.
// - hybrid EFB copies:
// 1a) Whenever this function gets called, encode the requested EFB data to RAM (like EFB to RAM)
// 1b) Set type to TCET_EC_DYNAMIC for all texture cache entries in the destination address range.
// If EFB copy caching is enabled, further checks will (try to) prevent redundant EFB copies.
// 2) Check if a texture cache entry for the specified dstAddr already exists (i.e. if an EFB copy was triggered to that address before):
// 2a) Entry doesn't exist:
// - Also copy the requested EFB data to a texture object in VRAM (like EFB to texture)
// - Create a texture cache entry for the target (type = TCET_EC_VRAM)
// - Store a hash of the encoded RAM data in the texcache entry.
// 2b) Entry exists AND type is TCET_EC_VRAM:
// - Like case 2a, but reuse the old texcache entry instead of creating a new one.
// 2c) Entry exists AND type is TCET_EC_DYNAMIC:
// - Only encode the texture to RAM (like EFB to RAM) and store a hash of the encoded data in the existing texcache entry.
// - Do NOT copy the requested EFB data to a VRAM object. Reason: the texture is dynamic, i.e. the CPU is modifying it. Storing a VRAM copy is useless, because we'd always end up deleting it and reloading the data from RAM anyway.
// 3) If the EFB copy gets used as a texture, compare the source RAM hash with the hash you stored when encoding the EFB data to RAM.
// 3a) If the two hashes match AND type is TCET_EC_VRAM, reuse the VRAM copy you created
// 3b) If the two hashes differ AND type is TCET_EC_VRAM, screw your existing VRAM copy. Set type to TCET_EC_DYNAMIC.
// Redecode the source RAM data to a VRAM object. The entry basically behaves like a normal texture now.
// 3c) If type is TCET_EC_DYNAMIC, treat the EFB copy like a normal texture.
// Advantage: Non-dynamic EFB copies can be visually enhanced like with EFB to texture.
// Compatibility is as good as EFB to RAM.
// Disadvantage: Slower than EFB to texture and often even slower than EFB to RAM.
// EFB copy cache depends on accurate texture hashing being enabled. However, with accurate hashing you end up being as slow as without a copy cache anyway.
//
// Disadvantage of all methods: Calling this function requires the GPU to perform a pipeline flush which stalls any further CPU processing.
//
// For historical reasons, Dolphin doesn't actually implement "pure" EFB to RAM emulation, but only EFB to texture and hybrid EFB copies.
float colmat[28] = {0}; float colmat[28] = {0};
float *const fConstAdd = colmat + 16; float *const fConstAdd = colmat + 16;
float *const ColorMask = colmat + 20; float *const ColorMask = colmat + 20;
@ -472,9 +470,9 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
cbufid = 5; cbufid = 5;
break; break;
case 12: // Z16L - copy lower 16 depth bits case 12: // Z16L - copy lower 16 depth bits
// expected to be used as an IA8 texture (upper 8 bits stored as intensity, lower 8 bits stored as alpha) // expected to be used as an IA8 texture (upper 8 bits stored as intensity, lower 8 bits stored as alpha)
// Used e.g. in Zelda: Skyward Sword // Used e.g. in Zelda: Skyward Sword
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1.0f; colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1.0f;
cbufid = 6; cbufid = 6;
break; break;
@ -628,15 +626,15 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
unsigned int scaled_tex_w = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledX(tex_w) : tex_w; unsigned int scaled_tex_w = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledX(tex_w) : tex_w;
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h; unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h;
bool texture_is_dynamic = false;
TCacheEntryBase *entry = textures[dstAddr]; TCacheEntryBase *entry = textures[dstAddr];
if (entry) if (entry)
{ {
if ((entry->isRenderTarget && entry->virtualW == scaled_tex_w && entry->virtualH == scaled_tex_h) if ((entry->type == TCET_EC_VRAM && entry->virtual_width == scaled_tex_w && entry->virtual_height == scaled_tex_h)
|| (entry->isDynamic && entry->realW == tex_w && entry->realH == tex_h)) || (entry->type == TCET_EC_DYNAMIC && entry->native_width == tex_w && entry->native_height == tex_h))
{ {
texture_is_dynamic = entry->isDynamic; scaled_tex_w = tex_w;
scaled_tex_h = tex_h;
} }
else else
{ {
@ -646,32 +644,16 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
} }
} }
if (texture_is_dynamic)
{
scaled_tex_w = tex_w;
scaled_tex_h = tex_h;
}
if (NULL == entry) if (NULL == entry)
{ {
// create the texture // create the texture
textures[dstAddr] = entry = g_texture_cache->CreateRenderTargetTexture(scaled_tex_w, scaled_tex_h); textures[dstAddr] = entry = g_texture_cache->CreateRenderTargetTexture(scaled_tex_w, scaled_tex_h);
entry->addr = dstAddr; // TODO: Using the wrong dstFormat, dumb...
entry->hash = 0; entry->SetGeneralParameters(dstAddr, 0, dstFormat, 0);
entry->SetDimensions(tex_w, tex_h, scaled_tex_w, scaled_tex_h);
entry->realW = tex_w; entry->SetHashes(TEXHASH_INVALID);
entry->realH = tex_h; entry->type = TCET_EC_VRAM;
entry->virtualW = scaled_tex_w;
entry->virtualH = scaled_tex_h;
entry->format = dstFormat;
entry->mipLevels = 0;
entry->isRenderTarget = true;
entry->isNonPow2 = true;
entry->isDynamic = false;
} }
entry->frameCount = frameCount; entry->frameCount = frameCount;

View File

@ -1,3 +1,19 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _TEXTURECACHEBASE_H #ifndef _TEXTURECACHEBASE_H
#define _TEXTURECACHEBASE_H #define _TEXTURECACHEBASE_H
@ -14,47 +30,57 @@
class TextureCache class TextureCache
{ {
public: public:
enum TexCacheEntryType
{
TCET_AUTOFETCH, // Most textures, automatically fetched whenever they change
// TCET_PRELOADED, // Textures which reside in TMEM areas which are manually managed by the game
TCET_EC_VRAM, // EFB copy which sits in VRAM and is ready to be used
TCET_EC_DYNAMIC, // EFB copy which sits in RAM and needs to be decoded before being used
};
struct TCacheEntryBase struct TCacheEntryBase
{ {
// TODO: organize #define TEXHASH_INVALID 0
// common members
u32 addr; u32 addr;
u32 size_in_bytes; u32 size_in_bytes;
u64 hash; u64 hash;
//u32 paletteHash; //u32 pal_hash;
u32 oldpixel;
u32 format; u32 format;
enum TexCacheEntryType type;
unsigned int num_mipmaps;
unsigned int native_width, native_height; // Texture dimensions from the GameCube's point of view
unsigned int virtual_width, virtual_height; // Texture dimensions from OUR point of view - for hires textures or scaled EFB copies
// used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames
int frameCount; int frameCount;
unsigned int realW, realH; // Texture dimensions from the GameCube's point of view
unsigned int virtualW, virtualH; // Texture dimensions from OUR point of view
// Real and virtual dimensions are usually the same, but may be
// different if e.g. we use high-res textures. Then, realW,realH will
// be the dimensions of the original GameCube texture and
// virtualW,virtualH will be the dimensions of the high-res texture.
unsigned int mipLevels; void SetGeneralParameters(u32 addr, u32 size, u32 format, unsigned int num_mipmaps)
{
this->addr = addr;
this->size_in_bytes = size;
this->format = format;
this->num_mipmaps = num_mipmaps;
}
bool isRenderTarget; // copied from EFB void SetDimensions(unsigned int native_width, unsigned int native_height, unsigned int virtual_width, unsigned int virtual_height)
bool isDynamic; // Used for hybrid EFB copies to enable checks for CPU modifications {
bool isNonPow2; // doesn't seem to be used anywhere this->native_width = native_width;
this->native_height = native_height;
this->virtual_width = virtual_width;
this->virtual_height = virtual_height;
}
void SetHashes(u64 hash/*, u32 pal_hash*/)
{
this->hash = hash;
//this->pal_hash = pal_hash;
}
//TCacheEntryBase()
//{
// // TODO: remove these
// isRenderTarget = 0;
// hash = 0;
// //paletteHash = 0;
// oldpixel = 0;
// addr = 0;
// size_in_bytes = 0;
// frameCount = 0;
// isNonPow2 = true;
// w = 0;
// h = 0;
// scaledW = 0;
// scaledH = 0;
//}
virtual ~TCacheEntryBase(); virtual ~TCacheEntryBase();
@ -62,13 +88,15 @@ public:
virtual bool Save(const char filename[]) = 0; virtual bool Save(const char filename[]) = 0;
virtual void Load(unsigned int width, unsigned int height, virtual void Load(unsigned int width, unsigned int height,
unsigned int expanded_width, unsigned int level, bool autogen_mips = false) = 0; unsigned int expanded_width, unsigned int level, bool autogen_mips) = 0;
virtual void FromRenderTarget(u32 dstAddr, unsigned int dstFormat, virtual void FromRenderTarget(u32 dstAddr, unsigned int dstFormat,
unsigned int srcFormat, const EFBRectangle& srcRect, unsigned int srcFormat, const EFBRectangle& srcRect,
bool isIntensity, bool scaleByHalf, unsigned int cbufid, bool isIntensity, bool scaleByHalf, unsigned int cbufid,
const float *colmat) = 0; const float *colmat) = 0;
int IntersectsMemoryRange(u32 range_address, u32 range_size) const; int IntersectsMemoryRange(u32 range_address, u32 range_size) const;
bool IsEfbCopy() { return (type == TCET_EC_VRAM || type == TCET_EC_DYNAMIC); }
}; };
virtual ~TextureCache(); // needs virtual for DX11 dtor virtual ~TextureCache(); // needs virtual for DX11 dtor

View File

@ -57,12 +57,8 @@ void VideoConfig::Load(const char *ini_file)
iniFile.Get("Settings", "Crop", &bCrop, false); iniFile.Get("Settings", "Crop", &bCrop, false);
iniFile.Get("Settings", "UseXFB", &bUseXFB, 0); iniFile.Get("Settings", "UseXFB", &bUseXFB, 0);
iniFile.Get("Settings", "UseRealXFB", &bUseRealXFB, 0); iniFile.Get("Settings", "UseRealXFB", &bUseRealXFB, 0);
iniFile.Get("Settings", "UseNativeMips", &bUseNativeMips, false); iniFile.Get("Settings", "UseNativeMips", &bUseNativeMips, false);
iniFile.Get("Settings", "SafeTextureCache", &bSafeTextureCache, true); // Settings
//Safe texture cache params
iniFile.Get("Settings", "SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples,128); iniFile.Get("Settings", "SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples,128);
iniFile.Get("Settings", "ShowFPS", &bShowFPS, false); // Settings iniFile.Get("Settings", "ShowFPS", &bShowFPS, false); // Settings
iniFile.Get("Settings", "ShowInputDisplay", &bShowInputDisplay, false); iniFile.Get("Settings", "ShowInputDisplay", &bShowInputDisplay, false);
iniFile.Get("Settings", "OverlayStats", &bOverlayStats, false); iniFile.Get("Settings", "OverlayStats", &bOverlayStats, false);
@ -134,7 +130,6 @@ void VideoConfig::GameIniLoad(const char *ini_file)
iniFile.GetIfExists("Video_Settings", "UseXFB", &bUseXFB); iniFile.GetIfExists("Video_Settings", "UseXFB", &bUseXFB);
iniFile.GetIfExists("Video_Settings", "UseRealXFB", &bUseRealXFB); iniFile.GetIfExists("Video_Settings", "UseRealXFB", &bUseRealXFB);
iniFile.GetIfExists("Video_Settings", "UseNativeMips", &bUseNativeMips); iniFile.GetIfExists("Video_Settings", "UseNativeMips", &bUseNativeMips);
iniFile.GetIfExists("Video_Settings", "SafeTextureCache", &bSafeTextureCache);
iniFile.GetIfExists("Video_Settings", "SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples); iniFile.GetIfExists("Video_Settings", "SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples);
iniFile.GetIfExists("Video_Settings", "DLOptimize", &iCompileDLsLevel); iniFile.GetIfExists("Video_Settings", "DLOptimize", &iCompileDLsLevel);
iniFile.GetIfExists("Video_Settings", "HiresTextures", &bHiresTextures); iniFile.GetIfExists("Video_Settings", "HiresTextures", &bHiresTextures);
@ -196,11 +191,7 @@ void VideoConfig::Save(const char *ini_file)
iniFile.Set("Settings", "UseXFB", bUseXFB); iniFile.Set("Settings", "UseXFB", bUseXFB);
iniFile.Set("Settings", "UseRealXFB", bUseRealXFB); iniFile.Set("Settings", "UseRealXFB", bUseRealXFB);
iniFile.Set("Settings", "UseNativeMips", bUseNativeMips); iniFile.Set("Settings", "UseNativeMips", bUseNativeMips);
iniFile.Set("Settings", "SafeTextureCache", bSafeTextureCache);
//safe texture cache params
iniFile.Set("Settings", "SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples); iniFile.Set("Settings", "SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples);
iniFile.Set("Settings", "ShowFPS", bShowFPS); iniFile.Set("Settings", "ShowFPS", bShowFPS);
iniFile.Set("Settings", "ShowInputDisplay", bShowInputDisplay); iniFile.Set("Settings", "ShowInputDisplay", bShowInputDisplay);
iniFile.Set("Settings", "OverlayStats", bOverlayStats); iniFile.Set("Settings", "OverlayStats", bOverlayStats);
@ -279,7 +270,6 @@ void VideoConfig::GameIniSave(const char* default_ini, const char* game_ini)
SET_IF_DIFFERS("Video_Settings", "UseXFB", bUseXFB); SET_IF_DIFFERS("Video_Settings", "UseXFB", bUseXFB);
SET_IF_DIFFERS("Video_Settings", "UseRealXFB", bUseRealXFB); SET_IF_DIFFERS("Video_Settings", "UseRealXFB", bUseRealXFB);
SET_IF_DIFFERS("Video_Settings", "UseNativeMips", bUseNativeMips); SET_IF_DIFFERS("Video_Settings", "UseNativeMips", bUseNativeMips);
SET_IF_DIFFERS("Video_Settings", "SafeTextureCache", bSafeTextureCache);
SET_IF_DIFFERS("Video_Settings", "SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples); SET_IF_DIFFERS("Video_Settings", "SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples);
SET_IF_DIFFERS("Video_Settings", "DLOptimize", iCompileDLsLevel); SET_IF_DIFFERS("Video_Settings", "DLOptimize", iCompileDLsLevel);
SET_IF_DIFFERS("Video_Settings", "HiresTextures", bHiresTextures); SET_IF_DIFFERS("Video_Settings", "HiresTextures", bHiresTextures);

View File

@ -129,7 +129,6 @@ struct VideoConfig
bool bOSDHotKey; bool bOSDHotKey;
bool bCopyEFBToTexture; bool bCopyEFBToTexture;
bool bCopyEFBScaled; bool bCopyEFBScaled;
bool bSafeTextureCache;
int iSafeTextureCache_ColorSamples; int iSafeTextureCache_ColorSamples;
int iPhackvalue[4]; int iPhackvalue[4];
std::string sPhackvalue[2]; std::string sPhackvalue[2];

View File

@ -1106,12 +1106,11 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons
DLCache::ProgressiveCleanup(); DLCache::ProgressiveCleanup();
TextureCache::Cleanup(); TextureCache::Cleanup();
// reload textures if these settings changed // Reload textures if this settings changes
if (g_Config.bSafeTextureCache != g_ActiveConfig.bSafeTextureCache || if (g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
TextureCache::Invalidate(false); TextureCache::Invalidate(false);
// Enable any configuration changes // Enable configuration changes
UpdateActiveConfig(); UpdateActiveConfig();
SetWindowSize(fbWidth, fbHeight); SetWindowSize(fbWidth, fbHeight);

View File

@ -102,12 +102,12 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
bool isIntensity, bool scaleByHalf, unsigned int cbufid, bool isIntensity, bool scaleByHalf, unsigned int cbufid,
const float *colmat) const float *colmat)
{ {
if (!isDynamic || g_ActiveConfig.bCopyEFBToTexture) if (type != TCET_EC_DYNAMIC || g_ActiveConfig.bCopyEFBToTexture)
{ {
g_renderer->ResetAPIState(); g_renderer->ResetAPIState();
// stretch picture with increased internal resolution // stretch picture with increased internal resolution
const D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)virtualW, (float)virtualH); const D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)virtual_width, (float)virtual_height);
D3D::context->RSSetViewports(1, &vp); D3D::context->RSSetViewports(1, &vp);
// set transformation // set transformation
@ -149,17 +149,15 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
if (!g_ActiveConfig.bCopyEFBToTexture) if (!g_ActiveConfig.bCopyEFBToTexture)
{ {
u8* dst = Memory::GetPointer(dstAddr); u8* dst = Memory::GetPointer(dstAddr);
size_t encodeSize = g_encoder->Encode(dst, dstFormat, srcFormat, srcRect, isIntensity, scaleByHalf); size_t encoded_size = g_encoder->Encode(dst, dstFormat, srcFormat, srcRect, isIntensity, scaleByHalf);
hash = GetHash64(dst, encodeSize, g_ActiveConfig.iSafeTextureCache_ColorSamples);
if (g_ActiveConfig.bEFBCopyCacheEnable)
{
// If the texture in RAM is already in the texture cache,
// do not copy it again as it has not changed.
if (TextureCache::Find(dstAddr, hash))
return;
}
TextureCache::MakeRangeDynamic(dstAddr, encodeSize); hash = GetHash64(dst, (int)encoded_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
// Mark texture entries in destination address range dynamic unless caching is enabled and the texture entry is up to date
if (!g_ActiveConfig.bEFBCopyCacheEnable)
TextureCache::MakeRangeDynamic(addr, (u32)encoded_size);
else if (!TextureCache::Find(addr, hash))
TextureCache::MakeRangeDynamic(addr, (u32)encoded_size);
} }
} }

View File

@ -233,7 +233,7 @@ void VertexManager::vFlush()
if (tentry) if (tentry)
{ {
// 0s are probably for no manual wrapping needed. // 0s are probably for no manual wrapping needed.
PixelShaderManager::SetTexDims(i, tentry->realW, tentry->realH, 0, 0); PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height, 0, 0);
} }
else else
ERROR_LOG(VIDEO, "error loading texture"); ERROR_LOG(VIDEO, "error loading texture");

View File

@ -1114,12 +1114,11 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons
DLCache::ProgressiveCleanup(); DLCache::ProgressiveCleanup();
TextureCache::Cleanup(); TextureCache::Cleanup();
// reload textures if these settings changed // Reload textures if these settings changed
if (g_Config.bSafeTextureCache != g_ActiveConfig.bSafeTextureCache || if (g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
TextureCache::Invalidate(false); TextureCache::Invalidate(false);
// Enable any configuration changes // Enable configuration changes
UpdateActiveConfig(); UpdateActiveConfig();
SetWindowSize(fbWidth, fbHeight); SetWindowSize(fbWidth, fbHeight);

View File

@ -21,6 +21,7 @@
#include "Statistics.h" #include "Statistics.h"
#include "MemoryUtil.h" #include "MemoryUtil.h"
#include "Hash.h" #include "Hash.h"
#include "HW/Memmap.h"
#include "CommonPaths.h" #include "CommonPaths.h"
#include "FileUtil.h" #include "FileUtil.h"
@ -78,7 +79,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
FramebufferManager::GetEFBDepthTexture() : FramebufferManager::GetEFBDepthTexture() :
FramebufferManager::GetEFBColorTexture(); FramebufferManager::GetEFBColorTexture();
if (!isDynamic || g_ActiveConfig.bCopyEFBToTexture) if (type != TCET_EC_DYNAMIC || g_ActiveConfig.bCopyEFBToTexture)
{ {
LPDIRECT3DSURFACE9 Rendersurf = NULL; LPDIRECT3DSURFACE9 Rendersurf = NULL;
texture->GetSurfaceLevel(0, &Rendersurf); texture->GetSurfaceLevel(0, &Rendersurf);
@ -90,15 +91,15 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
// Stretch picture with increased internal resolution // Stretch picture with increased internal resolution
vp.X = 0; vp.X = 0;
vp.Y = 0; vp.Y = 0;
vp.Width = virtualW; vp.Width = virtual_width;
vp.Height = virtualH; vp.Height = virtual_height;
vp.MinZ = 0.0f; vp.MinZ = 0.0f;
vp.MaxZ = 1.0f; vp.MaxZ = 1.0f;
D3D::dev->SetViewport(&vp); D3D::dev->SetViewport(&vp);
RECT destrect; RECT destrect;
destrect.bottom = virtualH; destrect.bottom = virtual_height;
destrect.left = 0; destrect.left = 0;
destrect.right = virtualW; destrect.right = virtual_width;
destrect.top = 0; destrect.top = 0;
PixelShaderManager::SetColorMatrix(colmat); // set transformation PixelShaderManager::SetColorMatrix(colmat); // set transformation
@ -133,7 +134,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
D3D::drawShadedTexQuad(read_texture, &sourcerect, D3D::drawShadedTexQuad(read_texture, &sourcerect,
Renderer::GetTargetWidth(), Renderer::GetTargetHeight(), Renderer::GetTargetWidth(), Renderer::GetTargetHeight(),
virtualW, virtualH, virtual_width, virtual_height,
// TODO: why is D3DFMT_D24X8 singled out here? why not D3DFMT_D24X4S4/D24S8/D24FS8/D32/D16/D15S1 too, or none of them? // TODO: why is D3DFMT_D24X8 singled out here? why not D3DFMT_D24X4S4/D24S8/D24FS8/D32/D16/D15S1 too, or none of them?
PixelShaderCache::GetDepthMatrixProgram(SSAAMode, (srcFormat == PIXELFMT_Z24) && bformat != FOURCC_RAWZ && bformat != D3DFMT_D24X8), PixelShaderCache::GetDepthMatrixProgram(SSAAMode, (srcFormat == PIXELFMT_Z24) && bformat != FOURCC_RAWZ && bformat != D3DFMT_D24X8),
VertexShaderCache::GetSimpleVertexShader(SSAAMode)); VertexShaderCache::GetSimpleVertexShader(SSAAMode));
@ -143,16 +144,25 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
if (!g_ActiveConfig.bCopyEFBToTexture) if (!g_ActiveConfig.bCopyEFBToTexture)
{ {
hash = TextureConverter::EncodeToRamFromTexture( int encoded_size = TextureConverter::EncodeToRamFromTexture(
addr, addr,
read_texture, read_texture,
Renderer::GetTargetWidth(), Renderer::GetTargetWidth(),
Renderer::GetTargetHeight(), Renderer::GetTargetHeight(),
srcFormat == PIXELFMT_Z24, srcFormat == PIXELFMT_Z24,
isIntensity, isIntensity,
dstFormat, dstFormat,
scaleByHalf, scaleByHalf,
srcRect); srcRect);
u8* dst = Memory::GetPointer(addr);
hash = GetHash64(dst,encoded_size,g_ActiveConfig.iSafeTextureCache_ColorSamples);
// Mark texture entries in destination address range dynamic unless caching is enabled and the texture entry is up to date
if (!g_ActiveConfig.bEFBCopyCacheEnable)
TextureCache::MakeRangeDynamic(addr,encoded_size);
else if (!TextureCache::Find(addr, hash))
TextureCache::MakeRangeDynamic(addr,encoded_size);
} }
D3D::RefreshSamplerState(0, D3DSAMP_MINFILTER); D3D::RefreshSamplerState(0, D3DSAMP_MINFILTER);

View File

@ -252,10 +252,10 @@ void EncodeToRamUsingShader(LPDIRECT3DPIXELSHADER9 shader, LPDIRECT3DTEXTURE9 sr
s_texConvReadSurface = TrnBuffers[index].ReadSurface; s_texConvReadSurface = TrnBuffers[index].ReadSurface;
Rendersurf = TrnBuffers[index].RenderSurface; Rendersurf = TrnBuffers[index].RenderSurface;
hr = D3D::dev->SetDepthStencilSurface(NULL); hr = D3D::dev->SetDepthStencilSurface(NULL);
hr = D3D::dev->SetRenderTarget(0, Rendersurf); hr = D3D::dev->SetRenderTarget(0, Rendersurf);
if (linearFilter) if (linearFilter)
{ {
D3D::ChangeSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); D3D::ChangeSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
@ -290,7 +290,7 @@ void EncodeToRamUsingShader(LPDIRECT3DPIXELSHADER9 shader, LPDIRECT3DTEXTURE9 sr
D3D::RefreshSamplerState(0, D3DSAMP_MINFILTER); D3D::RefreshSamplerState(0, D3DSAMP_MINFILTER);
// .. and then read back the results. // .. and then read back the results.
// TODO: make this less slow. // TODO: make this less slow.
hr = D3D::dev->GetRenderTargetData(Rendersurf,s_texConvReadSurface); hr = D3D::dev->GetRenderTargetData(Rendersurf,s_texConvReadSurface);
D3DLOCKED_RECT drect; D3DLOCKED_RECT drect;
@ -313,76 +313,7 @@ void EncodeToRamUsingShader(LPDIRECT3DPIXELSHADER9 shader, LPDIRECT3DTEXTURE9 sr
hr = s_texConvReadSurface->UnlockRect(); hr = s_texConvReadSurface->UnlockRect();
} }
void EncodeToRam(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source) int EncodeToRamFromTexture(u32 address,LPDIRECT3DTEXTURE9 source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source)
{
u32 format = copyfmt;
if (bFromZBuffer)
{
format |= _GX_TF_ZTF;
if (copyfmt == 11)
format = GX_TF_Z16;
else if (format < GX_TF_Z8 || format > GX_TF_Z24X8)
format |= _GX_TF_CTF;
}
else
if (copyfmt > GX_TF_RGBA8 || (copyfmt < GX_TF_RGB565 && !bIsIntensityFmt))
format |= _GX_TF_CTF;
LPDIRECT3DPIXELSHADER9 texconv_shader = GetOrCreateEncodingShader(format);
if (!texconv_shader)
return;
u8 *dest_ptr = Memory::GetPointer(address);
LPDIRECT3DTEXTURE9 source_texture = bFromZBuffer ? FramebufferManager::GetEFBDepthTexture() : FramebufferManager::GetEFBColorTexture();
int width = (source.right - source.left) >> bScaleByHalf;
int height = (source.bottom - source.top) >> bScaleByHalf;
int size_in_bytes = TexDecoder_GetTextureSizeInBytes(width, height, format);
// Invalidate any existing texture covering this memory range.
// TODO - don't delete the texture if it already exists, just replace the contents.
TextureCache::InvalidateRange(address, size_in_bytes);
u16 blkW = TexDecoder_GetBlockWidthInTexels(format) - 1;
u16 blkH = TexDecoder_GetBlockHeightInTexels(format) - 1;
u16 samples = TextureConversionShader::GetEncodedSampleCount(format);
// only copy on cache line boundaries
// extra pixels are copied but not displayed in the resulting texture
s32 expandedWidth = (width + blkW) & (~blkW);
s32 expandedHeight = (height + blkH) & (~blkH);
float sampleStride = bScaleByHalf ? 2.f : 1.f;
TextureConversionShader::SetShaderParameters(
(float)expandedWidth,
(float)Renderer::EFBToScaledY(expandedHeight), // TODO: Why do we scale this?
(float)Renderer::EFBToScaledX(source.left),
(float)Renderer::EFBToScaledY(source.top),
Renderer::EFBToScaledXf(sampleStride),
Renderer::EFBToScaledYf(sampleStride),
(float)Renderer::GetTargetWidth(),
(float)Renderer::GetTargetHeight());
TargetRectangle scaledSource;
scaledSource.top = 0;
scaledSource.bottom = expandedHeight;
scaledSource.left = 0;
scaledSource.right = expandedWidth / samples;
int cacheBytes = 32;
if ((format & 0x0f) == 6)
cacheBytes = 64;
int readStride = (expandedWidth * cacheBytes) / TexDecoder_GetBlockWidthInTexels(format);
g_renderer->ResetAPIState();
EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource, dest_ptr, expandedWidth / samples, expandedHeight, readStride, true, bScaleByHalf > 0,1.0f);
D3D::dev->SetRenderTarget(0, FramebufferManager::GetEFBColorRTSurface());
D3D::dev->SetDepthStencilSurface(FramebufferManager::GetEFBDepthRTSurface());
g_renderer->RestoreAPIState();
}
u64 EncodeToRamFromTexture(u32 address,LPDIRECT3DTEXTURE9 source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source)
{ {
u32 format = copyfmt; u32 format = copyfmt;
@ -440,16 +371,7 @@ u64 EncodeToRamFromTexture(u32 address,LPDIRECT3DTEXTURE9 source_texture, u32 So
int readStride = (expandedWidth * cacheBytes) / TexDecoder_GetBlockWidthInTexels(format); int readStride = (expandedWidth * cacheBytes) / TexDecoder_GetBlockWidthInTexels(format);
EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource, dest_ptr, expandedWidth / samples, expandedHeight, readStride, true, bScaleByHalf > 0,1.0f); EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource, dest_ptr, expandedWidth / samples, expandedHeight, readStride, true, bScaleByHalf > 0,1.0f);
u64 hash = GetHash64(dest_ptr,size_in_bytes,g_ActiveConfig.iSafeTextureCache_ColorSamples); return size_in_bytes; // TODO: D3D11 is calculating this value differently!
if (g_ActiveConfig.bEFBCopyCacheEnable)
{
// If the texture in RAM is already in the texture cache, do not copy it again as it has not changed.
if (TextureCache::Find(address, hash))
return hash;
}
TextureCache::MakeRangeDynamic(address,size_in_bytes);
return hash;
} }
void EncodeToRamYUYV(LPDIRECT3DTEXTURE9 srcTexture, const TargetRectangle& sourceRc, u8* destAddr, int dstWidth, int dstHeight,float Gamma) void EncodeToRamYUYV(LPDIRECT3DTEXTURE9 srcTexture, const TargetRectangle& sourceRc, u8* destAddr, int dstWidth, int dstHeight,float Gamma)
@ -490,7 +412,7 @@ void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, LPDIRECT3DTEXTURE
destTexture->GetSurfaceLevel(0,&Rendersurf); destTexture->GetSurfaceLevel(0,&Rendersurf);
D3D::dev->SetDepthStencilSurface(NULL); D3D::dev->SetDepthStencilSurface(NULL);
D3D::dev->SetRenderTarget(0, Rendersurf); D3D::dev->SetRenderTarget(0, Rendersurf);
D3DVIEWPORT9 vp; D3DVIEWPORT9 vp;
// Stretch picture with increased internal resolution // Stretch picture with increased internal resolution

View File

@ -35,15 +35,13 @@ namespace TextureConverter
void Init(); void Init();
void Shutdown(); void Shutdown();
void EncodeToRam(u32 address, bool bFromZBuffer, bool bIsIntensityFmt,
u32 copyfmt, int bScaleByHalf, const EFBRectangle& source);
void EncodeToRamYUYV(LPDIRECT3DTEXTURE9 srcTexture, const TargetRectangle& sourceRc, void EncodeToRamYUYV(LPDIRECT3DTEXTURE9 srcTexture, const TargetRectangle& sourceRc,
u8* destAddr, int dstWidth, int dstHeight,float Gamma); u8* destAddr, int dstWidth, int dstHeight,float Gamma);
void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, LPDIRECT3DTEXTURE9 destTexture); void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, LPDIRECT3DTEXTURE9 destTexture);
u64 EncodeToRamFromTexture(u32 address,LPDIRECT3DTEXTURE9 source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source); // returns size of the encoded data (in bytes)
int EncodeToRamFromTexture(u32 address,LPDIRECT3DTEXTURE9 source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source);
} }

View File

@ -142,7 +142,7 @@ void VertexManager::vFlush()
if (tentry) if (tentry)
{ {
// 0s are probably for no manual wrapping needed. // 0s are probably for no manual wrapping needed.
PixelShaderManager::SetTexDims(i, tentry->realW, tentry->realH, 0, 0); PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height, 0, 0);
} }
else else
ERROR_LOG(VIDEO, "error loading texture"); ERROR_LOG(VIDEO, "error loading texture");

View File

@ -1394,8 +1394,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons
g_Config.iSaveTargetId = 0; g_Config.iSaveTargetId = 0;
// reload textures if these settings changed // reload textures if these settings changed
if (g_Config.bSafeTextureCache != g_ActiveConfig.bSafeTextureCache || if (g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
g_Config.bUseNativeMips != g_ActiveConfig.bUseNativeMips)
TextureCache::Invalidate(false); TextureCache::Invalidate(false);
if (g_Config.bCopyEFBToTexture != g_ActiveConfig.bCopyEFBToTexture) if (g_Config.bCopyEFBToTexture != g_ActiveConfig.bCopyEFBToTexture)

View File

@ -39,6 +39,7 @@
#include "Globals.h" #include "Globals.h"
#include "Hash.h" #include "Hash.h"
#include "HiresTextures.h" #include "HiresTextures.h"
#include "HW/Memmap.h"
#include "ImageWrite.h" #include "ImageWrite.h"
#include "MemoryUtil.h" #include "MemoryUtil.h"
#include "PixelShaderCache.h" #include "PixelShaderCache.h"
@ -124,7 +125,7 @@ bool TextureCache::TCacheEntry::Save(const char filename[])
std::string tga_filename(filename); std::string tga_filename(filename);
tga_filename.replace(tga_filename.size() - 3, 3, "tga"); tga_filename.replace(tga_filename.size() - 3, 3, "tga");
return SaveTexture(tga_filename.c_str(), GL_TEXTURE_2D, texture, realW, realH); return SaveTexture(tga_filename.c_str(), GL_TEXTURE_2D, texture, virtual_width, virtual_height);
} }
TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width, TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width,
@ -278,7 +279,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
GL_REPORT_ERRORD(); GL_REPORT_ERRORD();
if (false == isDynamic || g_ActiveConfig.bCopyEFBToTexture) if (type != TCET_EC_DYNAMIC || g_ActiveConfig.bCopyEFBToTexture)
{ {
if (s_TempFramebuffer == 0) if (s_TempFramebuffer == 0)
glGenFramebuffersEXT(1, (GLuint*)&s_TempFramebuffer); glGenFramebuffersEXT(1, (GLuint*)&s_TempFramebuffer);
@ -294,7 +295,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
glEnable(GL_TEXTURE_RECTANGLE_ARB); glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, read_texture); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, read_texture);
glViewport(0, 0, virtualW, virtualH); glViewport(0, 0, virtual_width, virtual_height);
PixelShaderCache::SetCurrentShader((srcFormat == PIXELFMT_Z24) ? PixelShaderCache::GetDepthMatrixProgram() : PixelShaderCache::GetColorMatrixProgram()); PixelShaderCache::SetCurrentShader((srcFormat == PIXELFMT_Z24) ? PixelShaderCache::GetDepthMatrixProgram() : PixelShaderCache::GetColorMatrixProgram());
PixelShaderManager::SetColorMatrix(colmat); // set transformation PixelShaderManager::SetColorMatrix(colmat); // set transformation
@ -317,7 +318,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
if (false == g_ActiveConfig.bCopyEFBToTexture) if (false == g_ActiveConfig.bCopyEFBToTexture)
{ {
hash = TextureConverter::EncodeToRamFromTexture( int encoded_size = TextureConverter::EncodeToRamFromTexture(
addr, addr,
read_texture, read_texture,
srcFormat == PIXELFMT_Z24, srcFormat == PIXELFMT_Z24,
@ -325,6 +326,15 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
dstFormat, dstFormat,
scaleByHalf, scaleByHalf,
srcRect); srcRect);
u8* dst = Memory::GetPointer(addr);
hash = GetHash64(dst,encoded_size,g_ActiveConfig.iSafeTextureCache_ColorSamples);
// Mark texture entries in destination address range dynamic unless caching is enabled and the texture entry is up to date
if (!g_ActiveConfig.bEFBCopyCacheEnable)
TextureCache::MakeRangeDynamic(addr,encoded_size);
else if (!TextureCache::Find(addr, hash))
TextureCache::MakeRangeDynamic(addr,encoded_size);
} }
FramebufferManager::SetFramebuffer(0); FramebufferManager::SetFramebuffer(0);
@ -337,7 +347,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
{ {
static int count = 0; static int count = 0;
SaveTexture(StringFromFormat("%sefb_frame_%i.tga", File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(), SaveTexture(StringFromFormat("%sefb_frame_%i.tga", File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(),
count++).c_str(), GL_TEXTURE_2D, texture, realW, realH); count++).c_str(), GL_TEXTURE_2D, texture, virtual_width, virtual_height);
} }
} }

View File

@ -250,78 +250,7 @@ void EncodeToRamUsingShader(FRAGMENTSHADER& shader, GLuint srcTexture, const Tar
} }
void EncodeToRam(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source) int EncodeToRamFromTexture(u32 address,GLuint source_texture, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source)
{
u32 format = copyfmt;
if (bFromZBuffer)
{
format |= _GX_TF_ZTF;
if (copyfmt == 11)
format = GX_TF_Z16;
else if (format < GX_TF_Z8 || format > GX_TF_Z24X8)
format |= _GX_TF_CTF;
}
else
if (copyfmt > GX_TF_RGBA8 || (copyfmt < GX_TF_RGB565 && !bIsIntensityFmt))
format |= _GX_TF_CTF;
FRAGMENTSHADER& texconv_shader = GetOrCreateEncodingShader(format);
if (texconv_shader.glprogid == 0)
return;
u8 *dest_ptr = Memory::GetPointer(address);
GLuint source_texture = bFromZBuffer ? FramebufferManager::ResolveAndGetDepthTarget(source) : FramebufferManager::ResolveAndGetRenderTarget(source);
int width = (source.right - source.left) >> bScaleByHalf;
int height = (source.bottom - source.top) >> bScaleByHalf;
int size_in_bytes = TexDecoder_GetTextureSizeInBytes(width, height, format);
// Invalidate any existing texture covering this memory range.
// TODO - don't delete the texture if it already exists, just replace the contents.
TextureCache::InvalidateRange(address, size_in_bytes);
u16 blkW = TexDecoder_GetBlockWidthInTexels(format) - 1;
u16 blkH = TexDecoder_GetBlockHeightInTexels(format) - 1;
u16 samples = TextureConversionShader::GetEncodedSampleCount(format);
// only copy on cache line boundaries
// extra pixels are copied but not displayed in the resulting texture
s32 expandedWidth = (width + blkW) & (~blkW);
s32 expandedHeight = (height + blkH) & (~blkH);
float sampleStride = bScaleByHalf ? 2.f : 1.f;
TextureConversionShader::SetShaderParameters(
(float)expandedWidth,
(float)Renderer::EFBToScaledY(expandedHeight), // TODO: Why do we scale this?
(float)Renderer::EFBToScaledX(source.left),
(float)Renderer::EFBToScaledY(EFB_HEIGHT - source.top - expandedHeight),
Renderer::EFBToScaledXf(sampleStride),
Renderer::EFBToScaledYf(sampleStride));
TargetRectangle scaledSource;
scaledSource.top = 0;
scaledSource.bottom = expandedHeight;
scaledSource.left = 0;
scaledSource.right = expandedWidth / samples;
int cacheBytes = 32;
if ((format & 0x0f) == 6)
cacheBytes = 64;
int readStride = (expandedWidth * cacheBytes) / TexDecoder_GetBlockWidthInTexels(format);
g_renderer->ResetAPIState();
EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource, dest_ptr, expandedWidth / samples, expandedHeight, readStride, true, bScaleByHalf > 0);
FramebufferManager::SetFramebuffer(0);
VertexShaderManager::SetViewportChanged();
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
TextureCache::DisableStage(0);
g_renderer->RestoreAPIState();
GL_REPORT_ERRORD();
}
u64 EncodeToRamFromTexture(u32 address,GLuint source_texture, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source)
{ {
u32 format = copyfmt; u32 format = copyfmt;
@ -379,19 +308,8 @@ u64 EncodeToRamFromTexture(u32 address,GLuint source_texture, bool bFromZBuffer,
EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource, EncodeToRamUsingShader(texconv_shader, source_texture, scaledSource,
dest_ptr, expandedWidth / samples, expandedHeight, readStride, dest_ptr, expandedWidth / samples, expandedHeight, readStride,
true, bScaleByHalf > 0 && !bFromZBuffer); true, bScaleByHalf > 0 && !bFromZBuffer);
return size_in_bytes; // TODO: D3D11 is calculating this value differently!
u64 hash = GetHash64(dest_ptr, size_in_bytes,
g_ActiveConfig.iSafeTextureCache_ColorSamples);
if (g_ActiveConfig.bEFBCopyCacheEnable)
{
// If the texture in RAM is already in the texture cache,
// do not copy it again as it has not changed.
if (TextureCache::Find(address, hash))
return hash;
}
TextureCache::MakeRangeDynamic(address,size_in_bytes);
return hash;
} }
void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, u8* destAddr, int dstWidth, int dstHeight) void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, u8* destAddr, int dstWidth, int dstHeight)

View File

@ -32,15 +32,13 @@ namespace TextureConverter
void Init(); void Init();
void Shutdown(); void Shutdown();
void EncodeToRam(u32 address, bool bFromZBuffer, bool bIsIntensityFmt,
u32 copyfmt, int bScaleByHalf, const EFBRectangle& source);
void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc,
u8* destAddr, int dstWidth, int dstHeight); u8* destAddr, int dstWidth, int dstHeight);
void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture); void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture);
u64 EncodeToRamFromTexture(u32 address, GLuint source_texture, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source); // returns size of the encoded data (in bytes)
int EncodeToRamFromTexture(u32 address, GLuint source_texture, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source);
} }

View File

@ -160,7 +160,7 @@ void VertexManager::vFlush()
if (tentry) if (tentry)
{ {
// 0s are probably for no manual wrapping needed. // 0s are probably for no manual wrapping needed.
PixelShaderManager::SetTexDims(i, tentry->realW, tentry->realH, 0, 0); PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height, 0, 0);
if (g_ActiveConfig.iLog & CONF_SAVETEXTURES) if (g_ActiveConfig.iLog & CONF_SAVETEXTURES)
{ {