shader_decompiler: Remove FragCoord.w hack and change IPA implementation

Credits go to gdkchan and Ryujinx. The pull request used for this can
be found here: https://github.com/Ryujinx/Ryujinx/pull/1082

yuzu was already using the header for interpolation, but it was missing
the FragCoord.w multiplication described in the linked pull request.
This commit finally removes the FragCoord.w == 1.0f hack from the shader
decompiler.

While we are at it, this commit renames some enumerations to match
Nvidia's documentation (linked below) and fixes component declaration
order in the shader program header (z and w were swapped).

https://github.com/NVIDIA/open-gpu-doc/blob/master/Shader-Program-Header/Shader-Program-Header.html
merge-requests/60/head
ReinUsesLisp 2020-04-01 21:37:32 +07:00
parent baf91c920c
commit 2339fe199f
4 changed files with 74 additions and 68 deletions

@ -4,6 +4,9 @@
#pragma once #pragma once
#include <array>
#include <optional>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
@ -16,7 +19,7 @@ enum class OutputTopology : u32 {
TriangleStrip = 7, TriangleStrip = 7,
}; };
enum class AttributeUse : u8 { enum class PixelImap : u8 {
Unused = 0, Unused = 0,
Constant = 1, Constant = 1,
Perspective = 2, Perspective = 2,
@ -24,7 +27,7 @@ enum class AttributeUse : u8 {
}; };
// Documentation in: // Documentation in:
// http://download.nvidia.com/open-gpu-doc/Shader-Program-Header/1/Shader-Program-Header.html#ImapTexture // http://download.nvidia.com/open-gpu-doc/Shader-Program-Header/1/Shader-Program-Header.html
struct Header { struct Header {
union { union {
BitField<0, 5, u32> sph_type; BitField<0, 5, u32> sph_type;
@ -59,8 +62,8 @@ struct Header {
union { union {
BitField<0, 12, u32> max_output_vertices; BitField<0, 12, u32> max_output_vertices;
BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders. BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders.
BitField<24, 4, u32> reserved; BitField<20, 4, u32> reserved;
BitField<12, 8, u32> store_req_end; // NOTE: not used by geometry shaders. BitField<24, 8, u32> store_req_end; // NOTE: not used by geometry shaders.
} common4{}; } common4{};
union { union {
@ -93,17 +96,20 @@ struct Header {
struct { struct {
INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA
INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB
union { union {
BitField<0, 2, AttributeUse> x; BitField<0, 2, PixelImap> x;
BitField<2, 2, AttributeUse> y; BitField<2, 2, PixelImap> y;
BitField<4, 2, AttributeUse> w; BitField<4, 2, PixelImap> z;
BitField<6, 2, AttributeUse> z; BitField<6, 2, PixelImap> w;
u8 raw; u8 raw;
} imap_generic_vector[32]; } imap_generic_vector[32];
INSERT_UNION_PADDING_BYTES(2); // ImapColor INSERT_UNION_PADDING_BYTES(2); // ImapColor
INSERT_UNION_PADDING_BYTES(2); // ImapSystemValuesC INSERT_UNION_PADDING_BYTES(2); // ImapSystemValuesC
INSERT_UNION_PADDING_BYTES(10); // ImapFixedFncTexture[10] INSERT_UNION_PADDING_BYTES(10); // ImapFixedFncTexture[10]
INSERT_UNION_PADDING_BYTES(2); // ImapReserved INSERT_UNION_PADDING_BYTES(2); // ImapReserved
struct { struct {
u32 target; u32 target;
union { union {
@ -112,31 +118,30 @@ struct Header {
BitField<2, 30, u32> reserved; BitField<2, 30, u32> reserved;
}; };
} omap; } omap;
bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const { bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const {
const u32 bit = render_target * 4 + component; const u32 bit = render_target * 4 + component;
return omap.target & (1 << bit); return omap.target & (1 << bit);
} }
AttributeUse GetAttributeIndexUse(u32 attribute, u32 index) const {
return static_cast<AttributeUse>( PixelImap GetPixelImap(u32 attribute) const {
(imap_generic_vector[attribute].raw >> (index * 2)) & 0x03); const auto get_index = [this, attribute](u32 index) {
} return static_cast<PixelImap>(
AttributeUse GetAttributeUse(u32 attribute) const { (imap_generic_vector[attribute].raw >> (index * 2)) & 3);
AttributeUse result = AttributeUse::Unused; };
for (u32 i = 0; i < 4; i++) {
const auto index = GetAttributeIndexUse(attribute, i); std::optional<PixelImap> result;
if (index == AttributeUse::Unused) { for (u32 component = 0; component < 4; ++component) {
const PixelImap index = get_index(component);
if (index == PixelImap::Unused) {
continue; continue;
} }
if (result == AttributeUse::Unused || result == index) { if (result && result != index) {
result = index; LOG_CRITICAL(HW_GPU, "Generic attribute conflict in interpolation mode");
continue;
} }
LOG_CRITICAL(HW_GPU, "Generic Attribute Conflict in Interpolation Mode");
if (index == AttributeUse::Perspective) {
result = index; result = index;
} }
} return result.value_or(PixelImap::Unused);
return result;
} }
} ps; } ps;

@ -31,11 +31,11 @@ namespace {
using Tegra::Engines::ShaderType; using Tegra::Engines::ShaderType;
using Tegra::Shader::Attribute; using Tegra::Shader::Attribute;
using Tegra::Shader::AttributeUse;
using Tegra::Shader::Header; using Tegra::Shader::Header;
using Tegra::Shader::IpaInterpMode; using Tegra::Shader::IpaInterpMode;
using Tegra::Shader::IpaMode; using Tegra::Shader::IpaMode;
using Tegra::Shader::IpaSampleMode; using Tegra::Shader::IpaSampleMode;
using Tegra::Shader::PixelImap;
using Tegra::Shader::Register; using Tegra::Shader::Register;
using VideoCommon::Shader::BuildTransformFeedback; using VideoCommon::Shader::BuildTransformFeedback;
using VideoCommon::Shader::Registry; using VideoCommon::Shader::Registry;
@ -702,20 +702,19 @@ private:
code.AddNewLine(); code.AddNewLine();
} }
std::string GetInputFlags(AttributeUse attribute) { const char* GetInputFlags(PixelImap attribute) {
switch (attribute) { switch (attribute) {
case AttributeUse::Perspective: case PixelImap::Perspective:
// Default, Smooth return "smooth";
return {}; case PixelImap::Constant:
case AttributeUse::Constant: return "flat";
return "flat "; case PixelImap::ScreenLinear:
case AttributeUse::ScreenLinear: return "noperspective";
return "noperspective "; case PixelImap::Unused:
default: break;
case AttributeUse::Unused:
UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute));
return {};
} }
UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute));
return {};
} }
void DeclareInputAttributes() { void DeclareInputAttributes() {
@ -749,8 +748,8 @@ private:
std::string suffix; std::string suffix;
if (stage == ShaderType::Fragment) { if (stage == ShaderType::Fragment) {
const auto input_mode{header.ps.GetAttributeUse(location)}; const auto input_mode{header.ps.GetPixelImap(location)};
if (skip_unused && input_mode == AttributeUse::Unused) { if (input_mode == PixelImap::Unused) {
return; return;
} }
suffix = GetInputFlags(input_mode); suffix = GetInputFlags(input_mode);
@ -927,7 +926,7 @@ private:
const u32 address{generic_base + index * generic_stride + element * element_stride}; const u32 address{generic_base + index * generic_stride + element * element_stride};
const bool declared = stage != ShaderType::Fragment || const bool declared = stage != ShaderType::Fragment ||
header.ps.GetAttributeUse(index) != AttributeUse::Unused; header.ps.GetPixelImap(index) != PixelImap::Unused;
const std::string value = const std::string value =
declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f"; declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f";
code.AddLine("case 0x{:X}U: return {};", address, value); code.AddLine("case 0x{:X}U: return {};", address, value);
@ -1142,8 +1141,7 @@ private:
GetSwizzle(element)), GetSwizzle(element)),
Type::Float}; Type::Float};
case ShaderType::Fragment: case ShaderType::Fragment:
return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)), return {"gl_FragCoord"s + GetSwizzle(element), Type::Float};
Type::Float};
default: default:
UNREACHABLE(); UNREACHABLE();
} }

