[GPU] Dynamic r# count via shader modifications + refactoring

This commit is contained in:
Triang3l
2020-12-19 16:14:54 +03:00
parent b106aa88e6
commit e6fa0ad139
30 changed files with 1684 additions and 1716 deletions

View File

@@ -11,9 +11,9 @@
#define XENIA_GPU_SHADER_H_
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
@@ -593,6 +593,41 @@ struct ParsedAluInstruction {
void Disassemble(StringBuffer* out) const;
};
void ParseControlFlowExec(const ucode::ControlFlowExecInstruction& cf,
uint32_t cf_index, ParsedExecInstruction& instr);
void ParseControlFlowCondExec(const ucode::ControlFlowCondExecInstruction& cf,
uint32_t cf_index, ParsedExecInstruction& instr);
void ParseControlFlowCondExecPred(
const ucode::ControlFlowCondExecPredInstruction& cf, uint32_t cf_index,
ParsedExecInstruction& instr);
void ParseControlFlowLoopStart(const ucode::ControlFlowLoopStartInstruction& cf,
uint32_t cf_index,
ParsedLoopStartInstruction& instr);
void ParseControlFlowLoopEnd(const ucode::ControlFlowLoopEndInstruction& cf,
uint32_t cf_index,
ParsedLoopEndInstruction& instr);
void ParseControlFlowCondCall(const ucode::ControlFlowCondCallInstruction& cf,
uint32_t cf_index, ParsedCallInstruction& instr);
void ParseControlFlowReturn(const ucode::ControlFlowReturnInstruction& cf,
uint32_t cf_index, ParsedReturnInstruction& instr);
void ParseControlFlowCondJmp(const ucode::ControlFlowCondJmpInstruction& cf,
uint32_t cf_index, ParsedJumpInstruction& instr);
void ParseControlFlowAlloc(const ucode::ControlFlowAllocInstruction& cf,
uint32_t cf_index, bool is_vertex_shader,
ParsedAllocInstruction& instr);
// Returns whether the fetch is a full one, and the next parsed mini vertex
// fetch should inherit most of its parameters.
bool ParseVertexFetchInstruction(
const ucode::VertexFetchInstruction& op,
const ucode::VertexFetchInstruction& previous_full_op,
ParsedVertexFetchInstruction& instr);
void ParseTextureFetchInstruction(const ucode::TextureFetchInstruction& op,
ParsedTextureFetchInstruction& instr);
void ParseAluInstruction(const ucode::AluInstruction& op,
xenos::ShaderType shader_type,
ParsedAluInstruction& instr);
class Shader {
public:
// Type of the vertex shader in a D3D11-like rendering pipeline - shader
@@ -619,12 +654,8 @@ class Shader {
struct VertexBinding {
struct Attribute {
// Attribute index, 0-based in the entire shader.
int attrib_index;
// Fetch instruction with all parameters.
ParsedVertexFetchInstruction fetch_instr;
// Size of the attribute, in words.
uint32_t size_words;
};
// Index within the vertex binding listing.
@@ -691,6 +722,10 @@ class Shader {
}
};
// Based on the number of AS_VS/PS_EXPORT_STREAM_* enum sets found in a game
// .pdb.
static constexpr uint32_t kMaxMemExports = 16;
class Translation {
public:
virtual ~Translation() {}
@@ -698,7 +733,7 @@ class Shader {
Shader& shader() const { return shader_; }
// Translator-specific modification bits.
uint32_t modification() const { return modification_; }
uint64_t modification() const { return modification_; }
// True if the shader was translated and prepared without error.
bool is_valid() const { return is_valid_; }
@@ -735,7 +770,7 @@ class Shader {
const char* path_prefix);
protected:
Translation(Shader& shader, uint32_t modification)
Translation(Shader& shader, uint64_t modification)
: shader_(shader), modification_(modification) {}
private:
@@ -743,7 +778,7 @@ class Shader {
friend class ShaderTranslator;
Shader& shader_;
uint32_t modification_;
uint64_t modification_;
bool is_valid_ = false;
bool is_translated_ = false;
@@ -765,32 +800,23 @@ class Shader {
const uint32_t* ucode_dwords() const { return ucode_data_.data(); }
size_t ucode_dword_count() const { return ucode_data_.size(); }
// Host translations with the specified modification bits. Not thread-safe
// with respect to translation creation/destruction.
const std::unordered_map<uint32_t, Translation*>& translations() const {
return translations_;
}
Translation* GetTranslation(uint32_t modification) const {
auto it = translations_.find(modification);
if (it != translations_.cend()) {
return it->second;
}
return nullptr;
}
Translation* GetOrCreateTranslation(uint32_t modification,
bool* is_new = nullptr);
// For shader storage loading, to remove a modification in case of translation
// failure. Not thread-safe.
void DestroyTranslation(uint32_t modification);
bool is_ucode_analyzed() const { return is_ucode_analyzed_; }
// ucode_disasm_buffer is temporary storage for disassembly (provided
// externally so it won't need to be reallocated for every shader).
void AnalyzeUcode(StringBuffer& ucode_disasm_buffer);
// The following parameters, until the translation, are valid if ucode
// information has been gathered.
// Microcode disassembly in D3D format.
const std::string& ucode_disassembly() const { return ucode_disassembly_; }
// All vertex bindings used in the shader.
// Valid for vertex shaders only.
const std::vector<VertexBinding>& vertex_bindings() const {
return vertex_bindings_;
}
// All texture bindings used in the shader.
// Valid for both vertex and pixel shaders.
const std::vector<TextureBinding>& texture_bindings() const {
return texture_bindings_;
}
@@ -800,24 +826,99 @@ class Shader {
return constant_register_map_;
}
// uint5[Shader::kMaxMemExports] - bits indicating which eM# registers have
// been written to after each `alloc export`, for up to Shader::kMaxMemExports
// exports. This will contain zero for certain corrupt exports - for those to
// which a valid eA was not written via a MAD with a stream constant.
const uint8_t* memexport_eM_written() const { return memexport_eM_written_; }
// All c# registers used as the addend in MAD operations to eA.
const std::vector<uint32_t>& memexport_stream_constants() const {
const std::set<uint32_t>& memexport_stream_constants() const {
return memexport_stream_constants_;
}
// Returns true if the given color target index [0-3].
bool writes_color_target(uint32_t i) const {
return writes_color_targets_[i];
// Labels that jumps (explicit or from loops) can be done to.
const std::set<uint32_t>& label_addresses() const { return label_addresses_; }
// Exclusive upper bound of the indexes of paired control flow instructions
// (each corresponds to 3 dwords).
uint32_t cf_pair_index_bound() const { return cf_pair_index_bound_; }
// Upper bound of temporary registers addressed statically by the shader -
// highest static register address + 1, or 0 if no registers referenced this
// way. SQ_PROGRAM_CNTL is not always reliable - some draws (like single point
// draws with oPos = 0001 that are done by Xbox 360's Direct3D 9 sometimes;
// can be reproduced by launching Arrival in Halo 3 from the campaign lobby)
// that aren't supposed to cover any pixels use an invalid (zero)
// SQ_PROGRAM_CNTL, but with an outdated pixel shader loaded, in this case
// SQ_PROGRAM_CNTL may contain a number smaller than actually needed by the
// pixel shader - SQ_PROGRAM_CNTL should be used to go above this count if
// uses_register_dynamic_addressing is true.
uint32_t register_static_address_bound() const {
return register_static_address_bound_;
}
// True if the shader overrides the pixel depth.
bool writes_depth() const { return writes_depth_; }
// Whether the shader addresses temporary registers dynamically, thus
// SQ_PROGRAM_CNTL should determine the number of registers to use, not only
// register_static_address_bound.
bool uses_register_dynamic_addressing() const {
return uses_register_dynamic_addressing_;
}
// For building shader modification bits (and also for normalization of them),
// returns the amount of temporary registers that need to be allocated
// explicitly - if not using register dynamic addressing, the shader
// translator will use register_static_address_bound directly.
uint32_t GetDynamicAddressableRegisterCount(
uint32_t program_cntl_num_reg) const {
if (!uses_register_dynamic_addressing()) {
return 0;
}
return std::max((program_cntl_num_reg & 0x80)
? uint32_t(0)
: (program_cntl_num_reg + uint32_t(1)),
register_static_address_bound());
}
// True if the current shader has any `kill` instructions.
bool kills_pixels() const { return kills_pixels_; }
// Microcode disassembly in D3D format.
const std::string& ucode_disassembly() const { return ucode_disassembly_; }
// True if the shader overrides the pixel depth.
bool writes_depth() const { return writes_depth_; }
// Whether the shader can have early depth and stencil writing enabled, unless
// alpha test or alpha to coverage is enabled.
bool implicit_early_z_write_allowed() const {
// TODO(Triang3l): Investigate what happens to memexport when the pixel
// fails the depth/stencil test, but in Direct3D 11 UAV writes disable early
// depth/stencil.
return !writes_depth() && !kills_pixels() &&
memexport_stream_constants().empty();
}
// Whether each color render target is written to on any exection path.
uint32_t writes_color_targets() const { return writes_color_targets_; }
bool writes_color_target(uint32_t i) const {
return (writes_color_targets() & (uint32_t(1) << i)) != 0;
}
// Host translations with the specified modification bits. Not thread-safe
// with respect to translation creation/destruction.
const std::unordered_map<uint64_t, Translation*>& translations() const {
return translations_;
}
Translation* GetTranslation(uint64_t modification) const {
auto it = translations_.find(modification);
if (it != translations_.cend()) {
return it->second;
}
return nullptr;
}
Translation* GetOrCreateTranslation(uint64_t modification,
bool* is_new = nullptr);
// For shader storage loading, to remove a modification in case of translation
// failure. Not thread-safe.
void DestroyTranslation(uint64_t modification);
// An externally managed identifier of the shader storage the microcode of the
// shader was last written to, or was loaded from, to only write the shader
@@ -835,33 +936,68 @@ class Shader {
protected:
friend class ShaderTranslator;
virtual Translation* CreateTranslationInstance(uint32_t modification);
virtual Translation* CreateTranslationInstance(uint64_t modification);
xenos::ShaderType shader_type_;
std::vector<uint32_t> ucode_data_;
uint64_t ucode_data_hash_;
// Modification bits -> translation.
std::unordered_map<uint32_t, Translation*> translations_;
// Whether info needed before translating has been gathered already - may be
// needed to determine which modifications are actually needed and make sense
// (for instance, there may be draws not covering anything and not allocating
// any pixel shader registers in SQ_PROGRAM_CNTL, but still using the pixel
// shader from the previous draw - in this case, every shader that happens to
// be before such draw will need to be translated again with a different
// dynamically addressed register count, which may cause compilation of
// different random pipelines across many random frames, thus causing
// stuttering - normally host pipeline states are deterministically only
// compiled when a new material appears in the game, and having the order of
// draws also matter in such unpredictable way would break this rule; limit
// the effect to shaders with dynamic register addressing only, which are
// extremely rare), also some info needed for drawing is collected during the
// ucode analysis.
bool is_ucode_analyzed_ = false;
// Whether setup of the post-translation parameters (listed below, plus those
// specific to the implementation) has been initiated, by any thread. If
// translation is performed on multiple threads, only one thread must be
// setting this up (other threads would write the same data anyway).
std::atomic_flag post_translation_info_set_up_ = ATOMIC_FLAG_INIT;
// Initialized after the first successful translation (these don't depend on
// the host-side modification bits).
std::string ucode_disassembly_;
std::vector<VertexBinding> vertex_bindings_;
std::vector<TextureBinding> texture_bindings_;
ConstantRegisterMap constant_register_map_ = {0};
bool writes_color_targets_[4] = {false, false, false, false};
bool writes_depth_ = false;
uint8_t memexport_eM_written_[kMaxMemExports] = {};
std::set<uint32_t> memexport_stream_constants_;
std::set<uint32_t> label_addresses_;
uint32_t cf_pair_index_bound_ = 0;
uint32_t register_static_address_bound_ = 0;
bool uses_register_dynamic_addressing_ = false;
bool kills_pixels_ = false;
std::vector<uint32_t> memexport_stream_constants_;
bool writes_depth_ = false;
uint32_t writes_color_targets_ = 0b0000;
// Modification bits -> translation.
std::unordered_map<uint64_t, Translation*> translations_;
uint32_t ucode_storage_index_ = UINT32_MAX;
private:
void GatherExecInformation(
const ParsedExecInstruction& instr,
ucode::VertexFetchInstruction& previous_vfetch_full,
uint32_t& unique_texture_bindings, uint32_t memexport_alloc_current_count,
uint32_t& memexport_eA_written, StringBuffer& ucode_disasm_buffer);
void GatherVertexFetchInformation(
const ucode::VertexFetchInstruction& op,
ucode::VertexFetchInstruction& previous_vfetch_full,
StringBuffer& ucode_disasm_buffer);
void GatherTextureFetchInformation(const ucode::TextureFetchInstruction& op,
uint32_t& unique_texture_bindings,
StringBuffer& ucode_disasm_buffer);
void GatherAluInstructionInformation(const ucode::AluInstruction& op,
uint32_t memexport_alloc_current_count,
uint32_t& memexport_eA_written,
StringBuffer& ucode_disasm_buffer);
void GatherOperandInformation(const InstructionOperand& operand);
void GatherFetchResultInformation(const InstructionResult& result);
void GatherAluResultInformation(const InstructionResult& result,
uint32_t memexport_alloc_current_count);
};
} // namespace gpu