#include "shader_translator.h" /** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/shader_translator.h" #include #include #include #include "xenia/base/logging.h" #include "xenia/base/math.h" namespace xe { namespace gpu { using namespace ucode; // The Xbox 360 GPU is effectively an Adreno A200: // https://github.com/freedreno/freedreno/wiki/A2XX-Shader-Instruction-Set-Architecture // // A lot of this information is derived from the freedreno drivers, AMD's // documentation, publicly available Xbox presentations (from GDC/etc), and // other reverse engineering. // // Naming has been matched as closely as possible to the real thing by using the // publicly available XNA Game Studio shader assembler. // You can find a tool for exploring this under tools/shader-playground/, // allowing interative assembling/disassembling of shader code. // // Though the 360's GPU is similar to the Adreno r200, the microcode format is // slightly different. Though this is a great guide it cannot be assumed it // matches the 360 in all areas: // https://github.com/freedreno/freedreno/blob/master/util/disasm-a2xx.c // // Lots of naming comes from the disassembly spit out by the XNA GS compiler // and dumps of d3dcompiler and games: https://pastebin.com/i4kAv7bB ShaderTranslator::ShaderTranslator() = default; ShaderTranslator::~ShaderTranslator() = default; void ShaderTranslator::Reset() { errors_.clear(); ucode_disasm_buffer_.Reset(); ucode_disasm_line_number_ = 0; previous_ucode_disasm_scan_offset_ = 0; register_count_ = 64; total_attrib_count_ = 0; vertex_bindings_.clear(); unique_vertex_bindings_ = 0; texture_bindings_.clear(); unique_texture_bindings_ = 0; std::memset(&constant_register_map_, 0, sizeof(constant_register_map_)); uses_register_dynamic_addressing_ = false; for (size_t i = 0; i < xe::countof(writes_color_targets_); ++i) { writes_color_targets_[i] = false; } writes_depth_ = false; implicit_early_z_allowed_ = true; memexport_alloc_count_ = 0; memexport_eA_written_ = 0; std::memset(&memexport_eM_written_, 0, sizeof(memexport_eM_written_)); memexport_stream_constants_.clear(); } bool ShaderTranslator::GatherAllBindingInformation(Shader* shader) { // DEPRECATED: remove this codepath when GL4 goes away. Reset(); shader_type_ = shader->type(); ucode_dwords_ = shader->ucode_dwords(); ucode_dword_count_ = shader->ucode_dword_count(); uint32_t max_cf_dword_index = static_cast(ucode_dword_count_); for (uint32_t i = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; ControlFlowInstruction cf_b; UnpackControlFlowInstructions(ucode_dwords_ + i, &cf_a, &cf_b); if (IsControlFlowOpcodeExec(cf_a.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_a.exec.address() * 3); } if (IsControlFlowOpcodeExec(cf_b.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_b.exec.address() * 3); } GatherInstructionInformation(cf_a); GatherInstructionInformation(cf_b); } 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) { shader->writes_color_targets_[i] = writes_color_targets_[i]; } return true; } bool ShaderTranslator::Translate( Shader* shader, reg::SQ_PROGRAM_CNTL cntl, Shader::HostVertexShaderType host_vertex_shader_type) { Reset(); uint32_t cntl_num_reg = shader->type() == xenos::ShaderType::kVertex ? cntl.vs_num_reg : cntl.ps_num_reg; register_count_ = (cntl_num_reg & 0x80) ? 0 : (cntl_num_reg + 1); return TranslateInternal(shader, host_vertex_shader_type); } bool ShaderTranslator::Translate( Shader* shader, Shader::HostVertexShaderType host_vertex_shader_type) { Reset(); return TranslateInternal(shader, host_vertex_shader_type); } bool ShaderTranslator::TranslateInternal( Shader* shader, Shader::HostVertexShaderType host_vertex_shader_type) { shader_type_ = shader->type(); host_vertex_shader_type_ = host_vertex_shader_type; ucode_dwords_ = shader->ucode_dwords(); ucode_dword_count_ = shader->ucode_dword_count(); // Run through and gather all binding, operand addressing and export // information. Translators may need this before they start codegen. uint32_t max_cf_dword_index = static_cast(ucode_dword_count_); for (uint32_t i = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; ControlFlowInstruction cf_b; UnpackControlFlowInstructions(ucode_dwords_ + i, &cf_a, &cf_b); if (IsControlFlowOpcodeExec(cf_a.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_a.exec.address() * 3); } if (IsControlFlowOpcodeExec(cf_b.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_b.exec.address() * 3); } GatherInstructionInformation(cf_a); GatherInstructionInformation(cf_b); } if (constant_register_map_.float_dynamic_addressing) { // All potentially can be referenced. constant_register_map_.float_count = 256; memset(constant_register_map_.float_bitmap, UINT8_MAX, sizeof(constant_register_map_.float_bitmap)); } else { constant_register_map_.float_count = 0; for (int i = 0; i < 4; ++i) { // Each bit indicates a vec4 (4 floats). constant_register_map_.float_count += xe::bit_count(constant_register_map_.float_bitmap[i]); } } // Cleanup invalid/unneeded memexport allocs. for (uint32_t i = 0; i < kMaxMemExports; ++i) { if (!(memexport_eA_written_ & (uint32_t(1) << i))) { memexport_eM_written_[i] = 0; } else if (!memexport_eM_written_[i]) { memexport_eA_written_ &= ~(uint32_t(1) << i); } } if (memexport_eA_written_ == 0) { memexport_stream_constants_.clear(); } if (!memexport_stream_constants_.empty()) { // 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. implicit_early_z_allowed_ = false; } StartTranslation(); TranslateBlocks(); shader->errors_ = std::move(errors_); shader->translated_binary_ = CompleteTranslation(); shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string(); shader->host_vertex_shader_type_ = host_vertex_shader_type_; shader->vertex_bindings_ = std::move(vertex_bindings_); shader->texture_bindings_ = std::move(texture_bindings_); shader->constant_register_map_ = std::move(constant_register_map_); for (size_t i = 0; i < xe::countof(writes_color_targets_); ++i) { shader->writes_color_targets_[i] = writes_color_targets_[i]; } shader->writes_depth_ = writes_depth_; shader->implicit_early_z_allowed_ = implicit_early_z_allowed_; shader->memexport_stream_constants_.clear(); for (uint32_t memexport_stream_constant : memexport_stream_constants_) { shader->memexport_stream_constants_.push_back(memexport_stream_constant); } shader->is_valid_ = true; shader->is_translated_ = true; for (const auto& error : shader->errors_) { if (error.is_fatal) { shader->is_valid_ = false; break; } } PostTranslation(shader); return shader->is_valid_; } void ShaderTranslator::MarkUcodeInstruction(uint32_t dword_offset) { auto disasm = ucode_disasm_buffer_.buffer(); size_t current_offset = ucode_disasm_buffer_.length(); for (size_t i = previous_ucode_disasm_scan_offset_; i < current_offset; ++i) { if (disasm[i] == '\n') { ++ucode_disasm_line_number_; } } previous_ucode_disasm_scan_offset_ = current_offset; } void ShaderTranslator::AppendUcodeDisasm(char c) { ucode_disasm_buffer_.Append(c); } void ShaderTranslator::AppendUcodeDisasm(const char* value) { ucode_disasm_buffer_.Append(value); } void ShaderTranslator::AppendUcodeDisasmFormat(const char* format, ...) { va_list va; va_start(va, format); ucode_disasm_buffer_.AppendVarargs(format, va); va_end(va); } void ShaderTranslator::EmitTranslationError(const char* message, bool is_fatal) { Shader::Error error; error.is_fatal = is_fatal; error.message = message; // TODO(benvanik): location information. errors_.push_back(std::move(error)); XELOGE("Shader translation {}error: {}", is_fatal ? "fatal " : "", message); } void ShaderTranslator::GatherInstructionInformation( const ControlFlowInstruction& cf) { uint32_t bool_constant_index = UINT32_MAX; switch (cf.opcode()) { case ControlFlowOpcode::kCondExec: case ControlFlowOpcode::kCondExecEnd: case ControlFlowOpcode::kCondExecPredClean: case ControlFlowOpcode::kCondExecPredCleanEnd: bool_constant_index = cf.cond_exec.bool_address(); break; case ControlFlowOpcode::kCondCall: if (!cf.cond_call.is_unconditional() && !cf.cond_call.is_predicated()) { bool_constant_index = cf.cond_call.bool_address(); } break; case ControlFlowOpcode::kCondJmp: if (!cf.cond_jmp.is_unconditional() && !cf.cond_jmp.is_predicated()) { bool_constant_index = cf.cond_jmp.bool_address(); } break; case ControlFlowOpcode::kLoopStart: constant_register_map_.loop_bitmap |= uint32_t(1) << cf.loop_start.loop_id(); break; case ControlFlowOpcode::kLoopEnd: constant_register_map_.loop_bitmap |= uint32_t(1) << cf.loop_end.loop_id(); break; case ControlFlowOpcode::kAlloc: if (cf.alloc.alloc_type() == AllocType::kMemory) { ++memexport_alloc_count_; } break; default: break; } if (bool_constant_index != UINT32_MAX) { constant_register_map_.bool_bitmap[bool_constant_index / 32] |= uint32_t(1) << (bool_constant_index % 32); } switch (cf.opcode()) { case ControlFlowOpcode::kExec: case ControlFlowOpcode::kExecEnd: case ControlFlowOpcode::kCondExec: case ControlFlowOpcode::kCondExecEnd: case ControlFlowOpcode::kCondExecPred: case ControlFlowOpcode::kCondExecPredEnd: case ControlFlowOpcode::kCondExecPredClean: case ControlFlowOpcode::kCondExecPredCleanEnd: { uint32_t sequence = cf.exec.sequence(); for (uint32_t instr_offset = cf.exec.address(); instr_offset < cf.exec.address() + cf.exec.count(); ++instr_offset, sequence >>= 2) { bool is_fetch = (sequence & 0x1) == 0x1; if (is_fetch) { // Gather vertex and texture fetches. auto fetch_opcode = static_cast(ucode_dwords_[instr_offset * 3] & 0x1F); if (fetch_opcode == FetchOpcode::kVertexFetch) { assert_true(is_vertex_shader()); GatherVertexFetchInformation( *reinterpret_cast( ucode_dwords_ + instr_offset * 3)); } else { GatherTextureFetchInformation( *reinterpret_cast( ucode_dwords_ + instr_offset * 3)); } } else { // Gather info needed for the translation pass because having such // state changed in the middle of translation may break things. Check // the comments for each specific variable set here to see usage // restrictions that can be assumed here (such as only marking exports // as written if the used write mask is non-empty). auto& op = *reinterpret_cast(ucode_dwords_ + instr_offset * 3); ParsedAluInstruction instr; ParseAluInstruction(op, instr); const auto& vector_opcode_info = alu_vector_opcode_infos_[uint32_t(op.vector_opcode())]; implicit_early_z_allowed_ &= !vector_opcode_info.disable_implicit_early_z; const auto& scalar_opcode_info = alu_scalar_opcode_infos_[uint32_t(op.scalar_opcode())]; implicit_early_z_allowed_ &= !scalar_opcode_info.disable_implicit_early_z; if (instr.vector_and_constant_result.storage_target != InstructionStorageTarget::kRegister || instr.scalar_result.storage_target != InstructionStorageTarget::kRegister) { // Export is done to vector_dest of the ucode instruction for both // vector and scalar operations - no need to check separately. assert_true(instr.vector_and_constant_result.storage_target == instr.scalar_result.storage_target && instr.vector_and_constant_result.storage_index == instr.scalar_result.storage_index); if (instr.vector_and_constant_result.GetUsedWriteMask() || instr.scalar_result.GetUsedWriteMask()) { InstructionStorageTarget export_target = instr.vector_and_constant_result.storage_target; uint32_t export_index = instr.vector_and_constant_result.storage_index; switch (export_target) { case InstructionStorageTarget::kExportAddress: // Store used memexport constants because CPU code needs // addresses and sizes, and also whether there have been // writes to eA and eM# for register allocation in shader // translator implementations. // eA is (hopefully) always written to using: // mad eA, r#, const0100, c# // (though there are some exceptions, shaders in Halo 3 for // some reason set eA to zeros, but the swizzle of the // constant is not .xyzw in this case, and they don't write to // eM#). if (memexport_alloc_count_ > 0 && memexport_alloc_count_ <= kMaxMemExports) { uint32_t memexport_stream_constant = instr.GetMemExportStreamConstant(); if (memexport_stream_constant != UINT32_MAX) { memexport_eA_written_ |= uint32_t(1) << (memexport_alloc_count_ - 1); memexport_stream_constants_.insert( memexport_stream_constant); } else { XELOGE( "ShaderTranslator::GatherInstructionInformation: " "Couldn't extract memexport stream constant index"); } } break; case InstructionStorageTarget::kExportData: if (memexport_alloc_count_ > 0 && memexport_alloc_count_ <= kMaxMemExports) { memexport_eM_written_[memexport_alloc_count_ - 1] |= uint32_t(1) << export_index; } break; case InstructionStorageTarget::kColor: writes_color_targets_[export_index] = true; break; case InstructionStorageTarget::kDepth: writes_depth_ = true; implicit_early_z_allowed_ = false; break; default: break; } } } else { if ((instr.vector_and_constant_result.GetUsedWriteMask() && instr.vector_and_constant_result.storage_addressing_mode != InstructionStorageAddressingMode::kStatic) || (instr.scalar_result.GetUsedWriteMask() && instr.scalar_result.storage_addressing_mode != InstructionStorageAddressingMode::kStatic)) { uses_register_dynamic_addressing_ = true; } } uint32_t total_operand_count = instr.vector_operand_count + instr.scalar_operand_count; for (uint32_t i = 0; i < total_operand_count; ++i) { const InstructionOperand& operand = (i < instr.vector_operand_count) ? instr.vector_operands[i] : instr.scalar_operands[i - instr.vector_operand_count]; if (operand.storage_source == InstructionStorageSource::kRegister) { if (operand.storage_addressing_mode != InstructionStorageAddressingMode::kStatic) { uses_register_dynamic_addressing_ = true; } } else if (operand.storage_source == InstructionStorageSource::kConstantFloat) { if (operand.storage_addressing_mode == InstructionStorageAddressingMode::kStatic) { // Store used float constants before translating so the // translator can use tightly packed indices if not dynamically // indexed. uint32_t constant_index = operand.storage_index; constant_register_map_.float_bitmap[constant_index / 64] |= uint64_t(1) << (constant_index % 64); } else { constant_register_map_.float_dynamic_addressing = true; } } } } } } break; default: break; } } void ShaderTranslator::GatherVertexFetchInformation( const VertexFetchInstruction& op) { ParsedVertexFetchInstruction fetch_instr; ParseVertexFetchInstruction(op, &fetch_instr); // Don't bother setting up a binding for an instruction that fetches nothing. if (!op.fetches_any_data()) { return; } // Check if using dynamic register indices. if (op.is_dest_relative() || op.is_src_relative()) { uses_register_dynamic_addressing_ = true; } // Try to allocate an attribute on an existing binding. // If no binding for this fetch slot is found create it. using VertexBinding = Shader::VertexBinding; VertexBinding::Attribute* attrib = nullptr; for (auto& vertex_binding : vertex_bindings_) { if (vertex_binding.fetch_constant == op.fetch_constant_index()) { // It may not hold that all strides are equal, but I hope it does. assert_true(!fetch_instr.attributes.stride || vertex_binding.stride_words == fetch_instr.attributes.stride); vertex_binding.attributes.push_back({}); attrib = &vertex_binding.attributes.back(); break; } } if (!attrib) { assert_not_zero(fetch_instr.attributes.stride); VertexBinding vertex_binding; vertex_binding.binding_index = int(vertex_bindings_.size()); vertex_binding.fetch_constant = op.fetch_constant_index(); vertex_binding.stride_words = fetch_instr.attributes.stride; vertex_binding.attributes.push_back({}); vertex_bindings_.emplace_back(std::move(vertex_binding)); attrib = &vertex_bindings_.back().attributes.back(); } // Populate attribute. attrib->attrib_index = total_attrib_count_++; attrib->fetch_instr = fetch_instr; attrib->size_words = xenos::GetVertexFormatSizeInWords( attrib->fetch_instr.attributes.data_format); } void ShaderTranslator::GatherTextureFetchInformation( const TextureFetchInstruction& op) { // Check if using dynamic register indices. if (op.is_dest_relative() || op.is_src_relative()) { uses_register_dynamic_addressing_ = true; } switch (op.opcode()) { case FetchOpcode::kSetTextureLod: case FetchOpcode::kSetTextureGradientsHorz: case FetchOpcode::kSetTextureGradientsVert: // Doesn't use bindings. return; default: // Continue. break; } Shader::TextureBinding binding; binding.binding_index = -1; ParseTextureFetchInstruction(op, &binding.fetch_instr); binding.fetch_constant = binding.fetch_instr.operands[1].storage_index; // Check and see if this fetch constant was previously used... for (auto& tex_binding : texture_bindings_) { if (tex_binding.fetch_constant == binding.fetch_constant) { binding.binding_index = tex_binding.binding_index; break; } } if (binding.binding_index == -1) { // Assign a unique binding index. binding.binding_index = unique_texture_bindings_++; } texture_bindings_.emplace_back(std::move(binding)); } void AddControlFlowTargetLabel(const ControlFlowInstruction& cf, std::set* label_addresses) { switch (cf.opcode()) { case ControlFlowOpcode::kLoopStart: label_addresses->insert(cf.loop_start.address()); break; case ControlFlowOpcode::kLoopEnd: label_addresses->insert(cf.loop_end.address()); break; case ControlFlowOpcode::kCondCall: label_addresses->insert(cf.cond_call.address()); break; case ControlFlowOpcode::kCondJmp: label_addresses->insert(cf.cond_jmp.address()); break; default: // Ignored. break; } } bool ShaderTranslator::TranslateBlocks() { // Control flow instructions come paired in blocks of 3 dwords and all are // listed at the top of the ucode. // Each control flow instruction is executed sequentially until the final // ending instruction. // Guess how long the control flow program is by scanning for the first // kExec-ish and instruction and using its address as the upper bound. // This is what freedreno does. uint32_t max_cf_dword_index = static_cast(ucode_dword_count_); std::set label_addresses; std::vector cf_instructions; for (uint32_t i = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; ControlFlowInstruction cf_b; UnpackControlFlowInstructions(ucode_dwords_ + i, &cf_a, &cf_b); if (IsControlFlowOpcodeExec(cf_a.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_a.exec.address() * 3); } if (IsControlFlowOpcodeExec(cf_b.opcode())) { max_cf_dword_index = std::min(max_cf_dword_index, cf_b.exec.address() * 3); } AddControlFlowTargetLabel(cf_a, &label_addresses); AddControlFlowTargetLabel(cf_b, &label_addresses); cf_instructions.push_back(cf_a); cf_instructions.push_back(cf_b); } PreProcessControlFlowInstructions(cf_instructions); // Translate all instructions. for (uint32_t i = 0, cf_index = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; ControlFlowInstruction cf_b; UnpackControlFlowInstructions(ucode_dwords_ + i, &cf_a, &cf_b); cf_index_ = cf_index; MarkUcodeInstruction(i); if (label_addresses.count(cf_index)) { AppendUcodeDisasmFormat(" label L%u\n", cf_index); ProcessLabel(cf_index); } AppendUcodeDisasmFormat("/* %4u.0 */ ", cf_index / 2); ProcessControlFlowInstructionBegin(cf_index); TranslateControlFlowInstruction(cf_a); ProcessControlFlowInstructionEnd(cf_index); ++cf_index; cf_index_ = cf_index; MarkUcodeInstruction(i); if (label_addresses.count(cf_index)) { AppendUcodeDisasmFormat(" label L%u\n", cf_index); ProcessLabel(cf_index); } AppendUcodeDisasmFormat("/* %4u.1 */ ", cf_index / 2); ProcessControlFlowInstructionBegin(cf_index); TranslateControlFlowInstruction(cf_b); ProcessControlFlowInstructionEnd(cf_index); ++cf_index; } return true; } std::vector UcodeShaderTranslator::CompleteTranslation() { return ucode_disasm_buffer().to_bytes(); } void ShaderTranslator::TranslateControlFlowInstruction( const ControlFlowInstruction& cf) { switch (cf.opcode()) { case ControlFlowOpcode::kNop: TranslateControlFlowNop(cf); break; case ControlFlowOpcode::kExec: TranslateControlFlowExec(cf.exec); break; case ControlFlowOpcode::kExecEnd: TranslateControlFlowExec(cf.exec); break; case ControlFlowOpcode::kCondExec: TranslateControlFlowCondExec(cf.cond_exec); break; case ControlFlowOpcode::kCondExecEnd: TranslateControlFlowCondExec(cf.cond_exec); break; case ControlFlowOpcode::kCondExecPred: TranslateControlFlowCondExecPred(cf.cond_exec_pred); break; case ControlFlowOpcode::kCondExecPredEnd: TranslateControlFlowCondExecPred(cf.cond_exec_pred); break; case ControlFlowOpcode::kCondExecPredClean: TranslateControlFlowCondExec(cf.cond_exec); break; case ControlFlowOpcode::kCondExecPredCleanEnd: TranslateControlFlowCondExec(cf.cond_exec); break; case ControlFlowOpcode::kLoopStart: TranslateControlFlowLoopStart(cf.loop_start); break; case ControlFlowOpcode::kLoopEnd: TranslateControlFlowLoopEnd(cf.loop_end); break; case ControlFlowOpcode::kCondCall: TranslateControlFlowCondCall(cf.cond_call); break; case ControlFlowOpcode::kReturn: TranslateControlFlowReturn(cf.ret); break; case ControlFlowOpcode::kCondJmp: TranslateControlFlowCondJmp(cf.cond_jmp); break; case ControlFlowOpcode::kAlloc: TranslateControlFlowAlloc(cf.alloc); break; case ControlFlowOpcode::kMarkVsFetchDone: break; default: assert_unhandled_case(cf.opcode); break; } bool ends_shader = DoesControlFlowOpcodeEndShader(cf.opcode()); if (ends_shader) { // TODO(benvanik): return? } } void ShaderTranslator::TranslateControlFlowNop( const ControlFlowInstruction& cf) { ucode_disasm_buffer_.Append(" cnop\n"); ProcessControlFlowNopInstruction(cf_index_); } void ShaderTranslator::TranslateControlFlowExec( const ControlFlowExecInstruction& cf) { ParsedExecInstruction i; i.dword_index = cf_index_; i.opcode = cf.opcode(); i.opcode_name = cf.opcode() == ControlFlowOpcode::kExecEnd ? "exece" : "exec"; i.instruction_address = cf.address(); i.instruction_count = cf.count(); i.type = ParsedExecInstruction::Type::kUnconditional; i.is_end = cf.opcode() == ControlFlowOpcode::kExecEnd; i.clean = cf.clean(); i.is_yield = cf.is_yield(); i.sequence = cf.sequence(); TranslateExecInstructions(i); } void ShaderTranslator::TranslateControlFlowCondExec( const ControlFlowCondExecInstruction& cf) { ParsedExecInstruction i; i.dword_index = cf_index_; i.opcode = cf.opcode(); i.opcode_name = "cexec"; switch (cf.opcode()) { case ControlFlowOpcode::kCondExecEnd: case ControlFlowOpcode::kCondExecPredCleanEnd: i.opcode_name = "cexece"; i.is_end = true; break; default: break; } i.instruction_address = cf.address(); i.instruction_count = cf.count(); i.type = ParsedExecInstruction::Type::kConditional; i.bool_constant_index = cf.bool_address(); assert_not_zero( constant_register_map_.bool_bitmap[i.bool_constant_index / 32] & (uint32_t(1) << (i.bool_constant_index % 32))); i.condition = cf.condition(); switch (cf.opcode()) { case ControlFlowOpcode::kCondExec: case ControlFlowOpcode::kCondExecEnd: i.clean = false; break; default: break; } i.is_yield = cf.is_yield(); i.sequence = cf.sequence(); TranslateExecInstructions(i); } void ShaderTranslator::TranslateControlFlowCondExecPred( const ControlFlowCondExecPredInstruction& cf) { ParsedExecInstruction i; i.dword_index = cf_index_; i.opcode = cf.opcode(); i.opcode_name = cf.opcode() == ControlFlowOpcode::kCondExecPredEnd ? "exece" : "exec"; i.instruction_address = cf.address(); i.instruction_count = cf.count(); i.type = ParsedExecInstruction::Type::kPredicated; i.condition = cf.condition(); i.is_end = cf.opcode() == ControlFlowOpcode::kCondExecPredEnd; i.clean = cf.clean(); i.is_yield = cf.is_yield(); i.sequence = cf.sequence(); TranslateExecInstructions(i); } void ShaderTranslator::TranslateControlFlowLoopStart( const ControlFlowLoopStartInstruction& cf) { ParsedLoopStartInstruction i; i.dword_index = cf_index_; i.loop_constant_index = cf.loop_id(); assert_not_zero(constant_register_map_.loop_bitmap & (uint32_t(1) << i.loop_constant_index)); i.is_repeat = cf.is_repeat(); i.loop_skip_address = cf.address(); i.Disassemble(&ucode_disasm_buffer_); ProcessLoopStartInstruction(i); } void ShaderTranslator::TranslateControlFlowLoopEnd( const ControlFlowLoopEndInstruction& cf) { ParsedLoopEndInstruction i; i.dword_index = cf_index_; i.is_predicated_break = cf.is_predicated_break(); i.predicate_condition = cf.condition(); i.loop_constant_index = cf.loop_id(); assert_not_zero(constant_register_map_.loop_bitmap & (uint32_t(1) << i.loop_constant_index)); i.loop_body_address = cf.address(); i.Disassemble(&ucode_disasm_buffer_); ProcessLoopEndInstruction(i); } void ShaderTranslator::TranslateControlFlowCondCall( const ControlFlowCondCallInstruction& cf) { ParsedCallInstruction i; i.dword_index = cf_index_; i.target_address = cf.address(); if (cf.is_unconditional()) { i.type = ParsedCallInstruction::Type::kUnconditional; } else if (cf.is_predicated()) { i.type = ParsedCallInstruction::Type::kPredicated; i.condition = cf.condition(); } else { i.type = ParsedCallInstruction::Type::kConditional; i.bool_constant_index = cf.bool_address(); assert_not_zero( constant_register_map_.bool_bitmap[i.bool_constant_index / 32] & (uint32_t(1) << (i.bool_constant_index % 32))); i.condition = cf.condition(); } i.Disassemble(&ucode_disasm_buffer_); ProcessCallInstruction(i); } void ShaderTranslator::TranslateControlFlowReturn( const ControlFlowReturnInstruction& cf) { ParsedReturnInstruction i; i.dword_index = cf_index_; i.Disassemble(&ucode_disasm_buffer_); ProcessReturnInstruction(i); } void ShaderTranslator::TranslateControlFlowCondJmp( const ControlFlowCondJmpInstruction& cf) { ParsedJumpInstruction i; i.dword_index = cf_index_; i.target_address = cf.address(); if (cf.is_unconditional()) { i.type = ParsedJumpInstruction::Type::kUnconditional; } else if (cf.is_predicated()) { i.type = ParsedJumpInstruction::Type::kPredicated; i.condition = cf.condition(); } else { i.type = ParsedJumpInstruction::Type::kConditional; i.bool_constant_index = cf.bool_address(); assert_not_zero( constant_register_map_.bool_bitmap[i.bool_constant_index / 32] & (uint32_t(1) << (i.bool_constant_index % 32))); i.condition = cf.condition(); } i.Disassemble(&ucode_disasm_buffer_); ProcessJumpInstruction(i); } void ShaderTranslator::TranslateControlFlowAlloc( const ControlFlowAllocInstruction& cf) { ParsedAllocInstruction i; i.dword_index = cf_index_; i.type = cf.alloc_type(); i.count = cf.size(); i.is_vertex_shader = is_vertex_shader(); i.Disassemble(&ucode_disasm_buffer_); ProcessAllocInstruction(i); } void ShaderTranslator::TranslateExecInstructions( const ParsedExecInstruction& instr) { instr.Disassemble(&ucode_disasm_buffer_); ProcessExecInstructionBegin(instr); uint32_t sequence = instr.sequence; for (uint32_t instr_offset = instr.instruction_address; instr_offset < instr.instruction_address + instr.instruction_count; ++instr_offset, sequence >>= 2) { MarkUcodeInstruction(instr_offset); AppendUcodeDisasmFormat("/* %4u */ ", instr_offset); bool is_sync = (sequence & 0x2) == 0x2; bool is_fetch = (sequence & 0x1) == 0x1; if (is_sync) { AppendUcodeDisasm(" serialize\n "); } if (is_fetch) { auto fetch_opcode = static_cast(ucode_dwords_[instr_offset * 3] & 0x1F); if (fetch_opcode == FetchOpcode::kVertexFetch) { auto& op = *reinterpret_cast( ucode_dwords_ + instr_offset * 3); TranslateVertexFetchInstruction(op); } else { auto& op = *reinterpret_cast( ucode_dwords_ + instr_offset * 3); TranslateTextureFetchInstruction(op); } } else { auto& op = *reinterpret_cast(ucode_dwords_ + instr_offset * 3); TranslateAluInstruction(op); } } ProcessExecInstructionEnd(instr); } void ParseFetchInstructionResult(uint32_t dest, uint32_t swizzle, bool is_relative, InstructionResult* out_result) { out_result->storage_target = InstructionStorageTarget::kRegister; out_result->storage_index = dest; out_result->is_clamped = false; out_result->storage_addressing_mode = is_relative ? InstructionStorageAddressingMode::kAddressRelative : InstructionStorageAddressingMode::kStatic; out_result->original_write_mask = 0b1111; for (int i = 0; i < 4; ++i) { switch (swizzle & 0x7) { case 4: case 6: out_result->components[i] = SwizzleSource::k0; break; case 5: out_result->components[i] = SwizzleSource::k1; break; case 7: out_result->original_write_mask &= ~uint32_t(1 << i); break; default: out_result->components[i] = GetSwizzleFromComponentIndex(swizzle & 0x3); } swizzle >>= 3; } } void ShaderTranslator::TranslateVertexFetchInstruction( const VertexFetchInstruction& op) { ParsedVertexFetchInstruction instr; ParseVertexFetchInstruction(op, &instr); instr.Disassemble(&ucode_disasm_buffer_); ProcessVertexFetchInstruction(instr); } void ShaderTranslator::ParseVertexFetchInstruction( const VertexFetchInstruction& op, ParsedVertexFetchInstruction* out_instr) { auto& i = *out_instr; i.opcode = FetchOpcode::kVertexFetch; i.opcode_name = op.is_mini_fetch() ? "vfetch_mini" : "vfetch_full"; i.is_mini_fetch = op.is_mini_fetch(); i.is_predicated = op.is_predicated(); i.predicate_condition = op.predicate_condition(); ParseFetchInstructionResult(op.dest(), op.dest_swizzle(), op.is_dest_relative(), &i.result); // Reuse previous vfetch_full if this is a mini. const auto& full_op = op.is_mini_fetch() ? previous_vfetch_full_ : op; auto& src_op = i.operands[i.operand_count++]; src_op.storage_source = InstructionStorageSource::kRegister; src_op.storage_index = full_op.src(); src_op.storage_addressing_mode = full_op.is_src_relative() ? InstructionStorageAddressingMode::kAddressRelative : InstructionStorageAddressingMode::kStatic; src_op.is_negated = false; src_op.is_absolute_value = false; src_op.component_count = 1; uint32_t swizzle = full_op.src_swizzle(); for (uint32_t j = 0; j < src_op.component_count; ++j, swizzle >>= 2) { src_op.components[j] = GetSwizzleFromComponentIndex(swizzle & 0x3); } auto& const_op = i.operands[i.operand_count++]; const_op.storage_source = InstructionStorageSource::kVertexFetchConstant; const_op.storage_index = full_op.fetch_constant_index(); i.attributes.data_format = op.data_format(); i.attributes.offset = op.offset(); i.attributes.stride = full_op.stride(); i.attributes.exp_adjust = op.exp_adjust(); i.attributes.prefetch_count = op.prefetch_count(); i.attributes.is_index_rounded = op.is_index_rounded(); i.attributes.is_signed = op.is_signed(); i.attributes.is_integer = !op.is_normalized(); i.attributes.signed_rf_mode = op.signed_rf_mode(); // Store for later use by mini fetches. if (!op.is_mini_fetch()) { previous_vfetch_full_ = op; } } void ShaderTranslator::TranslateTextureFetchInstruction( const TextureFetchInstruction& op) { ParsedTextureFetchInstruction instr; ParseTextureFetchInstruction(op, &instr); instr.Disassemble(&ucode_disasm_buffer_); ProcessTextureFetchInstruction(instr); } void ShaderTranslator::ParseTextureFetchInstruction( const TextureFetchInstruction& op, ParsedTextureFetchInstruction* out_instr) { struct TextureFetchOpcodeInfo { const char* name; bool has_dest; bool has_const; bool has_attributes; uint32_t override_component_count; } opcode_info; switch (op.opcode()) { case FetchOpcode::kTextureFetch: { static const char* kNames[] = {"tfetch1D", "tfetch2D", "tfetch3D", "tfetchCube"}; opcode_info = {kNames[static_cast(op.dimension())], true, true, true, 0}; } break; case FetchOpcode::kGetTextureBorderColorFrac: { static const char* kNames[] = {"getBCF1D", "getBCF2D", "getBCF3D", "getBCFCube"}; opcode_info = {kNames[static_cast(op.dimension())], true, true, true, 0}; } break; case FetchOpcode::kGetTextureComputedLod: { static const char* kNames[] = {"getCompTexLOD1D", "getCompTexLOD2D", "getCompTexLOD3D", "getCompTexLODCube"}; opcode_info = {kNames[static_cast(op.dimension())], true, true, true, 0}; } break; case FetchOpcode::kGetTextureGradients: opcode_info = {"getGradients", true, true, true, 2}; break; case FetchOpcode::kGetTextureWeights: { static const char* kNames[] = {"getWeights1D", "getWeights2D", "getWeights3D", "getWeightsCube"}; opcode_info = {kNames[static_cast(op.dimension())], true, true, true, 0}; } break; case FetchOpcode::kSetTextureLod: opcode_info = {"setTexLOD", false, false, false, 1}; break; case FetchOpcode::kSetTextureGradientsHorz: opcode_info = {"setGradientH", false, false, false, 3}; break; case FetchOpcode::kSetTextureGradientsVert: opcode_info = {"setGradientV", false, false, false, 3}; break; default: assert_unhandled_case(fetch_opcode); return; } auto& i = *out_instr; i.opcode = op.opcode(); i.opcode_name = opcode_info.name; i.dimension = op.dimension(); i.is_predicated = op.is_predicated(); i.predicate_condition = op.predicate_condition(); if (opcode_info.has_dest) { ParseFetchInstructionResult(op.dest(), op.dest_swizzle(), op.is_dest_relative(), &i.result); } else { i.result.storage_target = InstructionStorageTarget::kNone; } auto& src_op = i.operands[i.operand_count++]; src_op.storage_source = InstructionStorageSource::kRegister; src_op.storage_index = op.src(); src_op.storage_addressing_mode = op.is_src_relative() ? InstructionStorageAddressingMode::kAddressRelative : InstructionStorageAddressingMode::kStatic; src_op.is_negated = false; src_op.is_absolute_value = false; src_op.component_count = opcode_info.override_component_count ? opcode_info.override_component_count : xenos::GetFetchOpDimensionComponentCount(op.dimension()); uint32_t swizzle = op.src_swizzle(); for (uint32_t j = 0; j < src_op.component_count; ++j, swizzle >>= 2) { src_op.components[j] = GetSwizzleFromComponentIndex(swizzle & 0x3); } if (opcode_info.has_const) { auto& const_op = i.operands[i.operand_count++]; const_op.storage_source = InstructionStorageSource::kTextureFetchConstant; const_op.storage_index = op.fetch_constant_index(); } if (opcode_info.has_attributes) { i.attributes.fetch_valid_only = op.fetch_valid_only(); i.attributes.unnormalized_coordinates = op.unnormalized_coordinates(); i.attributes.mag_filter = op.mag_filter(); i.attributes.min_filter = op.min_filter(); i.attributes.mip_filter = op.mip_filter(); i.attributes.aniso_filter = op.aniso_filter(); i.attributes.vol_mag_filter = op.vol_mag_filter(); i.attributes.vol_min_filter = op.vol_min_filter(); i.attributes.use_computed_lod = op.use_computed_lod(); i.attributes.use_register_lod = op.use_register_lod(); i.attributes.use_register_gradients = op.use_register_gradients(); i.attributes.lod_bias = op.lod_bias(); i.attributes.offset_x = op.offset_x(); i.attributes.offset_y = op.offset_y(); i.attributes.offset_z = op.offset_z(); } } uint32_t ParsedTextureFetchInstruction::GetNonZeroResultComponents() const { uint32_t components = 0b0000; switch (opcode) { case FetchOpcode::kTextureFetch: case FetchOpcode::kGetTextureGradients: components = 0b1111; break; case FetchOpcode::kGetTextureBorderColorFrac: components = 0b0001; break; case FetchOpcode::kGetTextureComputedLod: // Not checking if the MipFilter is basemap because XNA doesn't accept // MipFilter for getCompTexLOD. components = 0b0001; break; case FetchOpcode::kGetTextureWeights: // FIXME(Triang3l): Not caring about mag/min filters currently for // simplicity. It's very unlikely that this instruction is ever seriously // used to retrieve weights of zero though. switch (dimension) { case xenos::FetchOpDimension::k1D: components = 0b1001; break; case xenos::FetchOpDimension::k2D: case xenos::FetchOpDimension::kCube: // TODO(Triang3l): Is the depth lerp factor always 0 for cube maps? components = 0b1011; break; case xenos::FetchOpDimension::k3DOrStacked: components = 0b1111; break; } if (attributes.mip_filter == xenos::TextureFilter::kBaseMap || attributes.mip_filter == xenos::TextureFilter::kPoint) { components &= ~uint32_t(0b1000); } break; case FetchOpcode::kSetTextureLod: case FetchOpcode::kSetTextureGradientsHorz: case FetchOpcode::kSetTextureGradientsVert: components = 0b0000; break; default: assert_unhandled_case(opcode); } return result.GetUsedResultComponents() & components; } const ShaderTranslator::AluOpcodeInfo ShaderTranslator::alu_vector_opcode_infos_[0x20] = { {"add", 2, 4, false}, // 0 {"mul", 2, 4, false}, // 1 {"max", 2, 4, false}, // 2 {"min", 2, 4, false}, // 3 {"seq", 2, 4, false}, // 4 {"sgt", 2, 4, false}, // 5 {"sge", 2, 4, false}, // 6 {"sne", 2, 4, false}, // 7 {"frc", 1, 4, false}, // 8 {"trunc", 1, 4, false}, // 9 {"floor", 1, 4, false}, // 10 {"mad", 3, 4, false}, // 11 {"cndeq", 3, 4, false}, // 12 {"cndge", 3, 4, false}, // 13 {"cndgt", 3, 4, false}, // 14 {"dp4", 2, 4, false}, // 15 {"dp3", 2, 4, false}, // 16 {"dp2add", 3, 4, false}, // 17 {"cube", 2, 4, false}, // 18 {"max4", 1, 4, false}, // 19 {"setp_eq_push", 2, 4, false}, // 20 {"setp_ne_push", 2, 4, false}, // 21 {"setp_gt_push", 2, 4, false}, // 22 {"setp_ge_push", 2, 4, false}, // 23 {"kill_eq", 2, 4, true}, // 24 {"kill_gt", 2, 4, true}, // 25 {"kill_ge", 2, 4, true}, // 26 {"kill_ne", 2, 4, true}, // 27 {"dst", 2, 4, false}, // 28 {"maxa", 2, 4, false}, // 29 }; const ShaderTranslator::AluOpcodeInfo ShaderTranslator::alu_scalar_opcode_infos_[0x40] = { {"adds", 1, 2, false}, // 0 {"adds_prev", 1, 1, false}, // 1 {"muls", 1, 2, false}, // 2 {"muls_prev", 1, 1, false}, // 3 {"muls_prev2", 1, 2, false}, // 4 {"maxs", 1, 2, false}, // 5 {"mins", 1, 2, false}, // 6 {"seqs", 1, 1, false}, // 7 {"sgts", 1, 1, false}, // 8 {"sges", 1, 1, false}, // 9 {"snes", 1, 1, false}, // 10 {"frcs", 1, 1, false}, // 11 {"truncs", 1, 1, false}, // 12 {"floors", 1, 1, false}, // 13 {"exp", 1, 1, false}, // 14 {"logc", 1, 1, false}, // 15 {"log", 1, 1, false}, // 16 {"rcpc", 1, 1, false}, // 17 {"rcpf", 1, 1, false}, // 18 {"rcp", 1, 1, false}, // 19 {"rsqc", 1, 1, false}, // 20 {"rsqf", 1, 1, false}, // 21 {"rsq", 1, 1, false}, // 22 {"maxas", 1, 2, false}, // 23 {"maxasf", 1, 2, false}, // 24 {"subs", 1, 2, false}, // 25 {"subs_prev", 1, 1, false}, // 26 {"setp_eq", 1, 1, false}, // 27 {"setp_ne", 1, 1, false}, // 28 {"setp_gt", 1, 1, false}, // 29 {"setp_ge", 1, 1, false}, // 30 {"setp_inv", 1, 1, false}, // 31 {"setp_pop", 1, 1, false}, // 32 {"setp_clr", 0, 0, false}, // 33 {"setp_rstr", 1, 1, false}, // 34 {"kills_eq", 1, 1, true}, // 35 {"kills_gt", 1, 1, true}, // 36 {"kills_ge", 1, 1, true}, // 37 {"kills_ne", 1, 1, true}, // 38 {"kills_one", 1, 1, true}, // 39 {"sqrt", 1, 1, false}, // 40 {"UNKNOWN", 0, 0, false}, // 41 {"mulsc", 2, 1, false}, // 42 {"mulsc", 2, 1, false}, // 43 {"addsc", 2, 1, false}, // 44 {"addsc", 2, 1, false}, // 45 {"subsc", 2, 1, false}, // 46 {"subsc", 2, 1, false}, // 47 {"sin", 1, 1, false}, // 48 {"cos", 1, 1, false}, // 49 {"retain_prev", 0, 0, false}, // 50 }; void ShaderTranslator::TranslateAluInstruction(const AluInstruction& op) { ParsedAluInstruction instr; ParseAluInstruction(op, instr); instr.Disassemble(&ucode_disasm_buffer_); ProcessAluInstruction(instr); } void ShaderTranslator::ParseAluInstruction(const AluInstruction& op, ParsedAluInstruction& instr) const { instr.is_predicated = op.is_predicated(); instr.predicate_condition = op.predicate_condition(); bool is_export = op.is_export(); InstructionStorageTarget storage_target = InstructionStorageTarget::kRegister; uint32_t storage_index_export = 0; if (is_export) { storage_target = InstructionStorageTarget::kNone; // Both vector and scalar operation export to vector_dest. ExportRegister export_register = ExportRegister(op.vector_dest()); if (export_register == ExportRegister::kExportAddress) { storage_target = InstructionStorageTarget::kExportAddress; } else if (export_register >= ExportRegister::kExportData0 && export_register <= ExportRegister::kExportData4) { storage_target = InstructionStorageTarget::kExportData; storage_index_export = uint32_t(export_register) - uint32_t(ExportRegister::kExportData0); } else if (is_vertex_shader()) { if (export_register >= ExportRegister::kVSInterpolator0 && export_register <= ExportRegister::kVSInterpolator15) { storage_target = InstructionStorageTarget::kInterpolator; storage_index_export = uint32_t(export_register) - uint32_t(ExportRegister::kVSInterpolator0); } else if (export_register == ExportRegister::kVSPosition) { storage_target = InstructionStorageTarget::kPosition; } else if (export_register == ExportRegister::kVSPointSizeEdgeFlagKillVertex) { storage_target = InstructionStorageTarget::kPointSizeEdgeFlagKillVertex; } } else if (is_pixel_shader()) { if (export_register >= ExportRegister::kPSColor0 && export_register <= ExportRegister::kPSColor3) { storage_target = InstructionStorageTarget::kColor; storage_index_export = uint32_t(export_register) - uint32_t(ExportRegister::kPSColor0); } else if (export_register == ExportRegister::kPSDepth) { storage_target = InstructionStorageTarget::kDepth; } } if (storage_target == InstructionStorageTarget::kNone) { assert_always(); XELOGE( "ShaderTranslator::ParseAluInstruction: Unsupported write to export " "{}", uint32_t(export_register)); } } // Vector operation and constant 0/1 writes. instr.vector_opcode = op.vector_opcode(); const auto& vector_opcode_info = alu_vector_opcode_infos_[uint32_t(instr.vector_opcode)]; instr.vector_opcode_name = vector_opcode_info.name; instr.vector_and_constant_result.storage_target = storage_target; instr.vector_and_constant_result.storage_addressing_mode = InstructionStorageAddressingMode::kStatic; if (is_export) { instr.vector_and_constant_result.storage_index = storage_index_export; } else { instr.vector_and_constant_result.storage_index = op.vector_dest(); assert_true(op.vector_dest() < register_count()); if (op.is_vector_dest_relative()) { instr.vector_and_constant_result.storage_addressing_mode = InstructionStorageAddressingMode::kAddressRelative; } } instr.vector_and_constant_result.is_clamped = op.vector_clamp(); uint32_t constant_0_mask = op.GetConstant0WriteMask(); uint32_t constant_1_mask = op.GetConstant1WriteMask(); instr.vector_and_constant_result.original_write_mask = op.GetVectorOpResultWriteMask() | constant_0_mask | constant_1_mask; for (uint32_t i = 0; i < 4; ++i) { SwizzleSource component = GetSwizzleFromComponentIndex(i); if (constant_0_mask & (1 << i)) { component = SwizzleSource::k0; } else if (constant_1_mask & (1 << i)) { component = SwizzleSource::k1; } instr.vector_and_constant_result.components[i] = component; } instr.vector_operand_count = vector_opcode_info.argument_count; for (uint32_t i = 0; i < instr.vector_operand_count; ++i) { InstructionOperand& vector_operand = instr.vector_operands[i]; ParseAluInstructionOperand(op, i + 1, vector_opcode_info.src_swizzle_component_count, vector_operand); } // Scalar operation. instr.scalar_opcode = op.scalar_opcode(); const auto& scalar_opcode_info = alu_scalar_opcode_infos_[uint32_t(instr.scalar_opcode)]; instr.scalar_opcode_name = scalar_opcode_info.name; instr.scalar_result.storage_target = storage_target; instr.scalar_result.storage_addressing_mode = InstructionStorageAddressingMode::kStatic; if (is_export) { instr.scalar_result.storage_index = storage_index_export; } else { instr.scalar_result.storage_index = op.scalar_dest(); assert_true(op.scalar_dest() < register_count()); if (op.is_scalar_dest_relative()) { instr.scalar_result.storage_addressing_mode = InstructionStorageAddressingMode::kAddressRelative; } } instr.scalar_result.is_clamped = op.scalar_clamp(); instr.scalar_result.original_write_mask = op.GetScalarOpResultWriteMask(); for (uint32_t i = 0; i < 4; ++i) { instr.scalar_result.components[i] = GetSwizzleFromComponentIndex(i); } instr.scalar_operand_count = scalar_opcode_info.argument_count; if (instr.scalar_operand_count) { if (instr.scalar_operand_count == 1) { ParseAluInstructionOperand(op, 3, scalar_opcode_info.src_swizzle_component_count, instr.scalar_operands[0]); } else { uint32_t src3_swizzle = op.src_swizzle(3); uint32_t component_a = ((src3_swizzle >> 6) + 3) & 0x3; uint32_t component_b = ((src3_swizzle >> 0) + 0) & 0x3; uint32_t reg2 = (src3_swizzle & 0x3C) | (op.src_is_temp(3) << 1) | (static_cast(op.scalar_opcode()) & 1); int const_slot = (op.src_is_temp(1) || op.src_is_temp(2)) ? 1 : 0; ParseAluInstructionOperandSpecial( op, InstructionStorageSource::kConstantFloat, op.src_reg(3), op.src_negate(3), 0, component_a, instr.scalar_operands[0]); ParseAluInstructionOperandSpecial(op, InstructionStorageSource::kRegister, reg2, op.src_negate(3), const_slot, component_b, instr.scalar_operands[1]); } } } void ShaderTranslator::ParseAluInstructionOperand( const AluInstruction& op, uint32_t i, uint32_t swizzle_component_count, InstructionOperand& out_op) { int const_slot = 0; switch (i) { case 2: const_slot = op.src_is_temp(1) ? 0 : 1; break; case 3: const_slot = op.src_is_temp(1) && op.src_is_temp(2) ? 0 : 1; break; } out_op.is_negated = op.src_negate(i); uint32_t reg = op.src_reg(i); if (op.src_is_temp(i)) { out_op.storage_source = InstructionStorageSource::kRegister; out_op.storage_index = reg & 0x1F; out_op.is_absolute_value = (reg & 0x80) == 0x80; out_op.storage_addressing_mode = (reg & 0x40) ? InstructionStorageAddressingMode::kAddressRelative : InstructionStorageAddressingMode::kStatic; } else { out_op.storage_source = InstructionStorageSource::kConstantFloat; out_op.storage_index = reg; if ((const_slot == 0 && op.is_const_0_addressed()) || (const_slot == 1 && op.is_const_1_addressed())) { if (op.is_address_relative()) { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kAddressAbsolute; } else { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kAddressRelative; } } else { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kStatic; } out_op.is_absolute_value = op.abs_constants(); } out_op.component_count = swizzle_component_count; uint32_t swizzle = op.src_swizzle(i); if (swizzle_component_count == 1) { uint32_t a = ((swizzle >> 6) + 3) & 0x3; out_op.components[0] = GetSwizzleFromComponentIndex(a); } else if (swizzle_component_count == 2) { uint32_t a = ((swizzle >> 6) + 3) & 0x3; uint32_t b = ((swizzle >> 0) + 0) & 0x3; out_op.components[0] = GetSwizzleFromComponentIndex(a); out_op.components[1] = GetSwizzleFromComponentIndex(b); } else if (swizzle_component_count == 3) { assert_always(); } else if (swizzle_component_count == 4) { for (uint32_t j = 0; j < swizzle_component_count; ++j, swizzle >>= 2) { out_op.components[j] = GetSwizzleFromComponentIndex((swizzle + j) & 0x3); } } } void ShaderTranslator::ParseAluInstructionOperandSpecial( const AluInstruction& op, InstructionStorageSource storage_source, uint32_t reg, bool negate, int const_slot, uint32_t component_index, InstructionOperand& out_op) { out_op.is_negated = negate; out_op.is_absolute_value = op.abs_constants(); out_op.storage_source = storage_source; if (storage_source == InstructionStorageSource::kRegister) { out_op.storage_index = reg & 0x7F; out_op.storage_addressing_mode = InstructionStorageAddressingMode::kStatic; } else { out_op.storage_index = reg; if ((const_slot == 0 && op.is_const_0_addressed()) || (const_slot == 1 && op.is_const_1_addressed())) { if (op.is_address_relative()) { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kAddressAbsolute; } else { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kAddressRelative; } } else { out_op.storage_addressing_mode = InstructionStorageAddressingMode::kStatic; } } out_op.component_count = 1; out_op.components[0] = GetSwizzleFromComponentIndex(component_index); } bool ParsedAluInstruction::IsVectorOpDefaultNop() const { if (vector_opcode != ucode::AluVectorOpcode::kMax || vector_and_constant_result.original_write_mask || vector_and_constant_result.is_clamped || vector_operands[0].storage_source != InstructionStorageSource::kRegister || vector_operands[0].storage_index != 0 || vector_operands[0].storage_addressing_mode != InstructionStorageAddressingMode::kStatic || vector_operands[0].is_negated || vector_operands[0].is_absolute_value || !vector_operands[0].IsStandardSwizzle() || vector_operands[1].storage_source != InstructionStorageSource::kRegister || vector_operands[1].storage_index != 0 || vector_operands[1].storage_addressing_mode != InstructionStorageAddressingMode::kStatic || vector_operands[1].is_negated || vector_operands[1].is_absolute_value || !vector_operands[1].IsStandardSwizzle()) { return false; } if (vector_and_constant_result.storage_target == InstructionStorageTarget::kRegister) { if (vector_and_constant_result.storage_index != 0 || vector_and_constant_result.storage_addressing_mode != InstructionStorageAddressingMode::kStatic) { return false; } } else { // In case both vector and scalar operations are nop, still need to write // somewhere that it's an export, not mov r0._, r0 + retain_prev r0._. // Accurate round trip is possible only if the target is o0 or oC0, because // if the total write mask is empty, the XNA assembler forces the // destination to be o0/oC0, but this doesn't really matter in this case. if (IsScalarOpDefaultNop()) { return false; } } return true; } bool ParsedAluInstruction::IsScalarOpDefaultNop() const { if (scalar_opcode != ucode::AluScalarOpcode::kRetainPrev || scalar_result.original_write_mask || scalar_result.is_clamped) { return false; } if (scalar_result.storage_target == InstructionStorageTarget::kRegister) { if (scalar_result.storage_index != 0 || scalar_result.storage_addressing_mode != InstructionStorageAddressingMode::kStatic) { return false; } } // For exports, if both are nop, the vector operation will be kept to state in // the microcode that the destination in the microcode is an export. return true; } bool ParsedAluInstruction::IsNop() const { return scalar_opcode == ucode::AluScalarOpcode::kRetainPrev && !scalar_result.GetUsedWriteMask() && !vector_and_constant_result.GetUsedWriteMask() && !ucode::AluVectorOpHasSideEffects(vector_opcode); } uint32_t ParsedAluInstruction::GetMemExportStreamConstant() const { if (vector_and_constant_result.storage_target == InstructionStorageTarget::kExportAddress && vector_opcode == ucode::AluVectorOpcode::kMad && vector_and_constant_result.GetUsedResultComponents() == 0b1111 && !vector_and_constant_result.is_clamped && vector_operands[2].storage_source == InstructionStorageSource::kConstantFloat && vector_operands[2].storage_addressing_mode == InstructionStorageAddressingMode::kStatic && vector_operands[2].IsStandardSwizzle() && !vector_operands[2].is_negated && !vector_operands[2].is_absolute_value) { return vector_operands[2].storage_index; } return UINT32_MAX; } } // namespace gpu } // namespace xe