From c86e479214ffaf540d29a35a91f1fe94eb16ba1f Mon Sep 17 00:00:00 2001 From: Ben Vanik Date: Sun, 6 Dec 2015 10:36:07 -0800 Subject: [PATCH] Replacing old Shader with TranslatedShader. --- src/xenia/gpu/gl4/gl4_command_processor.cc | 30 +- src/xenia/gpu/gl4/gl4_command_processor.h | 3 +- src/xenia/gpu/gl4/gl4_shader.cc | 73 +-- src/xenia/gpu/gl4/gl4_shader.h | 3 +- src/xenia/gpu/glsl_shader_translator.cc | 3 +- src/xenia/gpu/sampler_info.h | 2 +- src/xenia/gpu/shader.cc | 75 ++- src/xenia/gpu/shader.h | 557 ++++++++++++++++++++- src/xenia/gpu/shader_compiler_main.cc | 14 +- src/xenia/gpu/shader_translator.cc | 76 +-- src/xenia/gpu/shader_translator.h | 534 +------------------- src/xenia/gpu/trace_viewer.cc | 25 +- src/xenia/gpu/trace_viewer.h | 9 +- tools/shader-playground/Editor.cs | 16 +- 14 files changed, 693 insertions(+), 727 deletions(-) diff --git a/src/xenia/gpu/gl4/gl4_command_processor.cc b/src/xenia/gpu/gl4/gl4_command_processor.cc index e94c2f33b..e71753642 100644 --- a/src/xenia/gpu/gl4/gl4_command_processor.cc +++ b/src/xenia/gpu/gl4/gl4_command_processor.cc @@ -491,12 +491,21 @@ Shader* GL4CommandProcessor::LoadShader(ShaderType shader_type, // Perform translation. // If this fails the shader will be marked as invalid and ignored later. - shader_ptr->Prepare(&shader_translator_); + if (shader_translator_.Translate(shader_ptr)) { + shader_ptr->Prepare(); + + // Dump shader files if desired. + if (!FLAGS_dump_shaders.empty()) { + shader_ptr->Dump(FLAGS_dump_shaders, "gl4"); + } + } else { + XELOGE("Shader failed translation"); + } XELOGGPU("Set %s shader at %0.8X (%db):\n%s", shader_type == ShaderType::kVertex ? "vertex" : "pixel", guest_address, dword_count * 4, - shader_ptr->translated_shader()->ucode_disassembly().c_str()); + shader_ptr->ucode_disassembly().c_str()); } return shader_ptr; } @@ -779,7 +788,6 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateRenderTargets() { // Note that write mask may be more permissive than we want, so we mix that // with the actual targets the pixel shader writes to. GLenum draw_buffers[4] = {GL_NONE, GL_NONE, GL_NONE, GL_NONE}; - auto pixel_shader = active_pixel_shader_->translated_shader(); GLuint color_targets[4] = {kAnyTarget, kAnyTarget, kAnyTarget, kAnyTarget}; if (enable_mode == ModeControl::kColorDepth) { uint32_t color_info[4] = { @@ -789,7 +797,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateRenderTargets() { // A2XX_RB_COLOR_MASK_WRITE_* == D3DRS_COLORWRITEENABLE for (int n = 0; n < xe::countof(color_info); n++) { uint32_t write_mask = (regs.rb_color_mask >> (n * 4)) & 0xF; - if (!write_mask || !pixel_shader->writes_color_target(n)) { + if (!write_mask || !active_pixel_shader_->writes_color_target(n)) { // Unused, so keep disabled and set to wildcard so we'll take any // framebuffer that has it. continue; @@ -1362,9 +1370,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateVertexBuffers() { auto& regs = *register_file_; assert_not_null(active_vertex_shader_); - const auto& vertex_bindings = - active_vertex_shader_->translated_shader()->vertex_bindings(); - for (const auto& vertex_binding : vertex_bindings) { + for (const auto& vertex_binding : active_vertex_shader_->vertex_bindings()) { int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + (vertex_binding.fetch_constant / 3) * 6; const auto group = reinterpret_cast(®s.values[r]); @@ -1434,9 +1440,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() { bool has_setup_sampler[32] = {false}; // Vertex texture samplers. - const auto& vertex_sampler_inputs = - active_vertex_shader_->translated_shader()->texture_bindings(); - for (auto& texture_binding : vertex_sampler_inputs) { + for (auto& texture_binding : active_vertex_shader_->texture_bindings()) { if (has_setup_sampler[texture_binding.fetch_constant]) { continue; } @@ -1450,9 +1454,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() { } // Pixel shader texture sampler. - const auto& pixel_sampler_inputs = - active_pixel_shader_->translated_shader()->texture_bindings(); - for (auto& texture_binding : pixel_sampler_inputs) { + for (auto& texture_binding : active_pixel_shader_->texture_bindings()) { if (has_setup_sampler[texture_binding.fetch_constant]) { continue; } @@ -1469,7 +1471,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() { } GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSampler( - const TranslatedShader::TextureBinding& texture_binding) { + const Shader::TextureBinding& texture_binding) { auto& regs = *register_file_; int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + texture_binding.fetch_constant * 6; diff --git a/src/xenia/gpu/gl4/gl4_command_processor.h b/src/xenia/gpu/gl4/gl4_command_processor.h index 4373d7a21..f113d8673 100644 --- a/src/xenia/gpu/gl4/gl4_command_processor.h +++ b/src/xenia/gpu/gl4/gl4_command_processor.h @@ -123,8 +123,7 @@ class GL4CommandProcessor : public CommandProcessor { UpdateStatus PopulateIndexBuffer(IndexBufferInfo* index_buffer_info); UpdateStatus PopulateVertexBuffers(); UpdateStatus PopulateSamplers(); - UpdateStatus PopulateSampler( - const TranslatedShader::TextureBinding& texture_binding); + UpdateStatus PopulateSampler(const Shader::TextureBinding& texture_binding); bool IssueCopy() override; CachedFramebuffer* GetFramebuffer(GLuint color_targets[4], diff --git a/src/xenia/gpu/gl4/gl4_shader.cc b/src/xenia/gpu/gl4/gl4_shader.cc index 37805daaf..19a508345 100644 --- a/src/xenia/gpu/gl4/gl4_shader.cc +++ b/src/xenia/gpu/gl4/gl4_shader.cc @@ -9,11 +9,8 @@ #include "xenia/gpu/gl4/gl4_shader.h" -#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" -#include "xenia/gpu/gl4/gl4_gpu_flags.h" -#include "xenia/gpu/gpu_flags.h" namespace xe { namespace gpu { @@ -30,8 +27,8 @@ GL4Shader::~GL4Shader() { glDeleteVertexArrays(1, &vao_); } -bool GL4Shader::Prepare(ShaderTranslator* shader_translator) { - if (!Shader::Prepare(shader_translator)) { +bool GL4Shader::Prepare() { + if (!Shader::Prepare()) { return false; } @@ -51,7 +48,7 @@ bool GL4Shader::Prepare(ShaderTranslator* shader_translator) { bool GL4Shader::PrepareVertexArrayObject() { glCreateVertexArrays(1, &vao_); - for (const auto& vertex_binding : translated_shader_->vertex_bindings()) { + for (const auto& vertex_binding : vertex_bindings()) { for (const auto& attrib : vertex_binding.attributes) { auto comp_count = GetVertexFormatComponentCount( attrib.fetch_instr.attributes.data_format); @@ -126,43 +123,8 @@ bool GL4Shader::PrepareVertexArrayObject() { bool GL4Shader::CompileProgram() { assert_zero(program_); - auto source_str = translated_shader_->GetBinaryString(); - - // Save to disk, if we asked for it. - auto base_path = FLAGS_dump_shaders.c_str(); - char file_name[kMaxPath]; - snprintf(file_name, xe::countof(file_name), "%s/gl4_gen_%.16llX.%s", - base_path, data_hash_, - shader_type_ == ShaderType::kVertex ? "vert" : "frag"); - if (!FLAGS_dump_shaders.empty()) { - // Ensure shader dump path exists. - auto dump_shaders_path = xe::to_wstring(FLAGS_dump_shaders); - if (!dump_shaders_path.empty()) { - dump_shaders_path = xe::to_absolute_path(dump_shaders_path); - xe::filesystem::CreateFolder(dump_shaders_path); - } - - char bin_file_name[kMaxPath]; - snprintf(bin_file_name, xe::countof(file_name), "%s/gl4_gen_%.16llX.bin.%s", - base_path, data_hash_, - shader_type_ == ShaderType::kVertex ? "vert" : "frag"); - FILE* f = fopen(bin_file_name, "w"); - if (f) { - fwrite(data_.data(), 4, data_.size(), f); - fclose(f); - } - - // Note that we put the translated source first so we get good line numbers. - f = fopen(file_name, "w"); - if (f) { - fprintf(f, "%s", source_str.c_str()); - fprintf(f, "/*\n"); - fprintf(f, "%s", translated_shader_->ucode_disassembly().c_str()); - fprintf(f, " */\n"); - fclose(f); - } - } - + // Give source to GL. + auto source_str = GetTranslatedBinaryString(); auto source_str_ptr = source_str.c_str(); program_ = glCreateShaderProgramv(shader_type_ == ShaderType::kVertex ? GL_VERTEX_SHADER @@ -185,7 +147,7 @@ bool GL4Shader::CompileProgram() { glGetProgramInfoLog(program_, log_length, &log_length, const_cast(info_log.data())); XELOGE("Unable to link program: %s", info_log.c_str()); - error_log_ = std::move(info_log); + host_error_log_ = std::move(info_log); assert_always("Unable to link generated shader"); return false; } @@ -194,21 +156,21 @@ bool GL4Shader::CompileProgram() { GLint binary_length = 0; glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &binary_length); if (binary_length) { - translated_binary_.resize(binary_length); + host_binary_.resize(binary_length); GLenum binary_format; glGetProgramBinary(program_, binary_length, &binary_length, &binary_format, - translated_binary_.data()); + host_binary_.data()); // If we are on nvidia, we can find the disassembly string. // I haven't been able to figure out from the format how to do this // without a search like this. const char* disasm_start = nullptr; size_t search_offset = 0; - char* search_start = reinterpret_cast(translated_binary_.data()); + char* search_start = reinterpret_cast(host_binary_.data()); while (true) { auto p = reinterpret_cast( - memchr(translated_binary_.data() + search_offset, '!', - translated_binary_.size() - search_offset)); + memchr(host_binary_.data() + search_offset, '!', + host_binary_.size() - search_offset)); if (!p) { break; } @@ -224,19 +186,6 @@ bool GL4Shader::CompileProgram() { } else { host_disassembly_ = std::string("Shader disassembly not available."); } - - // Append to shader dump. - if (!FLAGS_dump_shaders.empty()) { - if (disasm_start) { - FILE* f = fopen(file_name, "a"); - fprintf(f, "\n\n/*\n"); - fprintf(f, "%s", disasm_start); - fprintf(f, "\n*/\n"); - fclose(f); - } else { - XELOGW("Got program binary but unable to find disassembly"); - } - } } return true; diff --git a/src/xenia/gpu/gl4/gl4_shader.h b/src/xenia/gpu/gl4/gl4_shader.h index 5782997ec..98346f122 100644 --- a/src/xenia/gpu/gl4/gl4_shader.h +++ b/src/xenia/gpu/gl4/gl4_shader.h @@ -13,7 +13,6 @@ #include #include "xenia/gpu/shader.h" -#include "xenia/gpu/shader_translator.h" #include "xenia/ui/gl/gl_context.h" namespace xe { @@ -29,7 +28,7 @@ class GL4Shader : public Shader { GLuint program() const { return program_; } GLuint vao() const { return vao_; } - bool Prepare(ShaderTranslator* shader_translator); + bool Prepare() override; protected: bool PrepareVertexArrayObject(); diff --git a/src/xenia/gpu/glsl_shader_translator.cc b/src/xenia/gpu/glsl_shader_translator.cc index 479ff79c3..73ac07f9e 100644 --- a/src/xenia/gpu/glsl_shader_translator.cc +++ b/src/xenia/gpu/glsl_shader_translator.cc @@ -90,8 +90,7 @@ void GlslShaderTranslator::StartTranslation() { // Tons of boilerplate for shaders, here. // We have a large amount of shared state defining uniforms and some common // utility functions used in both vertex and pixel shaders. - EmitSource(R"( -#version 450 + EmitSource(R"(#version 450 #extension all : warn #extension GL_ARB_bindless_texture : require #extension GL_ARB_explicit_uniform_location : require diff --git a/src/xenia/gpu/sampler_info.h b/src/xenia/gpu/sampler_info.h index 32cce2bff..1a4543893 100644 --- a/src/xenia/gpu/sampler_info.h +++ b/src/xenia/gpu/sampler_info.h @@ -10,7 +10,7 @@ #ifndef XENIA_GPU_SAMPLER_INFO_H_ #define XENIA_GPU_SAMPLER_INFO_H_ -#include "xenia/gpu/shader_translator.h" +#include "xenia/gpu/shader.h" #include "xenia/gpu/xenos.h" namespace xe { diff --git a/src/xenia/gpu/shader.cc b/src/xenia/gpu/shader.cc index f0d19924b..c2fc1f25d 100644 --- a/src/xenia/gpu/shader.cc +++ b/src/xenia/gpu/shader.cc @@ -2,38 +2,83 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * + * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/shader.h" -#include "xenia/base/logging.h" +#include + +#include "xenia/base/filesystem.h" +#include "xenia/base/math.h" #include "xenia/base/memory.h" +#include "xenia/base/string.h" namespace xe { namespace gpu { -Shader::Shader(ShaderType shader_type, uint64_t data_hash, - const uint32_t* dword_ptr, uint32_t dword_count) - : shader_type_(shader_type), data_hash_(data_hash) { - data_.resize(dword_count); - xe::copy_and_swap(data_.data(), dword_ptr, dword_count); +Shader::Shader(ShaderType shader_type, uint64_t ucode_data_hash, + const uint32_t* ucode_dwords, size_t ucode_dword_count) + : shader_type_(shader_type), ucode_data_hash_(ucode_data_hash) { + // We keep ucode data in host native format so it's easier to work with. + ucode_data_.resize(ucode_dword_count); + xe::copy_and_swap(ucode_data_.data(), ucode_dwords, ucode_dword_count); } Shader::~Shader() = default; -bool Shader::Prepare(ShaderTranslator* shader_translator) { - // Perform translation. - translated_shader_ = shader_translator->Translate(shader_type_, data_hash_, - data_.data(), data_.size()); - if (!translated_shader_) { - XELOGE("Shader failed translation"); - return false; +std::string Shader::GetTranslatedBinaryString() const { + std::string result; + result.resize(translated_binary_.size()); + std::memcpy(const_cast(result.data()), translated_binary_.data(), + translated_binary_.size()); + return result; +} + +void Shader::Dump(const std::string& base_path, const char* path_prefix) { + // Ensure target path exists. + auto target_path = xe::to_wstring(base_path); + if (!target_path.empty()) { + target_path = xe::to_absolute_path(target_path); + xe::filesystem::CreateFolder(target_path); } - return true; + char txt_file_name[kMaxPath]; + std::snprintf(txt_file_name, xe::countof(txt_file_name), + "%s/shader_%s_%.16llX.%s", base_path.c_str(), path_prefix, + ucode_data_hash_, + shader_type_ == ShaderType::kVertex ? "vert" : "frag"); + FILE* f = fopen(txt_file_name, "w"); + if (f) { + fwrite(translated_binary_.data(), 1, translated_binary_.size(), f); + fprintf(f, "\n\n"); + auto ucode_disasm_ptr = ucode_disassembly().c_str(); + while (*ucode_disasm_ptr) { + auto line_end = std::strchr(ucode_disasm_ptr, '\n'); + fprintf(f, "// "); + fwrite(ucode_disasm_ptr, 1, line_end - ucode_disasm_ptr + 1, f); + ucode_disasm_ptr = line_end + 1; + } + fprintf(f, "\n\n"); + if (!host_disassembly_.empty()) { + fprintf(f, "\n\n/*\n%s\n*/\n", host_disassembly_.c_str()); + } + fclose(f); + } + + char bin_file_name[kMaxPath]; + std::snprintf(bin_file_name, xe::countof(bin_file_name), + "%s/shader_%s_%.16llX.bin.%s", base_path.c_str(), path_prefix, + ucode_data_hash_, + shader_type_ == ShaderType::kVertex ? "vert" : "frag"); + f = fopen(bin_file_name, "w"); + if (f) { + fwrite(ucode_data_.data(), 4, ucode_data_.size(), f); + fclose(f); + } } + } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/shader.h b/src/xenia/gpu/shader.h index 5f66816e5..1c5cf1419 100644 --- a/src/xenia/gpu/shader.h +++ b/src/xenia/gpu/shader.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * + * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -13,42 +13,565 @@ #include #include -#include "xenia/gpu/shader_translator.h" +#include "xenia/base/string_buffer.h" +#include "xenia/gpu/ucode.h" #include "xenia/gpu/xenos.h" namespace xe { namespace gpu { +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 to the position export (gl_Position). + kPosition, + // Result is stored to the point size export (gl_PointSize). + kPointSize, + // Result is stored to a color target export indexed by storage_index [0-3]. + kColorTarget, + // Result is stored to the depth export (gl_FragDepth). + kDepth, +}; + +enum class InstructionStorageAddressingMode { + // The storage index is not dynamically addressed. + kStatic, + // The storage index is addressed by a0. + kAddressAbsolute, + // The storage index is addressed by aL. + kAddressRelative, +}; + +// Describes the source value of a particular component. +enum class SwizzleSource { + // Component receives the source X. + kX, + // Component receives the source Y. + kY, + // Component receives the source Z. + kZ, + // Component receives the source W. + kW, + // Component receives constant 0. + k0, + // Component receives constant 1. + k1, +}; + +constexpr SwizzleSource GetSwizzleFromComponentIndex(int i) { + return static_cast(i); +} +inline char GetCharForComponentIndex(int 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(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; + // 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 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 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; + } + // True if the components are in their 'standard' swizzle arrangement (xyzw). + bool is_standard_swizzle() const { + return has_all_writes() && components[0] == SwizzleSource::kX && + components[1] == SwizzleSource::kY && + components[2] == SwizzleSource::kZ && + components[3] == SwizzleSource::kW; + } +}; + +enum class InstructionStorageSource { + // Source is stored in a temporary register indexed by storage_index [0-31]. + kRegister, + // Source is stored in a float constant indexed by storage_index [0-511]. + kConstantFloat, + // Source is stored in a float constant indexed by storage_index [0-31]. + kConstantInt, + // Source is stored in a float constant indexed by storage_index [0-255]. + kConstantBool, + // Source is stored in a vertex fetch constant indexed by storage_index + // [0-95]. + kVertexFetchConstant, + // Source is stored in a texture fetch constant indexed by storage_index + // [0-31]. + kTextureFetchConstant, +}; + +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; + // How the storage index is dynamically addressed, if it is. + InstructionStorageAddressingMode storage_addressing_mode = + InstructionStorageAddressingMode::kStatic; + // True to negate the operand value. + bool is_negated = false; + // 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; + // Defines the source for each component xyzw (up to the given + // component_count). + SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY, + SwizzleSource::kZ, SwizzleSource::kW}; + // True if the components are in their 'standard' swizzle arrangement (xyzw). + bool is_standard_swizzle() const { + switch (component_count) { + case 4: + return components[0] == SwizzleSource::kX && + components[1] == SwizzleSource::kY && + components[2] == SwizzleSource::kZ && + components[3] == SwizzleSource::kW; + } + return false; + } +}; + +struct ParsedExecInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Opcode for the instruction. + ucode::ControlFlowOpcode opcode; + // Friendly name of the instruction. + const char* opcode_name = nullptr; + + // Instruction address where ALU/fetch instructions reside. + uint32_t instruction_address = 0; + // Number of instructions to execute. + uint32_t instruction_count = 0; + + enum class Type { + // Block is always executed. + kUnconditional, + // Execution is conditional on the value of the boolean constant. + kConditional, + // Execution is predicated. + kPredicated, + }; + // Condition required to execute the instructions. + Type type = Type::kUnconditional; + // Constant index used as the conditional if kConditional. + uint32_t bool_constant_index = 0; + // Required condition value of the comparision (true or false). + bool condition = false; + + // Whether to reset the current predicate. + bool clean = true; + // ? + bool is_yield = false; + + // Sequence bits, 2 per instruction, indicating whether ALU or fetch. + uint32_t sequence = 0; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedLoopStartInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Integer constant register that holds the loop parameters. + // Byte-wise: [loop count, start, step [-128, 127], ?] + uint32_t loop_constant_index = 0; + // Whether to reuse the current aL instead of reset it to loop start. + bool is_repeat = false; + + // Target address to jump to when skipping the loop. + uint32_t loop_skip_address = 0; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedLoopEndInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Break from the loop if the predicate matches the expected value. + bool is_predicated_break = false; + // Required condition value of the comparision (true or false). + bool predicate_condition = false; + + // Integer constant register that holds the loop parameters. + // Byte-wise: [loop count, start, step [-128, 127], ?] + uint32_t loop_constant_index = 0; + + // Target address of the start of the loop body. + uint32_t loop_body_address = 0; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedCallInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Target address. + uint32_t target_address = 0; + + enum class Type { + // Call is always made. + kUnconditional, + // Call is conditional on the value of the boolean constant. + kConditional, + // Call is predicated. + kPredicated, + }; + // Condition required to make the call. + Type type = Type::kUnconditional; + // Constant index used as the conditional if kConditional. + uint32_t bool_constant_index = 0; + // Required condition value of the comparision (true or false). + bool condition = false; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedReturnInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedJumpInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // Target address. + uint32_t target_address = 0; + + enum class Type { + // Jump is always taken. + kUnconditional, + // Jump is conditional on the value of the boolean constant. + kConditional, + // Jump is predicated. + kPredicated, + }; + // Condition required to make the jump. + Type type = Type::kUnconditional; + // Constant index used as the conditional if kConditional. + uint32_t bool_constant_index = 0; + // Required condition value of the comparision (true or false). + bool condition = false; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedAllocInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + // The type of resource being allocated. + ucode::AllocType type = ucode::AllocType::kNone; + // Total count associated with the allocation. + int count = 0; + + // True if this allocation is in a vertex shader. + bool is_vertex_shader = false; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +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. + const char* opcode_name = nullptr; + + // True if the fetch is reusing a previous full fetch. + // The previous fetch source and constant data will be populated. + bool is_mini_fetch = false; + + // True if the instruction is predicated on the specified + // predicate_condition. + bool is_predicated = false; + // Expected predication condition value if predicated. + bool predicate_condition = false; + + // Describes how the instruction result is stored. + InstructionResult result; + + // Number of source operands. + size_t operand_count = 0; + // Describes each source operand. + InstructionOperand operands[2]; + + struct Attributes { + VertexFormat data_format = VertexFormat::kUndefined; + int offset = 0; + int stride = 0; // In dwords. + int exp_adjust = 0; + bool is_index_rounded = false; + bool is_signed = false; + bool is_integer = false; + int prefetch_count = 0; + }; + // Attributes describing the fetch operation. + Attributes attributes; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +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. + const char* opcode_name = nullptr; + // Texture dimension for opcodes that have multiple dimension forms. + TextureDimension dimension = TextureDimension::k1D; + + // True if the instruction is predicated on the specified + // predicate_condition. + bool is_predicated = false; + // Expected predication condition value if predicated. + bool predicate_condition = false; + + // True if the instruction has a result. + bool has_result() const { + return result.storage_target != InstructionStorageTarget::kNone; + } + // Describes how the instruction result is stored. + InstructionResult result; + + // Number of source operands. + size_t operand_count = 0; + // Describes each source operand. + InstructionOperand operands[2]; + + struct Attributes { + bool fetch_valid_only = true; + bool unnormalized_coordinates = false; + TextureFilter mag_filter = TextureFilter::kUseFetchConst; + TextureFilter min_filter = TextureFilter::kUseFetchConst; + TextureFilter mip_filter = TextureFilter::kUseFetchConst; + AnisoFilter aniso_filter = AnisoFilter::kUseFetchConst; + bool use_computed_lod = true; + bool use_register_lod = false; + bool use_register_gradients = false; + float offset_x = 0.0f; + float offset_y = 0.0f; + float offset_z = 0.0f; + }; + // Attributes describing the fetch operation. + Attributes attributes; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + +struct ParsedAluInstruction { + // Index into the ucode dword source. + uint32_t dword_index = 0; + + enum class Type { + kNop, + kVector, + kScalar, + }; + // Type of the instruction. + Type type = Type::kNop; + bool is_nop() const { return type == Type::kNop; } + bool is_vector_type() const { return type == Type::kVector; } + bool is_scalar_type() const { return type == Type::kScalar; } + // Opcode for the instruction if it is a vector type. + ucode::AluVectorOpcode vector_opcode = ucode::AluVectorOpcode::kAdd; + // Opcode for the instruction if it is a scalar type. + ucode::AluScalarOpcode scalar_opcode = ucode::AluScalarOpcode::kAdds; + // Friendly name of the instruction. + const char* opcode_name = nullptr; + + // True if the instruction is paired with another instruction. + bool is_paired = false; + // True if the instruction is predicated on the specified + // predicate_condition. + bool is_predicated = false; + // Expected predication condition value if predicated. + bool predicate_condition = false; + + // Describes how the instruction result is stored. + InstructionResult result; + + // Number of source operands. + size_t operand_count = 0; + // Describes each source operand. + InstructionOperand operands[3]; + + // Disassembles the instruction into ucode assembly text. + void Disassemble(StringBuffer* out) const; +}; + class Shader { public: + struct Error { + bool is_fatal = false; + std::string message; + }; + + 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. + int binding_index; + // Fetch constant index [0-95]. + uint32_t fetch_constant; + // Stride of the entire binding, in words. + uint32_t stride_words; + // Packed attributes within the binding buffer. + std::vector attributes; + }; + + struct TextureBinding { + // Index within the texture binding listing. + size_t binding_index; + // Fetch constant index [0-31]. + uint32_t fetch_constant; + // Fetch instruction with all parameters. + ParsedTextureFetchInstruction fetch_instr; + }; + + Shader(ShaderType shader_type, uint64_t ucode_data_hash, + const uint32_t* ucode_dwords, size_t ucode_dword_count); virtual ~Shader(); + // Whether the shader is identified as a vertex or pixel shader. ShaderType type() const { return shader_type_; } - bool is_valid() const { return !!translated_shader_; } - const std::string& host_disassembly() const { return host_disassembly_; } - TranslatedShader* translated_shader() const { - return translated_shader_.get(); + + // Microcode dwords in host endianness. + const std::vector& ucode_data() const { return ucode_data_; } + const uint32_t* ucode_dwords() const { return ucode_data_.data(); } + size_t ucode_dword_count() const { return ucode_data_.size(); } + + // All vertex bindings used in the shader. + // Valid for vertex shaders only. + const std::vector& vertex_bindings() const { + return vertex_bindings_; } - const uint32_t* data() const { return data_.data(); } - uint32_t dword_count() const { return uint32_t(data_.size()); } + // All texture bindings used in the shader. + // Valid for both vertex and pixel shaders. + const std::vector& texture_bindings() const { + return texture_bindings_; + } - virtual bool Prepare(ShaderTranslator* shader_translator); + // Returns true if the given color target index [0-3]. + bool writes_color_target(int i) const { return writes_color_targets_[i]; } + + // True if the shader was translated and prepared without error. + bool is_valid() const { return is_valid_; } + + // Errors that occurred during translation. + const std::vector& errors() const { return errors_; } + + // Microcode disassembly in D3D format. + const std::string& ucode_disassembly() const { return ucode_disassembly_; } + + // Translated shader binary (or text). + const std::vector& translated_binary() const { + return translated_binary_; + } + + // Gets the translated shader binary as a string. + // This is only valid if it is actually text. + std::string GetTranslatedBinaryString() const; + + // Disassembly of the translated from the host graphics layer. + // May be empty if the host does not support disassembly. + const std::string& host_disassembly() const { return host_disassembly_; } + // A lot of errors that occurred during preparation of the host shader. + const std::string& host_error_log() const { return host_error_log_; } + // Host binary that can be saved and reused across runs. + // May be empty if the host does not support saving binaries. + const std::vector& host_binary() const { return host_binary_; } + + // Prepares the shader for use in the host graphics API. + virtual bool Prepare() { return is_valid_; } + + // Dumps the shader to a file in the given path based on ucode hash. + // Both the ucode binary and disassembled and translated shader will be + // written. + void Dump(const std::string& base_path, const char* path_prefix); protected: - Shader(ShaderType shader_type, uint64_t data_hash, const uint32_t* dword_ptr, - uint32_t dword_count); + friend class ShaderTranslator; ShaderType shader_type_; - uint64_t data_hash_; - std::vector data_; + std::vector ucode_data_; + uint64_t ucode_data_hash_; - std::string translated_disassembly_; + std::vector vertex_bindings_; + std::vector texture_bindings_; + bool writes_color_targets_[4] = {false, false, false, false}; + + bool is_valid_ = false; + std::vector errors_; + + std::string ucode_disassembly_; std::vector translated_binary_; std::string host_disassembly_; - std::string error_log_; - - std::unique_ptr translated_shader_; + std::string host_error_log_; + std::vector host_binary_; }; } // namespace gpu diff --git a/src/xenia/gpu/shader_compiler_main.cc b/src/xenia/gpu/shader_compiler_main.cc index 62e5b53cf..dd0657bfb 100644 --- a/src/xenia/gpu/shader_compiler_main.cc +++ b/src/xenia/gpu/shader_compiler_main.cc @@ -79,6 +79,11 @@ int shader_compiler_main(const std::vector& args) { shader_type == ShaderType::kVertex ? "vertex" : "pixel", ucode_dwords.size(), ucode_dwords.size() * 4); + // TODO(benvanik): hash? need to return the data to big-endian format first. + uint64_t ucode_data_hash = 0; + auto shader = std::make_unique( + shader_type, ucode_data_hash, ucode_dwords.data(), ucode_dwords.size()); + std::unique_ptr translator; if (FLAGS_shader_output_type == "spirv" || FLAGS_shader_output_type == "spirvtext") { @@ -90,13 +95,10 @@ int shader_compiler_main(const std::vector& args) { translator = std::make_unique(); } - // TODO(benvanik): hash? need to return the data to big-endian format first. - uint64_t ucode_data_hash = 0; - auto translated_shader = translator->Translate( - shader_type, ucode_data_hash, ucode_dwords.data(), ucode_dwords.size()); + translator->Translate(shader.get()); - const void* source_data = translated_shader->binary().data(); - size_t source_data_size = translated_shader->binary().size(); + const void* source_data = shader->translated_binary().data(); + size_t source_data_size = shader->translated_binary().size(); std::unique_ptr spirv_disasm_result; if (FLAGS_shader_output_type == "spirvtext") { diff --git a/src/xenia/gpu/shader_translator.cc b/src/xenia/gpu/shader_translator.cc index 272c31b47..719cf451c 100644 --- a/src/xenia/gpu/shader_translator.cc +++ b/src/xenia/gpu/shader_translator.cc @@ -40,36 +40,6 @@ using namespace ucode; // Lots of naming comes from the disassembly spit out by the XNA GS compiler // and dumps of d3dcompiler and games: http://pastebin.com/i4kAv7bB -TranslatedShader::TranslatedShader(ShaderType shader_type, - uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, - size_t ucode_dword_count, - std::vector errors) - : shader_type_(shader_type), - ucode_data_hash_(ucode_data_hash), - errors_(std::move(errors)) { - ucode_data_.resize(ucode_dword_count); - std::memcpy(ucode_data_.data(), ucode_dwords, - ucode_dword_count * sizeof(uint32_t)); - - is_valid_ = true; - for (const auto& error : errors_) { - if (error.is_fatal) { - is_valid_ = false; - break; - } - } -} - -TranslatedShader::~TranslatedShader() = default; - -std::string TranslatedShader::GetBinaryString() const { - std::string result; - result.resize(binary_.size()); - std::memcpy(const_cast(result.data()), binary_.data(), binary_.size()); - return result; -} - ShaderTranslator::ShaderTranslator() = default; ShaderTranslator::~ShaderTranslator() = default; @@ -87,14 +57,12 @@ void ShaderTranslator::Reset() { } } -std::unique_ptr ShaderTranslator::Translate( - ShaderType shader_type, uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, size_t ucode_dword_count) { +bool ShaderTranslator::Translate(Shader* shader) { Reset(); - shader_type_ = shader_type; - ucode_dwords_ = ucode_dwords; - ucode_dword_count_ = ucode_dword_count; + shader_type_ = shader->type(); + ucode_dwords_ = shader->ucode_dwords(); + ucode_dword_count_ = shader->ucode_dword_count(); // Run through and gather all binding information. // Translators may need this before they start codegen. @@ -119,17 +87,24 @@ std::unique_ptr ShaderTranslator::Translate( TranslateBlocks(); - std::unique_ptr translated_shader( - new TranslatedShader(shader_type, ucode_data_hash, ucode_dwords, - ucode_dword_count, std::move(errors_))); - translated_shader->binary_ = CompleteTranslation(); - translated_shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string(); - translated_shader->vertex_bindings_ = std::move(vertex_bindings_); - translated_shader->texture_bindings_ = std::move(texture_bindings_); + shader->errors_ = std::move(errors_); + shader->translated_binary_ = CompleteTranslation(); + shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string(); + shader->vertex_bindings_ = std::move(vertex_bindings_); + shader->texture_bindings_ = std::move(texture_bindings_); for (size_t i = 0; i < xe::countof(writes_color_targets_); ++i) { - translated_shader->writes_color_targets_[i] = writes_color_targets_[i]; + shader->writes_color_targets_[i] = writes_color_targets_[i]; } - return translated_shader; + + shader->is_valid_ = true; + for (const auto& error : shader->errors_) { + if (error.is_fatal) { + shader->is_valid_ = false; + break; + } + } + + return shader->is_valid_; } void ShaderTranslator::MarkUcodeInstruction(uint32_t dword_offset) { @@ -159,7 +134,7 @@ void ShaderTranslator::AppendUcodeDisasmFormat(const char* format, ...) { } void ShaderTranslator::EmitTranslationError(const char* message) { - TranslatedShader::Error error; + Shader::Error error; error.is_fatal = true; error.message = message; // TODO(benvanik): location information. @@ -167,7 +142,7 @@ void ShaderTranslator::EmitTranslationError(const char* message) { } void ShaderTranslator::EmitUnimplementedTranslationError() { - TranslatedShader::Error error; + Shader::Error error; error.is_fatal = true; error.message = "Unimplemented translation"; // TODO(benvanik): location information. @@ -195,6 +170,7 @@ void ShaderTranslator::GatherBindingInformation( auto fetch_opcode = static_cast(ucode_dwords_[instr_offset * 3] & 0x1F); if (fetch_opcode == FetchOpcode::kVertexFetch) { + assert_true(is_vertex_shader()); GatherVertexBindingInformation( *reinterpret_cast( ucode_dwords_ + instr_offset * 3)); @@ -231,7 +207,7 @@ void ShaderTranslator::GatherVertexBindingInformation( // Try to allocate an attribute on an existing binding. // If no binding for this fetch slot is found create it. - using VertexBinding = TranslatedShader::VertexBinding; + using VertexBinding = Shader::VertexBinding; VertexBinding::Attribute* attrib = nullptr; for (auto& vertex_binding : vertex_bindings_) { if (vertex_binding.fetch_constant == op.fetch_constant_index()) { @@ -244,7 +220,7 @@ void ShaderTranslator::GatherVertexBindingInformation( } if (!attrib) { assert_not_zero(op.stride()); - TranslatedShader::VertexBinding vertex_binding; + VertexBinding vertex_binding; vertex_binding.binding_index = static_cast(vertex_bindings_.size()); vertex_binding.fetch_constant = op.fetch_constant_index(); vertex_binding.stride_words = op.stride(); @@ -269,7 +245,7 @@ void ShaderTranslator::GatherTextureBindingInformation( // Doesn't use bindings. return; } - TranslatedShader::TextureBinding binding; + Shader::TextureBinding binding; binding.binding_index = texture_bindings_.size(); ParseTextureFetchInstruction(op, &binding.fetch_instr); binding.fetch_constant = binding.fetch_instr.operands[1].storage_index; diff --git a/src/xenia/gpu/shader_translator.h b/src/xenia/gpu/shader_translator.h index 5b519475b..5766b5d40 100644 --- a/src/xenia/gpu/shader_translator.h +++ b/src/xenia/gpu/shader_translator.h @@ -15,537 +15,18 @@ #include #include "xenia/base/string_buffer.h" +#include "xenia/gpu/shader.h" #include "xenia/gpu/ucode.h" #include "xenia/gpu/xenos.h" namespace xe { namespace gpu { -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 to the position export (gl_Position). - kPosition, - // Result is stored to the point size export (gl_PointSize). - kPointSize, - // Result is stored to a color target export indexed by storage_index [0-3]. - kColorTarget, - // Result is stored to the depth export (gl_FragDepth). - kDepth, -}; - -enum class InstructionStorageAddressingMode { - // The storage index is not dynamically addressed. - kStatic, - // The storage index is addressed by a0. - kAddressAbsolute, - // The storage index is addressed by aL. - kAddressRelative, -}; - -// Describes the source value of a particular component. -enum class SwizzleSource { - // Component receives the source X. - kX, - // Component receives the source Y. - kY, - // Component receives the source Z. - kZ, - // Component receives the source W. - kW, - // Component receives constant 0. - k0, - // Component receives constant 1. - k1, -}; - -constexpr SwizzleSource GetSwizzleFromComponentIndex(int i) { - return static_cast(i); -} -inline char GetCharForComponentIndex(int 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(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; - // 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 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 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; - } - // True if the components are in their 'standard' swizzle arrangement (xyzw). - bool is_standard_swizzle() const { - return has_all_writes() && components[0] == SwizzleSource::kX && - components[1] == SwizzleSource::kY && - components[2] == SwizzleSource::kZ && - components[3] == SwizzleSource::kW; - } -}; - -enum class InstructionStorageSource { - // Source is stored in a temporary register indexed by storage_index [0-31]. - kRegister, - // Source is stored in a float constant indexed by storage_index [0-511]. - kConstantFloat, - // Source is stored in a float constant indexed by storage_index [0-31]. - kConstantInt, - // Source is stored in a float constant indexed by storage_index [0-255]. - kConstantBool, - // Source is stored in a vertex fetch constant indexed by storage_index - // [0-95]. - kVertexFetchConstant, - // Source is stored in a texture fetch constant indexed by storage_index - // [0-31]. - kTextureFetchConstant, -}; - -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; - // How the storage index is dynamically addressed, if it is. - InstructionStorageAddressingMode storage_addressing_mode = - InstructionStorageAddressingMode::kStatic; - // True to negate the operand value. - bool is_negated = false; - // 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; - // Defines the source for each component xyzw (up to the given - // component_count). - SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY, - SwizzleSource::kZ, SwizzleSource::kW}; - // True if the components are in their 'standard' swizzle arrangement (xyzw). - bool is_standard_swizzle() const { - switch (component_count) { - case 4: - return components[0] == SwizzleSource::kX && - components[1] == SwizzleSource::kY && - components[2] == SwizzleSource::kZ && - components[3] == SwizzleSource::kW; - } - return false; - } -}; - -struct ParsedExecInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Opcode for the instruction. - ucode::ControlFlowOpcode opcode; - // Friendly name of the instruction. - const char* opcode_name = nullptr; - - // Instruction address where ALU/fetch instructions reside. - uint32_t instruction_address = 0; - // Number of instructions to execute. - uint32_t instruction_count = 0; - - enum class Type { - // Block is always executed. - kUnconditional, - // Execution is conditional on the value of the boolean constant. - kConditional, - // Execution is predicated. - kPredicated, - }; - // Condition required to execute the instructions. - Type type = Type::kUnconditional; - // Constant index used as the conditional if kConditional. - uint32_t bool_constant_index = 0; - // Required condition value of the comparision (true or false). - bool condition = false; - - // Whether to reset the current predicate. - bool clean = true; - // ? - bool is_yield = false; - - // Sequence bits, 2 per instruction, indicating whether ALU or fetch. - uint32_t sequence = 0; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedLoopStartInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Integer constant register that holds the loop parameters. - // Byte-wise: [loop count, start, step [-128, 127], ?] - uint32_t loop_constant_index = 0; - // Whether to reuse the current aL instead of reset it to loop start. - bool is_repeat = false; - - // Target address to jump to when skipping the loop. - uint32_t loop_skip_address = 0; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedLoopEndInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Break from the loop if the predicate matches the expected value. - bool is_predicated_break = false; - // Required condition value of the comparision (true or false). - bool predicate_condition = false; - - // Integer constant register that holds the loop parameters. - // Byte-wise: [loop count, start, step [-128, 127], ?] - uint32_t loop_constant_index = 0; - - // Target address of the start of the loop body. - uint32_t loop_body_address = 0; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedCallInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Target address. - uint32_t target_address = 0; - - enum class Type { - // Call is always made. - kUnconditional, - // Call is conditional on the value of the boolean constant. - kConditional, - // Call is predicated. - kPredicated, - }; - // Condition required to make the call. - Type type = Type::kUnconditional; - // Constant index used as the conditional if kConditional. - uint32_t bool_constant_index = 0; - // Required condition value of the comparision (true or false). - bool condition = false; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedReturnInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedJumpInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // Target address. - uint32_t target_address = 0; - - enum class Type { - // Jump is always taken. - kUnconditional, - // Jump is conditional on the value of the boolean constant. - kConditional, - // Jump is predicated. - kPredicated, - }; - // Condition required to make the jump. - Type type = Type::kUnconditional; - // Constant index used as the conditional if kConditional. - uint32_t bool_constant_index = 0; - // Required condition value of the comparision (true or false). - bool condition = false; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedAllocInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - // The type of resource being allocated. - ucode::AllocType type = ucode::AllocType::kNone; - // Total count associated with the allocation. - int count = 0; - - // True if this allocation is in a vertex shader. - bool is_vertex_shader = false; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -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. - const char* opcode_name = nullptr; - - // True if the fetch is reusing a previous full fetch. - // The previous fetch source and constant data will be populated. - bool is_mini_fetch = false; - - // True if the instruction is predicated on the specified - // predicate_condition. - bool is_predicated = false; - // Expected predication condition value if predicated. - bool predicate_condition = false; - - // Describes how the instruction result is stored. - InstructionResult result; - - // Number of source operands. - size_t operand_count = 0; - // Describes each source operand. - InstructionOperand operands[2]; - - struct Attributes { - VertexFormat data_format = VertexFormat::kUndefined; - int offset = 0; - int stride = 0; // In dwords. - int exp_adjust = 0; - bool is_index_rounded = false; - bool is_signed = false; - bool is_integer = false; - int prefetch_count = 0; - }; - // Attributes describing the fetch operation. - Attributes attributes; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -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. - const char* opcode_name = nullptr; - // Texture dimension for opcodes that have multiple dimension forms. - TextureDimension dimension = TextureDimension::k1D; - - // True if the instruction is predicated on the specified - // predicate_condition. - bool is_predicated = false; - // Expected predication condition value if predicated. - bool predicate_condition = false; - - // True if the instruction has a result. - bool has_result() const { - return result.storage_target != InstructionStorageTarget::kNone; - } - // Describes how the instruction result is stored. - InstructionResult result; - - // Number of source operands. - size_t operand_count = 0; - // Describes each source operand. - InstructionOperand operands[2]; - - struct Attributes { - bool fetch_valid_only = true; - bool unnormalized_coordinates = false; - TextureFilter mag_filter = TextureFilter::kUseFetchConst; - TextureFilter min_filter = TextureFilter::kUseFetchConst; - TextureFilter mip_filter = TextureFilter::kUseFetchConst; - AnisoFilter aniso_filter = AnisoFilter::kUseFetchConst; - bool use_computed_lod = true; - bool use_register_lod = false; - bool use_register_gradients = false; - float offset_x = 0.0f; - float offset_y = 0.0f; - float offset_z = 0.0f; - }; - // Attributes describing the fetch operation. - Attributes attributes; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -struct ParsedAluInstruction { - // Index into the ucode dword source. - uint32_t dword_index = 0; - - enum class Type { - kNop, - kVector, - kScalar, - }; - // Type of the instruction. - Type type = Type::kNop; - bool is_nop() const { return type == Type::kNop; } - bool is_vector_type() const { return type == Type::kVector; } - bool is_scalar_type() const { return type == Type::kScalar; } - // Opcode for the instruction if it is a vector type. - ucode::AluVectorOpcode vector_opcode = ucode::AluVectorOpcode::kAdd; - // Opcode for the instruction if it is a scalar type. - ucode::AluScalarOpcode scalar_opcode = ucode::AluScalarOpcode::kAdds; - // Friendly name of the instruction. - const char* opcode_name = nullptr; - - // True if the instruction is paired with another instruction. - bool is_paired = false; - // True if the instruction is predicated on the specified - // predicate_condition. - bool is_predicated = false; - // Expected predication condition value if predicated. - bool predicate_condition = false; - - // Describes how the instruction result is stored. - InstructionResult result; - - // Number of source operands. - size_t operand_count = 0; - // Describes each source operand. - InstructionOperand operands[3]; - - // Disassembles the instruction into ucode assembly text. - void Disassemble(StringBuffer* out) const; -}; - -class TranslatedShader { - public: - struct Error { - bool is_fatal = false; - std::string message; - }; - - 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. - int binding_index; - // Fetch constant index [0-95]. - uint32_t fetch_constant; - // Stride of the entire binding, in words. - uint32_t stride_words; - // Packed attributes within the binding buffer. - std::vector attributes; - }; - - struct TextureBinding { - // Index within the texture binding listing. - size_t binding_index; - // Fetch constant index [0-31]. - uint32_t fetch_constant; - // Fetch instruction with all parameters. - ParsedTextureFetchInstruction fetch_instr; - }; - - ~TranslatedShader(); - - ShaderType type() const { return shader_type_; } - const std::vector& ucode_data() const { return ucode_data_; } - const uint32_t* ucode_dwords() const { return ucode_data_.data(); } - size_t ucode_dword_count() const { return ucode_data_.size(); } - - const std::vector& vertex_bindings() const { - return vertex_bindings_; - } - const std::vector& texture_bindings() const { - return texture_bindings_; - } - // Returns true if the given color target index [0-3]. - bool writes_color_target(int i) const { return writes_color_targets_[i]; } - - bool is_valid() const { return is_valid_; } - const std::vector& errors() const { return errors_; } - - const std::vector& binary() const { return binary_; } - const std::string& ucode_disassembly() const { return ucode_disassembly_; } - - std::string GetBinaryString() const; - - private: - friend class ShaderTranslator; - - TranslatedShader(ShaderType shader_type, uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, size_t ucode_dword_count, - std::vector errors); - - ShaderType shader_type_; - std::vector ucode_data_; - uint64_t ucode_data_hash_; - - std::vector vertex_bindings_; - std::vector texture_bindings_; - bool writes_color_targets_[4] = {false, false, false, false}; - - bool is_valid_ = false; - std::vector errors_; - - std::string ucode_disassembly_; - std::vector binary_; -}; - class ShaderTranslator { public: virtual ~ShaderTranslator(); - std::unique_ptr Translate(ShaderType shader_type, - uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, - size_t ucode_dword_count); + bool Translate(Shader* shader); protected: ShaderTranslator(); @@ -558,12 +39,11 @@ class ShaderTranslator { // True if the current shader is a pixel shader. bool is_pixel_shader() const { return shader_type_ == ShaderType::kPixel; } // A list of all vertex bindings, populated before translation occurs. - const std::vector& vertex_bindings() const { + const std::vector& vertex_bindings() const { return vertex_bindings_; } // A list of all texture bindings, populated before translation occurs. - const std::vector& texture_bindings() - const { + const std::vector& texture_bindings() const { return texture_bindings_; } @@ -684,7 +164,7 @@ class ShaderTranslator { size_t ucode_dword_count_; // Accumulated translation errors. - std::vector errors_; + std::vector errors_; // Microcode disassembly buffer, accumulated throughout the translation. StringBuffer ucode_disasm_buffer_; @@ -698,8 +178,8 @@ class ShaderTranslator { // Detected binding information gathered before translation. int total_attrib_count_ = 0; - std::vector vertex_bindings_; - std::vector texture_bindings_; + std::vector vertex_bindings_; + std::vector texture_bindings_; bool writes_color_targets_[4] = {false, false, false, false}; static const AluOpcodeInfo alu_vector_opcode_infos_[0x20]; diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc index 59d0fc640..a68d2b907 100644 --- a/src/xenia/gpu/trace_viewer.cc +++ b/src/xenia/gpu/trace_viewer.cc @@ -486,11 +486,11 @@ void TraceViewer::DrawShaderUI(Shader* shader, ShaderDisplayType display_type) { switch (display_type) { case ShaderDisplayType::kUcode: { - DrawMultilineString(shader->translated_shader()->ucode_disassembly()); + DrawMultilineString(shader->ucode_disassembly()); break; } case ShaderDisplayType::kTranslated: { - const auto& str = shader->translated_shader()->GetBinaryString(); + const auto& str = shader->GetTranslatedBinaryString(); size_t i = 0; bool done = false; while (!done && i < str.size()) { @@ -567,7 +567,7 @@ void TraceViewer::DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, } void TraceViewer::DrawTextureInfo( - const TranslatedShader::TextureBinding& texture_binding) { + const Shader::TextureBinding& texture_binding) { auto& regs = *graphics_system_->register_file(); int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + @@ -633,15 +633,14 @@ void TraceViewer::DrawTextureInfo( } void TraceViewer::DrawFailedTextureInfo( - const TranslatedShader::TextureBinding& texture_binding, - const char* message) { + const Shader::TextureBinding& texture_binding, const char* message) { // TODO(benvanik): better error info/etc. ImGui::TextColored(kColorError, "ERROR: %s", message); } -void TraceViewer::DrawVertexFetcher( - Shader* shader, const TranslatedShader::VertexBinding& vertex_binding, - const xe_gpu_vertex_fetch_t* fetch) { +void TraceViewer::DrawVertexFetcher(Shader* shader, + const Shader::VertexBinding& vertex_binding, + const xe_gpu_vertex_fetch_t* fetch) { const uint8_t* addr = memory_->TranslatePhysical(fetch->address << 2); uint32_t vertex_count = (fetch->size * 4) / vertex_binding.stride_words; int column_count = 0; @@ -1411,9 +1410,7 @@ void TraceViewer::DrawStateUI() { if (ImGui::CollapsingHeader("Vertex Buffers")) { auto shader = command_processor->active_vertex_shader(); if (shader) { - const auto& vertex_bindings = - shader->translated_shader()->vertex_bindings(); - for (const auto& vertex_binding : vertex_bindings) { + for (const auto& vertex_binding : shader->vertex_bindings()) { int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + (vertex_binding.fetch_constant / 3) * 6; const auto group = @@ -1451,8 +1448,7 @@ void TraceViewer::DrawStateUI() { if (ImGui::CollapsingHeader("Vertex Textures")) { auto shader = command_processor->active_vertex_shader(); if (shader) { - const auto& texture_bindings = - shader->translated_shader()->texture_bindings(); + const auto& texture_bindings = shader->texture_bindings(); if (!texture_bindings.empty()) { for (const auto& texture_binding : texture_bindings) { DrawTextureInfo(texture_binding); @@ -1467,8 +1463,7 @@ void TraceViewer::DrawStateUI() { if (ImGui::CollapsingHeader("Textures")) { auto shader = command_processor->active_pixel_shader(); if (shader) { - const auto& texture_bindings = - shader->translated_shader()->texture_bindings(); + const auto& texture_bindings = shader->texture_bindings(); if (!texture_bindings.empty()) { for (const auto& texture_binding : texture_bindings) { DrawTextureInfo(texture_binding); diff --git a/src/xenia/gpu/trace_viewer.h b/src/xenia/gpu/trace_viewer.h index c76586141..f657565d6 100644 --- a/src/xenia/gpu/trace_viewer.h +++ b/src/xenia/gpu/trace_viewer.h @@ -85,13 +85,12 @@ class TraceViewer { void DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, uint32_t blend_op); - void DrawTextureInfo(const TranslatedShader::TextureBinding& texture_binding); - void DrawFailedTextureInfo( - const TranslatedShader::TextureBinding& texture_binding, - const char* message); + void DrawTextureInfo(const Shader::TextureBinding& texture_binding); + void DrawFailedTextureInfo(const Shader::TextureBinding& texture_binding, + const char* message); void DrawVertexFetcher(Shader* shader, - const TranslatedShader::VertexBinding& vertex_binding, + const Shader::VertexBinding& vertex_binding, const xenos::xe_gpu_vertex_fetch_t* fetch); }; diff --git a/tools/shader-playground/Editor.cs b/tools/shader-playground/Editor.cs index 2a1bc1dbd..e8f56bd42 100644 --- a/tools/shader-playground/Editor.cs +++ b/tools/shader-playground/Editor.cs @@ -361,25 +361,23 @@ namespace shader_playground { (shaderCode[6] << 8) | (shaderCode[7] << 0); int wordOffset = byteOffset / 4; - uint[] swappedCode = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)]; - Buffer.BlockCopy(shaderCode, wordOffset * 4, swappedCode, 0, shaderCode.Length - wordOffset * 4); - for (int i = 0; i < swappedCode.Length; ++i) { - swappedCode[i] = SwapBytes(swappedCode[i]); - } + uint[] shaderDwords = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)]; + Buffer.BlockCopy(shaderCode, wordOffset * 4, shaderDwords, 0, shaderCode.Length - wordOffset * 4); + var sb = new StringBuilder(); sb.Append("const uint32_t shader_dwords[] = {"); - for (int i = 0; i < swappedCode.Length; ++i) { - sb.AppendFormat("0x{0:X8}, ", swappedCode[i]); + for (int i = 0; i < shaderDwords.Length; ++i) { + sb.AppendFormat("0x{0:X8}, ", SwapByte(shaderDwords[i])); } sb.Append("};" + Environment.NewLine); sb.Append("shader_type = ShaderType::" + (shaderType == "vs" ? "kVertex" : "kPixel") + ";" + Environment.NewLine); UpdateTextBox(wordsTextBox, sb.ToString(), true); wordsTextBox.SelectAll(); - return swappedCode; + return shaderDwords; } - uint SwapBytes(uint x) { + uint SwapByte(uint x) { return ((x & 0x000000ff) << 24) + ((x & 0x0000ff00) << 8) + ((x & 0x00ff0000) >> 8) +