[GPU] Fix scalar c[#+aL], shader docs/refactoring

This commit is contained in:
Triang3l
2022-04-13 23:08:19 +03:00
parent 1f324bebcd
commit fea430f1f9
6 changed files with 395 additions and 201 deletions

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -16,11 +16,45 @@
#include "xenia/base/platform.h"
#include "xenia/gpu/xenos.h"
// Closest AMD doc:
// The XNA Game Studio 3.1 contains Graphics.ShaderCompiler.AssembleFromSource,
// which, for TargetPlatform.Xbox360, can validate and assemble Xbox 360 shader
// microcode from Xbox 360 and Direct3D 9 shader assembly, returning the binary,
// as well as validation warnings and errors and the disassembly via the warning
// output. It is the primary source of information about the binary encoding of
// the instructions, as well as valid usage of instruction parameters and
// sequences.
// https://www.microsoft.com/en-us/download/details.aspx?id=39
// (XNAGS31_setup.exe)
// Xenia provides a tool, tools/shader-playground, that invokes the assembler,
// displays the binary and the disassembly from the official assembler, and also
// shows the disassembly generated by Xenia, and passes it back to the assembler
// to validate Xenia's microcode parsing and disassembly by checking if
// reassembling the disassembly results in the same binary.
//
// The behavior and the parameters of some of the instructions were previously
// documented on MSDN in the XNA Game Studio programming guide:
// http://web.archive.org/web/20081211005537/http://msdn.microsoft.com/en-us/library/bb313877.aspx
//
// A great amount of documentation, such as the R400 sequencer specification and
// the official emulator code, was made available during the LG Electronics,
// Inc. v. ATI Technologies ULC "Multi-thread Graphics Processing System" patent
// dispute IPR2015-00325, with the motion to seal having been denied due to "a
// strong public policy interest in making all information filed in an inter
// partes review publicly available". Most of the documents attached, however,
// cover early versions - the development process - of the R400 architecture, so
// there are some differences from the final Xenos GPU (DOT2ADDv is defined
// differently, for example, and MUL/ADD/SUB_CONST are missing).
// https://portal.unifiedpatents.com/ptab/case/IPR2015-00325
//
// Also, the R600, while having a different 5-scalar, as opposed to vec4|scalar,
// parallelism model and instruction encodings and targeting Direct3D 10 rather
// that 9, inherits a lot of instructions and architectural concepts from the
// R400.
// https://www.x.org/docs/AMD/old/r600isa.pdf
// https://developer.amd.com/wordpress/media/2012/10/r600isa.pdf
// https://developer.amd.com/wordpress/media/2012/10/R600_Instruction_Set_Architecture.pdf
// Microcode format differs, but most fields/enums are the same.
// This code comes from the freedreno project:
// Parts of this code also come from the freedreno project:
// https://github.com/freedreno/freedreno/blob/master/includes/instr-a2xx.h
/*
* Copyright (c) 2012 Rob Clark <robdclark@gmail.com>
@@ -156,7 +190,8 @@ struct ControlFlowExecInstruction {
uint32_t address() const { return address_; }
// Number of instructions being executed.
uint32_t count() const { return count_; }
// Sequence bits, 2 per instruction, indicating whether ALU or fetch.
// Sequence bits, 2 per instruction.
// [0] - ALU (0) or fetch (1), [1] - serialize.
uint32_t sequence() const { return serialize_; }
// Whether to reset the current predicate.
bool clean() const { return clean_ == 1; }
@@ -189,7 +224,8 @@ struct ControlFlowCondExecInstruction {
uint32_t address() const { return address_; }
// Number of instructions being executed.
uint32_t count() const { return count_; }
// Sequence bits, 2 per instruction, indicating whether ALU or fetch.
// Sequence bits, 2 per instruction.
// [0] - ALU (0) or fetch (1), [1] - serialize.
uint32_t sequence() const { return serialize_; }
// Constant index used as the conditional.
uint32_t bool_address() const { return bool_address_; }
@@ -224,7 +260,8 @@ struct ControlFlowCondExecPredInstruction {
uint32_t address() const { return address_; }
// Number of instructions being executed.
uint32_t count() const { return count_; }
// Sequence bits, 2 per instruction, indicating whether ALU or fetch.
// Sequence bits, 2 per instruction.
// [0] - ALU (0) or fetch (1), [1] - serialize.
uint32_t sequence() const { return serialize_; }
// Whether to reset the current predicate.
bool clean() const { return clean_ == 1; }
@@ -591,6 +628,24 @@ enum class FetchOpcode : uint32_t {
kSetTextureGradientsVert = 26,
};
enum class FetchDestinationSwizzle {
// The component indices are absolute (not relative to the component itself,
// unlike in ALU operation sources).
kX = 0,
kY = 1,
kZ = 2,
kW = 3,
k0 = 4,
k1 = 5,
// Keep the current value of the destination register (don't write).
kKeep = 7,
};
constexpr FetchDestinationSwizzle GetFetchDestinationComponentSwizzle(
uint32_t swizzle, uint32_t component) {
return FetchDestinationSwizzle((swizzle >> (3 * component)) & 0b111);
}
struct alignas(uint32_t) VertexFetchInstruction {
FetchOpcode opcode() const { return data_.opcode_value; }
@@ -614,29 +669,6 @@ struct alignas(uint32_t) VertexFetchInstruction {
uint32_t src_swizzle() const { return data_.src_swiz; }
bool is_src_relative() const { return data_.src_reg_am; }
// Returns true if the fetch actually fetches data.
// This may be false if it's used only to populate constants.
bool fetches_any_data() const {
uint32_t dst_swiz = data_.dst_swiz;
bool fetches_any_data = false;
for (int i = 0; i < 4; i++) {
if ((dst_swiz & 0x7) == 4) {
// 0.0
} else if ((dst_swiz & 0x7) == 5) {
// 1.0
} else if ((dst_swiz & 0x7) == 6) {
// ?
} else if ((dst_swiz & 0x7) == 7) {
// Previous register value.
} else {
fetches_any_data = true;
break;
}
dst_swiz >>= 3;
}
return fetches_any_data;
}
uint32_t prefetch_count() const { return data_.prefetch_count; }
bool is_mini_fetch() const { return data_.is_mini_fetch == 1; }
@@ -676,6 +708,7 @@ struct alignas(uint32_t) VertexFetchInstruction {
uint32_t const_index_sel : 2;
// Prefetch count minus 1.
uint32_t prefetch_count : 3;
// Absolute, one component.
uint32_t src_swiz : 2;
};
struct {
@@ -769,10 +802,11 @@ struct alignas(uint32_t) TextureFetchInstruction {
uint32_t fetch_valid_only : 1;
uint32_t const_index : 5;
uint32_t tx_coord_denorm : 1;
uint32_t src_swiz : 6; // xyz
// Absolute, three components.
uint32_t src_swiz : 6;
};
struct {
uint32_t dst_swiz : 12; // xyzw
uint32_t dst_swiz : 12;
xenos::TextureFilter mag_filter : 2;
xenos::TextureFilter min_filter : 2;
xenos::TextureFilter mip_filter : 2;
@@ -801,21 +835,96 @@ struct alignas(uint32_t) TextureFetchInstruction {
};
static_assert_size(TextureFetchInstruction, sizeof(uint32_t) * 3);
union alignas(uint32_t) FetchInstruction {
public:
FetchOpcode opcode() const { return data_.opcode_value; }
// Whether the jump is predicated (or conditional).
bool is_predicated() const { return data_.is_predicated; }
// Required condition value of the comparision (true or false).
bool predicate_condition() const { return data_.pred_condition == 1; }
uint32_t dest() const { return data_.dst_reg; }
uint32_t dest_swizzle() const { return data_.dst_swiz; }
bool is_dest_relative() const { return data_.dst_reg_am; }
uint32_t src() const { return data_.src_reg; }
bool is_src_relative() const { return data_.src_reg_am; }
// For FetchOpcode::kVertexFetch.
const VertexFetchInstruction& vertex_fetch() const { return vertex_fetch_; }
// For operations other than FetchOpcode::kVertexFetch.
const TextureFetchInstruction& texture_fetch() const {
return texture_fetch_;
}
private:
struct Data {
struct {
FetchOpcode opcode_value : 5;
uint32_t src_reg : 6;
uint32_t src_reg_am : 1;
uint32_t dst_reg : 6;
uint32_t dst_reg_am : 1;
// Specific to vertex or texture fetch.
uint32_t : 1;
// [0-31], points to one tf# or three vf# constants.
uint32_t const_index : 5;
// Specific to vertex or texture fetch.
uint32_t : 7;
};
struct {
uint32_t dst_swiz : 12;
// Specific to vertex or texture fetch.
uint32_t : 19;
uint32_t is_predicated : 1;
};
struct {
// Specific to vertex or texture fetch.
uint32_t : 31;
uint32_t pred_condition : 1;
};
};
Data data_;
VertexFetchInstruction vertex_fetch_;
TextureFetchInstruction texture_fetch_;
};
static_assert_size(FetchInstruction, sizeof(uint32_t) * 3);
// What follows is largely a mash up of the microcode assembly naming and the
// R600 docs that have a near 1:1 with the instructions available in the xenos
// R600 docs that have a near 1:1 with the instructions available in the Xenos
// GPU, and Adreno 2xx instruction names found in Freedreno. Some of the
// behavior has been experimentally verified. Some has been guessed.
// Docs: https://www.x.org/docs/AMD/old/r600isa.pdf
// behavior has been experimentally verified. Some has been guessed. Some
// instructions are implemented in the Exhibit 2092 - sq_alu of IPR2015-00325,
// however, the code provided there is early and incomplete.
//
// Conventions:
// - All temporary registers are vec4s.
// - Scalar ops swizzle out a single component of their source registers denoted
// by 'a' or 'b'. src0.a means 'the first component specified for src0' and
// src0.ab means 'two components specified for src0, in order'.
// - Scalar ops write the result to the entire destination register.
// - pv and ps are the previous results of a vector or scalar ALU operation.
// Both are valid only within the current ALU clause. They are not modified
// when the instruction that would write them fails its predication check.
// - Most scalar ALU operations work with one or two components of the source
// register passed as the third operand of the whole co-issued ALU operation,
// denoted by `a` (the left-hand operand) and `b` (the right-hand operand).
// `a` is the [(3 + src3_swizzle[6:7]) & 3] component (W - alpha).
// `b` is the [(0 + src3_swizzle[0:1]) & 3] component (X - red).
// - mulsc, addsc, subsc scalar ALU operations accept two operands - a float
// constant with the `a` (W) swizzle (addressed by the third operand index and
// addressing mode) being the left-hand operand, and a temporary register with
// the `b` (X) swizzle with the index constructed from:
// - [0:0] = scalar_opcode[0:0]
// - [1:1] = src3_sel[0:0]
// - [2:5] = src3_swizzle[2:5]
// abs_constants and third source's negation are applied to both the constant
// and the temporary register.
// - Some scalar ALU instructions don't have operands.
// - Scalar ALU operations replicate the result into all masked components.
// - Overall, the WXYZ order is pretty commonly used in the Exhibit 2092 -
// sq_alu of IPR2015-00325, this is where the AB = WX order of scalar operands
// likely comes from. Vector predicate instructions also involve the W and X
// components, and in IPR2015-00325 sq_alu, individual components in the
// emulated vector instructions are handled in the WXYZ order. However, max4's
// "greater than the rest" check order is RGBA (XYZW) there. dp4, though, sums
// the products in WXYZ order in IPR2015-00325 sq_alu (but in XYZW order on
// MSDN).
// - ps is the previous result of a scalar ALU operation. It is not modified
// when the instruction that would write it fails its predication check.
// - Direct3D 9 rules (like in GCN v_*_legacy_f32 instructions) for
// multiplication (+-0 or denormal * anything = +0) wherever it's present
// (mul, mad, dp, etc.) and for NaN in min/max. It's very important to respect
@@ -1137,6 +1246,9 @@ enum class AluScalarOpcode : uint32_t {
// dest.xyzw = sqrt(src0.a);
kSqrt = 40,
// 0 and 1 are the same instruction - one bit of the register index is stored
// in the opcode field.
// mulsc/MUL_CONST_0 dest, src0.a, src1.a
kMulsc0 = 42,
// mulsc/MUL_CONST_1 dest, src0.a, src1.a
@@ -1303,19 +1415,24 @@ enum class AluVectorOpcode : uint32_t {
// dp4/DOT4v dest, src0, src1
// dest.xyzw = src0.x * src1.x + src0.y * src1.y + src0.z * src1.z +
// src0.w * src1.w;
// Note: only pv.x contains the value.
kDp4 = 15,
// Three-Element Dot Product
// dp3/DOT3v dest, src0, src1
// dest.xyzw = src0.x * src1.x + src0.y * src1.y + src0.z * src1.z;
// Note: only pv.x contains the value.
kDp3 = 16,
// Two-Element Dot Product and Add
// dp2add/DOT2ADDv dest, src0, src1, src2
// dest.xyzw = src0.x * src1.x + src0.y * src1.y + src2.x;
// Note: only pv.x contains the value.
// IPR2015-00325 sq_alu may be an outdated and unreliable reference (Sequencer
// Parts Development folder history lists a few changes regarding the swizzle
// in dot2add, sq_alu though implements the instruction as
// src0.x * src1.x + src0.z * src1.z + src2.y, but MSDN specifies the correct
// order as provided in the beginning of this comment, further proven by
// assembling PC shader assembly using XNA, with Shader Model 2 dp2add being
// translated directly into Xenos dp2add without additional swizzling).
// http://web.archive.org/web/20100705150552/http://msdn.microsoft.com/en-us/library/bb313922.aspx
kDp2Add = 17,
// Cube Map
@@ -1363,8 +1480,16 @@ enum class AluVectorOpcode : uint32_t {
// Four-Element Maximum
// max4/MAX4v dest, src0
// dest.xyzw = max(src0.x, src0.y, src0.z, src0.w);
// Note: only pv.x contains the value.
// According to IPR2015-00325 sq_alu:
// if (src0.x > src0.y && src0.x > src0.z && src0.x > src0.w) {
// dest.xyzw = src0.x;
// } else if (src0.y > src0.z && src0.y > src0.w) {
// dest.xyzw = src0.y;
// } else if (src0.z > src0.w) {
// dest.xyzw = src0.z;
// } else {
// dest.xyzw = src0.w;
// }
kMax4 = 19,
// Floating-Point Predicate Counter Increment If Equal
@@ -1672,7 +1797,9 @@ struct alignas(uint32_t) AluInstruction {
bool abs_constants() const { return data_.abs_constants == 1; }
bool is_const_0_addressed() const { return data_.const_0_rel_abs == 1; }
bool is_const_1_addressed() const { return data_.const_1_rel_abs == 1; }
bool is_address_relative() const { return data_.address_absolute == 1; }
bool is_const_address_register_relative() const {
return data_.const_address_register_relative == 1;
}
AluVectorOpcode vector_opcode() const { return data_.vector_opc; }
uint32_t vector_write_mask() const { return data_.vector_write_mask; }
@@ -1686,6 +1813,18 @@ struct alignas(uint32_t) AluInstruction {
bool is_scalar_dest_relative() const { return data_.scalar_dest_rel == 1; }
bool scalar_clamp() const { return data_.scalar_clamp == 1; }
static constexpr uint32_t src_temp_reg(uint32_t src_reg) {
return src_reg & 0x3F;
}
static constexpr bool is_src_temp_relative(uint32_t src_reg) {
return (src_reg & 0x40) != 0;
}
static constexpr bool is_src_temp_value_absolute(uint32_t src_reg) {
return (src_reg & 0x80) != 0;
}
// Full register index for constants, packed structure for temporary
// registers (unpack using src_temp_reg, is_src_temp_relative,
// is_src_temp_value_absolute).
uint32_t src_reg(size_t i) const {
switch (i) {
case 1:
@@ -1702,16 +1841,59 @@ struct alignas(uint32_t) AluInstruction {
bool src_is_temp(size_t i) const {
switch (i) {
case 1:
return data_.src1_sel == 1;
return bool(data_.src1_sel);
case 2:
return data_.src2_sel == 1;
return bool(data_.src2_sel);
case 3:
return data_.src3_sel == 1;
return bool(data_.src3_sel);
default:
assert_unhandled_case(i);
return 0;
}
}
// Whether the specified operand is actually a constant is disregarded in this
// function so its scope is limited to just parsing the structure's layout -
// to decide whether to use relative addressing for the operand as a whole,
// check externally whether the operand is actually a constant first.
//
// For the constant operand in mulsc, addsc, subsc, this should be called for
// the operand index 3. Note that the XNA disassembler takes the addressing
// mode for the constant scalar operand unconditionally from const_1_rel_abs,
// and ignores the +aL for it unless the scalar operation is co-issued with a
// vector operation reading from a constant. However, the XNA assembler treats
// the constant scalar operand as a constant in the third operand, and places
// the addressing mode for it in const_0_rel_abs if no other constants are
// used in the whole ALU instruction. The validator also doesn't report
// anything if +aL is used when the constant scalar operand is the only
// constant in the instruction (and explicitly calls it the third constant in
// the error message in case both vector operands are constants, and different
// addressing modes are used for the second vector operand and the constant
// scalar operand). Passing the disassembly produced by XNA back to the
// assembler results in different microcode in this case. This indicates that
// most likely there's a bug in the XNA disassembler, and that the addressing
// mode for the constant scalar operand should actually be taken the same way
// as for the third vector operand - from const_0_rel_abs if there are no
// constant vector operands, or from const_1_rel_abs if there is at least one.
bool src_const_is_addressed(size_t i) const {
// "error X7100: When three constants are used in one instruction, the
// second and third constant must either both be non-relative, or both be
// relative."
// Whether to use const_0_rel_abs or const_1_rel_abs is essentially
// min(sum of whether the previous operands are constants, 1).
switch (i) {
case 1:
return bool(data_.const_0_rel_abs);
case 2:
return bool(src_is_temp(1) ? data_.const_0_rel_abs
: data_.const_1_rel_abs);
case 3:
return bool((src_is_temp(1) && src_is_temp(2)) ? data_.const_0_rel_abs
: data_.const_1_rel_abs);
default:
assert_unhandled_case(i);
return false;
}
}
uint32_t src_swizzle(size_t i) const {
switch (i) {
case 1:
@@ -1739,8 +1921,20 @@ struct alignas(uint32_t) AluInstruction {
}
}
uint32_t scalar_const_op_src_temp_reg() const {
return (uint32_t(data_.scalar_opc) & 1) | (data_.src3_sel << 1) |
(data_.src3_swiz & 0x3C);
}
// Helpers.
// Returns the absolute component index calculated from the relative swizzle
// in an ALU instruction.
static constexpr uint32_t GetSwizzledComponentIndex(
uint32_t swizzle, uint32_t component_index) {
return ((swizzle >> (2 * component_index)) + component_index) & 3;
}
// Note that even if the export component is unused (like W of the vertex
// shader misc register, YZW of pixel shader depth), it must still not be
// excluded - that may make disassembly not reassemblable if there are
@@ -1803,6 +1997,7 @@ struct alignas(uint32_t) AluInstruction {
AluScalarOpcode scalar_opc : 6;
};
struct {
// Swizzles are component-relative.
uint32_t src3_swiz : 8;
uint32_t src2_swiz : 8;
uint32_t src1_swiz : 8;
@@ -1811,7 +2006,9 @@ struct alignas(uint32_t) AluInstruction {
uint32_t src1_reg_negate : 1;
uint32_t pred_condition : 1;
uint32_t is_predicated : 1;
uint32_t address_absolute : 1;
// Temporary registers can have only absolute and aL-relative indices, not
// a0-relative.
uint32_t const_address_register_relative : 1;
uint32_t const_1_rel_abs : 1;
uint32_t const_0_rel_abs : 1;
};