rsx: Use native half float types if available

- Emulating f16 with f32 is not ideal and requires a lot of value clamping
- Using native data type can significantly improve performance and accuracy
- With openGL, check for the compatible extensions NV_gpu_shader5 and
AMD_gpu_shader_half_float
- With Vulkan, enable this functionality in the deviceFeatures if
applicable. (VK_KHR_shader_float16_int8 extension)
- Temporarily disable hw fp16 for vulkan
This commit is contained in:
kd-11 2019-04-13 00:25:44 +03:00 committed by kd-11
parent ee319f7c13
commit a668560c68
14 changed files with 435 additions and 236 deletions

View File

@ -16,7 +16,6 @@ FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &p
m_size = 0;
}
void FragmentProgramDecompiler::SetDst(std::string code, bool append_mask)
{
if (!src0.exec_if_eq && !src0.exec_if_gr && !src0.exec_if_lt) return;
@ -45,6 +44,20 @@ void FragmentProgramDecompiler::SetDst(std::string code, bool append_mask)
code = "((" + code + "- 0.5) * 2.)";
}
if (dst.fp16 && device_props.has_native_half_support)
{
// Cast to native data type
if (dst.opcode == RSX_FP_OPCODE_NRM)
{
// Returns a 3-component vector as the result
code = ClampValue(code + ".xyzz", 1);
}
else
{
code = ClampValue(code, 1);
}
}
if (dst.saturate)
{
code = saturate(code);
@ -67,16 +80,20 @@ void FragmentProgramDecompiler::SetDst(std::string code, bool append_mask)
case RSX_FP_OPCODE_LG2:
break;
case RSX_FP_OPCODE_MOV:
//NOTE: Sometimes varying inputs from VS are out of range so do not exempt any input types, unless fp16 (Naruto UNS)
// NOTE: Sometimes varying inputs from VS are out of range so do not exempt any input types, unless fp16 (Naruto UNS)
if (dst.fp16 && src0.fp16 && src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP)
break;
default:
{
//fp16 precsion flag on f32 register; ignore
// fp16 precsion flag on f32 register; ignore
if (dst.prec == 1 && !dst.fp16)
break;
//clamp value to allowed range
// 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
code = ClampValue(code, dst.prec);
break;
}
@ -100,7 +117,7 @@ void FragmentProgramDecompiler::SetDst(std::string code, bool append_mask)
return;
}
std::string dest = AddReg(dst.dest_reg, dst.fp16) + "$m";
std::string dest = AddReg(dst.dest_reg, !!dst.fp16) + "$m";
AddCodeCond(Format(dest), code);
//AddCode("$ifcond " + dest + code + (append_mask ? "$m;" : ";"));
@ -159,15 +176,20 @@ std::string FragmentProgramDecompiler::GetMask()
return ret.empty() || strncmp(ret.c_str(), dst_mask, 4) == 0 ? "" : ("." + ret);
}
std::string FragmentProgramDecompiler::AddReg(u32 index, int fp16)
std::string FragmentProgramDecompiler::AddReg(u32 index, bool fp16)
{
return m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), std::string(fp16 ? "h" : "r") + std::to_string(index), getFloatTypeName(4) + "(0., 0., 0., 0.)");
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.AddParam(PF_PARAM_NONE, type_name, reg_name, type_name + "(0., 0., 0., 0.)");
}
bool FragmentProgramDecompiler::HasReg(u32 index, int fp16)
bool FragmentProgramDecompiler::HasReg(u32 index, bool fp16)
{
return m_parr.HasParam(PF_PARAM_NONE, getFloatTypeName(4),
std::string(fp16 ? "h" : "r") + std::to_string(index));
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);
}
std::string FragmentProgramDecompiler::AddCond()
@ -177,22 +199,23 @@ std::string FragmentProgramDecompiler::AddCond()
std::string FragmentProgramDecompiler::AddConst()
{
std::string name = std::string("fc") + std::to_string(m_size + 4 * 4);
if (m_parr.HasParam(PF_PARAM_UNIFORM, getFloatTypeName(4), name))
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))
{
return name;
}
auto data = (be_t<u32>*) ((char*)m_prog.addr + m_size + 4 * u32{sizeof(u32)});
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]);
return m_parr.AddParam(PF_PARAM_UNIFORM, getFloatTypeName(4), name,
std::string(getFloatTypeName(4) + "(") + std::to_string((float&)x) + ", " + std::to_string((float&)y)
+ ", " + std::to_string((float&)z) + ", " + std::to_string((float&)w) + ")");
const auto var = fmt::format("%s(%f, %f, %f, %f)", type, (f32&)x, (f32&)y, (f32&)z, (f32&)w);
return m_parr.AddParam(PF_PARAM_UNIFORM, type, name, var);
}
std::string FragmentProgramDecompiler::AddTex()
@ -240,9 +263,9 @@ std::string FragmentProgramDecompiler::NotZeroPositive(const std::string& code)
std::string FragmentProgramDecompiler::ClampValue(const std::string& code, u32 precision)
{
//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
// 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
switch (precision)
{
@ -405,7 +428,7 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
}
}
ret += AddReg(src.tmp_reg_index, src.fp16);
ret += AddReg(src.tmp_reg_index, !!src.fp16);
break;
case RSX_FP_REGISTER_TYPE_INPUT:
@ -472,7 +495,7 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
if (strncmp(swizzle.c_str(), f, 4) != 0) ret += "." + swizzle;
//Warning: Modifier order matters. e.g neg should be applied after precision clamping (tested with Naruto UNS)
// Warning: Modifier order matters. e.g neg should be applied after precision clamping (tested with Naruto UNS)
if (src.abs) ret = "abs(" + ret + ")";
if (apply_precision_modifier) ret = ClampValue(ret, src1.input_prec_mod);
if (src.neg) ret = "-" + ret;
@ -485,7 +508,8 @@ std::string FragmentProgramDecompiler::BuildCode()
// Shader validation
// Shader must at least write to one output for the body to be considered valid
const std::string vec4_type = getFloatTypeName(4);
const bool fp16_out = !(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS);
const std::string vec4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4);
const std::string init_value = vec4_type + "(0., 0., 0., 0.)";
std::array<std::string, 4> output_register_names;
std::array<u32, 4> ouput_register_indices = { 0, 2, 3, 4 };
@ -502,7 +526,7 @@ std::string FragmentProgramDecompiler::BuildCode()
// 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)
if (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
if (!fp16_out)
{
output_register_names = { "r0", "r2", "r3", "r4" };
}
@ -548,22 +572,32 @@ std::string FragmentProgramDecompiler::BuildCode()
// Insert global function definitions
insertGlobalFunctions(OS);
// Accurate float to half clamping (preserves IEEE-754 NaN)
OS <<
"vec4 clamp16(vec4 x)\n"
"{\n"
" bvec4 sel = isnan(x);\n"
" vec4 clamped = clamp(x, -65504., +65504.);\n"
" if (!any(sel))\n"
" {\n"
" return clamped;\n"
" }\n\n"
" return _select(clamped, x, sel);\n"
"}\n\n"
if (!device_props.has_native_half_support)
{
// Accurate float to half clamping (preserves IEEE-754 NaN)
OS <<
"vec4 clamp16(vec4 x)\n"
"{\n"
" bvec4 sel = isnan(x);\n"
" vec4 clamped = clamp(x, -65504., +65504.);\n"
" if (!any(sel))\n"
" {\n"
" return clamped;\n"
" }\n\n"
" return _select(clamped, x, sel);\n"
"}\n\n"
"vec3 clamp16(vec3 x){ return clamp16(x.xyzz).xyz; }\n"
"vec2 clamp16(vec2 x){ return clamp16(x.xyxy).xy; }\n"
"float clamp16(float x){ return isnan(x)? x : clamp(x, -65504., +65504.); }\n";
"vec3 clamp16(vec3 x){ return clamp16(x.xyzz).xyz; }\n"
"vec2 clamp16(vec2 x){ return clamp16(x.xyxy).xy; }\n"
"float clamp16(float x){ return isnan(x)? x : clamp(x, -65504., +65504.); }\n\n";
}
else
{
// Define raw casts from f32->f16
const std::string half4 = getHalfTypeName(4);
OS << "#define clamp16(x) " << half4 << "(x)\n\n";
}
// Declare register gather/merge if needed
if (properties.has_gather_op)
@ -595,7 +629,7 @@ std::string FragmentProgramDecompiler::BuildCode()
return OS.str();
}
bool FragmentProgramDecompiler::handle_sct(u32 opcode)
bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode)
{
switch (opcode)
{
@ -626,24 +660,9 @@ bool FragmentProgramDecompiler::handle_sct(u32 opcode)
case RSX_FP_OPCODE_SLT: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SLT, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SNE: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SNE, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_STR: SetDst(getFunction(FUNCTION::FUNCTION_STR)); return true;
}
return false;
}
bool FragmentProgramDecompiler::handle_scb(u32 opcode)
{
switch (opcode)
{
case RSX_FP_OPCODE_ADD: SetDst("($0 + $1)"); return true;
// SCB-only ops
case RSX_FP_OPCODE_COS: SetDst("cos($0.xxxx)"); return true;
case RSX_FP_OPCODE_DIV: SetDst("($0 / " + NotZero("$1.x") + ")"); return true;
// Note: DIVSQ is not IEEE compliant. sqrt(0, 0) is 0 (Super Puzzle Fighter II Turbo HD Remix).
// sqrt(x, 0) might be equal to some big value (in absolute) whose sign is sign(x) but it has to be proven.
case RSX_FP_OPCODE_DIVSQ: SetDst("($0 / sqrt(" + NotZeroPositive("$1.x") + "))"); return true;
case RSX_FP_OPCODE_DP2: SetDst(getFunction(FUNCTION::FUNCTION_DP2)); return true;
case RSX_FP_OPCODE_DP3: SetDst(getFunction(FUNCTION::FUNCTION_DP3)); return true;
case RSX_FP_OPCODE_DP4: SetDst(getFunction(FUNCTION::FUNCTION_DP4)); return true;
case RSX_FP_OPCODE_DP2A: SetDst(getFunction(FUNCTION::FUNCTION_DP2A)); return true;
case RSX_FP_OPCODE_DST: SetDst("vec4(distance($0, $1))"); return true;
case RSX_FP_OPCODE_REFL: SetDst(getFunction(FUNCTION::FUNCTION_REFL)); return true;
case RSX_FP_OPCODE_EX2: SetDst("exp2($0.xxxx)"); return true;
@ -656,27 +675,14 @@ bool FragmentProgramDecompiler::handle_scb(u32 opcode)
case RSX_FP_OPCODE_LIF: SetDst(getFloatTypeName(4) + "(1.0, $0.y, ($0.y > 0 ? pow(2.0, $0.w) : 0.0), 1.0)"); return true;
case RSX_FP_OPCODE_LRP: SetDst(getFloatTypeName(4) + "($2 * (1 - $0) + $1 * $0)"); return true;
case RSX_FP_OPCODE_LG2: SetDst("log2(" + NotZeroPositive("$0.x") + ").xxxx"); return true;
case RSX_FP_OPCODE_MAD: SetDst("($0 * $1 + $2)"); return true;
case RSX_FP_OPCODE_MAX: SetDst("max($0, $1)"); return true;
case RSX_FP_OPCODE_MIN: SetDst("min($0, $1)"); return true;
case RSX_FP_OPCODE_MOV: SetDst("$0"); return true;
case RSX_FP_OPCODE_MUL: SetDst("($0 * $1)"); return true;
//Pack operations. See https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
//Pack operations. See https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
case RSX_FP_OPCODE_PK2: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packHalf2x16($0.xy)))"); return true;
case RSX_FP_OPCODE_PK4: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packSnorm4x8($0)))"); return true;
case RSX_FP_OPCODE_PK16: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packSnorm2x16($0.xy)))"); return true;
case RSX_FP_OPCODE_PKG:
//Should be similar to PKB but with gamma correction, see description of PK4UBG in khronos page
//Should be similar to PKB but with gamma correction, see description of PK4UBG in khronos page
case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true;
case RSX_FP_OPCODE_SEQ: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SEQ, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SFL: SetDst(getFunction(FUNCTION::FUNCTION_SFL)); return true;
case RSX_FP_OPCODE_SGE: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SGE, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SGT: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SGT, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true;
case RSX_FP_OPCODE_SLE: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SLE, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SLT: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SLT, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_SNE: SetDst(getFloatTypeName(4) + "(" + compareFunction(COMPARE::FUNCTION_SNE, "$0", "$1") + ")"); return true;
case RSX_FP_OPCODE_STR: SetDst(getFunction(FUNCTION::FUNCTION_STR)); return true;
}
return false;
}
@ -949,16 +955,14 @@ std::string FragmentProgramDecompiler::Decompile()
default:
int prev_force_unit = forced_unit;
//Some instructions do not respect forced unit
//Tested with Tales of Vesperia
// Some instructions do not respect forced unit
// Tested with Tales of Vesperia
if (SIP()) break;
if (handle_tex_srb(opcode)) break;
//FENCT/FENCB do not actually reject instructions if they dont match the forced unit
//Tested with Dark Souls II where the respecting FENCX instruction will result in empty luminance averaging shaders
//TODO: More research is needed to determine what real HW does
if (handle_sct(opcode)) break;
if (handle_scb(opcode)) break;
// 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;
forced_unit = FORCE_NONE;
LOG_ERROR(RSX, "Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit);

View File

@ -3,10 +3,119 @@
#include "Emu/RSX/RSXFragmentProgram.h"
#include <sstream>
// Helper for GPR occupancy tracking
struct temp_register
{
bool aliased_r0 = false;
bool aliased_h0 = false;
bool aliased_h1 = false;
bool last_write_half[4] = { false, false, false, false };
u32 real_index = UINT32_MAX;
u32 h0_writes = 0u; // Number of writes to the first 64-bits of the register
u32 h1_writes = 0u; // Number of writes to the last 64-bits of the register
void tag(u32 index, bool half_register, bool x, bool y, bool z, bool w)
{
if (half_register)
{
if (index & 1)
{
if (x) last_write_half[2] = true;
if (y) last_write_half[2] = true;
if (z) last_write_half[3] = true;
if (w) last_write_half[3] = true;
aliased_h1 = true;
h1_writes++;
}
else
{
if (x) last_write_half[0] = true;
if (y) last_write_half[0] = true;
if (z) last_write_half[1] = true;
if (w) last_write_half[1] = true;
aliased_h0 = true;
h0_writes++;
}
}
else
{
if (x) last_write_half[0] = false;
if (y) last_write_half[1] = false;
if (z) last_write_half[2] = false;
if (w) last_write_half[3] = false;
aliased_r0 = true;
h0_writes++;
h1_writes++;
}
if (real_index == UINT32_MAX)
{
if (half_register)
real_index = index >> 1;
else
real_index = index;
}
}
bool requires_gather(u8 channel) const
{
//Data fetched from the single precision register requires merging of the two half registers
verify(HERE), channel < 4;
if (aliased_h0 && channel < 2)
{
return last_write_half[channel];
}
if (aliased_h1 && channel > 1)
{
return last_write_half[channel];
}
return false;
}
bool requires_split(u32 /*index*/) const
{
//Data fetched from any of the two half registers requires sync with the full register
if (!(last_write_half[0] || last_write_half[1]) && aliased_r0)
{
//r0 has been written to
//TODO: Check for specific elements in real32 register
return true;
}
return false;
}
std::string gather_r()
{
std::string h0 = "h" + std::to_string(real_index << 1);
std::string h1 = "h" + std::to_string(real_index << 1 | 1);
std::string reg = "r" + std::to_string(real_index);
std::string ret = "//Invalid gather";
if (aliased_h0 && aliased_h1)
ret = "(gather(" + h0 + ", " + h1 + "))";
else if (aliased_h0)
ret = "(gather(" + h0 + "), " + reg + ".zw)";
else if (aliased_h1)
ret = "(" + reg + ".xy, gather(" + h1 + "))";
return ret;
}
};
/**
* This class is used to translate RSX Fragment program to GLSL/HLSL code
* Backend with text based shader can subclass this class and implement :
* - virtual std::string getFloatTypeName(size_t elementCount) = 0;
* - virtual std::string getHalfTypeName(size_t elementCount) = 0;
* - virtual std::string getFunction(enum class FUNCTION) = 0;
* - virtual std::string saturate(const std::string &code) = 0;
* - virtual std::string compareFunction(enum class COMPARE, const std::string &, const std::string &) = 0;
@ -19,113 +128,6 @@
*/
class FragmentProgramDecompiler
{
struct temp_register
{
bool aliased_r0 = false;
bool aliased_h0 = false;
bool aliased_h1 = false;
bool last_write_half[4] = { false, false, false, false };
u32 real_index = UINT32_MAX;
u32 h0_writes = 0u; // Number of writes to the first 64-bits of the register
u32 h1_writes = 0u; // Number of writes to the last 64-bits of the register
void tag(u32 index, bool half_register, bool x, bool y, bool z, bool w)
{
if (half_register)
{
if (index & 1)
{
if (x) last_write_half[2] = true;
if (y) last_write_half[2] = true;
if (z) last_write_half[3] = true;
if (w) last_write_half[3] = true;
aliased_h1 = true;
h1_writes++;
}
else
{
if (x) last_write_half[0] = true;
if (y) last_write_half[0] = true;
if (z) last_write_half[1] = true;
if (w) last_write_half[1] = true;
aliased_h0 = true;
h0_writes++;
}
}
else
{
if (x) last_write_half[0] = false;
if (y) last_write_half[1] = false;
if (z) last_write_half[2] = false;
if (w) last_write_half[3] = false;
aliased_r0 = true;
h0_writes++;
h1_writes++;
}
if (real_index == UINT32_MAX)
{
if (half_register)
real_index = index >> 1;
else
real_index = index;
}
}
bool requires_gather(u8 channel) const
{
//Data fetched from the single precision register requires merging of the two half registers
verify(HERE), channel < 4;
if (aliased_h0 && channel < 2)
{
return last_write_half[channel];
}
if (aliased_h1 && channel > 1)
{
return last_write_half[channel];
}
return false;
}
bool requires_split(u32 /*index*/) const
{
//Data fetched from any of the two half registers requires sync with the full register
if (!(last_write_half[0] || last_write_half[1]) && aliased_r0)
{
//r0 has been written to
//TODO: Check for specific elements in real32 register
return true;
}
return false;
}
std::string gather_r()
{
std::string h0 = "h" + std::to_string(real_index << 1);
std::string h1 = "h" + std::to_string(real_index << 1 | 1);
std::string reg = "r" + std::to_string(real_index);
std::string ret = "//Invalid gather";
if (aliased_h0 && aliased_h1)
ret = "(gather(" + h0 + ", " + h1 + "))";
else if (aliased_h0)
ret = "(gather(" + h0 + "), " + reg + ".zw)";
else if (aliased_h1)
ret = "(" + reg + ".xy, gather(" + h1 + "))";
return ret;
}
};
OPDEST dst;
SRC0 src0;
SRC1 src1;
@ -148,8 +150,8 @@ class FragmentProgramDecompiler
void SetDst(std::string code, bool append_mask = true);
void AddCode(const std::string& code);
std::string AddReg(u32 index, int fp16);
bool HasReg(u32 index, int fp16);
std::string AddReg(u32 index, bool fp16);
bool HasReg(u32 index, bool fp16);
std::string AddCond();
std::string AddConst();
std::string AddTex();
@ -184,18 +186,11 @@ class FragmentProgramDecompiler
u32 GetData(const u32 d) const { return d << 16 | d >> 16; }
/**
* Emits code if opcode is an SCT one and returns true,
* Emits code if opcode is an SCT/SCB one and returns true,
* otherwise do nothing and return false.
* NOTE: What does SCT means ???
*/
bool handle_sct(u32 opcode);
/**
* Emits code if opcode is an SCB one and returns true,
* otherwise do nothing and return false.
* NOTE: What does SCB means ???
*/
bool handle_scb(u32 opcode);
bool handle_sct_scb(u32 opcode);
/**
* Emits code if opcode is an TEX SRB one and returns true,
@ -203,6 +198,7 @@ class FragmentProgramDecompiler
* NOTE: What does TEX SRB means ???
*/
bool handle_tex_srb(u32 opcode);
protected:
const RSXFragmentProgram &m_prog;
u32 m_ctrl = 0;
@ -214,6 +210,10 @@ protected:
*/
virtual std::string getFloatTypeName(size_t elementCount) = 0;
/** returns the type name of half vectors.
*/
virtual std::string getHalfTypeName(size_t elementCount) = 0;
/** returns string calling function where arguments are passed via
* $0 $1 $2 substring.
*/
@ -259,6 +259,12 @@ public:
}
properties;
struct
{
bool has_native_half_support = false;
}
device_props;
ParamArray m_parr;
FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size);
FragmentProgramDecompiler(const FragmentProgramDecompiler&) = delete;