@ -35,7 +35,7 @@ namespace {
using Sirit::Id; using Sirit::Id;
using Tegra::Engines::ShaderType; using Tegra::Engines::ShaderType;
using Tegra::Shader::Attribute; using Tegra::Shader::Attribute;
using Tegra::Shader::AttributeUse; using Tegra::Shader::PixelImap;
using Tegra::Shader::Register; using Tegra::Shader::Register;
using namespace VideoCommon::Shader; using namespace VideoCommon::Shader;
@ -752,16 +752,16 @@ private:
if (stage != ShaderType::Fragment) { if (stage != ShaderType::Fragment) {
continue; continue;
} }
switch (header.ps.GetAttributeUse(location)) { switch (header.ps.GetPixelImap(location)) {
case AttributeUse::Constant: case PixelImap::Constant:
Decorate(id, spv::Decoration::Flat); Decorate(id, spv::Decoration::Flat);
break; break;
case AttributeUse::ScreenLinear: case PixelImap::Perspective:
Decorate(id, spv::Decoration::NoPerspective);
break;
case AttributeUse::Perspective:
// Default // Default
break; break;
case PixelImap::ScreenLinear:
Decorate(id, spv::Decoration::NoPerspective);
break;
default: default:
UNREACHABLE_MSG("Unused attribute being fetched"); UNREACHABLE_MSG("Unused attribute being fetched");
} }
@ -1145,9 +1145,6 @@ private:
switch (attribute) { switch (attribute) {
case Attribute::Index::Position: { case Attribute::Index::Position: {
if (stage == ShaderType::Fragment) { if (stage == ShaderType::Fragment) {
if (element == 3) {
return {Constant(t_float, 1.0f), Type::Float};
}
return {OpLoad(t_float, AccessElement(t_in_float, frag_coord, element)), return {OpLoad(t_float, AccessElement(t_in_float, frag_coord, element)),
Type::Float}; Type::Float};
} }

@ -11,12 +11,17 @@
namespace VideoCommon::Shader { namespace VideoCommon::Shader {
using std::move;
using Tegra::Shader::ConditionCode; using Tegra::Shader::ConditionCode;
using Tegra::Shader::Instruction; using Tegra::Shader::Instruction;
using Tegra::Shader::IpaInterpMode;
using Tegra::Shader::OpCode; using Tegra::Shader::OpCode;
using Tegra::Shader::PixelImap;
using Tegra::Shader::Register; using Tegra::Shader::Register;
using Tegra::Shader::SystemVariable; using Tegra::Shader::SystemVariable;
using Index = Tegra::Shader::Attribute::Index;
u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]}; const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr); const auto opcode = OpCode::Decode(instr);
@ -213,27 +218,28 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
} }
case OpCode::Id::IPA: { case OpCode::Id::IPA: {
const bool is_physical = instr.ipa.idx && instr.gpr8.Value() != 0xff; const bool is_physical = instr.ipa.idx && instr.gpr8.Value() != 0xff;
const auto attribute = instr.attribute.fmt28; const auto attribute = instr.attribute.fmt28;
const Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(), const Index index = attribute.index;
instr.ipa.sample_mode.Value()};
Node value = is_physical ? GetPhysicalInputAttribute(instr.gpr8) Node value = is_physical ? GetPhysicalInputAttribute(instr.gpr8)
: GetInputAttribute(attribute.index, attribute.element); : GetInputAttribute(index, attribute.element);
const Tegra::Shader::Attribute::Index index = attribute.index.Value();
const bool is_generic = index >= Tegra::Shader::Attribute::Index::Attribute_0 &&
index <= Tegra::Shader::Attribute::Index::Attribute_31;
if (is_generic || is_physical) {
// TODO(Blinkhawk): There are cases where a perspective attribute use PASS.
// In theory by setting them as perspective, OpenGL does the perspective correction.
// A way must figured to reverse the last step of it.
if (input_mode.interpolation_mode == Tegra::Shader::IpaInterpMode::Multiply) {
value = Operation(OperationCode::FMul, PRECISE, value, GetRegister(instr.gpr20));
}
}
value = GetSaturatedFloat(value, instr.ipa.saturate);
SetRegister(bb, instr.gpr0, value); // Code taken from Ryujinx.
if (index >= Index::Attribute_0 && index <= Index::Attribute_31) {
const u32 location = static_cast<u32>(index) - static_cast<u32>(Index::Attribute_0);
if (header.ps.GetPixelImap(location) == PixelImap::Perspective) {
Node position_w = GetInputAttribute(Index::Position, 3);
value = Operation(OperationCode::FMul, move(value), move(position_w));
}
}
if (instr.ipa.interp_mode == IpaInterpMode::Multiply) {
value = Operation(OperationCode::FMul, move(value), GetRegister(instr.gpr20));
}
value = GetSaturatedFloat(move(value), instr.ipa.saturate);
SetRegister(bb, instr.gpr0, move(value));
break; break;
} }
case OpCode::Id::OUT_R: { case OpCode::Id::OUT_R: {