diff --git a/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.cpp index de45a34168..85a4860035 100644 --- a/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.cpp @@ -482,6 +482,59 @@ template std::string FragmentProgramDecompiler::GetSRC(T src) 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 std::string init_value = vec4_type + "(0., 0., 0., 0.)"; + std::array output_register_names; + std::array ouput_register_indices = { 0, 2, 3, 4 }; + bool shader_is_valid = false; + + // Check depth export + if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) + { + if (shader_is_valid = !!temp_registers[1].h0_writes; !shader_is_valid) + { + LOG_WARNING(RSX, "Fragment shader fails to write the depth value!"); + } + } + + // 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) + { + output_register_names = { "r0", "r2", "r3", "r4" }; + } + else + { + output_register_names = { "h0", "h4", "h6", "h8" }; + } + + for (int n = 0; n < 4; ++n) + { + if (!m_parr.HasParam(PF_PARAM_NONE, vec4_type, output_register_names[n])) + { + m_parr.AddParam(PF_PARAM_NONE, vec4_type, output_register_names[n], init_value); + 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 + LOG_WARNING(RSX, "Shader does not write to any output register and will be NOPed"); + main = "/*" + main + "*/"; + } + } + std::stringstream OS; insertHeader(OS); OS << "\n"; @@ -765,23 +818,6 @@ 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); @@ -888,7 +924,10 @@ std::string FragmentProgramDecompiler::Decompile() switch (opcode) { case RSX_FP_OPCODE_NOP: break; - case RSX_FP_OPCODE_KIL: AddFlowOp("discard"); break; + case RSX_FP_OPCODE_KIL: + properties.has_discard_op = true; + AddFlowOp("discard"); + break; default: int prev_force_unit = forced_unit; diff --git a/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.h b/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.h index 312365f71c..fd95663851 100644 --- a/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.h +++ b/rpcs3/Emu/RSX/Common/FragmentProgramDecompiler.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "ShaderParam.h" #include "Emu/RSX/RSXFragmentProgram.h" #include @@ -28,6 +28,9 @@ class FragmentProgramDecompiler 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) @@ -40,6 +43,7 @@ class FragmentProgramDecompiler if (w) last_write_half[3] = true; aliased_h1 = true; + h1_writes++; } else { @@ -49,6 +53,7 @@ class FragmentProgramDecompiler if (w) last_write_half[1] = true; aliased_h0 = true; + h0_writes++; } } else @@ -59,6 +64,9 @@ class FragmentProgramDecompiler if (w) last_write_half[3] = false; aliased_r0 = true; + + h0_writes++; + h1_writes++; } if (real_index == UINT32_MAX) @@ -247,6 +255,7 @@ public: bool has_gather_op = false; bool has_wpos_input = false; bool has_no_output = false; + bool has_discard_op = false; } properties; diff --git a/rpcs3/Emu/RSX/Common/ShaderParam.h b/rpcs3/Emu/RSX/Common/ShaderParam.h index 532de11952..da7dd93a00 100644 --- a/rpcs3/Emu/RSX/Common/ShaderParam.h +++ b/rpcs3/Emu/RSX/Common/ShaderParam.h @@ -67,8 +67,8 @@ enum ParamFlag struct ParamItem { - std::string name; - std::string value; + const std::string name; + const std::string value; int location; ParamItem(const std::string& _name, int _location, const std::string& _value = "") @@ -81,7 +81,7 @@ struct ParamItem struct ParamType { const ParamFlag flag; - std::string type; + const std::string type; std::vector items; ParamType(const ParamFlag _flag, const std::string& _type) @@ -127,13 +127,13 @@ struct ParamArray return false; } - bool HasParam(const ParamFlag flag, std::string type, const std::string& name) + bool HasParam(const ParamFlag flag, const std::string& type, const std::string& name) { ParamType* t = SearchParam(flag, type); return t && t->SearchName(name); } - std::string AddParam(const ParamFlag flag, std::string type, const std::string& name, const std::string& value) + std::string AddParam(const ParamFlag flag, const std::string& type, const std::string& name, const std::string& value) { ParamType* t = SearchParam(flag, type); @@ -150,7 +150,7 @@ struct ParamArray return name; } - std::string AddParam(const ParamFlag flag, std::string type, const std::string& name, int location = -1) + std::string AddParam(const ParamFlag flag, const std::string& type, const std::string& name, int location = -1) { ParamType* t = SearchParam(flag, type);