rsx/fp: Color output registers are always present and zero initialized

- According to NV_fragment_program spec, registers are zero initialized always
- A program even without writing to these registers will have black (0, 0, 0, 0) output
  Confirmed behaviour with MotorStorm games. Their engine uses this quirk to clear color buffers when doing depth replace
  Might be an unfixed game bug
This commit is contained in:
kd-11 2018-02-25 16:07:55 +03:00
parent 4804efc17d
commit d41b49d8b4

View File

@ -496,79 +496,6 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
std::string FragmentProgramDecompiler::BuildCode()
{
//Scan if any outputs are available
const bool use_32_bit_exports = !!(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS);
const bool exports_depth = !!(m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
const std::set<std::string> output_values =
{
(use_32_bit_exports) ? "r0" : "h0",
"r1",
(use_32_bit_exports) ? "r2" : "h4",
(use_32_bit_exports) ? "r3" : "h6",
(use_32_bit_exports) ? "r4" : "h8",
};
bool gather_output_registers = true;
const auto float4_name = getFloatTypeName(4);
for (auto &v : output_values)
{
if (m_parr.HasParam(PF_PARAM_NONE, float4_name, v))
{
gather_output_registers = false;
break;
}
}
//Explicitly discard on encountering null shaders
if (gather_output_registers)
{
bool has_any_output = false;
bool first_output_exists = false;
if (use_32_bit_exports || exports_depth)
{
for (int reg = 0; reg < 5; ++reg)
{
if (reg == 1 && !exports_depth)
continue;
const std::string half_register = "h" + std::to_string(reg + 1);
if (m_parr.HasParam(PF_PARAM_NONE, float4_name, half_register))
{
has_any_output = true;
if (!reg) first_output_exists = true;
const std::string this_register = "r" + std::to_string(reg);
AddReg(reg, 0);
AddCode("//Register gather because output was not specified");
AddCode(this_register + ".zw = gather(" + half_register + ");");
}
}
}
if (!has_any_output)
{
properties.has_no_output = true;
LOG_ERROR(RSX, "Invalid fragment shader: No output register was updated!");
//Comment out main block as it is now useless
main = "/*\n" + main + "*/\n";
AddCode("//No output, manually abort writes (nvidia+vulkan writes garbage otherwise)");
AddCode("discard;");
}
else
{
//Requires gather operation for output...
properties.has_gather_op = true;
if (!first_output_exists)
{
LOG_WARNING(RSX, "Fragment shader does not write to first RTT and has no explicit output registers");
}
}
}
std::stringstream OS;
insertHeader(OS);
OS << "\n";
@ -830,10 +757,27 @@ std::string FragmentProgramDecompiler::Decompile()
int forced_unit = FORCE_NONE;
//Add the 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)
{
AddReg(0, CELL_GCM_FALSE);
AddReg(2, CELL_GCM_FALSE);
AddReg(3, CELL_GCM_FALSE);
AddReg(4, CELL_GCM_FALSE);
}
else
{
AddReg(0, CELL_GCM_TRUE);
AddReg(4, CELL_GCM_TRUE);
AddReg(6, CELL_GCM_TRUE);
AddReg(8, CELL_GCM_TRUE);
}
while (true)
{
for (auto found = std::find(m_end_offsets.begin(), m_end_offsets.end(), m_size);
found != m_end_offsets.end();
found != m_end_offsets.end();
found = std::find(m_end_offsets.begin(), m_end_offsets.end(), m_size))
{
m_end_offsets.erase(found);
@ -843,7 +787,7 @@ std::string FragmentProgramDecompiler::Decompile()
}
for (auto found = std::find(m_else_offsets.begin(), m_else_offsets.end(), m_size);
found != m_else_offsets.end();
found != m_else_offsets.end();
found = std::find(m_else_offsets.begin(), m_else_offsets.end(), m_size))
{
m_else_offsets.erase(found);