2010-04-25 00:31:27 +00:00
/*
2009-02-09 21:15:56 +00:00
* 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 .
2010-04-25 00:31:27 +00:00
*
2009-02-09 21:15:56 +00:00
* 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 .
2010-04-25 00:31:27 +00:00
*
2009-02-09 21:15:56 +00:00
* You should have received a copy of the GNU General Public License
* along with GNU Make ; see the file COPYING . If not , write to
2012-09-09 18:16:11 +00:00
* the Free Software Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA USA .
2009-02-09 21:15:56 +00:00
* http : //www.gnu.org/copyleft/gpl.html
*
*/
# include "stdafx.h"
2010-04-25 00:31:27 +00:00
# include "GSRendererHW.h"
2012-01-05 02:40:24 +00:00
2019-08-28 17:39:02 +00:00
const float GSRendererHW : : SSR_UV_TOLERANCE = 1e-3 f ;
2012-01-19 04:53:36 +00:00
GSRendererHW : : GSRendererHW ( GSTextureCache * tc )
2017-11-08 06:47:23 +00:00
: m_width ( default_rt_size . x )
, m_height ( default_rt_size . y )
2017-05-19 17:48:03 +00:00
, m_custom_width ( 1024 )
2016-08-12 20:48:20 +00:00
, m_custom_height ( 1024 )
2012-01-05 02:40:24 +00:00
, m_reset ( false )
2020-01-16 23:49:33 +00:00
, m_userhacks_ts_half_bottom ( - 1 )
2013-06-28 17:32:37 +00:00
, m_tc ( tc )
2019-05-12 02:00:29 +00:00
, m_src ( nullptr )
2018-09-10 20:13:34 +00:00
, m_userhacks_tcoffset ( false )
2018-09-10 16:22:27 +00:00
, m_userhacks_tcoffset_x ( 0 )
, m_userhacks_tcoffset_y ( 0 )
2016-04-28 17:39:57 +00:00
, m_channel_shuffle ( false )
2016-10-01 15:37:55 +00:00
, m_lod ( GSVector2i ( 0 , 0 ) )
2012-01-05 02:40:24 +00:00
{
2016-11-05 16:49:17 +00:00
m_mipmap = theApp . GetConfigI ( " mipmap_hw " ) ;
2016-05-24 19:52:06 +00:00
m_upscale_multiplier = theApp . GetConfigI ( " upscale_multiplier " ) ;
2016-06-17 13:25:09 +00:00
m_large_framebuffer = theApp . GetConfigB ( " large_framebuffer " ) ;
2018-11-28 19:33:15 +00:00
m_accurate_date = theApp . GetConfigI ( " accurate_date " ) ;
2019-04-22 20:57:39 +00:00
2016-05-24 19:52:06 +00:00
if ( theApp . GetConfigB ( " UserHacks " ) ) {
2018-12-31 00:20:16 +00:00
m_userhacks_enabled_gs_mem_clear = ! theApp . GetConfigB ( " UserHacks_Disable_Safe_Features " ) ;
m_userHacks_enabled_unscale_ptln = ! theApp . GetConfigB ( " UserHacks_Disable_Safe_Features " ) ;
2016-05-24 19:52:06 +00:00
m_userhacks_align_sprite_X = theApp . GetConfigB ( " UserHacks_align_sprite_X " ) ;
2020-01-16 23:49:33 +00:00
m_userHacks_merge_sprite = theApp . GetConfigB ( " UserHacks_merge_pp_sprite " ) ;
m_userhacks_ts_half_bottom = theApp . GetConfigI ( " UserHacks_Half_Bottom_Override " ) ;
2016-05-24 19:52:06 +00:00
m_userhacks_round_sprite_offset = theApp . GetConfigI ( " UserHacks_round_sprite_offset " ) ;
2017-02-17 09:59:21 +00:00
m_userHacks_HPO = theApp . GetConfigI ( " UserHacks_HalfPixelOffset " ) ;
2018-09-10 20:13:34 +00:00
m_userhacks_tcoffset_x = theApp . GetConfigI ( " UserHacks_TCOffsetX " ) / - 1000.0f ;
m_userhacks_tcoffset_y = theApp . GetConfigI ( " UserHacks_TCOffsetY " ) / - 1000.0f ;
m_userhacks_tcoffset = m_userhacks_tcoffset_x < 0.0f | | m_userhacks_tcoffset_y < 0.0f ;
2016-05-24 19:52:06 +00:00
} else {
2018-12-31 00:20:16 +00:00
m_userhacks_enabled_gs_mem_clear = true ;
m_userHacks_enabled_unscale_ptln = true ;
2016-05-24 19:52:06 +00:00
m_userhacks_align_sprite_X = false ;
2020-01-16 23:49:33 +00:00
m_userHacks_merge_sprite = false ;
m_userhacks_ts_half_bottom = - 1 ;
2016-05-24 19:52:06 +00:00
m_userhacks_round_sprite_offset = 0 ;
2017-02-17 09:59:21 +00:00
m_userHacks_HPO = 0 ;
2016-05-24 19:52:06 +00:00
}
2015-06-07 15:57:55 +00:00
2015-09-21 20:26:00 +00:00
if ( ! m_upscale_multiplier ) { //Custom Resolution
2016-06-26 06:03:16 +00:00
m_custom_width = m_width = theApp . GetConfigI ( " resx " ) ;
m_custom_height = m_height = theApp . GetConfigI ( " resy " ) ;
2015-06-07 15:57:55 +00:00
}
2015-09-08 12:55:01 +00:00
if ( m_upscale_multiplier = = 1 ) { // hacks are only needed for upscaling issues.
2018-01-12 14:38:36 +00:00
m_userhacks_round_sprite_offset = 0 ;
m_userhacks_align_sprite_X = false ;
m_userHacks_merge_sprite = false ;
2015-06-07 15:57:55 +00:00
}
2016-07-14 17:41:21 +00:00
m_dump_root = root_hw ;
2015-05-13 18:00:25 +00:00
}
2015-09-20 12:41:21 +00:00
void GSRendererHW : : SetScaling ( )
{
2017-05-19 17:57:17 +00:00
if ( ! m_upscale_multiplier )
{
CustomResolutionScaling ( ) ;
return ;
}
2016-03-09 20:30:38 +00:00
GSVector2i crtc_size ( GetDisplayRect ( ) . width ( ) , GetDisplayRect ( ) . height ( ) ) ;
2015-09-20 12:41:21 +00:00
2016-05-23 17:23:04 +00:00
// Details of (potential) perf impact of a big framebuffer
// 1/ extra memory
// 2/ texture cache framebuffer rescaling/copy
// 3/ upload of framebuffer (preload hack)
// 4/ framebuffer clear (color/depth/stencil)
// 5/ read back of the frambuffer
//
// With the solution
// 1/ Nothing to do.Except the texture cache bug (channel shuffle effect)
// most of the market is 1GB of VRAM (and soon 2GB)
// 2/ limit rescaling/copy to the valid data of the framebuffer
// 3/ ??? no solution so far
// 4a/ stencil can be limited to valid data.
// 4b/ is it useful to clear color? depth? (in any case, it ought to be few operation)
// 5/ limit the read to the valid data
2016-02-07 11:08:02 +00:00
// Framebuffer width is always a multiple of 64 so at certain cases it can't cover some weird width values.
// 480P , 576P use width as 720 which is not referencable by FBW * 64. so it produces 704 ( the closest value multiple by 64).
// In such cases, let's just use the CRTC width.
2017-05-26 16:30:44 +00:00
int fb_width = std : : max ( { ( int ) m_context - > FRAME . FBW * 64 , crtc_size . x , 512 } ) ;
2016-02-07 11:08:02 +00:00
// GS doesn't have a specific register for the FrameBuffer height. so we get the height
// from physical units of the display rectangle in case the game uses a heigher value of height.
2016-03-28 10:09:59 +00:00
//
// Gregory: the framebuffer must have enough room to draw
// * at least 2 frames such as FMV (see OI_BlitFMV)
// * high resolution game such as snowblind engine game
//
// Autodetection isn't a good idea because it will create flickering
// If memory consumption is an issue, there are 2 possibilities
// * 1/ Avoid to create hundreds of RT
// * 2/ Use sparse texture (requires recent HW)
2016-05-17 18:01:00 +00:00
//
// Avoid to alternate between 640x1280 and 1280x1024 on snow blind engine game
// int fb_height = (fb_width < 1024) ? 1280 : 1024;
2016-05-23 17:27:13 +00:00
//
// Until performance issue is properly fixed, let's keep an option to reduce the framebuffer size.
int fb_height = m_large_framebuffer ? 1280 :
2017-05-26 16:30:44 +00:00
( fb_width < 1024 ) ? std : : max ( 512 , crtc_size . y ) : 1024 ;
2016-03-09 20:30:38 +00:00
int upscaled_fb_w = fb_width * m_upscale_multiplier ;
int upscaled_fb_h = fb_height * m_upscale_multiplier ;
bool good_rt_size = m_width > = upscaled_fb_w & & m_height > = upscaled_fb_h ;
2012-01-05 02:40:24 +00:00
2016-02-07 11:08:02 +00:00
// No need to resize for native/custom resolutions as default size will be enough for native and we manually get RT Buffer size for custom.
// don't resize until the display rectangle and register states are stabilized.
2016-03-09 20:30:38 +00:00
if ( m_upscale_multiplier < = 1 | | good_rt_size )
2016-02-07 11:08:02 +00:00
return ;
2015-04-13 16:31:48 +00:00
2016-02-07 11:08:02 +00:00
m_tc - > RemovePartial ( ) ;
2016-03-09 20:30:38 +00:00
m_width = upscaled_fb_w ;
m_height = upscaled_fb_h ;
printf ( " Frame buffer size set to %dx%d (%dx%d) \n " , fb_width , fb_height , m_width , m_height ) ;
2012-01-05 02:40:24 +00:00
}
2017-05-19 17:57:17 +00:00
void GSRendererHW : : CustomResolutionScaling ( )
{
const int crtc_width = GetDisplayRect ( ) . width ( ) ;
const int crtc_height = GetDisplayRect ( ) . height ( ) ;
2018-09-27 10:04:26 +00:00
GSVector2 scaling_ratio ;
scaling_ratio . x = std : : ceil ( static_cast < float > ( m_custom_width ) / crtc_width ) ;
scaling_ratio . y = std : : ceil ( static_cast < float > ( m_custom_height ) / crtc_height ) ;
2017-05-19 17:57:17 +00:00
// Avoid using a scissor value which is too high, developers can even leave the scissor to max (2047)
// at some cases when they don't want to limit the rendering size. Our assumption is that developers
// set the scissor to the actual data in the buffer. Let's use the scissoring value only at such cases
2018-09-27 10:04:26 +00:00
const int scissor_width = std : : min ( 640 , static_cast < int > ( m_context - > SCISSOR . SCAX1 - m_context - > SCISSOR . SCAX0 ) + 1 ) ;
2017-05-19 17:57:17 +00:00
const int scissor_height = std : : min ( 640 , static_cast < int > ( m_context - > SCISSOR . SCAY1 - m_context - > SCISSOR . SCAY0 ) + 1 ) ;
2018-09-27 10:04:26 +00:00
GSVector2i scissored_buffer_size ;
//TODO: SCAX is not used yet, not sure if it's worth considering the horizontal scissor? dunno where it helps yet.
// the ICO testcase is there to show that vertical scissor is helpful on the double scan mode games.
scissored_buffer_size . x = std : : max ( crtc_width , scissor_width ) ;
scissored_buffer_size . y = std : : max ( crtc_height , scissor_height ) ;
// We also consider for potential scissor sizes which are around
2017-05-19 17:57:17 +00:00
// the size of the actual image data stored. (Helps ICO to properly scale to right size by help of the
// scissoring values) Display rectangle has a height of 256 but scissor has a height of 512 which seems to
2018-09-27 10:04:26 +00:00
// be the real buffer size. Not sure if the width one is needed, need to check it on some random data before enabling it.
// int framebuffer_width = static_cast<int>(std::round(scissored_buffer_size.x * scaling_ratio.x));
int framebuffer_height = static_cast < int > ( std : : round ( scissored_buffer_size . y * scaling_ratio . y ) ) ;
2017-05-19 17:57:17 +00:00
2018-09-27 10:04:26 +00:00
if ( m_width > = m_custom_width & & m_height > = framebuffer_height )
2017-05-19 17:57:17 +00:00
return ;
m_tc - > RemovePartial ( ) ;
2017-11-08 06:47:23 +00:00
m_width = std : : max ( m_width , default_rt_size . x ) ;
2018-09-27 10:04:26 +00:00
m_height = std : : max ( framebuffer_height , default_rt_size . y ) ;
printf ( " Frame buffer size set to %dx%d (%dx%d) \n " , scissored_buffer_size . x , scissored_buffer_size . y , m_width , m_height ) ;
2017-05-19 17:57:17 +00:00
}
2012-01-05 02:40:24 +00:00
GSRendererHW : : ~ GSRendererHW ( )
{
delete m_tc ;
}
void GSRendererHW : : SetGameCRC ( uint32 crc , int options )
{
GSRenderer : : SetGameCRC ( crc , options ) ;
m_hacks . SetGameCRC ( m_game ) ;
2017-10-14 11:00:26 +00:00
// Code for Automatic Mipmapping. Relies on game CRCs.
2018-10-19 20:16:00 +00:00
if ( theApp . GetConfigT < HWMipmapLevel > ( " mipmap_hw " ) = = HWMipmapLevel : : Automatic )
{
switch ( CRC : : Lookup ( crc ) . title )
{
2019-04-07 17:50:46 +00:00
case CRC : : AceCombatZero :
case CRC : : AceCombat4 :
case CRC : : AceCombat5 :
case CRC : : ApeEscape2 :
case CRC : : Barnyard :
case CRC : : BrianLaraInternationalCricket :
case CRC : : DarkCloud :
case CRC : : DestroyAllHumans :
case CRC : : DestroyAllHumans2 :
case CRC : : FIFA03 :
case CRC : : FIFA04 :
case CRC : : FIFA05 :
case CRC : : HarryPotterATCOS :
2019-07-08 01:15:19 +00:00
case CRC : : HarryPotterATGOF :
2019-04-07 17:50:46 +00:00
case CRC : : HarryPotterATHBP :
case CRC : : HarryPotterATPOA :
case CRC : : HarryPotterOOTP :
2020-10-21 15:34:33 +00:00
case CRC : : ICO :
2019-08-19 21:27:32 +00:00
case CRC : : Jak1 :
case CRC : : Jak3 :
2019-04-07 17:50:46 +00:00
case CRC : : LegacyOfKainDefiance :
case CRC : : NicktoonsUnite :
2019-10-25 23:00:31 +00:00
case CRC : : Persona3 :
2019-07-08 01:15:19 +00:00
case CRC : : ProjectSnowblind :
case CRC : : Quake3Revolution :
2019-04-07 17:50:46 +00:00
case CRC : : RatchetAndClank :
case CRC : : RatchetAndClank2 :
case CRC : : RatchetAndClank3 :
case CRC : : RatchetAndClank4 :
case CRC : : RatchetAndClank5 :
case CRC : : RickyPontingInternationalCricket :
case CRC : : Shox :
case CRC : : SoulReaver2 :
case CRC : : TheIncredibleHulkUD :
case CRC : : TombRaiderAnniversary :
case CRC : : TribesAerialAssault :
case CRC : : Whiplash :
m_mipmap = static_cast < int > ( HWMipmapLevel : : Basic ) ;
break ;
default :
m_mipmap = static_cast < int > ( HWMipmapLevel : : Off ) ;
break ;
2017-10-14 11:00:26 +00:00
}
}
2012-01-05 02:40:24 +00:00
}
bool GSRendererHW : : CanUpscale ( )
{
2015-06-14 08:19:15 +00:00
if ( m_hacks . m_cu & & ! ( this - > * m_hacks . m_cu ) ( ) )
2012-01-05 02:40:24 +00:00
{
return false ;
}
2015-09-08 12:55:01 +00:00
return m_upscale_multiplier ! = 1 & & m_regs - > PMODE . EN ! = 0 ; // upscale ratio depends on the display size, with no output it may not be set correctly (ps2 logo to game transition)
2012-01-05 02:40:24 +00:00
}
int GSRendererHW : : GetUpscaleMultiplier ( )
{
2016-06-26 06:03:16 +00:00
return m_upscale_multiplier ;
}
GSVector2i GSRendererHW : : GetCustomResolution ( )
{
return GSVector2i ( m_custom_width , m_custom_height ) ;
2012-01-05 02:40:24 +00:00
}
void GSRendererHW : : Reset ( )
{
// TODO: GSreset can come from the main thread too => crash
// m_tc->RemoveAll();
m_reset = true ;
GSRenderer : : Reset ( ) ;
}
void GSRendererHW : : VSync ( int field )
{
2015-05-13 18:00:25 +00:00
//Check if the frame buffer width or display width has changed
SetScaling ( ) ;
2012-01-05 02:40:24 +00:00
if ( m_reset )
{
m_tc - > RemoveAll ( ) ;
m_reset = false ;
}
2012-01-18 11:47:31 +00:00
GSRenderer : : VSync ( field ) ;
m_tc - > IncAge ( ) ;
2015-07-10 19:11:14 +00:00
m_tc - > PrintMemoryUsage ( ) ;
m_dev - > PrintMemoryUsage ( ) ;
2012-01-18 11:47:31 +00:00
m_skip = 0 ;
2018-08-02 19:55:14 +00:00
m_skip_offset = 0 ;
2012-01-05 02:40:24 +00:00
}
void GSRendererHW : : ResetDevice ( )
{
m_tc - > RemoveAll ( ) ;
GSRenderer : : ResetDevice ( ) ;
}
2016-03-19 11:37:25 +00:00
GSTexture * GSRendererHW : : GetOutput ( int i , int & y_offset )
2012-01-05 02:40:24 +00:00
{
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 ;
2016-12-12 08:00:25 +00:00
if ( GSTextureCache : : Target * rt = m_tc - > LookupTarget ( TEX0 , m_width , m_height , GetFramebufferHeight ( ) ) )
2012-01-05 02:40:24 +00:00
{
t = rt - > m_texture ;
2016-03-19 11:37:25 +00:00
int delta = TEX0 . TBP0 - rt - > m_TEX0 . TBP0 ;
2016-11-04 22:02:49 +00:00
if ( delta > 0 & & DISPFB . FBW ! = 0 ) {
2016-05-05 10:17:57 +00:00
int pages = delta > > 5u ;
int y_pages = pages / DISPFB . FBW ;
y_offset = y_pages * GSLocalMemory : : m_psm [ DISPFB . PSM ] . pgs . y ;
2016-03-19 11:37:25 +00:00
GL_CACHE ( " Frame y offset %d pixels, unit %d " , y_offset , i ) ;
}
2016-04-23 10:09:57 +00:00
# ifdef ENABLE_OGL_DEBUG
2012-01-05 02:40:24 +00:00
if ( s_dump )
{
2015-05-31 15:38:52 +00:00
if ( s_savef & & s_n > = s_saven )
2012-01-05 02:40:24 +00:00
{
2016-09-05 18:34:29 +00:00
t - > Save ( m_dump_root + format ( " %05d_f%lld_fr%d_%05x_%s.bmp " , s_n , m_perfmon . GetFrame ( ) , i , ( int ) TEX0 . TBP0 , psm_str ( TEX0 . PSM ) ) ) ;
2012-01-05 02:40:24 +00:00
}
}
2016-03-31 07:50:06 +00:00
# endif
2012-01-05 02:40:24 +00:00
}
return t ;
}
2016-10-09 12:46:55 +00:00
GSTexture * GSRendererHW : : GetFeedbackOutput ( )
{
GIFRegTEX0 TEX0 ;
TEX0 . TBP0 = m_regs - > EXTBUF . EXBP ;
TEX0 . TBW = m_regs - > EXTBUF . EXBW ;
TEX0 . PSM = m_regs - > DISP [ m_regs - > EXTBUF . FBIN & 1 ] . DISPFB . PSM ;
GSTextureCache : : Target * rt = m_tc - > LookupTarget ( TEX0 , m_width , m_height , /*GetFrameRect(i).bottom*/ 0 ) ;
GSTexture * t = rt - > m_texture ;
# ifdef ENABLE_OGL_DEBUG
if ( s_dump & & s_savef & & s_n > = s_saven )
t - > Save ( m_dump_root + format ( " %05d_f%lld_fr%d_%05x_%s.bmp " , s_n , m_perfmon . GetFrame ( ) , 3 , ( int ) TEX0 . TBP0 , psm_str ( TEX0 . PSM ) ) ) ;
# endif
return t ;
}
2017-03-03 21:18:49 +00:00
void GSRendererHW : : Lines2Sprites ( )
{
ASSERT ( m_vt . m_primclass = = GS_SPRITE_CLASS ) ;
// each sprite converted to quad needs twice the space
while ( m_vertex . tail * 2 > m_vertex . maxcount )
{
GrowVertexBuffer ( ) ;
}
// assume vertices are tightly packed and sequentially indexed (it should be the case)
if ( m_vertex . next > = 2 )
{
size_t count = m_vertex . next ;
int i = ( int ) count * 2 - 4 ;
GSVertex * s = & m_vertex . buff [ count - 2 ] ;
GSVertex * q = & m_vertex . buff [ count * 2 - 4 ] ;
uint32 * RESTRICT index = & m_index . buff [ count * 3 - 6 ] ;
for ( ; i > = 0 ; i - = 4 , s - = 2 , q - = 4 , index - = 6 )
{
GSVertex v0 = s [ 0 ] ;
GSVertex v1 = s [ 1 ] ;
v0 . RGBAQ = v1 . RGBAQ ;
v0 . XYZ . Z = v1 . XYZ . Z ;
v0 . FOG = v1 . FOG ;
if ( PRIM - > TME & & ! PRIM - > FST ) {
GSVector4 st0 = GSVector4 : : loadl ( & v0 . ST . u64 ) ;
GSVector4 st1 = GSVector4 : : loadl ( & v1 . ST . u64 ) ;
GSVector4 Q = GSVector4 ( v1 . RGBAQ . Q , v1 . RGBAQ . Q , v1 . RGBAQ . Q , v1 . RGBAQ . Q ) ;
GSVector4 st = st0 . upld ( st1 ) / Q ;
GSVector4 : : storel ( & v0 . ST . u64 , st ) ;
GSVector4 : : storeh ( & v1 . ST . u64 , st ) ;
v0 . RGBAQ . Q = 1.0f ;
v1 . RGBAQ . Q = 1.0f ;
}
q [ 0 ] = v0 ;
q [ 3 ] = v1 ;
// swap x, s, u
uint16 x = v0 . XYZ . X ;
v0 . XYZ . X = v1 . XYZ . X ;
v1 . XYZ . X = x ;
float s = v0 . ST . S ;
v0 . ST . S = v1 . ST . S ;
v1 . ST . S = s ;
uint16 u = v0 . U ;
v0 . U = v1 . U ;
v1 . U = u ;
q [ 1 ] = v0 ;
q [ 2 ] = v1 ;
index [ 0 ] = i + 0 ;
index [ 1 ] = i + 1 ;
index [ 2 ] = i + 2 ;
index [ 3 ] = i + 1 ;
index [ 4 ] = i + 2 ;
index [ 5 ] = i + 3 ;
}
m_vertex . head = m_vertex . tail = m_vertex . next = count * 2 ;
m_index . tail = count * 3 ;
}
}
2020-10-30 01:09:51 +00:00
void GSRendererHW : : EmulateAtst ( GSVector4 & FogColor_AREF , uint8 & ps_atst , const bool pass_2 )
2020-09-28 19:20:14 +00:00
{
static const uint32 inverted_atst [ ] = { ATST_ALWAYS , ATST_NEVER , ATST_GEQUAL , ATST_GREATER , ATST_NOTEQUAL , ATST_LESS , ATST_LEQUAL , ATST_EQUAL } ;
if ( ! m_context - > TEST . ATE )
return ;
// Check for pass 2, otherwise do pass 1.
const int atst = pass_2 ? inverted_atst [ m_context - > TEST . ATST ] : m_context - > TEST . ATST ;
switch ( atst )
{
case ATST_LESS :
FogColor_AREF . a = ( float ) m_context - > TEST . AREF - 0.1f ;
ps_atst = 1 ;
break ;
case ATST_LEQUAL :
FogColor_AREF . a = ( float ) m_context - > TEST . AREF - 0.1f + 1.0f ;
ps_atst = 1 ;
break ;
case ATST_GEQUAL :
// Maybe a -1 trick multiplication factor could be used to merge with ATST_LEQUAL case
FogColor_AREF . a = ( float ) m_context - > TEST . AREF - 0.1f ;
ps_atst = 2 ;
break ;
case ATST_GREATER :
// Maybe a -1 trick multiplication factor could be used to merge with ATST_LESS case
FogColor_AREF . a = ( float ) m_context - > TEST . AREF - 0.1f + 1.0f ;
ps_atst = 2 ;
break ;
case ATST_EQUAL :
FogColor_AREF . a = ( float ) m_context - > TEST . AREF ;
ps_atst = 3 ;
break ;
case ATST_NOTEQUAL :
FogColor_AREF . a = ( float ) m_context - > TEST . AREF ;
ps_atst = 4 ;
break ;
case ATST_NEVER : // Draw won't be done so no need to implement it in shader
case ATST_ALWAYS :
default :
ps_atst = 0 ;
break ;
}
}
2019-02-21 12:10:41 +00:00
// Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color
void GSRendererHW : : ConvertSpriteTextureShuffle ( bool & write_ba , bool & read_ba )
{
size_t count = m_vertex . next ;
GSVertex * v = & m_vertex . buff [ 0 ] ;
const GIFRegXYOFFSET & o = m_context - > XYOFFSET ;
// vertex position is 8 to 16 pixels, therefore it is the 16-31 bits of the colors
int pos = ( v [ 0 ] . XYZ . X - o . OFX ) & 0xFF ;
write_ba = ( pos > 112 & & pos < 136 ) ;
// Read texture is 8 to 16 pixels (same as above)
float tw = ( float ) ( 1u < < m_context - > TEX0 . TW ) ;
int tex_pos = ( PRIM - > FST ) ? v [ 0 ] . U : ( int ) ( tw * v [ 0 ] . ST . S ) ;
tex_pos & = 0xFF ;
read_ba = ( tex_pos > 112 & & tex_pos < 144 ) ;
2020-01-16 23:49:33 +00:00
bool half_bottom = false ;
switch ( m_userhacks_ts_half_bottom ) {
case 0 :
// Force Disabled.
// Force Disabled will help games such as Xenosaga.
// Xenosaga handles the half bottom as an vertex offset instead of a buffer offset which does the effect twice.
// Half bottom won't trigger a cache miss that skip the draw because it is still the normal buffer but with a vertices offset.
half_bottom = false ;
break ;
case 1 :
// Force Enabled.
// Force Enabled will help games such as Superman Shadows of Apokolips, The Lord of the Rings: The Two Towers,
// Demon Stone, Midnight Club 3.
half_bottom = true ;
break ;
case - 1 :
default :
// Default, Automatic.
// Here's the idea
// TS effect is 16 bits but we emulate it on a 32 bits format
// Normally this means we need to divide size by 2.
//
// Some games do two TS effects on each half of the buffer.
// This makes a mess for us in the TC because we end up with two targets
// when we only want one, thus half screen bug.
//
// 32bits emulation means we can do the effect once but double the size.
// Test cases: Crash Twinsantiy and DBZ BT3
const int height_delta = m_src - > m_valid_rect . height ( ) - m_r . height ( ) ;
// Test Case: NFS: HP2 splits the effect h:256 and h:192 so 64
half_bottom = abs ( height_delta ) < = 64 ;
break ;
}
2019-04-24 12:28:56 +00:00
2019-02-21 12:10:41 +00:00
if ( PRIM - > FST ) {
GL_INS ( " First vertex is P: %d => %d T: %d => %d " , v [ 0 ] . XYZ . X , v [ 1 ] . XYZ . X , v [ 0 ] . U , v [ 1 ] . U ) ;
for ( size_t i = 0 ; i < count ; i + = 2 ) {
if ( write_ba )
v [ i ] . XYZ . X - = 128u ;
else
v [ i + 1 ] . XYZ . X + = 128u ;
if ( read_ba )
v [ i ] . U - = 128u ;
else
v [ i + 1 ] . U + = 128u ;
2019-04-24 12:28:56 +00:00
if ( ! half_bottom ) {
// Height is too big (2x).
int tex_offset = v [ i ] . V & 0xF ;
GSVector4i offset ( o . OFY , tex_offset , o . OFY , tex_offset ) ;
2019-02-21 12:10:41 +00:00
2019-04-24 12:28:56 +00:00
GSVector4i tmp ( v [ i ] . XYZ . Y , v [ i ] . V , v [ i + 1 ] . XYZ . Y , v [ i + 1 ] . V ) ;
tmp = GSVector4i ( tmp - offset ) . srl32 ( 1 ) + offset ;
2019-02-21 12:10:41 +00:00
2019-04-24 12:28:56 +00:00
v [ i ] . XYZ . Y = ( uint16 ) tmp . x ;
v [ i ] . V = ( uint16 ) tmp . y ;
v [ i + 1 ] . XYZ . Y = ( uint16 ) tmp . z ;
v [ i + 1 ] . V = ( uint16 ) tmp . w ;
}
2019-02-21 12:10:41 +00:00
}
} else {
const float offset_8pix = 8.0f / tw ;
GL_INS ( " First vertex is P: %d => %d T: %f => %f (offset %f) " , v [ 0 ] . XYZ . X , v [ 1 ] . XYZ . X , v [ 0 ] . ST . S , v [ 1 ] . ST . S , offset_8pix ) ;
for ( size_t i = 0 ; i < count ; i + = 2 ) {
if ( write_ba )
v [ i ] . XYZ . X - = 128u ;
else
v [ i + 1 ] . XYZ . X + = 128u ;
if ( read_ba )
v [ i ] . ST . S - = offset_8pix ;
else
v [ i + 1 ] . ST . S + = offset_8pix ;
2019-04-24 12:28:56 +00:00
if ( ! half_bottom ) {
// Height is too big (2x).
GSVector4i offset ( o . OFY , o . OFY ) ;
2019-02-21 12:10:41 +00:00
2019-04-24 12:28:56 +00:00
GSVector4i tmp ( v [ i ] . XYZ . Y , v [ i + 1 ] . XYZ . Y ) ;
tmp = GSVector4i ( tmp - offset ) . srl32 ( 1 ) + offset ;
2019-02-21 12:10:41 +00:00
2019-04-24 12:28:56 +00:00
//fprintf(stderr, "Before %d, After %d\n", v[i+1].XYZ.Y, tmp.y);
v [ i ] . XYZ . Y = ( uint16 ) tmp . x ;
v [ i ] . ST . T / = 2.0f ;
v [ i + 1 ] . XYZ . Y = ( uint16 ) tmp . y ;
v [ i + 1 ] . ST . T / = 2.0f ;
}
2019-02-21 12:10:41 +00:00
}
}
// Update vertex trace too. Avoid issue to compute bounding box
if ( write_ba )
m_vt . m_min . p . x - = 8.0f ;
else
m_vt . m_max . p . x + = 8.0f ;
2019-04-24 12:28:56 +00:00
if ( ! half_bottom ) {
float delta_Y = m_vt . m_max . p . y - m_vt . m_min . p . y ;
m_vt . m_max . p . y - = delta_Y / 2.0f ;
}
2019-02-21 12:10:41 +00:00
if ( read_ba )
m_vt . m_min . t . x - = 8.0f ;
else
m_vt . m_max . t . x + = 8.0f ;
2019-04-24 12:28:56 +00:00
if ( ! half_bottom ) {
float delta_T = m_vt . m_max . t . y - m_vt . m_min . t . y ;
m_vt . m_max . t . y - = delta_T / 2.0f ;
}
2019-02-21 12:10:41 +00:00
}
2017-02-17 09:59:21 +00:00
GSVector4 GSRendererHW : : RealignTargetTextureCoordinate ( const GSTextureCache : : Source * tex )
{
if ( m_userHacks_HPO < = 1 | | GetUpscaleMultiplier ( ) = = 1 ) return GSVector4 ( 0.0f ) ;
GSVertex * v = & m_vertex . buff [ 0 ] ;
const GSVector2 & scale = tex - > m_texture - > GetScale ( ) ;
bool linear = m_vt . IsRealLinear ( ) ;
int t_position = v [ 0 ] . U ;
GSVector4 half_offset ( 0.0f ) ;
// FIXME Let's start with something wrong same mess on X and Y
// FIXME Maybe it will be enough to check linear
if ( PRIM - > FST ) {
if ( m_userHacks_HPO = = 3 ) {
if ( ! linear & & t_position = = 8 ) {
half_offset . x = 8 ;
half_offset . y = 8 ;
} else if ( linear & & t_position = = 16 ) {
half_offset . x = 16 ;
half_offset . y = 16 ;
} else if ( m_vt . m_min . p . x = = - 0.5f ) {
half_offset . x = 8 ;
half_offset . y = 8 ;
}
} else {
if ( ! linear & & t_position = = 8 ) {
half_offset . x = 8 - 8 / scale . x ;
half_offset . y = 8 - 8 / scale . y ;
} else if ( linear & & t_position = = 16 ) {
half_offset . x = 16 - 16 / scale . x ;
half_offset . y = 16 - 16 / scale . y ;
} else if ( m_vt . m_min . p . x = = - 0.5f ) {
half_offset . x = 8 ;
half_offset . y = 8 ;
}
}
GL_INS ( " offset detected %f,%f t_pos %d (linear %d, scale %f) " ,
half_offset . x , half_offset . y , t_position , linear , scale . x ) ;
} else if ( m_vt . m_eq . q ) {
float tw = ( float ) ( 1 < < m_context - > TEX0 . TW ) ;
float th = ( float ) ( 1 < < m_context - > TEX0 . TH ) ;
float q = v [ 0 ] . RGBAQ . Q ;
// Tales of Abyss
half_offset . x = 0.5f * q / tw ;
half_offset . y = 0.5f * q / th ;
GL_INS ( " ST offset detected %f,%f (linear %d, scale %f) " ,
half_offset . x , half_offset . y , linear , scale . x ) ;
}
return half_offset ;
}
2019-01-12 06:37:37 +00:00
GSVector4i GSRendererHW : : ComputeBoundingBox ( const GSVector2 & rtscale , const GSVector2i & rtsize )
{
GSVector4 scale = GSVector4 ( rtscale . x , rtscale . y ) ;
GSVector4 offset = GSVector4 ( - 1.0f , 1.0f ) ; // Round value
GSVector4 box = m_vt . m_min . p . xyxy ( m_vt . m_max . p ) + offset . xxyy ( ) ;
return GSVector4i ( box * scale . xyxy ( ) ) . rintersect ( GSVector4i ( 0 , 0 , rtsize . x , rtsize . y ) ) ;
}
2017-03-18 13:19:48 +00:00
void GSRendererHW : : MergeSprite ( GSTextureCache : : Source * tex )
{
// Upscaling hack to avoid various line/grid issues
if ( m_userHacks_merge_sprite & & tex & & tex - > m_target & & ( m_vt . m_primclass = = GS_SPRITE_CLASS ) ) {
if ( PRIM - > FST & & GSLocalMemory : : m_psm [ tex - > m_TEX0 . PSM ] . fmt < 2 & & ( ( m_vt . m_eq . value & 0xCFFFF ) = = 0xCFFFF ) ) {
// Ideally the hack ought to be enabled in a true paving mode only. I don't know how to do it accurately
// neither in a fast way. So instead let's just take the hypothesis that all sprites must have the same
// size.
// Tested on Tekken 5.
GSVertex * v = & m_vertex . buff [ 0 ] ;
bool is_paving = true ;
// SSE optimization: shuffle m[1] to have (4*32 bits) X, Y, U, V
int first_dpX = v [ 1 ] . XYZ . X - v [ 0 ] . XYZ . X ;
int first_dpU = v [ 1 ] . U - v [ 0 ] . U ;
for ( size_t i = 0 ; i < m_vertex . next ; i + = 2 ) {
int dpX = v [ i + 1 ] . XYZ . X - v [ i ] . XYZ . X ;
int dpU = v [ i + 1 ] . U - v [ i ] . U ;
if ( dpX ! = first_dpX | | dpU ! = first_dpU ) {
is_paving = false ;
break ;
}
}
#if 0
GSVector4 delta_p = m_vt . m_max . p - m_vt . m_min . p ;
GSVector4 delta_t = m_vt . m_max . t - m_vt . m_min . t ;
bool is_blit = PrimitiveOverlap ( ) = = PRIM_OVERLAP_NO ;
GL_INS ( " PP SAMPLER: Dp %f %f Dt %f %f. Is blit %d, is paving %d, count %d " , delta_p . x , delta_p . y , delta_t . x , delta_t . y , is_blit , is_paving , m_vertex . tail ) ;
# endif
if ( is_paving ) {
// Replace all sprite with a single fullscreen sprite.
GSVertex * s = & m_vertex . buff [ 0 ] ;
s [ 0 ] . XYZ . X = static_cast < uint16 > ( ( 16.0f * m_vt . m_min . p . x ) + m_context - > XYOFFSET . OFX ) ;
s [ 1 ] . XYZ . X = static_cast < uint16 > ( ( 16.0f * m_vt . m_max . p . x ) + m_context - > XYOFFSET . OFX ) ;
s [ 0 ] . XYZ . Y = static_cast < uint16 > ( ( 16.0f * m_vt . m_min . p . y ) + m_context - > XYOFFSET . OFY ) ;
s [ 1 ] . XYZ . Y = static_cast < uint16 > ( ( 16.0f * m_vt . m_max . p . y ) + m_context - > XYOFFSET . OFY ) ;
s [ 0 ] . U = static_cast < uint16 > ( 16.0f * m_vt . m_min . t . x ) ;
s [ 0 ] . V = static_cast < uint16 > ( 16.0f * m_vt . m_min . t . y ) ;
s [ 1 ] . U = static_cast < uint16 > ( 16.0f * m_vt . m_max . t . x ) ;
s [ 1 ] . V = static_cast < uint16 > ( 16.0f * m_vt . m_max . t . y ) ;
m_vertex . head = m_vertex . tail = m_vertex . next = 2 ;
m_index . tail = 2 ;
}
}
}
}
2012-01-05 02:40:24 +00:00
void GSRendererHW : : 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 GSRendererHW : : InvalidateLocalMem ( const GIFRegBITBLTBUF & BITBLTBUF , const GSVector4i & r , bool clut )
{
// 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);
if ( clut ) return ; // FIXME
2016-03-19 10:43:27 +00:00
2012-01-05 02:40:24 +00:00
m_tc - > InvalidateLocalMem ( m_mem . GetOffset ( BITBLTBUF . SBP , BITBLTBUF . SBW , BITBLTBUF . SPSM ) , r ) ;
}
2016-04-14 10:00:58 +00:00
uint16 GSRendererHW : : Interpolate_UV ( float alpha , int t0 , int t1 )
2015-04-09 17:51:50 +00:00
{
float t = ( 1.0f - alpha ) * t0 + alpha * t1 ;
2016-04-14 10:00:58 +00:00
return ( uint16 ) t & ~ 0xF ; // cheap rounding
2015-04-09 17:51:50 +00:00
}
2015-04-12 15:10:45 +00:00
float GSRendererHW : : alpha0 ( int L , int X0 , int X1 )
2015-04-09 17:51:50 +00:00
{
2016-04-14 10:00:58 +00:00
int x = ( X0 + 15 ) & ~ 0xF ; // Round up
return float ( x - X0 ) / ( float ) L ;
2015-04-09 17:51:50 +00:00
}
2015-04-12 15:10:45 +00:00
float GSRendererHW : : alpha1 ( int L , int X0 , int X1 )
2015-04-09 17:51:50 +00:00
{
2016-04-14 10:00:58 +00:00
int x = ( X1 - 1 ) & ~ 0xF ; // Round down. Note -1 because right pixel isn't included in primitive so 0x100 must return 0.
return float ( x - X0 ) / ( float ) L ;
2015-04-09 17:51:50 +00:00
}
2019-07-12 23:05:27 +00:00
void GSRendererHW : : SwSpriteRender ( )
{
// Supported drawing attributes
2019-08-28 17:24:27 +00:00
ASSERT ( PRIM - > PRIM = = GS_TRIANGLESTRIP | | PRIM - > PRIM = = GS_SPRITE ) ;
2019-07-12 23:05:27 +00:00
ASSERT ( ! PRIM - > FGE ) ; // No FOG
ASSERT ( ! PRIM - > AA1 ) ; // No antialiasing
ASSERT ( ! PRIM - > FIX ) ; // Normal fragment value control
ASSERT ( ! m_env . DTHE . DTHE ) ; // No dithering
ASSERT ( ! m_context - > TEST . ATE ) ; // No alpha test
ASSERT ( ! m_context - > TEST . DATE ) ; // No destination alpha test
ASSERT ( ! m_context - > DepthRead ( ) & & ! m_context - > DepthWrite ( ) ) ; // No depth handling
ASSERT ( ! m_context - > TEX0 . CSM ) ; // No CLUT usage
ASSERT ( ! m_env . PABE . PABE ) ; // No PABE
// PSMCT32 pixel format
ASSERT ( ! PRIM - > TME | | ( PRIM - > TME & & m_context - > TEX0 . PSM = = PSM_PSMCT32 ) ) ;
ASSERT ( m_context - > FRAME . PSM = = PSM_PSMCT32 ) ;
// No rasterization required
2020-05-03 13:47:46 +00:00
ASSERT ( PRIM - > PRIM = = GS_SPRITE
| | ( ( PRIM - > IIP | | m_vt . m_eq . rgba = = 0xffff )
& & m_vt . m_eq . z = = 0x1
& & ( ! PRIM - > TME | | PRIM - > FST | | m_vt . m_eq . q = = 0x1 ) ) ) ; // Check Q equality only if texturing enabled and STQ coords used
2019-07-12 23:05:27 +00:00
2020-06-03 17:49:51 +00:00
const bool texture_mapping_enabled = PRIM - > TME ;
2019-07-12 23:05:27 +00:00
// Setup registers for SW rendering
2020-06-03 17:49:51 +00:00
GIFRegBITBLTBUF bitbltbuf = { } ;
2019-07-12 23:05:27 +00:00
if ( texture_mapping_enabled )
{
bitbltbuf . SBP = m_context - > TEX0 . TBP0 ;
bitbltbuf . SBW = m_context - > TEX0 . TBW ;
bitbltbuf . SPSM = m_context - > TEX0 . PSM ;
}
bitbltbuf . DBP = m_context - > FRAME . Block ( ) ;
bitbltbuf . DBW = m_context - > FRAME . FBW ;
bitbltbuf . DPSM = m_context - > FRAME . PSM ;
2019-08-28 17:38:22 +00:00
ASSERT ( m_r . x = = 0 & & m_r . y = = 0 ) ; // No rendering region offset
2019-08-28 17:39:02 +00:00
ASSERT ( ! PRIM - > TME | | ( abs ( m_vt . m_min . t . x ) < = SSR_UV_TOLERANCE & & abs ( m_vt . m_min . t . y ) < = SSR_UV_TOLERANCE ) ) ; // No input texture offset, if any
ASSERT ( ! PRIM - > TME | | ( abs ( m_vt . m_max . t . x - m_r . z ) < = SSR_UV_TOLERANCE & & abs ( m_vt . m_max . t . y - m_r . w ) < = SSR_UV_TOLERANCE ) ) ; // No input texture min/mag, if any
2019-08-28 17:38:22 +00:00
ASSERT ( ! PRIM - > TME | | ( m_vt . m_max . t . x < = ( 1 < < m_context - > TEX0 . TW ) & & m_vt . m_max . t . y < = ( 1 < < m_context - > TEX0 . TH ) ) ) ; // No texture UV wrap, if any
2020-06-03 17:49:51 +00:00
GIFRegTRXPOS trxpos = { } ;
2019-07-12 23:05:27 +00:00
trxpos . DSAX = 0 ;
trxpos . DSAY = 0 ;
trxpos . SSAX = 0 ;
trxpos . SSAY = 0 ;
2020-06-03 17:49:51 +00:00
GIFRegTRXREG trxreg = { } ;
2019-07-12 23:05:27 +00:00
2019-08-28 17:38:22 +00:00
trxreg . RRW = m_r . z ;
trxreg . RRH = m_r . w ;
2019-07-12 23:05:27 +00:00
// SW rendering code, mainly taken from GSState::Move(), TRXPOS.DIR{X,Y} management excluded
int sx = trxpos . SSAX ;
int sy = trxpos . SSAY ;
int dx = trxpos . DSAX ;
int dy = trxpos . DSAY ;
2020-06-03 17:49:51 +00:00
const int w = trxreg . RRW ;
const int h = trxreg . RRH ;
2019-07-12 23:05:27 +00:00
GL_INS ( " SwSpriteRender: Dest 0x%x W:%d F:%s, size(%d %d) " , bitbltbuf . DBP , bitbltbuf . DBW , psm_str ( bitbltbuf . DPSM ) , w , h ) ;
if ( texture_mapping_enabled )
InvalidateLocalMem ( bitbltbuf , GSVector4i ( sx , sy , sx + w , sy + h ) ) ;
InvalidateVideoMem ( bitbltbuf , GSVector4i ( dx , dy , dx + w , dy + h ) ) ;
GSOffset * RESTRICT spo = texture_mapping_enabled ? m_mem . GetOffset ( bitbltbuf . SBP , bitbltbuf . SBW , bitbltbuf . SPSM ) : nullptr ;
GSOffset * RESTRICT dpo = m_mem . GetOffset ( bitbltbuf . DBP , bitbltbuf . DBW , bitbltbuf . DPSM ) ;
2020-06-03 17:49:51 +00:00
const int * RESTRICT scol = texture_mapping_enabled ? & spo - > pixel . col [ 0 ] [ sx ] : nullptr ;
2019-07-12 23:05:27 +00:00
int * RESTRICT dcol = & dpo - > pixel . col [ 0 ] [ dx ] ;
2020-06-03 17:49:51 +00:00
const bool alpha_blending_enabled = PRIM - > ABE ;
2019-07-12 23:05:27 +00:00
2020-05-03 13:47:46 +00:00
const GSVertex & v = m_vertex . buff [ m_index . buff [ m_index . tail - 1 ] ] ; // Last vertex.
const GSVector4i vc = GSVector4i ( v . RGBAQ . R , v . RGBAQ . G , v . RGBAQ . B , v . RGBAQ . A ) // 0x000000AA000000BB000000GG000000RR
. ps32 ( ) ; // 0x00AA00BB00GG00RR00AA00BB00GG00RR
2019-07-12 23:05:27 +00:00
2020-06-03 17:49:51 +00:00
const GSVector4i a_mask = GSVector4i : : xff000000 ( ) . u8to16 ( ) ; // 0x00FF00000000000000FF000000000000
2019-07-12 23:05:27 +00:00
2020-06-03 17:49:51 +00:00
const bool fb_mask_enabled = m_context - > FRAME . FBMSK ! = 0x0 ;
const GSVector4i fb_mask = GSVector4i ( m_context - > FRAME . FBMSK ) . u8to16 ( ) ; // 0x00AA00BB00GG00RR00AA00BB00GG00RR
2019-07-12 23:05:27 +00:00
2020-06-03 17:49:51 +00:00
const uint8 tex0_tfx = m_context - > TEX0 . TFX ;
const uint8 tex0_tcc = m_context - > TEX0 . TCC ;
const uint8 alpha_b = m_context - > ALPHA . B ;
const uint8 alpha_c = m_context - > ALPHA . C ;
const uint8 alpha_fix = m_context - > ALPHA . FIX ;
2019-07-12 23:05:27 +00:00
for ( int y = 0 ; y < h ; y + + , + + sy , + + dy )
{
2020-06-03 17:49:51 +00:00
const uint32 * RESTRICT s = texture_mapping_enabled ? & m_mem . m_vm32 [ spo - > pixel . row [ sy ] ] : nullptr ;
2019-07-12 23:05:27 +00:00
uint32 * RESTRICT d = & m_mem . m_vm32 [ dpo - > pixel . row [ dy ] ] ;
ASSERT ( w % 2 = = 0 ) ;
for ( int x = 0 ; x < w ; x + = 2 )
{
GSVector4i sc ;
if ( texture_mapping_enabled )
{
// Read 2 source pixel colors
ASSERT ( ( scol [ x ] + 1 ) = = scol [ x + 1 ] ) ; // Source pixel pair is adjacent in memory
sc = GSVector4i : : loadl ( & s [ scol [ x ] ] ) . u8to16 ( ) ; // 0x00AA00BB00GG00RR00aa00bb00gg00rr
// Apply TFX
2019-08-10 12:15:01 +00:00
ASSERT ( tex0_tfx = = 0 | | tex0_tfx = = 1 ) ;
if ( tex0_tfx = = 0 )
sc = sc . mul16l ( vc ) . srl16 ( 7 ) . clamp8 ( ) ; // clamp((sc * vc) >> 7, 0, 255), srl16 is ok because 16 bit values are unsigned
2019-07-12 23:05:27 +00:00
if ( tex0_tcc = = 0 )
sc = sc . blend ( vc , a_mask ) ;
}
else
sc = vc ;
// No FOG
GSVector4i dc0 ;
GSVector4i dc ;
if ( alpha_blending_enabled | | fb_mask_enabled )
{
// Read 2 destination pixel colors
ASSERT ( ( dcol [ x ] + 1 ) = = dcol [ x + 1 ] ) ; // Destination pixel pair is adjacent in memory
dc0 = GSVector4i : : loadl ( & d [ dcol [ x ] ] ) . u8to16 ( ) ; // 0x00AA00BB00GG00RR00aa00bb00gg00rr
}
if ( alpha_blending_enabled )
{
// Blending
ASSERT ( m_context - > ALPHA . A = = 0 ) ;
ASSERT ( alpha_b = = 1 | | alpha_b = = 2 ) ;
ASSERT ( m_context - > ALPHA . D = = 1 ) ;
2019-08-10 12:14:26 +00:00
// Flag C
GSVector4i sc_alpha_vec ;
if ( alpha_c = = 2 )
sc_alpha_vec = GSVector4i ( alpha_fix ) . xxxx ( ) . ps32 ( ) ;
else
sc_alpha_vec = ( alpha_c = = 0 ? sc : dc0 )
. yyww ( ) // 0x00AA00BB00AA00BB00aa00bb00aa00bb
. srl32 ( 16 ) // 0x000000AA000000AA000000aa000000aa
. ps32 ( ) // 0x00AA00AA00aa00aa00AA00AA00aa00aa
. xxyy ( ) ; // 0x00AA00AA00AA00AA00aa00aa00aa00aa
2019-07-12 23:05:27 +00:00
switch ( alpha_b )
{
case 1 :
2019-08-10 12:14:26 +00:00
dc = sc . sub16 ( dc0 ) . mul16l ( sc_alpha_vec ) . sra16 ( 7 ) . add16 ( dc0 ) ; // (((Cs - Cd) * C) >> 7) + Cd, must use sra16 due to signed 16 bit values
2019-07-12 23:05:27 +00:00
break ;
default :
2019-08-10 12:14:26 +00:00
dc = sc . mul16l ( sc_alpha_vec ) . sra16 ( 7 ) . add16 ( dc0 ) ; // (((Cs - 0) * C) >> 7) + Cd, must use sra16 due to signed 16 bit values
2019-07-12 23:05:27 +00:00
break ;
}
// dc alpha channels (dc.u16[3], dc.u16[7]) dirty
}
else
dc = sc ;
// No dithering
// Clamping
if ( m_env . COLCLAMP . CLAMP )
dc = dc . clamp8 ( ) ; // clamp(dc, 0, 255)
else
dc = dc . sll16 ( 8 ) . srl16 ( 8 ) ; // Mask, lower 8 bits enabled per channel
// No Alpha Correction
ASSERT ( m_context - > FBA . FBA = = 0 ) ;
dc = dc . blend ( sc , a_mask ) ;
// dc alpha channels valid
// Frame buffer mask
if ( fb_mask_enabled )
dc = dc . blend ( dc0 , fb_mask ) ;
// Store 2 pixel colors
dc = dc . pu16 ( GSVector4i : : zero ( ) ) ; // 0x0000000000000000AABBGGRRaabbggrr
ASSERT ( ( dcol [ x ] + 1 ) = = dcol [ x + 1 ] ) ; // Destination pixel pair is adjacent in memory
GSVector4i : : storel ( & d [ dcol [ x ] ] , dc ) ;
}
}
}
2019-08-28 17:39:02 +00:00
bool GSRendererHW : : CanUseSwSpriteRender ( bool allow_64x64_sprite )
{
2020-06-03 17:49:51 +00:00
const bool r_0_0_64_64 = allow_64x64_sprite ? ( m_r = = GSVector4i ( 0 , 0 , 64 , 64 ) ) . alltrue ( ) : false ;
2019-08-28 17:39:02 +00:00
if ( r_0_0_64_64 & & ! allow_64x64_sprite ) // Rendering region 64x64 support is enabled via parameter
return false ;
2020-06-03 17:49:51 +00:00
const bool r_0_0_16_16 = ( m_r = = GSVector4i ( 0 , 0 , 16 , 16 ) ) . alltrue ( ) ;
2019-08-28 17:39:02 +00:00
if ( ! r_0_0_16_16 & & ! r_0_0_64_64 ) // Rendering region is 16x16 or 64x64, without offset
return false ;
if ( PRIM - > PRIM ! = GS_SPRITE
& & ( ( PRIM - > IIP & & m_vt . m_eq . rgba ! = 0xffff )
| | ( PRIM - > TME & & ! PRIM - > FST & & m_vt . m_eq . q ! = 0x1 )
| | m_vt . m_eq . z ! = 0x1 ) ) // No rasterization
return false ;
if ( m_vt . m_primclass ! = GS_TRIANGLE_CLASS & & m_vt . m_primclass ! = GS_SPRITE_CLASS ) // Triangle or sprite class prims
return false ;
if ( PRIM - > PRIM ! = GS_TRIANGLESTRIP & & PRIM - > PRIM ! = GS_SPRITE ) // Triangle strip or sprite draw
return false ;
if ( m_vt . m_primclass = = GS_TRIANGLE_CLASS & & ( PRIM - > PRIM ! = GS_TRIANGLESTRIP | | m_vertex . tail ! = 4 ) ) // If triangle class, strip draw with 4 vertices (two prims, emulating single sprite prim)
return false ;
// TODO If GS_TRIANGLESTRIP draw, check that the draw is axis aligned
if ( m_vt . m_primclass = = GS_SPRITE_CLASS & & ( PRIM - > PRIM ! = GS_SPRITE | | m_vertex . tail ! = 2 ) ) // If sprite class, sprite draw with 2 vertices (one prim)
return false ;
if ( m_context - > DepthRead ( ) | | m_context - > DepthWrite ( ) ) // No depth handling
return false ;
if ( m_context - > FRAME . PSM ! = PSM_PSMCT32 ) // Frame buffer format is 32 bit color
return false ;
if ( PRIM - > TME )
{
// Texture mapping enabled
if ( m_context - > TEX0 . PSM ! = PSM_PSMCT32 ) // Input texture format is 32 bit color
return false ;
if ( IsMipMapDraw ( ) ) // No mipmapping
return false ;
if ( abs ( m_vt . m_min . t . x ) > SSR_UV_TOLERANCE | | abs ( m_vt . m_min . t . y ) > SSR_UV_TOLERANCE ) // No horizontal nor vertical offset
return false ;
if ( abs ( m_vt . m_max . t . x - m_r . z ) > SSR_UV_TOLERANCE | | abs ( m_vt . m_max . t . y - m_r . w ) > SSR_UV_TOLERANCE ) // No texture width or height mag/min
return false ;
2020-06-03 17:49:51 +00:00
const int tw = 1 < < m_context - > TEX0 . TW ;
const int th = 1 < < m_context - > TEX0 . TH ;
2019-08-28 17:39:02 +00:00
if ( m_vt . m_max . t . x > tw | | m_vt . m_max . t . y > th ) // No UV wrapping
return false ;
}
// The draw call is a good candidate for using the SwSpriteRender to replace the GPU draw
// However, some draw attributes might not be supported yet by the SwSpriteRender,
// so if any bug occurs in using it, enabling debug build would probably
// make failing some of the assertions used in the SwSpriteRender to highlight its limitations.
// In that case, either the condition can be added here to discard the draw, or the
// SwSpriteRender can be improved by adding the missing features.
return true ;
}
2015-04-12 10:24:07 +00:00
template < bool linear >
void GSRendererHW : : RoundSpriteOffset ( )
{
//#define DEBUG_U
//#define DEBUG_V
2015-04-16 16:27:27 +00:00
# if defined(DEBUG_V) || defined(DEBUG_U)
2015-04-12 10:24:07 +00:00
bool debug = linear ;
2015-04-16 16:27:27 +00:00
# endif
2015-04-12 10:24:07 +00:00
size_t count = m_vertex . next ;
GSVertex * v = & m_vertex . buff [ 0 ] ;
for ( size_t i = 0 ; i < count ; i + = 2 ) {
2015-04-12 15:10:45 +00:00
// Performance note: if it had any impact on perf, someone would port it to SSE (AKA GSVector)
2015-04-12 10:24:07 +00:00
// Compute the coordinate of first and last texels (in native with a linear filtering)
2016-04-14 10:00:58 +00:00
int ox = m_context - > XYOFFSET . OFX ;
int X0 = v [ i ] . XYZ . X - ox ;
int X1 = v [ i + 1 ] . XYZ . X - ox ;
int Lx = ( v [ i + 1 ] . XYZ . X - v [ i ] . XYZ . X ) ;
float ax0 = alpha0 ( Lx , X0 , X1 ) ;
float ax1 = alpha1 ( Lx , X0 , X1 ) ;
uint16 tx0 = Interpolate_UV ( ax0 , v [ i ] . U , v [ i + 1 ] . U ) ;
uint16 tx1 = Interpolate_UV ( ax1 , v [ i ] . U , v [ i + 1 ] . U ) ;
2015-04-12 10:24:07 +00:00
# ifdef DEBUG_U
if ( debug ) {
fprintf ( stderr , " u0:%d and u1:%d \n " , v [ i ] . U , v [ i + 1 ] . U ) ;
fprintf ( stderr , " a0:%f and a1:%f \n " , ax0 , ax1 ) ;
fprintf ( stderr , " t0:%d and t1:%d \n " , tx0 , tx1 ) ;
}
# endif
2016-04-14 10:00:58 +00:00
int oy = m_context - > XYOFFSET . OFY ;
int Y0 = v [ i ] . XYZ . Y - oy ;
int Y1 = v [ i + 1 ] . XYZ . Y - oy ;
int Ly = ( v [ i + 1 ] . XYZ . Y - v [ i ] . XYZ . Y ) ;
float ay0 = alpha0 ( Ly , Y0 , Y1 ) ;
float ay1 = alpha1 ( Ly , Y0 , Y1 ) ;
uint16 ty0 = Interpolate_UV ( ay0 , v [ i ] . V , v [ i + 1 ] . V ) ;
uint16 ty1 = Interpolate_UV ( ay1 , v [ i ] . V , v [ i + 1 ] . V ) ;
2015-04-12 10:24:07 +00:00
# ifdef DEBUG_V
if ( debug ) {
fprintf ( stderr , " v0:%d and v1:%d \n " , v [ i ] . V , v [ i + 1 ] . V ) ;
fprintf ( stderr , " a0:%f and a1:%f \n " , ay0 , ay1 ) ;
fprintf ( stderr , " t0:%d and t1:%d \n " , ty0 , ty1 ) ;
}
# endif
# ifdef DEBUG_U
if ( debug )
fprintf ( stderr , " GREP_BEFORE %d => %d \n " , v [ i ] . U , v [ i + 1 ] . U ) ;
# endif
# ifdef DEBUG_V
if ( debug )
fprintf ( stderr , " GREP_BEFORE %d => %d \n " , v [ i ] . V , v [ i + 1 ] . V ) ;
# endif
# if 1
// Use rounded value of the newly computed texture coordinate. It ensures
// that sampling will remains inside texture boundary
//
2015-04-16 16:27:27 +00:00
// Note for bilinear: by definition it will never work correctly! A sligh modification
// of interpolation migth trigger a discard (with alpha testing)
// Let's use something simple that correct really bad case (for a couple of 2D games).
// I hope it won't create too much glitches.
2015-04-12 15:57:37 +00:00
if ( linear ) {
2015-04-16 16:27:27 +00:00
int Lu = v [ i + 1 ] . U - v [ i ] . U ;
// Note 32 is based on taisho-mononoke
if ( ( Lu > 0 ) & & ( Lu < = ( Lx + 32 ) ) ) {
v [ i + 1 ] . U - = 8 ;
2015-04-12 15:57:37 +00:00
}
2015-04-12 10:24:07 +00:00
} else {
2015-04-12 15:57:37 +00:00
if ( tx0 < = tx1 ) {
v [ i ] . U = tx0 ;
v [ i + 1 ] . U = tx1 + 16 ;
} else {
v [ i ] . U = tx0 + 15 ;
2015-04-14 16:39:26 +00:00
v [ i + 1 ] . U = tx1 ;
2015-04-12 15:57:37 +00:00
}
2015-04-12 10:24:07 +00:00
}
# endif
# if 1
2015-04-12 15:57:37 +00:00
if ( linear ) {
2015-04-16 16:27:27 +00:00
int Lv = v [ i + 1 ] . V - v [ i ] . V ;
if ( ( Lv > 0 ) & & ( Lv < = ( Ly + 32 ) ) ) {
v [ i + 1 ] . V - = 8 ;
2015-04-12 15:57:37 +00:00
}
2015-04-12 10:24:07 +00:00
} else {
2015-04-12 15:57:37 +00:00
if ( ty0 < = ty1 ) {
v [ i ] . V = ty0 ;
v [ i + 1 ] . V = ty1 + 16 ;
} else {
v [ i ] . V = ty0 + 15 ;
2015-04-14 16:39:26 +00:00
v [ i + 1 ] . V = ty1 ;
2015-04-12 15:57:37 +00:00
}
2015-04-12 10:24:07 +00:00
}
# endif
# ifdef DEBUG_U
if ( debug )
fprintf ( stderr , " GREP_AFTER %d => %d \n \n " , v [ i ] . U , v [ i + 1 ] . U ) ;
# endif
# ifdef DEBUG_V
if ( debug )
fprintf ( stderr , " GREP_AFTER %d => %d \n \n " , v [ i ] . V , v [ i + 1 ] . V ) ;
# endif
}
}
2012-01-05 02:40:24 +00:00
void GSRendererHW : : Draw ( )
{
2016-05-05 14:22:14 +00:00
if ( m_dev - > IsLost ( ) | | IsBadFrame ( ) ) {
2015-05-24 10:53:23 +00:00
GL_INS ( " Warning skipping a draw call (%d) " , s_n ) ;
2015-05-05 15:48:11 +00:00
return ;
}
2015-05-16 17:28:22 +00:00
GL_PUSH ( " HW Draw %d " , s_n ) ;
2012-01-05 02:40:24 +00:00
GSDrawingEnvironment & env = m_env ;
GSDrawingContext * context = m_context ;
2016-09-25 16:56:48 +00:00
const GSLocalMemory : : psm_t & tex_psm = GSLocalMemory : : m_psm [ m_context - > TEX0 . PSM ] ;
2019-02-15 13:35:21 +00:00
// Fix TEX0 size
if ( PRIM - > TME & & ! IsMipMapActive ( ) )
2019-05-30 13:43:16 +00:00
m_context - > ComputeFixedTEX0 ( m_vt . m_min . t . xyxy ( m_vt . m_max . t ) ) ;
2019-02-15 13:35:21 +00:00
2016-09-25 16:56:48 +00:00
// skip alpha test if possible
// Note: do it first so we know if frame/depth writes are masked
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 ;
// Note required to compute TryAlphaTest below. So do it now.
if ( PRIM - > TME & & tex_psm . pal > 0 )
m_mem . m_clut . Read32 ( context - > TEX0 , env . TEXA ) ;
// Test if we can optimize Alpha Test as a NOP
2016-10-05 07:41:28 +00:00
context - > TEST . ATE = context - > TEST . ATE & & ! GSRenderer : : TryAlphaTest ( fm , zm ) ;
2016-09-25 16:56:48 +00:00
context - > FRAME . FBMSK = fm ;
context - > ZBUF . ZMSK = zm ! = 0 ;
2012-01-05 02:40:24 +00:00
2015-06-05 20:37:34 +00:00
// It is allowed to use the depth and rt at the same location. However at least 1 must
2016-09-25 17:00:33 +00:00
// be disabled. Or the written value must be the same on both channels.
2015-07-04 08:52:43 +00:00
// 1/ GoW uses a Cd blending on a 24 bits buffer (no alpha)
2015-08-04 17:30:12 +00:00
// 2/ SuperMan really draws (0,0,0,0) color and a (0) 32-bits depth
// 3/ 50cents really draws (0,0,0,128) color and a (0) 24 bits depth
2015-07-09 14:19:11 +00:00
// Note: FF DoC has both buffer at same location but disable the depth test (write?) with ZTE = 0
2015-08-04 17:30:12 +00:00
const bool no_rt = ( context - > ALPHA . IsCd ( ) & & PRIM - > ABE & & ( context - > FRAME . PSM = = 1 ) ) ;
2016-02-17 18:58:27 +00:00
const bool no_ds = ! no_rt & & (
2016-09-25 17:00:33 +00:00
// Depth is always pass/fail (no read) and write are discarded (tekken 5). (Note: DATE is currently implemented with a stencil buffer => a depth/stencil buffer)
( zm ! = 0 & & m_context - > TEST . ZTST < = ZTST_ALWAYS & & ! m_context - > TEST . DATE ) | |
2016-02-17 18:58:27 +00:00
// Depth will be written through the RT
2016-09-25 17:00:33 +00:00
( context - > FRAME . FBP = = context - > ZBUF . ZBP & & ! PRIM - > TME & & zm = = 0 & & fm = = 0 & & context - > TEST . ZTE )
2016-02-17 18:58:27 +00:00
) ;
2015-06-05 20:37:34 +00:00
2016-04-29 21:38:46 +00:00
const bool draw_sprite_tex = PRIM - > TME & & ( m_vt . m_primclass = = GS_SPRITE_CLASS ) ;
const GSVector4 delta_p = m_vt . m_max . p - m_vt . m_min . p ;
2016-04-29 23:37:53 +00:00
bool single_page = ( delta_p . x < = 64.0f ) & & ( delta_p . y < = 64.0f ) ;
2016-04-29 21:38:46 +00:00
2016-04-29 16:00:23 +00:00
if ( m_channel_shuffle ) {
2016-04-29 21:38:46 +00:00
m_channel_shuffle = draw_sprite_tex & & ( m_context - > TEX0 . PSM = = PSM_PSMT8 ) & & single_page ;
2016-04-29 16:00:23 +00:00
if ( m_channel_shuffle ) {
2016-05-05 09:18:48 +00:00
GL_CACHE ( " Channel shuffle effect detected SKIP " ) ;
2016-04-29 16:00:23 +00:00
return ;
}
2016-04-29 21:38:46 +00:00
} else if ( draw_sprite_tex & & m_context - > FRAME . Block ( ) = = m_context - > TEX0 . TBP0 ) {
2016-04-28 17:39:57 +00:00
// Special post-processing effect
2016-10-02 16:28:40 +00:00
if ( ( m_context - > TEX0 . PSM = = PSM_PSMT8 ) & & single_page ) {
2016-05-03 06:52:35 +00:00
GL_INS ( " Channel shuffle effect detected " ) ;
m_channel_shuffle = true ;
2016-04-28 17:39:57 +00:00
} else {
2016-10-14 18:24:52 +00:00
GL_DBG ( " Special post-processing effect not supported " ) ;
2016-04-28 17:39:57 +00:00
m_channel_shuffle = false ;
}
} else {
m_channel_shuffle = false ;
}
2012-01-05 02:40:24 +00:00
GIFRegTEX0 TEX0 ;
TEX0 . TBP0 = context - > FRAME . Block ( ) ;
TEX0 . TBW = context - > FRAME . FBW ;
TEX0 . PSM = context - > FRAME . PSM ;
2015-06-05 20:37:34 +00:00
2016-05-05 16:01:44 +00:00
GSTextureCache : : Target * rt = NULL ;
GSTexture * rt_tex = NULL ;
if ( ! no_rt ) {
2016-10-03 18:34:04 +00:00
rt = m_tc - > LookupTarget ( TEX0 , m_width , m_height , GSTextureCache : : RenderTarget , true , fm ) ;
2016-05-05 16:01:44 +00:00
rt_tex = rt - > m_texture ;
}
2012-01-05 02:40:24 +00:00
TEX0 . TBP0 = context - > ZBUF . Block ( ) ;
TEX0 . TBW = context - > FRAME . FBW ;
TEX0 . PSM = context - > ZBUF . PSM ;
2016-05-05 16:01:44 +00:00
GSTextureCache : : Target * ds = NULL ;
GSTexture * ds_tex = NULL ;
if ( ! no_ds ) {
ds = m_tc - > LookupTarget ( TEX0 , m_width , m_height , GSTextureCache : : DepthStencil , context - > DepthWrite ( ) ) ;
ds_tex = ds - > m_texture ;
2012-01-05 02:40:24 +00:00
}
2019-04-24 12:27:59 +00:00
m_src = nullptr ;
2015-06-17 18:02:03 +00:00
m_texture_shuffle = false ;
2012-01-05 02:40:24 +00:00
if ( PRIM - > TME )
{
2016-09-24 07:57:01 +00:00
GIFRegCLAMP MIP_CLAMP = context - > CLAMP ;
2016-10-01 15:37:55 +00:00
int mxl = std : : min < int > ( ( int ) m_context - > TEX1 . MXL , 6 ) ;
m_lod = GSVector2i ( 0 , 0 ) ;
2016-09-24 07:57:01 +00:00
// Code from the SW renderer
if ( IsMipMapActive ( ) ) {
2016-09-26 17:42:07 +00:00
int interpolation = ( context - > TEX1 . MMIN & 1 ) + 1 ; // 1: round, 2: tri
2016-09-24 07:57:01 +00:00
2016-09-24 18:05:39 +00:00
int k = ( m_context - > TEX1 . K + 8 ) > > 4 ;
2016-09-24 07:57:01 +00:00
int lcm = m_context - > TEX1 . LCM ;
if ( ( int ) m_vt . m_lod . x > = mxl ) {
k = mxl ; // set lod to max level
lcm = 1 ; // constant lod
}
if ( PRIM - > FST ) {
ASSERT ( lcm = = 1 ) ;
ASSERT ( ( ( m_vt . m_min . t . uph ( m_vt . m_max . t ) = = GSVector4 : : zero ( ) ) . mask ( ) & 3 ) = = 3 ) ; // ratchet and clank (menu)
lcm = 1 ;
}
if ( lcm = = 1 ) {
2016-10-01 15:37:55 +00:00
m_lod . x = std : : max < int > ( k , 0 ) ;
m_lod . y = m_lod . x ;
2016-09-24 07:57:01 +00:00
} else {
// Not constant but who care !
2016-09-26 17:42:07 +00:00
if ( interpolation = = 2 ) {
// Mipmap Linear. Both layers are sampled, only take the big one
2016-10-01 15:37:55 +00:00
m_lod . x = std : : max < int > ( ( int ) floor ( m_vt . m_lod . x ) , 0 ) ;
2016-09-26 17:42:07 +00:00
} else {
2016-09-28 07:55:04 +00:00
// On GS lod is a fixed float number 7:4 (4 bit for the frac part)
2016-09-28 17:35:46 +00:00
#if 0
2016-10-01 15:37:55 +00:00
m_lod . x = std : : max < int > ( ( int ) round ( m_vt . m_lod . x + 0.0625 ) , 0 ) ;
2016-09-28 17:35:46 +00:00
# else
// Same as above with a bigger margin on rounding
// The goal is to avoid 1 undrawn pixels around the edge which trigger the load of the big
// layer.
if ( ceil ( m_vt . m_lod . x ) < m_vt . m_lod . y )
2016-10-01 15:37:55 +00:00
m_lod . x = std : : max < int > ( ( int ) round ( m_vt . m_lod . x + 0.0625 + 0.01 ) , 0 ) ;
2016-09-28 17:35:46 +00:00
else
2016-10-01 15:37:55 +00:00
m_lod . x = std : : max < int > ( ( int ) round ( m_vt . m_lod . x + 0.0625 ) , 0 ) ;
2016-09-28 17:35:46 +00:00
# endif
2016-09-26 17:42:07 +00:00
}
2016-10-01 15:37:55 +00:00
m_lod . y = std : : max < int > ( ( int ) ceil ( m_vt . m_lod . y ) , 0 ) ;
2016-09-24 07:57:01 +00:00
}
2016-10-01 15:37:55 +00:00
m_lod . x = std : : min < int > ( m_lod . x , mxl ) ;
m_lod . y = std : : min < int > ( m_lod . y , mxl ) ;
2016-09-26 17:42:07 +00:00
2016-10-01 15:37:55 +00:00
TEX0 = GetTex0Layer ( m_lod . x ) ;
2016-09-25 09:51:48 +00:00
2016-10-01 15:37:55 +00:00
MIP_CLAMP . MINU > > = m_lod . x ;
MIP_CLAMP . MINV > > = m_lod . x ;
MIP_CLAMP . MAXU > > = m_lod . x ;
MIP_CLAMP . MAXV > > = m_lod . x ;
2016-09-24 07:57:01 +00:00
2016-10-01 15:37:55 +00:00
for ( int i = 0 ; i < m_lod . x ; i + + ) {
2016-09-24 07:57:01 +00:00
m_vt . m_min . t * = 0.5f ;
m_vt . m_max . t * = 0.5f ;
}
2016-10-01 15:37:55 +00:00
GL_CACHE ( " Mipmap LOD %d %d (%f %f) new size %dx%d (K %d L %u) " , m_lod . x , m_lod . y , m_vt . m_lod . x , m_vt . m_lod . y , 1 < < TEX0 . TW , 1 < < TEX0 . TH , m_context - > TEX1 . K , m_context - > TEX1 . L ) ;
2016-09-25 09:51:48 +00:00
} else {
TEX0 = GetTex0Layer ( 0 ) ;
2016-09-24 07:57:01 +00:00
}
2016-09-26 18:34:38 +00:00
m_context - > offset . tex = m_mem . GetOffset ( TEX0 . TBP0 , TEX0 . TBW , TEX0 . PSM ) ;
2016-04-22 17:46:44 +00:00
2012-01-05 02:40:24 +00:00
GSVector4i r ;
2016-09-24 16:57:47 +00:00
GetTextureMinMax ( r , TEX0 , MIP_CLAMP , m_vt . IsLinear ( ) ) ;
2012-01-05 02:40:24 +00:00
2019-04-24 12:27:59 +00:00
m_src = tex_psm . depth ? m_tc - > LookupDepthSource ( TEX0 , env . TEXA , r ) : m_tc - > LookupSource ( TEX0 , env . TEXA , r ) ;
2012-01-05 02:40:24 +00:00
2016-10-01 15:38:38 +00:00
// Round 2
2016-10-02 08:51:29 +00:00
if ( IsMipMapActive ( ) & & m_mipmap = = 2 & & ! tex_psm . depth ) {
2016-10-01 15:38:38 +00:00
// Upload remaining texture layers
GSVector4 tmin = m_vt . m_min . t ;
GSVector4 tmax = m_vt . m_max . t ;
for ( int layer = m_lod . x + 1 ; layer < = m_lod . y ; layer + + ) {
const GIFRegTEX0 & MIP_TEX0 = GetTex0Layer ( layer ) ;
m_context - > offset . tex = m_mem . GetOffset ( MIP_TEX0 . TBP0 , MIP_TEX0 . TBW , MIP_TEX0 . PSM ) ;
MIP_CLAMP . MINU > > = 1 ;
MIP_CLAMP . MINV > > = 1 ;
MIP_CLAMP . MAXU > > = 1 ;
MIP_CLAMP . MAXV > > = 1 ;
m_vt . m_min . t * = 0.5f ;
m_vt . m_max . t * = 0.5f ;
GetTextureMinMax ( r , MIP_TEX0 , MIP_CLAMP , m_vt . IsLinear ( ) ) ;
2019-04-24 12:27:59 +00:00
m_src - > UpdateLayer ( MIP_TEX0 , r , layer - m_lod . x ) ;
2016-10-01 15:38:38 +00:00
}
m_vt . m_min . t = tmin ;
m_vt . m_max . t = tmax ;
}
2015-07-30 07:15:04 +00:00
// Hypothesis: texture shuffle is used as a postprocessing effect so texture will be an old target.
// Initially code also tested the RT but it gives too much false-positive
//
// Both input and output are 16 bits and texture was initially 32 bits!
2016-04-22 17:46:44 +00:00
m_texture_shuffle = ( GSLocalMemory : : m_psm [ context - > FRAME . PSM ] . bpp = = 16 ) & & ( tex_psm . bpp = = 16 )
2019-04-24 12:27:59 +00:00
& & draw_sprite_tex & & m_src - > m_32_bits_fmt ;
2015-07-30 07:15:04 +00:00
2020-06-03 16:58:10 +00:00
// Okami mustn't call this code
GSdx-D3D: Texture and channel shuffle improvements.
Texture Shuffle changes:
Always Enable Texture shuffle on D3D10/11.
Previously Texture shuffle was enabled if CRC hack
level was below Full, this was kinda not good since
D3D also relies on CRC hacks on Full so you could either
stick with texture shuffle or crc hacks.
Texture shuffle is not supported on D3D9, however we can do a partial
port where instead of vertical lines with the effect we get the effect
on the entire screen. Better than nothing I suppose.
Ported some of the code from OpenGL to D3D
( just a copy - paste job :) ),
part of the code misses a dedicated shader but we can still
use it to fix various issues on many games.
List of affected games tested so far:
The Godfather, Final Fight Streetwise, The Suffering Ties that Bind,
Urban Chaos have their vertical lines issues fixed
(highly possible for other games as well), MGS and Stolen see an improvement
but they are still broken without crc hacks. Other games that suffered
similar issues are probably affected as well.
Channel Shuffle changes:
Update Channel Shuffle detection. A lot of games should see an improvement,
MGS, Urban Chaos, Stolen have their top left corner issues resolved.
Other games should be affected as well that use similar logic.
They still miss a shader so some effects are still broken/show glitches
but it's a nice improvement for D3D users.
Shared changes:
Texture Shuffle and Channel shuffle have been moved to their
own dedicated functions. Should make things a bit cleaner.
Move part of the code for Texture Shuffle to GSRendererHW to be shared
across all HW renderers, should aboid copy paste/duplicate code.
2018-03-18 09:08:36 +00:00
if ( m_texture_shuffle & & m_vertex . next < 3 & & PRIM - > FST & & ( m_context - > FRAME . FBMSK = = 0 ) ) {
// Avious dubious call to m_texture_shuffle on 16 bits games
// The pattern is severals column of 8 pixels. A single sprite
// smell fishy but a big sprite is wrong.
2020-06-03 16:58:10 +00:00
// Shadow of Memories/Destiny shouldn't call this code.
// Causes shadow flickering.
GSdx-D3D: Texture and channel shuffle improvements.
Texture Shuffle changes:
Always Enable Texture shuffle on D3D10/11.
Previously Texture shuffle was enabled if CRC hack
level was below Full, this was kinda not good since
D3D also relies on CRC hacks on Full so you could either
stick with texture shuffle or crc hacks.
Texture shuffle is not supported on D3D9, however we can do a partial
port where instead of vertical lines with the effect we get the effect
on the entire screen. Better than nothing I suppose.
Ported some of the code from OpenGL to D3D
( just a copy - paste job :) ),
part of the code misses a dedicated shader but we can still
use it to fix various issues on many games.
List of affected games tested so far:
The Godfather, Final Fight Streetwise, The Suffering Ties that Bind,
Urban Chaos have their vertical lines issues fixed
(highly possible for other games as well), MGS and Stolen see an improvement
but they are still broken without crc hacks. Other games that suffered
similar issues are probably affected as well.
Channel Shuffle changes:
Update Channel Shuffle detection. A lot of games should see an improvement,
MGS, Urban Chaos, Stolen have their top left corner issues resolved.
Other games should be affected as well that use similar logic.
They still miss a shader so some effects are still broken/show glitches
but it's a nice improvement for D3D users.
Shared changes:
Texture Shuffle and Channel shuffle have been moved to their
own dedicated functions. Should make things a bit cleaner.
Move part of the code for Texture Shuffle to GSRendererHW to be shared
across all HW renderers, should aboid copy paste/duplicate code.
2018-03-18 09:08:36 +00:00
GSVertex * v = & m_vertex . buff [ 0 ] ;
2020-06-03 16:58:10 +00:00
m_texture_shuffle = ( ( v [ 1 ] . U - v [ 0 ] . U ) < 256 ) | |
// Tomb Raider Angel of Darkness relies on this behavior to produce a fog effect.
// In this case, the address of the framebuffer and texture are the same.
// The game will take RG => BA and then the BA => RG of next pixels.
// However, only RG => BA needs to be emulated because RG isn't used.
m_context - > FRAME . Block ( ) = = m_context - > TEX0 . TBP0 | |
// DMC3, Onimusha 3 rely on this behavior.
// They do fullscreen rectangle with scissor, then shift by 8 pixels, not done with recursion.
// So we check if it's a TS effect by checking the scissor.
( ( m_context - > SCISSOR . SCAX1 - m_context - > SCISSOR . SCAX0 ) < 32 ) ;
GL_INS ( " WARNING: Possible misdetection of effect, texture shuffle is %s " , m_texture_shuffle ? " Enabled " : " Disabled " ) ;
GSdx-D3D: Texture and channel shuffle improvements.
Texture Shuffle changes:
Always Enable Texture shuffle on D3D10/11.
Previously Texture shuffle was enabled if CRC hack
level was below Full, this was kinda not good since
D3D also relies on CRC hacks on Full so you could either
stick with texture shuffle or crc hacks.
Texture shuffle is not supported on D3D9, however we can do a partial
port where instead of vertical lines with the effect we get the effect
on the entire screen. Better than nothing I suppose.
Ported some of the code from OpenGL to D3D
( just a copy - paste job :) ),
part of the code misses a dedicated shader but we can still
use it to fix various issues on many games.
List of affected games tested so far:
The Godfather, Final Fight Streetwise, The Suffering Ties that Bind,
Urban Chaos have their vertical lines issues fixed
(highly possible for other games as well), MGS and Stolen see an improvement
but they are still broken without crc hacks. Other games that suffered
similar issues are probably affected as well.
Channel Shuffle changes:
Update Channel Shuffle detection. A lot of games should see an improvement,
MGS, Urban Chaos, Stolen have their top left corner issues resolved.
Other games should be affected as well that use similar logic.
They still miss a shader so some effects are still broken/show glitches
but it's a nice improvement for D3D users.
Shared changes:
Texture Shuffle and Channel shuffle have been moved to their
own dedicated functions. Should make things a bit cleaner.
Move part of the code for Texture Shuffle to GSRendererHW to be shared
across all HW renderers, should aboid copy paste/duplicate code.
2018-03-18 09:08:36 +00:00
}
2015-07-30 07:15:04 +00:00
// Texture shuffle is not yet supported with strange clamp mode
ASSERT ( ! m_texture_shuffle | | ( context - > CLAMP . WMS < 3 & & context - > CLAMP . WMT < 3 ) ) ;
2016-04-29 16:00:23 +00:00
2019-04-24 12:27:59 +00:00
if ( m_src - > m_target & & m_context - > TEX0 . PSM = = PSM_PSMT8 & & single_page & & draw_sprite_tex ) {
2016-04-29 16:00:23 +00:00
GL_INS ( " Channel shuffle effect detected (2nd shot) " ) ;
m_channel_shuffle = true ;
} else {
m_channel_shuffle = false ;
}
2015-07-30 07:15:04 +00:00
}
if ( rt ) {
// Be sure texture shuffle detection is properly propagated
// Otherwise set or clear the flag (Code in texture cache only set the flag)
// Note: it is important to clear the flag when RT is used as a real 16 bits target.
2016-04-22 17:46:44 +00:00
rt - > m_32_bits_fmt = m_texture_shuffle | | ( GSLocalMemory : : m_psm [ context - > FRAME . PSM ] . bpp ! = 16 ) ;
2012-01-05 02:40:24 +00:00
}
if ( s_dump )
{
uint64 frame = m_perfmon . GetFrame ( ) ;
2017-05-26 15:26:46 +00:00
std : : string s ;
2012-01-05 02:40:24 +00:00
2015-05-23 10:23:05 +00:00
if ( s_n > = s_saven ) {
// Dump Register state
s = format ( " %05d_context.txt " , s_n ) ;
2016-07-14 17:41:21 +00:00
m_env . Dump ( m_dump_root + s ) ;
m_context - > Dump ( m_dump_root + s ) ;
2015-05-23 10:23:05 +00:00
}
2019-04-24 12:27:59 +00:00
if ( s_savet & & s_n > = s_saven & & m_src )
2012-01-05 02:40:24 +00:00
{
2016-09-27 16:59:06 +00:00
s = format ( " %05d_f%lld_itex_%05x_%s_%d%d_%02x_%02x_%02x_%02x.dds " ,
2016-09-05 18:34:29 +00:00
s_n , frame , ( int ) context - > TEX0 . TBP0 , psm_str ( context - > TEX0 . PSM ) ,
2012-01-05 02:40:24 +00:00
( int ) context - > CLAMP . WMS , ( int ) context - > CLAMP . WMT ,
( int ) context - > CLAMP . MINU , ( int ) context - > CLAMP . MAXU ,
( int ) context - > CLAMP . MINV , ( int ) context - > CLAMP . MAXV ) ;
2019-04-24 12:27:59 +00:00
m_src - > m_texture - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
2019-04-24 12:27:59 +00:00
if ( m_src - > m_palette )
2012-01-05 02:40:24 +00:00
{
2016-09-27 16:59:06 +00:00
s = format ( " %05d_f%lld_itpx_%05x_%s.dds " , s_n , frame , context - > TEX0 . CBP , psm_str ( context - > TEX0 . CPSM ) ) ;
2012-01-05 02:40:24 +00:00
2019-04-24 12:27:59 +00:00
m_src - > m_palette - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
}
}
if ( s_save & & s_n > = s_saven )
{
2016-09-05 18:34:29 +00:00
s = format ( " %05d_f%lld_rt0_%05x_%s.bmp " , s_n , frame , context - > FRAME . Block ( ) , psm_str ( context - > FRAME . PSM ) ) ;
2012-01-05 02:40:24 +00:00
2015-06-05 20:37:34 +00:00
if ( rt )
2016-07-14 17:41:21 +00:00
rt - > m_texture - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
}
if ( s_savez & & s_n > = s_saven )
{
2016-09-05 18:34:29 +00:00
s = format ( " %05d_f%lld_rz0_%05x_%s.bmp " , s_n , frame , context - > ZBUF . Block ( ) , psm_str ( context - > ZBUF . PSM ) ) ;
2012-01-05 02:40:24 +00:00
2015-09-10 12:42:54 +00:00
if ( ds_tex )
2016-07-14 17:41:21 +00:00
ds_tex - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
}
}
2016-03-19 10:43:27 +00:00
// The rectangle of the draw
2019-04-22 11:28:34 +00:00
m_r = GSVector4i ( m_vt . m_min . p . xyxy ( m_vt . m_max . p ) ) . rintersect ( GSVector4i ( context - > scissor . in ) ) ;
2016-03-19 10:43:27 +00:00
2019-04-24 12:27:59 +00:00
if ( m_hacks . m_oi & & ! ( this - > * m_hacks . m_oi ) ( rt_tex , ds_tex , m_src ) )
2012-01-05 02:40:24 +00:00
{
2016-03-19 10:43:27 +00:00
GL_INS ( " Warning skipping a draw call (%d) " , s_n ) ;
return ;
}
2019-04-24 12:27:59 +00:00
if ( ! OI_BlitFMV ( rt , m_src , m_r ) ) {
2016-03-19 10:43:27 +00:00
GL_INS ( " Warning skipping a draw call (%d) " , s_n ) ;
2012-01-05 02:40:24 +00:00
return ;
}
2018-12-31 00:20:16 +00:00
if ( m_userhacks_enabled_gs_mem_clear ) {
2016-09-20 16:57:07 +00:00
// Constant Direct Write without texture/test/blending (aka a GS mem clear)
2016-09-30 17:15:25 +00:00
if ( ( m_vt . m_primclass = = GS_SPRITE_CLASS ) & & ! PRIM - > TME // Direct write
& & ( ! PRIM - > ABE | | m_context - > ALPHA . IsOpaque ( ) ) // No transparency
2016-09-20 16:57:07 +00:00
& & ( m_context - > FRAME . FBMSK = = 0 ) // no color mask
& & ! m_context - > TEST . ATE // no alpha test
& & ( ! m_context - > TEST . ZTE | | m_context - > TEST . ZTST = = ZTST_ALWAYS ) // no depth test
& & ( m_vt . m_eq . rgba = = 0xFFFF ) // constant color write
2019-04-22 11:28:34 +00:00
& & m_r . x = = 0 & & m_r . y = = 0 ) { // Likely full buffer write
2016-09-20 16:57:07 +00:00
OI_GsMemClear ( ) ;
OI_DoubleHalfClear ( rt_tex , ds_tex ) ;
}
2016-03-19 12:35:23 +00:00
}
2015-05-16 17:28:22 +00:00
// A couple of hack to avoid upscaling issue. So far it seems to impacts mostly sprite
2016-05-09 07:43:24 +00:00
// Note: first hack corrects both position and texture coordinate
// Note: second hack corrects only the texture coordinate
if ( ( m_upscale_multiplier > 1 ) & & ( m_vt . m_primclass = = GS_SPRITE_CLASS ) ) {
2015-04-07 17:18:24 +00:00
size_t count = m_vertex . next ;
GSVertex * v = & m_vertex . buff [ 0 ] ;
2015-04-03 23:21:46 +00:00
// Hack to avoid vertical black line in various games (ace combat/tekken)
if ( m_userhacks_align_sprite_X ) {
// Note for performance reason I do the check only once on the first
// primitive
int win_position = v [ 1 ] . XYZ . X - context - > XYOFFSET . OFX ;
const bool unaligned_position = ( ( win_position & 0xF ) = = 8 ) ;
2015-04-16 16:08:32 +00:00
const bool unaligned_texture = ( ( v [ 1 ] . U & 0xF ) = = 0 ) & & PRIM - > FST ; // I'm not sure this check is useful
const bool hole_in_vertex = ( count < 4 ) | | ( v [ 1 ] . XYZ . X ! = v [ 2 ] . XYZ . X ) ;
if ( hole_in_vertex & & unaligned_position & & ( unaligned_texture | | ! PRIM - > FST ) ) {
2015-04-03 23:21:46 +00:00
// Normaly vertex are aligned on full pixels and texture in half
// pixels. Let's extend the coverage of an half-pixel to avoid
// hole after upscaling
for ( size_t i = 0 ; i < count ; i + = 2 ) {
v [ i + 1 ] . XYZ . X + = 8 ;
2015-04-16 16:08:32 +00:00
// I really don't know if it is a good idea. Neither what to do for !PRIM->FST
if ( unaligned_texture )
2015-04-03 23:21:46 +00:00
v [ i + 1 ] . U + = 8 ;
}
2015-04-03 19:02:25 +00:00
}
}
2016-05-09 07:43:24 +00:00
// Noting to do if no texture is sampled
if ( PRIM - > FST & & draw_sprite_tex ) {
2015-04-15 07:40:06 +00:00
if ( ( m_userhacks_round_sprite_offset > 1 ) | | ( m_userhacks_round_sprite_offset = = 1 & & ! m_vt . IsLinear ( ) ) ) {
if ( m_vt . IsLinear ( ) )
RoundSpriteOffset < true > ( ) ;
else
RoundSpriteOffset < false > ( ) ;
}
} else {
; // vertical line in Yakuza (note check m_userhacks_align_sprite_X behavior)
2015-04-09 17:51:50 +00:00
}
2015-04-03 16:11:47 +00:00
}
2012-01-05 02:40:24 +00:00
//
2019-04-24 12:27:59 +00:00
DrawPrims ( rt_tex , ds_tex , m_src ) ;
2012-01-05 02:40:24 +00:00
//
context - > TEST = TEST ;
context - > FRAME = FRAME ;
context - > ZBUF = ZBUF ;
//
2016-03-10 17:28:08 +00:00
// Help to detect rendering outside of the framebuffer
# if _DEBUG
2019-04-22 11:28:34 +00:00
if ( m_upscale_multiplier * m_r . z > m_width ) {
GL_INS ( " ERROR: RT width is too small only %d but require %d " , m_width , m_upscale_multiplier * m_r . z ) ;
2016-03-10 17:28:08 +00:00
}
2019-04-22 11:28:34 +00:00
if ( m_upscale_multiplier * m_r . w > m_height ) {
GL_INS ( " ERROR: RT height is too small only %d but require %d " , m_height , m_upscale_multiplier * m_r . w ) ;
2016-03-10 17:28:08 +00:00
}
# endif
2015-06-05 20:37:34 +00:00
if ( fm ! = 0xffffffff & & rt )
2012-01-05 02:40:24 +00:00
{
2016-03-06 13:24:20 +00:00
//rt->m_valid = rt->m_valid.runion(r);
2019-04-22 11:28:34 +00:00
rt - > UpdateValidity ( m_r ) ;
2012-01-05 02:40:24 +00:00
2019-04-22 11:28:34 +00:00
m_tc - > InvalidateVideoMem ( context - > offset . fb , m_r , false ) ;
2015-06-05 21:37:06 +00:00
m_tc - > InvalidateVideoMemType ( GSTextureCache : : DepthStencil , context - > FRAME . Block ( ) ) ;
2012-01-05 02:40:24 +00:00
}
2015-08-04 17:30:12 +00:00
if ( zm ! = 0xffffffff & & ds )
2012-01-05 02:40:24 +00:00
{
2016-03-06 13:24:20 +00:00
//ds->m_valid = ds->m_valid.runion(r);
2019-04-22 11:28:34 +00:00
ds - > UpdateValidity ( m_r ) ;
2012-01-05 02:40:24 +00:00
2019-04-22 11:28:34 +00:00
m_tc - > InvalidateVideoMem ( context - > offset . zb , m_r , false ) ;
2015-06-05 21:37:06 +00:00
m_tc - > InvalidateVideoMemType ( GSTextureCache : : RenderTarget , context - > ZBUF . Block ( ) ) ;
2012-01-05 02:40:24 +00:00
}
//
2015-06-14 08:19:15 +00:00
if ( m_hacks . m_oo )
2012-01-05 02:40:24 +00:00
{
( this - > * m_hacks . m_oo ) ( ) ;
}
if ( s_dump )
{
uint64 frame = m_perfmon . GetFrame ( ) ;
2017-05-26 15:26:46 +00:00
std : : string s ;
2012-01-05 02:40:24 +00:00
if ( s_save & & s_n > = s_saven )
{
2016-09-05 18:34:29 +00:00
s = format ( " %05d_f%lld_rt1_%05x_%s.bmp " , s_n , frame , context - > FRAME . Block ( ) , psm_str ( context - > FRAME . PSM ) ) ;
2012-01-05 02:40:24 +00:00
2015-06-05 20:37:34 +00:00
if ( rt )
2016-07-14 17:41:21 +00:00
rt - > m_texture - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
}
if ( s_savez & & s_n > = s_saven )
{
2016-09-05 18:34:29 +00:00
s = format ( " %05d_f%lld_rz1_%05x_%s.bmp " , s_n , frame , context - > ZBUF . Block ( ) , psm_str ( context - > ZBUF . PSM ) ) ;
2012-01-05 02:40:24 +00:00
2015-09-25 18:59:36 +00:00
if ( ds_tex )
2016-07-14 17:41:21 +00:00
ds_tex - > Save ( m_dump_root + s ) ;
2012-01-05 02:40:24 +00:00
}
2015-08-05 17:10:55 +00:00
if ( s_savel > 0 & & ( s_n - s_saven ) > s_savel )
{
2015-05-05 15:48:11 +00:00
s_dump = 0 ;
}
2012-01-05 02:40:24 +00:00
}
# ifdef DISABLE_HW_TEXTURE_CACHE
2015-06-05 20:37:34 +00:00
if ( rt )
2019-06-16 22:11:47 +00:00
m_tc - > Read ( rt , m_r ) ;
2012-01-05 02:40:24 +00:00
# endif
}
// hacks
GSRendererHW : : Hacks : : 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 )
{
2019-07-19 16:06:40 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : BigMuthaTruckers , CRC : : RegionCount , & GSRendererHW : : OI_BigMuthaTruckers ) ) ;
2020-06-16 19:29:59 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : DBZBT2 , CRC : : RegionCount , & GSRendererHW : : OI_DBZBTGames ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : DBZBT3 , CRC : : RegionCount , & GSRendererHW : : OI_DBZBTGames ) ) ;
2012-01-05 02:40:24 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : FFXII , CRC : : EU , & GSRendererHW : : OI_FFXII ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : FFX , CRC : : RegionCount , & GSRendererHW : : OI_FFX ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : MetalSlug6 , CRC : : RegionCount , & GSRendererHW : : OI_MetalSlug6 ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : RozenMaidenGebetGarden , CRC : : RegionCount , & GSRendererHW : : OI_RozenMaidenGebetGarden ) ) ;
2019-05-14 20:17:23 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : SonicUnleashed , CRC : : RegionCount , & GSRendererHW : : OI_SonicUnleashed ) ) ;
2015-07-05 15:32:57 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : SuperManReturns , CRC : : RegionCount , & GSRendererHW : : OI_SuperManReturns ) ) ;
2016-05-16 08:55:02 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : ArTonelico2 , CRC : : RegionCount , & GSRendererHW : : OI_ArTonelico2 ) ) ;
2019-07-12 23:05:27 +00:00
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : Jak2 , CRC : : RegionCount , & GSRendererHW : : OI_JakGames ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : Jak3 , CRC : : RegionCount , & GSRendererHW : : OI_JakGames ) ) ;
m_oi_list . push_back ( HackEntry < OI_Ptr > ( CRC : : JakX , CRC : : RegionCount , & GSRendererHW : : OI_JakGames ) ) ;
2016-09-18 15:25:53 +00:00
2012-01-05 02:40:24 +00:00
m_oo_list . push_back ( HackEntry < OO_Ptr > ( CRC : : MajokkoALaMode2 , CRC : : RegionCount , & GSRendererHW : : OO_MajokkoALaMode2 ) ) ;
m_cu_list . push_back ( HackEntry < CU_Ptr > ( CRC : : MajokkoALaMode2 , CRC : : RegionCount , & GSRendererHW : : CU_MajokkoALaMode2 ) ) ;
m_cu_list . push_back ( HackEntry < CU_Ptr > ( CRC : : TalesOfAbyss , CRC : : RegionCount , & GSRendererHW : : CU_TalesOfAbyss ) ) ;
}
void GSRendererHW : : Hacks : : SetGameCRC ( 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 ] ;
2015-06-14 17:42:11 +00:00
if ( game . flags & CRC : : PointListPalette ) {
2012-01-05 02:40:24 +00:00
ASSERT ( m_oi = = NULL ) ;
m_oi = & GSRendererHW : : OI_PointListPalette ;
}
2015-06-14 17:42:11 +00:00
}
2016-09-20 16:57:07 +00:00
// Trick to do a fast clear on the GS
// Set frame buffer pointer on the start of the buffer. Set depth buffer pointer on the half buffer
// FB + depth write will fill the full buffer.
void GSRendererHW : : OI_DoubleHalfClear ( GSTexture * rt , GSTexture * ds )
2015-06-14 17:42:11 +00:00
{
2016-09-20 16:57:07 +00:00
// Note gs mem clear must be tested before calling this function
// Limit further to unmask Z write
if ( ! m_context - > ZBUF . ZMSK & & rt & & ds ) {
2016-09-18 10:20:09 +00:00
const GSVertex * v = & m_vertex . buff [ 0 ] ;
const GSLocalMemory : : psm_t & frame_psm = GSLocalMemory : : m_psm [ m_context - > FRAME . PSM ] ;
2016-10-02 08:18:15 +00:00
//const GSLocalMemory::psm_t& depth_psm = GSLocalMemory::m_psm[m_context->ZBUF.PSM];
2016-09-18 10:20:09 +00:00
// Z and color must be constant and the same
2016-09-18 15:24:33 +00:00
if ( m_vt . m_eq . rgba ! = 0xFFFF | | ! m_vt . m_eq . z | | v [ 1 ] . XYZ . Z ! = v [ 1 ] . RGBAQ . u32 [ 0 ] )
2016-09-20 16:57:07 +00:00
return ;
2016-09-17 08:28:20 +00:00
2016-09-30 20:25:24 +00:00
// Format doesn't have the same size. It smells fishy (xmen...)
//if (frame_psm.trbpp != depth_psm.trbpp)
// return;
2016-09-17 08:28:20 +00:00
2016-09-18 10:20:09 +00:00
// Size of the current draw
2016-11-04 18:12:12 +00:00
uint32 w_pages = static_cast < uint32 > ( roundf ( m_vt . m_max . p . x / frame_psm . pgs . x ) ) ;
uint32 h_pages = static_cast < uint32 > ( roundf ( m_vt . m_max . p . y / frame_psm . pgs . y ) ) ;
2016-09-18 10:20:09 +00:00
uint32 written_pages = w_pages * h_pages ;
2016-09-16 21:57:33 +00:00
2016-09-18 10:20:09 +00:00
// Frame and depth pointer can be inverted
2016-09-16 21:57:33 +00:00
uint32 base ;
uint32 half ;
if ( m_context - > FRAME . FBP > m_context - > ZBUF . ZBP ) {
base = m_context - > ZBUF . ZBP ;
half = m_context - > FRAME . FBP ;
} else {
base = m_context - > FRAME . FBP ;
half = m_context - > ZBUF . ZBP ;
}
2016-09-18 10:20:09 +00:00
// If both buffers are side by side we can expect a fast clear in on-going
if ( half < = ( base + written_pages ) ) {
uint32 color = v [ 1 ] . RGBAQ . u32 [ 0 ] ;
2019-02-26 14:36:28 +00:00
bool clear_depth = ( m_context - > FRAME . FBP > m_context - > ZBUF . ZBP ) ;
2016-09-17 08:28:20 +00:00
2019-02-26 14:36:28 +00:00
GL_INS ( " OI_DoubleHalfClear:%s: base %x half %x. w_pages %d h_pages %d fbw %d. Color %x " ,
clear_depth ? " depth " : " target " , base < < 5 , half < < 5 , w_pages , h_pages , m_context - > FRAME . FBW , color ) ;
2016-09-18 10:20:09 +00:00
2019-02-26 14:36:28 +00:00
// Commit texture with a factor 2 on the height
GSTexture * t = clear_depth ? ds : rt ;
GSVector4i commitRect = ComputeBoundingBox ( t - > GetScale ( ) , t - > GetSize ( ) ) ;
t - > CommitRegion ( GSVector2i ( commitRect . z , 2 * commitRect . w ) ) ;
if ( clear_depth ) {
2016-09-18 10:20:09 +00:00
// Only pure clear are supported for depth
ASSERT ( color = = 0 ) ;
2019-02-26 14:36:28 +00:00
m_dev - > ClearDepth ( t ) ;
2016-09-16 21:57:33 +00:00
} else {
2019-02-26 14:36:28 +00:00
m_dev - > ClearRenderTarget ( t , color ) ;
2016-09-16 21:57:33 +00:00
}
}
}
}
2016-03-19 12:35:23 +00:00
// Note: hack is safe, but it could impact the perf a little (normally games do only a couple of clear by frame)
void GSRendererHW : : OI_GsMemClear ( )
{
2016-09-20 16:57:07 +00:00
// Note gs mem clear must be tested before calling this function
// Limit it further to a full screen 0 write
if ( ( m_vertex . next = = 2 ) & & m_vt . m_min . c . eq ( GSVector4i ( 0 ) ) ) {
2016-03-26 10:27:31 +00:00
GSOffset * off = m_context - > offset . fb ;
GSVector4i r = GSVector4i ( m_vt . m_min . p . xyxy ( m_vt . m_max . p ) ) . rintersect ( GSVector4i ( m_context - > scissor . in ) ) ;
2016-08-03 09:20:56 +00:00
// Limit the hack to a single fullscreen clear. Some games might use severals column to clear a screen
// but hopefully it will be enough.
if ( r . width ( ) < = 128 | | r . height ( ) < = 128 )
return ;
2016-03-26 10:27:31 +00:00
2016-08-03 09:20:56 +00:00
GL_INS ( " OI_GsMemClear (%d,%d => %d,%d) " , r . x , r . y , r . z , r . w ) ;
2016-03-28 08:43:34 +00:00
int format = GSLocalMemory : : m_psm [ m_context - > FRAME . PSM ] . fmt ;
2016-04-28 15:13:14 +00:00
// FIXME: loop can likely be optimized with AVX/SSE. Pixels aren't
// linear but the value will be done for all pixels of a block.
2016-05-23 17:23:04 +00:00
// FIXME: maybe we could limit the write to the top and bottom row page.
2016-03-28 08:43:34 +00:00
if ( format = = 0 ) {
// Based on WritePixel32
for ( int y = r . top ; y < r . bottom ; y + + )
{
uint32 * RESTRICT d = & m_mem . m_vm32 [ off - > pixel . row [ y ] ] ;
int * RESTRICT col = off - > pixel . col [ 0 ] ;
for ( int x = r . left ; x < r . right ; x + + )
{
d [ col [ x ] ] = 0 ; // Here the constant color
}
}
} else if ( format = = 1 ) {
// Based on WritePixel24
for ( int y = r . top ; y < r . bottom ; y + + )
{
uint32 * RESTRICT d = & m_mem . m_vm32 [ off - > pixel . row [ y ] ] ;
int * RESTRICT col = off - > pixel . col [ 0 ] ;
2016-03-19 12:35:23 +00:00
2016-03-28 08:43:34 +00:00
for ( int x = r . left ; x < r . right ; x + + )
{
d [ col [ x ] ] & = 0xff000000 ; // Clear the color
}
}
} else if ( format = = 2 ) {
; // Hack is used for FMV which are likely 24/32 bits. Let's keep the for reference
#if 0
// Based on WritePixel16
for ( int y = r . top ; y < r . bottom ; y + + )
2016-03-26 10:27:31 +00:00
{
2016-03-28 08:43:34 +00:00
uint32 * RESTRICT d = & m_mem . m_vm16 [ off - > pixel . row [ y ] ] ;
int * RESTRICT col = off - > pixel . col [ 0 ] ;
for ( int x = r . left ; x < r . right ; x + + )
{
d [ col [ x ] ] = 0 ; // Here the constant color
}
2016-03-19 12:35:23 +00:00
}
2016-03-28 08:43:34 +00:00
# endif
2016-03-19 12:35:23 +00:00
}
}
}
2016-03-19 10:43:27 +00:00
bool GSRendererHW : : OI_BlitFMV ( GSTextureCache : : Target * _rt , GSTextureCache : : Source * tex , const GSVector4i & r_draw )
{
2017-01-02 20:14:54 +00:00
if ( r_draw . w > 1024 & & ( m_vt . m_primclass = = GS_SPRITE_CLASS ) & & ( m_vertex . next = = 2 ) & & PRIM - > TME & & ! PRIM - > ABE & & tex & & ! tex - > m_target & & m_context - > TEX0 . TBW > 0 ) {
2016-03-19 10:43:27 +00:00
GL_PUSH ( " OI_BlitFMV " ) ;
GL_INS ( " OI_BlitFMV " ) ;
// The draw is done past the RT at the location of the texture. To avoid various upscaling mess
// We will blit the data from the top to the bottom of the texture manually.
// Expected memory representation
// -----------------------------------------------------------------
// RT (2 half frame)
// -----------------------------------------------------------------
// Top of Texture (full height frame)
//
// Bottom of Texture (half height frame, will be the copy of Top texture after the draw)
// -----------------------------------------------------------------
// sRect is the top of texture
int tw = ( int ) ( 1 < < m_context - > TEX0 . TW ) ;
int th = ( int ) ( 1 < < m_context - > TEX0 . TH ) ;
GSVector4 sRect ;
sRect . x = m_vt . m_min . t . x / tw ;
sRect . y = m_vt . m_min . t . y / th ;
sRect . z = m_vt . m_max . t . x / tw ;
sRect . w = m_vt . m_max . t . y / th ;
// Compute the Bottom of texture rectangle
ASSERT ( m_context - > TEX0 . TBP0 > m_context - > FRAME . Block ( ) ) ;
int offset = ( m_context - > TEX0 . TBP0 - m_context - > FRAME . Block ( ) ) / m_context - > TEX0 . TBW ;
GSVector4i r_texture ( r_draw ) ;
r_texture . y - = offset ;
r_texture . w - = offset ;
GSVector4 dRect ( r_texture ) ;
// Do the blit. With a Copy mess to avoid issue with limited API (dx)
// m_dev->StretchRect(tex->m_texture, sRect, tex->m_texture, dRect);
GSVector4i r_full ( 0 , 0 , tw , th ) ;
2019-01-14 21:28:23 +00:00
if ( GSTexture * rt = m_dev - > CreateRenderTarget ( tw , th ) ) {
2016-03-19 10:43:27 +00:00
m_dev - > CopyRect ( tex - > m_texture , rt , r_full ) ;
m_dev - > StretchRect ( tex - > m_texture , sRect , rt , dRect ) ;
m_dev - > CopyRect ( rt , tex - > m_texture , r_full ) ;
m_dev - > Recycle ( rt ) ;
}
// Copy back the texture into the GS mem. I don't know why but it will be
// reuploaded again later
m_tc - > Read ( tex , r_texture ) ;
m_tc - > InvalidateVideoMemSubTarget ( _rt ) ;
return false ; // skip current draw
}
// Nothing to see keep going
return true ;
}
2015-06-04 16:52:58 +00:00
// OI (others input?/implementation?) hacks replace current draw call
2019-07-19 16:06:40 +00:00
bool GSRendererHW : : OI_BigMuthaTruckers ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
// Rendering pattern:
// CRTC frontbuffer at 0x0 is interlaced (half vertical resolution),
// game needs to do a depth effect (so green channel to alpha),
// but there is a vram limitation so green is pushed into the alpha channel of the CRCT buffer,
// vertical resolution is half so only half is processed at once
// We, however, don't have this limitation so we'll replace the draw with a full-screen TS.
GIFRegTEX0 Texture = m_context - > TEX0 ;
GIFRegTEX0 Frame ;
Frame . TBW = m_context - > FRAME . FBW ;
Frame . TBP0 = m_context - > FRAME . FBP ;
Frame . TBP0 = m_context - > FRAME . Block ( ) ;
if ( PRIM - > TME & & Frame . TBW = = 10 & & Texture . TBW = = 10 & & Frame . TBP0 = = 0x00a00 & & Texture . PSM = = PSM_PSMT8H & & ( m_r . y = = 256 | | m_r . y = = 224 ) )
{
// 224 ntsc, 256 pal.
GL_INS ( " OI_BigMuthaTruckers half bottom offset " ) ;
size_t count = m_vertex . next ;
GSVertex * v = & m_vertex . buff [ 0 ] ;
const uint16 offset = ( uint16 ) m_r . y * 16 ;
for ( size_t i = 0 ; i < count ; i + + )
v [ i ] . V + = offset ;
}
return true ;
}
2020-06-16 19:29:59 +00:00
bool GSRendererHW : : OI_DBZBTGames ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
2019-08-28 17:52:01 +00:00
{
if ( t & & t - > m_from_target ) // Avoid slow framebuffer readback
return true ;
// Sprite rendering
if ( ! CanUseSwSpriteRender ( true ) )
return true ;
SwSpriteRender ( ) ;
return false ; // Skip current draw
}
2012-01-05 02:40:24 +00:00
bool GSRendererHW : : OI_FFXII ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
static uint32 * video = NULL ;
static size_t lines = 0 ;
if ( lines = = 0 )
{
2012-01-19 04:53:36 +00:00
if ( m_vt . m_primclass = = GS_LINE_CLASS & & ( m_vertex . next = = 448 * 2 | | m_vertex . next = = 512 * 2 ) )
2012-01-05 02:40:24 +00:00
{
2012-01-06 00:17:52 +00:00
lines = m_vertex . next / 2 ;
2012-01-05 02:40:24 +00:00
}
}
else
{
2012-01-19 04:53:36 +00:00
if ( m_vt . m_primclass = = GS_POINT_CLASS )
2012-01-05 02:40:24 +00:00
{
2012-01-06 00:17:52 +00:00
if ( m_vertex . next > = 16 * 512 )
2012-01-05 02:40:24 +00:00
{
// incoming pixels are stored in columns, one column is 16x512, total res 448x512 or 448x454
if ( ! video ) video = new uint32 [ 512 * 512 ] ;
2012-03-23 21:00:22 +00:00
int ox = m_context - > XYOFFSET . OFX - 8 ;
int oy = m_context - > XYOFFSET . OFY - 8 ;
2012-01-05 02:40:24 +00:00
2012-01-19 04:53:36 +00:00
const GSVertex * RESTRICT v = m_vertex . buff ;
2012-01-05 02:40:24 +00:00
2012-03-23 21:00:22 +00:00
for ( int i = ( int ) m_vertex . next ; i > 0 ; i - - , v + + )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
int x = ( v - > XYZ . X - ox ) > > 4 ;
int y = ( v - > XYZ . Y - oy ) > > 4 ;
2016-03-19 10:43:27 +00:00
2012-03-23 21:00:22 +00:00
if ( x < 0 | | x > = 448 | | y < 0 | | y > = ( int ) lines ) return false ; // le sigh
2016-03-19 10:43:27 +00:00
2012-01-19 04:53:36 +00:00
video [ ( y < < 8 ) + ( y < < 7 ) + ( y < < 6 ) + x ] = v - > RGBAQ . u32 [ 0 ] ;
2012-01-05 02:40:24 +00:00
}
return false ;
}
else
{
lines = 0 ;
}
}
2012-01-19 04:53:36 +00:00
else if ( m_vt . m_primclass = = GS_LINE_CLASS )
2012-01-05 02:40:24 +00:00
{
2012-01-06 00:17:52 +00:00
if ( m_vertex . next = = lines * 2 )
2012-01-05 02:40:24 +00:00
{
// 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 ) ;
2012-01-19 04:53:36 +00:00
m_vertex . buff [ 2 ] = m_vertex . buff [ m_vertex . next - 2 ] ;
m_vertex . buff [ 3 ] = m_vertex . buff [ m_vertex . next - 1 ] ;
2012-01-05 02:40:24 +00:00
m_index . buff [ 0 ] = 0 ;
m_index . buff [ 1 ] = 1 ;
m_index . buff [ 2 ] = 2 ;
m_index . buff [ 3 ] = 1 ;
m_index . buff [ 4 ] = 2 ;
m_index . buff [ 5 ] = 3 ;
2012-01-06 00:17:52 +00:00
m_vertex . head = m_vertex . tail = m_vertex . next = 4 ;
2012-01-05 02:40:24 +00:00
m_index . tail = 6 ;
2016-11-11 22:37:13 +00:00
m_vt . Update ( m_vertex . buff , m_index . buff , m_vertex . tail , m_index . tail , GS_TRIANGLE_CLASS ) ;
2012-01-05 02:40:24 +00:00
}
else
{
lines = 0 ;
}
}
}
return true ;
}
bool GSRendererHW : : 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)
2019-07-29 23:01:23 +00:00
GL_INS ( " OI_FFX ZB clear " ) ;
2019-05-02 17:57:00 +00:00
if ( ds )
ds - > Commit ( ) ; // Don't bother to save few MB for a single game
2016-07-27 21:22:46 +00:00
m_dev - > ClearDepth ( ds ) ;
2012-01-05 02:40:24 +00:00
}
return true ;
}
bool GSRendererHW : : OI_MetalSlug6 ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
// missing red channel fix (looks alright in pcsx2 r5000+)
2012-01-19 04:53:36 +00:00
GSVertex * RESTRICT v = m_vertex . buff ;
2012-01-05 02:40:24 +00:00
2012-03-23 21:00:22 +00:00
for ( int i = ( int ) m_vertex . next ; i > 0 ; i - - , v + + )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
uint32 c = v - > RGBAQ . u32 [ 0 ] ;
2012-01-05 02:40:24 +00:00
uint32 r = ( c > > 0 ) & 0xff ;
uint32 g = ( c > > 8 ) & 0xff ;
uint32 b = ( c > > 16 ) & 0xff ;
if ( r = = 0 & & g ! = 0 & & b ! = 0 )
{
2012-01-19 04:53:36 +00:00
v - > RGBAQ . u32 [ 0 ] = ( c & 0xffffff00 ) | ( ( g + b + 1 ) > > 1 ) ;
2012-01-05 02:40:24 +00:00
}
}
2016-11-11 22:37:13 +00:00
m_vt . Update ( m_vertex . buff , m_index . buff , m_vertex . tail , m_index . tail , m_vt . m_primclass ) ;
2016-03-19 10:43:27 +00:00
2012-01-05 02:40:24 +00:00
return true ;
}
bool GSRendererHW : : 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 ;
2016-04-14 10:00:58 +00:00
if ( GSTextureCache : : Target * tmp_rt = m_tc - > LookupTarget ( TEX0 , m_width , m_height , GSTextureCache : : RenderTarget , true ) )
2012-01-05 02:40:24 +00:00
{
2019-07-29 23:01:23 +00:00
GL_INS ( " OI_RozenMaidenGebetGarden FB clear " ) ;
2019-02-26 14:36:28 +00:00
tmp_rt - > m_texture - > Commit ( ) ; // Don't bother to save few MB for a single game
2016-04-14 10:00:58 +00:00
m_dev - > ClearRenderTarget ( tmp_rt - > m_texture , 0 ) ;
2012-01-05 02:40:24 +00:00
}
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 ;
2016-04-14 10:00:58 +00:00
if ( GSTextureCache : : Target * tmp_ds = m_tc - > LookupTarget ( TEX0 , m_width , m_height , GSTextureCache : : DepthStencil , true ) )
2012-01-05 02:40:24 +00:00
{
2019-07-29 23:01:23 +00:00
GL_INS ( " OI_RozenMaidenGebetGarden ZB clear " ) ;
2019-02-26 14:36:28 +00:00
tmp_ds - > m_texture - > Commit ( ) ; // Don't bother to save few MB for a single game
2016-07-27 21:22:46 +00:00
m_dev - > ClearDepth ( tmp_ds - > m_texture ) ;
2012-01-05 02:40:24 +00:00
}
return false ;
}
}
return true ;
}
2019-05-14 20:17:23 +00:00
bool GSRendererHW : : OI_SonicUnleashed ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
// Rendering pattern is:
// Save RG channel with a kind of a TS (replaced by a copy in this hack),
// compute shadow in RG,
// save result in alpha with a TS,
// Restore RG channel that we previously copied to render shadows.
GIFRegTEX0 Texture = m_context - > TEX0 ;
GIFRegTEX0 Frame ;
Frame . TBW = m_context - > FRAME . FBW ;
Frame . TBP0 = m_context - > FRAME . FBP ;
Frame . TBP0 = m_context - > FRAME . Block ( ) ;
Frame . PSM = m_context - > FRAME . PSM ;
if ( ( ! PRIM - > TME ) | | ( GSLocalMemory : : m_psm [ Texture . PSM ] . bpp ! = 16 ) | | ( GSLocalMemory : : m_psm [ Frame . PSM ] . bpp ! = 16 ) )
return true ;
if ( ( Texture . TBP0 = = Frame . TBP0 ) | | ( Frame . TBW ! = 16 & & Texture . TBW ! = 16 ) )
return true ;
2019-08-04 11:16:05 +00:00
GL_INS ( " OI_SonicUnleashed replace draw by a copy " ) ;
2019-05-14 20:17:23 +00:00
GSTextureCache : : Target * src = m_tc - > LookupTarget ( Texture , m_width , m_height , GSTextureCache : : RenderTarget , true ) ;
2019-08-04 11:16:05 +00:00
GSVector2i size = rt - > GetSize ( ) ;
2019-05-14 20:17:23 +00:00
GSVector4 sRect ( 0 , 0 , 1 , 1 ) ;
2019-08-04 11:16:05 +00:00
GSVector4 dRect ( 0 , 0 , size . x , size . y ) ;
2019-05-14 20:17:23 +00:00
m_dev - > StretchRect ( src - > m_texture , sRect , rt , dRect , true , true , true , false ) ;
return false ;
}
2012-01-05 02:40:24 +00:00
bool GSRendererHW : : OI_PointListPalette ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
2012-01-19 04:53:36 +00:00
if ( m_vt . m_primclass = = GS_POINT_CLASS & & ! PRIM - > TME )
2012-01-05 02:40:24 +00:00
{
uint32 FBP = m_context - > FRAME . Block ( ) ;
uint32 FBW = m_context - > FRAME . FBW ;
if ( FBP > = 0x03f40 & & ( FBP & 0x1f ) = = 0 )
{
2012-01-06 00:17:52 +00:00
if ( m_vertex . next = = 16 )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
GSVertex * RESTRICT v = m_vertex . buff ;
2012-01-05 02:40:24 +00:00
2012-01-19 04:53:36 +00:00
for ( int i = 0 ; i < 16 ; i + + , v + + )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
uint32 c = v - > RGBAQ . u32 [ 0 ] ;
2012-01-05 02:40:24 +00:00
uint32 a = c > > 24 ;
c = ( a > = 0x80 ? 0xff000000 : ( a < < 25 ) ) | ( c & 0x00ffffff ) ;
2012-01-19 04:53:36 +00:00
v - > RGBAQ . u32 [ 0 ] = c ;
2012-01-05 02:40:24 +00:00
m_mem . WritePixel32 ( i & 7 , i > > 3 , c , FBP , FBW ) ;
}
m_mem . m_clut . Invalidate ( ) ;
return false ;
}
2012-01-06 00:17:52 +00:00
else if ( m_vertex . next = = 256 )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
GSVertex * RESTRICT v = m_vertex . buff ;
2012-01-05 02:40:24 +00:00
2012-01-19 04:53:36 +00:00
for ( int i = 0 ; i < 256 ; i + + , v + + )
2012-01-05 02:40:24 +00:00
{
2012-01-19 04:53:36 +00:00
uint32 c = v - > RGBAQ . u32 [ 0 ] ;
2012-01-05 02:40:24 +00:00
uint32 a = c > > 24 ;
c = ( a > = 0x80 ? 0xff000000 : ( a < < 25 ) ) | ( c & 0x00ffffff ) ;
2012-01-19 04:53:36 +00:00
v - > RGBAQ . u32 [ 0 ] = c ;
2012-01-05 02:40:24 +00:00
m_mem . WritePixel32 ( i & 15 , i > > 4 , c , FBP , FBW ) ;
}
m_mem . m_clut . Invalidate ( ) ;
return false ;
}
else
{
ASSERT ( 0 ) ;
}
}
}
return true ;
}
2015-07-07 15:56:04 +00:00
bool GSRendererHW : : OI_SuperManReturns ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
2015-07-04 08:52:43 +00:00
{
// Instead to use a fullscreen rectangle they use a 32 pixels, 4096 pixels with a FBW of 1.
// Technically the FB wrap/overlap on itself...
GSDrawingContext * ctx = m_context ;
2016-04-28 21:29:52 +00:00
# ifndef NDEBUG
2015-07-04 08:52:43 +00:00
GSVertex * v = & m_vertex . buff [ 0 ] ;
2016-04-28 21:29:52 +00:00
# endif
2015-07-04 08:52:43 +00:00
2015-08-04 17:30:12 +00:00
if ( ! ( ctx - > FRAME . FBP = = ctx - > ZBUF . ZBP & & ! PRIM - > TME & & ! ctx - > ZBUF . ZMSK & & ! ctx - > FRAME . FBMSK & & m_vt . m_eq . rgba = = 0xFFFF ) )
2015-07-04 08:52:43 +00:00
return true ;
// Please kill those crazy devs!
ASSERT ( m_vertex . next = = 2 ) ;
ASSERT ( m_vt . m_primclass = = GS_SPRITE_CLASS ) ;
ASSERT ( ( v - > RGBAQ . A < < 24 | v - > RGBAQ . B < < 16 | v - > RGBAQ . G < < 8 | v - > RGBAQ . R ) = = ( int ) v - > XYZ . Z ) ;
// Do a direct write
2019-05-02 17:57:00 +00:00
if ( rt )
rt - > Commit ( ) ; // Don't bother to save few MB for a single game
2015-08-04 17:30:12 +00:00
m_dev - > ClearRenderTarget ( rt , GSVector4 ( m_vt . m_min . c ) ) ;
2015-07-04 08:52:43 +00:00
2015-08-04 17:30:12 +00:00
m_tc - > InvalidateVideoMemType ( GSTextureCache : : DepthStencil , ctx - > FRAME . Block ( ) ) ;
2019-07-29 23:01:23 +00:00
GL_INS ( " OI_SuperManReturns " ) ;
2015-07-04 08:52:43 +00:00
return false ;
}
2016-05-16 08:55:02 +00:00
bool GSRendererHW : : OI_ArTonelico2 ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
// world map clipping
//
// The bad draw call is a sprite rendering to clear the z buffer
/*
Depth buffer description
* width is 10 pages
* texture / scissor size is 640 x448
* depth is 16 bits so it writes 70 ( 10 w * 7 h ) pages of data .
following draw calls will use the buffer as 6 pages width with a scissor
test of 384 x672 . So the above texture can be seen as a
* texture width : 6 pages * 64 pixels / page = 384
* texture height : 70 / 6 pages * 64 pixels / page = 746
So as you can see the GS issue a write of 640 x448 but actually it
expects to clean a 384 x746 area . Ideally the fix will transform the
buffer to adapt the page width properly .
*/
GSVertex * v = & m_vertex . buff [ 0 ] ;
if ( m_vertex . next = = 2 & & ! PRIM - > TME & & m_context - > FRAME . FBW = = 10 & & v - > XYZ . Z = = 0 & & m_context - > TEST . ZTST = = ZTST_ALWAYS ) {
GL_INS ( " OI_ArTonelico2 " ) ;
2019-05-02 17:57:00 +00:00
if ( ds )
ds - > Commit ( ) ; // Don't bother to save few MB for a single game
2016-07-27 21:22:46 +00:00
m_dev - > ClearDepth ( ds ) ;
2016-05-16 08:55:02 +00:00
}
2016-09-04 17:41:31 +00:00
return true ;
}
2019-07-12 23:05:27 +00:00
bool GSRendererHW : : OI_JakGames ( GSTexture * rt , GSTexture * ds , GSTextureCache : : Source * t )
{
2020-06-20 18:22:27 +00:00
if ( ! CanUseSwSpriteRender ( false ) )
2019-07-12 23:05:27 +00:00
return true ;
2020-06-20 18:22:27 +00:00
// Render 16x16 palette via CPU.
SwSpriteRender ( ) ;
2019-07-12 23:05:27 +00:00
2020-06-20 18:22:27 +00:00
return false ; // Skip current draw.
2019-07-12 23:05:27 +00:00
}
2015-06-04 16:52:58 +00:00
// OO (others output?) hacks: invalidate extra local memory after the draw call
2012-01-05 02:40:24 +00:00
void GSRendererHW : : 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 ) ) ;
}
}
2015-06-04 16:52:58 +00:00
// Can Upscale hacks: disable upscaling for some draw calls
2012-01-05 02:40:24 +00:00
bool GSRendererHW : : CU_MajokkoALaMode2 ( )
{
// palette should stay 16 x 16
uint32 FBP = m_context - > FRAME . Block ( ) ;
return FBP ! = 0x03f40 ;
}
bool GSRendererHW : : CU_TalesOfAbyss ( )
{
// full image blur and brightening
uint32 FBP = m_context - > FRAME . Block ( ) ;
return FBP ! = 0x036e0 & & FBP ! = 0x03560 & & FBP ! = 0x038e0 ;
}