/* ZZ Open GL graphics plugin * Copyright (c)2009 zeydlitz@gmail.com * Based on Zerofrog's ZeroGS KOSMOS (c)2005-2006 * * 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 of the License, 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Zerogs:VB implementation. // VB stands for Visual Buffer, as I think //------------------- Includes #include "zerogs.h" #include "targets.h" #include "GS.h" #include "Mem.h" using namespace ZeroGS; // ----------------- Defines #define MINMAX_SHIFT 3 //------------------ Constants // ----------------- Global Variables int maxmin = 608; // ----------------- Code // Constructor. Set width and height to 1 ZeroGS::VB::VB() { memset(this, 0, sizeof(ZeroGS::VB)); tex0.tw = 1; tex0.th = 1; } // Destructor ZeroGS::VB::~VB() { Destroy(); } void ZeroGS::VB::Destroy() { _aligned_free(pBufferData); pBufferData = NULL; nNumVertices = 0; prndr = NULL; pdepth = NULL; } int ConstraintReson; // Return number of 64-pixels block, that guaranted could be hold in memory // from gsfb.fbp and tbp (textrure pase), zbuf.zbp (Z-buffer), frame.fbp // (previous frame). inline int ZeroGS::VB::FindMinimalMemoryConstrain(int tbp, int maxpos) { int MinConstraint = maxpos; // make sure texture is far away from tbp { int Constraint = tbp - gsfb.fbp; if ((0 < Constraint) && (Constraint < MinConstraint)) { MinConstraint = Constraint; ConstraintReson = 1; } } // offroad uses 0x80 fbp which messes up targets // special case when double buffering (hamsterball) // Suikoden 3 require e00 have this issue too. P3 - 0x1000. if (prndr != NULL) { int Constraint = frame.fbp - gsfb.fbp; if ((0x0 < Constraint) && (Constraint < MinConstraint)) { MinConstraint = Constraint; ConstraintReson = 2; } } // old caching method // zmsk necessary for KH movie if (!zbuf.zmsk){ int Constraint = zbuf.zbp - gsfb.fbp; if ((0 < Constraint) && (Constraint < MinConstraint)) { MinConstraint = Constraint; ConstraintReson = 3; } } // In 16Bit mode in one Word freame stored 2 pixels if (PSMT_ISHALF(gsfb.psm)) MinConstraint *= 2; return MinConstraint ; } // Return number of 64 pizel words that could be placed in Z-Buffer // If no Z-buffer present return old constraint inline int ZeroGS::VB::FindZbufferMemoryConstrain(int tbp, int maxpos) { int MinConstraint = maxpos; // Check tbp / zbuffer constraint if (!zbuf.zmsk) { int Constraint = (tbp - zbuf.zbp) * (PSMT_ISHALF(zbuf.psm) ? 2 : 1 ); if ((0 < Constraint) && (Constraint < MinConstraint)) { MinConstraint = Constraint; ConstraintReson = 4; } } return MinConstraint; } // Return heights limiter form scissor. . inline int GetScissorY(int y) { int fbh = (y >> MINMAX_SHIFT) + 1; if ( fbh > 2 && ( fbh & 1 ) ) fbh -= 1; return fbh; } //There is several reason to limit a height of frame: maximum buffer size, calculated size //from fbw and fbh and scissoring. inline int ZeroGS::VB::FindMinimalHeightConstrain(int maxpos) { int MinConstraint = maxpos; if (maxmin < MinConstraint) { MinConstraint = maxmin; ConstraintReson = 5; } if (gsfb.fbh < MinConstraint) { MinConstraint = gsfb.fbh; ConstraintReson = 6; } int ScissorConstraint = GetScissorY(scissor.y1) ; if (ScissorConstraint < MinConstraint) { MinConstraint = ScissorConstraint; ConstraintReson = 7; } return MinConstraint; } // 32 bit frames have additional constraints to frame // maxpos was maximum length of frame at normal constraints inline void ZeroGS::VB::CheckFrame32bitRes(int maxpos) { int fbh = frame.fbh; if ( frame.fbh >= 512 ) { // neopets hack maxmin = min(maxmin, frame.fbh); frame.fbh = maxmin; ConstraintReson = 8; } // ffxii hack to stop resolving if( frame.fbp >= 0x3000 && fbh >= 0x1a0 ) { int endfbp = frame.fbp + frame.fbw * fbh / (PSMT_ISHALF(gsfb.psm) ? 128 : 64); // see if there is a previous render target in the way, reduce for (CRenderTargetMngr::MAPTARGETS::iterator itnew = s_RTs.mapTargets.begin(); itnew != s_RTs.mapTargets.end(); ++itnew) { if ( itnew->second->fbp > frame.fbp && endfbp > itnew->second->fbp ) { endfbp = itnew->second->fbp; } } frame.fbh = (endfbp - frame.fbp) * ( PSMT_ISHALF(gsfb.psm) ? 128: 64 ) / frame.fbw; if (frame.fbh < fbh) ConstraintReson = 9; } } // This is the main code for frame resizing. // It checks for several reasons to resize and resizes if it needs to. // 4Mb memory in 64 bit (4 bytes) words. // |------------------------|---------------------|----------|----------|---------------------| // 0 gsfb.fbp zbuff.zpb tbp frame.fbp 2^20/64 inline int ZeroGS::VB::CheckFrameAddConstraints(int tbp) { if ( gsfb.fbw <= 0 ) { ERROR_LOG_SPAM("render target null, no constraints. Ignoring\n"); return -1; } // Memory region after fbp int maxmemorypos = 0x4000 - gsfb.fbp; ConstraintReson = 0; maxmemorypos = FindMinimalMemoryConstrain(tbp, maxmemorypos); maxmemorypos = FindZbufferMemoryConstrain(tbp, maxmemorypos); int maxpos = 64 * maxmemorypos ; maxpos /= gsfb.fbw; //? atelier iris crashes without it if( maxpos > 256 ) maxpos &= ~0x1f; #ifdef DEVBUILD int noscissorpos = maxpos; int ConstrainR1 = ConstraintReson; #endif maxpos = FindMinimalHeightConstrain(maxpos); frame = gsfb; frame.fbh = maxpos; if( !PSMT_ISHALF(frame.psm) || !(g_GameSettings&GAME_FULL16BITRES) ) CheckFrame32bitRes( maxpos ) ; #ifdef DEVBUILD if (frame.fbh == 0xe2) ZZLog::Error_Log ("Const: %x %x %d| %x %d %x %x", frame.fbh, frame.fbw, ConstraintReson, noscissorpos, ConstrainR1, tbp, frame.fbp); #endif // Fixme: Reserved psm for framebuffers // gsfb.psm &= 0xf; // shadow tower return 0 ; } // Check if after resising new depth target is need to be used. // it return 2 if new deapth target used. bool Chose is used to proprely check // renderer target status inline int ZeroGS::VB::CheckFrameResolveDepth(int tbp) { int result = 0 ; CDepthTarget* pprevdepth = pdepth; pdepth = NULL; // just z changed frameInfo f = CreateFrame(zbuf.zbp, prndr->fbw, prndr->fbh, zbuf.psm, (zbuf.psm == 0x31) ? 0xff000000 : 0); CDepthTarget* pnewdepth = (CDepthTarget*)s_DepthRTs.GetTarg(f, CRenderTargetMngr::TO_DepthBuffer|CRenderTargetMngr::TO_StrictHeight| (zbuf.zmsk?CRenderTargetMngr::TO_Virtual:0), get_maxheight(zbuf.zbp, gsfb.fbw, 0)); assert( pnewdepth != NULL && prndr != NULL ); assert( pnewdepth->fbh == prndr->fbh ); if( (pprevdepth != pnewdepth) || (pprevdepth != NULL && (pprevdepth->status & CRenderTarget::TS_NeedUpdate)) ) result = 2; pdepth = pnewdepth; return result ; } // Check if after resings new render target is need to be used. Also perform deptaget check // Return 1 if only render target is changed and 3 -- if both. inline int ZeroGS::VB::CheckFrameResolveRender(int tbp) { int result = 0 ; CRenderTarget* pprevrndr = prndr; prndr = NULL; CDepthTarget* pprevdepth = pdepth; pdepth = NULL; // Set renderes to NULL to prevent Flushing. CRenderTarget* pnewtarg = s_RTs.GetTarg(frame, 0, maxmin); assert( pnewtarg != NULL ); // pnewtarg->fbh >= 0x1c0 needed for ffx if( (pnewtarg->fbh >= 0x1c0) && pnewtarg->fbh > frame.fbh && zbuf.zbp < tbp && !zbuf.zmsk ) { // check if zbuf is in the way of the texture (suikoden5) int maxallowedfbh = (tbp-zbuf.zbp)*(PSMT_ISHALF(zbuf.psm)?128:64) / gsfb.fbw; if (PSMT_ISHALF(gsfb.psm)) maxallowedfbh *= 2; if (pnewtarg->fbh > maxallowedfbh + 32) { // +32 needed for ffx2 // destroy and recreate s_RTs.DestroyAllTargs(0, 0x100, pnewtarg->fbw); pnewtarg = s_RTs.GetTarg(frame, 0, maxmin); assert( pnewtarg != NULL ); } } ZZLog::Prim_Log("frame_%d: fbp=0x%x fbw=%d fbh=%d(%d) psm=0x%x fbm=0x%x\n", ictx, gsfb.fbp, gsfb.fbw, gsfb.fbh, pnewtarg->fbh, gsfb.psm, gsfb.fbm); if( (pprevrndr != pnewtarg) || (pprevrndr != NULL && (pprevrndr->status & CRenderTarget::TS_NeedUpdate)) ) result = 1; prndr = pnewtarg; pdepth = pprevdepth ; result |= CheckFrameResolveDepth(tbp) ; return result ; } // After frame resetting it is possible that 16 to 32 or 32 to 16 (color bits) conversion should be made. inline void ZeroGS::VB::CheckFrame16vs32Convesion() { if( prndr->status & CRenderTarget::TS_NeedConvert32) { if( pdepth->pdepth != 0 ) pdepth->SetDepthStencilSurface(); prndr->fbh *= 2; prndr->ConvertTo32(); prndr->status &= ~CRenderTarget::TS_NeedConvert32; } else if( prndr->status & CRenderTarget::TS_NeedConvert16 ) { if( pdepth->pdepth != 0 ) pdepth->SetDepthStencilSurface(); prndr->fbh /= 2; prndr->ConvertTo16(); prndr->status &= ~CRenderTarget::TS_NeedConvert16; } } // a lot of times, target is too big and overwrites the texture using, // if tbp != 0, use it to bound void ZeroGS::VB::CheckFrame(int tbp) { static int bChanged; if( bNeedZCheck ) { ZZLog::Prim_Log("zbuf_%d: zbp=0x%x psm=0x%x, zmsk=%d\n", ictx, zbuf.zbp, zbuf.psm, zbuf.zmsk); //zbuf = *zb; } if( m_Blocks[gsfb.psm].bpp == 0 ) { ZZLog::Error_Log("CheckFrame invalid bpp %d.", gsfb.psm); return; } bChanged = 0; if( bNeedFrameCheck ) { // important to set before calling GetTarg bNeedFrameCheck = 0; bNeedZCheck = 0; if ( CheckFrameAddConstraints(tbp) == -1 ) return ; if ( ( prndr != NULL ) && ( prndr->psm != gsfb.psm ) ) { // behavior for dest alpha varies ResetAlphaVariables(); } bChanged = CheckFrameResolveRender(tbp) ; CheckFrame16vs32Convesion(); } else if (bNeedZCheck) { bNeedZCheck = 0; if (prndr != NULL && gsfb.fbw > 0 ) CheckFrameResolveDepth(tbp); } if( prndr != NULL ) SetContextTarget(ictx); } // This is the case, most easy to perform, when nothinh was changed inline void ZeroGS::VB::FlushTexUnchangedClutDontUpdate() { if (ZZOglGet_cld_TexBits(uNextTex0Data[1])) { ZeroGS::texClutWrite(ictx); // invalidate to make sure target didn't change! bVarsTexSync = FALSE; } } // The second of easy branch. We does not change storage model, so we don't need to // update anything except texture itself inline void ZeroGS::VB::FlushTexClutDontUpdate() { if (!ZZOglClutStorageUnchanged(uCurTex0Data, uNextTex0Data)) ZeroGS::Flush(ictx); // clut memory isn't going to be loaded so can ignore, but at least update CSA and CPSM! uCurTex0Data[1] = (uCurTex0Data[1] & CPSM_CSA_NOTMASK) | (uNextTex0Data[1] & CPSM_CSA_BITMASK); tex0.csa = ZZOglGet_csa_TexBits(uNextTex0Data[1]); tex0.cpsm = ZZOglGet_cpsm_TexBits(uNextTex0Data[1]); ZeroGS::texClutWrite(ictx); bVarsTexSync = FALSE; } // Set texture variables after big change inline void ZeroGS::VB::FlushTexSetNewVars(u32 psm) { tex0.tbp0 = ZZOglGet_tbp0_TexBits(uNextTex0Data[0]); tex0.tbw = ZZOglGet_tbw_TexBitsMult(uNextTex0Data[0]); tex0.psm = psm; tex0.tw = ZZOglGet_tw_TexBitsExp(uNextTex0Data[0]); tex0.th = ZZOglGet_th_TexBitsExp(uNextTex0Data[0], uNextTex0Data[1]); tex0.tcc = ZZOglGet_tcc_TexBits(uNextTex0Data[1]); tex0.tfx = ZZOglGet_tfx_TexBits(uNextTex0Data[1]); ZeroGS::fiTexWidth[ictx] = (1/16.0f)/ tex0.tw; ZeroGS::fiTexHeight[ictx] = (1/16.0f) / tex0.th; } // Flush == draw on screen // This function made VB state consistant before real Flush. void ZeroGS::VB::FlushTexData() { assert( bNeedTexCheck ); bNeedTexCheck = 0; u32 psm = ZZOglGet_psm_TexBitsFix(uNextTex0Data[0]); // don't update unless necessary if (ZZOglAllExceptClutIsSame(uCurTex0Data, uNextTex0Data)) { // Don't need to do anything if there is no clutting and VB tex data was not changed if( !PSMT_ISCLUT(psm) ) return ; // have to write the CLUT again if only CLD was changed if( ZZOglClutMinusCLDunchanged(uCurTex0Data, uNextTex0Data) ) { FlushTexUnchangedClutDontUpdate(); return; } // Cld bit is 0 means that clut buffer stay unchanged if( ZZOglGet_cld_TexBits(uNextTex0Data[1]) == 0 ) { FlushTexClutDontUpdate(); return; } } // Made the full update ZeroGS::Flush(ictx); bVarsTexSync = FALSE; bTexConstsSync = FALSE; uCurTex0Data[0] = uNextTex0Data[0]; uCurTex0Data[1] = uNextTex0Data[1]; FlushTexSetNewVars(psm); if( PSMT_ISCLUT(psm) ) ZeroGS::CluttingForFlushedTex(&tex0, uNextTex0Data[1], ictx ) ; }