Merged common texture cache code from video plugins into VideoCommon. (DX11 native mipmaps currently broken, disabled) Hopefully everything else should still be working.
git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@6288 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
parent
16ff21b0d5
commit
cf05cca7a6
|
@ -95,7 +95,7 @@ void Shutdown()
|
|||
textureMap.clear();
|
||||
}
|
||||
|
||||
PC_TexFormat GetHiresTex(const char *fileName, int *pWidth, int *pHeight, int texformat, u8 *data)
|
||||
PC_TexFormat GetHiresTex(const char *fileName, unsigned int *pWidth, unsigned int *pHeight, int texformat, u8 *data)
|
||||
{
|
||||
std::string key(fileName);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace HiresTextures
|
|||
{
|
||||
void Init(const char *gameCode);
|
||||
void Shutdown();
|
||||
PC_TexFormat GetHiresTex(const char *fileName, int *pWidth, int *pHeight, int texformat, u8 *data);
|
||||
PC_TexFormat GetHiresTex(const char *fileName, unsigned int *pWidth, unsigned int *pHeight, int texformat, u8 *data);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ files = [
|
|||
'TextureConversionShader.cpp',
|
||||
'ImageWrite.cpp',
|
||||
'VertexManagerBase.cpp',
|
||||
'TextureCacheBase.cpp',
|
||||
'Statistics.cpp',
|
||||
'Fifo.cpp',
|
||||
'VideoState.cpp',
|
||||
|
|
|
@ -0,0 +1,659 @@
|
|||
|
||||
#include "MemoryUtil.h"
|
||||
|
||||
#include "VideoConfig.h"
|
||||
#include "Statistics.h"
|
||||
#include "HiresTextures.h"
|
||||
#include "Render.h"
|
||||
#include "FileUtil.h"
|
||||
#include "Profiler.h"
|
||||
|
||||
#include "PluginSpecs.h"
|
||||
|
||||
#include "TextureCacheBase.h"
|
||||
|
||||
// ugly
|
||||
extern int frameCount;
|
||||
|
||||
enum
|
||||
{
|
||||
TEMP_SIZE = (1024 * 1024 * 4),
|
||||
TEXTURE_KILL_THRESHOLD = 200,
|
||||
};
|
||||
|
||||
TextureCache *g_texture_cache;
|
||||
|
||||
u8 *TextureCache::temp;
|
||||
TextureCache::TexCache TextureCache::textures;
|
||||
|
||||
// returns the exponent of the smallest power of two which is greater than val
|
||||
unsigned int GetPow2(unsigned int val)
|
||||
{
|
||||
unsigned int ret = 0;
|
||||
for (; val; val >>= 1)
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase::~TCacheEntryBase()
|
||||
{
|
||||
if (0 == addr)
|
||||
return;
|
||||
|
||||
if (!isRenderTarget && !g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
u32 *const ptr = (u32*)g_VideoInitialize.pGetMemoryPointer(addr);
|
||||
if (ptr && *ptr == hash)
|
||||
*ptr = oldpixel;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: uglyness
|
||||
extern PLUGIN_GLOBALS *globals;
|
||||
|
||||
TextureCache::TextureCache()
|
||||
{
|
||||
temp = (u8*)AllocateMemoryPages(TEMP_SIZE);
|
||||
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter);
|
||||
HiresTextures::Init(globals->unique_id);
|
||||
}
|
||||
|
||||
void TextureCache::Invalidate(bool shutdown)
|
||||
{
|
||||
TexCache::iterator
|
||||
iter = textures.begin(),
|
||||
tcend = textures.end();
|
||||
for (; iter != tcend; ++iter)
|
||||
{
|
||||
if (shutdown)
|
||||
iter->second->addr = 0;
|
||||
delete iter->second;
|
||||
}
|
||||
|
||||
textures.clear();
|
||||
HiresTextures::Shutdown();
|
||||
}
|
||||
|
||||
TextureCache::~TextureCache()
|
||||
{
|
||||
Invalidate(true);
|
||||
FreeMemoryPages(temp, TEMP_SIZE);
|
||||
temp = NULL;
|
||||
}
|
||||
|
||||
void TextureCache::Cleanup()
|
||||
{
|
||||
TexCache::iterator
|
||||
iter = textures.begin(),
|
||||
tcend = textures.end();
|
||||
while (iter != tcend)
|
||||
{
|
||||
if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount)
|
||||
{
|
||||
delete iter->second;
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator
|
||||
iter = textures.begin(),
|
||||
tcend = textures.end();
|
||||
while (iter != tcend)
|
||||
{
|
||||
const int rangePosition = iter->second->IntersectsMemoryRange(start_address, size);
|
||||
if (0 == rangePosition)
|
||||
{
|
||||
delete iter->second;
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator
|
||||
iter = textures.begin(),
|
||||
tcend = textures.end();
|
||||
for (; iter != tcend; ++iter)
|
||||
{
|
||||
const int rangePosition = iter->second->IntersectsMemoryRange(start_address, size);
|
||||
if (0 == rangePosition)
|
||||
{
|
||||
iter->second->hash = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int TextureCache::TCacheEntryBase::IntersectsMemoryRange(u32 range_address, u32 range_size) const
|
||||
{
|
||||
if (addr + size_in_bytes < range_address)
|
||||
return -1;
|
||||
|
||||
if (addr >= range_address + range_size)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextureCache::ClearRenderTargets()
|
||||
{
|
||||
TexCache::iterator
|
||||
iter = textures.begin(),
|
||||
tcend = textures.end();
|
||||
for (; iter!=tcend; ++iter)
|
||||
iter->second->isRenderTarget = false;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int stage,
|
||||
u32 address, unsigned int width, unsigned int height, int texformat,
|
||||
unsigned int tlutaddr, int tlutfmt, bool UseNativeMips, unsigned int maxlevel)
|
||||
{
|
||||
// necessary?
|
||||
if (0 == address)
|
||||
return NULL;
|
||||
|
||||
u8* ptr = g_VideoInitialize.pGetMemoryPointer(address);
|
||||
|
||||
// TexelSizeInNibbles(format)*width*height/16;
|
||||
const unsigned int bsw = TexDecoder_GetBlockWidthInTexels(texformat) - 1;
|
||||
const unsigned int bsh = TexDecoder_GetBlockHeightInTexels(texformat) - 1;
|
||||
|
||||
unsigned int expandedWidth = (width + bsw) & (~bsw);
|
||||
unsigned int expandedHeight = (height + bsh) & (~bsh);
|
||||
|
||||
u64 hash_value = 0;
|
||||
u64 texHash = 0;
|
||||
u32 texID = address;
|
||||
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;
|
||||
|
||||
// someone who understands this var could rename it :p
|
||||
const bool isC4_C8_C14X2 = (texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2);
|
||||
|
||||
if (isC4_C8_C14X2)
|
||||
full_format = texformat | (tlutfmt << 16);
|
||||
|
||||
// hires texture loading and texture dumping require accurate hashes
|
||||
if (g_ActiveConfig.bSafeTextureCache || g_ActiveConfig.bHiresTextures || g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
texHash = GetHash64(ptr, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
|
||||
if (isC4_C8_C14X2)
|
||||
{
|
||||
// WARNING! texID != address now => may break CopyRenderTargetToTexture (cf. TODO up)
|
||||
// tlut size can be up to 32768B (GX_TF_C14X2) but Safer == Slower.
|
||||
// This trick (to change the texID depending on the TLUT addr) is a trick to get around
|
||||
// an issue with metroid prime's fonts, where it has multiple sets of fonts on top of
|
||||
// each other stored in a single texture, and uses the palette to make different characters
|
||||
// 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.
|
||||
|
||||
const u64 tlutHash = GetHash64(texMem + tlutaddr, palette_size,
|
||||
g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
|
||||
texHash ^= tlutHash;
|
||||
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
texID ^= ((u32)tlutHash) ^ (tlutHash >> 32);
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
hash_value = texHash;
|
||||
}
|
||||
|
||||
TCacheEntryBase *entry = textures[texID];
|
||||
if (entry)
|
||||
{
|
||||
if (false == g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
if (entry->isRenderTarget || entry->isDynamic)
|
||||
{
|
||||
if (false == g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
hash_value = GetHash64(ptr, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
|
||||
if (isC4_C8_C14X2)
|
||||
{
|
||||
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: Is the mipLevels check needed?
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's reload the new texture data into the same texture,
|
||||
// instead of destroying it and having to create a new one.
|
||||
// Might speed up movie playback very, very slightly.
|
||||
texture_is_dynamic = (entry->isRenderTarget || entry->isDynamic) && !g_ActiveConfig.bCopyEFBToTexture;
|
||||
|
||||
// TODO: Is the mipLevels check needed?
|
||||
if (!entry->isRenderTarget &&
|
||||
((!entry->isDynamic && width == entry->w && height == entry->h && full_format == entry->format && entry->mipLevels == maxlevel)
|
||||
|| (entry->isDynamic && entry->w == width && entry->h == height)))
|
||||
{
|
||||
// reuse the texture
|
||||
}
|
||||
else
|
||||
{
|
||||
// delete the texture and make a new one
|
||||
delete entry;
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int nativeW = width;
|
||||
const unsigned int nativeH = height;
|
||||
|
||||
PC_TexFormat pcfmt = PC_TEX_FMT_NONE;
|
||||
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
// Load Custom textures
|
||||
char texPathTemp[MAX_PATH];
|
||||
|
||||
unsigned int newWidth = width;
|
||||
unsigned int newHeight = height;
|
||||
|
||||
sprintf(texPathTemp, "%s_%08x_%i", globals->unique_id, texHash, texformat);
|
||||
pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, texformat, temp);
|
||||
|
||||
if (pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
expandedWidth = width = newWidth;
|
||||
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)
|
||||
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth,
|
||||
expandedHeight, texformat, tlutaddr, tlutfmt, !g_texture_cache->isOGL());
|
||||
|
||||
const bool isPow2 = !((width & (width - 1)) || (height & (height - 1)));
|
||||
unsigned int texLevels = (isPow2 && UseNativeMips && maxlevel) ?
|
||||
GetPow2(std::max(width, height)) : !isPow2;
|
||||
|
||||
if ((texLevels > (maxlevel + 1)) && maxlevel)
|
||||
texLevels = maxlevel + 1;
|
||||
|
||||
// create the entry/texture
|
||||
if (NULL == entry)
|
||||
{
|
||||
textures[texID] = entry = g_texture_cache->CreateTexture(width, height, expandedWidth, texLevels, pcfmt);
|
||||
|
||||
entry->addr = address;
|
||||
entry->format = full_format;
|
||||
entry->mipLevels = maxlevel;
|
||||
entry->size_in_bytes = texture_size;
|
||||
|
||||
entry->scaledW = entry->w = width;
|
||||
entry->scaledH = entry->h = height;
|
||||
|
||||
entry->nativeH = nativeH;
|
||||
entry->nativeW = nativeW;
|
||||
|
||||
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
|
||||
entry->Load(width, height, expandedWidth, 0);
|
||||
|
||||
// load mips
|
||||
if (texLevels > 1 && pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
const unsigned int bsdepth = TexDecoder_GetTexelSizeInNibbles(texformat);
|
||||
|
||||
unsigned int level = 1;
|
||||
unsigned int mipWidth = (width + 1) >> 1;
|
||||
unsigned int mipHeight = (height + 1) >> 1;
|
||||
ptr += texture_size;
|
||||
|
||||
while ((mipHeight || mipWidth) && (level < texLevels))
|
||||
{
|
||||
const unsigned int currentWidth = (mipWidth > 0) ? mipWidth : 1;
|
||||
const unsigned int currentHeight = (mipHeight > 0) ? mipHeight : 1;
|
||||
|
||||
expandedWidth = (currentWidth + bsw) & (~bsw);
|
||||
expandedHeight = (currentHeight + bsh) & (~bsh);
|
||||
|
||||
TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, texformat, tlutaddr, tlutfmt, !g_texture_cache->isOGL());
|
||||
entry->Load(currentWidth, currentHeight, expandedWidth, level);
|
||||
|
||||
ptr += ((std::max(mipWidth, bsw) * std::max(mipHeight, bsh) * bsdepth) >> 1);
|
||||
mipWidth >>= 1;
|
||||
mipHeight >>= 1;
|
||||
++level;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: won't this cause loaded hires textures to be dumped as well?
|
||||
// dump texture to file
|
||||
if (g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
char szTemp[MAX_PATH];
|
||||
char szDir[MAX_PATH];
|
||||
|
||||
// make sure that the directory exists
|
||||
sprintf(szDir, "%s%s", File::GetUserPath(D_DUMPTEXTURES_IDX), globals->unique_id);
|
||||
if (false == File::Exists(szDir) || false == File::IsDirectory(szDir))
|
||||
File::CreateDir(szDir);
|
||||
|
||||
sprintf(szTemp, "%s/%s_%08x_%i.png", szDir, globals->unique_id, texHash, texformat);
|
||||
|
||||
if (false == File::Exists(szTemp))
|
||||
entry->Save(szTemp);
|
||||
}
|
||||
|
||||
INCSTAT(stats.numTexturesCreated);
|
||||
SETSTAT(stats.numTexturesAlive, textures.size());
|
||||
|
||||
return_entry:
|
||||
|
||||
entry->frameCount = frameCount;
|
||||
entry->Bind(stage);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer,
|
||||
bool bIsIntensityFmt, u32 copyfmt, bool bScaleByHalf, const EFBRectangle &source_rect)
|
||||
{
|
||||
DVSTARTPROFILE();
|
||||
|
||||
float colmat[20] = {};
|
||||
// last four floats of colmat for fConstAdd
|
||||
float *const fConstAdd = colmat + 16;
|
||||
unsigned int cbufid = -1;
|
||||
|
||||
if (bFromZBuffer)
|
||||
{
|
||||
// TODO: these values differ slightly from the DX9/11 values,
|
||||
// do they need to? or can this be removed
|
||||
if (g_texture_cache->isOGL())
|
||||
{
|
||||
switch(copyfmt)
|
||||
{
|
||||
case 0: // Z4
|
||||
case 1: // Z8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
break;
|
||||
|
||||
case 3: // Z16 //?
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
break;
|
||||
|
||||
case 11: // Z16 (reverse order)
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[13] = 1;
|
||||
break;
|
||||
|
||||
case 6: // Z24X8
|
||||
colmat[2] = colmat[5] = colmat[8] = colmat[15] = 1;
|
||||
break;
|
||||
|
||||
case 9: // Z8M
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
break;
|
||||
|
||||
case 10: // Z8L
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
break;
|
||||
|
||||
case 12: // Z16L
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy zbuf format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // Z4
|
||||
case 1: // Z8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1.0f;
|
||||
cbufid = 12;
|
||||
break;
|
||||
|
||||
case 3: // Z16 //?
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[12] = 1.0f;
|
||||
cbufid = 13;
|
||||
break;
|
||||
|
||||
case 11: // Z16 (reverse order)
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1.0f;
|
||||
cbufid = 14;
|
||||
break;
|
||||
|
||||
case 6: // Z24X8
|
||||
colmat[0] = colmat[5] = colmat[10] = 1.0f;
|
||||
cbufid = 15;
|
||||
break;
|
||||
|
||||
case 9: // Z8M
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1.0f;
|
||||
cbufid = 16;
|
||||
break;
|
||||
|
||||
case 10: // Z8L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1.0f;
|
||||
cbufid = 17;
|
||||
break;
|
||||
|
||||
case 12: // Z16L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[13] = 1.0f;
|
||||
cbufid = 18;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy zbuf format: 0x%x", copyfmt);
|
||||
colmat[2] = colmat[5] = colmat[8] = 1.0f;
|
||||
cbufid = 19;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (bIsIntensityFmt)
|
||||
{
|
||||
fConstAdd[0] = fConstAdd[1] = fConstAdd[2] = 16.0f/255.0f;
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // I4
|
||||
case 1: // I8
|
||||
case 2: // IA4
|
||||
case 3: // IA8
|
||||
// TODO - verify these coefficients
|
||||
colmat[0] = 0.257f; colmat[1] = 0.504f; colmat[2] = 0.098f;
|
||||
colmat[4] = 0.257f; colmat[5] = 0.504f; colmat[6] = 0.098f;
|
||||
colmat[8] = 0.257f; colmat[9] = 0.504f; colmat[10] = 0.098f;
|
||||
|
||||
if (copyfmt < 2)
|
||||
{
|
||||
fConstAdd[3] = 16.0f / 255.0f;
|
||||
colmat[12] = 0.257f; colmat[13] = 0.504f; colmat[14] = 0.098f;
|
||||
cbufid = 0;
|
||||
}
|
||||
else// alpha
|
||||
{
|
||||
colmat[15] = 1;
|
||||
cbufid = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy intensity format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // R4
|
||||
case 8: // R8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
cbufid = 2;
|
||||
break;
|
||||
|
||||
case 2: // RA4
|
||||
case 3: // RA8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[15] = 1;
|
||||
cbufid = 3;
|
||||
break;
|
||||
|
||||
case 7: // A8
|
||||
colmat[3] = colmat[7] = colmat[11] = colmat[15] = 1;
|
||||
cbufid = 4;
|
||||
break;
|
||||
|
||||
case 9: // G8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
cbufid = 5;
|
||||
break;
|
||||
|
||||
case 10: // B8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
cbufid = 6;
|
||||
break;
|
||||
|
||||
case 11: // RG8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
cbufid = 7;
|
||||
break;
|
||||
|
||||
case 12: // GB8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
cbufid = 8;
|
||||
break;
|
||||
|
||||
case 4: // RGB565
|
||||
colmat[0] = colmat[5] = colmat[10] = 1;
|
||||
fConstAdd[3] = 1; // set alpha to 1
|
||||
cbufid = 9;
|
||||
break;
|
||||
|
||||
case 5: // RGB5A3
|
||||
case 6: // RGBA8
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
cbufid = 10;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy color format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
cbufid = 11;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int tex_w = (abs(source_rect.GetWidth()) >> (int)bScaleByHalf);
|
||||
const unsigned int tex_h = (abs(source_rect.GetHeight()) >> (int)bScaleByHalf);
|
||||
|
||||
const float xScale = Renderer::GetTargetScaleX();
|
||||
const float yScale = Renderer::GetTargetScaleY();
|
||||
|
||||
unsigned int scaled_tex_w = g_ActiveConfig.bCopyEFBScaled ? (int)(tex_w * xScale) : tex_w;
|
||||
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? (int)(tex_h * yScale) : tex_h;
|
||||
|
||||
bool texture_is_dynamic = false;
|
||||
|
||||
TCacheEntryBase *entry = textures[address];
|
||||
if (entry)
|
||||
{
|
||||
if ((entry->isRenderTarget && entry->scaledW == scaled_tex_w && entry->scaledH == scaled_tex_h)
|
||||
|| (entry->isDynamic && entry->w == tex_w && entry->h == tex_h))
|
||||
{
|
||||
texture_is_dynamic = entry->isDynamic;
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove it and recreate it as a render target
|
||||
delete entry;
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (texture_is_dynamic)
|
||||
{
|
||||
scaled_tex_w = tex_w;
|
||||
scaled_tex_h = tex_h;
|
||||
}
|
||||
|
||||
if (NULL == entry)
|
||||
{
|
||||
// create the texture
|
||||
textures[address] = entry = g_texture_cache->CreateRenderTargetTexture(scaled_tex_w, scaled_tex_h);
|
||||
|
||||
entry->addr = address;
|
||||
entry->hash = 0;
|
||||
|
||||
entry->w = entry->nativeW = tex_w;
|
||||
entry->h = entry->nativeH = tex_h;
|
||||
|
||||
entry->scaledW = scaled_tex_w;
|
||||
entry->scaledH = scaled_tex_h;
|
||||
|
||||
entry->format = copyfmt;
|
||||
entry->mipLevels = 0;
|
||||
|
||||
entry->isRenderTarget = true;
|
||||
entry->isNonPow2 = true;
|
||||
entry->isDynamic = false;
|
||||
}
|
||||
|
||||
entry->frameCount = frameCount;
|
||||
|
||||
Renderer::ResetAPIState(); // reset any game specific settings
|
||||
|
||||
entry->FromRenderTarget(bFromZBuffer, bScaleByHalf, cbufid, colmat, source_rect, bIsIntensityFmt, copyfmt);
|
||||
|
||||
Renderer::RestoreAPIState();
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
#ifndef _TEXTURECACHEBASE_H
|
||||
#define _TEXTURECACHEBASE_H
|
||||
|
||||
#include <map>
|
||||
|
||||
//#include "VideoCommon.h"
|
||||
#include "TextureDecoder.h"
|
||||
#include "BPMemory.h"
|
||||
|
||||
#include "CommonTypes.h"
|
||||
|
||||
class TextureCache
|
||||
{
|
||||
public:
|
||||
struct TCacheEntryBase
|
||||
{
|
||||
// TODO: organize
|
||||
u32 addr;
|
||||
u32 size_in_bytes;
|
||||
u64 hash;
|
||||
//u32 paletteHash;
|
||||
u32 oldpixel;
|
||||
u32 format;
|
||||
|
||||
int frameCount;
|
||||
unsigned int w, h, mipLevels;
|
||||
// TODO: it looks like scaledW/H can be removed and w/h can be used in their place
|
||||
unsigned int scaledW, scaledH, nativeW, nativeH;
|
||||
|
||||
bool isRenderTarget;
|
||||
bool isDynamic; // mofified from cpu
|
||||
bool isNonPow2; // doesn't seem to be used anywhere
|
||||
|
||||
//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 void Bind(unsigned int stage) = 0;
|
||||
virtual bool Save(const char filename[]) = 0;
|
||||
|
||||
virtual void Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int level) = 0;
|
||||
virtual void FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float *colmat, const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt) = 0;
|
||||
|
||||
int IntersectsMemoryRange(u32 range_address, u32 range_size) const;
|
||||
};
|
||||
|
||||
virtual ~TextureCache(); // needs virtual for DX11 dtor
|
||||
|
||||
static void Cleanup();
|
||||
|
||||
static void Invalidate(bool shutdown);
|
||||
static void InvalidateRange(u32 start_address, u32 size);
|
||||
static void MakeRangeDynamic(u32 start_address, u32 size);
|
||||
static void ClearRenderTargets(); // currently only used by OGL
|
||||
|
||||
virtual TCacheEntryBase* CreateTexture(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt) = 0;
|
||||
virtual TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h) = 0;
|
||||
|
||||
static TCacheEntryBase* Load(unsigned int stage, u32 address, unsigned int width, unsigned int height,
|
||||
int format, unsigned int tlutaddr, int tlutfmt, bool UseNativeMips, unsigned int maxlevel);
|
||||
static void CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt,
|
||||
u32 copyfmt, bool bScaleByHalf, const EFBRectangle &source_rect);
|
||||
|
||||
protected:
|
||||
TextureCache();
|
||||
|
||||
static u8 *temp;
|
||||
|
||||
private:
|
||||
typedef std::map<u32, TCacheEntryBase*> TexCache;
|
||||
|
||||
static TexCache textures;
|
||||
|
||||
virtual bool isOGL() { return false; } // Hacks for TextureDecode_real support
|
||||
};
|
||||
|
||||
extern TextureCache *g_texture_cache;
|
||||
|
||||
#endif
|
|
@ -4,6 +4,12 @@
|
|||
#include "Statistics.h"
|
||||
#include "OpcodeDecoding.h"
|
||||
#include "IndexGenerator.h"
|
||||
#include "VertexShaderManager.h"
|
||||
#include "PixelShaderManager.h"
|
||||
#include "NativeVertexFormat.h"
|
||||
#include "TextureCacheBase.h"
|
||||
#include "Render.h"
|
||||
#include "Profiler.h"
|
||||
|
||||
#include "VertexManagerBase.h"
|
||||
|
||||
|
@ -151,8 +157,158 @@ void VertexManager::AddVertices(int primitive, int numVertices)
|
|||
AddIndices(primitive, numVertices);
|
||||
}
|
||||
|
||||
// TODO: merge this func, (need to merge TextureCache)
|
||||
void VertexManager::Flush()
|
||||
{
|
||||
g_vertex_manager->vFlush();
|
||||
}
|
||||
|
||||
// TODO: need to merge more stuff into VideoCommon to use this
|
||||
#if (0)
|
||||
void VertexManager::Flush()
|
||||
{
|
||||
if (LocalVBuffer == s_pCurBufferPointer || Flushed)
|
||||
return;
|
||||
|
||||
Flushed = true;
|
||||
|
||||
VideoFifo_CheckEFBAccess();
|
||||
|
||||
#if defined(_DEBUG) || defined(DEBUGFAST)
|
||||
PRIM_LOG("frame%d:\n texgen=%d, numchan=%d, dualtex=%d, ztex=%d, cole=%d, alpe=%d, ze=%d", g_ActiveConfig.iSaveTargetId, xfregs.numTexGens,
|
||||
xfregs.nNumChans, (int)xfregs.bEnableDualTexTransform, bpmem.ztex2.op,
|
||||
bpmem.blendmode.colorupdate, bpmem.blendmode.alphaupdate, bpmem.zmode.updateenable);
|
||||
|
||||
for (int i = 0; i < xfregs.nNumChans; ++i)
|
||||
{
|
||||
LitChannel* ch = &xfregs.colChans[i].color;
|
||||
PRIM_LOG("colchan%d: matsrc=%d, light=0x%x, ambsrc=%d, diffunc=%d, attfunc=%d", i, ch->matsource, ch->GetFullLightMask(), ch->ambsource, ch->diffusefunc, ch->attnfunc);
|
||||
ch = &xfregs.colChans[i].alpha;
|
||||
PRIM_LOG("alpchan%d: matsrc=%d, light=0x%x, ambsrc=%d, diffunc=%d, attfunc=%d", i, ch->matsource, ch->GetFullLightMask(), ch->ambsource, ch->diffusefunc, ch->attnfunc);
|
||||
}
|
||||
|
||||
for (int i = 0; i < xfregs.numTexGens; ++i)
|
||||
{
|
||||
TexMtxInfo tinfo = xfregs.texcoords[i].texmtxinfo;
|
||||
if (tinfo.texgentype != XF_TEXGEN_EMBOSS_MAP) tinfo.hex &= 0x7ff;
|
||||
if (tinfo.texgentype != XF_TEXGEN_REGULAR) tinfo.projection = 0;
|
||||
|
||||
PRIM_LOG("txgen%d: proj=%d, input=%d, gentype=%d, srcrow=%d, embsrc=%d, emblght=%d, postmtx=%d, postnorm=%d",
|
||||
i, tinfo.projection, tinfo.inputform, tinfo.texgentype, tinfo.sourcerow, tinfo.embosssourceshift, tinfo.embosslightshift,
|
||||
xfregs.texcoords[i].postmtxinfo.index, xfregs.texcoords[i].postmtxinfo.normalize);
|
||||
}
|
||||
|
||||
PRIM_LOG("pixel: tev=%d, ind=%d, texgen=%d, dstalpha=%d, alphafunc=0x%x", bpmem.genMode.numtevstages+1, bpmem.genMode.numindstages,
|
||||
bpmem.genMode.numtexgens, (u32)bpmem.dstalpha.enable, (bpmem.alphaFunc.hex>>16)&0xff);
|
||||
#endif
|
||||
|
||||
DVSTARTPROFILE();
|
||||
|
||||
// set the textures
|
||||
DVSTARTSUBPROFILE("VertexManager::Flush:textures");
|
||||
|
||||
u32 usedtextures = 0;
|
||||
for (u32 i = 0; i < (u32)bpmem.genMode.numtevstages + 1; ++i)
|
||||
if (bpmem.tevorders[i / 2].getEnable(i & 1))
|
||||
usedtextures |= 1 << bpmem.tevorders[i/2].getTexMap(i & 1);
|
||||
|
||||
if (bpmem.genMode.numindstages > 0)
|
||||
for (u32 i = 0; i < (u32)bpmem.genMode.numtevstages + 1; ++i)
|
||||
if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages)
|
||||
usedtextures |= 1 << bpmem.tevindref.getTexMap(bpmem.tevind[i].bt);
|
||||
|
||||
for (u32 i = 0; i < 8; ++i)
|
||||
{
|
||||
if (usedtextures & (1 << i))
|
||||
{
|
||||
// TODO:
|
||||
//glActiveTexture(GL_TEXTURE0 + i);
|
||||
|
||||
Renderer::SetSamplerState(i & 3, i >> 2);
|
||||
FourTexUnits &tex = bpmem.tex[i >> 2];
|
||||
|
||||
TCacheEntry::TCacheEntryBase* tentry = TextureCache::Load(i,
|
||||
(tex.texImage3[i&3].image_base/* & 0x1FFFFF*/) << 5,
|
||||
tex.texImage0[i&3].width + 1, tex.texImage0[i&3].height + 1,
|
||||
tex.texImage0[i&3].format, tex.texTlut[i&3].tmem_offset<<9,
|
||||
tex.texTlut[i&3].tlut_format,
|
||||
(tex.texMode0[i&3].min_filter & 3) && (tex.texMode0[i&3].min_filter != 8) && g_ActiveConfig.bUseNativeMips,
|
||||
(tex.texMode1[i&3].max_lod >> 4));
|
||||
|
||||
if (tentry)
|
||||
{
|
||||
// 0s are probably for no manual wrapping needed.
|
||||
PixelShaderManager::SetTexDims(i, tentry->nativeW, tentry->nativeH, 0, 0);
|
||||
|
||||
// TODO:
|
||||
//if (g_ActiveConfig.iLog & CONF_SAVETEXTURES)
|
||||
//{
|
||||
// // save the textures
|
||||
// char strfile[255];
|
||||
// sprintf(strfile, "%stex%.3d_%d.tga", File::GetUserPath(D_DUMPFRAMES_IDX), g_Config.iSaveTargetId, i);
|
||||
// SaveTexture(strfile, GL_TEXTURE_2D, tentry->texture, tentry->w, tentry->h);
|
||||
//}
|
||||
}
|
||||
else
|
||||
ERROR_LOG(VIDEO, "error loading texture");
|
||||
}
|
||||
}
|
||||
|
||||
// set global constants
|
||||
VertexShaderManager::SetConstants();
|
||||
PixelShaderManager::SetConstants();
|
||||
|
||||
// finally bind
|
||||
if (false == PixelShaderCache::SetShader(false, g_nativeVertexFmt->m_components))
|
||||
goto shader_fail;
|
||||
if (false == VertexShaderCache::SetShader(g_nativeVertexFmt->m_components))
|
||||
goto shader_fail;
|
||||
|
||||
const int stride = g_nativeVertexFmt->GetVertexStride();
|
||||
//if (g_nativeVertexFmt)
|
||||
g_nativeVertexFmt->SetupVertexPointers();
|
||||
|
||||
g_vertex_manager->Draw(stride, false);
|
||||
|
||||
// run through vertex groups again to set alpha
|
||||
if (false == g_ActiveConfig.bDstAlphaPass && bpmem.dstalpha.enable && bpmem.blendmode.alphaupdate)
|
||||
{
|
||||
if (false == PixelShaderCache::SetShader(true, g_nativeVertexFmt->m_components))
|
||||
goto shader_fail;
|
||||
|
||||
g_vertex_manager->Draw(stride, true);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
//IndexGenerator::Start(TIBuffer, LIBuffer, PIBuffer);
|
||||
|
||||
#if defined(_DEBUG) || defined(DEBUGFAST)
|
||||
if (g_ActiveConfig.iLog & CONF_SAVESHADERS)
|
||||
{
|
||||
// save the shaders
|
||||
char strfile[255];
|
||||
sprintf(strfile, "%sps%.3d.txt", File::GetUserPath(D_DUMPFRAMES_IDX), g_ActiveConfig.iSaveTargetId);
|
||||
std::ofstream fps(strfile);
|
||||
fps << ps->strprog.c_str();
|
||||
sprintf(strfile, "%svs%.3d.txt", File::GetUserPath(D_DUMPFRAMES_IDX), g_ActiveConfig.iSaveTargetId);
|
||||
std::ofstream fvs(strfile);
|
||||
fvs << vs->strprog.c_str();
|
||||
}
|
||||
|
||||
if (g_ActiveConfig.iLog & CONF_SAVETARGETS)
|
||||
{
|
||||
char str[128];
|
||||
sprintf(str, "%starg%.3d.tga", File::GetUserPath(D_DUMPFRAMES_IDX), g_ActiveConfig.iSaveTargetId);
|
||||
TargetRectangle tr;
|
||||
tr.left = 0;
|
||||
tr.right = Renderer::GetTargetWidth();
|
||||
tr.top = 0;
|
||||
tr.bottom = Renderer::GetTargetHeight();
|
||||
Renderer::SaveRenderTarget(str, tr);
|
||||
}
|
||||
#endif
|
||||
++g_Config.iSaveTargetId;
|
||||
|
||||
shader_fail:
|
||||
ResetBuffer();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -48,8 +48,10 @@ protected:
|
|||
|
||||
private:
|
||||
static void AddIndices(int primitive, int numVertices);
|
||||
// temporary
|
||||
//virtual void Draw(u32 stride, bool alphapass) = 0;
|
||||
// temp
|
||||
virtual void vFlush() = 0;
|
||||
|
||||
};
|
||||
|
||||
extern VertexManager *g_vertex_manager;
|
||||
|
|
|
@ -730,6 +730,14 @@
|
|||
<Filter
|
||||
Name="Base"
|
||||
>
|
||||
<File
|
||||
RelativePath=".\Src\TextureCacheBase.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Src\TextureCacheBase.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Src\VertexManagerBase.cpp"
|
||||
>
|
||||
|
|
|
@ -80,7 +80,7 @@ void CopyEFB(const BPCmd &bp, const EFBRectangle &rc, const u32 &address, const
|
|||
// bpmem.zcontrol.pixel_format to PIXELFMT_Z24 is when the game wants to copy from ZBuffer (Zbuffer uses 24-bit Format)
|
||||
if (!g_ActiveConfig.bEFBCopyDisable)
|
||||
{
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, scaleByHalf, rc);
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, !!scaleByHalf, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,39 +40,143 @@
|
|||
#include "TextureCache.h"
|
||||
#include "HiresTextures.h"
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
|
||||
ID3D11BlendState* efbcopyblendstate = NULL;
|
||||
ID3D11RasterizerState* efbcopyraststate = NULL;
|
||||
ID3D11DepthStencilState* efbcopydepthstate = NULL;
|
||||
ID3D11Buffer* efbcopycbuf[20] = { NULL };
|
||||
ID3D11Buffer* efbcopycbuf[20] = {};
|
||||
|
||||
u8* TextureCache::temp = NULL;
|
||||
TextureCache::TexCache TextureCache::textures;
|
||||
|
||||
extern int frameCount;
|
||||
|
||||
#define TEMP_SIZE (2048*2048*4)
|
||||
#define TEXTURE_KILL_THRESHOLD 200
|
||||
|
||||
void TextureCache::TCacheEntry::Destroy(bool shutdown)
|
||||
TextureCache::TCacheEntry::~TCacheEntry()
|
||||
{
|
||||
SAFE_RELEASE(texture);
|
||||
|
||||
if (!isRenderTarget && !shutdown && !g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
u32 *ptr = (u32*)g_VideoInitialize.pGetMemoryPointer(addr);
|
||||
if (ptr && *ptr == hash)
|
||||
*ptr = oldpixel;
|
||||
}
|
||||
texture->Release();
|
||||
}
|
||||
|
||||
void TextureCache::Init()
|
||||
void TextureCache::TCacheEntry::Bind(unsigned int stage)
|
||||
{
|
||||
D3D::gfxstate->SetShaderResource(stage, texture->GetSRV());
|
||||
}
|
||||
|
||||
bool TextureCache::TCacheEntry::Save(const char filename[])
|
||||
{
|
||||
return SUCCEEDED(PD3DX11SaveTextureToFileA(D3D::context, texture->GetTex(), D3DX11_IFF_PNG, filename));
|
||||
}
|
||||
|
||||
void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int level)
|
||||
{
|
||||
// TODO: remove hax
|
||||
if (level != 0) return;
|
||||
|
||||
D3D::ReplaceRGBATexture2D(texture->GetTex(), TextureCache::temp, width, height, expanded_width, level, usage);
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width,
|
||||
unsigned int height, unsigned int expanded_width,
|
||||
unsigned int tex_levels, PC_TexFormat pcfmt)
|
||||
{
|
||||
D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
|
||||
D3D11_CPU_ACCESS_FLAG cpu_access = (D3D11_CPU_ACCESS_FLAG)0;
|
||||
D3D11_SUBRESOURCE_DATA srdata, *data = NULL;
|
||||
|
||||
// TODO: remove hax
|
||||
tex_levels = 1;
|
||||
|
||||
if (1 == tex_levels)
|
||||
{
|
||||
usage = D3D11_USAGE_DYNAMIC;
|
||||
cpu_access = D3D11_CPU_ACCESS_WRITE;
|
||||
|
||||
srdata.pSysMem = TextureCache::temp;
|
||||
srdata.SysMemPitch = 4 * expanded_width;
|
||||
// testing
|
||||
//data = &srdata;
|
||||
}
|
||||
|
||||
const D3D11_TEXTURE2D_DESC texdesc = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
width, height, 1, tex_levels, D3D11_BIND_SHADER_RESOURCE, usage, cpu_access);
|
||||
|
||||
ID3D11Texture2D *pTexture;
|
||||
const HRESULT hr = D3D::device->CreateTexture2D(&texdesc, data, &pTexture);
|
||||
CHECK(SUCCEEDED(hr), "Create texture of the TextureCache");
|
||||
|
||||
TCacheEntry* const entry = new TCacheEntry(new D3DTexture2D(pTexture, D3D11_BIND_SHADER_RESOURCE));
|
||||
entry->usage = usage;
|
||||
|
||||
// TODO: better debug names
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)entry->texture->GetTex(), "a texture of the TextureCache");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)entry->texture->GetSRV(), "shader resource view of a texture of the TextureCache");
|
||||
|
||||
SAFE_RELEASE(pTexture);
|
||||
|
||||
if (0 == tex_levels)
|
||||
PD3DX11FilterTexture(D3D::context, entry->texture->GetTex(), 0, D3DX11_DEFAULT);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void TextureCache::TCacheEntry::FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float colmat[], const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt)
|
||||
{
|
||||
// stretch picture with increased internal resolution
|
||||
const D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)scaledW, (float)scaledH);
|
||||
D3D::context->RSSetViewports(1, &vp);
|
||||
|
||||
// set transformation
|
||||
if (NULL == efbcopycbuf[cbufid])
|
||||
{
|
||||
const D3D11_BUFFER_DESC cbdesc = CD3D11_BUFFER_DESC(20 * sizeof(float), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DEFAULT);
|
||||
D3D11_SUBRESOURCE_DATA data;
|
||||
data.pSysMem = colmat;
|
||||
HRESULT hr = D3D::device->CreateBuffer(&cbdesc, &data, &efbcopycbuf[cbufid]);
|
||||
CHECK(SUCCEEDED(hr), "Create efb copy constant buffer %d", cbufid);
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopycbuf[cbufid], "a constant buffer used in TextureCache::CopyRenderTargetToTexture");
|
||||
}
|
||||
D3D::context->PSSetConstantBuffers(0, 1, &efbcopycbuf[cbufid]);
|
||||
|
||||
const TargetRectangle targetSource = Renderer::ConvertEFBRectangle(source_rect);
|
||||
// TODO: try targetSource.asRECT();
|
||||
const D3D11_RECT sourcerect = CD3D11_RECT(targetSource.left, targetSource.top, targetSource.right, targetSource.bottom);
|
||||
|
||||
// Use linear filtering if (bScaleByHalf), use point filtering otherwise
|
||||
if (bScaleByHalf)
|
||||
D3D::SetLinearCopySampler();
|
||||
else
|
||||
D3D::SetPointCopySampler();
|
||||
|
||||
D3D::stateman->PushBlendState(efbcopyblendstate);
|
||||
D3D::stateman->PushRasterizerState(efbcopyraststate);
|
||||
D3D::stateman->PushDepthState(efbcopydepthstate);
|
||||
|
||||
D3D::context->OMSetRenderTargets(1, &texture->GetRTV(), NULL);
|
||||
|
||||
D3D::drawShadedTexQuad(
|
||||
(bFromZBuffer) ? g_framebufferManager.GetEFBDepthTexture()->GetSRV() : g_framebufferManager.GetEFBColorTexture()->GetSRV(),
|
||||
&sourcerect, Renderer::GetFullTargetWidth(), Renderer::GetFullTargetHeight(),
|
||||
(bFromZBuffer) ? PixelShaderCache::GetDepthMatrixProgram() : PixelShaderCache::GetColorMatrixProgram(),
|
||||
VertexShaderCache::GetSimpleVertexShader(), VertexShaderCache::GetSimpleInputLayout());
|
||||
|
||||
D3D::context->OMSetRenderTargets(1, &g_framebufferManager.GetEFBColorTexture()->GetRTV(), g_framebufferManager.GetEFBDepthTexture()->GetDSV());
|
||||
|
||||
D3D::stateman->PopBlendState();
|
||||
D3D::stateman->PopDepthState();
|
||||
D3D::stateman->PopRasterizerState();
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateRenderTargetTexture(
|
||||
unsigned int scaled_tex_w, unsigned int scaled_tex_h)
|
||||
{
|
||||
return new TCacheEntry(D3DTexture2D::Create(scaled_tex_w, scaled_tex_h,
|
||||
(D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET | (int)D3D11_BIND_SHADER_RESOURCE),
|
||||
D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8G8B8A8_UNORM));
|
||||
}
|
||||
|
||||
TextureCache::TextureCache()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
temp = (u8*)AllocateMemoryPages(TEMP_SIZE);
|
||||
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter);
|
||||
HiresTextures::Init(globals->unique_id);
|
||||
|
||||
D3D11_BLEND_DESC blenddesc;
|
||||
blenddesc.AlphaToCoverageEnable = FALSE;
|
||||
blenddesc.IndependentBlendEnable = FALSE;
|
||||
|
@ -85,8 +189,8 @@ void TextureCache::Init()
|
|||
blenddesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
||||
blenddesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||
hr = D3D::device->CreateBlendState(&blenddesc, &efbcopyblendstate);
|
||||
CHECK(hr==S_OK, "Create blend state for TextureCache::CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopyblendstate, "blend state used in TextureCache::CopyRenderTargetToTexture");
|
||||
CHECK(hr==S_OK, "Create blend state for CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopyblendstate, "blend state used in CopyRenderTargetToTexture");
|
||||
|
||||
D3D11_DEPTH_STENCIL_DESC depthdesc;
|
||||
depthdesc.DepthEnable = FALSE;
|
||||
|
@ -96,8 +200,8 @@ void TextureCache::Init()
|
|||
depthdesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
|
||||
depthdesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
|
||||
hr = D3D::device->CreateDepthStencilState(&depthdesc, &efbcopydepthstate);
|
||||
CHECK(hr==S_OK, "Create depth state for TextureCache::CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopydepthstate, "depth stencil state used in TextureCache::CopyRenderTargetToTexture");
|
||||
CHECK(hr==S_OK, "Create depth state for CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopydepthstate, "depth stencil state used in CopyRenderTargetToTexture");
|
||||
|
||||
D3D11_RASTERIZER_DESC rastdesc;
|
||||
rastdesc.CullMode = D3D11_CULL_NONE;
|
||||
|
@ -111,528 +215,18 @@ void TextureCache::Init()
|
|||
rastdesc.MultisampleEnable = false;
|
||||
rastdesc.AntialiasedLineEnable = false;
|
||||
hr = D3D::device->CreateRasterizerState(&rastdesc, &efbcopyraststate);
|
||||
CHECK(hr==S_OK, "Create rasterizer state for TextureCache::CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopyraststate, "rasterizer state used in TextureCache::CopyRenderTargetToTexture");
|
||||
CHECK(hr==S_OK, "Create rasterizer state for CopyRenderTargetToTexture");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopyraststate, "rasterizer state used in CopyRenderTargetToTexture");
|
||||
}
|
||||
|
||||
void TextureCache::Invalidate(bool shutdown)
|
||||
TextureCache::~TextureCache()
|
||||
{
|
||||
for (TexCache::iterator iter = textures.begin(); iter != textures.end(); ++iter)
|
||||
iter->second.Destroy(shutdown);
|
||||
textures.clear();
|
||||
HiresTextures::Shutdown();
|
||||
}
|
||||
|
||||
void TextureCache::Shutdown()
|
||||
{
|
||||
Invalidate(true);
|
||||
FreeMemoryPages(temp, TEMP_SIZE);
|
||||
temp = NULL;
|
||||
|
||||
SAFE_RELEASE(efbcopyblendstate);
|
||||
SAFE_RELEASE(efbcopyraststate);
|
||||
SAFE_RELEASE(efbcopydepthstate);
|
||||
|
||||
for (unsigned int k = 0; k < 20;k++)
|
||||
for (unsigned int k = 0; k < 20; ++k)
|
||||
SAFE_RELEASE(efbcopycbuf[k]);
|
||||
}
|
||||
|
||||
void TextureCache::Cleanup()
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second.frameCount)
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
iter = textures.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
if (iter->second.IntersectsMemoryRange(start_address, size))
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TextureCache::TCacheEntry::IntersectsMemoryRange(u32 range_address, u32 range_size)
|
||||
{
|
||||
if (addr + size_in_bytes < range_address) return false;
|
||||
if (addr >= range_address + range_size) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// returns the exponent of the smallest power of two which is greater than val
|
||||
unsigned int GetPow2(unsigned int val)
|
||||
{
|
||||
unsigned int ret = 0;
|
||||
for (;val;val>>=1) ret++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntry* TextureCache::Load(unsigned int stage, u32 address, unsigned int width, unsigned int height, unsigned int tex_format, unsigned int tlutaddr, unsigned int tlutfmt, bool UseNativeMips, unsigned int maxlevel)
|
||||
{
|
||||
// notes (about "UNsafe texture cache"):
|
||||
// Have to be removed soon.
|
||||
// But we keep it until the "safe" way became rock solid
|
||||
// pros: it has an unique ID held by the texture data itself (@address) once cached.
|
||||
// cons: it writes this unique ID in the gc RAM <- very dangerous (break MP1) and ugly
|
||||
|
||||
// notes (about "safe texture cache"):
|
||||
// Metroids text issue (character table):
|
||||
// Same addr, same GX_TF_C4 texture data but different TLUT (hence different outputs).
|
||||
// That's why we have to hash the TLUT too for TLUT tex_format dependent textures (ie. GX_TF_C4, GX_TF_C8, GX_TF_C14X2).
|
||||
// And since the address and tex data don't change, the key index in the cacheEntry map can't be the address but
|
||||
// have to be a real unique ID.
|
||||
// DONE but not satifiying yet -> may break copyEFBToTexture sometimes.
|
||||
|
||||
// Pokemon Colosseum text issue (plain text):
|
||||
// Use a GX_TF_I4 512x512 text-flush-texture at a const address.
|
||||
// The problem here was just the sparse hash on the texture. This texture is partly overwrited (what is needed only)
|
||||
// so lot's of remaning old text. Thin white chars on black bg too.
|
||||
|
||||
// TODO: - clean this up when ready to kill old "unsafe texture cache"
|
||||
// - fix the key index situation with CopyRenderTargetToTexture.
|
||||
// Could happen only for GX_TF_C4, GX_TF_C8 and GX_TF_C14X2 fmt.
|
||||
// Wonder if we can't use tex width&height to know if EFB might be copied to it...
|
||||
// raw idea: TOCHECK if addresses are aligned we have few bits left...
|
||||
|
||||
if (address == 0)
|
||||
return NULL;
|
||||
|
||||
u8 *ptr = g_VideoInitialize.pGetMemoryPointer(address);
|
||||
unsigned int bsw = TexDecoder_GetBlockWidthInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
unsigned int bsh = TexDecoder_GetBlockHeightInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
unsigned int bsdepth = TexDecoder_GetTexelSizeInNibbles(tex_format);
|
||||
unsigned int expandedWidth = (width + bsw) & (~bsw);
|
||||
unsigned int expandedHeight = (height + bsh) & (~bsh);
|
||||
|
||||
u64 hash_value = 0;
|
||||
u32 texID = address;
|
||||
u64 texHash = 0;
|
||||
u32 FullFormat = tex_format;
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
FullFormat = (tex_format | (tlutfmt << 16));
|
||||
|
||||
// hires texture loading and texture dumping require accurate hashes
|
||||
if (g_ActiveConfig.bSafeTextureCache || g_ActiveConfig.bHiresTextures || g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
texHash = GetHash64(ptr,TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
{
|
||||
// WARNING! texID != address now => may break CopyRenderTargetToTexture (cf. TODO up)
|
||||
// tlut size can be up to 32768B (GX_TF_C14X2) but Safer == Slower.
|
||||
// This trick (to change the texID depending on the TLUT addr) is a trick to get around
|
||||
// an issue with metroid prime's fonts, where it has multiple sets of fonts on top of
|
||||
// each other stored in a single texture, and uses the palette to make different characters
|
||||
// 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.
|
||||
u64 tlutHash = GetHash64(&texMem[tlutaddr], TexDecoder_GetPaletteSize(tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
texHash ^= tlutHash;
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
texID = texID ^ ((u32)(tlutHash & 0xFFFFFFFF)) ^ ((u32)((tlutHash >> 32) & 0xFFFFFFFF));
|
||||
}
|
||||
}
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
hash_value = texHash;
|
||||
}
|
||||
|
||||
bool skip_texture_create = false;
|
||||
TexCache::iterator iter = textures.find(texID);
|
||||
|
||||
if (iter != textures.end())
|
||||
{
|
||||
TCacheEntry &entry = iter->second;
|
||||
|
||||
if (!g_ActiveConfig.bSafeTextureCache)
|
||||
hash_value = ((u32*)ptr)[0];
|
||||
|
||||
// TODO: Is the (entry.MipLevels == maxlevel) check needed?
|
||||
if (entry.isRenderTarget || ((address == entry.addr) && (hash_value == entry.hash) && FullFormat == entry.fmt && entry.MipLevels == maxlevel))
|
||||
{
|
||||
entry.frameCount = frameCount;
|
||||
D3D::gfxstate->SetShaderResource(stage, entry.texture->GetSRV());
|
||||
return &entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's reload the new texture data into the same texture,
|
||||
// instead of destroying it and having to create a new one.
|
||||
// Might speed up movie playback very, very slightly.
|
||||
|
||||
// TODO: Is the (entry.MipLevels < maxlevel) check needed?
|
||||
if (width == entry.w && height==entry.h && FullFormat == entry.fmt && entry.MipLevels < maxlevel)
|
||||
{
|
||||
skip_texture_create = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Destroy(false);
|
||||
textures.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make an entry in the table
|
||||
TCacheEntry& entry = textures[texID];
|
||||
entry.Realw = width;
|
||||
entry.Realh = height;
|
||||
PC_TexFormat pcfmt = PC_TEX_FMT_NONE;
|
||||
|
||||
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt, true);
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
// Load Custom textures
|
||||
char texPathTemp[MAX_PATH];
|
||||
int newWidth = width;
|
||||
int newHeight = height;
|
||||
|
||||
sprintf(texPathTemp, "%s_%08x_%i", globals->unique_id, texHash, tex_format);
|
||||
pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, GX_TF_RGBA8, temp);
|
||||
if (pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
expandedWidth = width = newWidth;
|
||||
expandedHeight = height = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcfmt == PC_TEX_FMT_NONE)
|
||||
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt, true);
|
||||
|
||||
entry.oldpixel = ((u32*)ptr)[0];
|
||||
if (g_ActiveConfig.bSafeTextureCache) entry.hash = hash_value;
|
||||
else entry.hash = ((u32*)ptr)[0] = (u32)(((double)rand() / RAND_MAX) * 0xFFFFFFFF);
|
||||
|
||||
bool isPow2 = !((width & (width - 1)) || (height & (height - 1)));
|
||||
unsigned int TexLevels = (isPow2 && UseNativeMips && maxlevel) ? GetPow2(max(width, height)) : ((isPow2)? 0 : 1);
|
||||
if (TexLevels > (maxlevel + 1) && maxlevel)
|
||||
TexLevels = maxlevel + 1;
|
||||
|
||||
D3D11_USAGE usage = (TexLevels == 1) ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
|
||||
|
||||
if (!skip_texture_create)
|
||||
{
|
||||
// TODO: A little more verbosity in the debug names would be quite helpful..
|
||||
D3D11_CPU_ACCESS_FLAG cpu_access = (TexLevels == 1) ? D3D11_CPU_ACCESS_WRITE : (D3D11_CPU_ACCESS_FLAG)0;
|
||||
ID3D11Texture2D* pTexture = NULL;
|
||||
HRESULT hr;
|
||||
D3D11_SUBRESOURCE_DATA data;
|
||||
data.pSysMem = temp;
|
||||
data.SysMemPitch = 4*expandedWidth;
|
||||
|
||||
D3D11_TEXTURE2D_DESC texdesc = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 1, TexLevels, D3D11_BIND_SHADER_RESOURCE, usage, cpu_access);
|
||||
hr = D3D::device->CreateTexture2D(&texdesc, (TexLevels==1)?&data:NULL, &pTexture);
|
||||
CHECK(hr==S_OK, "Create texture of the TextureCache");
|
||||
entry.texture = new D3DTexture2D(pTexture, D3D11_BIND_SHADER_RESOURCE);
|
||||
CHECK(entry.texture!=NULL, "Create texture of the TextureCache");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)entry.texture->GetTex(), "a texture of the TextureCache");
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)entry.texture->GetSRV(), "shader resource view of a texture of the TextureCache");
|
||||
SAFE_RELEASE(pTexture);
|
||||
|
||||
// if (TexLevels == 1), we already loaded the data into our texture upon creation
|
||||
if (TexLevels != 1) D3D::ReplaceRGBATexture2D(entry.texture->GetTex(), temp, width, height, expandedWidth, 0, usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
D3D::ReplaceRGBATexture2D(entry.texture->GetTex(), temp, width, height, expandedWidth, 0, usage);
|
||||
}
|
||||
|
||||
entry.addr = address;
|
||||
entry.size_in_bytes = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format);
|
||||
entry.isRenderTarget = false;
|
||||
entry.isNonPow2 = false;
|
||||
entry.MipLevels = maxlevel;
|
||||
|
||||
if (TexLevels == 0) PD3DX11FilterTexture(D3D::context, entry.texture->GetTex(), 0, D3DX11_DEFAULT);
|
||||
else if (TexLevels > 1 && pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
unsigned int level = 1;
|
||||
unsigned int mipWidth = (width + 1) >> 1;
|
||||
unsigned int mipHeight = (height + 1) >> 1;
|
||||
ptr += entry.size_in_bytes;
|
||||
while ((mipHeight || mipWidth) && (level < TexLevels))
|
||||
{
|
||||
unsigned int currentWidth = (mipWidth > 0) ? mipWidth : 1;
|
||||
unsigned int currentHeight = (mipHeight > 0) ? mipHeight : 1;
|
||||
expandedWidth = (currentWidth + bsw) & (~bsw);
|
||||
expandedHeight = (currentHeight + bsh) & (~bsh);
|
||||
TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt, true);
|
||||
D3D::ReplaceRGBATexture2D(entry.texture->GetTex(), temp, currentWidth, currentHeight, expandedWidth, level, usage);
|
||||
u32 size = (max(mipWidth, bsw) * max(mipHeight, bsh) * bsdepth) >> 1;
|
||||
ptr += size;
|
||||
mipWidth >>= 1;
|
||||
mipHeight >>= 1;
|
||||
level++;
|
||||
}
|
||||
}
|
||||
entry.frameCount = frameCount;
|
||||
entry.w = width;
|
||||
entry.h = height;
|
||||
entry.Scaledw = width;
|
||||
entry.Scaledh = height;
|
||||
entry.fmt = FullFormat;
|
||||
|
||||
// dump texture to file
|
||||
if (g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
char szTemp[MAX_PATH];
|
||||
char szDir[MAX_PATH];
|
||||
|
||||
// make sure that the directory exists
|
||||
sprintf(szDir, "%s%s", File::GetUserPath(D_DUMPTEXTURES_IDX), globals->unique_id);
|
||||
if (!File::Exists(szDir) || !File::IsDirectory(szDir))
|
||||
File::CreateDir(szDir);
|
||||
|
||||
sprintf(szTemp, "%s/%s_%08x_%i.png", szDir, globals->unique_id, texHash, tex_format);
|
||||
if (!File::Exists(szTemp))
|
||||
if(FAILED(PD3DX11SaveTextureToFileA(D3D::context, entry.texture->GetTex(), D3DX11_IFF_PNG, szTemp))) PanicAlert("!!!");
|
||||
}
|
||||
|
||||
INCSTAT(stats.numTexturesCreated);
|
||||
SETSTAT(stats.numTexturesAlive, textures.size());
|
||||
|
||||
D3D::gfxstate->SetShaderResource(stage, entry.texture->GetSRV());
|
||||
|
||||
return &entry;
|
||||
}
|
||||
|
||||
void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, unsigned int bScaleByHalf, const EFBRectangle &source_rect)
|
||||
{
|
||||
int efb_w = source_rect.GetWidth();
|
||||
int efb_h = source_rect.GetHeight();
|
||||
|
||||
int tex_w = (abs(source_rect.GetWidth()) >> bScaleByHalf);
|
||||
int tex_h = (abs(source_rect.GetHeight()) >> bScaleByHalf);
|
||||
|
||||
int Scaledtex_w = (g_ActiveConfig.bCopyEFBScaled) ? ((int)(Renderer::GetTargetScaleX() * tex_w)) : tex_w;
|
||||
int Scaledtex_h = (g_ActiveConfig.bCopyEFBScaled) ? ((int)(Renderer::GetTargetScaleY() * tex_h)) : tex_h;
|
||||
|
||||
TexCache::iterator iter;
|
||||
D3DTexture2D* tex = NULL;
|
||||
iter = textures.find(address);
|
||||
if (iter != textures.end())
|
||||
{
|
||||
if (iter->second.isRenderTarget && iter->second.Scaledw == Scaledtex_w && iter->second.Scaledh == Scaledtex_h)
|
||||
{
|
||||
tex = iter->second.texture;
|
||||
iter->second.frameCount = frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove it and recreate it as a render target
|
||||
SAFE_RELEASE(iter->second.texture);
|
||||
textures.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tex)
|
||||
{
|
||||
TCacheEntry entry;
|
||||
entry.isRenderTarget = true;
|
||||
entry.hash = 0;
|
||||
entry.frameCount = frameCount;
|
||||
entry.w = entry.Realw = tex_w;
|
||||
entry.h = entry.Realh = tex_h;
|
||||
entry.Scaledw = Scaledtex_w;
|
||||
entry.Scaledh = Scaledtex_h;
|
||||
entry.fmt = copyfmt;
|
||||
entry.isNonPow2 = true;
|
||||
entry.texture = D3DTexture2D::Create(Scaledtex_w, Scaledtex_h, (D3D11_BIND_FLAG)((int)D3D11_BIND_RENDER_TARGET|(int)D3D11_BIND_SHADER_RESOURCE), D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8G8B8A8_UNORM);
|
||||
if (entry.texture == NULL) PanicAlert("CopyRenderTargetToTexture failed to create entry.texture at %s %d\n", __FILE__, __LINE__);
|
||||
textures[address] = entry;
|
||||
tex = entry.texture;
|
||||
}
|
||||
|
||||
float colmat[20]= {0.0f}; // last four floats for fConstAdd
|
||||
unsigned int cbufid = (unsigned int)-1;
|
||||
|
||||
// TODO: Move this to TextureCache::Init()
|
||||
if (bFromZBuffer)
|
||||
{
|
||||
switch(copyfmt)
|
||||
{
|
||||
case 0: // Z4
|
||||
case 1: // Z8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1.0f;
|
||||
cbufid = 12;
|
||||
break;
|
||||
case 3: // Z16 //?
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[12] = 1.0f;
|
||||
cbufid = 13;
|
||||
break;
|
||||
case 11: // Z16 (reverse order)
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1.0f;
|
||||
cbufid = 14;
|
||||
break;
|
||||
case 6: // Z24X8
|
||||
colmat[0] = colmat[5] = colmat[10] = 1.0f;
|
||||
cbufid = 15;
|
||||
break;
|
||||
case 9: // Z8M
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1.0f;
|
||||
cbufid = 16;
|
||||
break;
|
||||
case 10: // Z8L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1.0f;
|
||||
cbufid = 17;
|
||||
break;
|
||||
case 12: // Z16L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[13] = 1.0f;
|
||||
cbufid = 18;
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy zbuf format: 0x%x", copyfmt);
|
||||
colmat[2] = colmat[5] = colmat[8] = 1.0f;
|
||||
cbufid = 19;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (bIsIntensityFmt)
|
||||
{
|
||||
colmat[16] = colmat[17] = colmat[18] = 16.0f/255.0f;
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // I4
|
||||
case 1: // I8
|
||||
case 2: // IA4
|
||||
case 3: // IA8
|
||||
// TODO - verify these coefficients
|
||||
colmat[0] = 0.257f; colmat[1] = 0.504f; colmat[2] = 0.098f;
|
||||
colmat[4] = 0.257f; colmat[5] = 0.504f; colmat[6] = 0.098f;
|
||||
colmat[8] = 0.257f; colmat[9] = 0.504f; colmat[10] = 0.098f;
|
||||
|
||||
if (copyfmt < 2)
|
||||
{
|
||||
colmat[19] = 16.0f / 255.0f;
|
||||
colmat[12] = 0.257f; colmat[13] = 0.504f; colmat[14] = 0.098f;
|
||||
cbufid = 0;
|
||||
}
|
||||
else// alpha
|
||||
{
|
||||
colmat[15] = 1;
|
||||
cbufid = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy intensity format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // R4
|
||||
case 8: // R8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
cbufid = 2;
|
||||
break;
|
||||
case 2: // RA4
|
||||
case 3: // RA8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[15] = 1;
|
||||
cbufid = 3;
|
||||
break;
|
||||
|
||||
case 7: // A8
|
||||
colmat[3] = colmat[7] = colmat[11] = colmat[15] = 1;
|
||||
cbufid = 4;
|
||||
break;
|
||||
case 9: // G8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
cbufid = 5;
|
||||
break;
|
||||
case 10: // B8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
cbufid = 6;
|
||||
break;
|
||||
case 11: // RG8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
cbufid = 7;
|
||||
break;
|
||||
case 12: // GB8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
cbufid = 8;
|
||||
break;
|
||||
|
||||
case 4: // RGB565
|
||||
colmat[0] = colmat[5] = colmat[10] = 1;
|
||||
colmat[19] = 1; // set alpha to 1
|
||||
cbufid = 9;
|
||||
break;
|
||||
case 5: // RGB5A3
|
||||
case 6: // RGBA8
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
cbufid = 10;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy color format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
cbufid = 11;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Renderer::ResetAPIState(); // reset any game specific settings
|
||||
|
||||
// stretch picture with increased internal resolution
|
||||
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)Scaledtex_w, (float)Scaledtex_h);
|
||||
D3D::context->RSSetViewports(1, &vp);
|
||||
D3D11_RECT destrect = CD3D11_RECT(0, 0, Scaledtex_w, Scaledtex_h);
|
||||
|
||||
// set transformation
|
||||
if (efbcopycbuf[cbufid] == NULL)
|
||||
{
|
||||
D3D11_BUFFER_DESC cbdesc = CD3D11_BUFFER_DESC(20*sizeof(float), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DEFAULT);
|
||||
D3D11_SUBRESOURCE_DATA data;
|
||||
data.pSysMem = colmat;
|
||||
HRESULT hr = D3D::device->CreateBuffer(&cbdesc, &data, &efbcopycbuf[cbufid]);
|
||||
CHECK(hr==S_OK, "Create efb copy constant buffer %d", cbufid);
|
||||
D3D::SetDebugObjectName((ID3D11DeviceChild*)efbcopycbuf[cbufid], "a constant buffer used in TextureCache::CopyRenderTargetToTexture");
|
||||
}
|
||||
D3D::context->PSSetConstantBuffers(0, 1, &efbcopycbuf[cbufid]);
|
||||
|
||||
TargetRectangle targetSource = Renderer::ConvertEFBRectangle(source_rect);
|
||||
D3D11_RECT sourcerect = CD3D11_RECT(targetSource.left, targetSource.top, targetSource.right, targetSource.bottom);
|
||||
|
||||
// Use linear filtering if (bScaleByHalf), use point filtering otherwise
|
||||
if (bScaleByHalf) D3D::SetLinearCopySampler();
|
||||
else D3D::SetPointCopySampler();
|
||||
|
||||
D3D::stateman->PushBlendState(efbcopyblendstate);
|
||||
D3D::stateman->PushRasterizerState(efbcopyraststate);
|
||||
D3D::stateman->PushDepthState(efbcopydepthstate);
|
||||
D3D::context->OMSetRenderTargets(1, &tex->GetRTV(), NULL);
|
||||
D3D::drawShadedTexQuad(
|
||||
(bFromZBuffer) ? g_framebufferManager.GetEFBDepthTexture()->GetSRV() : g_framebufferManager.GetEFBColorTexture()->GetSRV(),
|
||||
&sourcerect,
|
||||
Renderer::GetFullTargetWidth(),
|
||||
Renderer::GetFullTargetHeight(),
|
||||
(bFromZBuffer) ? PixelShaderCache::GetDepthMatrixProgram() : PixelShaderCache::GetColorMatrixProgram(), VertexShaderCache::GetSimpleVertexShader(), VertexShaderCache::GetSimpleInputLayout());
|
||||
|
||||
D3D::context->OMSetRenderTargets(1, &g_framebufferManager.GetEFBColorTexture()->GetRTV(), g_framebufferManager.GetEFBDepthTexture()->GetDSV());
|
||||
D3D::stateman->PopBlendState();
|
||||
D3D::stateman->PopDepthState();
|
||||
D3D::stateman->PopRasterizerState();
|
||||
Renderer::RestoreAPIState();
|
||||
}
|
||||
|
|
|
@ -24,45 +24,42 @@
|
|||
#include "VideoCommon.h"
|
||||
#include "BPMemory.h"
|
||||
|
||||
class TextureCache
|
||||
#include "TextureCacheBase.h"
|
||||
|
||||
namespace DX11
|
||||
{
|
||||
|
||||
class TextureCache : public ::TextureCache
|
||||
{
|
||||
public:
|
||||
struct TCacheEntry
|
||||
{
|
||||
D3DTexture2D* texture;
|
||||
|
||||
u32 addr;
|
||||
u32 size_in_bytes;
|
||||
u64 hash;
|
||||
u32 paletteHash;
|
||||
u32 oldpixel;
|
||||
|
||||
int frameCount;
|
||||
unsigned int w, h, fmt, MipLevels;
|
||||
int Realw, Realh, Scaledw, Scaledh;
|
||||
|
||||
bool isRenderTarget;
|
||||
bool isNonPow2;
|
||||
|
||||
TCacheEntry() : texture(NULL), addr(0), size_in_bytes(0), hash(0), paletteHash(0), oldpixel(0),
|
||||
frameCount(0), w(0), h(0), fmt(0), MipLevels(0), Realw(0), Realh(0), Scaledw(0), Scaledh(0),
|
||||
isRenderTarget(false), isNonPow2(true) {}
|
||||
|
||||
void Destroy(bool shutdown);
|
||||
bool IntersectsMemoryRange(u32 range_address, u32 range_size);
|
||||
};
|
||||
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
static void Shutdown();
|
||||
static void Invalidate(bool shutdown);
|
||||
static void InvalidateRange(u32 start_address, u32 size);
|
||||
static TCacheEntry* TextureCache::Load(unsigned int stage, u32 address, unsigned int width, unsigned int height, unsigned int tex_format, unsigned int tlutaddr, unsigned int tlutfmt, bool UseNativeMips, unsigned int maxlevel);
|
||||
static void CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, unsigned int bScaleByHalf, const EFBRectangle &source_rect);
|
||||
TextureCache();
|
||||
~TextureCache();
|
||||
|
||||
private:
|
||||
typedef std::map<u32,TCacheEntry> TexCache;
|
||||
struct TCacheEntry : TCacheEntryBase
|
||||
{
|
||||
D3DTexture2D *const texture;
|
||||
|
||||
static u8* temp;
|
||||
static TexCache textures;
|
||||
D3D11_USAGE usage;
|
||||
|
||||
TCacheEntry(D3DTexture2D *_tex) : texture(_tex) {}
|
||||
~TCacheEntry();
|
||||
|
||||
void Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int levels);
|
||||
|
||||
void FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float* colmat, const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt);
|
||||
|
||||
void Bind(unsigned int stage);
|
||||
bool Save(const char filename[]);
|
||||
};
|
||||
|
||||
TCacheEntryBase* CreateTexture(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt);
|
||||
|
||||
TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ void VertexManager::vFlush()
|
|||
{
|
||||
Renderer::SetSamplerState(i & 3, i >> 2);
|
||||
FourTexUnits &tex = bpmem.tex[i >> 2];
|
||||
TextureCache::TCacheEntry* tentry = TextureCache::Load(i,
|
||||
TextureCache::TCacheEntryBase* tentry = TextureCache::Load(i,
|
||||
(tex.texImage3[i&3].image_base/* & 0x1FFFFF*/) << 5,
|
||||
tex.texImage0[i&3].width + 1, tex.texImage0[i&3].height + 1,
|
||||
tex.texImage0[i&3].format, tex.texTlut[i&3].tmem_offset<<9,
|
||||
|
@ -225,7 +225,7 @@ void VertexManager::vFlush()
|
|||
if (tentry)
|
||||
{
|
||||
// 0s are probably for no manual wrapping needed.
|
||||
PixelShaderManager::SetTexDims(i, tentry->Realw, tentry->Realh, 0, 0);
|
||||
PixelShaderManager::SetTexDims(i, tentry->nativeW, tentry->nativeH, 0, 0);
|
||||
}
|
||||
else
|
||||
ERROR_LOG(VIDEO, "error loading texture");
|
||||
|
|
|
@ -215,7 +215,7 @@ void Video_Prepare()
|
|||
|
||||
// internal interfaces
|
||||
Renderer::Init();
|
||||
TextureCache::Init();
|
||||
g_texture_cache = new DX11::TextureCache;
|
||||
g_vertex_manager = new DX11::VertexManager;
|
||||
VertexShaderCache::Init();
|
||||
PixelShaderCache::Init();
|
||||
|
@ -258,7 +258,7 @@ void Shutdown()
|
|||
PixelShaderCache::Shutdown();
|
||||
VertexShaderCache::Shutdown();
|
||||
delete g_vertex_manager;
|
||||
TextureCache::Shutdown();
|
||||
delete g_texture_cache;
|
||||
Renderer::Shutdown();
|
||||
EmuWindow::Close();
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ void CopyEFB(const BPCmd &bp, const EFBRectangle &rc, const u32 &address, const
|
|||
// bpmem.zcontrol.pixel_format to PIXELFMT_Z24 is when the game wants to copy from ZBuffer (Zbuffer uses 24-bit Format)
|
||||
if (!g_ActiveConfig.bEFBCopyDisable)
|
||||
{
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, scaleByHalf, rc);
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, !!scaleByHalf, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,570 +43,44 @@
|
|||
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
u8 *TextureCache::temp = NULL;
|
||||
TextureCache::TexCache TextureCache::textures;
|
||||
|
||||
extern int frameCount;
|
||||
|
||||
#define TEMP_SIZE (2048*2048*4)
|
||||
#define TEXTURE_KILL_THRESHOLD 200
|
||||
|
||||
void TextureCache::TCacheEntry::Destroy(bool shutdown)
|
||||
namespace DX9
|
||||
{
|
||||
if (texture)
|
||||
texture->Release();
|
||||
texture = 0;
|
||||
if (!isRenderTarget && !shutdown && !g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
u32 *ptr = (u32*)g_VideoInitialize.pGetMemoryPointer(addr);
|
||||
if (ptr && *ptr == hash)
|
||||
*ptr = oldpixel;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntry::~TCacheEntry()
|
||||
{
|
||||
texture->Release();
|
||||
}
|
||||
|
||||
void TextureCache::Init()
|
||||
void TextureCache::TCacheEntry::Bind(unsigned int stage)
|
||||
{
|
||||
temp = (u8*)AllocateMemoryPages(TEMP_SIZE);
|
||||
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter);
|
||||
HiresTextures::Init(globals->unique_id);
|
||||
D3D::SetTexture(stage, texture);
|
||||
}
|
||||
|
||||
void TextureCache::Invalidate(bool shutdown)
|
||||
bool TextureCache::TCacheEntry::Save(const char filename[])
|
||||
{
|
||||
for (TexCache::iterator iter = textures.begin(); iter != textures.end(); ++iter)
|
||||
iter->second.Destroy(shutdown);
|
||||
textures.clear();
|
||||
HiresTextures::Shutdown();
|
||||
return SUCCEEDED(PD3DXSaveTextureToFileA(filename, D3DXIFF_PNG, texture, 0));
|
||||
}
|
||||
|
||||
void TextureCache::Shutdown()
|
||||
void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int level)
|
||||
{
|
||||
Invalidate(true);
|
||||
FreeMemoryPages(temp, TEMP_SIZE);
|
||||
temp = NULL;
|
||||
D3D::ReplaceTexture2D(texture, temp, width, height, expanded_width, d3d_fmt, swap_r_b, level);
|
||||
}
|
||||
|
||||
void TextureCache::Cleanup()
|
||||
void TextureCache::TCacheEntry::FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float *colmat, const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
const LPDIRECT3DTEXTURE9 read_texture = bFromZBuffer ?
|
||||
g_framebufferManager.GetEFBDepthTexture(source_rect) :
|
||||
g_framebufferManager.GetEFBColorTexture(source_rect);
|
||||
|
||||
if (!isDynamic || g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second.frameCount)
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
iter = textures.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
int rangePosition = iter->second.IntersectsMemoryRange(start_address, size);
|
||||
if (rangePosition == 0)
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
int rangePosition = iter->second.IntersectsMemoryRange(start_address, size);
|
||||
if ( rangePosition == 0)
|
||||
{
|
||||
iter->second.hash = 0;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
int TextureCache::TCacheEntry::IntersectsMemoryRange(u32 range_address, u32 range_size)
|
||||
{
|
||||
if (addr + size_in_bytes < range_address)
|
||||
return -1;
|
||||
if (addr >= range_address + range_size)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntry *TextureCache::Load(int stage, u32 address, int width, int height, int tex_format, int tlutaddr, int tlutfmt,bool UseNativeMips, int maxlevel)
|
||||
{
|
||||
// notes (about "UNsafe texture cache"):
|
||||
// Have to be removed soon.
|
||||
// But we keep it until the "safe" way became rock solid
|
||||
// pros: it has an unique ID held by the texture data itself (@address) once cached.
|
||||
// cons: it writes this unique ID in the gc RAM <- very dangerous (break MP1) and ugly
|
||||
|
||||
// notes (about "safe texture cache"):
|
||||
// Metroids text issue (character table):
|
||||
// Same addr, same GX_TF_C4 texture data but different TLUT (hence different outputs).
|
||||
// That's why we have to hash the TLUT too for TLUT tex_format dependent textures (ie. GX_TF_C4, GX_TF_C8, GX_TF_C14X2).
|
||||
// And since the address and tex data don't change, the key index in the cacheEntry map can't be the address but
|
||||
// have to be a real unique ID.
|
||||
// DONE but not satifiying yet -> may break copyEFBToTexture sometimes.
|
||||
|
||||
// Pokemon Colosseum text issue (plain text):
|
||||
// Use a GX_TF_I4 512x512 text-flush-texture at a const address.
|
||||
// The problem here was just the sparse hash on the texture. This texture is partly overwrited (what is needed only)
|
||||
// so lot's of remaning old text. Thin white chars on black bg too.
|
||||
|
||||
// TODO: - clean this up when ready to kill old "unsafe texture cache"
|
||||
// - fix the key index situation with CopyRenderTargetToTexture.
|
||||
// Could happen only for GX_TF_C4, GX_TF_C8 and GX_TF_C14X2 fmt.
|
||||
// Wonder if we can't use tex width&height to know if EFB might be copied to it...
|
||||
// raw idea: TOCHECK if addresses are aligned we have few bits left...
|
||||
|
||||
if (address == 0)
|
||||
return NULL;
|
||||
|
||||
u8 *ptr = g_VideoInitialize.pGetMemoryPointer(address);
|
||||
int bsw = TexDecoder_GetBlockWidthInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
int bsh = TexDecoder_GetBlockHeightInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
int bsdepth = TexDecoder_GetTexelSizeInNibbles(tex_format);
|
||||
int expandedWidth = (width + bsw) & (~bsw);
|
||||
int expandedHeight = (height + bsh) & (~bsh);
|
||||
|
||||
u64 hash_value = 0;
|
||||
u32 texID = address;
|
||||
u64 texHash = 0;
|
||||
u32 FullFormat = tex_format;
|
||||
bool TextureisDynamic = false;
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
FullFormat = (tex_format | (tlutfmt << 16));
|
||||
|
||||
if (g_ActiveConfig.bSafeTextureCache || g_ActiveConfig.bHiresTextures || g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
texHash = GetHash64(ptr,TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
{
|
||||
// WARNING! texID != address now => may break CopyRenderTargetToTexture (cf. TODO up)
|
||||
// tlut size can be up to 32768B (GX_TF_C14X2) but Safer == Slower.
|
||||
// This trick (to change the texID depending on the TLUT addr) is a trick to get around
|
||||
// an issue with metroid prime's fonts, where it has multiple sets of fonts on top of
|
||||
// each other stored in a single texture, and uses the palette to make different characters
|
||||
// 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.
|
||||
u64 tlutHash = GetHash64(&texMem[tlutaddr], TexDecoder_GetPaletteSize(tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
texHash ^= tlutHash;
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
texID = texID ^ ((u32)(tlutHash & 0xFFFFFFFF)) ^ ((u32)((tlutHash >> 32) & 0xFFFFFFFF));
|
||||
}
|
||||
}
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
hash_value = texHash;
|
||||
}
|
||||
|
||||
bool skip_texture_create = false;
|
||||
TexCache::iterator iter = textures.find(texID);
|
||||
|
||||
if (iter != textures.end())
|
||||
{
|
||||
TCacheEntry &entry = iter->second;
|
||||
|
||||
if (!g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
if(entry.isRenderTarget || entry.isDynamic)
|
||||
{
|
||||
if(!g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
hash_value = GetHash64(ptr,TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
{
|
||||
hash_value ^= GetHash64(&texMem[tlutaddr], TexDecoder_GetPaletteSize(tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hash_value = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hash_value = ((u32 *)ptr)[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(entry.isRenderTarget || entry.isDynamic)
|
||||
{
|
||||
if(g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
hash_value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (((entry.isRenderTarget || entry.isDynamic) && hash_value == entry.hash && address == entry.addr)
|
||||
|| ((address == entry.addr) && (hash_value == entry.hash) && FullFormat == entry.fmt/* && entry.MipLevels == maxlevel*/))
|
||||
{
|
||||
entry.frameCount = frameCount;
|
||||
entry.isDynamic = false;
|
||||
D3D::SetTexture(stage, entry.texture);
|
||||
return &entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's reload the new texture data into the same texture,
|
||||
// instead of destroying it and having to create a new one.
|
||||
// Might speed up movie playback very, very slightly.
|
||||
TextureisDynamic = (entry.isRenderTarget || entry.isDynamic) && !g_ActiveConfig.bCopyEFBToTexture;
|
||||
|
||||
if (!entry.isRenderTarget &&
|
||||
((!entry.isDynamic && width == entry.w && height==entry.h && FullFormat == entry.fmt /* && entry.MipLevels < maxlevel*/)
|
||||
|| (entry.isDynamic && entry.w == width && entry.h == height)))
|
||||
{
|
||||
skip_texture_create = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Destroy(false);
|
||||
textures.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make an entry in the table
|
||||
TCacheEntry& entry = textures[texID];
|
||||
entry.isDynamic = TextureisDynamic;
|
||||
entry.Realw = width;
|
||||
entry.Realh = height;
|
||||
PC_TexFormat pcfmt = PC_TEX_FMT_NONE;
|
||||
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
// Load Custom textures
|
||||
char texPathTemp[MAX_PATH];
|
||||
int newWidth = width;
|
||||
int newHeight = height;
|
||||
|
||||
sprintf(texPathTemp, "%s_%08x_%i", globals->unique_id, texHash, tex_format);
|
||||
pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, tex_format, temp);
|
||||
|
||||
if (pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
expandedWidth = width = newWidth;
|
||||
expandedHeight = height = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcfmt == PC_TEX_FMT_NONE)
|
||||
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt);
|
||||
|
||||
entry.oldpixel = ((u32 *)ptr)[0];
|
||||
if (g_ActiveConfig.bSafeTextureCache || entry.isDynamic)
|
||||
entry.hash = hash_value;
|
||||
else
|
||||
{
|
||||
entry.hash = (u32)(((double)rand() / RAND_MAX) * 0xFFFFFFFF);
|
||||
((u32 *)ptr)[0] = entry.hash;
|
||||
}
|
||||
|
||||
entry.addr = address;
|
||||
entry.size_in_bytes = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format);
|
||||
entry.isRenderTarget = false;
|
||||
bool isPow2 = !((width & (width - 1)) || (height & (height - 1)));
|
||||
entry.isNonPow2 = false;
|
||||
int TexLevels = (width > height)?width:height;
|
||||
TexLevels = (isPow2 && UseNativeMips && (maxlevel > 0)) ? (int)(log((double)TexLevels)/log((double)2)) + 1 : (isPow2? 0 : 1);
|
||||
if(TexLevels > (maxlevel + 1) && maxlevel > 0)
|
||||
TexLevels = (maxlevel + 1);
|
||||
entry.MipLevels = maxlevel;
|
||||
|
||||
D3DFORMAT d3d_fmt;
|
||||
bool swap_r_b = false;
|
||||
|
||||
switch (pcfmt)
|
||||
{
|
||||
case PC_TEX_FMT_BGRA32:
|
||||
d3d_fmt = D3DFMT_A8R8G8B8;
|
||||
break;
|
||||
case PC_TEX_FMT_RGBA32:
|
||||
d3d_fmt = D3DFMT_A8R8G8B8;
|
||||
swap_r_b = true;
|
||||
break;
|
||||
case PC_TEX_FMT_RGB565:
|
||||
d3d_fmt = D3DFMT_R5G6B5;
|
||||
break;
|
||||
case PC_TEX_FMT_IA4_AS_IA8:
|
||||
d3d_fmt = D3DFMT_A8L8;
|
||||
break;
|
||||
case PC_TEX_FMT_I8:
|
||||
case PC_TEX_FMT_I4_AS_I8:
|
||||
// A hack which means the format is a packed
|
||||
// 8-bit intensity texture. It is unpacked
|
||||
// to A8L8 in D3DTexture.cpp
|
||||
d3d_fmt = D3DFMT_A8P8;
|
||||
break;
|
||||
case PC_TEX_FMT_IA8:
|
||||
d3d_fmt = D3DFMT_A8L8;
|
||||
break;
|
||||
case PC_TEX_FMT_DXT1:
|
||||
d3d_fmt = D3DFMT_DXT1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!skip_texture_create)
|
||||
{
|
||||
entry.texture = D3D::CreateTexture2D((BYTE*)temp, width, height, expandedWidth, d3d_fmt, swap_r_b, TexLevels);
|
||||
}
|
||||
else
|
||||
{
|
||||
D3D::ReplaceTexture2D(entry.texture, (BYTE*)temp, width, height, expandedWidth, d3d_fmt, swap_r_b, 0);
|
||||
}
|
||||
if(TexLevels > 1 && pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
int level = 1;
|
||||
int mipWidth = (width + 1) >> 1;
|
||||
int mipHeight = (height + 1) >> 1;
|
||||
ptr += entry.size_in_bytes;
|
||||
while((mipHeight || mipWidth) && (level < TexLevels))
|
||||
{
|
||||
u32 currentWidth = (mipWidth > 0)? mipWidth : 1;
|
||||
u32 currentHeight = (mipHeight > 0)? mipHeight : 1;
|
||||
expandedWidth = (currentWidth + bsw) & (~bsw);
|
||||
expandedHeight = (currentHeight + bsh) & (~bsh);
|
||||
TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt);
|
||||
D3D::ReplaceTexture2D(entry.texture, (BYTE*)temp, currentWidth, currentHeight, expandedWidth, d3d_fmt, swap_r_b,level);
|
||||
u32 size = (max(mipWidth, bsw) * max(mipHeight, bsh) * bsdepth) >> 1;
|
||||
ptr += size;
|
||||
mipWidth >>= 1;
|
||||
mipHeight >>= 1;
|
||||
level++;
|
||||
}
|
||||
}
|
||||
entry.frameCount = frameCount;
|
||||
entry.w = width;
|
||||
entry.h = height;
|
||||
entry.Scaledw = width;
|
||||
entry.Scaledh = height;
|
||||
entry.fmt = FullFormat;
|
||||
|
||||
if (g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
// dump texture to file
|
||||
char szTemp[MAX_PATH];
|
||||
char szDir[MAX_PATH];
|
||||
const char* uniqueId = globals->unique_id;
|
||||
static bool bCheckedDumpDir = false;
|
||||
|
||||
sprintf(szDir, "%s%s", File::GetUserPath(D_DUMPTEXTURES_IDX), uniqueId);
|
||||
|
||||
if (!bCheckedDumpDir)
|
||||
{
|
||||
if (!File::Exists(szDir) || !File::IsDirectory(szDir))
|
||||
File::CreateDir(szDir);
|
||||
|
||||
bCheckedDumpDir = true;
|
||||
}
|
||||
|
||||
sprintf(szTemp, "%s/%s_%08x_%i.png", szDir, uniqueId, texHash, tex_format);
|
||||
|
||||
if (!File::Exists(szTemp))
|
||||
PD3DXSaveTextureToFileA(szTemp,D3DXIFF_PNG,entry.texture,0);
|
||||
}
|
||||
|
||||
INCSTAT(stats.numTexturesCreated);
|
||||
SETSTAT(stats.numTexturesAlive, textures.size());
|
||||
|
||||
//Set the texture!
|
||||
D3D::SetTexture(stage, entry.texture);
|
||||
|
||||
DEBUGGER_PAUSE_LOG_AT(NEXT_NEW_TEXTURE,true,{printf("A new texture (%d x %d) is loaded", width, height);});
|
||||
return &entry;
|
||||
}
|
||||
|
||||
void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle &source_rect)
|
||||
{
|
||||
int tex_w = (abs(source_rect.GetWidth()) >> bScaleByHalf);
|
||||
int tex_h = (abs(source_rect.GetHeight()) >> bScaleByHalf);
|
||||
//compensate the texture grow if supersampling is enabled to conserve memory usage
|
||||
float SuperSampleCompensation = g_ActiveConfig.iMultisampleMode + 1;
|
||||
SuperSampleCompensation = 1.0f / SuperSampleCompensation;
|
||||
float xScale = Renderer::GetTargetScaleX();
|
||||
float yScale = Renderer::GetTargetScaleY();
|
||||
|
||||
int Scaledtex_w = (g_ActiveConfig.bCopyEFBScaled)?((int)(xScale * SuperSampleCompensation * tex_w)):tex_w;
|
||||
int Scaledtex_h = (g_ActiveConfig.bCopyEFBScaled)?((int)(yScale * SuperSampleCompensation * tex_h)):tex_h;
|
||||
|
||||
TexCache::iterator iter;
|
||||
LPDIRECT3DTEXTURE9 tex = NULL;
|
||||
iter = textures.find(address);
|
||||
bool TextureisDynamic = false;
|
||||
if (iter != textures.end())
|
||||
{
|
||||
if ((iter->second.isRenderTarget && iter->second.Scaledw == Scaledtex_w && iter->second.Scaledh == Scaledtex_h)
|
||||
|| (iter->second.isDynamic && iter->second.w == tex_w && iter->second.h == tex_h))
|
||||
{
|
||||
tex = iter->second.texture;
|
||||
TextureisDynamic = iter->second.isDynamic;
|
||||
iter->second.frameCount = frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove it and recreate it as a render target
|
||||
if(iter->second.texture)
|
||||
iter->second.texture->Release();
|
||||
iter->second.texture = 0;
|
||||
textures.erase(iter);
|
||||
}
|
||||
}
|
||||
if (TextureisDynamic)
|
||||
{
|
||||
Scaledtex_w = tex_w;
|
||||
Scaledtex_h = tex_h;
|
||||
}
|
||||
if (!tex)
|
||||
{
|
||||
TCacheEntry entry;
|
||||
entry.addr = address;
|
||||
entry.isRenderTarget = true;
|
||||
entry.hash = 0;
|
||||
entry.frameCount = frameCount;
|
||||
entry.w = entry.Realw = tex_w;
|
||||
entry.h = entry.Realh = tex_h;
|
||||
entry.Scaledw = Scaledtex_w;
|
||||
entry.Scaledh = Scaledtex_h;
|
||||
entry.fmt = copyfmt;
|
||||
entry.isNonPow2 = true;
|
||||
entry.isDynamic = false;
|
||||
D3D::dev->CreateTexture(Scaledtex_w, Scaledtex_h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &entry.texture, 0);
|
||||
textures[address] = entry;
|
||||
tex = entry.texture;
|
||||
}
|
||||
|
||||
// Make sure to resolve anything we need to read from.
|
||||
LPDIRECT3DTEXTURE9 read_texture = bFromZBuffer ? g_framebufferManager.GetEFBDepthTexture(source_rect) : g_framebufferManager.GetEFBColorTexture(source_rect);
|
||||
|
||||
// We have to run a pixel shader, for color conversion.
|
||||
Renderer::ResetAPIState(); // reset any game specific settings
|
||||
if(!TextureisDynamic || g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
|
||||
float colmat[16]= {0.0f};
|
||||
float fConstAdd[4] = {0.0f};
|
||||
|
||||
if (bFromZBuffer)
|
||||
{
|
||||
switch(copyfmt)
|
||||
{
|
||||
case 0: // Z4
|
||||
case 1: // Z8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1.0f;
|
||||
break;
|
||||
case 3: // Z16 //?
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[12] = 1.0f;
|
||||
break;
|
||||
case 11: // Z16 (reverse order)
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1.0f;
|
||||
break;
|
||||
case 6: // Z24X8
|
||||
colmat[0] = colmat[5] = colmat[10] = 1.0f;
|
||||
break;
|
||||
case 9: // Z8M
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1.0f;
|
||||
break;
|
||||
case 10: // Z8L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1.0f;
|
||||
break;
|
||||
case 12: // Z16L
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[13] = 1.0f;
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy zbuf format: 0x%x", copyfmt);
|
||||
colmat[2] = colmat[5] = colmat[8] = 1.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (bIsIntensityFmt)
|
||||
{
|
||||
fConstAdd[0] = fConstAdd[1] = fConstAdd[2] = 16.0f/255.0f;
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // I4
|
||||
case 1: // I8
|
||||
case 2: // IA4
|
||||
case 3: // IA8
|
||||
colmat[0] = 0.257f; colmat[1] = 0.504f; colmat[2] = 0.098f;
|
||||
colmat[4] = 0.257f; colmat[5] = 0.504f; colmat[6] = 0.098f;
|
||||
colmat[8] = 0.257f; colmat[9] = 0.504f; colmat[10] = 0.098f;
|
||||
|
||||
if (copyfmt < 2)
|
||||
{
|
||||
fConstAdd[3] = 16.0f / 255.0f;
|
||||
colmat[12] = 0.257f; colmat[13] = 0.504f; colmat[14] = 0.098f;
|
||||
}
|
||||
else// alpha
|
||||
colmat[15] = 1;
|
||||
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy intensity format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // R4
|
||||
case 8: // R8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
break;
|
||||
case 2: // RA4
|
||||
case 3: // RA8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[15] = 1;
|
||||
break;
|
||||
|
||||
case 7: // A8
|
||||
colmat[3] = colmat[7] = colmat[11] = colmat[15] = 1;
|
||||
break;
|
||||
case 9: // G8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
break;
|
||||
case 10: // B8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
break;
|
||||
case 11: // RG8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
break;
|
||||
case 12: // GB8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
break;
|
||||
|
||||
case 4: // RGB565
|
||||
colmat[0] = colmat[5] = colmat[10] = 1;
|
||||
fConstAdd[3] = 1; // set alpha to 1
|
||||
break;
|
||||
case 5: // RGB5A3
|
||||
case 6: // RGBA8
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy color format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LPDIRECT3DSURFACE9 Rendersurf = NULL;
|
||||
tex->GetSurfaceLevel(0,&Rendersurf);
|
||||
texture->GetSurfaceLevel(0, &Rendersurf);
|
||||
D3D::dev->SetDepthStencilSurface(NULL);
|
||||
D3D::dev->SetRenderTarget(0, Rendersurf);
|
||||
|
||||
|
@ -615,18 +89,18 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
// Stretch picture with increased internal resolution
|
||||
vp.X = 0;
|
||||
vp.Y = 0;
|
||||
vp.Width = Scaledtex_w;
|
||||
vp.Height = Scaledtex_h;
|
||||
vp.Width = scaledW;
|
||||
vp.Height = scaledH;
|
||||
vp.MinZ = 0.0f;
|
||||
vp.MaxZ = 1.0f;
|
||||
D3D::dev->SetViewport(&vp);
|
||||
RECT destrect;
|
||||
destrect.bottom = Scaledtex_h;
|
||||
destrect.bottom = scaledH;
|
||||
destrect.left = 0;
|
||||
destrect.right = Scaledtex_w;
|
||||
destrect.right = scaledW;
|
||||
destrect.top = 0;
|
||||
|
||||
|
||||
const float* const fConstAdd = colmat + 16; // fConstAdd is the last 4 floats of colmat
|
||||
PixelShaderManager::SetColorMatrix(colmat, fConstAdd); // set transformation
|
||||
TargetRectangle targetSource = Renderer::ConvertEFBRectangle(source_rect);
|
||||
RECT sourcerect;
|
||||
|
@ -635,10 +109,9 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
sourcerect.right = targetSource.right;
|
||||
sourcerect.top = targetSource.top;
|
||||
|
||||
|
||||
if(bFromZBuffer)
|
||||
if (bFromZBuffer)
|
||||
{
|
||||
if(bScaleByHalf || g_ActiveConfig.iMultisampleMode)
|
||||
if (bScaleByHalf || g_ActiveConfig.iMultisampleMode)
|
||||
{
|
||||
D3D::ChangeSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
D3D::ChangeSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
|
@ -654,31 +127,30 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
D3D::ChangeSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
D3D::ChangeSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
|
||||
|
||||
D3DFORMAT bformat = g_framebufferManager.GetEFBDepthRTSurfaceFormat();
|
||||
int SSAAMode = g_ActiveConfig.iMultisampleMode;
|
||||
D3D::drawShadedTexQuad(
|
||||
read_texture,
|
||||
&sourcerect,
|
||||
Renderer::GetFullTargetWidth() ,
|
||||
Renderer::GetFullTargetHeight(),
|
||||
Scaledtex_w,
|
||||
Scaledtex_h,
|
||||
((bformat != FOURCC_RAWZ && bformat != D3DFMT_D24X8) && bFromZBuffer)? PixelShaderCache::GetDepthMatrixProgram(SSAAMode): PixelShaderCache::GetColorMatrixProgram(SSAAMode),
|
||||
|
||||
D3D::drawShadedTexQuad(read_texture, &sourcerect,
|
||||
Renderer::GetFullTargetWidth(), Renderer::GetFullTargetHeight(),
|
||||
scaledW, scaledH,
|
||||
((bformat != FOURCC_RAWZ && bformat != D3DFMT_D24X8) && bFromZBuffer) ?
|
||||
PixelShaderCache::GetDepthMatrixProgram(SSAAMode) :
|
||||
PixelShaderCache::GetColorMatrixProgram(SSAAMode),
|
||||
VertexShaderCache::GetSimpleVertexShader(SSAAMode));
|
||||
Rendersurf->Release();
|
||||
|
||||
Rendersurf->Release();
|
||||
}
|
||||
|
||||
if(!g_ActiveConfig.bCopyEFBToTexture)
|
||||
|
||||
if (!g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
textures[address].hash = TextureConverter::EncodeToRamFromTexture(
|
||||
address,
|
||||
hash = TextureConverter::EncodeToRamFromTexture(
|
||||
addr,
|
||||
read_texture,
|
||||
Renderer::GetFullTargetWidth(),
|
||||
Renderer::GetFullTargetHeight(),
|
||||
xScale,
|
||||
yScale,
|
||||
Renderer::GetTargetScaleX(),
|
||||
Renderer::GetTargetScaleY(),
|
||||
(float)((Renderer::GetFullTargetWidth() - Renderer::GetTargetWidth()) / 2),
|
||||
(float)((Renderer::GetFullTargetHeight() - Renderer::GetTargetHeight()) / 2) ,
|
||||
bFromZBuffer,
|
||||
|
@ -690,8 +162,68 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
|
||||
D3D::RefreshSamplerState(0, D3DSAMP_MINFILTER);
|
||||
D3D::RefreshSamplerState(0, D3DSAMP_MAGFILTER);
|
||||
D3D::SetTexture(0,NULL);
|
||||
D3D::SetTexture(0, NULL);
|
||||
D3D::dev->SetRenderTarget(0, g_framebufferManager.GetEFBColorRTSurface());
|
||||
D3D::dev->SetDepthStencilSurface(g_framebufferManager.GetEFBDepthRTSurface());
|
||||
Renderer::RestoreAPIState();
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt)
|
||||
{
|
||||
D3DFORMAT d3d_fmt;
|
||||
bool swap_r_b = false;
|
||||
|
||||
switch (pcfmt)
|
||||
{
|
||||
case PC_TEX_FMT_BGRA32:
|
||||
d3d_fmt = D3DFMT_A8R8G8B8;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_RGBA32:
|
||||
d3d_fmt = D3DFMT_A8R8G8B8;
|
||||
swap_r_b = true;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_RGB565:
|
||||
d3d_fmt = D3DFMT_R5G6B5;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_IA4_AS_IA8:
|
||||
d3d_fmt = D3DFMT_A8L8;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_I8:
|
||||
case PC_TEX_FMT_I4_AS_I8:
|
||||
// A hack which means the format is a packed
|
||||
// 8-bit intensity texture. It is unpacked
|
||||
// to A8L8 in D3DTexture.cpp
|
||||
d3d_fmt = D3DFMT_A8P8;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_IA8:
|
||||
d3d_fmt = D3DFMT_A8L8;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_DXT1:
|
||||
d3d_fmt = D3DFMT_DXT1;
|
||||
break;
|
||||
}
|
||||
|
||||
TCacheEntry* entry = new TCacheEntry(D3D::CreateTexture2D(temp, width, height, expanded_width, d3d_fmt, swap_r_b, tex_levels));
|
||||
entry->swap_r_b = swap_r_b;
|
||||
entry->d3d_fmt = d3d_fmt;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateRenderTargetTexture(
|
||||
unsigned int scaled_tex_w, unsigned int scaled_tex_h)
|
||||
{
|
||||
LPDIRECT3DTEXTURE9 texture;
|
||||
D3D::dev->CreateTexture(scaled_tex_w, scaled_tex_h, 1, D3DUSAGE_RENDERTARGET,
|
||||
D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &texture, 0);
|
||||
|
||||
return new TCacheEntry(texture);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,64 +25,41 @@
|
|||
#include "VideoCommon.h"
|
||||
#include "BPMemory.h"
|
||||
|
||||
class TextureCache
|
||||
#include "TextureCacheBase.h"
|
||||
|
||||
namespace DX9
|
||||
{
|
||||
public:
|
||||
struct TCacheEntry
|
||||
|
||||
class TextureCache : public ::TextureCache
|
||||
{
|
||||
private:
|
||||
struct TCacheEntry : TCacheEntryBase
|
||||
{
|
||||
LPDIRECT3DTEXTURE9 texture;
|
||||
const LPDIRECT3DTEXTURE9 texture;
|
||||
|
||||
u32 addr;
|
||||
u32 size_in_bytes;
|
||||
u64 hash;
|
||||
u32 paletteHash;
|
||||
u32 oldpixel;
|
||||
|
||||
int frameCount;
|
||||
int w, h, fmt,MipLevels, Realw, Realh, Scaledw, Scaledh;
|
||||
D3DFORMAT d3d_fmt;
|
||||
bool swap_r_b;
|
||||
|
||||
bool isRenderTarget;
|
||||
bool isDynamic;// mofified from cpu
|
||||
bool isNonPow2;
|
||||
TCacheEntry(LPDIRECT3DTEXTURE9 _tex) : texture(_tex) {}
|
||||
~TCacheEntry();
|
||||
|
||||
TCacheEntry()
|
||||
{
|
||||
texture = 0;
|
||||
isRenderTarget = 0;
|
||||
hash = 0;
|
||||
paletteHash = 0;
|
||||
oldpixel = 0;
|
||||
addr = 0;
|
||||
size_in_bytes = 0;
|
||||
frameCount = 0;
|
||||
isNonPow2 = true;
|
||||
w = 0;
|
||||
h = 0;
|
||||
Realw = 0;
|
||||
Realh = 0;
|
||||
Scaledw = 0;
|
||||
Scaledh = 0;
|
||||
}
|
||||
void Destroy(bool shutdown);
|
||||
int IntersectsMemoryRange(u32 range_address, u32 range_size);
|
||||
void Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int levels);
|
||||
|
||||
void FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float* colmat, const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt);
|
||||
|
||||
void Bind(unsigned int stage);
|
||||
bool Save(const char filename[]);
|
||||
};
|
||||
|
||||
private:
|
||||
TCacheEntryBase* CreateTexture(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt);
|
||||
|
||||
typedef std::map<u32,TCacheEntry> TexCache;
|
||||
|
||||
static u8 *temp;
|
||||
static TexCache textures;
|
||||
|
||||
public:
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
static void Shutdown();
|
||||
static void Invalidate(bool shutdown);
|
||||
static void InvalidateRange(u32 start_address, u32 size);
|
||||
static void MakeRangeDynamic(u32 start_address, u32 size);
|
||||
static TCacheEntry *Load(int stage, u32 address, int width, int height, int format, int tlutaddr, int tlutfmt,bool UseNativeMips, int maxlevel);
|
||||
static void CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle &source_rect);
|
||||
TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -133,7 +133,7 @@ void VertexManager::vFlush()
|
|||
{
|
||||
Renderer::SetSamplerState(i & 3, i >> 2);
|
||||
FourTexUnits &tex = bpmem.tex[i >> 2];
|
||||
TextureCache::TCacheEntry* tentry = TextureCache::Load(i,
|
||||
TextureCache::TCacheEntryBase* tentry = TextureCache::Load(i,
|
||||
(tex.texImage3[i&3].image_base/* & 0x1FFFFF*/) << 5,
|
||||
tex.texImage0[i&3].width + 1, tex.texImage0[i&3].height + 1,
|
||||
tex.texImage0[i&3].format, tex.texTlut[i&3].tmem_offset<<9,
|
||||
|
@ -144,7 +144,7 @@ void VertexManager::vFlush()
|
|||
if (tentry)
|
||||
{
|
||||
// 0s are probably for no manual wrapping needed.
|
||||
PixelShaderManager::SetTexDims(i, tentry->Realw, tentry->Realh, 0, 0);
|
||||
PixelShaderManager::SetTexDims(i, tentry->nativeW, tentry->nativeH, 0, 0);
|
||||
}
|
||||
else
|
||||
ERROR_LOG(VIDEO, "error loading texture");
|
||||
|
|
|
@ -238,7 +238,7 @@ void Video_Prepare()
|
|||
|
||||
// internal interfaces
|
||||
Renderer::Init();
|
||||
TextureCache::Init();
|
||||
g_texture_cache = new DX9::TextureCache;
|
||||
g_vertex_manager = new DX9::VertexManager;
|
||||
// VideoCommon
|
||||
BPInit();
|
||||
|
@ -276,7 +276,7 @@ void Shutdown()
|
|||
PixelShaderCache::Shutdown();
|
||||
VertexShaderCache::Shutdown();
|
||||
delete g_vertex_manager;
|
||||
TextureCache::Shutdown();
|
||||
delete g_texture_cache;
|
||||
Renderer::Shutdown();
|
||||
D3D::Shutdown();
|
||||
EmuWindow::Close();
|
||||
|
|
|
@ -83,7 +83,7 @@ void CopyEFB(const BPCmd &bp, const EFBRectangle &rc, const u32 &address, const
|
|||
// bpmem.zcontrol.pixel_format to PIXELFMT_Z24 is when the game wants to copy from ZBuffer (Zbuffer uses 24-bit Format)
|
||||
if (!g_ActiveConfig.bEFBCopyDisable)
|
||||
{
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, scaleByHalf, rc);
|
||||
TextureCache::CopyRenderTargetToTexture(address, fromZBuffer, isIntensityFmt, copyfmt, !!scaleByHalf, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1082,7 +1082,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons
|
|||
|
||||
// Disable all other stages
|
||||
for (int i = 1; i < 8; ++i)
|
||||
TextureCache::DisableStage(i);
|
||||
OGL::TextureCache::DisableStage(i);
|
||||
|
||||
// Update GLViewPort
|
||||
glViewport(dst_rect.left, dst_rect.bottom, dst_rect.GetWidth(), dst_rect.GetHeight());
|
||||
|
@ -1246,7 +1246,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons
|
|||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
|
||||
TextureCache::DisableStage(0);
|
||||
OGL::TextureCache::DisableStage(0);
|
||||
|
||||
// Wireframe
|
||||
if (g_ActiveConfig.bWireFrame)
|
||||
|
|
|
@ -53,15 +53,11 @@
|
|||
#include "VertexShaderManager.h"
|
||||
#include "VideoConfig.h"
|
||||
|
||||
u8 *TextureCache::temp = NULL;
|
||||
TextureCache::TexCache TextureCache::textures;
|
||||
namespace OGL
|
||||
{
|
||||
|
||||
extern int frameCount;
|
||||
static u32 s_TempFramebuffer = 0;
|
||||
|
||||
#define TEMP_SIZE (2048*2048*4)
|
||||
#define TEXTURE_KILL_THRESHOLD 200
|
||||
|
||||
static const GLint c_MinLinearFilter[8] = {
|
||||
GL_NEAREST,
|
||||
GL_NEAREST_MIPMAP_NEAREST,
|
||||
|
@ -83,352 +79,63 @@ static const GLint c_WrapSettings[4] = {
|
|||
bool SaveTexture(const char* filename, u32 textarget, u32 tex, int width, int height)
|
||||
{
|
||||
std::vector<u32> data(width * height);
|
||||
glBindTexture(textarget, tex);
|
||||
glGetTexImage(textarget, 0, GL_BGRA, GL_UNSIGNED_BYTE, &data[0]);
|
||||
GLenum err = GL_REPORT_ERROR();
|
||||
if (err != GL_NO_ERROR)
|
||||
glBindTexture(textarget, tex);
|
||||
glGetTexImage(textarget, 0, GL_BGRA, GL_UNSIGNED_BYTE, &data[0]);
|
||||
|
||||
const GLenum err = GL_REPORT_ERROR();
|
||||
if (GL_NO_ERROR != err)
|
||||
{
|
||||
PanicAlert("Can't save texture, GL Error: %s", gluErrorString(err));
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return SaveTGA(filename, width, height, &data[0]);
|
||||
}
|
||||
|
||||
void TextureCache::TCacheEntry::SetTextureParameters(TexMode0 &newmode,TexMode1 &newmode1)
|
||||
TextureCache::TCacheEntry::~TCacheEntry()
|
||||
{
|
||||
mode = newmode;
|
||||
mode1 = newmode1;
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
||||
(newmode.mag_filter || g_Config.bForceFiltering) ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
if (bHaveMipMaps)
|
||||
if (texture)
|
||||
{
|
||||
if (g_ActiveConfig.bForceFiltering && newmode.min_filter < 4)
|
||||
mode.min_filter += 4; // take equivalent forced linear
|
||||
int filt = newmode.min_filter;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, c_MinLinearFilter[filt & 7]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, newmode1.min_lod >> 4);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, newmode1.max_lod >> 4);
|
||||
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, (newmode.lod_bias/32.0f));
|
||||
|
||||
}
|
||||
else
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
(g_ActiveConfig.bForceFiltering || newmode.min_filter >= 4) ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, c_WrapSettings[newmode.wrap_s]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, c_WrapSettings[newmode.wrap_t]);
|
||||
|
||||
if (g_Config.iMaxAnisotropy >= 1)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)(1 << g_ActiveConfig.iMaxAnisotropy));
|
||||
}
|
||||
|
||||
void TextureCache::TCacheEntry::Destroy(bool shutdown)
|
||||
{
|
||||
if (texture)
|
||||
glDeleteTextures(1, &texture);
|
||||
texture = 0;
|
||||
if (!isRenderTarget && !shutdown && !g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
u32 *ptr = (u32*)g_VideoInitialize.pGetMemoryPointer(addr);
|
||||
if (ptr && *ptr == hash)
|
||||
*ptr = oldpixel;
|
||||
texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::Init()
|
||||
TextureCache::TCacheEntry::TCacheEntry()
|
||||
{
|
||||
temp = (u8*)AllocateMemoryPages(TEMP_SIZE);
|
||||
TexDecoder_SetTexFmtOverlayOptions(g_ActiveConfig.bTexFmtOverlayEnable, g_ActiveConfig.bTexFmtOverlayCenter);
|
||||
HiresTextures::Init(globals->unique_id);
|
||||
}
|
||||
|
||||
void TextureCache::Invalidate(bool shutdown)
|
||||
{
|
||||
for (TexCache::iterator iter = textures.begin(); iter != textures.end(); ++iter)
|
||||
iter->second.Destroy(shutdown);
|
||||
textures.clear();
|
||||
HiresTextures::Shutdown();
|
||||
}
|
||||
|
||||
void TextureCache::Shutdown()
|
||||
{
|
||||
Invalidate(true);
|
||||
|
||||
if (s_TempFramebuffer)
|
||||
{
|
||||
glDeleteFramebuffersEXT(1, (GLuint *)&s_TempFramebuffer);
|
||||
s_TempFramebuffer = 0;
|
||||
}
|
||||
|
||||
FreeMemoryPages(temp, TEMP_SIZE);
|
||||
temp = NULL;
|
||||
}
|
||||
|
||||
void TextureCache::Cleanup()
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
if (frameCount > TEXTURE_KILL_THRESHOLD + iter->second.frameCount)
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
int rangePosition = iter->second.IntersectsMemoryRange(start_address, size);
|
||||
if (rangePosition == 0)
|
||||
{
|
||||
iter->second.Destroy(false);
|
||||
textures.erase(iter++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
||||
{
|
||||
TexCache::iterator iter = textures.begin();
|
||||
while (iter != textures.end())
|
||||
{
|
||||
int rangePosition = iter->second.IntersectsMemoryRange(start_address, size);
|
||||
if ( rangePosition == 0)
|
||||
{
|
||||
iter->second.hash = 0;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
int TextureCache::TCacheEntry::IntersectsMemoryRange(u32 range_address, u32 range_size)
|
||||
{
|
||||
if (addr + size_in_bytes < range_address)
|
||||
return -1;
|
||||
if (addr >= range_address + range_size)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntry* TextureCache::Load(int texstage, u32 address, int width, int height, u32 tex_format, int tlutaddr, int tlutfmt)
|
||||
{
|
||||
// notes (about "UNsafe texture cache"):
|
||||
// Have to be removed soon.
|
||||
// But we keep it until the "safe" way became rock solid
|
||||
// pros: it has an unique ID held by the texture data itself (@address) once cached.
|
||||
// cons: it writes this unique ID in the gc RAM <- very dangerous (break MP1) and ugly
|
||||
|
||||
// notes (about "safe texture cache"):
|
||||
// Metroids text issue (character table):
|
||||
// Same addr, same GX_TF_C4 texture data but different TLUT (hence different outputs).
|
||||
// That's why we have to hash the TLUT too for TLUT tex_format dependent textures (ie. GX_TF_C4, GX_TF_C8, GX_TF_C14X2).
|
||||
// And since the address and tex data don't change, the key index in the cacheEntry map can't be the address but
|
||||
// have to be a real unique ID.
|
||||
// DONE but not satifiying yet -> may break copyEFBToTexture sometimes.
|
||||
|
||||
// Pokemon Colosseum text issue (plain text):
|
||||
// Use a GX_TF_I4 512x512 text-flush-texture at a const address.
|
||||
// The problem here was just the sparse hash on the texture. This texture is partly overwrited (what is needed only)
|
||||
// so lot's of remaning old text. Thin white chars on black bg too.
|
||||
|
||||
// TODO: - clean this up when ready to kill old "unsafe texture cache"
|
||||
// - fix the key index situation with CopyRenderTargetToTexture.
|
||||
// Could happen only for GX_TF_C4, GX_TF_C8 and GX_TF_C14X2 fmt.
|
||||
// Wonder if we can't use tex width&height to know if EFB might be copied to it...
|
||||
// raw idea: TOCHECK if addresses are aligned we have few bits left...
|
||||
|
||||
if (address == 0)
|
||||
return NULL;
|
||||
|
||||
TexMode0 &tm0 = bpmem.tex[texstage >> 2].texMode0[texstage & 3];
|
||||
TexMode1 &tm1 = bpmem.tex[texstage >> 2].texMode1[texstage & 3];
|
||||
int maxlevel = (tm1.max_lod >> 4);
|
||||
bool UseNativeMips = (tm0.min_filter & 3) && (tm0.min_filter != 8) && g_ActiveConfig.bUseNativeMips;
|
||||
|
||||
u8 *ptr = g_VideoInitialize.pGetMemoryPointer(address);
|
||||
int bsw = TexDecoder_GetBlockWidthInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
int bsh = TexDecoder_GetBlockHeightInTexels(tex_format) - 1; // TexelSizeInNibbles(format)*width*height/16;
|
||||
int bsdepth = TexDecoder_GetTexelSizeInNibbles(tex_format);
|
||||
int expandedWidth = (width + bsw) & (~bsw);
|
||||
int expandedHeight = (height + bsh) & (~bsh);
|
||||
|
||||
u64 hash_value = 0;
|
||||
u32 texID = address;
|
||||
u64 texHash = 0;
|
||||
u32 FullFormat = tex_format;
|
||||
bool TextureisDynamic = false;
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
FullFormat = (tex_format | (tlutfmt << 16));
|
||||
|
||||
if (g_ActiveConfig.bSafeTextureCache || g_ActiveConfig.bHiresTextures || g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
texHash = GetHash64(ptr,TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
{
|
||||
// WARNING! texID != address now => may break CopyRenderTargetToTexture (cf. TODO up)
|
||||
// tlut size can be up to 32768B (GX_TF_C14X2) but Safer == Slower.
|
||||
// This trick (to change the texID depending on the TLUT addr) is a trick to get around
|
||||
// an issue with metroid prime's fonts, where it has multiple sets of fonts on top of
|
||||
// each other stored in a single texture, and uses the palette to make different characters
|
||||
// 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.
|
||||
u64 tlutHash = GetHash64(&texMem[tlutaddr], TexDecoder_GetPaletteSize(tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
texHash ^= tlutHash;
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
texID = texID ^ ((u32)(tlutHash & 0xFFFFFFFF)) ^ ((u32)((tlutHash >> 32) & 0xFFFFFFFF));
|
||||
}
|
||||
}
|
||||
if (g_ActiveConfig.bSafeTextureCache)
|
||||
hash_value = texHash;
|
||||
}
|
||||
|
||||
bool skip_texture_create = false;
|
||||
TexCache::iterator iter = textures.find(texID);
|
||||
|
||||
if (iter != textures.end())
|
||||
{
|
||||
TCacheEntry &entry = iter->second;
|
||||
|
||||
if (!g_ActiveConfig.bSafeTextureCache)
|
||||
{
|
||||
if(entry.isRenderTarget || entry.isDynamic)
|
||||
{
|
||||
if(!g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
hash_value = GetHash64(ptr,TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
if ((tex_format == GX_TF_C4) || (tex_format == GX_TF_C8) || (tex_format == GX_TF_C14X2))
|
||||
{
|
||||
hash_value ^= GetHash64(&texMem[tlutaddr], TexDecoder_GetPaletteSize(tex_format),g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hash_value = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hash_value = ((u32 *)ptr)[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(entry.isRenderTarget || entry.isDynamic)
|
||||
{
|
||||
if(g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
hash_value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (((entry.isRenderTarget || entry.isDynamic) && hash_value == entry.hash && address == entry.addr)
|
||||
|| ((address == entry.addr) && (hash_value == entry.hash) && ((int) FullFormat == entry.fmt) && entry.MipLevels >= maxlevel))
|
||||
{
|
||||
entry.frameCount = frameCount;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
||||
GL_REPORT_ERRORD();
|
||||
entry.SetTextureParameters(tm0,tm1);
|
||||
entry.isDynamic = false;
|
||||
return &entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's reload the new texture data into the same texture,
|
||||
// instead of destroying it and having to create a new one.
|
||||
// Might speed up movie playback very, very slightly.
|
||||
TextureisDynamic = (entry.isRenderTarget || entry.isDynamic) && !g_ActiveConfig.bCopyEFBToTexture;
|
||||
if (((!(entry.isRenderTarget || entry.isDynamic) && width == entry.w && height == entry.h && (int)FullFormat == entry.fmt) ||
|
||||
((entry.isRenderTarget || entry.isDynamic) && entry.w == width && entry.h == height && entry.Scaledw == width && entry.Scaledh == height)))
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
||||
GL_REPORT_ERRORD();
|
||||
entry.SetTextureParameters(tm0,tm1);
|
||||
skip_texture_create = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Destroy(false);
|
||||
textures.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make an entry in the table
|
||||
TCacheEntry& entry = textures[texID];
|
||||
entry.isDynamic = TextureisDynamic;
|
||||
entry.isRenderTarget = false;
|
||||
entry.Realw = width;
|
||||
entry.Realh = height;
|
||||
PC_TexFormat pcfmt = PC_TEX_FMT_NONE;
|
||||
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
// Load Custom textures
|
||||
char texPathTemp[MAX_PATH];
|
||||
|
||||
int newWidth = width;
|
||||
int newHeight = height;
|
||||
|
||||
sprintf(texPathTemp, "%s_%08x_%i", globals->unique_id, (unsigned int) texHash, tex_format);
|
||||
pcfmt = HiresTextures::GetHiresTex(texPathTemp, &newWidth, &newHeight, tex_format, temp);
|
||||
|
||||
if (pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
expandedWidth = width = newWidth;
|
||||
expandedHeight = height = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcfmt == PC_TEX_FMT_NONE)
|
||||
pcfmt = TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt);
|
||||
|
||||
entry.oldpixel = ((u32 *)ptr)[0];
|
||||
|
||||
if (g_ActiveConfig.bSafeTextureCache || entry.isDynamic)
|
||||
entry.hash = hash_value;
|
||||
else
|
||||
{
|
||||
entry.hash = (u32)(((double)rand() / RAND_MAX) * 0xFFFFFFFF);
|
||||
((u32 *)ptr)[0] = entry.hash;
|
||||
}
|
||||
|
||||
entry.addr = address;
|
||||
entry.size_in_bytes = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, tex_format);
|
||||
|
||||
GLenum target = GL_TEXTURE_2D;
|
||||
if (!skip_texture_create) {
|
||||
glGenTextures(1, (GLuint *)&entry.texture);
|
||||
glBindTexture(target, entry.texture);
|
||||
}
|
||||
|
||||
bool isPow2 = !((width & (width - 1)) || (height & (height - 1)));
|
||||
int TexLevels = (width > height)?width:height;
|
||||
TexLevels = (isPow2 && UseNativeMips && (maxlevel > 0)) ? (int)(log((double)TexLevels)/log((double)2)) + 1 : (isPow2? 0 : 1);
|
||||
if(TexLevels > (maxlevel + 1) && maxlevel > 0)
|
||||
TexLevels = (maxlevel + 1);
|
||||
entry.MipLevels = maxlevel;
|
||||
bool GenerateMipmaps = TexLevels > 1 || TexLevels == 0;
|
||||
entry.bHaveMipMaps = GenerateMipmaps;
|
||||
int gl_format = 0;
|
||||
int gl_iformat = 0;
|
||||
int gl_type = 0;
|
||||
glGenTextures(1, &texture);
|
||||
GL_REPORT_ERRORD();
|
||||
}
|
||||
|
||||
void TextureCache::TCacheEntry::Bind(unsigned int stage)
|
||||
{
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
// TODO: is this already done somewhere else?
|
||||
TexMode0 &tm0 = bpmem.tex[stage >> 2].texMode0[stage & 3];
|
||||
TexMode1 &tm1 = bpmem.tex[stage >> 2].texMode1[stage & 3];
|
||||
SetTextureParameters(tm0, tm1);
|
||||
}
|
||||
|
||||
bool TextureCache::TCacheEntry::Save(const char filename[])
|
||||
{
|
||||
// TODO: make ogl dump PNGs
|
||||
std::string tga_filename(filename);
|
||||
tga_filename.replace(tga_filename.size() - 3, 3, "tga");
|
||||
|
||||
return SaveTexture(tga_filename.c_str(), GL_TEXTURE_2D, texture, w, h);
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width,
|
||||
unsigned int height, unsigned int expanded_width,
|
||||
unsigned int tex_levels, PC_TexFormat pcfmt)
|
||||
{
|
||||
int gl_format = 0,
|
||||
gl_iformat = 0,
|
||||
gl_type = 0;
|
||||
|
||||
if (pcfmt != PC_TEX_FMT_DXT1)
|
||||
{
|
||||
switch (pcfmt)
|
||||
|
@ -441,371 +148,160 @@ TextureCache::TCacheEntry* TextureCache::Load(int texstage, u32 address, int wid
|
|||
gl_iformat = 4;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_RGBA32:
|
||||
gl_format = GL_RGBA;
|
||||
gl_iformat = 4;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_I4_AS_I8:
|
||||
gl_format = GL_LUMINANCE;
|
||||
gl_iformat = GL_INTENSITY4;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_IA4_AS_IA8:
|
||||
gl_format = GL_LUMINANCE_ALPHA;
|
||||
gl_iformat = GL_LUMINANCE4_ALPHA4;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_I8:
|
||||
gl_format = GL_LUMINANCE;
|
||||
gl_iformat = GL_INTENSITY8;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_IA8:
|
||||
gl_format = GL_LUMINANCE_ALPHA;
|
||||
gl_iformat = GL_LUMINANCE8_ALPHA8;
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case PC_TEX_FMT_RGB565:
|
||||
gl_format = GL_RGB;
|
||||
gl_iformat = GL_RGB;
|
||||
gl_type = GL_UNSIGNED_SHORT_5_6_5;
|
||||
break;
|
||||
}
|
||||
if (expandedWidth != width)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, expandedWidth);
|
||||
//generate mipmaps even if we use native mips to suport textures with less levels
|
||||
if(skip_texture_create)
|
||||
{
|
||||
glTexSubImage2D(target, 0,0,0,width, height, gl_format, gl_type, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GenerateMipmaps)
|
||||
{
|
||||
if(UseNativeMips)
|
||||
{
|
||||
glTexImage2D(target, 0, gl_iformat, width, height, 0, gl_format, gl_type, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
|
||||
glTexImage2D(target, 0, gl_iformat, width, height, 0, gl_format, gl_type, temp);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexImage2D(target, 0, gl_iformat, width, height, 0, gl_format, gl_type, temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (expandedWidth != width) // reset
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip_texture_create)
|
||||
{
|
||||
glCompressedTexSubImage2D(target, 0,0,0,width, height,
|
||||
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,expandedWidth*expandedHeight/2, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
glCompressedTexImage2D(target, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
|
||||
width, height, 0, expandedWidth*expandedHeight/2, temp);
|
||||
}
|
||||
}
|
||||
GL_REPORT_ERRORD();
|
||||
if(TexLevels > 1 && pcfmt != PC_TEX_FMT_NONE)
|
||||
{
|
||||
int level = 1;
|
||||
int mipWidth = width >> 1;
|
||||
int mipHeight = height >> 1;
|
||||
ptr += entry.size_in_bytes;
|
||||
while((mipHeight || mipWidth) && (level < TexLevels))
|
||||
{
|
||||
u32 currentWidth = (mipWidth > 0)? mipWidth : 1;
|
||||
u32 currentHeight = (mipHeight > 0)? mipHeight : 1;
|
||||
expandedWidth = (currentWidth + bsw) & (~bsw);
|
||||
expandedHeight = (currentHeight + bsh) & (~bsh);
|
||||
TexDecoder_Decode(temp, ptr, expandedWidth, expandedHeight, tex_format, tlutaddr, tlutfmt);
|
||||
if (pcfmt != PC_TEX_FMT_DXT1)
|
||||
{
|
||||
if (expandedWidth != (int)currentWidth)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, expandedWidth);
|
||||
glTexImage2D(target, level, gl_iformat, currentWidth, currentHeight, 0, gl_format, gl_type, temp);
|
||||
if (expandedWidth != (int)currentWidth)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
glCompressedTexImage2D(target, level, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, currentWidth, currentHeight, 0, expandedWidth*expandedHeight/2, temp);
|
||||
}
|
||||
GL_REPORT_ERRORD();
|
||||
u32 size = (max(mipWidth, bsw) * max(mipHeight, bsh) * bsdepth) >> 1;
|
||||
ptr += size;
|
||||
mipWidth >>= 1;
|
||||
mipHeight >>= 1;
|
||||
level++;
|
||||
}
|
||||
}
|
||||
entry.frameCount = frameCount;
|
||||
entry.w = width;
|
||||
entry.h = height;
|
||||
entry.Scaledw = width;
|
||||
entry.Scaledh = height;
|
||||
entry.fmt = FullFormat;
|
||||
entry.SetTextureParameters(tm0,tm1);
|
||||
if (g_ActiveConfig.bDumpTextures)
|
||||
{
|
||||
// dump texture to file
|
||||
char szTemp[MAX_PATH];
|
||||
char szDir[MAX_PATH];
|
||||
const char* uniqueId = globals->unique_id;
|
||||
static bool bCheckedDumpDir = false;
|
||||
|
||||
sprintf(szDir, "%s%s", File::GetUserPath(D_DUMPTEXTURES_IDX), uniqueId);
|
||||
|
||||
if (!bCheckedDumpDir)
|
||||
{
|
||||
if (!File::Exists(szDir) || !File::IsDirectory(szDir))
|
||||
File::CreateDir(szDir);
|
||||
|
||||
bCheckedDumpDir = true;
|
||||
}
|
||||
|
||||
sprintf(szTemp, "%s/%s_%08x_%i.tga", szDir, uniqueId, (unsigned int) texHash, tex_format);
|
||||
if (!File::Exists(szTemp))
|
||||
SaveTexture(szTemp, target, entry.texture, entry.w, entry.h);
|
||||
}
|
||||
|
||||
INCSTAT(stats.numTexturesCreated);
|
||||
SETSTAT(stats.numTexturesAlive, textures.size());
|
||||
TCacheEntry &entry = *new TCacheEntry;
|
||||
entry.gl_format = gl_format;
|
||||
entry.gl_iformat = gl_iformat;
|
||||
entry.gl_type = gl_type;
|
||||
entry.pcfmt = pcfmt;
|
||||
|
||||
entry.bHaveMipMaps = tex_levels != 1;
|
||||
|
||||
return &entry;
|
||||
}
|
||||
|
||||
void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle &source_rect)
|
||||
void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int level)
|
||||
{
|
||||
DVSTARTPROFILE();
|
||||
//glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
//GL_REPORT_ERRORD();
|
||||
|
||||
if (pcfmt != PC_TEX_FMT_DXT1)
|
||||
{
|
||||
if (expanded_width != width)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, expanded_width);
|
||||
|
||||
if (bHaveMipMaps && 0 == level)
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
|
||||
glTexImage2D(GL_TEXTURE_2D, level, gl_iformat, width, height, 0, gl_format, gl_type, temp);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexImage2D(GL_TEXTURE_2D, level, gl_iformat, width, height, 0, gl_format, gl_type, temp);
|
||||
}
|
||||
|
||||
if (expanded_width != width)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlert("PC_TEX_FMT_DXT1 support disabled");
|
||||
//glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
|
||||
//width, height, 0, expanded_width * expanded_height/2, temp);
|
||||
}
|
||||
GL_REPORT_ERRORD();
|
||||
}
|
||||
|
||||
TextureCache::TCacheEntryBase* TextureCache::CreateRenderTargetTexture(
|
||||
unsigned int scaled_tex_w, unsigned int scaled_tex_h)
|
||||
{
|
||||
TCacheEntry *const entry = new TCacheEntry;
|
||||
glBindTexture(GL_TEXTURE_2D, entry->texture);
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
// for intensity values, use Y of YUV format!
|
||||
// for all purposes, treat 4bit equivalents as 8bit (probably just used for compression)
|
||||
// RGBA8 - RGBA8
|
||||
// RGB565 - RGB565
|
||||
// RGB5A3 - RGB5A3
|
||||
// I4,R4,Z4 - I4
|
||||
// IA4,RA4 - IA4
|
||||
// Z8M,G8,I8,A8,Z8,R8,B8,Z8L - I8
|
||||
// Z16,GB8,RG8,Z16L,IA8,RA8 - IA8
|
||||
float colmat[16];
|
||||
float fConstAdd[4] = {0};
|
||||
memset(colmat, 0, sizeof(colmat));
|
||||
const GLenum
|
||||
gl_format = GL_RGBA,
|
||||
gl_iformat = 4,
|
||||
gl_type = GL_UNSIGNED_BYTE;
|
||||
|
||||
if (bFromZBuffer)
|
||||
{
|
||||
switch(copyfmt)
|
||||
{
|
||||
case 0: // Z4
|
||||
case 1: // Z8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
break;
|
||||
case 3: // Z16 //?
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
break;
|
||||
case 11: // Z16 (reverse order)
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[13] = 1;
|
||||
break;
|
||||
case 6: // Z24X8
|
||||
colmat[2] = colmat[5] = colmat[8] = colmat[15] = 1;
|
||||
break;
|
||||
case 9: // Z8M
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
break;
|
||||
case 10: // Z8L
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
break;
|
||||
case 12: // Z16L
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy zbuf format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (bIsIntensityFmt)
|
||||
{
|
||||
// TODO - verify these coefficients
|
||||
fConstAdd[0] = fConstAdd[1] = fConstAdd[2] = 16.0f/255.0f;
|
||||
colmat[0] = 0.257f; colmat[1] = 0.504f; colmat[2] = 0.098f;
|
||||
colmat[4] = 0.257f; colmat[5] = 0.504f; colmat[6] = 0.098f;
|
||||
colmat[8] = 0.257f; colmat[9] = 0.504f; colmat[10] = 0.098f;
|
||||
if (copyfmt < 2)
|
||||
{
|
||||
fConstAdd[3] = 16.0f / 255.0f;
|
||||
colmat[12] = 0.257f; colmat[13] = 0.504f; colmat[14] = 0.098f;
|
||||
}
|
||||
else// alpha
|
||||
colmat[15] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (copyfmt)
|
||||
{
|
||||
case 0: // R4
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
break;
|
||||
case 8: // R8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[12] = 1;
|
||||
break;
|
||||
case 2: // RA4
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[15] = 1;
|
||||
break;
|
||||
case 3: // RA8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[15] = 1;
|
||||
break;
|
||||
case 7: // A8
|
||||
colmat[3] = colmat[7] = colmat[11] = colmat[15] = 1;
|
||||
break;
|
||||
case 9: // G8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[13] = 1;
|
||||
break;
|
||||
case 10: // B8
|
||||
colmat[2] = colmat[6] = colmat[10] = colmat[14] = 1;
|
||||
break;
|
||||
case 11: // RG8
|
||||
colmat[0] = colmat[4] = colmat[8] = colmat[13] = 1;
|
||||
break;
|
||||
case 12: // GB8
|
||||
colmat[1] = colmat[5] = colmat[9] = colmat[14] = 1;
|
||||
break;
|
||||
case 4: // RGB565
|
||||
colmat[0] = colmat[5] = colmat[10] = 1;
|
||||
fConstAdd[3] = 1; // set alpha to 1
|
||||
break;
|
||||
case 5: // RGB5A3
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
case 6: // RGBA8
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(VIDEO, "Unknown copy color format: 0x%x", copyfmt);
|
||||
colmat[0] = colmat[5] = colmat[10] = colmat[15] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool bIsInit = textures.find(address) != textures.end();
|
||||
|
||||
PRIM_LOG("copytarg: addr=0x%x, fromz=%d, intfmt=%d, copyfmt=%d", address, (int)bFromZBuffer, (int)bIsIntensityFmt,copyfmt);
|
||||
|
||||
TCacheEntry& entry = textures[address];
|
||||
entry.hash = 0;
|
||||
entry.frameCount = frameCount;
|
||||
|
||||
int w = (abs(source_rect.GetWidth()) >> bScaleByHalf);
|
||||
int h = (abs(source_rect.GetHeight()) >> bScaleByHalf);
|
||||
|
||||
float xScale = Renderer::GetTargetScaleX();
|
||||
float yScale = Renderer::GetTargetScaleY();
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, gl_iformat, scaled_tex_w, scaled_tex_h, 0, gl_format, gl_type, NULL);
|
||||
|
||||
int Scaledtex_w = (g_ActiveConfig.bCopyEFBScaled)?((int)(xScale * w)) : w;
|
||||
int Scaledtex_h = (g_ActiveConfig.bCopyEFBScaled)?((int)(yScale * h)) : h;
|
||||
|
||||
GLenum gl_format = GL_RGBA;
|
||||
GLenum gl_iformat = 4;
|
||||
GLenum gl_type = GL_UNSIGNED_BYTE;
|
||||
|
||||
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
if (!bIsInit)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
if (GL_REPORT_ERROR() != GL_NO_ERROR)
|
||||
{
|
||||
glGenTextures(1, (GLuint *)&entry.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
||||
GL_REPORT_ERRORD();
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, gl_iformat, Scaledtex_w, Scaledtex_h, 0, gl_format, gl_type, NULL);
|
||||
GL_REPORT_ERRORD();
|
||||
entry.isRenderTarget = true;
|
||||
entry.isDynamic = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_assert_(entry.texture);
|
||||
GL_REPORT_ERRORD();
|
||||
if(entry.isDynamic)
|
||||
{
|
||||
Scaledtex_h = h;
|
||||
Scaledtex_w = w;
|
||||
}
|
||||
if (((entry.isRenderTarget || entry.isDynamic) && entry.Scaledw == Scaledtex_w && entry.Scaledh == Scaledtex_h))
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
||||
// for some reason mario sunshine errors here...
|
||||
// Beyond Good and Evil does too, occasionally.
|
||||
GL_REPORT_ERRORD();
|
||||
} else {
|
||||
// Delete existing texture.
|
||||
glDeleteTextures(1,(GLuint *)&entry.texture);
|
||||
glGenTextures(1, (GLuint *)&entry.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, gl_iformat, Scaledtex_w, Scaledtex_h, 0, gl_format, gl_type, NULL);
|
||||
GL_REPORT_ERRORD();
|
||||
entry.isRenderTarget = !entry.isDynamic;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bIsInit)
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
return entry;
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
if (GL_REPORT_ERROR() != GL_NO_ERROR) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
||||
GL_REPORT_ERRORD();
|
||||
}
|
||||
}
|
||||
|
||||
entry.addr = address;
|
||||
entry.w = entry.Realw = w;
|
||||
entry.h = entry.Realh = h;
|
||||
entry.Scaledw = Scaledtex_w;
|
||||
entry.Scaledh = Scaledtex_h;
|
||||
entry.fmt = copyfmt;
|
||||
entry.hash = 0;
|
||||
void TextureCache::TCacheEntry::FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float colmat[], const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// Make sure to resolve anything we need to read from.
|
||||
GLuint read_texture = bFromZBuffer ? g_framebufferManager.ResolveAndGetDepthTarget(source_rect) : g_framebufferManager.ResolveAndGetRenderTarget(source_rect);
|
||||
const GLuint read_texture = bFromZBuffer ?
|
||||
g_framebufferManager.ResolveAndGetDepthTarget(source_rect) :
|
||||
g_framebufferManager.ResolveAndGetRenderTarget(source_rect);
|
||||
|
||||
// TODO: move
|
||||
const float xScale = Renderer::GetTargetScaleX();
|
||||
const float yScale = Renderer::GetTargetScaleY();
|
||||
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
// We have to run a pixel shader, for color conversion.
|
||||
Renderer::ResetAPIState(); // reset any game specific settings
|
||||
if(!entry.isDynamic || g_ActiveConfig.bCopyEFBToTexture)
|
||||
if (false == isDynamic || g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
if (s_TempFramebuffer == 0)
|
||||
glGenFramebuffersEXT(1, (GLuint *)&s_TempFramebuffer);
|
||||
glGenFramebuffersEXT(1, (GLuint*)&s_TempFramebuffer);
|
||||
|
||||
g_framebufferManager.SetFramebuffer(s_TempFramebuffer);
|
||||
// Bind texture to temporary framebuffer
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, entry.texture, 0);
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture, 0);
|
||||
GL_REPORT_FBO_ERROR();
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glEnable(GL_TEXTURE_RECTANGLE_ARB);
|
||||
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, read_texture);
|
||||
|
||||
glViewport(0, 0, Scaledtex_w, Scaledtex_h);
|
||||
|
||||
glViewport(0, 0, scaledW, scaledH);
|
||||
|
||||
PixelShaderCache::SetCurrentShader(bFromZBuffer ? PixelShaderCache::GetDepthMatrixProgram() : PixelShaderCache::GetColorMatrixProgram());
|
||||
const float* const fConstAdd = colmat + 16; // fConstAdd is the last 4 floats of colmat
|
||||
PixelShaderManager::SetColorMatrix(colmat, fConstAdd); // set transformation
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
|
@ -823,11 +319,11 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
// Unbind texture from temporary framebuffer
|
||||
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
|
||||
if(!g_ActiveConfig.bCopyEFBToTexture)
|
||||
|
||||
if (false == g_ActiveConfig.bCopyEFBToTexture)
|
||||
{
|
||||
textures[address].hash = TextureConverter::EncodeToRamFromTexture(
|
||||
address,
|
||||
hash = TextureConverter::EncodeToRamFromTexture(
|
||||
addr,
|
||||
read_texture,
|
||||
xScale,
|
||||
yScale,
|
||||
|
@ -836,32 +332,69 @@ void TextureCache::CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, boo
|
|||
copyfmt,
|
||||
bScaleByHalf,
|
||||
source_rect);
|
||||
|
||||
}
|
||||
// Return to the EFB.
|
||||
|
||||
g_framebufferManager.SetFramebuffer(0);
|
||||
Renderer::RestoreAPIState();
|
||||
VertexShaderManager::SetViewportChanged();
|
||||
TextureCache::DisableStage(0);
|
||||
DisableStage(0);
|
||||
|
||||
GL_REPORT_ERRORD();
|
||||
|
||||
if (g_ActiveConfig.bDumpEFBTarget)
|
||||
{
|
||||
if (g_ActiveConfig.bDumpEFBTarget)
|
||||
{
|
||||
static int count = 0;
|
||||
SaveTexture(StringFromFormat("%sefb_frame_%i.tga", File::GetUserPath(D_DUMPTEXTURES_IDX), count++).c_str(), GL_TEXTURE_2D, entry.texture, entry.w, entry.h);
|
||||
}
|
||||
SaveTexture(StringFromFormat("%sefb_frame_%i.tga", File::GetUserPath(D_DUMPTEXTURES_IDX),
|
||||
count++).c_str(), GL_TEXTURE_2D, texture, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::DisableStage(int stage)
|
||||
void TextureCache::TCacheEntry::SetTextureParameters(const TexMode0 &newmode, const TexMode1 &newmode1)
|
||||
{
|
||||
// TODO: not used anywhere
|
||||
TexMode0 mode = newmode;
|
||||
//mode1 = newmode1;
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
||||
(newmode.mag_filter || g_Config.bForceFiltering) ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
if (bHaveMipMaps)
|
||||
{
|
||||
// TODO: not used anywhere
|
||||
if (g_ActiveConfig.bForceFiltering && newmode.min_filter < 4)
|
||||
mode.min_filter += 4; // take equivalent forced linear
|
||||
|
||||
int filt = newmode.min_filter;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, c_MinLinearFilter[filt & 7]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, newmode1.min_lod >> 4);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, newmode1.max_lod >> 4);
|
||||
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, (newmode.lod_bias / 32.0f));
|
||||
}
|
||||
else
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
(g_ActiveConfig.bForceFiltering || newmode.min_filter >= 4) ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, c_WrapSettings[newmode.wrap_s]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, c_WrapSettings[newmode.wrap_t]);
|
||||
|
||||
if (g_Config.iMaxAnisotropy >= 1)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
(float)(1 << g_ActiveConfig.iMaxAnisotropy));
|
||||
}
|
||||
|
||||
TextureCache::~TextureCache()
|
||||
{
|
||||
if (s_TempFramebuffer)
|
||||
{
|
||||
glDeleteFramebuffersEXT(1, (GLuint*)&s_TempFramebuffer);
|
||||
s_TempFramebuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::DisableStage(unsigned int stage)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + stage);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glDisable(GL_TEXTURE_RECTANGLE_ARB);
|
||||
}
|
||||
|
||||
void TextureCache::ClearRenderTargets()
|
||||
{
|
||||
for (TexCache::iterator iter = textures.begin(); iter != textures.end(); ++iter)
|
||||
iter->second.isRenderTarget = false;
|
||||
}
|
||||
|
|
|
@ -24,61 +24,62 @@
|
|||
#include "GLUtil.h"
|
||||
#include "BPStructs.h"
|
||||
|
||||
class TextureCache
|
||||
#include "TextureCacheBase.h"
|
||||
|
||||
namespace OGL
|
||||
{
|
||||
|
||||
class TextureCache : public ::TextureCache
|
||||
{
|
||||
public:
|
||||
struct TCacheEntry
|
||||
{
|
||||
TCacheEntry() : texture(0), addr(0), size_in_bytes(0), hash(0),
|
||||
w(0), h(0), MipLevels(0),Scaledw(0), Scaledh(0),
|
||||
isRenderTarget(false), isDynamic(false), bHaveMipMaps(false)
|
||||
{ mode.hex = 0xFCFCFCFC; }
|
||||
|
||||
GLuint texture;
|
||||
u32 addr;
|
||||
u32 size_in_bytes;
|
||||
u64 hash;
|
||||
u32 paletteHash;
|
||||
u32 oldpixel; // used for simple cleanup
|
||||
TexMode0 mode; // current filter and clamp modes that texture is set to
|
||||
TexMode1 mode1; // current filter and clamp modes that texture is set to
|
||||
|
||||
int frameCount;
|
||||
int w, h, fmt,MipLevels, Realw, Realh;
|
||||
int Scaledw, Scaledh;
|
||||
|
||||
bool isRenderTarget; // if render texture, then rendertex is filled with the direct copy of the render target
|
||||
// later conversions would have to convert properly from rendertexfmt to texfmt
|
||||
bool isDynamic; // modified from cpu
|
||||
bool bHaveMipMaps;
|
||||
|
||||
void SetTextureParameters(TexMode0& newmode,TexMode1 &newmode1);
|
||||
void Destroy(bool shutdown);
|
||||
void ConvertFromRenderTarget(u32 taddr, int twidth, int theight, int tformat, int tlutaddr, int tlutfmt);
|
||||
int IntersectsMemoryRange(u32 range_address, u32 range_size);
|
||||
};
|
||||
static void DisableStage(unsigned int stage);
|
||||
|
||||
private:
|
||||
typedef std::map<u32, TCacheEntry> TexCache;
|
||||
struct TCacheEntry : TCacheEntryBase
|
||||
{
|
||||
GLuint texture;
|
||||
|
||||
static u8 *temp;
|
||||
static TexCache textures;
|
||||
PC_TexFormat pcfmt;
|
||||
|
||||
public:
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
static void Shutdown();
|
||||
static void Invalidate(bool shutdown);
|
||||
static void InvalidateRange(u32 start_address, u32 size);
|
||||
static void MakeRangeDynamic(u32 start_address, u32 size);
|
||||
static TCacheEntry* Load(int texstage, u32 address, int width, int height, u32 format, int tlutaddr, int tlutfmt);
|
||||
static void CopyRenderTargetToTexture(u32 address, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle &source);
|
||||
int gl_format;
|
||||
int gl_iformat;
|
||||
int gl_type;
|
||||
|
||||
static void DisableStage(int stage); // sets active texture
|
||||
bool bHaveMipMaps;
|
||||
|
||||
static void ClearRenderTargets(); // sets render target value of all textures to false
|
||||
//TexMode0 mode; // current filter and clamp modes that texture is set to
|
||||
//TexMode1 mode1; // current filter and clamp modes that texture is set to
|
||||
|
||||
TCacheEntry();
|
||||
~TCacheEntry();
|
||||
|
||||
void Load(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int level);
|
||||
|
||||
void FromRenderTarget(bool bFromZBuffer, bool bScaleByHalf,
|
||||
unsigned int cbufid, const float colmat[], const EFBRectangle &source_rect,
|
||||
bool bIsIntensityFmt, u32 copyfmt);
|
||||
|
||||
void Bind(unsigned int stage);
|
||||
bool Save(const char filename[]);
|
||||
|
||||
private:
|
||||
void SetTextureParameters(const TexMode0 &newmode, const TexMode1 &newmode1);
|
||||
};
|
||||
|
||||
~TextureCache();
|
||||
|
||||
TCacheEntryBase* CreateTexture(unsigned int width, unsigned int height,
|
||||
unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt);
|
||||
|
||||
TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h);
|
||||
|
||||
private:
|
||||
bool isOGL() { return true; }
|
||||
};
|
||||
|
||||
bool SaveTexture(const char* filename, u32 textarget, u32 tex, int width, int height);
|
||||
|
||||
}
|
||||
|
||||
#endif // _TEXTUREMNGR_H_
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
namespace TextureConverter
|
||||
{
|
||||
|
||||
using OGL::TextureCache;
|
||||
|
||||
static GLuint s_texConvFrameBuffer = 0;
|
||||
static GLuint s_srcTexture = 0; // for decoding from RAM
|
||||
static GLuint s_srcTextureWidth = 0;
|
||||
|
|
|
@ -154,23 +154,25 @@ void VertexManager::vFlush()
|
|||
{
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
FourTexUnits &tex = bpmem.tex[i >> 2];
|
||||
TextureCache::TCacheEntry* tentry = TextureCache::Load(i,
|
||||
TextureCache::TCacheEntryBase* tentry = TextureCache::Load(i,
|
||||
(tex.texImage3[i&3].image_base/* & 0x1FFFFF*/) << 5,
|
||||
tex.texImage0[i&3].width + 1, tex.texImage0[i&3].height + 1,
|
||||
tex.texImage0[i&3].format, tex.texTlut[i&3].tmem_offset<<9,
|
||||
tex.texTlut[i&3].tlut_format);
|
||||
tex.texTlut[i&3].tlut_format,
|
||||
(tex.texMode0[i&3].min_filter & 3) && (tex.texMode0[i&3].min_filter != 8) && g_ActiveConfig.bUseNativeMips,
|
||||
(tex.texMode1[i&3].max_lod >> 4));
|
||||
|
||||
if (tentry)
|
||||
{
|
||||
// 0s are probably for no manual wrapping needed.
|
||||
PixelShaderManager::SetTexDims(i, tentry->Realw, tentry->Realh, 0, 0);
|
||||
PixelShaderManager::SetTexDims(i, tentry->nativeW, tentry->nativeH, 0, 0);
|
||||
|
||||
if (g_ActiveConfig.iLog & CONF_SAVETEXTURES)
|
||||
{
|
||||
// save the textures
|
||||
char strfile[255];
|
||||
sprintf(strfile, "%stex%.3d_%d.tga", File::GetUserPath(D_DUMPFRAMES_IDX), g_Config.iSaveTargetId, i);
|
||||
SaveTexture(strfile, GL_TEXTURE_2D, tentry->texture, tentry->w, tentry->h);
|
||||
tentry->Save(strfile);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -223,7 +223,7 @@ void Video_Prepare()
|
|||
CommandProcessor::Init();
|
||||
PixelEngine::Init();
|
||||
|
||||
TextureCache::Init();
|
||||
g_texture_cache = new OGL::TextureCache;
|
||||
|
||||
BPInit();
|
||||
g_vertex_manager = new OGL::VertexManager;
|
||||
|
@ -267,7 +267,7 @@ void Shutdown()
|
|||
PixelShaderManager::Shutdown();
|
||||
PixelShaderCache::Shutdown();
|
||||
delete g_vertex_manager;
|
||||
TextureCache::Shutdown();
|
||||
delete g_texture_cache;
|
||||
OpcodeDecoder_Shutdown();
|
||||
Renderer::Shutdown();
|
||||
OpenGL_Shutdown();
|
||||
|
|
Loading…
Reference in New Issue