2020-12-05 13:08:24 +01:00
# include "stdafx.h"
2015-05-19 18:17:08 +02:00
# include "Emu/System.h"
2019-08-23 18:36:01 +02:00
# include "../rsx_methods.h"
2015-06-19 17:49:38 +02:00
# include "FragmentProgramDecompiler.h"
2016-05-13 16:01:48 +02:00
# include <algorithm>
2021-08-24 16:10:59 +02:00
namespace rsx
{
namespace fragment_program
{
static const std : : string reg_table [ ] =
{
" wpos " ,
" diff_color " , " spec_color " ,
" fogc " ,
" tc0 " , " tc1 " , " tc2 " , " tc3 " , " tc4 " , " tc5 " , " tc6 " , " tc7 " , " tc8 " , " tc9 " ,
" ssa "
} ;
}
}
using namespace rsx : : fragment_program ;
2020-02-21 13:20:10 +01:00
FragmentProgramDecompiler : : FragmentProgramDecompiler ( const RSXFragmentProgram & prog , u32 & size )
: m_size ( size )
, m_prog ( prog )
, m_ctrl ( prog . ctrl )
2015-05-19 18:17:08 +02:00
{
m_size = 0 ;
}
2019-04-13 12:20:50 +02:00
void FragmentProgramDecompiler : : SetDst ( std : : string code , u32 flags )
2015-05-19 18:17:08 +02:00
{
if ( ! src0 . exec_if_eq & & ! src0 . exec_if_gr & & ! src0 . exec_if_lt ) return ;
2019-04-13 12:20:50 +02:00
if ( src1 . scale )
2015-05-19 18:17:08 +02:00
{
2019-04-13 12:20:50 +02:00
std : : string modifier ;
switch ( src1 . scale )
{
case 0 : break ;
case 1 : code = " ( " + code + " * " ; modifier = " 2. " ; break ;
case 2 : code = " ( " + code + " * " ; modifier = " 4. " ; break ;
case 3 : code = " ( " + code + " * " ; modifier = " 8. " ; break ;
case 5 : code = " ( " + code + " / " ; modifier = " 2. " ; break ;
case 6 : code = " ( " + code + " / " ; modifier = " 4. " ; break ;
case 7 : code = " ( " + code + " / " ; modifier = " 8. " ; break ;
2015-05-19 18:17:08 +02:00
2019-04-13 12:20:50 +02:00
default :
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Bad scale: %d " , u32 { src1 . scale } ) ;
2019-04-13 12:20:50 +02:00
break ;
}
if ( flags & OPFLAGS : : skip_type_cast & & dst . fp16 & & device_props . has_native_half_support )
{
modifier = getHalfTypeName ( 1 ) + " ( " + modifier + " ) " ;
}
if ( ! modifier . empty ( ) )
{
code = code + modifier + " ) " ;
}
2015-05-19 18:17:08 +02:00
}
2017-07-05 00:16:59 +02:00
if ( ! dst . no_dest )
2017-03-07 11:40:38 +01:00
{
2019-04-13 12:20:50 +02:00
if ( dst . fp16 & & device_props . has_native_half_support & & ! ( flags & OPFLAGS : : skip_type_cast ) )
2019-04-12 23:25:44 +02:00
{
// Cast to native data type
2019-04-23 16:53:39 +02:00
code = ClampValue ( code , 1 ) ;
2019-04-12 23:25:44 +02:00
}
2017-07-05 00:16:59 +02:00
if ( dst . saturate )
2018-01-24 22:09:27 +01:00
{
2019-04-23 16:53:39 +02:00
code = ClampValue ( code , 4 ) ;
2018-01-24 22:09:27 +01:00
}
else if ( dst . prec )
{
switch ( dst . opcode )
{
case RSX_FP_OPCODE_NRM :
case RSX_FP_OPCODE_MAX :
case RSX_FP_OPCODE_MIN :
case RSX_FP_OPCODE_COS :
case RSX_FP_OPCODE_SIN :
case RSX_FP_OPCODE_REFL :
case RSX_FP_OPCODE_FRC :
case RSX_FP_OPCODE_LIT :
case RSX_FP_OPCODE_LIF :
case RSX_FP_OPCODE_LG2 :
break ;
case RSX_FP_OPCODE_MOV :
2019-04-12 23:25:44 +02:00
// NOTE: Sometimes varying inputs from VS are out of range so do not exempt any input types, unless fp16 (Naruto UNS)
2018-01-24 22:09:27 +01:00
if ( dst . fp16 & & src0 . fp16 & & src0 . reg_type = = RSX_FP_REGISTER_TYPE_TEMP )
break ;
2018-09-06 13:28:12 +02:00
[[fallthrough]] ;
2018-01-24 22:09:27 +01:00
default :
{
2019-04-12 23:25:44 +02:00
// fp16 precsion flag on f32 register; ignore
2018-01-24 22:09:27 +01:00
if ( dst . prec = = 1 & & ! dst . fp16 )
break ;
2019-04-12 23:25:44 +02:00
// Native type already has fp16 clamped (input must have been cast)
if ( dst . prec = = 1 & & dst . fp16 & & device_props . has_native_half_support )
break ;
// clamp value to allowed range
2019-04-23 16:53:39 +02:00
code = ClampValue ( code , dst . prec ) ;
2018-01-24 22:09:27 +01:00
break ;
}
}
}
2017-03-07 11:40:38 +01:00
}
2015-05-19 18:17:08 +02:00
2019-04-13 12:20:50 +02:00
opflags = flags ;
code + = ( flags & OPFLAGS : : no_src_mask ) ? " " : " $m " ;
2015-05-19 18:17:08 +02:00
if ( dst . no_dest )
{
if ( dst . set_cond )
{
AddCode ( " $ifcond " + m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " cc " + std : : to_string ( src0 . cond_mod_reg_index ) ) + " $m = " + code + " ; " ) ;
}
else
{
AddCode ( " $ifcond " + code + " ; " ) ;
}
return ;
}
2020-06-02 22:00:29 +02:00
const std : : string dest = AddReg ( dst . dest_reg , ! ! dst . fp16 ) + " $m " ;
const std : : string decoded_dest = Format ( dest ) ;
2015-05-19 18:17:08 +02:00
2020-06-02 22:00:29 +02:00
AddCodeCond ( decoded_dest , code ) ;
2015-05-19 18:17:08 +02:00
//AddCode("$ifcond " + dest + code + (append_mask ? "$m;" : ";"));
if ( dst . set_cond )
{
AddCode ( m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " cc " + std : : to_string ( src0 . cond_mod_reg_index ) ) + " $m = " + dest + " ; " ) ;
}
2017-11-30 19:47:25 +01:00
u32 reg_index = dst . fp16 ? dst . dest_reg > > 1 : dst . dest_reg ;
2018-09-03 18:04:47 +02:00
2020-12-09 08:47:45 +01:00
ensure ( reg_index < temp_registers . size ( ) ) ;
2020-06-02 22:00:29 +02:00
if ( dst . opcode = = RSX_FP_OPCODE_MOV & &
src0 . reg_type = = RSX_FP_REGISTER_TYPE_TEMP & &
src0 . tmp_reg_index = = reg_index )
{
// The register did not acquire any new data
// Common in code with structures like r0.xy = r0.xy
// Unsure why such code would exist, maybe placeholders for dynamically generated shader code?
if ( decoded_dest = = Format ( code ) )
{
return ;
}
}
2018-02-23 20:48:51 +01:00
temp_registers [ reg_index ] . tag ( dst . dest_reg , ! ! dst . fp16 , dst . mask_x , dst . mask_y , dst . mask_z , dst . mask_w ) ;
2015-05-19 18:17:08 +02:00
}
2019-06-08 07:58:04 +02:00
void FragmentProgramDecompiler : : AddFlowOp ( const std : : string & code )
2017-05-21 21:14:59 +02:00
{
//Flow operations can only consider conditionals and have no dst
if ( src0 . exec_if_gr & & src0 . exec_if_lt & & src0 . exec_if_eq )
{
AddCode ( code + " ; " ) ;
return ;
}
else if ( ! src0 . exec_if_gr & & ! src0 . exec_if_lt & & ! src0 . exec_if_eq )
{
AddCode ( " // " + code + " ; " ) ;
return ;
}
//We have a conditional expression
std : : string cond = GetRawCond ( ) ;
AddCode ( " if (any( " + cond + " )) " + code + " ; " ) ;
}
2015-05-19 18:17:08 +02:00
void FragmentProgramDecompiler : : AddCode ( const std : : string & code )
{
main . append ( m_code_level , ' \t ' ) + = Format ( code ) + " \n " ;
}
2021-04-09 21:12:47 +02:00
std : : string FragmentProgramDecompiler : : GetMask ( ) const
2015-05-19 18:17:08 +02:00
{
std : : string ret ;
2020-06-05 16:35:53 +02:00
ret . reserve ( 5 ) ;
2020-12-09 08:47:45 +01:00
2020-06-05 16:35:53 +02:00
static constexpr std : : string_view dst_mask = " xyzw " ;
2015-05-19 18:17:08 +02:00
2020-06-05 16:35:53 +02:00
ret + = ' . ' ;
2015-05-19 18:17:08 +02:00
if ( dst . mask_x ) ret + = dst_mask [ 0 ] ;
if ( dst . mask_y ) ret + = dst_mask [ 1 ] ;
if ( dst . mask_z ) ret + = dst_mask [ 2 ] ;
if ( dst . mask_w ) ret + = dst_mask [ 3 ] ;
2020-06-05 16:35:53 +02:00
return ret = = " . " sv | | ret = = " .xyzw " sv ? " " : ( ret ) ;
2015-05-19 18:17:08 +02:00
}
2019-04-12 23:25:44 +02:00
std : : string FragmentProgramDecompiler : : AddReg ( u32 index , bool fp16 )
2015-05-19 18:17:08 +02:00
{
2019-04-12 23:25:44 +02:00
const std : : string type_name = ( fp16 & & device_props . has_native_half_support ) ? getHalfTypeName ( 4 ) : getFloatTypeName ( 4 ) ;
const std : : string reg_name = std : : string ( fp16 ? " h " : " r " ) + std : : to_string ( index ) ;
2020-09-26 19:42:31 +02:00
return m_parr . AddParam ( PF_PARAM_NONE , type_name , reg_name , type_name + " (0.) " ) ;
2015-05-19 18:17:08 +02:00
}
2019-04-12 23:25:44 +02:00
bool FragmentProgramDecompiler : : HasReg ( u32 index , bool fp16 )
2015-05-19 18:17:08 +02:00
{
2019-04-12 23:25:44 +02:00
const std : : string type_name = ( fp16 & & device_props . has_native_half_support ) ? getHalfTypeName ( 4 ) : getFloatTypeName ( 4 ) ;
const std : : string reg_name = std : : string ( fp16 ? " h " : " r " ) + std : : to_string ( index ) ;
return m_parr . HasParam ( PF_PARAM_NONE , type_name , reg_name ) ;
2015-05-19 18:17:08 +02:00
}
std : : string FragmentProgramDecompiler : : AddCond ( )
{
return m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " cc " + std : : to_string ( src0 . cond_reg_index ) ) ;
}
std : : string FragmentProgramDecompiler : : AddConst ( )
{
2019-04-12 23:25:44 +02:00
const std : : string name = std : : string ( " fc " ) + std : : to_string ( m_size + 4 * 4 ) ;
const std : : string type = getFloatTypeName ( 4 ) ;
if ( m_parr . HasParam ( PF_PARAM_UNIFORM , type , name ) )
2015-05-19 18:17:08 +02:00
{
return name ;
}
2021-07-30 08:31:36 +02:00
auto data = reinterpret_cast < be_t < u32 > * > ( reinterpret_cast < uptr > ( m_prog . get_data ( ) ) + m_size + 4 * sizeof ( u32 ) ) ;
2015-05-19 18:17:08 +02:00
m_offset = 2 * 4 * sizeof ( u32 ) ;
u32 x = GetData ( data [ 0 ] ) ;
u32 y = GetData ( data [ 1 ] ) ;
u32 z = GetData ( data [ 2 ] ) ;
u32 w = GetData ( data [ 3 ] ) ;
2019-04-12 23:25:44 +02:00
2019-06-01 23:12:17 +02:00
const auto var = fmt : : format ( " %s(%f, %f, %f, %f) " , type , std : : bit_cast < f32 > ( x ) , std : : bit_cast < f32 > ( y ) , std : : bit_cast < f32 > ( z ) , std : : bit_cast < f32 > ( w ) ) ;
2019-04-12 23:25:44 +02:00
return m_parr . AddParam ( PF_PARAM_UNIFORM , type , name , var ) ;
2015-05-19 18:17:08 +02:00
}
std : : string FragmentProgramDecompiler : : AddTex ( )
{
2019-04-15 19:39:42 +02:00
properties . has_tex_op = true ;
2016-01-24 22:24:28 +01:00
std : : string sampler ;
switch ( m_prog . get_texture_dimension ( dst . tex_num ) )
{
2016-03-30 21:52:29 +02:00
case rsx : : texture_dimension_extended : : texture_dimension_1d :
2016-01-27 00:34:34 +01:00
sampler = " sampler1D " ;
break ;
2016-03-30 21:52:29 +02:00
case rsx : : texture_dimension_extended : : texture_dimension_cubemap :
2016-01-24 22:24:28 +01:00
sampler = " samplerCube " ;
break ;
2016-03-30 21:52:29 +02:00
case rsx : : texture_dimension_extended : : texture_dimension_2d :
2016-01-24 22:24:28 +01:00
sampler = " sampler2D " ;
break ;
2016-03-30 21:52:29 +02:00
case rsx : : texture_dimension_extended : : texture_dimension_3d :
2016-01-24 22:24:28 +01:00
sampler = " sampler3D " ;
break ;
}
2019-04-15 19:39:42 +02:00
2019-04-23 16:53:39 +02:00
opflags | = OPFLAGS : : texture_ref ;
2016-01-24 22:24:28 +01:00
return m_parr . AddParam ( PF_PARAM_UNIFORM , sampler , std : : string ( " tex " ) + std : : to_string ( dst . tex_num ) ) ;
2015-05-19 18:17:08 +02:00
}
2017-03-21 12:53:52 +01:00
std : : string FragmentProgramDecompiler : : AddType3 ( )
{
2020-09-26 19:42:31 +02:00
return m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " src3 " , getFloatTypeName ( 4 ) + " (1.) " ) ;
2017-03-21 12:53:52 +01:00
}
2017-12-10 23:34:44 +01:00
std : : string FragmentProgramDecompiler : : AddX2d ( )
{
2020-09-26 19:42:31 +02:00
return m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " x2d " , getFloatTypeName ( 4 ) + " (0.) " ) ;
2017-12-10 23:34:44 +01:00
}
2019-04-23 16:53:39 +02:00
std : : string FragmentProgramDecompiler : : ClampValue ( const std : : string & code , u32 precision )
2017-03-07 11:40:38 +01:00
{
2019-04-12 23:25:44 +02:00
// FP16 is expected to overflow a lot easier at 0+-65504
// FP32 can still work up to 0+-3.4E38
// See http://http.download.nvidia.com/developer/Papers/2005/FP_Specials/FP_Specials.pdf
2017-03-07 11:40:38 +01:00
2019-04-23 16:53:39 +02:00
if ( precision > 1 & & precision < 5 )
2017-03-07 11:40:38 +01:00
{
2019-04-23 16:53:39 +02:00
// Define precision_clamp
properties . has_clamp = true ;
2019-04-13 12:20:50 +02:00
}
2019-04-23 16:53:39 +02:00
switch ( precision )
2019-04-13 12:20:50 +02:00
{
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_REAL :
2019-04-23 16:53:39 +02:00
// Full 32-bit precision
break ;
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_HALF :
2019-04-23 16:53:39 +02:00
return " clamp16( " + code + " ) " ;
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_FIXED12 :
2019-04-23 16:53:39 +02:00
return " precision_clamp( " + code + " , -2., 2.) " ;
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_FIXED9 :
2019-04-23 16:53:39 +02:00
return " precision_clamp( " + code + " , -1., 1.) " ;
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_SATURATE :
2019-04-23 16:53:39 +02:00
return " precision_clamp( " + code + " , 0., 1.) " ;
2019-06-08 12:15:54 +02:00
case RSX_FP_PRECISION_UNKNOWN :
2019-04-23 16:53:39 +02:00
// Doesn't seem to do anything to the input from hw tests, same as 0
break ;
default :
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Unexpected precision modifier (%d) \n " , precision ) ;
2019-04-23 16:53:39 +02:00
break ;
2017-03-07 11:40:38 +01:00
}
return code ;
}
2021-04-09 21:12:47 +02:00
bool FragmentProgramDecompiler : : DstExpectsSca ( ) const
2017-06-22 19:09:05 +02:00
{
int writes = 0 ;
2018-09-03 17:46:14 +02:00
2017-06-22 19:09:05 +02:00
if ( dst . mask_x ) writes + + ;
if ( dst . mask_y ) writes + + ;
if ( dst . mask_z ) writes + + ;
if ( dst . mask_w ) writes + + ;
return ( writes = = 1 ) ;
}
2017-12-10 23:34:44 +01:00
std : : string FragmentProgramDecompiler : : Format ( const std : : string & code , bool ignore_redirects )
2015-05-19 18:17:08 +02:00
{
const std : : pair < std : : string , std : : function < std : : string ( ) > > repl_list [ ] =
{
{ " $$ " , [ ] ( ) - > std : : string { return " $ " ; } } ,
2018-11-24 13:54:46 +01:00
{ " $0 " , [ this ] ( ) - > std : : string { return GetSRC < SRC0 > ( src0 ) ; } } ,
{ " $1 " , [ this ] ( ) - > std : : string { return GetSRC < SRC1 > ( src1 ) ; } } ,
{ " $2 " , [ this ] ( ) - > std : : string { return GetSRC < SRC2 > ( src2 ) ; } } ,
{ " $t " , [ this ] ( ) - > std : : string { return " tex " + std : : to_string ( dst . tex_num ) ; } } ,
2017-02-10 10:08:46 +01:00
{ " $_i " , [ this ] ( ) - > std : : string { return std : : to_string ( dst . tex_num ) ; } } ,
2015-05-19 18:17:08 +02:00
{ " $m " , std : : bind ( std : : mem_fn ( & FragmentProgramDecompiler : : GetMask ) , this ) } ,
{ " $ifcond " , [ this ] ( ) - > std : : string
2018-02-23 20:48:51 +01:00
{
const std : : string & cond = GetCond ( ) ;
if ( cond = = " true " ) return " " ;
return " if( " + cond + " ) " ;
}
2015-05-19 18:17:08 +02:00
} ,
{ " $cond " , std : : bind ( std : : mem_fn ( & FragmentProgramDecompiler : : GetCond ) , this ) } ,
2018-02-23 20:48:51 +01:00
{ " $_c " , std : : bind ( std : : mem_fn ( & FragmentProgramDecompiler : : AddConst ) , this ) } ,
2019-04-16 20:57:52 +02:00
{ " $float4 " , [ this ] ( ) - > std : : string { return getFloatTypeName ( 4 ) ; } } ,
{ " $float3 " , [ this ] ( ) - > std : : string { return getFloatTypeName ( 3 ) ; } } ,
{ " $float2 " , [ this ] ( ) - > std : : string { return getFloatTypeName ( 2 ) ; } } ,
2019-04-23 16:53:39 +02:00
{ " $float_t " , [ this ] ( ) - > std : : string { return getFloatTypeName ( 1 ) ; } } ,
{ " $half4 " , [ this ] ( ) - > std : : string { return getHalfTypeName ( 4 ) ; } } ,
{ " $half3 " , [ this ] ( ) - > std : : string { return getHalfTypeName ( 3 ) ; } } ,
{ " $half2 " , [ this ] ( ) - > std : : string { return getHalfTypeName ( 2 ) ; } } ,
{ " $half_t " , [ this ] ( ) - > std : : string { return getHalfTypeName ( 1 ) ; } } ,
2019-04-13 12:20:50 +02:00
{ " $Ty " , [ this ] ( ) - > std : : string { return ( ! device_props . has_native_half_support | | ! dst . fp16 ) ? getFloatTypeName ( 4 ) : getHalfTypeName ( 4 ) ; } }
2015-05-19 18:17:08 +02:00
} ;
2017-12-10 23:34:44 +01:00
if ( ! ignore_redirects )
{
//Special processing redirects
2017-12-11 09:37:20 +01:00
switch ( dst . opcode )
{
case RSX_FP_OPCODE_TEXBEM :
case RSX_FP_OPCODE_TXPBEM :
2017-12-10 23:34:44 +01:00
{
//Redirect parameter 0 to the x2d temp register for TEXBEM
//TODO: Organize this a little better
std : : pair < std : : string , std : : string > repl [ ] = { { " $0 " , " x2d " } } ;
std : : string result = fmt : : replace_all ( code , repl ) ;
2017-12-11 15:44:45 +01:00
return fmt : : replace_all ( result , repl_list ) ;
2017-12-10 23:34:44 +01:00
}
2017-12-11 09:37:20 +01:00
}
2017-12-10 23:34:44 +01:00
}
2015-05-19 18:17:08 +02:00
return fmt : : replace_all ( code , repl_list ) ;
}
2017-05-21 21:14:59 +02:00
std : : string FragmentProgramDecompiler : : GetRawCond ( )
2015-05-19 18:17:08 +02:00
{
2020-06-05 16:35:53 +02:00
static constexpr std : : string_view f = " xyzw " ;
2020-09-26 19:42:31 +02:00
const auto zero = getFloatTypeName ( 4 ) + " (0.) " ;
2015-05-19 18:17:08 +02:00
std : : string swizzle , cond ;
2020-06-05 16:35:53 +02:00
swizzle . reserve ( 5 ) ;
swizzle + = ' . ' ;
2015-05-19 18:17:08 +02:00
swizzle + = f [ src0 . cond_swizzle_x ] ;
swizzle + = f [ src0 . cond_swizzle_y ] ;
swizzle + = f [ src0 . cond_swizzle_z ] ;
swizzle + = f [ src0 . cond_swizzle_w ] ;
2020-06-05 16:35:53 +02:00
if ( swizzle = = " .xyzw " sv )
{
swizzle . clear ( ) ;
}
2015-05-19 18:17:08 +02:00
if ( src0 . exec_if_gr & & src0 . exec_if_eq )
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SGE , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
else if ( src0 . exec_if_lt & & src0 . exec_if_eq )
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SLE , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
else if ( src0 . exec_if_gr & & src0 . exec_if_lt )
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SNE , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
else if ( src0 . exec_if_gr )
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SGT , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
else if ( src0 . exec_if_lt )
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SLT , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
else //if(src0.exec_if_eq)
2021-10-05 19:33:58 +02:00
cond = compareFunction ( COMPARE : : SEQ , AddCond ( ) + swizzle , zero ) ;
2015-05-19 18:17:08 +02:00
2017-05-21 21:14:59 +02:00
return cond ;
}
std : : string FragmentProgramDecompiler : : GetCond ( )
{
if ( src0 . exec_if_gr & & src0 . exec_if_lt & & src0 . exec_if_eq )
{
return " true " ;
}
else if ( ! src0 . exec_if_gr & & ! src0 . exec_if_lt & & ! src0 . exec_if_eq )
{
return " false " ;
}
return " any( " + GetRawCond ( ) + " ) " ;
2015-05-19 18:17:08 +02:00
}
2019-08-31 13:31:01 +02:00
void FragmentProgramDecompiler : : AddCodeCond ( const std : : string & lhs , const std : : string & rhs )
2015-05-19 18:17:08 +02:00
{
if ( src0 . exec_if_gr & & src0 . exec_if_lt & & src0 . exec_if_eq )
{
2019-08-31 13:31:01 +02:00
AddCode ( lhs + " = " + rhs + " ; " ) ;
2015-05-19 18:17:08 +02:00
return ;
}
if ( ! src0 . exec_if_gr & & ! src0 . exec_if_lt & & ! src0 . exec_if_eq )
{
2019-08-31 13:31:01 +02:00
AddCode ( " // " + lhs + " = " + rhs + " ; " ) ;
2015-05-19 18:17:08 +02:00
return ;
}
2019-04-23 16:53:39 +02:00
std : : string src_prefix ;
if ( device_props . has_native_half_support & & ! this - > dst . fp16 )
{
// Target is not fp16 but src might be
// Usually vecX a = f16vecX b is fine, but causes operator overload issues when used in a mix/lerp function
// mix(f32, f16, bvec) causes compiler issues
// NOTE: If dst is fp16 the src will already have been cast to match so this is not a problem in that case
bool src_is_fp16 = false ;
if ( ( opflags & ( OPFLAGS : : texture_ref | OPFLAGS : : src_cast_f32 ) ) = = 0 & &
2020-03-05 12:05:23 +01:00
rhs . find ( " $0 " ) ! = umax )
2019-04-23 16:53:39 +02:00
{
// Texture sample operations are full-width and are exempt
src_is_fp16 = ( src0 . fp16 & & src0 . reg_type = = RSX_FP_REGISTER_TYPE_TEMP ) ;
2020-03-05 12:05:23 +01:00
if ( src_is_fp16 & & rhs . find ( " $1 " ) ! = umax )
2019-04-23 16:53:39 +02:00
{
// References operand 1
src_is_fp16 = ( src1 . fp16 & & src1 . reg_type = = RSX_FP_REGISTER_TYPE_TEMP ) ;
2020-03-05 12:05:23 +01:00
if ( src_is_fp16 & & rhs . find ( " $2 " ) ! = umax )
2019-04-23 16:53:39 +02:00
{
// References operand 2
src_is_fp16 = ( src2 . fp16 & & src2 . reg_type = = RSX_FP_REGISTER_TYPE_TEMP ) ;
}
}
}
if ( src_is_fp16 )
{
// LHS argument is of native half type, need to cast to proper type!
2019-08-31 13:31:01 +02:00
if ( rhs [ 0 ] ! = ' ( ' )
2019-04-23 16:53:39 +02:00
{
// Upcast inputs to processing function instead
opflags | = OPFLAGS : : src_cast_f32 ;
}
else
{
// No need to add explicit casts all over the place, just cast the result once
src_prefix = " $Ty " ;
}
}
}
2019-08-31 13:31:01 +02:00
// NOTE: x = _select(x, y, cond) is equivalent to x = cond? y : x;
const auto dst_var = ShaderVariable ( lhs ) ;
2019-08-14 18:35:03 +02:00
const auto raw_cond = dst_var . add_mask ( GetRawCond ( ) ) ;
const auto cond = dst_var . match_size ( raw_cond ) ;
2019-08-31 13:31:01 +02:00
AddCode ( lhs + " = _select( " + lhs + " , " + src_prefix + rhs + " , " + cond + " ); " ) ;
2015-05-19 18:17:08 +02:00
}
template < typename T > std : : string FragmentProgramDecompiler : : GetSRC ( T src )
{
std : : string ret ;
2020-06-06 18:46:30 +02:00
u32 precision_modifier = 0 ;
if constexpr ( std : : is_same < T , SRC0 > : : value )
{
precision_modifier = src1 . src0_prec_mod ;
}
else if constexpr ( std : : is_same < T , SRC1 > : : value )
{
precision_modifier = src1 . src1_prec_mod ;
}
else if constexpr ( std : : is_same < T , SRC2 > : : value )
{
precision_modifier = src1 . src2_prec_mod ;
}
2015-05-19 18:17:08 +02:00
switch ( src . reg_type )
{
2016-06-10 00:03:43 +02:00
case RSX_FP_REGISTER_TYPE_TEMP :
2017-11-30 19:47:25 +01:00
if ( ! src . fp16 )
{
if ( dst . opcode = = RSX_FP_OPCODE_UP16 | |
dst . opcode = = RSX_FP_OPCODE_UP2 | |
dst . opcode = = RSX_FP_OPCODE_UP4 | |
dst . opcode = = RSX_FP_OPCODE_UPB | |
dst . opcode = = RSX_FP_OPCODE_UPG )
{
auto & reg = temp_registers [ src . tmp_reg_index ] ;
2018-02-23 20:48:51 +01:00
if ( reg . requires_gather ( src . swizzle_x ) )
2018-01-24 22:09:27 +01:00
{
properties . has_gather_op = true ;
2018-02-23 20:48:51 +01:00
AddReg ( src . tmp_reg_index , src . fp16 ) ;
ret = getFloatTypeName ( 4 ) + reg . gather_r ( ) ;
break ;
2018-01-24 22:09:27 +01:00
}
2017-11-30 19:47:25 +01:00
}
}
2020-06-06 18:46:30 +02:00
else if ( precision_modifier = = RSX_FP_PRECISION_HALF )
2019-06-08 12:15:54 +02:00
{
// clamp16() is not a cheap operation when emulated; avoid at all costs
2020-06-06 18:46:30 +02:00
precision_modifier = RSX_FP_PRECISION_REAL ;
2019-06-08 12:15:54 +02:00
}
2019-04-13 12:20:50 +02:00
2019-04-23 16:53:39 +02:00
ret + = AddReg ( src . tmp_reg_index , src . fp16 ) ;
2019-04-13 12:20:50 +02:00
2019-04-23 16:53:39 +02:00
if ( opflags & OPFLAGS : : src_cast_f32 & & src . fp16 & & device_props . has_native_half_support )
2019-04-13 12:20:50 +02:00
{
// Upconvert if there is a chance for ambiguity
ret = getFloatTypeName ( 4 ) + " ( " + ret + " ) " ;
}
2017-11-30 19:47:25 +01:00
2015-05-19 18:17:08 +02:00
break ;
2016-06-10 00:03:43 +02:00
case RSX_FP_REGISTER_TYPE_INPUT :
2015-05-19 18:17:08 +02:00
{
2019-08-23 18:36:01 +02:00
// NOTE: Hw testing showed the following:
// 1. Reading from registers 1 and 2 (COL0 and COL1) is clamped to (0, 1)
// 2. Reading from registers 4-12 (inclusive) is not clamped, but..
2019-08-25 16:23:46 +02:00
// 3. If the texcoord control mask is enabled, the last 2 values are always 0 and hpos.w!
2021-08-18 23:12:46 +02:00
// 4. [A0 + N] addressing can be applied to dynamically sample texture coordinates.
// - This is explained in NV_fragment_program2 specification page, Fragment Attributes section.
// - There is no instruction that writes to the address register directly, it is supposed to be the loop counter!
2021-08-23 10:48:32 +02:00
u32 register_id = src2 . use_index_reg ? ( src2 . addr_reg + 4 ) : dst . src_attr_reg_num ;
2021-08-18 23:12:46 +02:00
const std : : string reg_var = ( register_id < std : : size ( reg_table ) ) ? reg_table [ register_id ] : " unk " ;
2019-08-23 18:36:01 +02:00
bool insert = true ;
2017-11-22 15:38:36 +01:00
2021-08-18 23:12:46 +02:00
if ( src2 . use_index_reg & & m_loop_count )
{
2021-08-23 10:48:32 +02:00
// Dynamically load the input
register_id = 0xFF ;
2021-08-18 23:12:46 +02:00
}
switch ( register_id )
2015-05-19 18:17:08 +02:00
{
2018-01-24 22:09:27 +01:00
case 0x00 :
2019-08-23 18:36:01 +02:00
{
// WPOS
2018-01-24 22:09:27 +01:00
ret + = reg_table [ 0 ] ;
2019-08-23 18:36:01 +02:00
insert = false ;
2018-01-24 22:09:27 +01:00
break ;
2019-08-23 18:36:01 +02:00
}
case 0x01 :
case 0x02 :
{
// COL0, COL1
2021-08-18 23:12:46 +02:00
ret + = " _saturate( " + reg_var + " ) " ;
precision_modifier = RSX_FP_PRECISION_REAL ;
2019-08-23 18:36:01 +02:00
break ;
}
case 0x03 :
{
// FOGC
2021-08-18 23:12:46 +02:00
ret + = reg_var ;
2019-08-23 18:36:01 +02:00
break ;
}
case 0x4 :
case 0x5 :
case 0x6 :
case 0x7 :
case 0x8 :
case 0x9 :
case 0xA :
case 0xB :
case 0xC :
case 0xD :
{
// TEX0 - TEX9
2019-11-08 18:19:54 +01:00
// Texcoord 2d mask seems to reset the last 2 arguments to 0 and w if set
2021-08-18 23:12:46 +02:00
const u8 texcoord = u8 ( register_id ) - 4 ;
2019-11-08 18:19:54 +01:00
if ( m_prog . texcoord_is_point_coord ( texcoord ) )
2015-05-19 18:17:08 +02:00
{
2019-11-08 18:19:54 +01:00
// Point sprite coord generation. Stacks with the 2D override mask.
if ( m_prog . texcoord_is_2d ( texcoord ) )
{
ret + = getFloatTypeName ( 4 ) + " (gl_PointCoord, 0., in_w) " ;
properties . has_w_access = true ;
}
else
{
ret + = getFloatTypeName ( 4 ) + " (gl_PointCoord, 1., 0.) " ;
}
}
2021-08-24 16:10:59 +02:00
else if ( src2 . perspective_corr )
2019-11-08 18:19:54 +01:00
{
2021-08-24 16:10:59 +02:00
// Perspective correct flag multiplies the result by 1/w
if ( m_prog . texcoord_is_2d ( texcoord ) )
{
ret + = getFloatTypeName ( 4 ) + " ( " + reg_var + " .xy * gl_FragCoord.w, 0., 1.) " ;
}
else
{
ret + = " ( " + reg_var + " * gl_FragCoord.w) " ;
}
2015-05-19 18:17:08 +02:00
}
else
2019-08-23 18:36:01 +02:00
{
2021-08-24 16:10:59 +02:00
if ( m_prog . texcoord_is_2d ( texcoord ) )
{
ret + = getFloatTypeName ( 4 ) + " ( " + reg_var + " .xy, 0., in_w) " ;
properties . has_w_access = true ;
}
else
{
ret + = reg_var ;
}
2019-08-23 18:36:01 +02:00
}
break ;
}
2021-08-23 10:48:32 +02:00
case 0xFF :
{
2021-08-24 16:10:59 +02:00
if ( m_loop_count > 1 )
2021-08-23 10:48:32 +02:00
{
2021-08-24 16:10:59 +02:00
// Afaik there is only one address/loop register on NV40
rsx_log . error ( " Nested loop with indexed load was detected. Report this to developers! " ) ;
2021-08-23 10:48:32 +02:00
}
2021-08-24 16:10:59 +02:00
if ( m_prog . texcoord_control_mask )
2021-08-23 10:48:32 +02:00
{
2021-08-24 16:10:59 +02:00
// This would require more work if it exists. It cannot be determined at compile time and has to be part of _indexed_load() subroutine.
rsx_log . error ( " Indexed load with control override mask detected. Report this to developers! " ) ;
2021-08-23 10:48:32 +02:00
}
2021-08-26 12:20:17 +02:00
const auto load_cmd = fmt : : format ( " _indexed_load(i%u + %u) " , m_loop_count - 1 , src2 . addr_reg ) ;
2021-08-23 10:48:32 +02:00
properties . has_dynamic_register_load = true ;
insert = false ;
2021-08-24 16:10:59 +02:00
if ( src2 . perspective_corr )
{
ret + = " ( " + load_cmd + " * gl_FragCoord.w) " ;
}
else
{
ret + = load_cmd ;
}
2021-08-23 10:48:32 +02:00
break ;
}
2019-08-23 18:36:01 +02:00
default :
{
// SSA (winding direction register)
// UNK
if ( reg_var = = " unk " )
2015-05-19 18:17:08 +02:00
{
2021-08-18 23:12:46 +02:00
rsx_log . error ( " Bad src reg num: %d " , u32 { register_id } ) ;
2015-05-19 18:17:08 +02:00
}
2019-08-23 18:36:01 +02:00
ret + = reg_var ;
2020-06-06 18:46:30 +02:00
precision_modifier = RSX_FP_PRECISION_REAL ;
2015-05-19 18:17:08 +02:00
break ;
}
2019-08-23 18:36:01 +02:00
}
if ( insert )
{
m_parr . AddParam ( PF_PARAM_IN , getFloatTypeName ( 4 ) , reg_var ) ;
}
2021-08-18 23:12:46 +02:00
properties . in_register_mask | = ( 1 < < register_id ) ;
2015-05-19 18:17:08 +02:00
}
break ;
2016-06-10 00:03:43 +02:00
case RSX_FP_REGISTER_TYPE_CONSTANT :
2015-05-19 18:17:08 +02:00
ret + = AddConst ( ) ;
break ;
2016-06-10 00:03:43 +02:00
case RSX_FP_REGISTER_TYPE_UNKNOWN : // ??? Used by a few games, what is it?
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Src type 3 used, opcode=0x%X, dst=0x%X s0=0x%X s1=0x%X s2=0x%X " ,
2017-03-21 12:53:52 +01:00
dst . opcode , dst . HEX , src0 . HEX , src1 . HEX , src2 . HEX ) ;
ret + = AddType3 ( ) ;
2020-06-06 18:46:30 +02:00
precision_modifier = RSX_FP_PRECISION_REAL ;
2015-10-17 19:47:18 +02:00
break ;
2015-09-10 16:30:14 +02:00
2015-05-19 18:17:08 +02:00
default :
2021-07-20 00:23:00 +02:00
rsx_log . fatal ( " Bad src type %d " , u32 { src . reg_type } ) ;
2015-05-19 18:17:08 +02:00
break ;
}
2020-06-05 16:35:53 +02:00
static constexpr std : : string_view f = " xyzw " ;
2015-05-19 18:17:08 +02:00
2019-06-08 08:04:56 +02:00
std : : string swizzle ;
2020-06-05 16:35:53 +02:00
swizzle . reserve ( 5 ) ;
swizzle + = ' . ' ;
2015-05-19 18:17:08 +02:00
swizzle + = f [ src . swizzle_x ] ;
swizzle + = f [ src . swizzle_y ] ;
swizzle + = f [ src . swizzle_z ] ;
swizzle + = f [ src . swizzle_w ] ;
2020-06-05 16:35:53 +02:00
if ( swizzle ! = " .xyzw " sv )
{
ret + = swizzle ;
}
2015-05-19 18:17:08 +02:00
2019-04-12 23:25:44 +02:00
// Warning: Modifier order matters. e.g neg should be applied after precision clamping (tested with Naruto UNS)
2015-05-19 18:17:08 +02:00
if ( src . abs ) ret = " abs( " + ret + " ) " ;
2020-06-06 18:46:30 +02:00
if ( precision_modifier ) ret = ClampValue ( ret , precision_modifier ) ;
2015-05-19 18:17:08 +02:00
if ( src . neg ) ret = " - " + ret ;
return ret ;
}
std : : string FragmentProgramDecompiler : : BuildCode ( )
{
2019-01-18 10:23:30 +01:00
// Shader validation
// Shader must at least write to one output for the body to be considered valid
2019-04-12 23:25:44 +02:00
const bool fp16_out = ! ( m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ) ;
2019-08-19 17:36:44 +02:00
const std : : string float4_type = ( fp16_out & & device_props . has_native_half_support ) ? getHalfTypeName ( 4 ) : getFloatTypeName ( 4 ) ;
2020-09-26 19:42:31 +02:00
const std : : string init_value = float4_type + " (0.) " ;
2019-01-18 10:23:30 +01:00
std : : array < std : : string , 4 > output_register_names ;
std : : array < u32 , 4 > ouput_register_indices = { 0 , 2 , 3 , 4 } ;
bool shader_is_valid = false ;
// Check depth export
if ( m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT )
{
2019-08-19 17:36:44 +02:00
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
2019-10-22 23:41:30 +02:00
m_parr . AddParam ( PF_PARAM_NONE , getFloatTypeName ( 4 ) , " r1 " , init_value ) ;
2019-08-19 17:36:44 +02:00
shader_is_valid = ( ! ! temp_registers [ 1 ] . h1_writes ) ;
2019-01-18 10:23:30 +01:00
}
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
// This can be used instead of an explicit clear pass in some games (Motorstorm)
2019-04-12 23:25:44 +02:00
if ( ! fp16_out )
2019-01-18 10:23:30 +01:00
{
output_register_names = { " r0 " , " r2 " , " r3 " , " r4 " } ;
}
else
{
output_register_names = { " h0 " , " h4 " , " h6 " , " h8 " } ;
}
for ( int n = 0 ; n < 4 ; + + n )
{
2019-08-19 17:36:44 +02:00
if ( ! m_parr . HasParam ( PF_PARAM_NONE , float4_type , output_register_names [ n ] ) )
2019-01-18 10:23:30 +01:00
{
2019-08-19 17:36:44 +02:00
m_parr . AddParam ( PF_PARAM_NONE , float4_type , output_register_names [ n ] , init_value ) ;
2019-01-18 10:23:30 +01:00
continue ;
}
const auto block_index = ouput_register_indices [ n ] ;
shader_is_valid | = ( ! ! temp_registers [ block_index ] . h0_writes ) ;
}
if ( ! shader_is_valid )
{
properties . has_no_output = true ;
if ( ! properties . has_discard_op )
{
// NOTE: Discard operation overrides output
2020-02-01 09:07:25 +01:00
rsx_log . warning ( " Shader does not write to any output register and will be NOPed " ) ;
2019-01-18 10:23:30 +01:00
main = " /* " + main + " */ " ;
}
}
2021-08-24 16:10:59 +02:00
if ( properties . has_dynamic_register_load )
{
// Since the registers will be loaded dynamically, declare all of them
for ( int i = 0 ; i < 10 ; + + i )
{
m_parr . AddParam ( PF_PARAM_IN , getFloatTypeName ( 4 ) , reg_table [ i + 4 ] ) ;
}
}
2015-05-19 18:17:08 +02:00
std : : stringstream OS ;
insertHeader ( OS ) ;
2017-12-02 13:13:13 +01:00
OS < < " \n " ;
2015-05-19 18:17:08 +02:00
insertConstants ( OS ) ;
2017-12-02 13:13:13 +01:00
OS < < " \n " ;
2018-04-29 08:41:51 +02:00
insertInputs ( OS ) ;
2017-12-02 13:13:13 +01:00
OS < < " \n " ;
2015-05-19 18:17:08 +02:00
insertOutputs ( OS ) ;
2017-12-02 13:13:13 +01:00
OS < < " \n " ;
2019-04-12 15:25:12 +02:00
// Insert global function definitions
2017-12-02 13:13:13 +01:00
insertGlobalFunctions ( OS ) ;
2017-11-30 19:47:25 +01:00
2019-04-16 20:57:52 +02:00
std : : string float4 = getFloatTypeName ( 4 ) ;
2019-04-20 12:02:05 +02:00
const bool glsl = float4 = = " vec4 " ;
2019-04-16 20:57:52 +02:00
2019-04-23 16:53:39 +02:00
if ( properties . has_clamp )
{
std : : string precision_func =
" $float4 precision_clamp($float4 x, float _min, float _max) \n "
" { \n "
" // Treat NaNs as 0 \n "
" bvec4 nans = isnan(x); \n "
2020-09-26 19:42:31 +02:00
" x = _select(x, $float4(0.), nans); \n "
2019-04-23 16:53:39 +02:00
" return clamp(x, _min, _max); \n "
" } \n \n " ;
if ( device_props . has_native_half_support )
{
precision_func + =
" $half4 precision_clamp($half4 x, float _min, float _max) \n "
" { \n "
" // Treat NaNs as 0 \n "
" bvec4 nans = isnan(x); \n "
2020-09-26 19:42:31 +02:00
" x = _select(x, $half4(0.), nans); \n "
2019-04-23 16:53:39 +02:00
" return clamp(x, $half_t(_min), $half_t(_max)); \n "
" } \n \n " ;
}
OS < < Format ( precision_func ) ;
}
2019-04-12 23:25:44 +02:00
if ( ! device_props . has_native_half_support )
{
// Accurate float to half clamping (preserves IEEE-754 NaN)
2020-09-26 19:42:31 +02:00
std : : string clamp_func ;
2019-04-20 12:02:05 +02:00
if ( glsl )
{
clamp_func + =
2020-09-26 19:42:31 +02:00
" vec2 clamp16(vec2 val){ return unpackHalf2x16(packHalf2x16(val)); } \n "
" vec4 clamp16(vec4 val){ return vec4(clamp16(val.xy), clamp16(val.zw)); } \n \n " ;
2019-04-20 12:02:05 +02:00
}
else
{
clamp_func + =
2020-09-26 19:42:31 +02:00
" $float4 clamp16($float4 x) \n "
" { \n "
2019-04-20 12:02:05 +02:00
" if (!isnan(x.x) && !isinf(x.x)) x.x = clamp(x.x, -65504., +65504.); \n "
" if (!isnan(x.x) && !isinf(x.x)) x.x = clamp(x.x, -65504., +65504.); \n "
" if (!isnan(x.x) && !isinf(x.x)) x.x = clamp(x.x, -65504., +65504.); \n "
" if (!isnan(x.x) && !isinf(x.x)) x.x = clamp(x.x, -65504., +65504.); \n "
2020-09-26 19:42:31 +02:00
" return x; \n "
" } \n \n " ;
2019-04-20 12:02:05 +02:00
}
2019-04-16 20:57:52 +02:00
OS < < Format ( clamp_func ) ;
2019-04-12 23:25:44 +02:00
}
else
{
// Define raw casts from f32->f16
2019-04-20 13:57:05 +02:00
OS < <
" #define clamp16(x) " < < getHalfTypeName ( 4 ) < < " (x) \n " ;
2019-04-12 23:25:44 +02:00
}
2019-04-16 20:57:52 +02:00
2019-04-20 13:57:05 +02:00
OS < <
" #define _builtin_lit lit_legacy \n "
2019-04-23 16:53:39 +02:00
" #define _builtin_log2 log2 \n "
" #define _builtin_normalize(x) (length(x) > 0? normalize(x) : x) \n " // HACK!! Workaround for some games that generate NaNs unless texture filtering exactly matches PS3 (BFBC)
2019-04-20 13:57:05 +02:00
" #define _builtin_sqrt(x) sqrt(abs(x)) \n "
" #define _builtin_rcp(x) (1. / x) \n "
" #define _builtin_rsq(x) (1. / _builtin_sqrt(x)) \n "
2021-06-09 00:37:59 +02:00
" #define _builtin_div(x, y) (x / y) \n " ;
if ( device_props . has_low_precision_rounding )
{
// NVIDIA has terrible rounding errors interpolating constant values across vertices with different w
// PS3 games blindly rely on interpolating a constant to not change the values
// Calling floor/equality will fail randomly causing a moire pattern
OS < <
" #define _builtin_floor(x) floor(x + 0.000001) \n \n " ;
}
else
{
OS < <
" #define _builtin_floor floor \n \n " ;
}
2019-04-20 13:57:05 +02:00
2021-06-05 01:40:39 +02:00
if ( properties . has_pkg )
{
OS < <
" vec4 _builtin_pkg(const in vec4 value) \n "
" { \n "
" vec4 convert = linear_to_srgb(value); \n "
" return uintBitsToFloat(packUnorm4x8(convert)).xxxx; \n "
" } \n \n " ;
}
if ( properties . has_upg )
{
OS < <
" vec4 _builtin_upg(const in float value) \n "
" { \n "
" vec4 raw = unpackUnorm4x8(floatBitsToUint(value)); \n "
" return srgb_to_linear(raw); \n "
" } \n \n " ;
}
2019-04-23 16:53:39 +02:00
if ( properties . has_divsq )
2019-04-20 12:02:05 +02:00
{
2019-04-23 16:53:39 +02:00
// Define RSX-compliant DIVSQ
// If the numerator is 0, the result is always 0 even if the denominator is 0
// NOTE: This operation is component-wise and cannot be accelerated with lerp/mix because these always return NaN if any of the choices is NaN
std : : string divsq_func =
" $float4 _builtin_divsq($float4 a, float b) \n "
" { \n "
" $float4 tmp = a / _builtin_sqrt(b); \n "
" $float4 choice = abs(a); \n " ;
2019-04-20 12:02:05 +02:00
2019-04-23 16:53:39 +02:00
if ( glsl )
{
divsq_func + =
" return _select(a, tmp, greaterThan(choice, vec4(0.))); \n " ;
}
else
{
divsq_func + =
" if (choice.x > 0.) a.x = tmp.x; \n "
" if (choice.y > 0.) a.y = tmp.y; \n "
" if (choice.z > 0.) a.z = tmp.z; \n "
" if (choice.w > 0.) a.w = tmp.w; \n "
" return a; \n " ;
}
2019-04-16 20:57:52 +02:00
2019-04-23 16:53:39 +02:00
divsq_func + =
" } \n \n " ;
OS < < Format ( divsq_func ) ;
}
2019-04-12 15:25:12 +02:00
// Declare register gather/merge if needed
2018-01-24 22:09:27 +01:00
if ( properties . has_gather_op )
{
std : : string float2 = getFloatTypeName ( 2 ) ;
OS < < float4 < < " gather( " < < float4 < < " _h0, " < < float4 < < " _h1) \n " ;
OS < < " { \n " ;
OS < < " float x = uintBitsToFloat(packHalf2x16(_h0.xy)); \n " ;
OS < < " float y = uintBitsToFloat(packHalf2x16(_h0.zw)); \n " ;
OS < < " float z = uintBitsToFloat(packHalf2x16(_h1.xy)); \n " ;
OS < < " float w = uintBitsToFloat(packHalf2x16(_h1.zw)); \n " ;
OS < < " return " < < float4 < < " (x, y, z, w); \n " ;
OS < < " } \n \n " ;
OS < < float2 < < " gather( " < < float4 < < " _h) \n " ;
OS < < " { \n " ;
OS < < " float x = uintBitsToFloat(packHalf2x16(_h.xy)); \n " ;
OS < < " float y = uintBitsToFloat(packHalf2x16(_h.zw)); \n " ;
OS < < " return " < < float2 < < " (x, y); \n " ;
OS < < " } \n \n " ;
}
2017-11-30 19:47:25 +01:00
2021-08-23 10:48:32 +02:00
if ( properties . has_dynamic_register_load )
{
OS < <
" vec4 _indexed_load(int index) \n "
" { \n "
" switch (index) \n "
" { \n "
" case 0: return tc0; \n "
" case 1: return tc1; \n "
" case 2: return tc2; \n "
" case 3: return tc3; \n "
" case 4: return tc4; \n "
" case 5: return tc5; \n "
" case 6: return tc6; \n "
" case 7: return tc7; \n "
" case 8: return tc8; \n "
" case 9: return tc9; \n "
" } \n "
" return vec4(0., 0., 0., 1.); \n "
" } \n \n " ;
}
2015-05-19 18:17:08 +02:00
insertMainStart ( OS ) ;
OS < < main < < std : : endl ;
insertMainEnd ( OS ) ;
return OS . str ( ) ;
}
2019-04-12 23:25:44 +02:00
bool FragmentProgramDecompiler : : handle_sct_scb ( u32 opcode )
2015-11-16 00:37:44 +01:00
{
2019-04-16 20:57:52 +02:00
// Compliance notes based on HW tests:
2019-04-23 16:53:39 +02:00
// DIV is IEEE compliant as is MUL, LG2, EX2. LG2 with negative input returns NaN as expected.
2019-04-16 20:57:52 +02:00
// DIVSQ is not compliant. Result is 0 if numerator is 0 regardless of denominator
// RSQ(0) and RCP(0) return INF as expected
2019-04-23 16:53:39 +02:00
// RSQ ignores the sign of the inputs (Metro Last Light, GTA4)
// SAT modifier flushes NaNs to 0
2019-04-16 20:57:52 +02:00
// Some games that rely on broken DIVSQ behaviour include Dark Souls II and Super Puzzle Fighter II Turbo HD Remix
2015-11-16 00:37:44 +01:00
switch ( opcode )
{
case RSX_FP_OPCODE_ADD : SetDst ( " ($0 + $1) " ) ; return true ;
2019-04-13 12:20:50 +02:00
case RSX_FP_OPCODE_DIV : SetDst ( " _builtin_div($0, $1.x) " ) ; return true ;
2019-04-23 16:53:39 +02:00
case RSX_FP_OPCODE_DIVSQ :
SetDst ( " _builtin_divsq($0, $1.x) " ) ;
properties . has_divsq = true ;
return true ;
2021-10-05 19:33:58 +02:00
case RSX_FP_OPCODE_DP2 : SetDst ( getFunction ( FUNCTION : : DP2 ) , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_DP3 : SetDst ( getFunction ( FUNCTION : : DP3 ) , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_DP4 : SetDst ( getFunction ( FUNCTION : : DP4 ) , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_DP2A : SetDst ( getFunction ( FUNCTION : : DP2A ) , OPFLAGS : : op_extern ) ; return true ;
2019-06-15 15:16:22 +02:00
case RSX_FP_OPCODE_MAD : SetDst ( " fma($0, $1, $2) " , OPFLAGS : : src_cast_f32 ) ; return true ;
2019-04-23 16:53:39 +02:00
case RSX_FP_OPCODE_MAX : SetDst ( " max($0, $1) " , OPFLAGS : : src_cast_f32 ) ; return true ;
case RSX_FP_OPCODE_MIN : SetDst ( " min($0, $1) " , OPFLAGS : : src_cast_f32 ) ; return true ;
2015-11-16 00:37:44 +01:00
case RSX_FP_OPCODE_MOV : SetDst ( " $0 " ) ; return true ;
case RSX_FP_OPCODE_MUL : SetDst ( " ($0 * $1) " ) ; return true ;
2019-04-13 12:20:50 +02:00
case RSX_FP_OPCODE_RCP : SetDst ( " _builtin_rcp($0.x).xxxx " ) ; return true ;
case RSX_FP_OPCODE_RSQ : SetDst ( " _builtin_rsq($0.x).xxxx " ) ; return true ;
2021-10-05 19:33:58 +02:00
case RSX_FP_OPCODE_SEQ : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SEQ , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_SFL : SetDst ( getFunction ( FUNCTION : : SFL ) , OPFLAGS : : skip_type_cast ) ; return true ;
case RSX_FP_OPCODE_SGE : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SGE , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_SGT : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SGT , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_SLE : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SLE , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_SLT : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SLT , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_SNE : SetDst ( " $Ty( " + compareFunction ( COMPARE : : SNE , " $0 " , " $1 " ) + " ) " , OPFLAGS : : op_extern ) ; return true ;
case RSX_FP_OPCODE_STR : SetDst ( getFunction ( FUNCTION : : STR ) , OPFLAGS : : skip_type_cast ) ; return true ;
2015-11-16 00:37:44 +01:00
2019-04-12 23:25:44 +02:00
// SCB-only ops
2015-11-16 00:37:44 +01:00
case RSX_FP_OPCODE_COS : SetDst ( " cos($0.xxxx) " ) ; return true ;
2020-04-05 13:22:32 +02:00
case RSX_FP_OPCODE_DST : SetDst ( " $Ty(1.0, $0.y * $1.y, $0.z, $1.w) " , OPFLAGS : : op_extern ) ; return true ;
2021-10-05 19:33:58 +02:00
case RSX_FP_OPCODE_REFL : SetDst ( getFunction ( FUNCTION : : REFL ) , OPFLAGS : : op_extern ) ; return true ;
2015-11-16 00:37:44 +01:00
case RSX_FP_OPCODE_EX2 : SetDst ( " exp2($0.xxxx) " ) ; return true ;
2021-06-09 00:37:59 +02:00
case RSX_FP_OPCODE_FLR : SetDst ( " _builtin_floor($0) " ) ; return true ;
2021-10-05 19:33:58 +02:00
case RSX_FP_OPCODE_FRC : SetDst ( getFunction ( FUNCTION : : FRACT ) ) ; return true ;
2018-01-24 22:09:27 +01:00
case RSX_FP_OPCODE_LIT :
2019-04-16 12:50:39 +02:00
SetDst ( " _builtin_lit($0) " ) ;
2018-01-24 22:09:27 +01:00
properties . has_lit_op = true ;
return true ;
2019-04-20 12:30:08 +02:00
case RSX_FP_OPCODE_LIF : SetDst ( " $Ty(1.0, $0.y, ($0.y > 0 ? pow(2.0, $0.w) : 0.0), 1.0) " , OPFLAGS : : op_extern ) ; return true ;
2019-04-13 12:20:50 +02:00
case RSX_FP_OPCODE_LRP : SetDst ( " $Ty($2 * (1 - $0) + $1 * $0) " , OPFLAGS : : skip_type_cast ) ; return true ;
case RSX_FP_OPCODE_LG2 : SetDst ( " _builtin_log2($0.x).xxxx " ) ; return true ;
2020-05-09 14:36:17 +02:00
// Pack operations. See https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
// PK2 = PK2H (2 16-bit floats)
// PK16 = PK2US (2 unsigned 16-bit scalars)
// PK4 = PK4B (4 signed 8-bit scalars)
// PKB = PK4UB (4 unsigned 8-bit scalars)
// PK16/UP16 behavior confirmed by Saints Row: Gat out of Hell, ARGB8 -> X16Y16 conversion relies on this to render the wings
2017-12-01 13:57:28 +01:00
case RSX_FP_OPCODE_PK2 : SetDst ( getFloatTypeName ( 4 ) + " (uintBitsToFloat(packHalf2x16($0.xy))) " ) ; return true ;
2017-11-29 17:09:59 +01:00
case RSX_FP_OPCODE_PK4 : SetDst ( getFloatTypeName ( 4 ) + " (uintBitsToFloat(packSnorm4x8($0))) " ) ; return true ;
2020-05-09 14:36:17 +02:00
case RSX_FP_OPCODE_PK16 : SetDst ( getFloatTypeName ( 4 ) + " (uintBitsToFloat(packUnorm2x16($0.xy))) " ) ; return true ;
2017-12-01 13:57:28 +01:00
case RSX_FP_OPCODE_PKG :
2019-04-13 12:20:50 +02:00
// Should be similar to PKB but with gamma correction, see description of PK4UBG in khronos page
2021-06-05 01:40:39 +02:00
properties . has_pkg = true ;
SetDst ( " _builtin_pkg($0) " ) ;
return true ;
2017-11-29 17:09:59 +01:00
case RSX_FP_OPCODE_PKB : SetDst ( getFloatTypeName ( 4 ) + " (uintBitsToFloat(packUnorm4x8($0))) " ) ; return true ;
2015-11-16 00:37:44 +01:00
case RSX_FP_OPCODE_SIN : SetDst ( " sin($0.xxxx) " ) ; return true ;
}
return false ;
}
bool FragmentProgramDecompiler : : handle_tex_srb ( u32 opcode )
{
2021-10-05 19:33:58 +02:00
auto insert_texture_fetch = [ this ] ( FUNCTION base_func )
2020-05-12 22:52:11 +02:00
{
const auto type = m_prog . get_texture_dimension ( dst . tex_num ) ;
2020-12-24 19:44:15 +01:00
const auto ref_mask = ( 1 < < dst . tex_num ) ;
std : : string swz_mask = " " ;
2020-05-12 22:52:11 +02:00
2021-10-05 19:33:58 +02:00
auto func_id = base_func ;
if ( m_prog . texture_state . shadow_textures & ref_mask )
2020-05-12 22:52:11 +02:00
{
2021-10-05 19:33:58 +02:00
properties . shadow_sampler_mask | = ref_mask ;
swz_mask = " .xxxx " ;
func_id = ( base_func = = FUNCTION : : TEXTURE_SAMPLE_PROJ_BASE ) ? FUNCTION : : TEXTURE_SAMPLE_SHADOW_PROJ_BASE : FUNCTION : : TEXTURE_SAMPLE_SHADOW_BASE ;
}
else
{
properties . common_access_sampler_mask | = ref_mask ;
if ( m_prog . texture_state . redirected_textures & ref_mask )
2020-05-12 22:52:11 +02:00
{
2021-10-05 19:33:58 +02:00
properties . redirected_sampler_mask | = ref_mask ;
func_id = ( base_func = = FUNCTION : : TEXTURE_SAMPLE_PROJ_BASE ) ? FUNCTION : : TEXTURE_SAMPLE_DEPTH_RGBA_PROJ_BASE : FUNCTION : : TEXTURE_SAMPLE_DEPTH_RGBA_BASE ;
2020-05-12 22:52:11 +02:00
}
}
2021-10-05 19:33:58 +02:00
// Sanity checks
if ( func_id ! = base_func & & base_func ! = FUNCTION : : TEXTURE_SAMPLE_BASE )
2020-06-11 21:46:53 +02:00
{
2021-10-05 19:33:58 +02:00
// A lot of redundant special-access modes would be needed to cater for all the variations, ignore special modifiers for now, but log an error
rsx_log . error ( " [Unimplemented warning] Conflicting texture decode options in the shaders detected. Base option=%d, selected=%d " , static_cast < int > ( base_func ) , static_cast < int > ( func_id ) ) ;
2020-06-11 21:46:53 +02:00
}
2021-10-05 19:33:58 +02:00
ensure ( func_id < = FUNCTION : : TEXTURE_SAMPLE_MAX_BASE_ENUM & & func_id > = FUNCTION : : TEXTURE_SAMPLE_BASE ) ;
2021-01-20 21:23:00 +01:00
2021-10-05 19:33:58 +02:00
// Clamp type to 3 types (1d, 2d, cube+3d) and offset into sampling redirection table
const auto type_offset = ( std : : min ( static_cast < int > ( type ) , 2 ) + 1 ) * static_cast < int > ( FUNCTION : : TEXTURE_SAMPLE_BASE_ENUM_COUNT ) ;
func_id = static_cast < FUNCTION > ( static_cast < int > ( func_id ) + type_offset ) ;
if ( dst . exp_tex )
2021-01-20 21:23:00 +01:00
{
2021-10-05 19:33:58 +02:00
properties . has_exp_tex_op = true ;
AddCode ( " _enable_texture_expand(); " ) ;
2021-01-20 21:23:00 +01:00
}
2021-10-05 19:33:58 +02:00
SetDst ( getFunction ( func_id ) + swz_mask ) ;
2020-06-11 21:46:53 +02:00
if ( dst . exp_tex )
{
// Cleanup
AddCode ( " _disable_texture_expand(); " ) ;
}
2020-05-12 22:52:11 +02:00
} ;
2015-11-16 00:37:44 +01:00
switch ( opcode )
{
2021-10-05 19:33:58 +02:00
case RSX_FP_OPCODE_DDX : SetDst ( getFunction ( FUNCTION : : DFDX ) ) ; return true ;
case RSX_FP_OPCODE_DDY : SetDst ( getFunction ( FUNCTION : : DFDY ) ) ; return true ;
2019-04-23 16:53:39 +02:00
case RSX_FP_OPCODE_NRM : SetDst ( " _builtin_normalize($0.xyz).xyzz " , OPFLAGS : : src_cast_f32 ) ; return true ;
2017-12-10 23:01:28 +01:00
case RSX_FP_OPCODE_BEM : SetDst ( " $0.xyxy + $1.xxxx * $2.xzxz + $1.yyyy * $2.ywyw " ) ; return true ;
2017-03-07 11:40:38 +01:00
case RSX_FP_OPCODE_TEXBEM :
2020-05-12 22:52:11 +02:00
{
2017-12-10 23:34:44 +01:00
//Untested, should be x2d followed by TEX
AddX2d ( ) ;
2017-12-11 10:03:31 +01:00
AddCode ( Format ( " x2d = $0.xyxy + $1.xxxx * $2.xzxz + $1.yyyy * $2.ywyw; " , true ) ) ;
2018-09-06 13:28:12 +02:00
[[fallthrough]] ;
2020-05-12 22:52:11 +02:00
}
2015-12-10 02:52:27 +01:00
case RSX_FP_OPCODE_TEX :
2020-05-12 22:52:11 +02:00
{
2018-11-24 13:54:46 +01:00
AddTex ( ) ;
2021-10-05 19:33:58 +02:00
insert_texture_fetch ( FUNCTION : : TEXTURE_SAMPLE_BASE ) ;
2020-05-12 22:52:11 +02:00
return true ;
}
2017-03-07 11:40:38 +01:00
case RSX_FP_OPCODE_TXPBEM :
2020-05-12 22:52:11 +02:00
{
// Untested, should be x2d followed by TXP
2017-12-11 09:37:20 +01:00
AddX2d ( ) ;
2017-12-11 10:03:31 +01:00
AddCode ( Format ( " x2d = $0.xyxy + $1.xxxx * $2.xzxz + $1.yyyy * $2.ywyw; " , true ) ) ;
2018-09-06 13:28:12 +02:00
[[fallthrough]] ;
2020-05-12 22:52:11 +02:00
}
2015-12-10 02:52:27 +01:00
case RSX_FP_OPCODE_TXP :
2020-05-12 22:52:11 +02:00
{
2018-11-24 13:54:46 +01:00
AddTex ( ) ;
2021-10-05 19:33:58 +02:00
insert_texture_fetch ( FUNCTION : : TEXTURE_SAMPLE_PROJ_BASE ) ;
2020-05-12 22:52:11 +02:00
return true ;
}
2016-05-29 17:33:41 +02:00
case RSX_FP_OPCODE_TXD :
2020-05-12 22:52:11 +02:00
{
2018-11-24 13:54:46 +01:00
AddTex ( ) ;
2021-10-05 19:33:58 +02:00
insert_texture_fetch ( FUNCTION : : TEXTURE_SAMPLE_GRAD_BASE ) ;
2020-05-12 22:52:11 +02:00
return true ;
}
2017-03-07 11:40:38 +01:00
case RSX_FP_OPCODE_TXB :
2020-05-12 22:52:11 +02:00
{
2018-11-24 13:54:46 +01:00
AddTex ( ) ;
2021-10-05 19:33:58 +02:00
insert_texture_fetch ( FUNCTION : : TEXTURE_SAMPLE_BIAS_BASE ) ;
2020-05-12 22:52:11 +02:00
return true ;
}
2016-01-10 00:16:13 +01:00
case RSX_FP_OPCODE_TXL :
2020-05-12 22:52:11 +02:00
{
2018-11-24 13:54:46 +01:00
AddTex ( ) ;
2021-10-05 19:33:58 +02:00
insert_texture_fetch ( FUNCTION : : TEXTURE_SAMPLE_LOD_BASE ) ;
2020-05-12 22:52:11 +02:00
return true ;
}
2019-04-13 12:20:50 +02:00
// Unpack operations. See https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
2020-05-09 14:36:17 +02:00
// UP2 = UP2H (2 16-bit floats)
// UP16 = UP2US (2 unsigned 16-bit scalars)
// UP4 = UP4B (4 signed 8-bit scalars)
// UPB = UP4UB (4 unsigned 8-bit scalars)
// PK16/UP16 behavior confirmed by Saints Row: Gat out of Hell, ARGB8 -> X16Y16 conversion relies on this to render the wings
2017-12-01 13:57:28 +01:00
case RSX_FP_OPCODE_UP2 : SetDst ( " unpackHalf2x16(floatBitsToUint($0.x)).xyxy " ) ; return true ;
case RSX_FP_OPCODE_UP4 : SetDst ( " unpackSnorm4x8(floatBitsToUint($0.x)) " ) ; return true ;
2020-05-09 14:36:17 +02:00
case RSX_FP_OPCODE_UP16 : SetDst ( " unpackUnorm2x16(floatBitsToUint($0.x)).xyxy " ) ; return true ;
2017-12-01 13:57:28 +01:00
case RSX_FP_OPCODE_UPG :
2021-06-05 01:40:39 +02:00
// Same as UPB with gamma correction
properties . has_upg = true ;
SetDst ( " _builtin_upg($0.x) " ) ;
return true ;
2017-11-29 17:09:59 +01:00
case RSX_FP_OPCODE_UPB : SetDst ( " (unpackUnorm4x8(floatBitsToUint($0.x))) " ) ; return true ;
2015-11-16 00:37:44 +01:00
}
return false ;
2019-05-11 10:14:56 +02:00
}
2015-11-16 00:37:44 +01:00
2015-05-19 18:17:08 +02:00
std : : string FragmentProgramDecompiler : : Decompile ( )
{
2020-10-27 21:41:20 +01:00
auto data = static_cast < be_t < u32 > * > ( m_prog . get_data ( ) ) ;
2015-05-19 18:17:08 +02:00
m_size = 0 ;
m_location = 0 ;
m_loop_count = 0 ;
m_code_level = 1 ;
enum
{
FORCE_NONE ,
FORCE_SCT ,
FORCE_SCB ,
} ;
int forced_unit = FORCE_NONE ;
while ( true )
{
2016-05-13 16:01:48 +02:00
for ( auto found = std : : find ( m_end_offsets . begin ( ) , m_end_offsets . end ( ) , m_size ) ;
2018-02-25 14:07:55 +01:00
found ! = m_end_offsets . end ( ) ;
2016-05-13 16:01:48 +02:00
found = std : : find ( m_end_offsets . begin ( ) , m_end_offsets . end ( ) , m_size ) )
2015-05-19 18:17:08 +02:00
{
2016-05-13 16:01:48 +02:00
m_end_offsets . erase ( found ) ;
2015-05-19 18:17:08 +02:00
m_code_level - - ;
AddCode ( " } " ) ;
m_loop_count - - ;
}
2016-05-13 16:01:48 +02:00
for ( auto found = std : : find ( m_else_offsets . begin ( ) , m_else_offsets . end ( ) , m_size ) ;
2018-02-25 14:07:55 +01:00
found ! = m_else_offsets . end ( ) ;
2016-05-13 16:01:48 +02:00
found = std : : find ( m_else_offsets . begin ( ) , m_else_offsets . end ( ) , m_size ) )
2015-05-19 18:17:08 +02:00
{
2016-05-13 16:01:48 +02:00
m_else_offsets . erase ( found ) ;
2015-05-19 18:17:08 +02:00
m_code_level - - ;
AddCode ( " } " ) ;
AddCode ( " else " ) ;
AddCode ( " { " ) ;
m_code_level + + ;
}
dst . HEX = GetData ( data [ 0 ] ) ;
src0 . HEX = GetData ( data [ 1 ] ) ;
src1 . HEX = GetData ( data [ 2 ] ) ;
src2 . HEX = GetData ( data [ 3 ] ) ;
m_offset = 4 * sizeof ( u32 ) ;
2019-04-13 12:20:50 +02:00
opflags = 0 ;
2015-05-19 18:17:08 +02:00
const u32 opcode = dst . opcode | ( src1 . opcode_is_branch < < 6 ) ;
auto SIP = [ & ] ( )
{
switch ( opcode )
{
2017-06-01 14:53:25 +02:00
case RSX_FP_OPCODE_BRK :
if ( m_loop_count ) AddFlowOp ( " break " ) ;
2020-02-01 09:07:25 +01:00
else rsx_log . error ( " BRK opcode found outside of a loop " ) ;
2017-06-01 14:53:25 +02:00
break ;
2018-02-03 09:37:42 +01:00
case RSX_FP_OPCODE_CAL :
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Unimplemented SIP instruction: CAL " ) ;
2018-02-03 09:37:42 +01:00
break ;
case RSX_FP_OPCODE_FENCT :
AddCode ( " //FENCT " ) ;
forced_unit = FORCE_SCT ;
break ;
case RSX_FP_OPCODE_FENCB :
AddCode ( " //FENCB " ) ;
forced_unit = FORCE_SCB ;
break ;
2015-05-19 18:17:08 +02:00
case RSX_FP_OPCODE_IFE :
AddCode ( " if($cond) " ) ;
2015-05-22 01:56:49 +02:00
if ( src2 . end_offset ! = src1 . else_offset )
m_else_offsets . push_back ( src1 . else_offset < < 2 ) ;
2015-05-19 18:17:08 +02:00
m_end_offsets . push_back ( src2 . end_offset < < 2 ) ;
AddCode ( " { " ) ;
m_code_level + + ;
break ;
case RSX_FP_OPCODE_LOOP :
if ( ! src0 . exec_if_eq & & ! src0 . exec_if_gr & & ! src0 . exec_if_lt )
{
2017-06-01 14:53:25 +02:00
AddCode ( fmt : : format ( " //$ifcond for(int i%u = %u; i%u < %u; i%u += %u) {} //-> %u //LOOP " ,
2015-05-19 18:17:08 +02:00
m_loop_count , src1 . init_counter , m_loop_count , src1 . end_counter , m_loop_count , src1 . increment , src2 . end_offset ) ) ;
}
else
{
2015-08-12 20:38:17 +02:00
AddCode ( fmt : : format ( " $ifcond for(int i%u = %u; i%u < %u; i%u += %u) //LOOP " ,
2015-05-19 18:17:08 +02:00
m_loop_count , src1 . init_counter , m_loop_count , src1 . end_counter , m_loop_count , src1 . increment ) ) ;
m_loop_count + + ;
m_end_offsets . push_back ( src2 . end_offset < < 2 ) ;
AddCode ( " { " ) ;
m_code_level + + ;
}
break ;
case RSX_FP_OPCODE_REP :
if ( ! src0 . exec_if_eq & & ! src0 . exec_if_gr & & ! src0 . exec_if_lt )
{
2017-06-01 14:53:25 +02:00
AddCode ( fmt : : format ( " //$ifcond for(int i%u = %u; i%u < %u; i%u += %u) {} //-> %u //REP " ,
2015-05-19 18:17:08 +02:00
m_loop_count , src1 . init_counter , m_loop_count , src1 . end_counter , m_loop_count , src1 . increment , src2 . end_offset ) ) ;
}
else
{
2015-08-12 20:38:17 +02:00
AddCode ( fmt : : format ( " if($cond) for(int i%u = %u; i%u < %u; i%u += %u) //REP " ,
2015-05-19 18:17:08 +02:00
m_loop_count , src1 . init_counter , m_loop_count , src1 . end_counter , m_loop_count , src1 . increment ) ) ;
m_loop_count + + ;
m_end_offsets . push_back ( src2 . end_offset < < 2 ) ;
AddCode ( " { " ) ;
m_code_level + + ;
}
break ;
2018-02-03 09:37:42 +01:00
case RSX_FP_OPCODE_RET :
AddFlowOp ( " return " ) ;
break ;
2015-05-19 18:17:08 +02:00
default :
return false ;
}
return true ;
} ;
switch ( opcode )
{
case RSX_FP_OPCODE_NOP : break ;
2019-01-18 10:23:30 +01:00
case RSX_FP_OPCODE_KIL :
properties . has_discard_op = true ;
2019-10-14 00:24:04 +02:00
AddFlowOp ( " _kill() " ) ;
2019-01-18 10:23:30 +01:00
break ;
2015-05-19 18:17:08 +02:00
default :
2016-04-02 18:18:25 +02:00
int prev_force_unit = forced_unit ;
2019-04-12 23:25:44 +02:00
// Some instructions do not respect forced unit
// Tested with Tales of Vesperia
2017-06-30 12:59:02 +02:00
if ( SIP ( ) ) break ;
if ( handle_tex_srb ( opcode ) ) break ;
2019-04-12 23:25:44 +02:00
// FENCT/FENCB do not actually reject instructions if they dont match the forced unit
// Looks like they are optimization hints and not hard-coded forced paths
if ( handle_sct_scb ( opcode ) ) break ;
2017-11-08 14:27:20 +01:00
forced_unit = FORCE_NONE ;
2015-05-19 18:17:08 +02:00
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Unknown/illegal instruction: 0x%x (forced unit %d) " , opcode , prev_force_unit ) ;
2015-05-19 18:17:08 +02:00
break ;
}
m_size + = m_offset ;
if ( dst . end ) break ;
2020-12-09 08:47:45 +01:00
ensure ( m_offset % sizeof ( u32 ) = = 0 ) ;
2015-05-19 18:17:08 +02:00
data + = m_offset / sizeof ( u32 ) ;
}
2017-09-05 15:25:02 +02:00
while ( m_code_level > 1 )
{
2020-02-01 09:07:25 +01:00
rsx_log . error ( " Hanging block found at end of shader. Malformed shader? " ) ;
2017-09-05 15:25:02 +02:00
m_code_level - - ;
AddCode ( " } " ) ;
}
2015-05-19 18:17:08 +02:00
// flush m_code_level
m_code_level = 1 ;
std : : string m_shader = BuildCode ( ) ;
main . clear ( ) ;
// m_parr.params.clear();
return m_shader ;
2015-05-23 16:28:22 +02:00
}