/* * Copyright (C) 2007-2009 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 * */ #pragma once #include "GSRenderer.h" template class GSTextureCache { typedef typename Device::Texture Texture; public: class GSSurface { protected: GSRenderer* m_renderer; typedef typename Device::Texture Texture; public: Texture m_texture; Texture m_palette; bool m_initpalette; int m_age; GSDirtyRectList m_dirty; GIFRegTEX0 m_TEX0; explicit GSSurface(GSRenderer* renderer) : m_renderer(renderer) , m_age(0) , m_initpalette(false) { m_TEX0.TBP0 = (UINT32)~0; } virtual ~GSSurface() { m_renderer->m_dev.Recycle(m_texture); m_renderer->m_dev.Recycle(m_palette); } virtual void Update() { m_age = 0; } }; class GSRenderTarget : public GSSurface { public: bool m_used; explicit GSRenderTarget(GSRenderer* renderer) : GSSurface(renderer) , m_used(true) { } virtual bool Create(int w, int h) { // FIXME: initial data should be unswizzled from local mem in Update() if dirty return m_renderer->m_dev.CreateRenderTarget(m_texture, w, h); } virtual void Read(CRect r) = 0; }; class GSDepthStencil : public GSSurface { public: bool m_used; explicit GSDepthStencil(GSRenderer* renderer) : GSSurface(renderer) , m_used(false) { } virtual bool Create(int w, int h) { // FIXME: initial data should be unswizzled from local mem in Update() if dirty return m_renderer->m_dev.CreateDepthStencil(m_texture, w, h); } }; class GSTexture : public GSSurface { protected: bool GetDirtyRect(CRect& rr) { int w = 1 << m_TEX0.TW; int h = 1 << m_TEX0.TH; CRect r(0, 0, w, h); POSITION pos = m_dirty.GetHeadPosition(); while(pos) { const CRect& dirty = m_dirty.GetNext(pos).GetDirtyRect(m_TEX0) & r; if(!(m_valid & dirty).IsRectEmpty()) { // find the rect having the largest area, outside dirty, inside m_valid CRect left(m_valid.left, m_valid.top, min(m_valid.right, dirty.left), m_valid.bottom); CRect top(m_valid.left, m_valid.top, m_valid.right, min(m_valid.bottom, dirty.top)); CRect right(max(m_valid.left, dirty.right), m_valid.top, m_valid.right, m_valid.bottom); CRect bottom(m_valid.left, max(m_valid.top, dirty.bottom), m_valid.right, m_valid.bottom); int leftsize = !left.IsRectEmpty() ? left.Width() * left.Height() : 0; int topsize = !top.IsRectEmpty() ? top.Width() * top.Height() : 0; int rightsize = !right.IsRectEmpty() ? right.Width() * right.Height() : 0; int bottomsize = !bottom.IsRectEmpty() ? bottom.Width() * bottom.Height() : 0; // TODO: sort m_valid = leftsize > 0 ? left : topsize > 0 ? top : rightsize > 0 ? right : bottomsize > 0 ? bottom : CRect(0, 0, 0, 0); } } m_dirty.RemoveAll(); m_renderer->MinMaxUV(w, h, r); if(GSUtil::IsRectInRect(r, m_valid)) { return false; } else if(GSUtil::IsRectInRectH(r, m_valid) && (r.left >= m_valid.left || r.right <= m_valid.right)) { r.top = m_valid.top; r.bottom = m_valid.bottom; if(r.left < m_valid.left) r.right = m_valid.left; else r.left = m_valid.right; // if(r.right > m_valid.right) } else if(GSUtil::IsRectInRectV(r, m_valid) && (r.top >= m_valid.top || r.bottom <= m_valid.bottom)) { r.left = m_valid.left; r.right = m_valid.right; if(r.top < m_valid.top) r.bottom = m_valid.top; else r.top = m_valid.bottom; // if(r.bottom > m_valid.bottom) } else { r |= m_valid; } if(r.IsRectEmpty()) { return false; } rr = r; return true; } public: GIFRegCLAMP m_CLAMP; DWORD* m_clut; // * CRect m_valid; int m_bpp; int m_bpp2; bool m_rendered; explicit GSTexture(GSRenderer* renderer) : GSSurface(renderer) , m_valid(0, 0, 0, 0) , m_bpp(0) , m_bpp2(0) , m_rendered(false) { m_clut = (DWORD*)_aligned_malloc(256 * sizeof(DWORD), 16); memset(m_clut, 0, sizeof(m_clut)); } ~GSTexture() { _aligned_free(m_clut); } virtual bool Create() = 0; virtual bool Create(GSRenderTarget* rt) = 0; virtual bool Create(GSDepthStencil* ds) = 0; }; protected: GSRenderer* m_renderer; CAtlList m_rt; CAtlList m_ds; CAtlList m_tex; template void RecycleByAge(CAtlList& l, int maxage = 60) { POSITION pos = l.GetHeadPosition(); while(pos) { POSITION cur = pos; T* t = l.GetNext(pos); if(++t->m_age > maxage) { l.RemoveAt(cur); delete t; } } } virtual GSRenderTarget* CreateRenderTarget() = 0; virtual GSDepthStencil* CreateDepthStencil() = 0; virtual GSTexture* CreateTexture() = 0; public: GSTextureCache(GSRenderer* renderer) : m_renderer(renderer) { } virtual ~GSTextureCache() { RemoveAll(); } void RemoveAll() { while(m_rt.GetCount()) delete m_rt.RemoveHead(); while(m_ds.GetCount()) delete m_ds.RemoveHead(); while(m_tex.GetCount()) delete m_tex.RemoveHead(); } GSRenderTarget* GetRenderTarget(const GIFRegTEX0& TEX0, int w, int h, bool fb = false) { GSRenderTarget* rt = NULL; if(rt == NULL) { for(POSITION pos = m_rt.GetHeadPosition(); pos; m_rt.GetNext(pos)) { GSRenderTarget* rt2 = m_rt.GetAt(pos); if(rt2->m_TEX0.TBP0 == TEX0.TBP0) { m_rt.MoveToHead(pos); rt = rt2; if(!fb) rt->m_TEX0 = TEX0; rt->Update(); break; } } } if(rt == NULL && fb) { // HACK: try to find something close to the base pointer for(POSITION pos = m_rt.GetHeadPosition(); pos; m_rt.GetNext(pos)) { GSRenderTarget* rt2 = m_rt.GetAt(pos); if(rt2->m_TEX0.TBP0 <= TEX0.TBP0 && TEX0.TBP0 < rt2->m_TEX0.TBP0 + 0x700 && (!rt || rt2->m_TEX0.TBP0 >= rt->m_TEX0.TBP0)) { rt = rt2; } } if(rt) { rt->Update(); } } if(rt == NULL) { rt = CreateRenderTarget(); rt->m_TEX0 = TEX0; if(!rt->Create(w, h)) { delete rt; return NULL; } m_rt.AddHead(rt); } if(m_renderer->CanUpscale()) { int ww = (int)(m_renderer->GetFramePos().cx + rt->m_TEX0.TBW * 64); int hh = (int)(m_renderer->GetFramePos().cy + m_renderer->GetDisplaySize().cy); if(hh <= m_renderer->GetDeviceSize().cy / 2) { hh *= 2; } if(ww > 0 && hh > 0) { rt->m_texture.m_scale.x = (float)w / ww; rt->m_texture.m_scale.y = (float)h / hh; } } if(!fb) { rt->m_used = true; } return rt; } GSDepthStencil* GetDepthStencil(const GIFRegTEX0& TEX0, int w, int h) { GSDepthStencil* ds = NULL; if(ds == NULL) { for(POSITION pos = m_ds.GetHeadPosition(); pos; m_ds.GetNext(pos)) { GSDepthStencil* ds2 = m_ds.GetAt(pos); if(ds2->m_TEX0.TBP0 == TEX0.TBP0) { m_ds.MoveToHead(pos); ds = ds2; ds->m_TEX0 = TEX0; ds->Update(); break; } } } if(ds == NULL) { ds = CreateDepthStencil(); ds->m_TEX0 = TEX0; if(!ds->Create(w, h)) { delete ds; return NULL; } m_ds.AddHead(ds); } if(m_renderer->m_context->DepthWrite()) { ds->m_used = true; } return ds; } GSTexture* GetTexture() { const GIFRegTEX0& TEX0 = m_renderer->m_context->TEX0; const GIFRegCLAMP& CLAMP = m_renderer->m_context->CLAMP; const DWORD* clut = m_renderer->m_mem.m_clut; const int pal = GSLocalMemory::m_psm[TEX0.PSM].pal; if(pal > 0) { m_renderer->m_mem.m_clut.Read(TEX0); /* POSITION pos = m_tex.GetHeadPosition(); while(pos) { POSITION cur = pos; GSSurface* s = m_tex.GetNext(pos); if(s->m_TEX0.TBP0 == TEX0.CBP) { m_tex.RemoveAt(cur); delete s; } } pos = m_rt.GetHeadPosition(); while(pos) { POSITION cur = pos; GSSurface* s = m_rt.GetNext(pos); if(s->m_TEX0.TBP0 == TEX0.CBP) { m_rt.RemoveAt(cur); delete s; } } pos = m_ds.GetHeadPosition(); while(pos) { POSITION cur = pos; GSSurface* s = m_ds.GetNext(pos); if(s->m_TEX0.TBP0 == TEX0.CBP) { m_ds.RemoveAt(cur); delete s; } } */ } GSTexture* t = NULL; for(POSITION pos = m_tex.GetHeadPosition(); pos; m_tex.GetNext(pos)) { t = m_tex.GetAt(pos); if(GSUtil::HasSharedBits(t->m_TEX0.TBP0, t->m_TEX0.PSM, TEX0.TBP0, TEX0.PSM)) { if(TEX0.PSM == t->m_TEX0.PSM && TEX0.TBW == t->m_TEX0.TBW && TEX0.TW == t->m_TEX0.TW && TEX0.TH == t->m_TEX0.TH && (m_renderer->m_psrr || (CLAMP.WMS != 3 && t->m_CLAMP.WMS != 3 && CLAMP.WMT != 3 && t->m_CLAMP.WMT != 3 || CLAMP.i64 == t->m_CLAMP.i64)) && (pal == 0 || TEX0.CPSM == t->m_TEX0.CPSM && GSVector4i::compare(t->m_clut, clut, pal * sizeof(clut[0])))) { m_tex.MoveToHead(pos); break; } } t = NULL; } if(t == NULL) { for(POSITION pos = m_rt.GetHeadPosition(); pos; m_rt.GetNext(pos)) { GSRenderTarget* rt = m_rt.GetAt(pos); if(rt->m_dirty.IsEmpty() && GSUtil::HasSharedBits(rt->m_TEX0.TBP0, rt->m_TEX0.PSM, TEX0.TBP0, TEX0.PSM)) { t = CreateTexture(); if(!t->Create(rt)) { delete t; return NULL; } m_tex.AddHead(t); break; } } } if(t == NULL) { for(POSITION pos = m_ds.GetHeadPosition(); pos; m_ds.GetNext(pos)) { GSDepthStencil* ds = m_ds.GetAt(pos); if(ds->m_dirty.IsEmpty() && ds->m_used && GSUtil::HasSharedBits(ds->m_TEX0.TBP0, ds->m_TEX0.PSM, TEX0.TBP0, TEX0.PSM)) { t = CreateTexture(); if(!t->Create(ds)) { delete t; return NULL; } m_tex.AddHead(t); break; } } } if(t == NULL) { t = CreateTexture(); if(!t->Create()) { delete t; return NULL; } m_tex.AddHead(t); } if(pal > 0) { int size = pal * sizeof(clut[0]); if(t->m_palette) { if(t->m_initpalette) { memcpy(t->m_clut, clut, size); t->m_palette.Update(CRect(0, 0, pal, 1), t->m_clut, size); t->m_initpalette = false; } else { if(GSVector4i::update(t->m_clut, clut, size)) { t->m_palette.Update(CRect(0, 0, pal, 1), t->m_clut, size); } } } else { memcpy(t->m_clut, clut, size); } } t->Update(); return t; } void InvalidateTextures(const GIFRegFRAME& FRAME, const GIFRegZBUF& ZBUF) { POSITION pos = m_tex.GetHeadPosition(); while(pos) { POSITION cur = pos; GSTexture* t = m_tex.GetNext(pos); if(GSUtil::HasSharedBits(FRAME.Block(), FRAME.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM) || GSUtil::HasSharedBits(ZBUF.Block(), ZBUF.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM)) { m_tex.RemoveAt(cur); delete t; } } } void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const CRect& r) { bool found = false; POSITION pos = m_tex.GetHeadPosition(); while(pos) { POSITION cur = pos; GSTexture* t = m_tex.GetNext(pos); if(GSUtil::HasSharedBits(BITBLTBUF.DBP, BITBLTBUF.DPSM, t->m_TEX0.TBP0, t->m_TEX0.PSM)) { if(BITBLTBUF.DBW == t->m_TEX0.TBW && !t->m_rendered) { t->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, r)); found = true; } else { m_tex.RemoveAt(cur); delete t; } } else if(GSUtil::HasCompatibleBits(BITBLTBUF.DPSM, t->m_TEX0.PSM)) { if(BITBLTBUF.DBW == t->m_TEX0.TBW && !t->m_rendered) { int rowsize = (int)BITBLTBUF.DBW * 8192; int offset = ((int)BITBLTBUF.DBP - (int)t->m_TEX0.TBP0) * 256; if(rowsize > 0 && offset % rowsize == 0) { int y = m_renderer->m_mem.m_psm[BITBLTBUF.DPSM].pgs.cy * offset / rowsize; CRect r2(r.left, r.top + y, r.right, r.bottom + y); int w = 1 << t->m_TEX0.TW; int h = 1 << t->m_TEX0.TH; if(r2.bottom > 0 && r2.top < h && r2.right > 0 && r2.left < w) { t->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, r2)); } } } } } pos = m_rt.GetHeadPosition(); while(pos) { POSITION cur = pos; GSRenderTarget* rt = m_rt.GetNext(pos); if(GSUtil::HasSharedBits(BITBLTBUF.DBP, BITBLTBUF.DPSM, rt->m_TEX0.TBP0, rt->m_TEX0.PSM)) { if(!found && GSUtil::HasCompatibleBits(BITBLTBUF.DPSM, rt->m_TEX0.PSM)) { rt->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, r)); rt->m_TEX0.TBW = BITBLTBUF.DBW; } else { m_rt.RemoveAt(cur); delete rt; continue; } } if(GSUtil::HasSharedBits(BITBLTBUF.DPSM, rt->m_TEX0.PSM) && BITBLTBUF.DBP < rt->m_TEX0.TBP0) { DWORD rowsize = BITBLTBUF.DBW * 8192; DWORD offset = (DWORD)((rt->m_TEX0.TBP0 - BITBLTBUF.DBP) * 256); if(rowsize > 0 && offset % rowsize == 0) { int y = m_renderer->m_mem.m_psm[BITBLTBUF.DPSM].pgs.cy * offset / rowsize; if(r.bottom >= y) { // TODO: do not add this rect above too rt->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, CRect(r.left, r.top - y, r.right, r.bottom - y))); rt->m_TEX0.TBW = BITBLTBUF.DBW; continue; } } } } // copypaste for ds pos = m_ds.GetHeadPosition(); while(pos) { POSITION cur = pos; GSDepthStencil* ds = m_ds.GetNext(pos); if(GSUtil::HasSharedBits(BITBLTBUF.DBP, BITBLTBUF.DPSM, ds->m_TEX0.TBP0, ds->m_TEX0.PSM)) { if(!found && GSUtil::HasCompatibleBits(BITBLTBUF.DPSM, ds->m_TEX0.PSM)) { ds->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, r)); ds->m_TEX0.TBW = BITBLTBUF.DBW; } else { m_ds.RemoveAt(cur); delete ds; continue; } } if(GSUtil::HasSharedBits(BITBLTBUF.DPSM, ds->m_TEX0.PSM) && BITBLTBUF.DBP < ds->m_TEX0.TBP0) { DWORD rowsize = BITBLTBUF.DBW * 8192; DWORD offset = (DWORD)((ds->m_TEX0.TBP0 - BITBLTBUF.DBP) * 256); if(rowsize > 0 && offset % rowsize == 0) { int y = m_renderer->m_mem.m_psm[BITBLTBUF.DPSM].pgs.cy * offset / rowsize; if(r.bottom >= y) { // TODO: do not add this rect above too ds->m_dirty.AddTail(GSDirtyRect(BITBLTBUF.DPSM, CRect(r.left, r.top - y, r.right, r.bottom - y))); ds->m_TEX0.TBW = BITBLTBUF.DBW; continue; } } } } } void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const CRect& r) { POSITION pos = m_rt.GetHeadPosition(); while(pos) { POSITION cur = pos; GSRenderTarget* rt = m_rt.GetNext(pos); if(GSUtil::HasSharedBits(BITBLTBUF.SBP, BITBLTBUF.SPSM, rt->m_TEX0.TBP0, rt->m_TEX0.PSM)) { if(GSUtil::HasCompatibleBits(BITBLTBUF.SPSM, rt->m_TEX0.PSM)) { rt->Read(r); return; } else if(BITBLTBUF.SPSM == PSM_PSMCT32 && (rt->m_TEX0.PSM == PSM_PSMCT16 || rt->m_TEX0.PSM == PSM_PSMCT16S)) { // ffx-2 riku changing to her default (shoots some reflecting glass at the end), 16-bit rt read as 32-bit rt->Read(CRect(r.left, r.top, r.right, r.top + (r.bottom - r.top) * 2)); return; } else { m_rt.RemoveAt(cur); delete rt; continue; } } } /* // no good, ffx does a lot of readback after exiting menu, at 0x02f00 this wrongly finds rt 0x02100 (0,448 - 512,480) GSRenderTarget* rt2 = NULL; int ymin = INT_MAX; pos = m_rt.GetHeadPosition(); while(pos) { GSRenderTarget* rt = m_rt.GetNext(pos); if(HasSharedBits(BITBLTBUF.SPSM, rt->m_TEX0.PSM) && BITBLTBUF.SBP > rt->m_TEX0.TBP0) { // ffx2 pause screen background DWORD rowsize = BITBLTBUF.SBW * 8192; DWORD offset = (DWORD)((BITBLTBUF.SBP - rt->m_TEX0.TBP0) * 256); if(rowsize > 0 && offset % rowsize == 0) { int y = m_renderer->m_mem.m_psm[BITBLTBUF.SPSM].pgs.cy * offset / rowsize; if(y < ymin && y < 512) { rt2 = rt; ymin = y; } } } } if(rt2) { rt2->Read(CRect(r.left, r.top + ymin, r.right, r.bottom + ymin)); } // TODO: ds */ } void IncAge() { RecycleByAge(m_tex, 2); RecycleByAge(m_rt); RecycleByAge(m_ds); } };