[GPU] Dynamic r# count via shader modifications + refactoring
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user