/* * 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" #include "GSTextureCache.h" #include "GSCrc.h" #include "GSFunctionMap.h" template class GSRendererHW : public GSRendererT { int m_width; int m_height; int m_skip; bool m_reset; #pragma region hacks typedef bool (GSRendererHW::*OI_Ptr)(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); typedef void (GSRendererHW::*OO_Ptr)(); typedef bool (GSRendererHW::*CU_Ptr)(); bool OI_FFXII(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { static uint32* video = NULL; static int lines = 0; if(lines == 0) { if(m_vt.m_primclass == GS_LINE_CLASS && (m_count == 448 * 2 || m_count == 512 * 2)) { lines = m_count / 2; } } else { if(m_vt.m_primclass == GS_POINT_CLASS) { if(m_count >= 16 * 512) { // incoming pixels are stored in columns, one column is 16x512, total res 448x512 or 448x454 if(!video) video = new uint32[512 * 512]; int ox = m_context->XYOFFSET.OFX; int oy = m_context->XYOFFSET.OFY; for(int i = 0; i < m_count; i++) { int x = ((int)m_vertices[i].p.x - ox) >> 4; int y = ((int)m_vertices[i].p.y - oy) >> 4; // video[y * 448 + x] = m_vertices[i].c0; video[(y << 8) + (y << 7) + (y << 6) + x] = m_vertices[i].c0; } return false; } else { lines = 0; } } else if(m_vt.m_primclass == GS_LINE_CLASS) { if(m_count == lines * 2) { // normally, this step would copy the video onto screen with 512 texture mapped horizontal lines, // but we use the stored video data to create a new texture, and replace the lines with two triangles m_dev->Recycle(t->m_texture); t->m_texture = m_dev->CreateTexture(512, 512); t->m_texture->Update(GSVector4i(0, 0, 448, lines), video, 448 * 4); m_vertices[0] = m_vertices[0]; m_vertices[1] = m_vertices[1]; m_vertices[2] = m_vertices[m_count - 2]; m_vertices[3] = m_vertices[1]; m_vertices[4] = m_vertices[2]; m_vertices[5] = m_vertices[m_count - 1]; m_count = 6; m_vt.Update(m_vertices, m_count, GS_TRIANGLE_CLASS); } else { lines = 0; } } } return true; } bool OI_FFX(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { uint32 FBP = m_context->FRAME.Block(); uint32 ZBP = m_context->ZBUF.Block(); uint32 TBP = m_context->TEX0.TBP0; if((FBP == 0x00d00 || FBP == 0x00000) && ZBP == 0x02100 && PRIM->TME && TBP == 0x01a00 && m_context->TEX0.PSM == PSM_PSMCT16S) { // random battle transition (z buffer written directly, clear it now) m_dev->ClearDepth(ds, 0); } return true; } bool OI_MetalSlug6(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { // missing red channel fix for(int i = 0, j = m_count; i < j; i++) { if(m_vertices[i].r == 0 && m_vertices[i].g != 0 && m_vertices[i].b != 0) { m_vertices[i].r = (m_vertices[i].g + m_vertices[i].b) / 2; } } m_vt.Update(m_vertices, m_count, m_vt.m_primclass); return true; } bool OI_GodOfWar2(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { uint32 FBP = m_context->FRAME.Block(); uint32 FBW = m_context->FRAME.FBW; uint32 FPSM = m_context->FRAME.PSM; if((FBP == 0x00f00 || FBP == 0x00100) && FPSM == PSM_PSMZ24) // ntsc 0xf00, pal 0x100 { // z buffer clear GIFRegTEX0 TEX0; TEX0.TBP0 = FBP; TEX0.TBW = FBW; TEX0.PSM = FPSM; if(GSTextureCache::Target* ds = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::DepthStencil, true)) { m_dev->ClearDepth(ds->m_texture, 0); } return false; } return true; } bool OI_SimpsonsGame(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { uint32 FBP = m_context->FRAME.Block(); uint32 FBW = m_context->FRAME.FBW; uint32 FPSM = m_context->FRAME.PSM; if(FBP == 0x01800 && FPSM == PSM_PSMZ24) { // instead of just simply drawing a full height 512x512 sprite to clear the z buffer, // it uses a 512x256 sprite only, yet it is still able to fill the whole surface with zeros, // how? by using a render target that overlaps with the lower half of the z buffer... m_dev->ClearDepth(ds, 0); return false; } return true; } bool OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { if(!PRIM->TME) { uint32 FBP = m_context->FRAME.Block(); uint32 ZBP = m_context->ZBUF.Block(); if(FBP == 0x008c0 && ZBP == 0x01a40) { // frame buffer clear, atst = fail, afail = write z only, z buffer points to frame buffer GIFRegTEX0 TEX0; TEX0.TBP0 = ZBP; TEX0.TBW = m_context->FRAME.FBW; TEX0.PSM = m_context->FRAME.PSM; if(GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::RenderTarget, true)) { m_dev->ClearRenderTarget(rt->m_texture, 0); } return false; } else if(FBP == 0x00000 && m_context->ZBUF.Block() == 0x01180) { // z buffer clear, frame buffer now points to the z buffer (how can they be so clever?) GIFRegTEX0 TEX0; TEX0.TBP0 = FBP; TEX0.TBW = m_context->FRAME.FBW; TEX0.PSM = m_context->ZBUF.PSM; if(GSTextureCache::Target* ds = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::DepthStencil, true)) { m_dev->ClearDepth(ds->m_texture, 0); } return false; } } return true; } bool OI_PointListPalette(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { if(m_vt.m_primclass == GS_POINT_CLASS && !PRIM->TME) { uint32 FBP = m_context->FRAME.Block(); uint32 FBW = m_context->FRAME.FBW; if(FBP >= 0x03f40 && (FBP & 0x1f) == 0) { if(m_count == 16) { for(int i = 0; i < 16; i++) { m_vertices[i].a = m_vertices[i].a >= 0x80 ? 0xff : m_vertices[i].a * 2; m_mem.WritePixel32(i & 7, i >> 3, m_vertices[i].c0, FBP, FBW); } m_mem.m_clut.Invalidate(); return false; } else if(m_count == 256) { for(int i = 0; i < 256; i++) { m_vertices[i].a = m_vertices[i].a >= 0x80 ? 0xff : m_vertices[i].a * 2; m_mem.WritePixel32(i & 15, i >> 4, m_vertices[i].c0, FBP, FBW); } m_mem.m_clut.Invalidate(); return false; } else { ASSERT(0); } } } return true; } void OO_DBZBT2() { // palette readback (cannot detect yet, when fetching the texture later) uint32 FBP = m_context->FRAME.Block(); uint32 TBP0 = m_context->TEX0.TBP0; if(PRIM->TME && (FBP == 0x03c00 && TBP0 == 0x03c80 || FBP == 0x03ac0 && TBP0 == 0x03b40)) { GIFRegBITBLTBUF BITBLTBUF; BITBLTBUF.SBP = FBP; BITBLTBUF.SBW = 1; BITBLTBUF.SPSM = PSM_PSMCT32; InvalidateLocalMem(BITBLTBUF, GSVector4i(0, 0, 64, 64)); } } void OO_MajokkoALaMode2() { // palette readback uint32 FBP = m_context->FRAME.Block(); if(!PRIM->TME && FBP == 0x03f40) { GIFRegBITBLTBUF BITBLTBUF; BITBLTBUF.SBP = FBP; BITBLTBUF.SBW = 1; BITBLTBUF.SPSM = PSM_PSMCT32; InvalidateLocalMem(BITBLTBUF, GSVector4i(0, 0, 16, 16)); } } bool CU_DBZBT2() { // palette should stay 64 x 64 uint32 FBP = m_context->FRAME.Block(); return FBP != 0x03c00 && FBP != 0x03ac0; } bool CU_MajokkoALaMode2() { // palette should stay 16 x 16 uint32 FBP = m_context->FRAME.Block(); return FBP != 0x03f40; } bool CU_TalesOfAbyss() { // full image blur and brightening uint32 FBP = m_context->FRAME.Block(); return FBP != 0x036e0 && FBP != 0x03560 && FBP != 0x038e0; } class Hacks { template struct HackEntry { CRC::Title title; CRC::Region region; T func; struct HackEntry(CRC::Title t, CRC::Region r, T f) { title = t; region = r; func = f; } }; template class FunctionMap : public GSFunctionMap { list >& m_tbl; T GetDefaultFunction(uint32 key) { CRC::Title title = (CRC::Title)(key & 0xffffff); CRC::Region region = (CRC::Region)(key >> 24); for(list >::iterator i = m_tbl.begin(); i != m_tbl.end(); i++) { if(i->title == title && (i->region == CRC::RegionCount || i->region == region)) { return i->func; } } return NULL; } public: FunctionMap(list >& tbl) : m_tbl(tbl) {} }; list > m_oi_list; list > m_oo_list; list > m_cu_list; FunctionMap m_oi_map; FunctionMap m_oo_map; FunctionMap m_cu_map; public: OI_Ptr m_oi; OO_Ptr m_oo; CU_Ptr m_cu; Hacks() : m_oi_map(m_oi_list) , m_oo_map(m_oo_list) , m_cu_map(m_cu_list) , m_oi(NULL) , m_oo(NULL) , m_cu(NULL) { m_oi_list.push_back(HackEntry(CRC::FFXII, CRC::EU, &GSRendererHW::OI_FFXII)); m_oi_list.push_back(HackEntry(CRC::FFX, CRC::RegionCount, &GSRendererHW::OI_FFX)); m_oi_list.push_back(HackEntry(CRC::MetalSlug6, CRC::RegionCount, &GSRendererHW::OI_MetalSlug6)); m_oi_list.push_back(HackEntry(CRC::GodOfWar2, CRC::RegionCount, &GSRendererHW::OI_GodOfWar2)); m_oi_list.push_back(HackEntry(CRC::SimpsonsGame, CRC::RegionCount, &GSRendererHW::OI_SimpsonsGame)); m_oi_list.push_back(HackEntry(CRC::RozenMaidenGebetGarden, CRC::RegionCount, &GSRendererHW::OI_RozenMaidenGebetGarden)); m_oo_list.push_back(HackEntry(CRC::DBZBT2, CRC::RegionCount, &GSRendererHW::OO_DBZBT2)); m_oo_list.push_back(HackEntry(CRC::MajokkoALaMode2, CRC::RegionCount, &GSRendererHW::OO_MajokkoALaMode2)); m_cu_list.push_back(HackEntry(CRC::DBZBT2, CRC::RegionCount, &GSRendererHW::CU_DBZBT2)); m_cu_list.push_back(HackEntry(CRC::MajokkoALaMode2, CRC::RegionCount, &GSRendererHW::CU_MajokkoALaMode2)); m_cu_list.push_back(HackEntry(CRC::TalesOfAbyss, CRC::RegionCount, &GSRendererHW::CU_TalesOfAbyss)); } void SetGame(const CRC::Game& game) { uint32 hash = (uint32)((game.region << 24) | game.title); m_oi = m_oi_map[hash]; m_oo = m_oo_map[hash]; m_cu = m_cu_map[hash]; if(game.flags & CRC::PointListPalette) { ASSERT(m_oi == NULL); m_oi = &GSRendererHW::OI_PointListPalette; } } } m_hacks; #pragma endregion protected: GSTextureCache* m_tc; void Reset() { // TODO: GSreset can come from the main thread too => crash // m_tc->RemoveAll(); m_reset = true; __super::Reset(); } void VSync(int field) { __super::VSync(field); m_tc->IncAge(); m_skip = 0; if(m_reset) { m_tc->RemoveAll(); m_reset = false; } } void ResetDevice() { m_tc->RemoveAll(); } GSTexture* GetOutput(int i) { const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB; GIFRegTEX0 TEX0; TEX0.TBP0 = DISPFB.Block(); TEX0.TBW = DISPFB.FBW; TEX0.PSM = DISPFB.PSM; // TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), (int)m_perfmon.GetFrame(), i, (int)TEX0.TBP0, (int)TEX0.PSM); GSTexture* t = NULL; if(GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::RenderTarget, true, true)) { t = rt->m_texture; if(s_dump) { if(s_save && s_n >= s_saven) { t->Save(format("c:\\temp2\\_%05d_f%I64d_fr%d_%05x_%d.bmp", s_n, m_perfmon.GetFrame(), i, (int)TEX0.TBP0, (int)TEX0.PSM)); } s_n++; } } return t; } void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r) { // printf("[%d] InvalidateVideoMem %d,%d - %d,%d %05x (%d)\n", (int)m_perfmon.GetFrame(), r.left, r.top, r.right, r.bottom, (int)BITBLTBUF.DBP, (int)BITBLTBUF.DPSM); m_tc->InvalidateVideoMem(m_mem.GetOffset(BITBLTBUF.DBP, BITBLTBUF.DBW, BITBLTBUF.DPSM), r); } void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r) { // printf("[%d] InvalidateLocalMem %d,%d - %d,%d %05x (%d)\n", (int)m_perfmon.GetFrame(), r.left, r.top, r.right, r.bottom, (int)BITBLTBUF.SBP, (int)BITBLTBUF.SPSM); m_tc->InvalidateLocalMem(m_mem.GetOffset(BITBLTBUF.SBP, BITBLTBUF.SBW, BITBLTBUF.SPSM), r); } void Draw() { if(IsBadFrame(m_skip)) return; GSDrawingEnvironment& env = m_env; GSDrawingContext* context = m_context; GIFRegTEX0 TEX0; TEX0.TBP0 = context->FRAME.Block(); TEX0.TBW = context->FRAME.FBW; TEX0.PSM = context->FRAME.PSM; GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::RenderTarget, true); TEX0.TBP0 = context->ZBUF.Block(); TEX0.TBW = context->FRAME.FBW; TEX0.PSM = context->ZBUF.PSM; GSTextureCache::Target* ds = m_tc->LookupTarget(TEX0, m_width, m_height, GSTextureCache::DepthStencil, m_context->DepthWrite()); GSTextureCache::Source* tex = NULL; if(PRIM->TME) { m_mem.m_clut.Read32(context->TEX0, env.TEXA); GSVector4i r; GetTextureMinMax(r, IsLinear()); tex = m_tc->LookupSource(context->TEX0, env.TEXA, r); if(!tex) return; } if(s_dump) { uint64 frame = m_perfmon.GetFrame(); string s; if(s_save && s_n >= s_saven && tex) { s = format("c:\\temp2\\_%05d_f%I64d_tex_%05x_%d_%d%d_%02x_%02x_%02x_%02x.dds", s_n, frame, (int)context->TEX0.TBP0, (int)context->TEX0.PSM, (int)context->CLAMP.WMS, (int)context->CLAMP.WMT, (int)context->CLAMP.MINU, (int)context->CLAMP.MAXU, (int)context->CLAMP.MINV, (int)context->CLAMP.MAXV); tex->m_texture->Save(s, true); if(tex->m_palette) { s = format("c:\\temp2\\_%05d_f%I64d_tpx_%05x_%d.dds", s_n, frame, context->TEX0.CBP, context->TEX0.CPSM); tex->m_palette->Save(s, true); } } s_n++; if(s_save && s_n >= s_saven) { s = format("c:\\temp2\\_%05d_f%I64d_rt0_%05x_%d.bmp", s_n, frame, context->FRAME.Block(), context->FRAME.PSM); rt->m_texture->Save(s); } if(s_savez && s_n >= s_saven) { s = format("c:\\temp2\\_%05d_f%I64d_rz0_%05x_%d.bmp", s_n, frame, context->ZBUF.Block(), context->ZBUF.PSM); ds->m_texture->Save(s); } s_n++; } if(m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt->m_texture, ds->m_texture, tex)) { return; } // skip alpha test if possible GIFRegTEST TEST = context->TEST; GIFRegFRAME FRAME = context->FRAME; GIFRegZBUF ZBUF = context->ZBUF; uint32 fm = context->FRAME.FBMSK; uint32 zm = context->ZBUF.ZMSK || context->TEST.ZTE == 0 ? 0xffffffff : 0; if(context->TEST.ATE && context->TEST.ATST != ATST_ALWAYS) { if(TryAlphaTest(fm, zm)) { context->TEST.ATST = ATST_ALWAYS; } } context->FRAME.FBMSK = fm; context->ZBUF.ZMSK = zm != 0; // Draw(rt->m_texture, ds->m_texture, tex); // context->TEST = TEST; context->FRAME = FRAME; context->ZBUF = ZBUF; // GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in)); if(fm != 0xffffffff) { rt->m_valid = rt->m_valid.runion(r); m_tc->InvalidateVideoMem(m_context->offset.fb, r, false); } if(zm != 0xffffffff) { ds->m_valid = ds->m_valid.runion(r); m_tc->InvalidateVideoMem(m_context->offset.zb, r, false); } // if(m_hacks.m_oo) { (this->*m_hacks.m_oo)(); } if(s_dump) { uint64 frame = m_perfmon.GetFrame(); string s; if(s_save && s_n >= s_saven) { s = format("c:\\temp2\\_%05d_f%I64d_rt1_%05x_%d.bmp", s_n, frame, context->FRAME.Block(), context->FRAME.PSM); rt->m_texture->Save(s); } if(s_savez && s_n >= s_saven) { s = format("c:\\temp2\\_%05d_f%I64d_rz1_%05x_%d.bmp", s_n, frame, context->ZBUF.Block(), context->ZBUF.PSM); ds->m_texture->Save(s); } s_n++; } } virtual void Draw(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* tex) = 0; bool CanUpscale() { if(m_hacks.m_cu && !(this->*m_hacks.m_cu)()) { return false; } return __super::CanUpscale(); } public: GSRendererHW(uint8* base, bool mt, void (*irq)(), GSDevice* dev, GSTextureCache* tc) : GSRendererT(base, mt, irq, dev) , m_tc(tc) , m_width(1024) , m_height(1024) , m_skip(0) , m_reset(false) { if(!m_nativeres) { m_width = theApp.GetConfig("resx", m_width); m_height = theApp.GetConfig("resy", m_height); } } virtual ~GSRendererHW() { delete m_tc; } void SetGameCRC(uint32 crc, int options) { __super::SetGameCRC(crc, options); m_hacks.SetGame(m_game); if(m_game.title == CRC::JackieChanAdv) { m_width = 1280; // TODO: uses a 1280px wide 16 bit render target, but this only fixes half of the problem } } };