View File

@ -111,6 +111,23 @@ namespace glsl
}
}
static std::string getHalfTypeNameImpl(size_t elementCount)
{
switch (elementCount)
{
default:
abort();
case 1:
return "float16_t";
case 2:
return "f16vec2";
case 3:
return "f16vec3";
case 4:
return "f16vec4";
}
}
static std::string compareFunctionImpl(COMPARE f, const std::string &Op0, const std::string &Op1, bool scalar = false)
{
if (scalar)
@ -372,7 +389,7 @@ namespace glsl
"}\n\n";
}
static void insert_rop(std::ostream& OS, bool _32_bit_exports)
static void insert_rop(std::ostream& OS, bool _32_bit_exports, bool native_half_support)
{
const std::string reg0 = _32_bit_exports ? "r0" : "h0";
const std::string reg1 = _32_bit_exports ? "r2" : "h4";
@ -398,15 +415,29 @@ namespace glsl
if (!_32_bit_exports)
{
//Tested using NPUB90375; some shaders (32-bit output only?) do not obey srgb flags
OS <<
" else if (srgb_convert)\n"
" {\n"
" " << reg0 << ".rgb = linear_to_srgb(" << reg0 << ").rgb;\n"
" " << reg1 << ".rgb = linear_to_srgb(" << reg1 << ").rgb;\n"
" " << reg2 << ".rgb = linear_to_srgb(" << reg2 << ").rgb;\n"
" " << reg3 << ".rgb = linear_to_srgb(" << reg3 << ").rgb;\n"
" }\n";
// Tested using NPUB90375; some shaders (32-bit output only?) do not obey srgb flags
if (native_half_support)
{
OS <<
" else if (srgb_convert)\n"
" {\n"
" " << reg0 << ".rgb = clamp16(linear_to_srgb(" << reg0 << ")).rgb;\n"
" " << reg1 << ".rgb = clamp16(linear_to_srgb(" << reg1 << ")).rgb;\n"
" " << reg2 << ".rgb = clamp16(linear_to_srgb(" << reg2 << ")).rgb;\n"
" " << reg3 << ".rgb = clamp16(linear_to_srgb(" << reg3 << ")).rgb;\n"
" }\n";
}
else
{
OS <<
" else if (srgb_convert)\n"
" {\n"
" " << reg0 << ".rgb = linear_to_srgb(" << reg0 << ").rgb;\n"
" " << reg1 << ".rgb = linear_to_srgb(" << reg1 << ").rgb;\n"
" " << reg2 << ".rgb = linear_to_srgb(" << reg2 << ").rgb;\n"
" " << reg3 << ".rgb = linear_to_srgb(" << reg3 << ").rgb;\n"
" }\n";
}
}
OS <<
@ -468,7 +499,7 @@ namespace glsl
// Alpha lower than the real threshold (e.g 0.25 for 4 samples) gets a randomized chance to make it to the lowest transparency state
// Helps to avoid A2C tested foliage disappearing in the distance
OS <<
"bool coverage_test_passes(inout vec4 _sample, uint control)\n"
"bool coverage_test_passes(/*inout*/in vec4 _sample, uint control)\n"
"{\n"
" if ((control & 0x1) == 0) return false;\n"
"\n"

View File

@ -18,6 +18,11 @@ std::string D3D12FragmentDecompiler::getFloatTypeName(size_t elementCount)
return getFloatTypeNameImp(elementCount);
}
std::string D3D12FragmentDecompiler::getHalfTypeName(size_t elementCount)
{
return getFloatTypeNameImp(elementCount);
}
std::string D3D12FragmentDecompiler::getFunction(enum class FUNCTION f)
{
return getFunctionImp(f);

View File

@ -9,6 +9,7 @@ class D3D12FragmentDecompiler : public FragmentProgramDecompiler
{
protected:
virtual std::string getFloatTypeName(size_t elementCount) override;
virtual std::string getHalfTypeName(size_t elementCount) override;
virtual std::string getFunction(enum class FUNCTION) override;
virtual std::string saturate(const std::string &code) override;
virtual std::string compareFunction(enum class COMPARE, const std::string &, const std::string &) override;

View File

@ -2,8 +2,8 @@
#include <set>
#include "Emu/Memory/vm.h"
#include "Emu/System.h"
#include "GLHelpers.h"
#include "GLFragmentProgram.h"
#include "../Common/ProgramStateCache.h"
#include "GLCommonDecompiler.h"
#include "../GCM.h"
@ -13,6 +13,11 @@ std::string GLFragmentDecompilerThread::getFloatTypeName(size_t elementCount)
return glsl::getFloatTypeNameImpl(elementCount);
}
std::string GLFragmentDecompilerThread::getHalfTypeName(size_t elementCount)
{
return glsl::getHalfTypeNameImpl(elementCount);
}
std::string GLFragmentDecompilerThread::getFunction(FUNCTION f)
{
return glsl::getFunctionImpl(f);
@ -31,6 +36,19 @@ std::string GLFragmentDecompilerThread::compareFunction(COMPARE f, const std::st
void GLFragmentDecompilerThread::insertHeader(std::stringstream & OS)
{
OS << "#version 430\n";
if (device_props.has_native_half_support)
{
const auto driver_caps = gl::get_driver_caps();
if (driver_caps.NV_gpu_shader5_supported)
{
OS << "#extension GL_NV_gpu_shader5: require\n";
}
else if (driver_caps.AMD_gpu_shader_half_float_supported)
{
OS << "#extension GL_AMD_gpu_shader_half_float: require\n";
}
}
}
void GLFragmentDecompilerThread::insertInputs(std::stringstream & OS)
@ -92,9 +110,10 @@ void GLFragmentDecompilerThread::insertOutputs(std::stringstream & OS)
{ "ocol3", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" },
};
const auto reg_type = (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) ? "vec4" : getHalfTypeName(4);
for (int i = 0; i < std::size(table); ++i)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", table[i].second))
if (m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
OS << "layout(location=" << i << ") out vec4 " << table[i].first << ";\n";
}
}
@ -206,14 +225,16 @@ void GLFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
};
std::string parameters = "";
const auto half4 = getHalfTypeName(4);
for (auto &reg_name : output_values)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", reg_name))
const auto type = (reg_name[0] == 'r' || !device_props.has_native_half_support)? "vec4" : half4;
if (m_parr.HasParam(PF_PARAM_NONE, type, reg_name))
{
if (parameters.length())
parameters += ", ";
parameters += "inout vec4 " + reg_name;
parameters += "inout " + type + " " + reg_name;
}
}
@ -307,21 +328,24 @@ void GLFragmentDecompilerThread::insertMainEnd(std::stringstream & OS)
OS << "{\n";
std::string parameters = "";
const auto half4 = getHalfTypeName(4);
for (auto &reg_name : output_values)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", reg_name))
const std::string type = (reg_name[0] == 'r' || !device_props.has_native_half_support)? "vec4" : half4;
if (m_parr.HasParam(PF_PARAM_NONE, type, reg_name))
{
if (parameters.length())
parameters += ", ";
parameters += reg_name;
OS << " vec4 " << reg_name << " = vec4(0.);\n";
OS << " " << type << " " << reg_name << " = " << type << "(0.);\n";
}
}
OS << "\n" << " fs_main(" + parameters + ");\n\n";
glsl::insert_rop(OS, !!(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS));
glsl::insert_rop(OS, !!(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS), device_props.has_native_half_support);
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
{
@ -359,7 +383,15 @@ void GLFragmentProgram::Decompile(const RSXFragmentProgram& prog)
{
u32 size;
GLFragmentDecompilerThread decompiler(shader, parr, prog, size);
if (!g_cfg.video.disable_native_float16)
{
const auto driver_caps = gl::get_driver_caps();
decompiler.device_props.has_native_half_support = driver_caps.NV_gpu_shader5_supported || driver_caps.AMD_gpu_shader_half_float_supported;
}
decompiler.Task();
for (const ParamType& PT : decompiler.m_parr.params[PF_PARAM_UNIFORM])
{
for (const ParamItem& PI : PT.items)

View File

@ -20,6 +20,7 @@ public:
protected:
virtual std::string getFloatTypeName(size_t elementCount) override;
virtual std::string getHalfTypeName(size_t elementCount) override;
virtual std::string getFunction(FUNCTION) override;
virtual std::string saturate(const std::string &code) override;
virtual std::string compareFunction(COMPARE, const std::string&, const std::string&) override;

View File

@ -102,15 +102,28 @@ namespace gl
bool ARB_depth_buffer_float_supported = false;
bool ARB_texture_barrier_supported = false;
bool NV_texture_barrier_supported = false;
bool NV_gpu_shader5_supported = false;
bool AMD_gpu_shader_half_float_supported = false;
bool initialized = false;
bool vendor_INTEL = false; //has broken GLSL compiler
bool vendor_AMD = false; //has broken ARB_multidraw
bool vendor_NVIDIA = false; //has NaN poisoning issues
bool vendor_MESA = false; //requires CLIENT_STORAGE bit set for streaming buffers
bool vendor_INTEL = false; // has broken GLSL compiler
bool vendor_AMD = false; // has broken ARB_multidraw
bool vendor_NVIDIA = false; // has NaN poisoning issues
bool vendor_MESA = false; // requires CLIENT_STORAGE bit set for streaming buffers
bool check(const std::string& ext_name, const char* test)
{
if (ext_name == test)
{
LOG_NOTICE(RSX, "Extension %s is supported", ext_name);
return true;
}
return false;
}
void initialize()
{
int find_count = 8;
int find_count = 10;
int ext_count = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &ext_count);
@ -120,64 +133,78 @@ namespace gl
const std::string ext_name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
if (ext_name == "GL_ARB_shader_draw_parameters")
if (check(ext_name, "GL_ARB_shader_draw_parameters"))
{
ARB_shader_draw_parameters_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_EXT_direct_state_access")
if (check(ext_name, "GL_EXT_direct_state_access"))
{
EXT_dsa_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_ARB_direct_state_access")
if (check(ext_name, "GL_ARB_direct_state_access"))
{
ARB_dsa_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_ARB_buffer_storage")
if (check(ext_name, "GL_ARB_buffer_storage"))
{
ARB_buffer_storage_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_ARB_texture_buffer_object")
if (check(ext_name, "GL_ARB_texture_buffer_object"))
{
ARB_texture_buffer_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_ARB_depth_buffer_float")
if (check(ext_name, "GL_ARB_depth_buffer_float"))
{
ARB_depth_buffer_float_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_ARB_texture_barrier")
if (check(ext_name, "GL_ARB_texture_barrier"))
{
ARB_texture_barrier_supported = true;
find_count--;
continue;
}
if (ext_name == "GL_NV_texture_barrier")
if (check(ext_name, "GL_NV_texture_barrier"))
{
NV_texture_barrier_supported = true;
find_count--;
continue;
}
if (check(ext_name, "GL_NV_gpu_shader5"))
{
NV_gpu_shader5_supported = true;
find_count--;
continue;
}
if (check(ext_name, "GL_AMD_gpu_shader_half_float"))
{
AMD_gpu_shader_half_float_supported = true;
find_count--;
continue;
}
}
//Workaround for intel drivers which have terrible capability reporting
// Workaround for intel drivers which have terrible capability reporting
std::string vendor_string = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
if (!vendor_string.empty())
{

View File

@ -149,7 +149,7 @@ namespace vk
shader_object.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 100);
shader_object.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetClientVersion::EShTargetVulkan_1_0);
shader_object.setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetLanguageVersion::EShTargetSpv_1_0);
bool success = false;
const char *shader_text = shader.data();

View File

@ -12,6 +12,11 @@ std::string VKFragmentDecompilerThread::getFloatTypeName(size_t elementCount)
return glsl::getFloatTypeNameImpl(elementCount);
}
std::string VKFragmentDecompilerThread::getHalfTypeName(size_t elementCount)
{
return glsl::getHalfTypeNameImpl(elementCount);
}
std::string VKFragmentDecompilerThread::getFunction(FUNCTION f)
{
return glsl::getFunctionImpl(f);
@ -29,7 +34,16 @@ std::string VKFragmentDecompilerThread::compareFunction(COMPARE f, const std::st
void VKFragmentDecompilerThread::insertHeader(std::stringstream & OS)
{
OS << "#version 420\n";
if (device_props.has_native_half_support)
{
OS << "#version 450\n";
OS << "#extension GL_KHX_shader_explicit_arithmetic_types_float16: enable\n";
}
else
{
OS << "#version 420\n";
}
OS << "#extension GL_ARB_separate_shader_objects: enable\n\n";
}
@ -93,9 +107,10 @@ void VKFragmentDecompilerThread::insertOutputs(std::stringstream & OS)
//NOTE: We do not skip outputs, the only possible combinations are a(0), b(0), ab(0,1), abc(0,1,2), abcd(0,1,2,3)
u8 output_index = 0;
const auto reg_type = (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) ? "vec4" : getHalfTypeName(4);
for (int i = 0; i < std::size(table); ++i)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", table[i].second))
if (m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
{
OS << "layout(location=" << std::to_string(output_index++) << ") " << "out vec4 " << table[i].first << ";\n";
vk_prog->output_color_masks[i] = UINT32_MAX;
@ -242,14 +257,16 @@ void VKFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
};
std::string parameters = "";
const auto half4 = getHalfTypeName(4);
for (auto &reg_name : output_values)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", reg_name))
const auto type = (reg_name[0] == 'r' || !device_props.has_native_half_support)? "vec4" : half4;
if (m_parr.HasParam(PF_PARAM_NONE, type, reg_name))
{
if (parameters.length())
parameters += ", ";
parameters += "inout vec4 " + reg_name;
parameters += "inout " + type + " " + reg_name;
}
}
@ -346,21 +363,24 @@ void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS)
OS << "{\n";
std::string parameters = "";
const auto half4 = getHalfTypeName(4);
for (auto &reg_name : output_values)
{
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", reg_name))
const std::string type = (reg_name[0] == 'r' || !device_props.has_native_half_support)? "vec4" : half4;
if (m_parr.HasParam(PF_PARAM_NONE, type, reg_name))
{
if (parameters.length())
parameters += ", ";
parameters += reg_name;
OS << " vec4 " << reg_name << " = vec4(0.);\n";
OS << " " << type << " " << reg_name << " = " << type << "(0.);\n";
}
}
OS << "\n" << " fs_main(" + parameters + ");\n\n";
glsl::insert_rop(OS, !!(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS));
glsl::insert_rop(OS, !!(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS), device_props.has_native_half_support);
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
{
@ -400,6 +420,12 @@ void VKFragmentProgram::Decompile(const RSXFragmentProgram& prog)
u32 size;
std::string source;
VKFragmentDecompilerThread decompiler(source, parr, prog, size, *this);
if (!g_cfg.video.disable_native_float16)
{
decompiler.device_props.has_native_half_support = vk::get_current_renderer()->get_shader_types_support().allow_float16;
}
decompiler.Task();
shader.create(::glsl::program_domain::glsl_fragment_program, source);

View File

@ -24,6 +24,7 @@ public:
const std::vector<vk::glsl::program_input>& get_inputs() { return inputs; }
protected:
virtual std::string getFloatTypeName(size_t elementCount) override;
virtual std::string getHalfTypeName(size_t elementCount) override;
virtual std::string getFunction(FUNCTION) override;
virtual std::string saturate(const std::string &code) override;
virtual std::string compareFunction(COMPARE, const std::string&, const std::string&) override;

View File

@ -201,6 +201,12 @@ namespace vk
bool bgra8_linear;
};
struct gpu_shader_types_support
{
bool allow_float16;
bool allow_int8;
};
// Memory Allocator - base class
class mem_allocator_base
@ -515,9 +521,32 @@ namespace vk
physical_device *pgpu = nullptr;
memory_type_mapping memory_map{};
gpu_formats_support m_formats_support{};
gpu_shader_types_support m_shader_types_support{};
std::unique_ptr<mem_allocator_base> m_allocator;
VkDevice dev = VK_NULL_HANDLE;
void get_physical_device_features(VkPhysicalDeviceFeatures& features)
{
if (!vkGetPhysicalDeviceFeatures2)
{
vkGetPhysicalDeviceFeatures(*pgpu, &features);
}
else
{
VkPhysicalDeviceFloat16Int8FeaturesKHR shader_support_info{};
shader_support_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
VkPhysicalDeviceFeatures2 features2;
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features2.pNext = &shader_support_info;
vkGetPhysicalDeviceFeatures2(*pgpu, &features2);
m_shader_types_support.allow_float16 = false;//!!shader_support_info.shaderFloat16;
m_shader_types_support.allow_int8 = !!shader_support_info.shaderInt8;
features = features2.features;
}
}
public:
render_device()
{}
@ -549,7 +578,7 @@ namespace vk
//2. DXT support
//3. Indexable storage buffers
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(*pgpu, &available_features);
get_physical_device_features(available_features);
available_features.samplerAnisotropy = VK_TRUE;
available_features.textureCompressionBC = VK_TRUE;
@ -566,6 +595,21 @@ namespace vk
device.ppEnabledExtensionNames = requested_extensions;
device.pEnabledFeatures = &available_features;
VkPhysicalDeviceFloat16Int8FeaturesKHR shader_support_info{};
if (m_shader_types_support.allow_float16)
{
// Allow use of f16 type in shaders if possible
shader_support_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
shader_support_info.shaderFloat16 = VK_TRUE;
device.pNext = &shader_support_info;
LOG_NOTICE(RSX, "GPU/driver supports float16 data types natively. Using native float16_t variables if possible.");
}
else
{
LOG_NOTICE(RSX, "GPU/driver lacks support for float16 data types. All float16_t arithmetic will be emulated with float32_t.");
}
CHECK_RESULT(vkCreateDevice(*pgpu, &device, nullptr, &dev));
memory_map = vk::get_memory_mapping(pdev);
@ -634,6 +678,11 @@ namespace vk
return m_formats_support;
}
const gpu_shader_types_support& get_shader_types_support() const
{
return m_shader_types_support;
}
mem_allocator_base* get_allocator() const
{
return m_allocator.get();

View File

@ -14,6 +14,21 @@
#include "define_new_memleakdetect.h"
#include "Utilities/types.h"
// TODO: Remove when packages catch up, ubuntu is stuck at 1.1.73 (bionic) and 1.1.82 (cosmic)
// Do we still use libvulkan-dev package on travis??????
#if VK_HEADER_VERSION < 95
typedef struct VkPhysicalDeviceFloat16Int8FeaturesKHR {
VkStructureType sType;
void* pNext;
VkBool32 shaderFloat16;
VkBool32 shaderInt8;
} VkPhysicalDeviceFloat16Int8FeaturesKHR;
#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR VkStructureType(1000082000)
#endif
namespace vk
{
void init();

View File

@ -448,6 +448,7 @@ struct cfg_root : cfg::node
cfg::_bool full_rgb_range_output{this, "Use full RGB output range", true}; // Video out dynamic range
cfg::_bool disable_asynchronous_shader_compiler{this, "Disable Asynchronous Shader Compiler", false};
cfg::_bool strict_texture_flushing{this, "Strict Texture Flushing", false};
cfg::_bool disable_native_float16{this, "Disable native float16 support", false};
cfg::_int<1, 8> consequtive_frames_to_draw{this, "Consecutive Frames To Draw", 1};
cfg::_int<1, 8> consequtive_frames_to_skip{this, "Consecutive Frames To Skip", 1};
cfg::_int<50, 800> resolution_scale_percent{this, "Resolution Scale", 100};