pcsx2/plugins/gs/gsdx9/GSTextureCache.cpp

1227 lines
30 KiB
C++

/*
* Copyright (C) 2003-2005 Gabest
* http://www.gabest.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
#include "StdAfx.h"
#include "GSTextureCache.h"
#include "GSHash.h"
#include "GSRendererHW.h"
//
bool IsRenderTarget(IDirect3DTexture9* pTexture)
{
D3DSURFACE_DESC desc;
memset(&desc, 0, sizeof(desc));
return pTexture && S_OK == pTexture->GetLevelDesc(0, &desc) && (desc.Usage&D3DUSAGE_RENDERTARGET);
}
bool HasSharedBits(DWORD sbp, DWORD spsm, DWORD dbp, DWORD dpsm)
{
if(sbp != dbp) return false;
switch(spsm)
{
case PSM_PSMCT32:
case PSM_PSMCT16:
case PSM_PSMCT16S:
case PSM_PSMT8:
case PSM_PSMT4:
return true;
case PSM_PSMCT24:
return !(dpsm == PSM_PSMT8H || dpsm == PSM_PSMT4HL || dpsm == PSM_PSMT4HH);
case PSM_PSMT8H:
return !(dpsm == PSM_PSMCT24);
case PSM_PSMT4HL:
return !(dpsm == PSM_PSMCT24 || dpsm == PSM_PSMT4HH);
case PSM_PSMT4HH:
return !(dpsm == PSM_PSMCT24 || dpsm == PSM_PSMT4HL);
}
return true;
}
//
GSDirtyRect::GSDirtyRect(DWORD PSM, CRect r)
{
m_PSM = PSM;
m_rcDirty = r;
}
CRect GSDirtyRect::GetDirtyRect(const GIFRegTEX0& TEX0)
{
CRect rcDirty = m_rcDirty;
CSize src = GSLocalMemory::m_psmtbl[m_PSM].bs;
rcDirty.left = (rcDirty.left) & ~(src.cx-1);
rcDirty.right = (rcDirty.right + (src.cx-1) /* + 1 */) & ~(src.cx-1);
rcDirty.top = (rcDirty.top) & ~(src.cy-1);
rcDirty.bottom = (rcDirty.bottom + (src.cy-1) /* + 1 */) & ~(src.cy-1);
if(m_PSM != TEX0.PSM)
{
CSize dst = GSLocalMemory::m_psmtbl[TEX0.PSM].bs;
rcDirty.left = MulDiv(m_rcDirty.left, dst.cx, src.cx);
rcDirty.right = MulDiv(m_rcDirty.right, dst.cx, src.cx);
rcDirty.top = MulDiv(m_rcDirty.top, dst.cy, src.cy);
rcDirty.bottom = MulDiv(m_rcDirty.bottom, dst.cy, src.cy);
}
rcDirty &= CRect(0, 0, 1<<TEX0.TW, 1<<TEX0.TH);
return rcDirty;
}
void GSDirtyRectList::operator = (const GSDirtyRectList& l)
{
RemoveAll();
POSITION pos = l.GetHeadPosition();
while(pos) AddTail(l.GetNext(pos));
}
CRect GSDirtyRectList::GetDirtyRect(const GIFRegTEX0& TEX0)
{
if(IsEmpty()) return CRect(0, 0, 0, 0);
CRect r(INT_MAX, INT_MAX, 0, 0);
POSITION pos = GetHeadPosition();
while(pos) r |= GetNext(pos).GetDirtyRect(TEX0);
return r;
}
//
GSTextureBase::GSTextureBase()
{
m_scale = scale_t(1, 1);
m_fRT = false;
memset(&m_desc, 0, sizeof(m_desc));
}
GSTexture::GSTexture()
{
m_TEX0.TBP0 = ~0;
m_rcValid = CRect(0, 0, 0, 0);
m_dwHash = ~0;
m_nHashDiff = m_nHashSame = 0;
m_rcHash = CRect(0, 0, 0, 0);
m_nBytes = 0;
m_nAge = 0;
m_nVsyncs = 0;
m_fTemp = false;
}
//
GSTextureCache::GSTextureCache()
{
}
GSTextureCache::~GSTextureCache()
{
RemoveAll();
}
HRESULT GSTextureCache::CreateTexture(GSState* s, GSTexture* pt, DWORD PSM, DWORD CPSM)
{
if(!pt || pt->m_pTexture) {ASSERT(0); return E_FAIL;}
int w = 1 << pt->m_TEX0.TW;
int h = 1 << pt->m_TEX0.TH;
int bpp = 0;
D3DFORMAT fmt = D3DFMT_UNKNOWN;
D3DFORMAT palfmt = D3DFMT_UNKNOWN;
switch(PSM)
{
default:
case PSM_PSMCT32:
bpp = 32;
fmt = D3DFMT_A8R8G8B8;
break;
case PSM_PSMCT24:
bpp = 32;
fmt = D3DFMT_X8R8G8B8;
break;
case PSM_PSMCT16:
case PSM_PSMCT16S:
bpp = 16;
fmt = D3DFMT_A1R5G5B5;
break;
case PSM_PSMT8:
case PSM_PSMT4:
case PSM_PSMT8H:
case PSM_PSMT4HL:
case PSM_PSMT4HH:
bpp = 8;
fmt = D3DFMT_L8;
palfmt = CPSM == PSM_PSMCT32 ? D3DFMT_A8R8G8B8 : D3DFMT_A1R5G5B5;
break;
}
pt->m_nBytes = w*h*bpp>>3;
POSITION pos = m_pTexturePool.GetHeadPosition();
while(pos)
{
IDirect3DTexture9* pTexture = m_pTexturePool.GetNext(pos);
D3DSURFACE_DESC desc;
memset(&desc, 0, sizeof(desc));
pTexture->GetLevelDesc(0, &desc);
if(w == desc.Width && h == desc.Height && fmt == desc.Format && !IsTextureInCache(pTexture))
{
pt->m_pTexture = pTexture;
pt->m_desc = desc;
break;
}
}
if(!pt->m_pTexture)
{
while(m_pTexturePool.GetCount() > 20)
m_pTexturePool.RemoveTail();
if(FAILED(s->m_pD3DDev->CreateTexture(w, h, 1, 0, fmt, D3DPOOL_MANAGED, &pt->m_pTexture, NULL)))
return E_FAIL;
pt->m_pTexture->GetLevelDesc(0, &pt->m_desc);
m_pTexturePool.AddHead(pt->m_pTexture);
}
if(bpp == 8)
{
if(FAILED(s->m_pD3DDev->CreateTexture(256, 1, 1, 0, palfmt, D3DPOOL_MANAGED, &pt->m_pPalette, NULL)))
{
pt->m_pTexture = NULL;
return E_FAIL;
}
}
return S_OK;
}
bool GSTextureCache::IsTextureInCache(IDirect3DTexture9* pTexture)
{
POSITION pos = GetHeadPosition();
while(pos)
{
if(GetNext(pos)->m_pTexture == pTexture)
return true;
}
return false;
}
void GSTextureCache::RemoveOldTextures(GSState* s)
{
DWORD nBytes = 0;
POSITION pos = GetHeadPosition();
while(pos) nBytes += GetNext(pos)->m_nBytes;
pos = GetTailPosition();
while(pos && nBytes > 96*1024*1024/*s->m_ddcaps.dwVidMemTotal*/)
{
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 too many textures in cache (%d, %.2f MB)\n"), GetCount(), 1.0f*nBytes/1024/1024);
#endif
POSITION cur = pos;
GSTexture* pt = GetPrev(pos);
if(!pt->m_fRT)
{
nBytes -= pt->m_nBytes;
RemoveAt(cur);
delete pt;
}
}
}
static bool RectInRect(const RECT& inner, const RECT& outer)
{
return outer.left <= inner.left && inner.right <= outer.right
&& outer.top <= inner.top && inner.bottom <= outer.bottom;
}
static bool RectInRectH(const RECT& inner, const RECT& outer)
{
return outer.top <= inner.top && inner.bottom <= outer.bottom;
}
static bool RectInRectV(const RECT& inner, const RECT& outer)
{
return outer.left <= inner.left && inner.right <= outer.right;
}
bool GSTextureCache::GetDirtyRect(GSState* s, GSTexture* pt, CRect& r)
{
int w = 1 << pt->m_TEX0.TW;
int h = 1 << pt->m_TEX0.TH;
r.SetRect(0, 0, w, h);
// FIXME: kyo's left hand after being selected for player one (PS2-SNK_Vs_Capcom_SVC_Chaos_PAL_CDFull.iso)
// return true;
s->MinMaxUV(w, h, r);
CRect rcDirty = pt->m_rcDirty.GetDirtyRect(pt->m_TEX0);
CRect rcValid = pt->m_rcValid;
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 used %d,%d-%d,%d (%dx%d), valid %d,%d-%d,%d, dirty %d,%d-%d,%d\n"), r, w, h, rcValid, rcDirty);
#endif
if(RectInRect(r, rcValid))
{
if(rcDirty.IsRectEmpty()) return false;
else if(RectInRect(rcDirty, r)) r = rcDirty;
else if(RectInRect(rcDirty, rcValid)) r |= rcDirty;
else r = rcValid | rcDirty;
}
else
{
if(RectInRectH(r, rcValid) && (r.left >= rcValid.left || r.right <= rcValid.right))
{
r.top = rcValid.top;
r.bottom = rcValid.bottom;
if(r.left < rcValid.left) r.right = rcValid.left;
else /*if(r.right > rcValid.right)*/ r.left = rcValid.right;
}
else if(RectInRectV(r, rcValid) && (r.top >= rcValid.top || r.bottom <= rcValid.bottom))
{
r.left = rcValid.left;
r.right = rcValid.right;
if(r.top < rcValid.top) r.bottom = rcValid.top;
else /*if(r.bottom > rcValid.bottom)*/ r.top = rcValid.bottom;
}
else
{
r |= rcValid;
}
}
return true;
}
DWORD GSTextureCache::HashTexture(const CRect& r, int pitch, void* bits)
{
// TODO: make the hash more unique
BYTE* p = (BYTE*)bits;
DWORD hash = r.left + r.right + r.top + r.bottom + pitch + *(BYTE*)bits;
if(r.Width() > 0)
{
int size = r.Width()*r.Height();
/*
if(size <= 8*8) return rand(); // :P
else
*/
if(size <= 16*16) hash += hash_crc(r, pitch, p);
else if(size <= 32*32) hash += hash_adler(r, pitch, p);
else hash += hash_checksum(r, pitch, p);
}
return hash;
}
HRESULT GSTextureCache::UpdateTexture(GSState* s, GSTexture* pt, GSLocalMemory::readTexture rt)
{
CRect r;
if(!GetDirtyRect(s, pt, r))
return S_OK;
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 updating texture %d,%d-%d,%d (%dx%d)\n"), r.left, r.top, r.right, r.bottom, 1 << pt->m_TEX0.TW, 1 << pt->m_TEX0.TH);
#endif
int bpp = 0;
switch(pt->m_desc.Format)
{
case D3DFMT_A8R8G8B8: bpp = 32; break;
case D3DFMT_X8R8G8B8: bpp = 32; break;
case D3DFMT_A1R5G5B5: bpp = 16; break;
case D3DFMT_L8: bpp = 8; break;
default: ASSERT(0); return E_FAIL;
}
D3DLOCKED_RECT lr;
if(FAILED(pt->m_pTexture->LockRect(0, &lr, &r, D3DLOCK_NO_DIRTY_UPDATE))) {ASSERT(0); return E_FAIL;}
(s->m_lm.*rt)(r, (BYTE*)lr.pBits, lr.Pitch, s->m_ctxt->TEX0, s->m_de.TEXA, s->m_ctxt->CLAMP);
s->m_perfmon.IncCounter(GSPerfMon::c_unswizzle, r.Width()*r.Height()*bpp>>3);
pt->m_pTexture->UnlockRect(0);
pt->m_rcValid |= r;
pt->m_rcDirty.RemoveAll();
const static DWORD limit = 7;
if((pt->m_nHashDiff & limit) && pt->m_nHashDiff >= limit && pt->m_rcHash == pt->m_rcValid) // predicted to be dirty
{
pt->m_nHashDiff++;
}
else
{
if(FAILED(pt->m_pTexture->LockRect(0, &lr, &pt->m_rcValid, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_READONLY))) {ASSERT(0); return E_FAIL;}
DWORD dwHash = HashTexture(
CRect((pt->m_rcValid.left>>2)*(bpp>>3), pt->m_rcValid.top, (pt->m_rcValid.right>>2)*(bpp>>3), pt->m_rcValid.bottom),
lr.Pitch, lr.pBits);
pt->m_pTexture->UnlockRect(0);
if(pt->m_rcHash != pt->m_rcValid)
{
pt->m_nHashDiff = 0;
pt->m_nHashSame = 0;
pt->m_rcHash = pt->m_rcValid;
pt->m_dwHash = dwHash;
}
else
{
if(pt->m_dwHash != dwHash)
{
pt->m_nHashDiff++;
pt->m_nHashSame = 0;
pt->m_dwHash = dwHash;
}
else
{
if(pt->m_nHashDiff < limit) r.SetRect(0, 0, 1, 1);
// else pt->m_dwHash is not reliable, must update
pt->m_nHashDiff = 0;
pt->m_nHashSame++;
}
}
}
pt->m_pTexture->AddDirtyRect(&r);
pt->m_pTexture->PreLoad();
s->m_perfmon.IncCounter(GSPerfMon::c_texture, r.Width()*r.Height()*bpp>>3);
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 texture was updated, valid %d,%dx%d,%d\n"), pt->m_rcValid);
#endif
#ifdef DEBUG_SAVETEXTURES
if(s->m_ctxt->FRAME.Block() == 0x00000 && pt->m_TEX0.TBP0 == 0x02800)
{
CString fn;
fn.Format(_T("c:\\%08I64x_%I64d_%I64d_%I64d_%I64d_%I64d_%I64d_%I64d-%I64d_%I64d-%I64d.bmp"),
pt->m_TEX0.TBP0, pt->m_TEX0.PSM, pt->m_TEX0.TBW,
pt->m_TEX0.TW, pt->m_TEX0.TH,
pt->m_CLAMP.WMS, pt->m_CLAMP.WMT, pt->m_CLAMP.MINU, pt->m_CLAMP.MAXU, pt->m_CLAMP.MINV, pt->m_CLAMP.MAXV);
D3DXSaveTextureToFile(fn, D3DXIFF_BMP, pt->m_pTexture, NULL);
}
#endif
return S_OK;
}
GSTexture* GSTextureCache::ConvertRTPitch(GSState* s, GSTexture* pt)
{
if(pt->m_TEX0.TBW == s->m_ctxt->TEX0.TBW)
return pt;
// sfex3 uses this trick (bw: 10 -> 5, wraps the right side below the left)
ASSERT(pt->m_TEX0.TBW > s->m_ctxt->TEX0.TBW); // otherwise scale.x need to be reduced to make the larger texture fit (TODO)
int bw = 64;
int bh = s->m_ctxt->TEX0.PSM == PSM_PSMCT32 || s->m_ctxt->TEX0.PSM == PSM_PSMCT24 ? 32 : 64;
int sw = pt->m_TEX0.TBW << 6;
int dw = s->m_ctxt->TEX0.TBW << 6;
int dh = 1 << s->m_ctxt->TEX0.TH;
// TRACE(_T("ConvertRT: %05x %x %d -> %d\n"), (DWORD)s->m_ctxt->TEX0.TBP0, (DWORD)s->m_ctxt->TEX0.PSM, (DWORD)pt->m_TEX0.TBW, (DWORD)s->m_ctxt->TEX0.TBW);
HRESULT hr;
/*
if(s->m_perfmon.GetFrame() > 400)
hr = D3DXSaveTextureToFile(_T("g:/1.bmp"), D3DXIFF_BMP, pt->m_pTexture, NULL);
*/
D3DSURFACE_DESC desc;
hr = pt->m_pTexture->GetLevelDesc(0, &desc);
if(FAILED(hr)) return NULL;
CComPtr<IDirect3DTexture9> pRT;
if(FAILED(hr = CreateRT(s, desc.Width, desc.Height, &pRT)))
return NULL;
CComPtr<IDirect3DSurface9> pSrc, pDst;
hr = pRT->GetSurfaceLevel(0, &pSrc);
if(FAILED(hr)) return NULL;
hr = pt->m_pTexture->GetSurfaceLevel(0, &pDst);
if(FAILED(hr)) return NULL;
hr = s->m_pD3DDev->StretchRect(pDst, NULL, pSrc, NULL, D3DTEXF_POINT);
if(FAILED(hr)) return NULL;
scale_t scale(pt->m_pTexture);
for(int dy = 0; dy < dh; dy += bh)
{
for(int dx = 0; dx < dw; dx += bw)
{
int o = dy * dw / bh + dx;
int sx = o % sw;
int sy = o / sw;
// TRACE(_T("%d,%d - %d,%d <= %d,%d - %d,%d\n"), dx, dy, dx + bw, dy + bh, sx, sy, sx + bw, sy + bh);
CRect src, dst;
src.left = (LONG)(scale.x * sx + 0.5f);
src.top = (LONG)(scale.y * sy + 0.5f);
src.right = (LONG)(scale.x * (sx + bw) + 0.5f);
src.bottom = (LONG)(scale.y * (sy + bh) + 0.5f);
dst.left = (LONG)(scale.x * dx + 0.5f);
dst.top = (LONG)(scale.y * dy + 0.5f);
dst.right = (LONG)(scale.x * (dx + bw) + 0.5f);
dst.bottom = (LONG)(scale.y * (dy + bh) + 0.5f);
hr = s->m_pD3DDev->StretchRect(pSrc, src, pDst, dst, D3DTEXF_POINT);
// TODO: this is quite a lot of StretchRect call, do it with one DrawPrimUP
}
}
pt->m_TEX0.TW = s->m_ctxt->TEX0.TW;
pt->m_TEX0.TBW = s->m_ctxt->TEX0.TBW;
/*
if(s->m_perfmon.GetFrame() > 400)
hr = D3DXSaveTextureToFile(_T("g:/2.bmp"), D3DXIFF_BMP, pt->m_pTexture, NULL);
*/
return pt;
}
GSTexture* GSTextureCache::ConvertRTWidthHeight(GSState* s, GSTexture* pt)
{
int tw = pt->m_scale.x * (1 << s->m_ctxt->TEX0.TW);
int th = pt->m_scale.y * (1 << s->m_ctxt->TEX0.TH);
int rw = pt->m_desc.Width;
int rh = pt->m_desc.Height;
if(tw != rw || th != rh)
//if(tw < rw && th <= rh || tw <= rw && th < rh)
{
GSTexture* pt2 = new GSTexture();
pt2->m_pPalette = pt->m_pPalette;
pt2->m_fRT = pt->m_pPalette == NULL;
pt2->m_scale = pt->m_scale;
pt2->m_fTemp = true;
POSITION pos = pt->m_pSubTextures.GetHeadPosition();
while(pos)
{
IDirect3DTexture9* pTexture = pt->m_pSubTextures.GetNext(pos);
pTexture->GetLevelDesc(0, &pt2->m_desc);
scale_t scale(pTexture);
if(pt2->m_desc.Width == tw && pt2->m_desc.Height == th
&& pt2->m_scale.x == scale.x && pt2->m_scale.y == scale.y)
{
pt2->m_pTexture = pTexture;
break;
}
}
if(!pt2->m_pTexture)
{
CRect dst(0, 0, tw, th);
if(tw > rw)
{
pt2->m_scale.x = pt2->m_scale.x * rw / tw;
dst.right = rw * rw / tw;
tw = rw;
}
if(th > rh)
{
pt2->m_scale.y = pt2->m_scale.y * rh / th;
dst.bottom = rh * rh / th;
th = rh;
}
CRect src(0, 0, tw, th);
HRESULT hr;
if(FAILED(hr = CreateRT(s, tw, th, &pt2->m_pTexture)) || FAILED(hr = pt2->m_pTexture->GetLevelDesc(0, &pt2->m_desc)))
{
delete pt2;
return false;
}
CComPtr<IDirect3DSurface9> pSrc, pDst;
hr = pt->m_pTexture->GetSurfaceLevel(0, &pSrc);
hr = pt2->m_pTexture->GetSurfaceLevel(0, &pDst);
ASSERT(pSrc);
ASSERT(pDst);
hr = s->m_pD3DDev->StretchRect(pSrc, src, pDst, dst, src == dst ? D3DTEXF_POINT : D3DTEXF_LINEAR);
pt2->m_scale.Set(pt2->m_pTexture);
pt->m_pSubTextures.AddTail(pt2->m_pTexture);
}
pt = pt2;
}
return pt;
}
HRESULT GSTextureCache::CreateRT(GSState* s, int w, int h, IDirect3DTexture9** ppRT)
{
ASSERT(ppRT && *ppRT == NULL);
HRESULT hr;
POSITION pos = m_pRTPool.GetHeadPosition();
while(pos)
{
IDirect3DTexture9* pRT = m_pRTPool.GetNext(pos);
D3DSURFACE_DESC desc;
pRT->GetLevelDesc(0, &desc);
if(desc.Width == w && desc.Height == h)
{
(*ppRT = pRT)->AddRef();
return S_OK;
}
}
hr = s->m_pD3DDev->CreateTexture(w, h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, ppRT, NULL);
if(FAILED(hr)) return hr;
/**/
m_pRTPool.AddHead(*ppRT);
while(m_pRTPool.GetCount() > 3) m_pRTPool.RemoveTail();
return S_OK;
}
GSTexture* GSTextureCache::ConvertRT(GSState* s, GSTexture* pt)
{
ASSERT(pt->m_fRT);
// FIXME: RT + 8h,4hl,4hh
if(s->m_ctxt->TEX0.PSM == PSM_PSMT8H)
{
if(!pt->m_pPalette)
{
if(FAILED(s->m_pD3DDev->CreateTexture(256, 1, 1, 0, s->m_ctxt->TEX0.CPSM == PSM_PSMCT32 ? D3DFMT_A8R8G8B8 : D3DFMT_A1R5G5B5, D3DPOOL_MANAGED, &pt->m_pPalette, NULL)))
return NULL;
}
}
else if(GSLocalMemory::m_psmtbl[s->m_ctxt->TEX0.PSM].pal)
{
return NULL;
}
pt = ConvertRTPitch(s, pt);
pt = ConvertRTWidthHeight(s, pt);
return pt;
}
bool GSTextureCache::Fetch(GSState* s, GSTextureBase& t)
{
GSTexture* pt = NULL;
int nPaletteEntries = GSLocalMemory::m_psmtbl[s->m_ctxt->TEX0.PSM].pal;
DWORD clut[256];
if(nPaletteEntries)
{
s->m_lm.SetupCLUT32(s->m_ctxt->TEX0, s->m_de.TEXA);
s->m_lm.CopyCLUT32(clut, nPaletteEntries);
}
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 Fetch %dx%d %05I64x %I64d (%d)\n"),
1 << s->m_ctxt->TEX0.TW, 1 << s->m_ctxt->TEX0.TH,
s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM, nPaletteEntries);
#endif
enum lookupresult {notfound, needsupdate, found} lr = notfound;
POSITION pos = GetHeadPosition();
while(pos && !pt)
{
POSITION cur = pos;
pt = GetNext(pos);
if(HasSharedBits(pt->m_TEX0.TBP0, pt->m_TEX0.PSM, s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM))
{
if(pt->m_fRT)
{
lr = found;
if(!(pt = ConvertRT(s, pt)))
return false;
}
else if(s->m_ctxt->TEX0.PSM == pt->m_TEX0.PSM && pt->m_TEX0.TBW == s->m_ctxt->TEX0.TBW
&& s->m_ctxt->TEX0.TW == pt->m_TEX0.TW && s->m_ctxt->TEX0.TH == pt->m_TEX0.TH
&& (!(s->m_ctxt->CLAMP.WMS&2) && !(pt->m_CLAMP.WMS&2) && !(s->m_ctxt->CLAMP.WMT&2) && !(pt->m_CLAMP.WMT&2) || s->m_ctxt->CLAMP.i64 == pt->m_CLAMP.i64)
&& s->m_de.TEXA.TA0 == pt->m_TEXA.TA0 && s->m_de.TEXA.TA1 == pt->m_TEXA.TA1 && s->m_de.TEXA.AEM == pt->m_TEXA.AEM
&& (!nPaletteEntries || s->m_ctxt->TEX0.CPSM == pt->m_TEX0.CPSM && !memcmp(pt->m_clut, clut, nPaletteEntries*sizeof(clut[0]))))
{
lr = needsupdate;
}
}
if(lr != notfound) {MoveToHead(cur); break;}
pt = NULL;
}
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 lr = %s\n"), lr == found ? _T("found") : lr == needsupdate ? _T("needsupdate") : _T("notfound"));
#endif
if(lr == notfound)
{
pt = new GSTexture();
pt->m_TEX0 = s->m_ctxt->TEX0;
pt->m_CLAMP = s->m_ctxt->CLAMP;
pt->m_TEXA = s->m_de.TEXA;
if(!SUCCEEDED(CreateTexture(s, pt, PSM_PSMCT32)))
{
delete pt;
return false;
}
RemoveOldTextures(s);
AddHead(pt);
lr = needsupdate;
}
ASSERT(pt);
if(pt && nPaletteEntries)
{
memcpy(pt->m_clut, clut, nPaletteEntries*sizeof(clut[0]));
}
if(lr == needsupdate)
{
UpdateTexture(s, pt, &GSLocalMemory::ReadTexture);
lr = found;
}
if(lr == found)
{
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 texture was found, age %d -> 0\n"), pt->m_nAge);
#endif
pt->m_nAge = 0;
t = *pt;
if(pt->m_fTemp) delete pt;
return true;
}
return false;
}
bool GSTextureCache::FetchP(GSState* s, GSTextureBase& t)
{
GSTexture* pt = NULL;
int nPaletteEntries = GSLocalMemory::m_psmtbl[s->m_ctxt->TEX0.PSM].pal;
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 Fetch %dx%d %05I64x %I64d (%d)\n"),
1 << s->m_ctxt->TEX0.TW, 1 << s->m_ctxt->TEX0.TH,
s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM, nPaletteEntries);
#endif
enum lookupresult {notfound, needsupdate, found} lr = notfound;
POSITION pos = GetHeadPosition();
while(pos && !pt)
{
POSITION cur = pos;
pt = GetNext(pos);
if(HasSharedBits(pt->m_TEX0.TBP0, pt->m_TEX0.PSM, s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM))
{
if(pt->m_fRT)
{
lr = found;
if(!(pt = ConvertRT(s, pt)))
return false;
}
else if(s->m_ctxt->TEX0.PSM == pt->m_TEX0.PSM && pt->m_TEX0.TBW == s->m_ctxt->TEX0.TBW
&& s->m_ctxt->TEX0.TW == pt->m_TEX0.TW && s->m_ctxt->TEX0.TH == pt->m_TEX0.TH
&& (!(s->m_ctxt->CLAMP.WMS&2) && !(pt->m_CLAMP.WMS&2) && !(s->m_ctxt->CLAMP.WMT&2) && !(pt->m_CLAMP.WMT&2) || s->m_ctxt->CLAMP.i64 == pt->m_CLAMP.i64))
{
lr = needsupdate;
}
}
if(lr != notfound) {MoveToHead(cur); break;}
pt = NULL;
}
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 lr = %s\n"), lr == found ? _T("found") : lr == needsupdate ? _T("needsupdate") : _T("notfound"));
#endif
if(lr == notfound)
{
pt = new GSTexture();
pt->m_TEX0 = s->m_ctxt->TEX0;
pt->m_CLAMP = s->m_ctxt->CLAMP;
// pt->m_TEXA = s->m_de.TEXA;
if(!SUCCEEDED(CreateTexture(s, pt, s->m_ctxt->TEX0.PSM, PSM_PSMCT32)))
{
delete pt;
return false;
}
RemoveOldTextures(s);
AddHead(pt);
lr = needsupdate;
}
ASSERT(pt);
if(pt && pt->m_pPalette)
{
D3DLOCKED_RECT r;
if(FAILED(pt->m_pPalette->LockRect(0, &r, NULL, 0)))
return false;
s->m_lm.ReadCLUT32(s->m_ctxt->TEX0, s->m_de.TEXA, (DWORD*)r.pBits);
pt->m_pPalette->UnlockRect(0);
s->m_perfmon.IncCounter(GSPerfMon::c_texture, 256*4);
}
if(lr == needsupdate)
{
UpdateTexture(s, pt, &GSLocalMemory::ReadTextureP);
lr = found;
}
if(lr == found)
{
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 texture was found, age %d -> 0\n"), pt->m_nAge);
#endif
pt->m_nAge = 0;
t = *pt;
if(pt->m_fTemp) delete pt;
return true;
}
return false;
}
bool GSTextureCache::FetchNP(GSState* s, GSTextureBase& t)
{
GSTexture* pt = NULL;
int nPaletteEntries = GSLocalMemory::m_psmtbl[s->m_ctxt->TEX0.PSM].pal;
DWORD clut[256];
if(nPaletteEntries)
{
s->m_lm.SetupCLUT(s->m_ctxt->TEX0, s->m_de.TEXA);
s->m_lm.CopyCLUT32(clut, nPaletteEntries);
}
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 Fetch %dx%d %05I64x %I64d (%d)\n"),
1 << s->m_ctxt->TEX0.TW, 1 << s->m_ctxt->TEX0.TH,
s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM, nPaletteEntries);
#endif
enum lookupresult {notfound, needsupdate, found} lr = notfound;
POSITION pos = GetHeadPosition();
while(pos && !pt)
{
POSITION cur = pos;
pt = GetNext(pos);
if(HasSharedBits(pt->m_TEX0.TBP0, pt->m_TEX0.PSM, s->m_ctxt->TEX0.TBP0, s->m_ctxt->TEX0.PSM))
{
if(pt->m_fRT)
{
lr = found;
if(!(pt = ConvertRT(s, pt)))
return false;
}
else if(s->m_ctxt->TEX0.PSM == pt->m_TEX0.PSM && pt->m_TEX0.TBW == s->m_ctxt->TEX0.TBW
&& s->m_ctxt->TEX0.TW == pt->m_TEX0.TW && s->m_ctxt->TEX0.TH == pt->m_TEX0.TH
&& (!(s->m_ctxt->CLAMP.WMS&2) && !(pt->m_CLAMP.WMS&2) && !(s->m_ctxt->CLAMP.WMT&2) && !(pt->m_CLAMP.WMT&2) || s->m_ctxt->CLAMP.i64 == pt->m_CLAMP.i64)
// && s->m_de.TEXA.TA0 == pt->m_TEXA.TA0 && s->m_de.TEXA.TA1 == pt->m_TEXA.TA1 && s->m_de.TEXA.AEM == pt->m_TEXA.AEM
&& (!nPaletteEntries || s->m_ctxt->TEX0.CPSM == pt->m_TEX0.CPSM && !memcmp(pt->m_clut, clut, nPaletteEntries*sizeof(clut[0]))))
{
lr = needsupdate;
}
}
if(lr != notfound) {MoveToHead(cur); break;}
pt = NULL;
}
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 lr = %s\n"), lr == found ? _T("found") : lr == needsupdate ? _T("needsupdate") : _T("notfound"));
#endif
if(lr == notfound)
{
pt = new GSTexture();
pt->m_TEX0 = s->m_ctxt->TEX0;
pt->m_CLAMP = s->m_ctxt->CLAMP;
// pt->m_TEXA = s->m_de.TEXA;
DWORD psm = s->m_ctxt->TEX0.PSM;
switch(psm)
{
case PSM_PSMT8:
case PSM_PSMT8H:
case PSM_PSMT4:
case PSM_PSMT4HL:
case PSM_PSMT4HH:
psm = s->m_ctxt->TEX0.CPSM;
break;
}
if(!SUCCEEDED(CreateTexture(s, pt, psm)))
{
delete pt;
return false;
}
RemoveOldTextures(s);
AddHead(pt);
lr = needsupdate;
}
ASSERT(pt);
if(pt && nPaletteEntries)
{
memcpy(pt->m_clut, clut, nPaletteEntries*sizeof(clut[0]));
}
if(lr == needsupdate)
{
UpdateTexture(s, pt, &GSLocalMemory::ReadTextureNP);
lr = found;
}
if(lr == found)
{
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 texture was found, age %d -> 0\n"), pt->m_nAge);
#endif
pt->m_nAge = 0;
t = *pt;
if(pt->m_fTemp) delete pt;
return true;
}
return false;
}
void GSTextureCache::IncAge(CSurfMap<IDirect3DTexture9>& pRTs)
{
POSITION pos = GetHeadPosition();
while(pos)
{
POSITION cur = pos;
GSTexture* pt = GetNext(pos);
pt->m_nAge++;
pt->m_nVsyncs++;
if(pt->m_nAge > 10 && (!pt->m_fRT || pRTs.GetCount() > 3))
{
pRTs.RemoveKey(pt->m_TEX0.TBP0);
RemoveAt(cur);
delete pt;
}
}
}
void GSTextureCache::ResetAge(DWORD TBP0)
{
POSITION pos = GetHeadPosition();
while(pos)
{
GSTexture* pt = GetNext(pos);
if(pt->m_TEX0.TBP0 == TBP0) pt->m_nAge = 0;
}
}
void GSTextureCache::RemoveAll()
{
while(GetCount()) delete RemoveHead();
m_pTexturePool.RemoveAll();
m_pRTPool.RemoveAll();
}
void GSTextureCache::InvalidateTexture(GSState* s, const GIFRegBITBLTBUF& BITBLTBUF, const CRect& r)
{
GIFRegTEX0 TEX0;
TEX0.TBP0 = BITBLTBUF.DBP;
TEX0.TBW = BITBLTBUF.DBW;
TEX0.PSM = BITBLTBUF.DPSM;
TEX0.TCC = 0;
#ifdef DEBUG_LOG
s->LOG(_T("*TC2 invalidate %05x %x (%d,%d-%d,%d)\n"), TEX0.TBP0, TEX0.PSM, r.left, r.top, r.right, r.bottom);
#endif
POSITION pos = GetHeadPosition();
while(pos)
{
POSITION cur = pos;
GSTexture* pt = GetNext(pos);
if(HasSharedBits(TEX0.TBP0, TEX0.PSM, pt->m_TEX0.TBP0, pt->m_TEX0.PSM))
{
if(TEX0.TBW != pt->m_TEX0.TBW)
{
// if TEX0.TBW != pt->m_TEX0.TBW then this render target is more likely to
// be discarded by the game (means it doesn't want to transfer an image over
// another pre-rendered image) and can be refetched from local mem safely.
RemoveAt(cur);
delete pt;
}
else if(pt->m_fRT)
{
// TEX0.TBW = pt->m_TEX0.TBW;
TEX0.PSM = pt->m_TEX0.PSM;
if(TEX0.PSM == PSM_PSMCT32 || TEX0.PSM == PSM_PSMCT24
|| TEX0.PSM == PSM_PSMCT16 || TEX0.PSM == PSM_PSMCT16S)
{
// pt->m_rcDirty.AddHead(GSDirtyRect(PSM, r));
HRESULT hr;
int tw = (r.Width() + 3) & ~3;
int th = r.Height();
CComPtr<IDirect3DTexture9> pSrc;
hr = s->m_pD3DDev->CreateTexture(tw, th, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &pSrc, NULL);
D3DLOCKED_RECT lr;
if(pSrc && SUCCEEDED(pSrc->LockRect(0, &lr, NULL, 0)))
{
GIFRegTEXA TEXA;
TEXA.AEM = 1;
TEXA.TA0 = 0;
TEXA.TA1 = 0x80;
GIFRegCLAMP CLAMP;
CLAMP.WMS = 0;
CLAMP.WMT = 0;
s->m_lm.ReadTexture(r, (BYTE*)lr.pBits, lr.Pitch, TEX0, TEXA, CLAMP);
s->m_perfmon.IncCounter(GSPerfMon::c_unswizzle, r.Width()*r.Height()*4);
pSrc->UnlockRect(0);
scale_t scale(pt->m_pTexture);
CRect dst;
dst.left = (long)(scale.x * r.left + 0.5);
dst.top = (long)(scale.y * r.top + 0.5);
dst.right = (long)(scale.x * r.right + 0.5);
dst.bottom = (long)(scale.y * r.bottom + 0.5);
//
CComPtr<IDirect3DSurface9> pRTSurf;
hr = pt->m_pTexture->GetSurfaceLevel(0, &pRTSurf);
hr = s->m_pD3DDev->SetRenderTarget(0, pRTSurf);
hr = s->m_pD3DDev->SetDepthStencilSurface(NULL);
hr = s->m_pD3DDev->SetTexture(0, pSrc);
hr = s->m_pD3DDev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
hr = s->m_pD3DDev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
hr = s->m_pD3DDev->SetRenderState(D3DRS_ZENABLE, FALSE);
hr = s->m_pD3DDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
hr = s->m_pD3DDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
hr = s->m_pD3DDev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
hr = s->m_pD3DDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
hr = s->m_pD3DDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
hr = s->m_pD3DDev->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
hr = s->m_pD3DDev->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RGBA);
hr = s->m_pD3DDev->SetPixelShader(NULL);
struct
{
float x, y, z, rhw;
float tu, tv;
}
pVertices[] =
{
{(float)dst.left, (float)dst.top, 0.5f, 2.0f, 0, 0},
{(float)dst.right, (float)dst.top, 0.5f, 2.0f, 1.0f * r.Width() / tw, 0},
{(float)dst.left, (float)dst.bottom, 0.5f, 2.0f, 0, 1},
{(float)dst.right, (float)dst.bottom, 0.5f, 2.0f, 1.0f * r.Width() / tw, 1},
};
hr = s->m_pD3DDev->BeginScene();
hr = s->m_pD3DDev->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
hr = s->m_pD3DDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, pVertices, sizeof(pVertices[0]));
hr = s->m_pD3DDev->EndScene();
}
}
else
{
RemoveAt(cur);
delete pt;
}
}
else
{
pt->m_rcDirty.AddHead(GSDirtyRect(TEX0.PSM, r));
}
}
}
}
void GSTextureCache::InvalidateLocalMem(GSState* s, DWORD TBP0, DWORD BW, DWORD PSM, const CRect& r)
{
CComPtr<IDirect3DTexture9> pRT;
POSITION pos = GetHeadPosition();
while(pos)
{
POSITION cur = pos;
GSTexture* pt = GetNext(pos);
if(pt->m_TEX0.TBP0 == TBP0 && pt->m_fRT)
{
pRT = pt->m_pTexture;
break;
}
}
if(!pRT) return;
// TODO: add resizing
/*
HRESULT hr;
D3DSURFACE_DESC desc;
hr = pRT->GetLevelDesc(0, &desc);
if(FAILED(hr)) return;
CComPtr<IDirect3DSurface9> pVidMem;
hr = pRT->GetSurfaceLevel(0, &pVidMem);
if(FAILED(hr)) return;
CComPtr<IDirect3DSurface9> pSysMem;
hr = s->m_pD3DDev->CreateOffscreenPlainSurface(desc.Width, desc.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pSysMem, NULL);
if(FAILED(hr)) return;
hr = s->m_pD3DDev->GetRenderTargetData(pVidMem, pSysMem);
if(FAILED(hr)) return;
D3DLOCKED_RECT lr;
hr = pSysMem->LockRect(&lr, &r, D3DLOCK_READONLY|D3DLOCK_NO_DIRTY_UPDATE);
if(SUCCEEDED(hr))
{
BYTE* p = (BYTE*)lr.pBits;
if(0 && r.left == 0 && r.top == 0 && PSM == PSM_PSMCT32)
{
}
else
{
GSLocalMemory::writeFrame wf = s->m_lm.GetWriteFrame(PSM);
for(int y = r.top; y < r.bottom; y++, p += lr.Pitch)
{
for(int x = r.left; x < r.right; x++)
{
(s->m_lm.*wf)(x, y, ((DWORD*)p)[x], TBP0, BW);
}
}
}
pSysMem->UnlockRect();
}
*/
}
void GSTextureCache::AddRT(GIFRegTEX0& TEX0, IDirect3DTexture9* pRT, scale_t scale)
{
POSITION pos = GetHeadPosition();
while(pos)
{
POSITION cur = pos;
GSTexture* pt = GetNext(pos);
if(HasSharedBits(TEX0.TBP0, TEX0.PSM, pt->m_TEX0.TBP0, pt->m_TEX0.PSM))
{
RemoveAt(cur);
delete pt;
}
}
GSTexture* pt = new GSTexture();
pt->m_TEX0 = TEX0;
pt->m_pTexture = pRT;
pt->m_pTexture->GetLevelDesc(0, &pt->m_desc);
pt->m_scale = scale;
pt->m_fRT = true;
AddHead(pt);
}