[GPU] Shader translator refactoring (mostly ALU), fixes for disassembly round trip and write masks

This commit is contained in:
Triang3l
2020-05-08 23:57:51 +03:00
parent 8f91e580f4
commit 3aa0ce3096
12 changed files with 959 additions and 765 deletions

View File

@@ -10,10 +10,12 @@
#ifndef XENIA_GPU_SHADER_H_
#define XENIA_GPU_SHADER_H_
#include <algorithm>
#include <filesystem>
#include <string>
#include <vector>
#include "xenia/base/math.h"
#include "xenia/base/string_buffer.h"
#include "xenia/gpu/ucode.h"
#include "xenia/gpu/xenos.h"
@@ -21,23 +23,32 @@
namespace xe {
namespace gpu {
// The structures here are used for both translation and disassembly.
//
// Because disassembly uses them too, to make sure "assemble -> disassemble ->
// reassemble" round trip is always successful with the XNA assembler (as it is
// the accuracy benchmark for translation), only generalization - not
// optimization like nop skipping/replacement - must be done while converting
// microcode to these structures (in other words, parsed shader code should be
// enough to accurately reconstruct the microcode for any shader that could be
// written by a human in assembly).
//
// During the "parsed -> host" part of the translation, however, translators are
// free to make any optimizations (as long as they don't affect the result, of
// course) they find appropriate.
enum class InstructionStorageTarget {
// Result is not stored.
kNone,
// Result is stored to a temporary register indexed by storage_index [0-31].
kRegister,
// Result is stored into a vertex shader interpolant export [0-15].
kInterpolant,
// Result is stored into a vertex shader interpolator export [0-15].
kInterpolator,
// Result is stored to the position export (gl_Position).
kPosition,
// Result is stored to the vertex shader misc export register.
// See R6xx/R7xx registers for details (USE_VTX_POINT_SIZE, USE_VTX_EDGE_FLAG,
// USE_VTX_KILL_FLAG).
// X - PSIZE (gl_PointSize).
// Y - EDGEFLAG (glEdgeFlag) for PrimitiveType::kPolygon wireframe/point
// drawing.
// Z - KILLVERTEX flag (used in Banjo-Kazooie: Nuts & Bolts for grass), set
// for killing primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition.
// Result is stored to the vertex shader misc export register, see
// ucode::ExportRegister::kVSPointSizeEdgeFlagKillVertex for description of
// components.
kPointSizeEdgeFlagKillVertex,
// Result is stored as memexport destination address
// (see xenos::xe_gpu_memexport_stream_t).
@@ -45,11 +56,29 @@ enum class InstructionStorageTarget {
// Result is stored to memexport destination data.
kExportData,
// Result is stored to a color target export indexed by storage_index [0-3].
kColorTarget,
// Result is stored to the depth export (gl_FragDepth).
kColor,
// X of the result is stored to the depth export (gl_FragDepth).
kDepth,
};
// Must be used only in translation to skip unused components, but not in
// disassembly (because oPts.x000 will be assembled, but oPts.x00_ has both
// skipped components and zeros, which cannot be encoded, and therefore it will
// not).
constexpr uint32_t GetInstructionStorageTargetUsedComponents(
InstructionStorageTarget target) {
switch (target) {
case InstructionStorageTarget::kNone:
return 0b0000;
case InstructionStorageTarget::kPointSizeEdgeFlagKillVertex:
return 0b0111;
case InstructionStorageTarget::kDepth:
return 0b0001;
default:
return 0b1111;
}
}
enum class InstructionStorageAddressingMode {
// The storage index is not dynamically addressed.
kStatic,
@@ -75,71 +104,63 @@ enum class SwizzleSource {
k1,
};
constexpr SwizzleSource GetSwizzleFromComponentIndex(int i) {
constexpr SwizzleSource GetSwizzleFromComponentIndex(uint32_t i) {
return static_cast<SwizzleSource>(i);
}
inline char GetCharForComponentIndex(int i) {
inline char GetCharForComponentIndex(uint32_t i) {
const static char kChars[] = {'x', 'y', 'z', 'w'};
return kChars[i];
}
inline char GetCharForSwizzle(SwizzleSource swizzle_source) {
const static char kChars[] = {'x', 'y', 'z', 'w', '0', '1'};
return kChars[static_cast<int>(swizzle_source)];
return kChars[static_cast<uint32_t>(swizzle_source)];
}
struct InstructionResult {
// Where the result is going.
InstructionStorageTarget storage_target = InstructionStorageTarget::kNone;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
uint32_t storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
// True if the result is exporting from the shader.
bool is_export = false;
// True to clamp the result value to [0-1].
bool is_clamped = false;
// Defines whether each output component is written.
bool write_mask[4] = {false, false, false, false};
// Defines whether each output component is written, though this is from the
// original microcode, not taking into account whether such components
// actually exist in the target.
uint32_t original_write_mask = 0b0000;
// Defines the source for each output component xyzw.
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// Returns true if any component is written to.
bool has_any_writes() const {
return write_mask[0] || write_mask[1] || write_mask[2] || write_mask[3];
}
// Returns true if all components are written to.
bool has_all_writes() const {
return write_mask[0] && write_mask[1] && write_mask[2] && write_mask[3];
}
// Returns number of components written
uint32_t num_writes() const {
uint32_t total = 0;
for (int i = 0; i < 4; i++) {
if (write_mask[i]) {
total++;
}
}
return total;
}
// Returns true if any non-constant components are written.
bool stores_non_constants() const {
for (int i = 0; i < 4; ++i) {
if (write_mask[i] && components[i] != SwizzleSource::k0 &&
components[i] != SwizzleSource::k1) {
return true;
}
}
return false;
// Returns the write mask containing only components actually present in the
// target.
uint32_t GetUsedWriteMask() const {
return original_write_mask &
GetInstructionStorageTargetUsedComponents(storage_target);
}
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
return has_all_writes() && components[0] == SwizzleSource::kX &&
bool IsStandardSwizzle() const {
return (GetUsedWriteMask() == 0b1111) &&
components[0] == SwizzleSource::kX &&
components[1] == SwizzleSource::kY &&
components[2] == SwizzleSource::kZ &&
components[3] == SwizzleSource::kW;
}
// Returns the components of the result, before swizzling, that won't be
// discarded or replaced with a constant.
uint32_t GetUsedResultComponents() const {
uint32_t used_write_mask = GetUsedWriteMask();
uint32_t used_components = 0b0000;
for (uint32_t i = 0; i < 4; ++i) {
if ((used_write_mask & (1 << i)) && components[i] >= SwizzleSource::kX &&
components[i] <= SwizzleSource::kW) {
used_components |=
1 << (uint32_t(components[i]) - uint32_t(SwizzleSource::kX));
}
}
return used_components;
}
};
enum class InstructionStorageSource {
@@ -159,7 +180,7 @@ struct InstructionOperand {
// Where the source comes from.
InstructionStorageSource storage_source = InstructionStorageSource::kRegister;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
uint32_t storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
@@ -168,13 +189,19 @@ struct InstructionOperand {
// True to take the absolute value of the source (before any negation).
bool is_absolute_value = false;
// Number of components taken from the source operand.
int component_count = 0;
uint32_t component_count = 4;
// Defines the source for each component xyzw (up to the given
// component_count).
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// Returns the swizzle source for the component, replicating the rightmost
// component if there are less than 4 components (similar to what the Xbox 360
// shader compiler does as a general rule for unspecified components).
SwizzleSource GetComponent(uint32_t index) const {
return components[std::min(index, component_count - 1)];
}
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
bool IsStandardSwizzle() const {
switch (component_count) {
case 4:
return components[0] == SwizzleSource::kX &&
@@ -185,26 +212,32 @@ struct InstructionOperand {
return false;
}
// Whether absolute values of two operands are identical (useful for emulating
// Shader Model 3 0*anything=0 multiplication behavior).
bool EqualsAbsolute(const InstructionOperand& other) const {
// Returns which components of two operands are identical, but may have
// different signs (for simplicity of usage with GetComponent, treating the
// rightmost component as replicated).
uint32_t GetAbsoluteIdenticalComponents(
const InstructionOperand& other) const {
if (storage_source != other.storage_source ||
storage_index != other.storage_index ||
storage_addressing_mode != other.storage_addressing_mode ||
component_count != other.component_count) {
return false;
storage_addressing_mode != other.storage_addressing_mode) {
return 0;
}
for (int i = 0; i < component_count; ++i) {
if (components[i] != other.components[i]) {
return false;
}
uint32_t identical_components = 0;
for (uint32_t i = 0; i < 4; ++i) {
identical_components |= uint32_t(GetComponent(i) == other.GetComponent(i))
<< i;
}
return true;
return identical_components;
}
bool operator==(const InstructionOperand& other) const {
return EqualsAbsolute(other) && is_negated == other.is_negated &&
is_absolute_value == other.is_absolute_value;
// Returns which components of two operands will always be bitwise equal, but
// may have different signs (disregarding component_count for simplicity of
// usage with GetComponent, treating the rightmost component as replicated).
uint32_t GetIdenticalComponents(const InstructionOperand& other) const {
if (is_negated != other.is_negated ||
is_absolute_value != other.is_absolute_value) {
return 0;
}
return GetAbsoluteIdenticalComponents(other);
}
};
@@ -365,9 +398,6 @@ struct ParsedAllocInstruction {
};
struct ParsedVertexFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
@@ -409,9 +439,6 @@ struct ParsedVertexFetchInstruction {
};
struct ParsedTextureFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
@@ -462,17 +489,6 @@ struct ParsedTextureFetchInstruction {
};
struct ParsedAluInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// True if the vector part of the instruction needs to be executed and data
// about it in this structure is valid.
bool has_vector_op = false;
// True if the scalar part of the instruction needs to be executed and data
// about it in this structure is valid.
bool has_scalar_op = false;
bool is_nop() const { return !has_vector_op && !has_scalar_op; }
// Opcode for the vector part of the instruction.
ucode::AluVectorOpcode vector_opcode = ucode::AluVectorOpcode::kAdd;
// Opcode for the scalar part of the instruction.
@@ -488,8 +504,20 @@ struct ParsedAluInstruction {
// Expected predication condition value if predicated.
bool predicate_condition = false;
// Describes how the vector operation result is stored.
InstructionResult vector_result;
// Describes how the vector operation result and, for exports, constant 0/1
// are stored. For simplicity of translation and disassembly, treating
// constant 0/1 writes as a part of the vector operation - they need to be
// expressed somehow in the disassembly anyway with a properly disassembled
// instruction even if only constants are being exported. The XNA disassembler
// falls back to displaying the whole vector operation, even if only constant
// components are written, if the scalar operation is a nop or if the vector
// operation has side effects (but if the scalar operation isn't nop, it
// outputs the entire constant mask in the scalar operation destination).
// Normally the XNA disassembler outputs the constant mask in both vector and
// scalar operations, but that's not required by assembler, so it doesn't
// really matter whether it's specified in the vector operation, in the scalar
// operation, or in both.
InstructionResult vector_and_constant_result;
// Describes how the scalar operation result is stored.
InstructionResult scalar_result;
// Both operations must be executed before any result is stored if vector and
@@ -499,27 +527,109 @@ struct ParsedAluInstruction {
// operations.
// Number of source operands of the vector operation.
size_t vector_operand_count = 0;
uint32_t vector_operand_count = 0;
// Describes each source operand of the vector operation.
InstructionOperand vector_operands[3];
// Number of source operands of the scalar operation.
size_t scalar_operand_count = 0;
uint32_t scalar_operand_count = 0;
// Describes each source operand of the scalar operation.
InstructionOperand scalar_operands[2];
// If this is a valid eA write (MAD with a stream constant), returns the index
// of the stream float constant, otherwise returns UINT32_MAX.
// Whether the vector part of the instruction is the same as if it was omitted
// in the assembly (if compiled or assembled with the Xbox 360 shader
// compiler), and thus reassembling the shader with this instruction omitted
// will result in the same microcode (since instructions with just an empty
// write mask may have different values in other fields).
// This is for disassembly! Translators should use the write masks and
// AluVectorOpHasSideEffects to skip operations, as this only covers one very
// specific nop format!
bool IsVectorOpDefaultNop() const {
if (vector_opcode != ucode::AluVectorOpcode::kMax ||
vector_and_constant_result.original_write_mask ||
vector_and_constant_result.is_clamped ||
vector_operands[0].storage_source !=
InstructionStorageSource::kRegister ||
vector_operands[0].storage_index != 0 ||
vector_operands[0].storage_addressing_mode !=
InstructionStorageAddressingMode::kStatic ||
vector_operands[0].is_negated || vector_operands[0].is_absolute_value ||
!vector_operands[0].IsStandardSwizzle() ||
vector_operands[1].storage_source !=
InstructionStorageSource::kRegister ||
vector_operands[1].storage_index != 0 ||
vector_operands[1].storage_addressing_mode !=
InstructionStorageAddressingMode::kStatic ||
vector_operands[1].is_negated || vector_operands[1].is_absolute_value ||
!vector_operands[1].IsStandardSwizzle()) {
return false;
}
if (vector_and_constant_result.storage_target ==
InstructionStorageTarget::kRegister) {
if (vector_and_constant_result.storage_index != 0 ||
vector_and_constant_result.storage_addressing_mode !=
InstructionStorageAddressingMode::kStatic) {
return false;
}
} else {
// In case both vector and scalar operations are nop, still need to write
// somewhere that it's an export, not mov r0._, r0 + retain_prev r0._.
// Accurate round trip is possible only if the target is o0 or oC0,
// because if the total write mask is empty, the XNA assembler forces the
// destination to be o0/oC0, but this doesn't really matter in this case.
if (IsScalarOpDefaultNop()) {
return false;
}
}
return true;
}
// Whether the scalar part of the instruction is the same as if it was omitted
// in the assembly (if compiled or assembled with the Xbox 360 shader
// compiler), and thus reassembling the shader with this instruction omitted
// will result in the same microcode (since instructions with just an empty
// write mask may have different values in other fields).
bool IsScalarOpDefaultNop() const {
if (scalar_opcode != ucode::AluScalarOpcode::kRetainPrev ||
scalar_result.original_write_mask || scalar_result.is_clamped) {
return false;
}
if (scalar_result.storage_target == InstructionStorageTarget::kRegister) {
if (scalar_result.storage_index != 0 ||
scalar_result.storage_addressing_mode !=
InstructionStorageAddressingMode::kStatic) {
return false;
}
}
// For exports, if both are nop, the vector operation will be kept to state
// in the microcode that the destination in the microcode is an export.
return true;
}
// For translation (not disassembly) - whether this instruction has totally no
// effect.
bool IsNop() const {
return scalar_opcode == ucode::AluScalarOpcode::kRetainPrev &&
!scalar_result.GetUsedWriteMask() &&
!vector_and_constant_result.GetUsedWriteMask() &&
!ucode::AluVectorOpHasSideEffects(vector_opcode);
}
// If this is a "normal" eA write recognized by Xenia (MAD with a stream
// constant), returns the index of the stream float constant, otherwise
// returns UINT32_MAX.
uint32_t GetMemExportStreamConstant() const {
if (has_vector_op &&
vector_result.storage_target ==
if (vector_and_constant_result.storage_target ==
InstructionStorageTarget::kExportAddress &&
vector_opcode == ucode::AluVectorOpcode::kMad &&
vector_result.has_all_writes() &&
vector_and_constant_result.GetUsedResultComponents() == 0b1111 &&
!vector_and_constant_result.is_clamped &&
vector_operands[2].storage_source ==
InstructionStorageSource::kConstantFloat &&
vector_operands[2].storage_addressing_mode ==
InstructionStorageAddressingMode::kStatic &&
vector_operands[2].is_standard_swizzle()) {
vector_operands[2].IsStandardSwizzle() &&
!vector_operands[2].is_negated &&
!vector_operands[2].is_absolute_value) {
return vector_operands[2].storage_index;
}
return UINT32_MAX;
@@ -581,9 +691,8 @@ class Shader {
struct ConstantRegisterMap {
// Bitmap of all kConstantFloat registers read by the shader.
// Any shader can only read up to 256 of the 512, and the base is dependent
// on the shader type. Each bit corresponds to a storage index from the type
// base, so bit 0 in a vertex shader is register 0, and bit 0 in a fragment
// shader is register 256.
// on the shader type and SQ_VS/PS_CONST registers. Each bit corresponds to
// a storage index from the type base.
uint64_t float_bitmap[256 / 64];
// Bitmap of all loop constants read by the shader.
// Each bit corresponds to a storage index [0-31].
@@ -595,8 +704,33 @@ class Shader {
// Total number of kConstantFloat registers read by the shader.
uint32_t float_count;
// Computed byte count of all registers required when packed.
uint32_t packed_byte_length;
// Whether kConstantFloat registers are indexed dynamically - in this case,
// float_bitmap must be set to all 1, and tight packing must not be done.
bool float_dynamic_addressing;
// Returns the index of the float4 constant as if all float4 constant
// registers actually referenced were tightly packed in a buffer, or
// UINT32_MAX if not found.
uint32_t GetPackedFloatConstantIndex(uint32_t float_constant) const {
if (float_constant >= 256) {
return UINT32_MAX;
}
if (float_dynamic_addressing) {
// Any can potentially be read - not packing.
return float_constant;
}
uint32_t block_index = float_constant / 64;
uint32_t bit_index = float_constant % 64;
if (!(float_bitmap[block_index] & (uint64_t(1) << bit_index))) {
return UINT32_MAX;
}
uint32_t offset = 0;
for (uint32_t i = 0; i < block_index; ++i) {
offset += xe::bit_count(float_bitmap[i]);
}
return offset + xe::bit_count(float_bitmap[block_index] &
((uint64_t(1) << bit_index) - 1));
}
};
Shader(ShaderType shader_type, uint64_t ucode_data_hash,
@@ -642,7 +776,9 @@ class Shader {
}
// Returns true if the given color target index [0-3].
bool writes_color_target(int i) const { return writes_color_targets_[i]; }
bool writes_color_target(uint32_t i) const {
return writes_color_targets_[i];
}
// True if the shader overrides the pixel depth.
bool writes_depth() const { return writes_depth_; }