/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2018 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/dxbc_shader_translator.h" #include #include #include #include "third_party/dxbc/DXBCChecksum.h" #include "third_party/dxbc/d3d12TokenizedProgramFormat.hpp" #include "xenia/base/assert.h" #include "xenia/base/cvar.h" #include "xenia/base/math.h" DEFINE_bool(dxbc_switch, true, "Use switch rather than if for flow control. Turning this off or " "on may improve stability, though this heavily depends on the " "driver - on AMD, it's recommended to have this set to true, as " "Halo 3 appears to crash when if is used for flow control " "(possibly the shader compiler tries to flatten them). On Intel " "HD Graphics, this is ignored because of a crash with the switch " "instruction.", "GPU"); DEFINE_bool(dxbc_source_map, false, "Disassemble Xenos instructions as comments in the resulting DXBC " "for debugging.", "GPU"); namespace xe { namespace gpu { using namespace ucode; // Notes about operands: // // Reading and writing: // - r# (temporary registers) are 4-component and can be used anywhere. // - v# (inputs) are 4-component and read-only. // - o# (outputs) are 4-component and write-only. // - oDepth (pixel shader depth output) is 1-component and write-only. // - x# (indexable temporary registers) are 4-component and can be accessed // either via a mov load or a mov store (and those movs are counted as // ArrayInstructions in STAT, not as MovInstructions), even though the D3D11.3 // functional specification says x# can be used wherever r# can be used, but // FXC emits only mov load/store in simple tests. // // Indexing: // - Constant buffers use 3D indices in CBx[y][z] format, where x is the ID of // the binding (CB#), y is the register to access within its space, z is the // 4-component vector to access within the register binding. // For example, if the requested vector is located in the beginning of the // second buffer in the descriptor array at b2, which is assigned to CB1, the // index would be CB1[3][0]. // - Resources and samplers use 2D indices, where the first dimension is the // S#/T#/U# binding index, and the second is the s#/t#/u# register index // within its space. DxbcShaderTranslator::DxbcShaderTranslator(uint32_t vendor_id, bool bindless_resources_used, bool edram_rov_used, bool force_emit_source_map) : vendor_id_(vendor_id), bindless_resources_used_(bindless_resources_used), edram_rov_used_(edram_rov_used) { emit_source_map_ = force_emit_source_map || cvars::dxbc_source_map; // Don't allocate again and again for the first shader. shader_code_.reserve(8192); shader_object_.reserve(16384); } DxbcShaderTranslator::~DxbcShaderTranslator() = default; std::vector DxbcShaderTranslator::ForceEarlyDepthStencil( const uint8_t* shader) { const uint32_t* old_shader = reinterpret_cast(shader); // To return something anyway even if patching fails. std::vector new_shader; uint32_t shader_size_bytes = old_shader[6]; new_shader.resize(shader_size_bytes); std::memcpy(new_shader.data(), shader, shader_size_bytes); // Find the SHEX chunk. uint32_t chunk_count = old_shader[7]; for (uint32_t i = 0; i < chunk_count; ++i) { uint32_t chunk_offset_bytes = old_shader[8 + i]; const uint32_t* chunk = old_shader + chunk_offset_bytes / sizeof(uint32_t); if (chunk[0] != 'XEHS') { continue; } // Find dcl_globalFlags and patch it. uint32_t code_size_dwords = chunk[3]; chunk += 4; for (uint32_t j = 0; j < code_size_dwords;) { uint32_t opcode_token = chunk[j]; uint32_t opcode = DECODE_D3D10_SB_OPCODE_TYPE(opcode_token); if (opcode == D3D10_SB_OPCODE_DCL_GLOBAL_FLAGS) { opcode_token |= D3D11_SB_GLOBAL_FLAG_FORCE_EARLY_DEPTH_STENCIL; std::memcpy(new_shader.data() + (chunk_offset_bytes + (4 + j) * sizeof(uint32_t)), &opcode_token, sizeof(uint32_t)); // Recalculate the checksum since the shader was modified. CalculateDXBCChecksum( reinterpret_cast(new_shader.data()), shader_size_bytes, reinterpret_cast(new_shader.data() + sizeof(uint32_t))); break; } if (opcode == D3D10_SB_OPCODE_CUSTOMDATA) { j += chunk[j + 1]; } else { j += DECODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(opcode_token); } } break; } return std::move(new_shader); } std::vector DxbcShaderTranslator::CreateDepthOnlyPixelShader() { Reset(); is_depth_only_pixel_shader_ = true; StartTranslation(); return std::move(CompleteTranslation()); } void DxbcShaderTranslator::Reset() { ShaderTranslator::Reset(); shader_code_.clear(); is_depth_only_pixel_shader_ = false; cbuffer_count_ = 0; // System constants always used in prologues/epilogues. cbuffer_index_system_constants_ = cbuffer_count_++; cbuffer_index_float_constants_ = kBindingIndexUnallocated; cbuffer_index_bool_loop_constants_ = kBindingIndexUnallocated; cbuffer_index_fetch_constants_ = kBindingIndexUnallocated; cbuffer_index_descriptor_indices_ = kBindingIndexUnallocated; system_constants_used_ = 0; in_domain_location_used_ = 0; in_primitive_id_used_ = false; in_control_point_index_used_ = false; in_position_xy_used_ = false; in_front_face_used_ = false; system_temp_count_current_ = 0; system_temp_count_max_ = 0; cf_exec_bool_constant_ = kCfExecBoolConstantNone; cf_exec_predicated_ = false; cf_instruction_predicate_if_open_ = false; cf_exec_predicate_written_ = false; srv_count_ = 0; srv_index_shared_memory_ = kBindingIndexUnallocated; srv_index_bindless_textures_2d_ = kBindingIndexUnallocated; srv_index_bindless_textures_3d_ = kBindingIndexUnallocated; srv_index_bindless_textures_cube_ = kBindingIndexUnallocated; texture_bindings_.clear(); texture_bindings_for_bindful_srv_indices_.clear(); uav_count_ = 0; uav_index_shared_memory_ = kBindingIndexUnallocated; uav_index_edram_ = kBindingIndexUnallocated; sampler_bindings_.clear(); memexport_alloc_current_count_ = 0; std::memset(&stat_, 0, sizeof(stat_)); } void DxbcShaderTranslator::DxbcSrc::Write(std::vector& code, bool is_integer, uint32_t mask, bool force_vector) const { uint32_t operand_token = GetOperandTokenTypeAndIndex(); uint32_t mask_single_component = DxbcDest::GetMaskSingleComponent(mask); uint32_t select_component = mask_single_component != UINT32_MAX ? mask_single_component : 0; bool is_vector = force_vector || (mask != 0b0000 && mask_single_component == UINT32_MAX); if (type_ == DxbcOperandType::kImmediate32) { if (is_vector) { operand_token |= uint32_t(DxbcOperandDimension::kVector) | (uint32_t(DxbcComponentSelection::kSwizzle) << 2) | (DxbcSrc::kXYZW << 4); } else { operand_token |= uint32_t(DxbcOperandDimension::kScalar); } code.push_back(operand_token); if (is_vector) { for (uint32_t i = 0; i < 4; ++i) { code.push_back((mask & (1 << i)) ? GetModifiedImmediate(i, is_integer) : 0); } } else { code.push_back(GetModifiedImmediate(select_component, is_integer)); } } else { switch (GetDimension()) { case DxbcOperandDimension::kScalar: if (is_vector) { operand_token |= uint32_t(DxbcOperandDimension::kVector) | (uint32_t(DxbcComponentSelection::kSwizzle) << 2) | (DxbcSrc::kXXXX << 4); } else { operand_token |= uint32_t(DxbcOperandDimension::kScalar); } break; case DxbcOperandDimension::kVector: operand_token |= uint32_t(DxbcOperandDimension::kVector); if (is_vector) { operand_token |= uint32_t(DxbcComponentSelection::kSwizzle) << 2; // Clear swizzle of unused components to a used value to avoid // referencing potentially uninitialized register components. uint32_t used_component; if (!xe::bit_scan_forward(mask, &used_component)) { used_component = 0; } for (uint32_t i = 0; i < 4; ++i) { uint32_t swizzle_index = (mask & (1 << i)) ? i : used_component; operand_token |= (((swizzle_ >> (swizzle_index * 2)) & 3) << (4 + i * 2)); } } else { operand_token |= (uint32_t(DxbcComponentSelection::kSelect1) << 2) | (((swizzle_ >> (select_component * 2)) & 3) << 4); } break; default: break; } DxbcOperandModifier modifier = DxbcOperandModifier::kNone; if (absolute_ && negate_) { modifier = DxbcOperandModifier::kAbsoluteNegate; } else if (absolute_) { modifier = DxbcOperandModifier::kAbsolute; } else if (negate_) { modifier = DxbcOperandModifier::kNegate; } if (modifier != DxbcOperandModifier::kNone) { operand_token |= uint32_t(1) << 31; } code.push_back(operand_token); if (modifier != DxbcOperandModifier::kNone) { code.push_back(uint32_t(DxbcExtendedOperandType::kModifier) | (uint32_t(modifier) << 6)); } DxbcOperandAddress::Write(code); } } bool DxbcShaderTranslator::UseSwitchForControlFlow() const { // Xenia crashes on Intel HD Graphics 4000 with switch. return cvars::dxbc_switch && vendor_id_ != 0x8086; } uint32_t DxbcShaderTranslator::PushSystemTemp(uint32_t zero_mask, uint32_t count) { uint32_t register_index = system_temp_count_current_; if (!uses_register_dynamic_addressing() && !is_depth_only_pixel_shader_) { // Guest shader registers first if they're not in x0. Depth-only pixel // shader is a special case of the DXBC translator usage, where there are no // GPRs because there's no shader to translate, and a guest shader is not // loaded. register_index += register_count(); } system_temp_count_current_ += count; system_temp_count_max_ = std::max(system_temp_count_max_, system_temp_count_current_); zero_mask &= 0b1111; if (zero_mask) { for (uint32_t i = 0; i < count; ++i) { DxbcOpMov(DxbcDest::R(register_index + i, zero_mask), DxbcSrc::LU(0)); } } return register_index; } void DxbcShaderTranslator::PopSystemTemp(uint32_t count) { assert_true(count <= system_temp_count_current_); system_temp_count_current_ -= std::min(count, system_temp_count_current_); } void DxbcShaderTranslator::ConvertPWLGamma( bool to_gamma, int32_t source_temp, uint32_t source_temp_component, uint32_t target_temp, uint32_t target_temp_component, uint32_t piece_temp, uint32_t piece_temp_component, uint32_t accumulator_temp, uint32_t accumulator_temp_component) { assert_true(source_temp != target_temp || source_temp_component != target_temp_component || ((target_temp != accumulator_temp || target_temp_component != accumulator_temp_component) && (target_temp != piece_temp || target_temp_component != piece_temp_component))); assert_true(piece_temp != source_temp || piece_temp_component != source_temp_component); assert_true(accumulator_temp != source_temp || accumulator_temp_component != source_temp_component); assert_true(piece_temp != accumulator_temp || piece_temp_component != accumulator_temp_component); DxbcSrc source_src(DxbcSrc::R(source_temp).Select(source_temp_component)); DxbcDest piece_dest(DxbcDest::R(piece_temp, 1 << piece_temp_component)); DxbcSrc piece_src(DxbcSrc::R(piece_temp).Select(piece_temp_component)); DxbcDest accumulator_dest( DxbcDest::R(accumulator_temp, 1 << accumulator_temp_component)); DxbcSrc accumulator_src( DxbcSrc::R(accumulator_temp).Select(accumulator_temp_component)); // For each piece: // 1) Calculate how far we are on it. Multiply by 1/width, subtract // start/width and saturate. // 2) Add the contribution of the piece - multiply the position on the piece // by its slope*width and accumulate. // Piece 1. DxbcOpMul(piece_dest, source_src, DxbcSrc::LF(to_gamma ? (1.0f / 0.0625f) : (1.0f / 0.25f)), true); DxbcOpMul(accumulator_dest, piece_src, DxbcSrc::LF(to_gamma ? (4.0f * 0.0625f) : (0.25f * 0.25f))); // Piece 2. DxbcOpMAd(piece_dest, source_src, DxbcSrc::LF(to_gamma ? (1.0f / 0.0625f) : (1.0f / 0.125f)), DxbcSrc::LF(to_gamma ? (-0.0625f / 0.0625f) : (-0.25f / 0.125f)), true); DxbcOpMAd(accumulator_dest, piece_src, DxbcSrc::LF(to_gamma ? (2.0f * 0.0625f) : (0.5f * 0.125f)), accumulator_src); // Piece 3. DxbcOpMAd(piece_dest, source_src, DxbcSrc::LF(to_gamma ? (1.0f / 0.375f) : (1.0f / 0.375f)), DxbcSrc::LF(to_gamma ? (-0.125f / 0.375f) : (-0.375f / 0.375f)), true); DxbcOpMAd(accumulator_dest, piece_src, DxbcSrc::LF(to_gamma ? (1.0f * 0.375f) : (1.0f * 0.375f)), accumulator_src); // Piece 4. DxbcOpMAd(piece_dest, source_src, DxbcSrc::LF(to_gamma ? (1.0f / 0.5f) : (1.0f / 0.25f)), DxbcSrc::LF(to_gamma ? (-0.5f / 0.5f) : (-0.75f / 0.25f)), true); DxbcOpMAd(DxbcDest::R(target_temp, 1 << target_temp_component), piece_src, DxbcSrc::LF(to_gamma ? (0.5f * 0.5f) : (2.0f * 0.25f)), accumulator_src); } void DxbcShaderTranslator::StartVertexShader_LoadVertexIndex() { if (register_count() < 1) { return; } // Writing the index to X of GPR 0 - either directly if not using indexable // registers, or via a system temporary register. uint32_t reg; if (uses_register_dynamic_addressing()) { reg = PushSystemTemp(); } else { reg = 0; } DxbcDest index_dest(DxbcDest::R(reg, 0b0001)); DxbcSrc index_src(DxbcSrc::R(reg, DxbcSrc::kXXXX)); // Check if the closing vertex of a non-indexed line loop is being processed. system_constants_used_ |= 1ull << kSysConst_LineLoopClosingIndex_Index; DxbcOpINE( index_dest, DxbcSrc::V(uint32_t(InOutRegister::kVSInVertexIndex), DxbcSrc::kXXXX), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_LineLoopClosingIndex_Vec) .Select(kSysConst_LineLoopClosingIndex_Comp)); // Zero the index if processing the closing vertex of a line loop, or do // nothing (replace 0 with 0) if not needed. DxbcOpAnd( index_dest, DxbcSrc::V(uint32_t(InOutRegister::kVSInVertexIndex), DxbcSrc::kXXXX), index_src); { // Swap the vertex index's endianness. system_constants_used_ |= 1ull << kSysConst_VertexIndexEndian_Index; DxbcSrc endian_src(DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_VertexIndexEndian_Vec) .Select(kSysConst_VertexIndexEndian_Comp)); DxbcDest swap_temp_dest(DxbcDest::R(reg, 0b0010)); DxbcSrc swap_temp_src(DxbcSrc::R(reg, DxbcSrc::kYYYY)); // 8-in-16 or one half of 8-in-32. DxbcOpSwitch(endian_src); DxbcOpCase(DxbcSrc::LU(uint32_t(xenos::Endian::k8in16))); DxbcOpCase(DxbcSrc::LU(uint32_t(xenos::Endian::k8in32))); // Temp = X0Z0. DxbcOpAnd(swap_temp_dest, index_src, DxbcSrc::LU(0x00FF00FF)); // Index = YZW0. DxbcOpUShR(index_dest, index_src, DxbcSrc::LU(8)); // Index = Y0W0. DxbcOpAnd(index_dest, index_src, DxbcSrc::LU(0x00FF00FF)); // Index = YXWZ. DxbcOpUMAd(index_dest, swap_temp_src, DxbcSrc::LU(256), index_src); DxbcOpBreak(); DxbcOpEndSwitch(); // 16-in-32 or another half of 8-in-32. DxbcOpSwitch(endian_src); DxbcOpCase(DxbcSrc::LU(uint32_t(xenos::Endian::k8in32))); DxbcOpCase(DxbcSrc::LU(uint32_t(xenos::Endian::k16in32))); // Temp = ZW00. DxbcOpUShR(swap_temp_dest, index_src, DxbcSrc::LU(16)); // Index = ZWXY. DxbcOpBFI(index_dest, DxbcSrc::LU(16), DxbcSrc::LU(16), index_src, swap_temp_src); DxbcOpBreak(); DxbcOpEndSwitch(); if (!uses_register_dynamic_addressing()) { // Break register dependency. DxbcOpMov(swap_temp_dest, DxbcSrc::LF(0.0f)); } } // Add the base vertex index. system_constants_used_ |= 1ull << kSysConst_VertexBaseIndex_Index; DxbcOpIAdd(index_dest, index_src, DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_VertexBaseIndex_Vec) .Select(kSysConst_VertexBaseIndex_Comp)); // Convert to float. DxbcOpIToF(index_dest, index_src); if (uses_register_dynamic_addressing()) { // Store to indexed GPR 0 in x0[0]. DxbcOpMov(DxbcDest::X(0, 0, 0b0001), index_src); PopSystemTemp(); } } void DxbcShaderTranslator::StartVertexOrDomainShader() { // Zero the interpolators. for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { DxbcOpMov(DxbcDest::O(uint32_t(InOutRegister::kVSDSOutInterpolators) + i), DxbcSrc::LF(0.0f)); } // Remember that x# are only accessible via mov load or store - use a // temporary variable if need to do any computations! switch (host_vertex_shader_type()) { case Shader::HostVertexShaderType::kVertex: StartVertexShader_LoadVertexIndex(); break; case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.xyz. // ZYX swizzle according to Call of Duty 3 and Viva Pinata. in_domain_location_used_ |= 0b0111; DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0111) : DxbcDest::R(0, 0b0111), DxbcSrc::VDomain(0b000110)); if (register_count() >= 2) { // Copy the control point indices (already swapped and converted to // float by the host vertex and hull shaders) to r1.xyz. DxbcDest control_point_index_dest(uses_register_dynamic_addressing() ? DxbcDest::X(0, 1) : DxbcDest::R(1)); in_control_point_index_used_ = true; for (uint32_t i = 0; i < 3; ++i) { DxbcOpMov(control_point_index_dest.Mask(1 << i), DxbcSrc::VICP( i, uint32_t(InOutRegister::kDSInControlPointIndex), DxbcSrc::kXXXX)); } } } break; case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.xyz. // ZYX swizzle with r1.y == 0, according to the water shader in // Banjo-Kazooie: Nuts & Bolts. in_domain_location_used_ |= 0b0111; DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0111) : DxbcDest::R(0, 0b0111), DxbcSrc::VDomain(0b000110)); if (register_count() >= 2) { // Copy the primitive index to r1.x as a float. uint32_t primitive_id_temp = uses_register_dynamic_addressing() ? PushSystemTemp() : 1; in_primitive_id_used_ = true; DxbcOpUToF(DxbcDest::R(primitive_id_temp, 0b0001), DxbcSrc::VPrim()); if (uses_register_dynamic_addressing()) { DxbcOpMov(DxbcDest::X(0, 1, 0b0001), DxbcSrc::R(primitive_id_temp, DxbcSrc::kXXXX)); // Release primitive_id_temp. PopSystemTemp(); } // Write the swizzle of the barycentric coordinates to r1.y. It // appears that the tessellator offloads the reordering of coordinates // for edges to game shaders. // // In Banjo-Kazooie: Nuts & Bolts, the water shader multiplies the // first control point's position by r0.z, the second CP's by r0.y, // and the third CP's by r0.x. But before doing that it swizzles // r0.xyz the following way depending on the value in r1.y: // - ZXY for 1.0. // - YZX for 2.0. // - XZY for 4.0. // - YXZ for 5.0. // - ZYX for 6.0. // Possibly, the logic here is that the value itself is the amount of // rotation of the swizzle to the right, and 1 << 2 is set when the // swizzle needs to be flipped before rotating. // // Direct3D 12 passes the coordinates in a consistent order, so can // just use the identity swizzle. DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 1, 0b0010) : DxbcDest::R(1, 0b0010), DxbcSrc::LF(0.0f)); } } break; case Shader::HostVertexShaderType::kQuadDomainCPIndexed: assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.xy. in_domain_location_used_ |= 0b0011; DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0011) : DxbcDest::R(0, 0b0011), DxbcSrc::VDomain()); // Control point indices according to the shader from the main menu of // Defender, which starts from `cndeq r2, c255.xxxy, r1.xyzz, r0.zzzz`, // where c255.x is 0, and c255.y is 1. // r0.z for (1 - r0.x) * (1 - r0.y) // r1.x for r0.x * (1 - r0.y) // r1.y for r0.x * r0.y // r1.z for (1 - r0.x) * r0.y in_control_point_index_used_ = true; DxbcOpMov( uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0100) : DxbcDest::R(0, 0b0100), DxbcSrc::VICP(0, uint32_t(InOutRegister::kDSInControlPointIndex), DxbcSrc::kXXXX)); if (register_count() >= 2) { DxbcDest r1_dest(uses_register_dynamic_addressing() ? DxbcDest::X(0, 1) : DxbcDest::R(1)); for (uint32_t i = 0; i < 3; ++i) { DxbcOpMov( r1_dest.Mask(1 << i), DxbcSrc::VICP(1 + i, uint32_t(InOutRegister::kDSInControlPointIndex), DxbcSrc::kXXXX)); } } } break; case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.yz. // XY swizzle according to the ground shader in Viva Pinata. in_domain_location_used_ |= 0b0011; DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0110) : DxbcDest::R(0, 0b0110), DxbcSrc::VDomain(0b010000)); // Copy the primitive index to r0.x as a float. uint32_t primitive_id_temp = uses_register_dynamic_addressing() ? PushSystemTemp() : 0; in_primitive_id_used_ = true; DxbcOpUToF(DxbcDest::R(primitive_id_temp, 0b0001), DxbcSrc::VPrim()); if (uses_register_dynamic_addressing()) { DxbcOpMov(DxbcDest::X(0, 0, 0b0001), DxbcSrc::R(primitive_id_temp, DxbcSrc::kXXXX)); // Release primitive_id_temp. PopSystemTemp(); } if (register_count() >= 2) { // Write the swizzle of the UV coordinates to r1.x. It appears that // the tessellator offloads the reordering of coordinates for edges to // game shaders. // // In Viva Pinata, if we assume that r0.y is U and r0.z is V, the // factors each control point value is multiplied by are the // following: // - (1-u)*(1-v), u*(1-v), (1-u)*v, u*v for 0.0 (identity swizzle). // - u*(1-v), (1-u)*(1-v), u*v, (1-u)*v for 1.0 (YXWZ). // - u*v, (1-u)*v, u*(1-v), (1-u)*(1-v) for 2.0 (WZYX). // - (1-u)*v, u*v, (1-u)*(1-v), u*(1-v) for 3.0 (ZWXY). // // Direct3D 12 passes the coordinates in a consistent order, so can // just use the identity swizzle. DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 1, 0b0001) : DxbcDest::R(1, 0b0001), DxbcSrc::LF(0.0f)); } } break; default: // TODO(Triang3l): Support line and non-adaptive quad patches. assert_unhandled_case(host_vertex_shader_type()); EmitTranslationError( "Unsupported host vertex shader type in StartVertexOrDomainShader"); break; } } void DxbcShaderTranslator::StartPixelShader() { if (edram_rov_used_) { // Load the EDRAM addresses and the coverage. StartPixelShader_LoadROVParameters(); // Do early 2x2 quad rejection if it makes sense. if (ROV_IsDepthStencilEarly()) { ROV_DepthStencilTest(); } } // If not translating anything, we only need the depth. if (is_depth_only_pixel_shader_) { return; } if (!edram_rov_used_ && writes_depth()) { // Initialize the depth output if used, which must be written to regardless // of the taken execution path. DxbcOpMov(DxbcDest::ODepth(), DxbcSrc::LF(0.0f)); } uint32_t interpolator_count = std::min(xenos::kMaxInterpolators, register_count()); if (interpolator_count != 0) { // Copy interpolants to GPRs. if (edram_rov_used_) { uint32_t centroid_temp = uses_register_dynamic_addressing() ? PushSystemTemp() : UINT32_MAX; system_constants_used_ |= 1ull << kSysConst_InterpolatorSamplingPattern_Index; DxbcSrc sampling_pattern_src( DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_InterpolatorSamplingPattern_Vec) .Select(kSysConst_InterpolatorSamplingPattern_Comp)); for (uint32_t i = 0; i < interpolator_count; ++i) { // With GPR dynamic addressing, first evaluate to centroid_temp r#, then // store to the x#. uint32_t centroid_register = uses_register_dynamic_addressing() ? centroid_temp : i; // Check if the input needs to be interpolated at center (if the bit is // set). DxbcOpAnd(DxbcDest::R(centroid_register, 0b0001), sampling_pattern_src, DxbcSrc::LU(uint32_t(1) << i)); DxbcOpIf(bool(xenos::SampleLocation::kCenter), DxbcSrc::R(centroid_register, DxbcSrc::kXXXX)); // At center. DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, i) : DxbcDest::R(i), DxbcSrc::V(uint32_t(InOutRegister::kPSInInterpolators) + i)); DxbcOpElse(); // At centroid. Not really important that 2x MSAA is emulated using // ForcedSampleCount 4 - what matters is that the sample position will // be within the primitive, and the value will not be extrapolated. DxbcOpEvalCentroid( DxbcDest::R(centroid_register), DxbcSrc::V(uint32_t(InOutRegister::kPSInInterpolators) + i)); if (uses_register_dynamic_addressing()) { DxbcOpMov(DxbcDest::X(0, i), DxbcSrc::R(centroid_register)); } DxbcOpEndIf(); } if (centroid_temp != UINT32_MAX) { PopSystemTemp(); } } else { // SSAA instead of MSAA without ROV - everything is interpolated at // samples, can't extrapolate. for (uint32_t i = 0; i < interpolator_count; ++i) { DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, i) : DxbcDest::R(i), DxbcSrc::V(uint32_t(InOutRegister::kPSInInterpolators) + i)); } } // Write pixel parameters - screen (XY absolute value) and point sprite (ZW // absolute value) coordinates, facing (X sign bit) - to the specified // interpolator register (ps_param_gen). system_constants_used_ |= 1ull << kSysConst_PSParamGen_Index; DxbcSrc param_gen_index_src( DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_PSParamGen_Vec) .Select(kSysConst_PSParamGen_Comp)); uint32_t param_gen_temp = PushSystemTemp(); // Check if pixel parameters need to be written. DxbcOpULT(DxbcDest::R(param_gen_temp, 0b0001), param_gen_index_src, DxbcSrc::LU(interpolator_count)); DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kXXXX)); { // XY - floored pixel position (Direct3D VPOS) in the absolute value, // faceness as X sign bit. Using Z as scratch register now. if (edram_rov_used_) { // Get XY address of the current host pixel as float. in_position_xy_used_ = true; DxbcOpRoundZ(DxbcDest::R(param_gen_temp, 0b0011), DxbcSrc::V(uint32_t(InOutRegister::kPSInPosition))); // Revert resolution scale - after truncating, so if the pixel position // is passed to tfetch (assuming the game doesn't round it by itself), // it will be sampled with higher resolution too. // Check if resolution scale is 2x2 and multiply by 0.5 in this case. system_constants_used_ |= 1ull << kSysConst_EdramResolutionSquareScale_Index; DxbcOpIEq(DxbcDest::R(param_gen_temp, 0b0100), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_EdramResolutionSquareScale_Vec) .Select(kSysConst_EdramResolutionSquareScale_Comp), DxbcSrc::LU(4)); DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ)); { DxbcOpMul(DxbcDest::R(param_gen_temp, 0b0011), DxbcSrc::R(param_gen_temp), DxbcSrc::LF(0.5f)); } DxbcOpEndIf(); } else { // Get XY address of the current SSAA sample by converting // SV_Position.xy to an integer. in_position_xy_used_ = true; DxbcOpFToU(DxbcDest::R(param_gen_temp, 0b0011), DxbcSrc::V(uint32_t(InOutRegister::kPSInPosition))); // Undo SSAA that is used instead of MSAA - since it's used as a // workaround for MSAA emulation, guest pixel position must be the same // for all samples, so this should be done is integers (or before // truncating). system_constants_used_ |= 1ull << kSysConst_SampleCountLog2_Index; DxbcOpUShR( DxbcDest::R(param_gen_temp, 0b0011), DxbcSrc::R(param_gen_temp), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_SampleCountLog2_Vec, kSysConst_SampleCountLog2_Comp | ((kSysConst_SampleCountLog2_Comp + 1) << 2))); // Convert the integer position to float Direct3D 9 VPOS. DxbcOpUToF(DxbcDest::R(param_gen_temp, 0b0011), DxbcSrc::R(param_gen_temp)); } // Check if faceness applies to the current primitive type. system_constants_used_ |= 1ull << kSysConst_Flags_Index; DxbcOpAnd(DxbcDest::R(param_gen_temp, 0b0100), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_Flags_Vec) .Select(kSysConst_Flags_Comp), DxbcSrc::LU(kSysFlag_PrimitiveTwoFaced)); DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ)); { // Negate modifier flips the sign bit even for 0 - set it to minus for // backfaces. in_front_face_used_ = true; DxbcOpMovC( DxbcDest::R(param_gen_temp, 0b0001), DxbcSrc::V(uint32_t(InOutRegister::kPSInFrontFace), DxbcSrc::kXXXX), DxbcSrc::R(param_gen_temp, DxbcSrc::kXXXX), -DxbcSrc::R(param_gen_temp, DxbcSrc::kXXXX)); } DxbcOpEndIf(); // ZW - UV within a point sprite in the absolute value, at centroid if // requested for the interpolator. DxbcDest point_coord_r_zw_dest(DxbcDest::R(param_gen_temp, 0b1100)); DxbcSrc point_coord_v_xxxy_src(DxbcSrc::V( uint32_t(InOutRegister::kPSInPointParameters), 0b01000000)); if (edram_rov_used_) { system_constants_used_ |= 1ull << kSysConst_InterpolatorSamplingPattern_Index; DxbcOpUBFE(DxbcDest::R(param_gen_temp, 0b0100), DxbcSrc::LU(1), param_gen_index_src, DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_InterpolatorSamplingPattern_Vec) .Select(kSysConst_InterpolatorSamplingPattern_Comp)); DxbcOpIf(bool(xenos::SampleLocation::kCenter), DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ)); // At center. DxbcOpMov(point_coord_r_zw_dest, point_coord_v_xxxy_src); DxbcOpElse(); // At centroid. DxbcOpEvalCentroid(point_coord_r_zw_dest, point_coord_v_xxxy_src); DxbcOpEndIf(); } else { // At the SSAA sample. DxbcOpMov(point_coord_r_zw_dest, point_coord_v_xxxy_src); } // Write ps_param_gen to the specified GPR. DxbcSrc param_gen_src(DxbcSrc::R(param_gen_temp)); if (uses_register_dynamic_addressing()) { // Copy the GPR number to r# for relative addressing. uint32_t param_gen_copy_temp = PushSystemTemp(); DxbcOpMov(DxbcDest::R(param_gen_copy_temp, 0b0001), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_PSParamGen_Vec) .Select(kSysConst_PSParamGen_Comp)); // Write to the GPR. DxbcOpMov(DxbcDest::X(0, DxbcIndex(param_gen_copy_temp, 0)), param_gen_src); // Release param_gen_copy_temp. PopSystemTemp(); } else { if (interpolator_count == 1) { DxbcOpMov(DxbcDest::R(0), param_gen_src); } else { // Write to the r# using binary search. uint32_t param_gen_copy_temp = PushSystemTemp(); auto param_gen_copy_node = [&](uint32_t low, uint32_t high, const auto& self) -> void { assert_true(low < high); uint32_t mid = low + (high - low + 1) / 2; DxbcOpULT(DxbcDest::R(param_gen_copy_temp, 0b0001), param_gen_index_src, DxbcSrc::LU(mid)); DxbcOpIf(true, DxbcSrc::R(param_gen_copy_temp, DxbcSrc::kXXXX)); { if (low + 1 == mid) { DxbcOpMov(DxbcDest::R(low), param_gen_src); } else { self(low, mid - 1, self); } } DxbcOpElse(); { if (mid == high) { DxbcOpMov(DxbcDest::R(mid), param_gen_src); } else { self(mid, high, self); } } DxbcOpEndIf(); }; param_gen_copy_node(0, interpolator_count - 1, param_gen_copy_node); // Release param_gen_copy_temp. PopSystemTemp(); } } } // Close the ps_param_gen check. DxbcOpEndIf(); // Release param_gen_temp. PopSystemTemp(); } } void DxbcShaderTranslator::StartTranslation() { // Allocate global system temporary registers that may also be used in the // epilogue. if (IsDxbcVertexOrDomainShader()) { system_temp_position_ = PushSystemTemp(0b1111); system_temp_point_size_edge_flag_kill_vertex_ = PushSystemTemp(0b0100); // Set the point size to a negative value to tell the geometry shader that // it should use the global point size if the vertex shader does not // override it. DxbcOpMov( DxbcDest::R(system_temp_point_size_edge_flag_kill_vertex_, 0b0001), DxbcSrc::LF(-1.0f)); } else if (IsDxbcPixelShader()) { if (edram_rov_used_) { // Will be initialized unconditionally. system_temp_rov_params_ = PushSystemTemp(); if (ROV_IsDepthStencilEarly() || writes_depth()) { // If the shader doesn't write to oDepth, each component will be written // to if depth/stencil is enabled and the respective sample is covered - // so need to initialize now because the first writes will be // conditional. If the shader writes to oDepth, this is oDepth of the // shader, written by the guest code, so initialize because assumptions // can't be made about the integrity of the guest code. system_temp_rov_depth_stencil_ = PushSystemTemp(writes_depth() ? 0b0001 : 0b1111); } } for (uint32_t i = 0; i < 4; ++i) { if (writes_color_target(i)) { system_temps_color_[i] = PushSystemTemp(0b1111); } } } if (!is_depth_only_pixel_shader_) { // Allocate temporary registers for memexport addresses and data. std::memset(system_temps_memexport_address_, 0xFF, sizeof(system_temps_memexport_address_)); std::memset(system_temps_memexport_data_, 0xFF, sizeof(system_temps_memexport_data_)); system_temp_memexport_written_ = UINT32_MAX; const uint8_t* memexports_written = memexport_eM_written(); for (uint32_t i = 0; i < kMaxMemExports; ++i) { uint32_t memexport_alloc_written = memexports_written[i]; if (memexport_alloc_written == 0) { continue; } // If memexport is used at all, allocate a register containing whether eM# // have actually been written to. if (system_temp_memexport_written_ == UINT32_MAX) { system_temp_memexport_written_ = PushSystemTemp(0b1111); } system_temps_memexport_address_[i] = PushSystemTemp(0b1111); uint32_t memexport_data_index; while (xe::bit_scan_forward(memexport_alloc_written, &memexport_data_index)) { memexport_alloc_written &= ~(1u << memexport_data_index); system_temps_memexport_data_[i][memexport_data_index] = PushSystemTemp(); } } // Allocate system temporary variables for the translated code. Since access // depends on the guest code (thus no guarantees), initialize everything // now (except for pv, it's an internal temporary variable, not accessible // by the guest). system_temp_result_ = PushSystemTemp(); system_temp_ps_pc_p0_a0_ = PushSystemTemp(0b1111); system_temp_aL_ = PushSystemTemp(0b1111); system_temp_loop_count_ = PushSystemTemp(0b1111); system_temp_grad_h_lod_ = PushSystemTemp(0b1111); system_temp_grad_v_ = PushSystemTemp(0b0111); // Zero general-purpose registers to prevent crashes when the game // references them after only initializing them conditionally. for (uint32_t i = IsDxbcPixelShader() ? xenos::kMaxInterpolators : 0; i < register_count(); ++i) { DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, i) : DxbcDest::R(i), DxbcSrc::LF(0.0f)); } } // Write stage-specific prologue. if (IsDxbcVertexOrDomainShader()) { StartVertexOrDomainShader(); } else if (IsDxbcPixelShader()) { StartPixelShader(); } // If not translating anything, don't start the main loop. if (is_depth_only_pixel_shader_) { return; } // Start the main loop (for jumping to labels by setting pc and continuing). DxbcOpLoop(); // Switch and the first label (pc == 0). if (UseSwitchForControlFlow()) { DxbcOpSwitch(DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kYYYY)); DxbcOpCase(DxbcSrc::LU(0)); } else { DxbcOpIf(false, DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kYYYY)); } } void DxbcShaderTranslator::CompleteVertexOrDomainShader() { uint32_t temp = PushSystemTemp(); DxbcDest temp_x_dest(DxbcDest::R(temp, 0b0001)); DxbcSrc temp_x_src(DxbcSrc::R(temp, DxbcSrc::kXXXX)); system_constants_used_ |= 1ull << kSysConst_Flags_Index; DxbcSrc flags_src(DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_Flags_Vec) .Select(kSysConst_Flags_Comp)); // Check if the shader already returns W, not 1/W, and if it doesn't, turn 1/W // into W. Using div rather than relaxed-precision rcp for safety. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_WNotReciprocal)); DxbcOpIf(false, temp_x_src); DxbcOpDiv(DxbcDest::R(system_temp_position_, 0b1000), DxbcSrc::LF(1.0f), DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW)); DxbcOpEndIf(); // Check if the shader returns XY/W rather than XY, and if it does, revert // that. // TODO(Triang3l): Check if having XY or Z pre-divided by W should result in // affine interpolation. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_XYDividedByW)); DxbcOpIf(true, temp_x_src); DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0011), DxbcSrc::R(system_temp_position_), DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW)); DxbcOpEndIf(); // Check if the shader returns Z/W rather than Z, and if it does, revert that. // TODO(Triang3l): Check if having XY or Z pre-divided by W should result in // affine interpolation. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_ZDividedByW)); DxbcOpIf(true, temp_x_src); DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0100), DxbcSrc::R(system_temp_position_, DxbcSrc::kZZZZ), DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW)); DxbcOpEndIf(); // Zero-initialize SV_ClipDistance# (for user clip planes) and SV_CullDistance // (for vertex kill) in case they're not needed. DxbcOpMov(DxbcDest::O(uint32_t(InOutRegister::kVSDSOutClipDistance0123)), DxbcSrc::LF(0.0f)); DxbcOpMov(DxbcDest::O( uint32_t(InOutRegister::kVSDSOutClipDistance45AndCullDistance), 0b0111), DxbcSrc::LF(0.0f)); // Clip against user clip planes. // Not possible to handle UCP_CULL_ONLY_ENA with the same shader though, since // there can be only 8 SV_ClipDistance + SV_CullDistance values at most, but // 12 would be needed. system_constants_used_ |= 1ull << kSysConst_UserClipPlanes_Index; for (uint32_t i = 0; i < 6; ++i) { // Check if the clip plane is enabled - this `if` is needed, as opposed to // just zeroing the clip planes in the constants, so Infinity and NaN in the // position won't have any effect caused by this if clip planes are // disabled. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_UserClipPlane0 << i)); DxbcOpIf(true, temp_x_src); DxbcOpDP4(DxbcDest::O( uint32_t(InOutRegister::kVSDSOutClipDistance0123) + (i >> 2), 1 << (i & 3)), DxbcSrc::R(system_temp_position_), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_UserClipPlanes_Vec + i)); DxbcOpEndIf(); } // Apply scale for drawing without a viewport, and also remap from OpenGL // Z clip space to Direct3D if needed. Also, if the vertex shader is // multipass, the NDC scale constant can be used to set position to NaN to // kill all primitives. system_constants_used_ |= 1ull << kSysConst_NDCScale_Index; DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111), DxbcSrc::R(system_temp_position_), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_NDCScale_Vec, kSysConst_NDCScale_Comp * 0b010101 + 0b100100)); // Reverse Z (Z = W - Z) if the viewport depth is inverted. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_ReverseZ)); DxbcOpIf(true, temp_x_src); DxbcOpAdd(DxbcDest::R(system_temp_position_, 0b0100), DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW), -DxbcSrc::R(system_temp_position_, DxbcSrc::kZZZZ)); DxbcOpEndIf(); // Apply offset (multiplied by W) for drawing without a viewport and for half // pixel offset. system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index; DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111), DxbcSrc::CB(cbuffer_index_system_constants_, uint32_t(CbufferRegister::kSystemConstants), kSysConst_NDCOffset_Vec, kSysConst_NDCOffset_Comp * 0b010101 + 0b100100), DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW), DxbcSrc::R(system_temp_position_)); // Write Z and W of the position to a separate attribute so ROV output can get // per-sample depth. DxbcOpMov(DxbcDest::O(uint32_t(InOutRegister::kVSDSOutClipSpaceZW), 0b0011), DxbcSrc::R(system_temp_position_, 0b1110)); // Assuming SV_CullDistance was zeroed earlier in this function. // Kill the primitive if needed - check if the shader wants to kill. // TODO(Triang3l): Find if the condition is actually the flag being non-zero. DxbcOpNE( temp_x_dest, DxbcSrc::R(system_temp_point_size_edge_flag_kill_vertex_, DxbcSrc::kZZZZ), DxbcSrc::LF(0.0f)); DxbcOpIf(true, temp_x_src); { // Extract the killing condition. DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_KillIfAnyVertexKilled)); DxbcOpIf(true, temp_x_src); { // Kill the primitive if any vertex is killed - write NaN to position. DxbcOpMov(DxbcDest::R(system_temp_position_, 0b1000), DxbcSrc::LF(std::nanf(""))); } DxbcOpElse(); { // Kill the primitive if all vertices are killed - set SV_CullDistance to // negative. DxbcOpMov( DxbcDest::O( uint32_t(InOutRegister::kVSDSOutClipDistance45AndCullDistance), 0b0100), DxbcSrc::LF(-1.0f)); } DxbcOpEndIf(); } DxbcOpEndIf(); // Write the position to the output. DxbcOpMov(DxbcDest::O(uint32_t(InOutRegister::kVSDSOutPosition)), DxbcSrc::R(system_temp_position_)); // Zero the point coordinate (will be set in the geometry shader if needed) // and write the point size. DxbcOpMov( DxbcDest::O(uint32_t(InOutRegister::kVSDSOutPointParameters), 0b0011), DxbcSrc::LF(0.0f)); DxbcOpMov( DxbcDest::O(uint32_t(InOutRegister::kVSDSOutPointParameters), 0b0100), DxbcSrc::R(system_temp_point_size_edge_flag_kill_vertex_, DxbcSrc::kXXXX)); // Release temp. PopSystemTemp(); } void DxbcShaderTranslator::CompleteShaderCode() { if (!is_depth_only_pixel_shader_) { // Close the last exec, there's nothing to merge it with anymore, and we're // closing upper-level flow control blocks. CloseExecConditionals(); // Close the last label and the switch. if (UseSwitchForControlFlow()) { DxbcOpBreak(); DxbcOpEndSwitch(); } else { DxbcOpEndIf(); } // End the main loop. DxbcOpBreak(); DxbcOpEndLoop(); // Release the following system temporary values so epilogue can reuse them: // - system_temp_result_. // - system_temp_ps_pc_p0_a0_. // - system_temp_aL_. // - system_temp_loop_count_. // - system_temp_grad_h_lod_. // - system_temp_grad_v_. PopSystemTemp(6); // Write memexported data to the shared memory UAV. ExportToMemory(); // Release memexport temporary registers. for (int i = kMaxMemExports - 1; i >= 0; --i) { if (system_temps_memexport_address_[i] == UINT32_MAX) { continue; } // Release exported data registers. for (int j = 4; j >= 0; --j) { if (system_temps_memexport_data_[i][j] != UINT32_MAX) { PopSystemTemp(); } } // Release the address register. PopSystemTemp(); } if (system_temp_memexport_written_ != UINT32_MAX) { PopSystemTemp(); } } // Write stage-specific epilogue. if (IsDxbcVertexOrDomainShader()) { CompleteVertexOrDomainShader(); } else if (IsDxbcPixelShader()) { CompletePixelShader(); } // Return from `main`. DxbcOpRet(); if (IsDxbcVertexOrDomainShader()) { // Release system_temp_position_ and // system_temp_point_size_edge_flag_kill_vertex_. PopSystemTemp(2); } else if (IsDxbcPixelShader()) { // Release system_temps_color_. for (int32_t i = 3; i >= 0; --i) { if (writes_color_target(i)) { PopSystemTemp(); } } if (edram_rov_used_) { if (ROV_IsDepthStencilEarly() || writes_depth()) { // Release system_temp_rov_depth_stencil_. PopSystemTemp(); } // Release system_temp_rov_params_. PopSystemTemp(); } } } std::vector DxbcShaderTranslator::CompleteTranslation() { // Write the code epilogue. CompleteShaderCode(); shader_object_.clear(); uint32_t has_pcsg = IsDxbcDomainShader() ? 1 : 0; // Write the shader object header. shader_object_.push_back('CBXD'); // Checksum (set later). for (uint32_t i = 0; i < 4; ++i) { shader_object_.push_back(0); } shader_object_.push_back(1); // Size (set later). shader_object_.push_back(0); // 5 or 6 chunks - RDEF, ISGN, optionally PCSG, OSGN, SHEX, STAT. shader_object_.push_back(5 + has_pcsg); // Chunk offsets (set later). for (uint32_t i = 0; i < shader_object_[7]; ++i) { shader_object_.push_back(0); } uint32_t chunk_position_dwords; // Write Resource DEFinitions. chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[8] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('FEDR'); shader_object_.push_back(0); WriteResourceDefinitions(); shader_object_[chunk_position_dwords + 1] = (uint32_t(shader_object_.size()) - chunk_position_dwords - 2) * sizeof(uint32_t); // Write Input SiGNature. chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[9] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('NGSI'); shader_object_.push_back(0); WriteInputSignature(); shader_object_[chunk_position_dwords + 1] = (uint32_t(shader_object_.size()) - chunk_position_dwords - 2) * sizeof(uint32_t); // Write Patch Constant SiGnature. if (has_pcsg) { chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[10] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('GSCP'); shader_object_.push_back(0); WritePatchConstantSignature(); shader_object_[chunk_position_dwords + 1] = (uint32_t(shader_object_.size()) - chunk_position_dwords - 2) * sizeof(uint32_t); } // Write Output SiGNature. chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[10 + has_pcsg] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('NGSO'); shader_object_.push_back(0); WriteOutputSignature(); shader_object_[chunk_position_dwords + 1] = (uint32_t(shader_object_.size()) - chunk_position_dwords - 2) * sizeof(uint32_t); // Write SHader EXtended. chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[11 + has_pcsg] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('XEHS'); shader_object_.push_back(0); WriteShaderCode(); shader_object_[chunk_position_dwords + 1] = (uint32_t(shader_object_.size()) - chunk_position_dwords - 2) * sizeof(uint32_t); // Write STATistics. chunk_position_dwords = uint32_t(shader_object_.size()); shader_object_[12 + has_pcsg] = chunk_position_dwords * sizeof(uint32_t); shader_object_.push_back('TATS'); shader_object_.push_back(sizeof(stat_)); shader_object_.resize(shader_object_.size() + sizeof(stat_) / sizeof(uint32_t)); std::memcpy(&shader_object_[chunk_position_dwords + 2], &stat_, sizeof(stat_)); // Fill the remaining fields of the header and copy bytes out. uint32_t shader_object_size = uint32_t(shader_object_.size() * sizeof(uint32_t)); shader_object_[6] = shader_object_size; // The checksum includes the size field, so it must be the last. CalculateDXBCChecksum(reinterpret_cast(shader_object_.data()), shader_object_size, reinterpret_cast(&shader_object_[1])); // TODO(Triang3l): Avoid copy? std::vector shader_object_bytes; shader_object_bytes.resize(shader_object_size); std::memcpy(shader_object_bytes.data(), shader_object_.data(), shader_object_size); return shader_object_bytes; } void DxbcShaderTranslator::EmitInstructionDisassembly() { if (!emit_source_map_) { return; } const char* source = instruction_disassembly_buffer_.buffer(); uint32_t length = uint32_t(instruction_disassembly_buffer_.length()); // Trim leading spaces and trailing new line. while (length != 0 && source[0] == ' ') { ++source; --length; } while (length != 0 && source[length - 1] == '\n') { --length; } if (length == 0) { return; } uint32_t length_dwords = (length + 1 + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); shader_code_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_CUSTOMDATA) | ENCODE_D3D10_SB_CUSTOMDATA_CLASS(D3D10_SB_CUSTOMDATA_COMMENT)); shader_code_.push_back(2 + length_dwords); size_t offset_dwords = shader_code_.size(); shader_code_.resize(offset_dwords + length_dwords); char* target = reinterpret_cast(&shader_code_[offset_dwords]); std::memcpy(target, source, length); target[length] = '\0'; // Don't leave uninitialized data, and make sure multiple invocations of the // translator for the same Xenos shader give the same DXBC. std::memset(target + length + 1, 0xAB, length_dwords * sizeof(uint32_t) - length - 1); } DxbcShaderTranslator::DxbcSrc DxbcShaderTranslator::LoadOperand( const InstructionOperand& operand, uint32_t needed_components, bool& temp_pushed_out) { temp_pushed_out = false; uint32_t first_needed_component; if (!xe::bit_scan_forward(needed_components, &first_needed_component)) { return DxbcSrc::LF(0.0f); } DxbcIndex index(operand.storage_index); switch (operand.storage_addressing_mode) { case InstructionStorageAddressingMode::kStatic: break; case InstructionStorageAddressingMode::kAddressAbsolute: index = DxbcIndex(system_temp_ps_pc_p0_a0_, 3, operand.storage_index); break; case InstructionStorageAddressingMode::kAddressRelative: index = DxbcIndex(system_temp_aL_, 0, operand.storage_index); break; } DxbcSrc src(DxbcSrc::LF(0.0f)); switch (operand.storage_source) { case InstructionStorageSource::kRegister: { if (uses_register_dynamic_addressing()) { // Load x#[#] to r# because x#[#] can be used only with mov. uint32_t temp = PushSystemTemp(); temp_pushed_out = true; uint32_t used_swizzle_components = 0; for (uint32_t i = 0; i < uint32_t(operand.component_count); ++i) { if (!(needed_components & (1 << i))) { continue; } SwizzleSource component = operand.GetComponent(i); assert_true(component >= SwizzleSource::kX && component <= SwizzleSource::kW); used_swizzle_components |= 1 << (uint32_t(component) - uint32_t(SwizzleSource::kX)); } assert_not_zero(used_swizzle_components); DxbcOpMov(DxbcDest::R(temp, used_swizzle_components), DxbcSrc::X(0, index)); src = DxbcSrc::R(temp); } else { assert_true(operand.storage_addressing_mode == InstructionStorageAddressingMode::kStatic); src = DxbcSrc::R(index.index_); } } break; case InstructionStorageSource::kConstantFloat: { if (cbuffer_index_float_constants_ == kBindingIndexUnallocated) { cbuffer_index_float_constants_ = cbuffer_count_++; } if (operand.storage_addressing_mode == InstructionStorageAddressingMode::kStatic) { uint32_t float_constant_index = constant_register_map().GetPackedFloatConstantIndex( operand.storage_index); assert_true(float_constant_index != UINT32_MAX); if (float_constant_index == UINT32_MAX) { return DxbcSrc::LF(0.0f); } index.index_ = float_constant_index; } else { assert_true(constant_register_map().float_dynamic_addressing); } src = DxbcSrc::CB(cbuffer_index_float_constants_, uint32_t(CbufferRegister::kFloatConstants), index); } break; default: assert_unhandled_case(operand.storage_source); return DxbcSrc::LF(0.0f); } // Swizzle, skipping unneeded components similar to how FXC skips components, // by replacing them with the leftmost used one. uint32_t swizzle = 0; for (uint32_t i = 0; i < 4; ++i) { SwizzleSource component = operand.GetComponent( (needed_components & (1 << i)) ? i : first_needed_component); assert_true(component >= SwizzleSource::kX && component <= SwizzleSource::kW); swizzle |= (uint32_t(component) - uint32_t(SwizzleSource::kX)) << (i * 2); } src = src.Swizzle(swizzle); return src.WithModifiers(operand.is_absolute_value, operand.is_negated); } void DxbcShaderTranslator::StoreResult(const InstructionResult& result, const DxbcSrc& src, bool can_store_memexport_address) { uint32_t used_write_mask = result.GetUsedWriteMask(); if (!used_write_mask) { return; } // Get the destination address and type. DxbcDest dest(DxbcDest::Null()); bool is_clamped = result.is_clamped; switch (result.storage_target) { case InstructionStorageTarget::kNone: return; case InstructionStorageTarget::kRegister: if (uses_register_dynamic_addressing()) { DxbcIndex register_index(result.storage_index); switch (result.storage_addressing_mode) { case InstructionStorageAddressingMode::kStatic: break; case InstructionStorageAddressingMode::kAddressAbsolute: register_index = DxbcIndex(system_temp_ps_pc_p0_a0_, 3, result.storage_index); break; case InstructionStorageAddressingMode::kAddressRelative: register_index = DxbcIndex(system_temp_aL_, 0, result.storage_index); break; } dest = DxbcDest::X(0, register_index); } else { assert_true(result.storage_addressing_mode == InstructionStorageAddressingMode::kStatic); dest = DxbcDest::R(result.storage_index); } break; case InstructionStorageTarget::kInterpolator: dest = DxbcDest::O(uint32_t(InOutRegister::kVSDSOutInterpolators) + result.storage_index); break; case InstructionStorageTarget::kPosition: dest = DxbcDest::R(system_temp_position_); break; case InstructionStorageTarget::kPointSizeEdgeFlagKillVertex: assert_zero(used_write_mask & 0b1000); dest = DxbcDest::R(system_temp_point_size_edge_flag_kill_vertex_); break; case InstructionStorageTarget::kExportAddress: // Validate memexport writes (Halo 3 has some weird invalid ones). if (!can_store_memexport_address || memexport_alloc_current_count_ == 0 || memexport_alloc_current_count_ > kMaxMemExports || system_temps_memexport_address_[memexport_alloc_current_count_ - 1] == UINT32_MAX) { return; } dest = DxbcDest::R( system_temps_memexport_address_[memexport_alloc_current_count_ - 1]); break; case InstructionStorageTarget::kExportData: { // Validate memexport writes (Halo 3 has some weird invalid ones). if (memexport_alloc_current_count_ == 0 || memexport_alloc_current_count_ > kMaxMemExports || system_temps_memexport_data_[memexport_alloc_current_count_ - 1] [result.storage_index] == UINT32_MAX) { return; } dest = DxbcDest::R( system_temps_memexport_data_[memexport_alloc_current_count_ - 1] [result.storage_index]); // Mark that the eM# has been written to and needs to be exported. assert_not_zero(used_write_mask); uint32_t memexport_index = memexport_alloc_current_count_ - 1; DxbcOpOr(DxbcDest::R(system_temp_memexport_written_, 1 << (memexport_index >> 2)), DxbcSrc::R(system_temp_memexport_written_) .Select(memexport_index >> 2), DxbcSrc::LU(uint32_t(1) << (result.storage_index + ((memexport_index & 3) << 3)))); } break; case InstructionStorageTarget::kColor: assert_not_zero(used_write_mask); assert_true(writes_color_target(result.storage_index)); dest = DxbcDest::R(system_temps_color_[result.storage_index]); if (edram_rov_used_) { // For ROV output, mark that the color has been written to. // According to: // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx9-graphics-reference-asm-ps-registers-output-color // if a color target hasn't been written to - including due to flow // control - the render target must not be modified (the unwritten // components of a written target are undefined, not sure if this // behavior is respected on the real GPU, but the ROV code currently // doesn't preserve unmodified components). DxbcOpOr(DxbcDest::R(system_temp_rov_params_, 0b0001), DxbcSrc::R(system_temp_rov_params_, DxbcSrc::kXXXX), DxbcSrc::LU(uint32_t(1) << (8 + result.storage_index))); } break; case InstructionStorageTarget::kDepth: // Writes X to scalar oDepth or to X of system_temp_rov_depth_stencil_, no // additional swizzling needed. assert_true(used_write_mask == 0b0001); assert_true(writes_depth()); if (edram_rov_used_) { dest = DxbcDest::R(system_temp_rov_depth_stencil_); } else { dest = DxbcDest::ODepth(); } // Depth outside [0, 1] is not safe for use with the ROV code. Though 20e4 // float depth can store values below 2, it's a very unusual case. // Direct3D 10+ SV_Depth, however, can accept any values, including // specials, when the depth buffer is floating-point. is_clamped = true; break; } if (dest.type_ == DxbcOperandType::kNull) { return; } // Write. uint32_t src_additional_swizzle = 0; uint32_t constant_mask = 0, constant_1_mask = 0; for (uint32_t i = 0; i < 4; ++i) { if (!(used_write_mask & (1 << i))) { continue; } SwizzleSource component = result.components[i]; if (component >= SwizzleSource::kX && component <= SwizzleSource::kW) { src_additional_swizzle |= (uint32_t(component) - uint32_t(SwizzleSource::kX)) << (i * 2); } else { constant_mask |= 1 << i; if (component == SwizzleSource::k1) { constant_1_mask |= 1 << i; } } } if (used_write_mask != constant_mask) { DxbcOpMov(dest.Mask(used_write_mask & ~constant_mask), src.SwizzleSwizzled(src_additional_swizzle), is_clamped); } if (constant_mask) { DxbcOpMov(dest.Mask(constant_mask), DxbcSrc::LF(float(constant_1_mask & 1), float((constant_1_mask >> 1) & 1), float((constant_1_mask >> 2) & 1), float((constant_1_mask >> 3) & 1))); } } void DxbcShaderTranslator::UpdateExecConditionalsAndEmitDisassembly( ParsedExecInstruction::Type type, uint32_t bool_constant_index, bool condition) { // Check if we can merge the new exec with the previous one, or the jump with // the previous exec. The instruction-level predicate check is also merged in // this case. bool merge = false; if (type == ParsedExecInstruction::Type::kConditional) { // Can merge conditional with conditional, as long as the bool constant and // the expected values are the same. if (cf_exec_bool_constant_ == bool_constant_index && cf_exec_bool_constant_condition_ == condition) { merge = true; } } else if (type == ParsedExecInstruction::Type::kPredicated) { // Can merge predicated with predicated if the conditions are the same and // the previous exec hasn't modified the predicate register. if (!cf_exec_predicate_written_ && cf_exec_predicated_ && cf_exec_predicate_condition_ == condition) { merge = true; } } else { // Can merge unconditional with unconditional. if (cf_exec_bool_constant_ == kCfExecBoolConstantNone && !cf_exec_predicated_) { merge = true; } } if (merge) { // Emit the disassembly for the exec/jump merged with the previous one. EmitInstructionDisassembly(); return; } CloseExecConditionals(); // Emit the disassembly for the new exec/jump. EmitInstructionDisassembly(); if (type == ParsedExecInstruction::Type::kConditional) { uint32_t bool_constant_test_temp = PushSystemTemp(); // Check the bool constant value. if (cbuffer_index_bool_loop_constants_ == kBindingIndexUnallocated) { cbuffer_index_bool_loop_constants_ = cbuffer_count_++; } DxbcOpAnd(DxbcDest::R(bool_constant_test_temp, 0b0001), DxbcSrc::CB(cbuffer_index_bool_loop_constants_, uint32_t(CbufferRegister::kBoolLoopConstants), bool_constant_index >> 7) .Select((bool_constant_index >> 5) & 3), DxbcSrc::LU(uint32_t(1) << (bool_constant_index & 31))); // Open the new `if`. DxbcOpIf(condition, DxbcSrc::R(bool_constant_test_temp, DxbcSrc::kXXXX)); // Release bool_constant_test_temp. PopSystemTemp(); cf_exec_bool_constant_ = bool_constant_index; cf_exec_bool_constant_condition_ = condition; } else if (type == ParsedExecInstruction::Type::kPredicated) { DxbcOpIf(condition, DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kZZZZ)); cf_exec_predicated_ = true; cf_exec_predicate_condition_ = condition; } } void DxbcShaderTranslator::CloseExecConditionals() { // Within the exec - instruction-level predicate check. CloseInstructionPredication(); // Exec level. if (cf_exec_bool_constant_ != kCfExecBoolConstantNone || cf_exec_predicated_) { DxbcOpEndIf(); cf_exec_bool_constant_ = kCfExecBoolConstantNone; cf_exec_predicated_ = false; } // Nothing relies on the predicate value being unchanged now. cf_exec_predicate_written_ = false; } void DxbcShaderTranslator::UpdateInstructionPredicationAndEmitDisassembly( bool predicated, bool condition) { if (!predicated) { CloseInstructionPredication(); EmitInstructionDisassembly(); return; } if (cf_instruction_predicate_if_open_) { if (cf_instruction_predicate_condition_ == condition) { // Already in the needed instruction-level `if`. EmitInstructionDisassembly(); return; } CloseInstructionPredication(); } // Emit the disassembly before opening (or not opening) the new conditional. EmitInstructionDisassembly(); // If the instruction predicate condition is the same as the exec predicate // condition, no need to open a check. However, if there was a `setp` prior // to this instruction, the predicate value now may be different than it was // in the beginning of the exec. if (!cf_exec_predicate_written_ && cf_exec_predicated_ && cf_exec_predicate_condition_ == condition) { return; } DxbcOpIf(condition, DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kZZZZ)); cf_instruction_predicate_if_open_ = true; cf_instruction_predicate_condition_ = condition; } void DxbcShaderTranslator::CloseInstructionPredication() { if (cf_instruction_predicate_if_open_) { DxbcOpEndIf(); cf_instruction_predicate_if_open_ = false; } } void DxbcShaderTranslator::JumpToLabel(uint32_t address) { DxbcOpMov(DxbcDest::R(system_temp_ps_pc_p0_a0_, 0b0010), DxbcSrc::LU(address)); DxbcOpContinue(); } void DxbcShaderTranslator::ProcessLabel(uint32_t cf_index) { if (cf_index == 0) { // 0 already added in the beginning. return; } // Close flow control on the deeper levels below - prevent attempts to merge // execs across labels. CloseExecConditionals(); if (UseSwitchForControlFlow()) { // Fallthrough to the label from the previous one on the next iteration if // no `continue` was done. Can't simply fallthrough because in DXBC, a // non-empty switch case must end with a break. JumpToLabel(cf_index); // Close the previous label. DxbcOpBreak(); // Go to the next label. DxbcOpCase(DxbcSrc::LU(cf_index)); } else { // Close the previous label. DxbcOpEndIf(); // if (pc <= cf_index) uint32_t test_temp = PushSystemTemp(); DxbcOpUGE(DxbcDest::R(test_temp, 0b0001), DxbcSrc::LU(cf_index), DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kYYYY)); DxbcOpIf(true, DxbcSrc::R(test_temp, DxbcSrc::kXXXX)); // Release test_temp. PopSystemTemp(); } } void DxbcShaderTranslator::ProcessExecInstructionBegin( const ParsedExecInstruction& instr) { if (emit_source_map_) { instruction_disassembly_buffer_.Reset(); instr.Disassemble(&instruction_disassembly_buffer_); } UpdateExecConditionalsAndEmitDisassembly( instr.type, instr.bool_constant_index, instr.condition); } void DxbcShaderTranslator::ProcessExecInstructionEnd( const ParsedExecInstruction& instr) { if (instr.is_end) { // Break out of the main loop. CloseInstructionPredication(); if (UseSwitchForControlFlow()) { // Write an invalid value to pc. DxbcOpMov(DxbcDest::R(system_temp_ps_pc_p0_a0_, 0b0010), DxbcSrc::LU(UINT32_MAX)); // Go to the next iteration, where switch cases won't be reached. DxbcOpContinue(); } else { DxbcOpBreak(); } } } void DxbcShaderTranslator::ProcessLoopStartInstruction( const ParsedLoopStartInstruction& instr) { // loop il, L - loop with loop data il, end @ L // Loop control is outside execs - actually close the last exec. CloseExecConditionals(); if (emit_source_map_) { instruction_disassembly_buffer_.Reset(); instr.Disassemble(&instruction_disassembly_buffer_); EmitInstructionDisassembly(); } // Count (unsigned) in bits 0:7 of the loop constant, initial aL (unsigned) in // 8:15. Starting from vector 2 because of bool constants. if (cbuffer_index_bool_loop_constants_ == kBindingIndexUnallocated) { cbuffer_index_bool_loop_constants_ = cbuffer_count_++; } DxbcSrc loop_constant_src( DxbcSrc::CB(cbuffer_index_bool_loop_constants_, uint32_t(CbufferRegister::kBoolLoopConstants), 2 + (instr.loop_constant_index >> 2)) .Select(instr.loop_constant_index & 3)); // Push the count to the loop count stack - move XYZ to YZW and set X to this // loop count. DxbcOpMov(DxbcDest::R(system_temp_loop_count_, 0b1110), DxbcSrc::R(system_temp_loop_count_, 0b10010000)); DxbcOpAnd(DxbcDest::R(system_temp_loop_count_, 0b0001), loop_constant_src, DxbcSrc::LU(UINT8_MAX)); // Push aL - keep the same value as in the previous loop if repeating, or the // new one otherwise. DxbcOpMov(DxbcDest::R(system_temp_aL_, instr.is_repeat ? 0b1111 : 0b1110), DxbcSrc::R(system_temp_aL_, 0b10010000)); if (!instr.is_repeat) { DxbcOpUBFE(DxbcDest::R(system_temp_aL_, 0b0001), DxbcSrc::LU(8), DxbcSrc::LU(8), loop_constant_src); } // Break if the loop counter is 0 (since the condition is checked in the end). DxbcOpIf(false, DxbcSrc::R(system_temp_loop_count_, DxbcSrc::kXXXX)); JumpToLabel(instr.loop_skip_address); DxbcOpEndIf(); } void DxbcShaderTranslator::ProcessLoopEndInstruction( const ParsedLoopEndInstruction& instr) { // endloop il, L - end loop w/ data il, head @ L // Loop control is outside execs - actually close the last exec. CloseExecConditionals(); if (emit_source_map_) { instruction_disassembly_buffer_.Reset(); instr.Disassemble(&instruction_disassembly_buffer_); EmitInstructionDisassembly(); } // Subtract 1 from the loop counter. DxbcOpIAdd(DxbcDest::R(system_temp_loop_count_, 0b0001), DxbcSrc::R(system_temp_loop_count_, DxbcSrc::kXXXX), DxbcSrc::LI(-1)); if (instr.is_predicated_break) { // if (loop_count.x == 0 || [!]p0) uint32_t break_case_temp = PushSystemTemp(); if (instr.predicate_condition) { // If p0 is non-zero, set the test value to 0 (since if_z is used, // otherwise check if the loop counter is zero). DxbcOpMovC(DxbcDest::R(break_case_temp, 0b0001), DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kZZZZ), DxbcSrc::LU(0), DxbcSrc::R(system_temp_loop_count_, DxbcSrc::kXXXX)); } else { // If p0 is zero, set the test value to 0 (since if_z is used, otherwise // check if the loop counter is zero). DxbcOpMovC(DxbcDest::R(break_case_temp, 0b0001), DxbcSrc::R(system_temp_ps_pc_p0_a0_, DxbcSrc::kZZZZ), DxbcSrc::R(system_temp_loop_count_, DxbcSrc::kXXXX), DxbcSrc::LU(0)); } DxbcOpIf(false, DxbcSrc::R(break_case_temp, DxbcSrc::kXXXX)); // Release break_case_temp. PopSystemTemp(); } else { // if (loop_count.x == 0) DxbcOpIf(false, DxbcSrc::R(system_temp_loop_count_, DxbcSrc::kXXXX)); } { // Break case. // Pop the current loop off the stack, move YZW to XYZ and set W to 0. DxbcOpMov(DxbcDest::R(system_temp_loop_count_, 0b0111), DxbcSrc::R(system_temp_loop_count_, 0b111001)); DxbcOpMov(DxbcDest::R(system_temp_loop_count_, 0b1000), DxbcSrc::LU(0)); // Now going to fall through to the next exec (no need to jump). } DxbcOpElse(); { // Continue case. uint32_t aL_add_temp = PushSystemTemp(); // Extract the value to add to aL (signed, in bits 16:23 of the loop // constant). Starting from vector 2 because of bool constants. if (cbuffer_index_bool_loop_constants_ == kBindingIndexUnallocated) { cbuffer_index_bool_loop_constants_ = cbuffer_count_++; } DxbcOpIBFE(DxbcDest::R(aL_add_temp, 0b0001), DxbcSrc::LU(8), DxbcSrc::LU(16), DxbcSrc::CB(cbuffer_index_bool_loop_constants_, uint32_t(CbufferRegister::kBoolLoopConstants), 2 + (instr.loop_constant_index >> 2)) .Select(instr.loop_constant_index & 3)); // Add the needed value to aL. DxbcOpIAdd(DxbcDest::R(system_temp_aL_, 0b0001), DxbcSrc::R(system_temp_aL_, DxbcSrc::kXXXX), DxbcSrc::R(aL_add_temp, DxbcSrc::kXXXX)); // Release aL_add_temp. PopSystemTemp(); // Jump back to the beginning of the loop body. JumpToLabel(instr.loop_body_address); } DxbcOpEndIf(); } void DxbcShaderTranslator::ProcessJumpInstruction( const ParsedJumpInstruction& instr) { if (emit_source_map_) { instruction_disassembly_buffer_.Reset(); instr.Disassemble(&instruction_disassembly_buffer_); } // Treat like exec, merge with execs if possible, since it's an if too. ParsedExecInstruction::Type type; if (instr.type == ParsedJumpInstruction::Type::kConditional) { type = ParsedExecInstruction::Type::kConditional; } else if (instr.type == ParsedJumpInstruction::Type::kPredicated) { type = ParsedExecInstruction::Type::kPredicated; } else { type = ParsedExecInstruction::Type::kUnconditional; } UpdateExecConditionalsAndEmitDisassembly(type, instr.bool_constant_index, instr.condition); // UpdateExecConditionalsAndEmitDisassembly may not necessarily close the // instruction-level predicate check (it's not necessary if the execs are // merged), but here the instruction itself is on the flow control level, so // the predicate check is on the flow control level too. CloseInstructionPredication(); JumpToLabel(instr.target_address); } void DxbcShaderTranslator::ProcessAllocInstruction( const ParsedAllocInstruction& instr) { if (emit_source_map_) { instruction_disassembly_buffer_.Reset(); instr.Disassemble(&instruction_disassembly_buffer_); EmitInstructionDisassembly(); } if (instr.type == AllocType::kMemory) { ++memexport_alloc_current_count_; } } uint32_t DxbcShaderTranslator::AppendString(std::vector& dest, const char* source) { size_t size = std::strlen(source) + 1; size_t size_aligned = xe::align(size, sizeof(uint32_t)); size_t dest_position = dest.size(); dest.resize(dest_position + size_aligned / sizeof(uint32_t)); std::memcpy(&dest[dest_position], source, size); // Don't leave uninitialized data, and make sure multiple invocations of the // translator for the same Xenos shader give the same DXBC. std::memset(reinterpret_cast(&dest[dest_position]) + size, 0xAB, size_aligned - size); return uint32_t(size_aligned); } const DxbcShaderTranslator::RdefType DxbcShaderTranslator::rdef_types_[size_t( DxbcShaderTranslator::RdefTypeIndex::kCount)] = { // kFloat {"float", DxbcRdefVariableClass::kScalar, DxbcRdefVariableType::kFloat, 1, 1, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kFloat2 {"float2", DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 2, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kFloat3 {"float3", DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 3, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kFloat4 {"float4", DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 4, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kInt {"int", DxbcRdefVariableClass::kScalar, DxbcRdefVariableType::kInt, 1, 1, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kUint {"uint", DxbcRdefVariableClass::kScalar, DxbcRdefVariableType::kUInt, 1, 1, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kUint2 {"uint2", DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 2, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kUint4 {"uint4", DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 4, 0, 0, RdefTypeIndex::kUnknown, nullptr}, // kFloat4Array4 {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 4, 4, 0, RdefTypeIndex::kFloat4, nullptr}, // kFloat4Array6 {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 4, 6, 0, RdefTypeIndex::kFloat4, nullptr}, // kFloat4ConstantArray - float constants - size written dynamically. {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kFloat, 1, 4, 0, 0, RdefTypeIndex::kFloat4, nullptr}, // kUint4Array2 {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 4, 2, 0, RdefTypeIndex::kUint4, nullptr}, // kUint4Array8 {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 4, 8, 0, RdefTypeIndex::kUint4, nullptr}, // kUint4Array48 {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 4, 48, 0, RdefTypeIndex::kUint4, nullptr}, // kUint4DescriptorIndexArray - bindless descriptor indices - size written // dynamically. {nullptr, DxbcRdefVariableClass::kVector, DxbcRdefVariableType::kUInt, 1, 4, 0, 0, RdefTypeIndex::kUint4, nullptr}, }; const DxbcShaderTranslator::SystemConstantRdef DxbcShaderTranslator:: system_constant_rdef_[DxbcShaderTranslator::kSysConst_Count] = { {"xe_flags", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_tessellation_factor_range", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_line_loop_closing_index", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_vertex_index_endian", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_vertex_base_index", RdefTypeIndex::kInt, sizeof(int32_t)}, {"xe_point_size", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_point_size_min_max", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_point_screen_to_ndc", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_user_clip_planes", RdefTypeIndex::kFloat4Array6, sizeof(float) * 4 * 6}, {"xe_ndc_scale", RdefTypeIndex::kFloat3, sizeof(float) * 3}, {"xe_interpolator_sampling_pattern", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_ndc_offset", RdefTypeIndex::kFloat3, sizeof(float) * 3}, {"xe_ps_param_gen", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_texture_swizzled_signs", RdefTypeIndex::kUint4Array2, sizeof(uint32_t) * 4 * 2}, {"xe_sample_count_log2", RdefTypeIndex::kUint2, sizeof(uint32_t) * 2}, {"xe_alpha_test_reference", RdefTypeIndex::kFloat, sizeof(float)}, {"xe_alpha_to_mask", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_color_exp_bias", RdefTypeIndex::kFloat4, sizeof(float) * 4}, {"xe_color_output_map", RdefTypeIndex::kUint4, sizeof(uint32_t) * 4}, {"xe_edram_resolution_square_scale", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_edram_pitch_tiles", RdefTypeIndex::kUint, sizeof(uint32_t)}, {"xe_edram_depth_range", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_edram_poly_offset_front", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_edram_poly_offset_back", RdefTypeIndex::kFloat2, sizeof(float) * 2}, {"xe_edram_depth_base_dwords", RdefTypeIndex::kUint, sizeof(uint32_t), sizeof(float) * 3}, {"xe_edram_stencil", RdefTypeIndex::kUint4Array2, sizeof(uint32_t) * 4 * 2}, {"xe_edram_rt_base_dwords_scaled", RdefTypeIndex::kUint4, sizeof(uint32_t) * 4}, {"xe_edram_rt_format_flags", RdefTypeIndex::kUint4, sizeof(uint32_t) * 4}, {"xe_edram_rt_clamp", RdefTypeIndex::kFloat4Array4, sizeof(float) * 4 * 4}, {"xe_edram_rt_keep_mask", RdefTypeIndex::kUint4Array2, sizeof(uint32_t) * 4 * 2}, {"xe_edram_rt_blend_factors_ops", RdefTypeIndex::kUint4, sizeof(uint32_t) * 4}, {"xe_edram_blend_constant", RdefTypeIndex::kFloat4, sizeof(float) * 4}, }; void DxbcShaderTranslator::WriteResourceDefinitions() { uint32_t chunk_position_dwords = uint32_t(shader_object_.size()); uint32_t new_offset; // *************************************************************************** // Header // *************************************************************************** // Constant buffer count. shader_object_.push_back(cbuffer_count_); // Constant buffer offset (set later). shader_object_.push_back(0); // Bindful resource count. uint32_t resource_count = srv_count_ + uav_count_ + cbuffer_count_; if (!sampler_bindings_.empty()) { if (bindless_resources_used_) { ++resource_count; } else { resource_count += uint32_t(sampler_bindings_.size()); } } shader_object_.push_back(resource_count); // Bindful resource buffer offset (set later). shader_object_.push_back(0); if (IsDxbcVertexShader()) { // vs_5_1 shader_object_.push_back(0xFFFE0501u); } else if (IsDxbcDomainShader()) { // ds_5_1 shader_object_.push_back(0x44530501u); } else { assert_true(IsDxbcPixelShader()); // ps_5_1 shader_object_.push_back(0xFFFF0501u); } // Compiler flags - default for SM 5.1 (no preshader, prefer flow control), // and also skip optimization and IEEE strictness. shader_object_.push_back(0x2504); // Generator offset (directly after the RDEF header in our case). shader_object_.push_back(60); // RD11, but with nibbles inverted (unlike in SM 5.0). shader_object_.push_back(0x25441313); // Unknown fields. shader_object_.push_back(60); shader_object_.push_back(24); // Was 32 in SM 5.0. shader_object_.push_back(40); shader_object_.push_back(40); shader_object_.push_back(36); shader_object_.push_back(12); shader_object_.push_back(0); // Generator name. AppendString(shader_object_, "Xenia"); // *************************************************************************** // Constant types // *************************************************************************** // Type names. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); uint32_t type_name_offsets[size_t(RdefTypeIndex::kCount)]; for (uint32_t i = 0; i < uint32_t(RdefTypeIndex::kCount); ++i) { const RdefType& type = rdef_types_[i]; if (type.name == nullptr) { // Array - use the name of the element type. type_name_offsets[i] = type_name_offsets[uint32_t(type.array_element_type)]; continue; } type_name_offsets[i] = new_offset; new_offset += AppendString(shader_object_, type.name); } // Types. uint32_t types_position_dwords = uint32_t(shader_object_.size()); const uint32_t type_size_dwords = 9; uint32_t types_offset = (types_position_dwords - chunk_position_dwords) * sizeof(uint32_t); const uint32_t type_size = type_size_dwords * sizeof(uint32_t); for (uint32_t i = 0; i < uint32_t(RdefTypeIndex::kCount); ++i) { const RdefType& type = rdef_types_[i]; shader_object_.push_back(uint32_t(type.variable_class) | (uint32_t(type.variable_type) << 16)); shader_object_.push_back(type.row_count | (type.column_count << 16)); switch (RdefTypeIndex(i)) { case RdefTypeIndex::kFloat4ConstantArray: // Declaring a 0-sized array may not be safe, so write something valid // even if they aren't used. shader_object_.push_back( std::max(constant_register_map().float_count, uint32_t(1))); break; case RdefTypeIndex::kUint4DescriptorIndexArray: shader_object_.push_back(std::max( uint32_t((GetBindlessResourceCount() + 3) >> 2), uint32_t(1))); break; default: shader_object_.push_back(type.element_count | (type.struct_member_count << 16)); } // Struct member offset (set later). shader_object_.push_back(0); // Unknown. shader_object_.push_back(0); shader_object_.push_back(0); shader_object_.push_back(0); shader_object_.push_back(0); shader_object_.push_back(type_name_offsets[i]); } #if 0 // Structure members. Structures are not used currently, but were used in the // past, so the code is kept here. for (uint32_t i = 0; i < uint32_t(RdefTypeIndex::kCount); ++i) { const RdefType& type = rdef_types_[i]; const RdefStructMember* struct_members = type.struct_members; if (struct_members == nullptr) { continue; } uint32_t struct_member_position_dwords = uint32_t(shader_object_.size()); shader_object_[types_position_dwords + i * type_size_dwords + 3] = (struct_member_position_dwords - chunk_position_dwords) * sizeof(uint32_t); uint32_t struct_member_count = type.struct_member_count; // Reserve space for names and write types and offsets. for (uint32_t j = 0; j < struct_member_count; ++j) { shader_object_.push_back(0); shader_object_.push_back(types_offset + uint32_t(struct_members[j].type) * type_size); shader_object_.push_back(struct_members[j].offset); } // Write member names. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); for (uint32_t j = 0; j < struct_member_count; ++j) { shader_object_[struct_member_position_dwords + j * 3] = new_offset; new_offset += AppendString(shader_object_, struct_members[j].name); } } #endif // *************************************************************************** // Constants // *************************************************************************** // Names. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); uint32_t constant_name_offsets_system[kSysConst_Count]; if (cbuffer_index_system_constants_ != kBindingIndexUnallocated) { for (uint32_t i = 0; i < kSysConst_Count; ++i) { constant_name_offsets_system[i] = new_offset; new_offset += AppendString(shader_object_, system_constant_rdef_[i].name); } } uint32_t constant_name_offset_float = new_offset; if (cbuffer_index_float_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_float_constants"); } uint32_t constant_name_offset_bool = new_offset; uint32_t constant_name_offset_loop = new_offset; if (cbuffer_index_bool_loop_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_bool_constants"); constant_name_offset_loop = new_offset; new_offset += AppendString(shader_object_, "xe_loop_constants"); } uint32_t constant_name_offset_fetch = new_offset; if (cbuffer_index_fetch_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_fetch_constants"); } uint32_t constant_name_offset_descriptor_indices = new_offset; if (cbuffer_index_descriptor_indices_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_descriptor_indices"); } const uint32_t constant_size = 10 * sizeof(uint32_t); // System constants. uint32_t constant_offset_system = new_offset; if (cbuffer_index_system_constants_ != kBindingIndexUnallocated) { uint32_t system_cbuffer_constant_offset = 0; for (uint32_t i = 0; i < kSysConst_Count; ++i) { const SystemConstantRdef& constant = system_constant_rdef_[i]; shader_object_.push_back(constant_name_offsets_system[i]); shader_object_.push_back(system_cbuffer_constant_offset); shader_object_.push_back(constant.size); shader_object_.push_back((system_constants_used_ & (1ull << i)) ? kDxbcRdefVariableFlagUsed : 0); shader_object_.push_back(types_offset + uint32_t(constant.type) * type_size); // Default value (always 0). shader_object_.push_back(0); // Unknown. shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); system_cbuffer_constant_offset += constant.size + constant.padding_after; new_offset += constant_size; } } // Float constants. uint32_t constant_offset_float = new_offset; if (cbuffer_index_float_constants_ != kBindingIndexUnallocated) { assert_not_zero(constant_register_map().float_count); shader_object_.push_back(constant_name_offset_float); shader_object_.push_back(0); shader_object_.push_back(constant_register_map().float_count * 4 * sizeof(float)); shader_object_.push_back(kDxbcRdefVariableFlagUsed); shader_object_.push_back(types_offset + uint32_t(RdefTypeIndex::kFloat4ConstantArray) * type_size); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); new_offset += constant_size; } // Bool and loop constants. uint32_t constant_offset_bool_loop = new_offset; if (cbuffer_index_bool_loop_constants_ != kBindingIndexUnallocated) { shader_object_.push_back(constant_name_offset_bool); shader_object_.push_back(0); shader_object_.push_back(2 * 4 * sizeof(uint32_t)); shader_object_.push_back(kDxbcRdefVariableFlagUsed); shader_object_.push_back(types_offset + uint32_t(RdefTypeIndex::kUint4Array2) * type_size); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); new_offset += constant_size; shader_object_.push_back(constant_name_offset_loop); shader_object_.push_back(2 * 4 * sizeof(uint32_t)); shader_object_.push_back(8 * 4 * sizeof(uint32_t)); shader_object_.push_back(kDxbcRdefVariableFlagUsed); shader_object_.push_back(types_offset + uint32_t(RdefTypeIndex::kUint4Array8) * type_size); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); new_offset += constant_size; } // Fetch constants. uint32_t constant_offset_fetch = new_offset; if (cbuffer_index_fetch_constants_ != kBindingIndexUnallocated) { shader_object_.push_back(constant_name_offset_fetch); shader_object_.push_back(0); shader_object_.push_back(32 * 6 * sizeof(uint32_t)); shader_object_.push_back(kDxbcRdefVariableFlagUsed); shader_object_.push_back( types_offset + uint32_t(RdefTypeIndex::kUint4Array48) * type_size); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); new_offset += constant_size; } // Bindless description indices. uint32_t constant_offset_descriptor_indices = new_offset; if (cbuffer_index_descriptor_indices_ != kBindingIndexUnallocated) { assert_not_zero(GetBindlessResourceCount()); shader_object_.push_back(constant_name_offset_descriptor_indices); shader_object_.push_back(0); shader_object_.push_back( xe::align(GetBindlessResourceCount(), uint32_t(4)) * sizeof(uint32_t)); shader_object_.push_back(kDxbcRdefVariableFlagUsed); shader_object_.push_back( types_offset + uint32_t(RdefTypeIndex::kUint4DescriptorIndexArray) * type_size); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(0); new_offset += constant_size; } // *************************************************************************** // Constant buffers // *************************************************************************** // Write the names. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); uint32_t cbuffer_name_offset_system = new_offset; if (cbuffer_index_system_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_system_cbuffer"); } uint32_t cbuffer_name_offset_float = new_offset; if (cbuffer_index_float_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_float_cbuffer"); } uint32_t cbuffer_name_offset_bool_loop = new_offset; if (cbuffer_index_bool_loop_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_bool_loop_cbuffer"); } uint32_t cbuffer_name_offset_fetch = new_offset; if (cbuffer_index_fetch_constants_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_fetch_cbuffer"); } uint32_t cbuffer_name_offset_descriptor_indices = new_offset; if (cbuffer_index_descriptor_indices_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_descriptor_indices_cbuffer"); } // Write the offset to the header. shader_object_[chunk_position_dwords + 1] = new_offset; // Write all the constant buffers, sorted by their binding index. for (uint32_t i = 0; i < cbuffer_count_; ++i) { if (i == cbuffer_index_system_constants_) { shader_object_.push_back(cbuffer_name_offset_system); shader_object_.push_back(kSysConst_Count); shader_object_.push_back(constant_offset_system); shader_object_.push_back( uint32_t(xe::align(sizeof(SystemConstants), 4 * sizeof(uint32_t)))); shader_object_.push_back(uint32_t(DxbcRdefCbufferType::kCbuffer)); // No D3D_SHADER_CBUFFER_FLAGS. shader_object_.push_back(0); } else if (i == cbuffer_index_float_constants_) { assert_not_zero(constant_register_map().float_count); shader_object_.push_back(cbuffer_name_offset_float); shader_object_.push_back(1); shader_object_.push_back(constant_offset_float); shader_object_.push_back(constant_register_map().float_count * 4 * sizeof(float)); shader_object_.push_back(uint32_t(DxbcRdefCbufferType::kCbuffer)); shader_object_.push_back(0); } else if (i == cbuffer_index_bool_loop_constants_) { shader_object_.push_back(cbuffer_name_offset_bool_loop); // Bool constants and loop constants are separate for easier debugging. shader_object_.push_back(2); shader_object_.push_back(constant_offset_bool_loop); shader_object_.push_back((2 + 8) * 4 * sizeof(uint32_t)); shader_object_.push_back(uint32_t(DxbcRdefCbufferType::kCbuffer)); shader_object_.push_back(0); } else if (i == cbuffer_index_fetch_constants_) { shader_object_.push_back(cbuffer_name_offset_fetch); shader_object_.push_back(1); shader_object_.push_back(constant_offset_fetch); shader_object_.push_back(32 * 6 * sizeof(uint32_t)); shader_object_.push_back(uint32_t(DxbcRdefCbufferType::kCbuffer)); shader_object_.push_back(0); } else if (i == cbuffer_index_descriptor_indices_) { assert_not_zero(GetBindlessResourceCount()); shader_object_.push_back(cbuffer_name_offset_descriptor_indices); shader_object_.push_back(1); shader_object_.push_back(constant_offset_descriptor_indices); shader_object_.push_back( xe::align(GetBindlessResourceCount(), uint32_t(4)) * sizeof(uint32_t)); shader_object_.push_back(uint32_t(DxbcRdefCbufferType::kCbuffer)); shader_object_.push_back(0); } else { assert_unhandled_case(i); } } // *************************************************************************** // Bindings, in s#, t#, u#, cb# order // *************************************************************************** // Write used resource names, except for constant buffers because we have // their names already. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); uint32_t sampler_name_offset = new_offset; if (!sampler_bindings_.empty()) { if (bindless_resources_used_) { new_offset += AppendString(shader_object_, "xe_samplers"); } else { for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { new_offset += AppendString(shader_object_, sampler_bindings_[i].name.c_str()); } } } uint32_t shared_memory_srv_name_offset = new_offset; if (srv_index_shared_memory_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_shared_memory_srv"); } uint32_t bindless_textures_2d_name_offset = new_offset; uint32_t bindless_textures_3d_name_offset = new_offset; uint32_t bindless_textures_cube_name_offset = new_offset; if (bindless_resources_used_) { if (srv_index_bindless_textures_2d_ != kBindingIndexUnallocated) { bindless_textures_2d_name_offset = new_offset; new_offset += AppendString(shader_object_, "xe_textures_2d"); } if (srv_index_bindless_textures_3d_ != kBindingIndexUnallocated) { bindless_textures_3d_name_offset = new_offset; new_offset += AppendString(shader_object_, "xe_textures_3d"); } if (srv_index_bindless_textures_cube_ != kBindingIndexUnallocated) { bindless_textures_cube_name_offset = new_offset; new_offset += AppendString(shader_object_, "xe_textures_cube"); } } else { for (TextureBinding& texture_binding : texture_bindings_) { texture_binding.bindful_srv_rdef_name_offset = new_offset; new_offset += AppendString(shader_object_, texture_binding.name.c_str()); } } uint32_t shared_memory_uav_name_offset = new_offset; if (uav_index_shared_memory_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_shared_memory_uav"); } uint32_t edram_name_offset = new_offset; if (uav_index_edram_ != kBindingIndexUnallocated) { new_offset += AppendString(shader_object_, "xe_edram"); } // Write the offset to the header. shader_object_[chunk_position_dwords + 3] = new_offset; // Samplers. if (!sampler_bindings_.empty()) { if (bindless_resources_used_) { // Bindless sampler heap. shader_object_.push_back(sampler_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kSampler)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kVoid)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kUnknown)); // Multisampling not applicable. shader_object_.push_back(0); // Registers s0:*. shader_object_.push_back(0); // Unbounded number of bindings. shader_object_.push_back(0); // No DxbcRdefInputFlags. shader_object_.push_back(0); // Register space 0. shader_object_.push_back(0); // Sampler ID S0. shader_object_.push_back(0); } else { // Bindful samplers. uint32_t sampler_current_name_offset = sampler_name_offset; for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { const SamplerBinding& sampler_binding = sampler_bindings_[i]; shader_object_.push_back(sampler_current_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kSampler)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kVoid)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kUnknown)); // Multisampling not applicable. shader_object_.push_back(0); // Register s[i]. shader_object_.push_back(i); // One binding. shader_object_.push_back(1); // No DxbcRdefInputFlags. shader_object_.push_back(0); // Register space 0. shader_object_.push_back(0); // Sampler ID S[i]. shader_object_.push_back(i); sampler_current_name_offset += GetStringLength(sampler_binding.name.c_str()); } } } // Shader resource views, sorted by binding index. for (uint32_t i = 0; i < srv_count_; ++i) { if (i == srv_index_shared_memory_) { // Shared memory (when memexport isn't used in the pipeline). shader_object_.push_back(shared_memory_srv_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kByteAddress)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kMixed)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kSRVBuffer)); // Multisampling not applicable. shader_object_.push_back(0); shader_object_.push_back(uint32_t(SRVMainRegister::kSharedMemory)); // One binding. shader_object_.push_back(1); // No DxbcRdefInputFlags. shader_object_.push_back(0); shader_object_.push_back(uint32_t(SRVSpace::kMain)); } else { uint32_t texture_name_offset; DxbcRdefDimension texture_dimension; uint32_t texture_register; uint32_t texture_register_count; SRVSpace texture_register_space; if (bindless_resources_used_) { // Bindless texture heap. if (i == srv_index_bindless_textures_3d_) { texture_name_offset = bindless_textures_3d_name_offset; texture_dimension = DxbcRdefDimension::kSRVTexture3D; texture_register_space = SRVSpace::kBindlessTextures3D; } else if (i == srv_index_bindless_textures_cube_) { texture_name_offset = bindless_textures_cube_name_offset; texture_dimension = DxbcRdefDimension::kSRVTextureCube; texture_register_space = SRVSpace::kBindlessTexturesCube; } else { assert_true(i == srv_index_bindless_textures_2d_); texture_name_offset = bindless_textures_2d_name_offset; texture_dimension = DxbcRdefDimension::kSRVTexture2DArray; texture_register_space = SRVSpace::kBindlessTextures2DArray; } texture_register = 0; texture_register_count = 0; } else { // Bindful texture. auto it = texture_bindings_for_bindful_srv_indices_.find(i); assert_true(it != texture_bindings_for_bindful_srv_indices_.end()); uint32_t texture_binding_index = it->second; const TextureBinding& texture_binding = texture_bindings_[texture_binding_index]; texture_name_offset = texture_binding.bindful_srv_rdef_name_offset; switch (texture_binding.dimension) { case xenos::FetchOpDimension::k3DOrStacked: texture_dimension = DxbcRdefDimension::kSRVTexture3D; break; case xenos::FetchOpDimension::kCube: texture_dimension = DxbcRdefDimension::kSRVTextureCube; break; default: assert_true(texture_binding.dimension == xenos::FetchOpDimension::k2D); texture_dimension = DxbcRdefDimension::kSRVTexture2DArray; } texture_register = uint32_t(SRVMainRegister::kBindfulTexturesStart) + texture_binding_index; texture_register_count = 1; texture_register_space = SRVSpace::kMain; } shader_object_.push_back(texture_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kTexture)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kFloat)); shader_object_.push_back(uint32_t(texture_dimension)); // Not multisampled. shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(texture_register); shader_object_.push_back(texture_register_count); // 4-component. shader_object_.push_back(DxbcRdefInputFlagsComponents); shader_object_.push_back(uint32_t(texture_register_space)); } // SRV ID T[i]. shader_object_.push_back(i); } // Unordered access views, sorted by binding index. for (uint32_t i = 0; i < uav_count_; ++i) { if (i == uav_index_shared_memory_) { // Shared memory (when memexport is used in the pipeline). shader_object_.push_back(shared_memory_uav_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kUAVRWByteAddress)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kMixed)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kUAVBuffer)); // Multisampling not applicable. shader_object_.push_back(0); shader_object_.push_back(uint32_t(UAVRegister::kSharedMemory)); // One binding. shader_object_.push_back(1); // No DxbcRdefInputFlags. shader_object_.push_back(0); // Register space 0. shader_object_.push_back(0); } else if (i == uav_index_edram_) { // EDRAM R32_UINT buffer. shader_object_.push_back(edram_name_offset); shader_object_.push_back(uint32_t(DxbcRdefInputType::kUAVRWTyped)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kUInt)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kUAVBuffer)); // Not multisampled. shader_object_.push_back(0xFFFFFFFFu); shader_object_.push_back(uint32_t(UAVRegister::kEdram)); // One binding. shader_object_.push_back(1); // No DxbcRdefInputFlags. shader_object_.push_back(0); // Register space 0. shader_object_.push_back(0); } else { assert_unhandled_case(i); } // UAV ID U[i]. shader_object_.push_back(i); } // Constant buffers. for (uint32_t i = 0; i < cbuffer_count_; ++i) { uint32_t register_index = 0; if (i == cbuffer_index_system_constants_) { shader_object_.push_back(cbuffer_name_offset_system); register_index = uint32_t(CbufferRegister::kSystemConstants); } else if (i == cbuffer_index_float_constants_) { shader_object_.push_back(cbuffer_name_offset_float); register_index = uint32_t(CbufferRegister::kFloatConstants); } else if (i == cbuffer_index_bool_loop_constants_) { shader_object_.push_back(cbuffer_name_offset_bool_loop); register_index = uint32_t(CbufferRegister::kBoolLoopConstants); } else if (i == cbuffer_index_fetch_constants_) { shader_object_.push_back(cbuffer_name_offset_fetch); register_index = uint32_t(CbufferRegister::kFetchConstants); } else if (i == cbuffer_index_descriptor_indices_) { shader_object_.push_back(cbuffer_name_offset_descriptor_indices); register_index = uint32_t(CbufferRegister::kDescriptorIndices); } else { assert_unhandled_case(i); } shader_object_.push_back(uint32_t(DxbcRdefInputType::kCbuffer)); shader_object_.push_back(uint32_t(DxbcRdefReturnType::kVoid)); shader_object_.push_back(uint32_t(DxbcRdefDimension::kUnknown)); // Multisampling not applicable. shader_object_.push_back(0); shader_object_.push_back(register_index); // One binding. shader_object_.push_back(1); // Like `cbuffer`, don't need `ConstantBuffer` properties. shader_object_.push_back(DxbcRdefInputFlagUserPacked); // Register space 0. shader_object_.push_back(0); // CBV ID CB[i]. shader_object_.push_back(i); } } void DxbcShaderTranslator::WriteInputSignature() { // Because of shader_object_.resize(), pointers can't be kept persistently // here! Resize also zeroes the memory. uint32_t chunk_position = uint32_t(shader_object_.size()); // Reserve space for the header. shader_object_.resize(shader_object_.size() + sizeof(DxbcSignature) / sizeof(uint32_t)); uint32_t parameter_count = 0; constexpr size_t kParameterDwords = sizeof(DxbcSignatureParameter) / sizeof(uint32_t); if (IsDxbcVertexShader()) { // Unswapped vertex index (SV_VertexID). size_t vertex_id_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& vertex_id = *reinterpret_cast(shader_object_.data() + vertex_id_position); vertex_id.system_value = DxbcName::kVertexID; vertex_id.component_type = DxbcSignatureRegisterComponentType::kUInt32; vertex_id.register_index = uint32_t(InOutRegister::kVSInVertexIndex); vertex_id.mask = 0b0001; vertex_id.always_reads_mask = (register_count() >= 1) ? 0b0001 : 0b0000; } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); { DxbcSignatureParameter& vertex_id = *reinterpret_cast(shader_object_.data() + vertex_id_position); vertex_id.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_VertexID"); } else if (IsDxbcDomainShader()) { // Control point indices, byte-swapped, biased according to the base index // and converted to float by the host vertex and hull shaders // (XEVERTEXID). Needed even for patch-indexed tessellation modes because // hull and domain shaders have strict linkage requirements, all hull shader // outputs must be declared in a domain shader, and the same hull shaders // are used for control-point-indexed and patch-indexed tessellation modes. size_t control_point_index_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& control_point_index = *reinterpret_cast( shader_object_.data() + control_point_index_position); control_point_index.component_type = DxbcSignatureRegisterComponentType::kFloat32; control_point_index.register_index = uint32_t(InOutRegister::kDSInControlPointIndex); control_point_index.mask = 0b0001; control_point_index.always_reads_mask = in_control_point_index_used_ ? 0b0001 : 0b0000; } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); { DxbcSignatureParameter& control_point_index = *reinterpret_cast( shader_object_.data() + control_point_index_position); control_point_index.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "XEVERTEXID"); } else if (IsDxbcPixelShader()) { // Written dynamically, so assume it's always used if it can be written to // any interpolator register. bool param_gen_used = !is_depth_only_pixel_shader_ && register_count() != 0; // Intepolators (TEXCOORD#). size_t interpolator_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + xenos::kMaxInterpolators * kParameterDwords); parameter_count += xenos::kMaxInterpolators; { DxbcSignatureParameter* interpolators = reinterpret_cast(shader_object_.data() + interpolator_position); for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { DxbcSignatureParameter& interpolator = interpolators[i]; interpolator.semantic_index = i; interpolator.component_type = DxbcSignatureRegisterComponentType::kFloat32; interpolator.register_index = uint32_t(InOutRegister::kPSInInterpolators) + i; interpolator.mask = 0b1111; // Interpolators are copied to GPRs in the beginning of the shader. If // there's a register to copy to, this interpolator is used. interpolator.always_reads_mask = (!is_depth_only_pixel_shader_ && i < register_count()) ? 0b1111 : 0b0000; } } // Point parameters for ps_param_gen - coordinate on the point and point // size as a float3 TEXCOORD (but the size in Z is not needed). size_t point_parameters_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& point_parameters = *reinterpret_cast(shader_object_.data() + point_parameters_position); point_parameters.semantic_index = kPointParametersTexCoord; point_parameters.component_type = DxbcSignatureRegisterComponentType::kFloat32; point_parameters.register_index = uint32_t(InOutRegister::kPSInPointParameters); point_parameters.mask = 0b0111; point_parameters.always_reads_mask = param_gen_used ? 0b0011 : 0b0000; } // Z and W in clip space, for getting per-sample depth with ROV (TEXCOORD#). size_t clip_space_zw_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& clip_space_zw = *reinterpret_cast(shader_object_.data() + clip_space_zw_position); clip_space_zw.semantic_index = kClipSpaceZWTexCoord; clip_space_zw.component_type = DxbcSignatureRegisterComponentType::kFloat32; clip_space_zw.register_index = uint32_t(InOutRegister::kPSInClipSpaceZW); clip_space_zw.mask = 0b0011; clip_space_zw.always_reads_mask = edram_rov_used_ ? 0b0011 : 0b0000; } // Pixel position. Z is not needed - ROV depth testing calculates the depth // from the clip space Z/W texcoord, and if oDepth is used, it must be // written to on every execution path anyway (SV_Position). size_t position_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& position = *reinterpret_cast(shader_object_.data() + position_position); position.system_value = DxbcName::kPosition; position.component_type = DxbcSignatureRegisterComponentType::kFloat32; position.register_index = uint32_t(InOutRegister::kPSInPosition); position.mask = 0b1111; position.always_reads_mask = in_position_xy_used_ ? 0b0011 : 0b0000; } // Is front face (SV_IsFrontFace). size_t is_front_face_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& is_front_face = *reinterpret_cast(shader_object_.data() + is_front_face_position); is_front_face.system_value = DxbcName::kIsFrontFace; is_front_face.component_type = DxbcSignatureRegisterComponentType::kUInt32; is_front_face.register_index = uint32_t(InOutRegister::kPSInFrontFace); is_front_face.mask = 0b0001; is_front_face.always_reads_mask = in_front_face_used_ ? 0b0001 : 0b0000; } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); { DxbcSignatureParameter* interpolators = reinterpret_cast(shader_object_.data() + interpolator_position); for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { interpolators[i].semantic_name = semantic_offset; } DxbcSignatureParameter& point_parameters = *reinterpret_cast(shader_object_.data() + point_parameters_position); point_parameters.semantic_name = semantic_offset; DxbcSignatureParameter& clip_space_zw = *reinterpret_cast(shader_object_.data() + clip_space_zw_position); clip_space_zw.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "TEXCOORD"); { DxbcSignatureParameter& position = *reinterpret_cast(shader_object_.data() + position_position); position.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_Position"); { DxbcSignatureParameter& is_front_face = *reinterpret_cast(shader_object_.data() + is_front_face_position); is_front_face.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_IsFrontFace"); } // Header. { DxbcSignature& header = *reinterpret_cast( shader_object_.data() + chunk_position); header.parameter_count = parameter_count; header.parameter_info_offset = sizeof(DxbcSignature); } } void DxbcShaderTranslator::WritePatchConstantSignature() { assert_true(IsDxbcDomainShader()); // Because of shader_object_.resize(), pointers can't be kept persistently // here! Resize also zeroes the memory. uint32_t chunk_position = uint32_t(shader_object_.size()); // Reserve space for the header. shader_object_.resize(shader_object_.size() + sizeof(DxbcSignature) / sizeof(uint32_t)); uint32_t parameter_count = 0; constexpr size_t kParameterDwords = sizeof(DxbcSignatureParameter) / sizeof(uint32_t); // FXC always compiles with SV_TessFactor and SV_InsideTessFactor input, so // this is required even if not referenced (HS and DS have very strict // linkage, by the way, everything that HS outputs must be listed in DS // inputs). uint32_t tess_factor_edge_count = 0; DxbcName tess_factor_edge_system_value = DxbcName::kUndefined; uint32_t tess_factor_inside_count = 0; DxbcName tess_factor_inside_system_value = DxbcName::kUndefined; switch (host_vertex_shader_type()) { case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: tess_factor_edge_count = 3; tess_factor_edge_system_value = DxbcName::kFinalTriEdgeTessFactor; tess_factor_inside_count = 1; tess_factor_inside_system_value = DxbcName::kFinalTriInsideTessFactor; break; case Shader::HostVertexShaderType::kQuadDomainCPIndexed: case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: tess_factor_edge_count = 4; tess_factor_edge_system_value = DxbcName::kFinalQuadEdgeTessFactor; tess_factor_inside_count = 2; tess_factor_inside_system_value = DxbcName::kFinalQuadInsideTessFactor; break; default: // TODO(Triang3l): Support line patches. assert_unhandled_case(host_vertex_shader_type()); EmitTranslationError( "Unsupported host vertex shader type in WritePatchConstantSignature"); } // Edge tessellation factors (SV_TessFactor). size_t tess_factor_edge_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + tess_factor_edge_count * kParameterDwords); parameter_count += tess_factor_edge_count; { DxbcSignatureParameter* tess_factors_edge = reinterpret_cast(shader_object_.data() + tess_factor_edge_position); for (uint32_t i = 0; i < tess_factor_edge_count; ++i) { DxbcSignatureParameter& tess_factor_edge = tess_factors_edge[i]; tess_factor_edge.semantic_index = i; tess_factor_edge.system_value = tess_factor_edge_system_value; tess_factor_edge.component_type = DxbcSignatureRegisterComponentType::kFloat32; // Not using any of these, just assigning consecutive registers. tess_factor_edge.register_index = i; tess_factor_edge.mask = 0b0001; } } // Inside tessellation factors (SV_InsideTessFactor). size_t tess_factor_inside_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + tess_factor_inside_count * kParameterDwords); parameter_count += tess_factor_inside_count; { DxbcSignatureParameter* tess_factors_inside = reinterpret_cast(shader_object_.data() + tess_factor_inside_position); for (uint32_t i = 0; i < tess_factor_inside_count; ++i) { DxbcSignatureParameter& tess_factor_inside = tess_factors_inside[i]; tess_factor_inside.semantic_index = i; tess_factor_inside.system_value = tess_factor_inside_system_value; tess_factor_inside.component_type = DxbcSignatureRegisterComponentType::kFloat32; // Not using any of these, just assigning consecutive registers. tess_factor_inside.register_index = tess_factor_edge_count + i; tess_factor_inside.mask = 0b0001; } } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); { DxbcSignatureParameter* tess_factors_edge = reinterpret_cast(shader_object_.data() + tess_factor_edge_position); for (uint32_t i = 0; i < tess_factor_edge_count; ++i) { tess_factors_edge[i].semantic_name = semantic_offset; } } semantic_offset += AppendString(shader_object_, "SV_TessFactor"); { DxbcSignatureParameter* tess_factors_inside = reinterpret_cast(shader_object_.data() + tess_factor_inside_position); for (uint32_t i = 0; i < tess_factor_inside_count; ++i) { tess_factors_inside[i].semantic_name = semantic_offset; } } semantic_offset += AppendString(shader_object_, "SV_InsideTessFactor"); // Header. { DxbcSignature& header = *reinterpret_cast( shader_object_.data() + chunk_position); header.parameter_count = parameter_count; header.parameter_info_offset = sizeof(DxbcSignature); } } void DxbcShaderTranslator::WriteOutputSignature() { // Because of shader_object_.resize(), pointers can't be kept persistently // here! Resize also zeroes the memory. uint32_t chunk_position = uint32_t(shader_object_.size()); // Reserve space for the header. shader_object_.resize(shader_object_.size() + sizeof(DxbcSignature) / sizeof(uint32_t)); uint32_t parameter_count = 0; constexpr size_t kParameterDwords = sizeof(DxbcSignatureParameter) / sizeof(uint32_t); if (IsDxbcVertexOrDomainShader()) { // Intepolators (TEXCOORD#). size_t interpolator_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + xenos::kMaxInterpolators * kParameterDwords); parameter_count += xenos::kMaxInterpolators; { DxbcSignatureParameter* interpolators = reinterpret_cast(shader_object_.data() + interpolator_position); for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { DxbcSignatureParameter& interpolator = interpolators[i]; interpolator.semantic_index = i; interpolator.component_type = DxbcSignatureRegisterComponentType::kFloat32; interpolator.register_index = uint32_t(InOutRegister::kVSDSOutInterpolators) + i; interpolator.mask = 0b1111; } } // Point parameters - coordinate on the point and point size as a float3 // TEXCOORD. Always used because reset to (0, 0, -1). size_t point_parameters_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& point_parameters = *reinterpret_cast(shader_object_.data() + point_parameters_position); point_parameters.semantic_index = kPointParametersTexCoord; point_parameters.component_type = DxbcSignatureRegisterComponentType::kFloat32; point_parameters.register_index = uint32_t(InOutRegister::kVSDSOutPointParameters); point_parameters.mask = 0b0111; point_parameters.never_writes_mask = 0b1000; } // Z and W in clip space, for getting per-sample depth with ROV (TEXCOORD#). size_t clip_space_zw_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& clip_space_zw = *reinterpret_cast(shader_object_.data() + clip_space_zw_position); clip_space_zw.semantic_index = kClipSpaceZWTexCoord; clip_space_zw.component_type = DxbcSignatureRegisterComponentType::kFloat32; clip_space_zw.register_index = uint32_t(InOutRegister::kVSDSOutClipSpaceZW); clip_space_zw.mask = 0b0011; clip_space_zw.never_writes_mask = 0b1100; } // Position (SV_Position). size_t position_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& position = *reinterpret_cast(shader_object_.data() + position_position); position.system_value = DxbcName::kPosition; position.component_type = DxbcSignatureRegisterComponentType::kFloat32; position.register_index = uint32_t(InOutRegister::kVSDSOutPosition); position.mask = 0b1111; } // Clip (SV_ClipDistance) and cull (SV_CullDistance) distances. size_t clip_distance_0123_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& clip_distance_0123 = *reinterpret_cast( shader_object_.data() + clip_distance_0123_position); clip_distance_0123.system_value = DxbcName::kClipDistance; clip_distance_0123.component_type = DxbcSignatureRegisterComponentType::kFloat32; clip_distance_0123.register_index = uint32_t(InOutRegister::kVSDSOutClipDistance0123); clip_distance_0123.mask = 0b1111; } size_t clip_distance_45_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& clip_distance_45 = *reinterpret_cast(shader_object_.data() + clip_distance_45_position); clip_distance_45.semantic_index = 1; clip_distance_45.system_value = DxbcName::kClipDistance; clip_distance_45.component_type = DxbcSignatureRegisterComponentType::kFloat32; clip_distance_45.register_index = uint32_t(InOutRegister::kVSDSOutClipDistance45AndCullDistance); clip_distance_45.mask = 0b0011; clip_distance_45.never_writes_mask = 0b1100; } size_t cull_distance_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; { DxbcSignatureParameter& cull_distance = *reinterpret_cast(shader_object_.data() + cull_distance_position); cull_distance.system_value = DxbcName::kCullDistance; cull_distance.component_type = DxbcSignatureRegisterComponentType::kFloat32; cull_distance.register_index = uint32_t(InOutRegister::kVSDSOutClipDistance45AndCullDistance); cull_distance.mask = 0b0100; cull_distance.never_writes_mask = 0b1011; } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); { DxbcSignatureParameter* interpolators = reinterpret_cast(shader_object_.data() + interpolator_position); for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { interpolators[i].semantic_name = semantic_offset; } DxbcSignatureParameter& point_parameters = *reinterpret_cast(shader_object_.data() + point_parameters_position); point_parameters.semantic_name = semantic_offset; DxbcSignatureParameter& clip_space_zw = *reinterpret_cast(shader_object_.data() + clip_space_zw_position); clip_space_zw.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "TEXCOORD"); { DxbcSignatureParameter& position = *reinterpret_cast(shader_object_.data() + position_position); position.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_Position"); { DxbcSignatureParameter& clip_distance_0123 = *reinterpret_cast( shader_object_.data() + clip_distance_0123_position); clip_distance_0123.semantic_name = semantic_offset; DxbcSignatureParameter& clip_distance_45 = *reinterpret_cast(shader_object_.data() + clip_distance_45_position); clip_distance_45.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_ClipDistance"); { DxbcSignatureParameter& cull_distance = *reinterpret_cast(shader_object_.data() + cull_distance_position); cull_distance.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_CullDistance"); } else if (IsDxbcPixelShader()) { if (!edram_rov_used_) { // Color render targets (SV_Target#). size_t target_position = SIZE_MAX; if (writes_any_color_target()) { target_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + 4 * kParameterDwords); parameter_count += 4; DxbcSignatureParameter* targets = reinterpret_cast(shader_object_.data() + target_position); for (uint32_t i = 0; i < 4; ++i) { DxbcSignatureParameter& target = targets[i]; target.semantic_index = i; target.component_type = DxbcSignatureRegisterComponentType::kFloat32; target.register_index = i; target.mask = 0b1111; // All are always written because X360 RTs are dynamically remapped to // D3D12 RTs to make RT indices consecutive. } } // Depth (SV_Depth). size_t depth_position = SIZE_MAX; if (writes_depth()) { depth_position = shader_object_.size(); shader_object_.resize(shader_object_.size() + kParameterDwords); ++parameter_count; DxbcSignatureParameter& depth = *reinterpret_cast(shader_object_.data() + depth_position); depth.component_type = DxbcSignatureRegisterComponentType::kFloat32; depth.register_index = UINT32_MAX; depth.mask = 0b0001; depth.never_writes_mask = 0b1110; } // Semantic names. uint32_t semantic_offset = uint32_t((shader_object_.size() - chunk_position) * sizeof(uint32_t)); if (target_position != SIZE_MAX) { { DxbcSignatureParameter* targets = reinterpret_cast(shader_object_.data() + target_position); for (uint32_t i = 0; i < 4; ++i) { targets[i].semantic_name = semantic_offset; } } semantic_offset += AppendString(shader_object_, "SV_Target"); } if (depth_position != SIZE_MAX) { { DxbcSignatureParameter& depth = *reinterpret_cast(shader_object_.data() + depth_position); depth.semantic_name = semantic_offset; } semantic_offset += AppendString(shader_object_, "SV_Depth"); } } } // Header. { DxbcSignature& header = *reinterpret_cast( shader_object_.data() + chunk_position); header.parameter_count = parameter_count; header.parameter_info_offset = sizeof(DxbcSignature); } } void DxbcShaderTranslator::WriteShaderCode() { uint32_t chunk_position_dwords = uint32_t(shader_object_.size()); uint32_t shader_type; if (IsDxbcVertexShader()) { shader_type = D3D10_SB_VERTEX_SHADER; } else if (IsDxbcDomainShader()) { shader_type = D3D11_SB_DOMAIN_SHADER; } else { assert_true(IsDxbcPixelShader()); shader_type = D3D10_SB_PIXEL_SHADER; } shader_object_.push_back( ENCODE_D3D10_SB_TOKENIZED_PROGRAM_VERSION_TOKEN(shader_type, 5, 1)); // Reserve space for the length token. shader_object_.push_back(0); // Declarations (don't increase the instruction count stat, and only inputs // and outputs are counted in dcl_count). // // Binding declarations have 3D-indexed operands with XYZW swizzle, the first // index being the binding ID (local to the shader), the second being the // lower register index bound, and the third being the highest register index // bound. Also dcl_ instructions for bindings are followed by the register // space index. // // Inputs/outputs have 1D-indexed operands with a component mask and a // register index. if (IsDxbcDomainShader()) { // Not using control point data since Xenos only has a vertex shader acting // as both vertex shader and domain shader. stat_.c_control_points = 3; stat_.tessellator_domain = DxbcTessellatorDomain::kTriangle; switch (host_vertex_shader_type()) { case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: stat_.c_control_points = 3; stat_.tessellator_domain = DxbcTessellatorDomain::kTriangle; break; case Shader::HostVertexShaderType::kQuadDomainCPIndexed: case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: stat_.c_control_points = 4; stat_.tessellator_domain = DxbcTessellatorDomain::kQuad; break; default: // TODO(Triang3l): Support line patches. assert_unhandled_case(host_vertex_shader_type()); EmitTranslationError( "Unsupported host vertex shader type in WriteShaderCode"); } shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE( D3D11_SB_OPCODE_DCL_INPUT_CONTROL_POINT_COUNT) | ENCODE_D3D11_SB_INPUT_CONTROL_POINT_COUNT(stat_.c_control_points) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(1)); shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D11_SB_OPCODE_DCL_TESS_DOMAIN) | ENCODE_D3D11_SB_TESS_DOMAIN(uint32_t(stat_.tessellator_domain)) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(1)); } // Don't allow refactoring when converting to native code to maintain position // invariance (needed even in pixel shaders for oDepth invariance). Also this // dcl will be modified by ForceEarlyDepthStencil. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_GLOBAL_FLAGS) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(1)); // Constant buffers, from most frequenly accessed to least frequently accessed // (the order is a hint to the driver according to the DXBC header). if (cbuffer_index_float_constants_ != kBindingIndexUnallocated) { assert_not_zero(constant_register_map().float_count); shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_CONSTANT_BUFFER) | ENCODE_D3D10_SB_D3D10_SB_CONSTANT_BUFFER_ACCESS_PATTERN( constant_register_map().float_dynamic_addressing ? D3D10_SB_CONSTANT_BUFFER_DYNAMIC_INDEXED : D3D10_SB_CONSTANT_BUFFER_IMMEDIATE_INDEXED) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index_float_constants_); shader_object_.push_back(uint32_t(CbufferRegister::kFloatConstants)); shader_object_.push_back(uint32_t(CbufferRegister::kFloatConstants)); shader_object_.push_back(constant_register_map().float_count); shader_object_.push_back(0); } if (cbuffer_index_system_constants_ != kBindingIndexUnallocated) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_CONSTANT_BUFFER) | ENCODE_D3D10_SB_D3D10_SB_CONSTANT_BUFFER_ACCESS_PATTERN( D3D10_SB_CONSTANT_BUFFER_IMMEDIATE_INDEXED) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index_system_constants_); shader_object_.push_back(uint32_t(CbufferRegister::kSystemConstants)); shader_object_.push_back(uint32_t(CbufferRegister::kSystemConstants)); shader_object_.push_back((sizeof(SystemConstants) + 15) >> 4); shader_object_.push_back(0); } if (cbuffer_index_fetch_constants_ != kBindingIndexUnallocated) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_CONSTANT_BUFFER) | ENCODE_D3D10_SB_D3D10_SB_CONSTANT_BUFFER_ACCESS_PATTERN( D3D10_SB_CONSTANT_BUFFER_IMMEDIATE_INDEXED) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index_fetch_constants_); shader_object_.push_back(uint32_t(CbufferRegister::kFetchConstants)); shader_object_.push_back(uint32_t(CbufferRegister::kFetchConstants)); shader_object_.push_back(48); shader_object_.push_back(0); } if (cbuffer_index_descriptor_indices_ != kBindingIndexUnallocated) { assert_not_zero(GetBindlessResourceCount()); shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_CONSTANT_BUFFER) | ENCODE_D3D10_SB_D3D10_SB_CONSTANT_BUFFER_ACCESS_PATTERN( D3D10_SB_CONSTANT_BUFFER_IMMEDIATE_INDEXED) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index_descriptor_indices_); shader_object_.push_back(uint32_t(CbufferRegister::kDescriptorIndices)); shader_object_.push_back(uint32_t(CbufferRegister::kDescriptorIndices)); shader_object_.push_back((GetBindlessResourceCount() + 3) >> 2); shader_object_.push_back(0); } if (cbuffer_index_bool_loop_constants_ != kBindingIndexUnallocated) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_CONSTANT_BUFFER) | ENCODE_D3D10_SB_D3D10_SB_CONSTANT_BUFFER_ACCESS_PATTERN( D3D10_SB_CONSTANT_BUFFER_IMMEDIATE_INDEXED) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index_bool_loop_constants_); shader_object_.push_back(uint32_t(CbufferRegister::kBoolLoopConstants)); shader_object_.push_back(uint32_t(CbufferRegister::kBoolLoopConstants)); shader_object_.push_back(10); shader_object_.push_back(0); } // Samplers. if (!sampler_bindings_.empty()) { if (bindless_resources_used_) { // Bindless sampler heap. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_SAMPLER) | ENCODE_D3D10_SB_SAMPLER_MODE(D3D10_SB_SAMPLER_MODE_DEFAULT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(6)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_SAMPLER, kSwizzleXYZW, 3)); shader_object_.push_back(0); shader_object_.push_back(0); shader_object_.push_back(UINT32_MAX); shader_object_.push_back(0); } else { // Bindful samplers. for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { const SamplerBinding& sampler_binding = sampler_bindings_[i]; shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_SAMPLER) | ENCODE_D3D10_SB_SAMPLER_MODE(D3D10_SB_SAMPLER_MODE_DEFAULT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(6)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_SAMPLER, kSwizzleXYZW, 3)); shader_object_.push_back(i); shader_object_.push_back(i); shader_object_.push_back(i); shader_object_.push_back(0); } } } // Shader resource views, sorted by binding index. for (uint32_t i = 0; i < srv_count_; ++i) { if (i == srv_index_shared_memory_) { // Shared memory ByteAddressBuffer. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D11_SB_OPCODE_DCL_RESOURCE_RAW) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(6)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_RESOURCE, kSwizzleXYZW, 3)); shader_object_.push_back(srv_index_shared_memory_); shader_object_.push_back(uint32_t(SRVMainRegister::kSharedMemory)); shader_object_.push_back(uint32_t(SRVMainRegister::kSharedMemory)); shader_object_.push_back(uint32_t(SRVSpace::kMain)); } else { // Texture or texture heap. D3D10_SB_RESOURCE_DIMENSION texture_srv_dimension; uint32_t texture_register_first, texture_register_last; SRVSpace texture_register_space; if (bindless_resources_used_) { // Bindless texture heap. texture_register_first = 0; texture_register_last = UINT32_MAX; if (i == srv_index_bindless_textures_3d_) { texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE3D; texture_register_space = SRVSpace::kBindlessTextures3D; } else if (i == srv_index_bindless_textures_cube_) { texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURECUBE; texture_register_space = SRVSpace::kBindlessTexturesCube; } else { assert_true(i == srv_index_bindless_textures_2d_); texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE2DARRAY; texture_register_space = SRVSpace::kBindlessTextures2DArray; } } else { // Bindful texture. auto it = texture_bindings_for_bindful_srv_indices_.find(i); assert_true(it != texture_bindings_for_bindful_srv_indices_.end()); uint32_t texture_binding_index = it->second; const TextureBinding& texture_binding = texture_bindings_[texture_binding_index]; switch (texture_binding.dimension) { case xenos::FetchOpDimension::k3DOrStacked: texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE3D; break; case xenos::FetchOpDimension::kCube: texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURECUBE; break; default: assert_true(texture_binding.dimension == xenos::FetchOpDimension::k2D); texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE2DARRAY; } texture_register_first = texture_register_last = uint32_t(SRVMainRegister::kBindfulTexturesStart) + texture_binding_index; texture_register_space = SRVSpace::kMain; } shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_RESOURCE) | ENCODE_D3D10_SB_RESOURCE_DIMENSION(texture_srv_dimension) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_RESOURCE, kSwizzleXYZW, 3)); shader_object_.push_back(i); shader_object_.push_back(texture_register_first); shader_object_.push_back(texture_register_last); shader_object_.push_back( ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 0) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 1) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 2) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 3)); shader_object_.push_back(uint32_t(texture_register_space)); } } // Unordered access views, sorted by binding index. for (uint32_t i = 0; i < uav_count_; ++i) { if (i == uav_index_shared_memory_) { // Shared memory RWByteAddressBuffer. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE( D3D11_SB_OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(6)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D11_SB_OPERAND_TYPE_UNORDERED_ACCESS_VIEW, kSwizzleXYZW, 3)); shader_object_.push_back(uav_index_shared_memory_); shader_object_.push_back(uint32_t(UAVRegister::kSharedMemory)); shader_object_.push_back(uint32_t(UAVRegister::kSharedMemory)); shader_object_.push_back(0); } else if (i == uav_index_edram_) { // EDRAM buffer R32_UINT rasterizer-ordered view. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE( D3D11_SB_OPCODE_DCL_UNORDERED_ACCESS_VIEW_TYPED) | ENCODE_D3D10_SB_RESOURCE_DIMENSION( D3D10_SB_RESOURCE_DIMENSION_BUFFER) | D3D11_SB_RASTERIZER_ORDERED_ACCESS | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7)); shader_object_.push_back(EncodeVectorSwizzledOperand( D3D11_SB_OPERAND_TYPE_UNORDERED_ACCESS_VIEW, kSwizzleXYZW, 3)); shader_object_.push_back(uav_index_edram_); shader_object_.push_back(uint32_t(UAVRegister::kEdram)); shader_object_.push_back(uint32_t(UAVRegister::kEdram)); shader_object_.push_back( ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_UINT, 0) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_UINT, 1) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_UINT, 2) | ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_UINT, 3)); shader_object_.push_back(0); } else { assert_unhandled_case(i); } } // Inputs and outputs. if (IsDxbcVertexOrDomainShader()) { if (IsDxbcDomainShader()) { if (in_domain_location_used_) { // Domain location input. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D11_SB_OPERAND_TYPE_INPUT_DOMAIN_POINT, in_domain_location_used_, 0)); ++stat_.dcl_count; } if (in_primitive_id_used_) { // Primitive (patch) index input. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); shader_object_.push_back( EncodeScalarOperand(D3D10_SB_OPERAND_TYPE_INPUT_PRIMITIVEID, 0)); ++stat_.dcl_count; } if (in_control_point_index_used_) { // Control point indices as float input. uint32_t control_point_array_size; switch (host_vertex_shader_type()) { case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: control_point_array_size = 3; break; case Shader::HostVertexShaderType::kQuadDomainCPIndexed: control_point_array_size = 4; break; default: // TODO(Triang3l): Support line patches. assert_unhandled_case(host_vertex_shader_type()); EmitTranslationError( "Unsupported host vertex shader type in " "StartVertexOrDomainShader"); control_point_array_size = 0; } if (control_point_array_size) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back(EncodeVectorMaskedOperand( D3D11_SB_OPERAND_TYPE_INPUT_CONTROL_POINT, 0b0001, 2)); shader_object_.push_back(control_point_array_size); shader_object_.push_back( uint32_t(InOutRegister::kDSInControlPointIndex)); ++stat_.dcl_count; } } } else { if (register_count()) { // Unswapped vertex index input (only X component). shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_SGV) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b0001, 1)); shader_object_.push_back(uint32_t(InOutRegister::kVSInVertexIndex)); shader_object_.push_back(ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_VERTEX_ID)); ++stat_.dcl_count; } } // Interpolator output. for (uint32_t i = 0; i < xenos::kMaxInterpolators; ++i) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_OUTPUT, 0b1111, 1)); shader_object_.push_back(uint32_t(InOutRegister::kVSDSOutInterpolators) + i); ++stat_.dcl_count; } // Point parameters output. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_OUTPUT, 0b0111, 1)); shader_object_.push_back(uint32_t(InOutRegister::kVSDSOutPointParameters)); ++stat_.dcl_count; // Clip space Z and W output. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_OUTPUT, 0b0011, 1)); shader_object_.push_back(uint32_t(InOutRegister::kVSDSOutClipSpaceZW)); ++stat_.dcl_count; // Position output. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT_SIV) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_OUTPUT, 0b1111, 1)); shader_object_.push_back(uint32_t(InOutRegister::kVSDSOutPosition)); shader_object_.push_back(ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_POSITION)); ++stat_.dcl_count; // Clip distance outputs. for (uint32_t i = 0; i < 2; ++i) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT_SIV) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back(EncodeVectorMaskedOperand( D3D10_SB_OPERAND_TYPE_OUTPUT, i ? 0b0011 : 0b1111, 1)); shader_object_.push_back( uint32_t(InOutRegister::kVSDSOutClipDistance0123) + i); shader_object_.push_back( ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_CLIP_DISTANCE)); ++stat_.dcl_count; } // Cull distance output. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT_SIV) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_OUTPUT, 0b0100, 1)); shader_object_.push_back( uint32_t(InOutRegister::kVSDSOutClipDistance45AndCullDistance)); shader_object_.push_back(ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_CULL_DISTANCE)); ++stat_.dcl_count; } else if (IsDxbcPixelShader()) { // Interpolator input. if (!is_depth_only_pixel_shader_) { uint32_t interpolator_count = std::min(xenos::kMaxInterpolators, register_count()); for (uint32_t i = 0; i < interpolator_count; ++i) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_PS) | ENCODE_D3D10_SB_INPUT_INTERPOLATION_MODE( D3D10_SB_INTERPOLATION_LINEAR) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b1111, 1)); shader_object_.push_back(uint32_t(InOutRegister::kPSInInterpolators) + i); ++stat_.dcl_count; } if (register_count()) { // Point parameters input (only coordinates, not size, needed). shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_PS) | ENCODE_D3D10_SB_INPUT_INTERPOLATION_MODE( D3D10_SB_INTERPOLATION_LINEAR) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b0011, 1)); shader_object_.push_back(uint32_t(InOutRegister::kPSInPointParameters)); ++stat_.dcl_count; } } if (edram_rov_used_) { // Z and W in clip space, for per-sample depth. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_PS) | ENCODE_D3D10_SB_INPUT_INTERPOLATION_MODE( D3D10_SB_INTERPOLATION_LINEAR) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b0011, 1)); shader_object_.push_back(uint32_t(InOutRegister::kPSInClipSpaceZW)); ++stat_.dcl_count; } if (in_position_xy_used_) { // Position input (only XY needed for ps_param_gen, and the ROV depth code // calculates the depth from clip space Z and W). shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_PS_SIV) | ENCODE_D3D10_SB_INPUT_INTERPOLATION_MODE( D3D10_SB_INTERPOLATION_LINEAR_NOPERSPECTIVE) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b0011, 1)); shader_object_.push_back(uint32_t(InOutRegister::kPSInPosition)); shader_object_.push_back(ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_POSITION)); ++stat_.dcl_count; } if (in_front_face_used_) { // Is front face. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT_PS_SGV) | // This needs to be set according to FXC output, despite the // description in d3d12TokenizedProgramFormat.hpp saying bits 11:23 // are ignored. ENCODE_D3D10_SB_INPUT_INTERPOLATION_MODE( D3D10_SB_INTERPOLATION_CONSTANT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); shader_object_.push_back( EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_INPUT, 0b0001, 1)); shader_object_.push_back(uint32_t(InOutRegister::kPSInFrontFace)); shader_object_.push_back( ENCODE_D3D10_SB_NAME(D3D10_SB_NAME_IS_FRONT_FACE)); ++stat_.dcl_count; } if (edram_rov_used_) { // Sample coverage input. shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); shader_object_.push_back( EncodeScalarOperand(D3D11_SB_OPERAND_TYPE_INPUT_COVERAGE_MASK, 0)); ++stat_.dcl_count; } else { if (writes_any_color_target()) { // Color output. for (uint32_t i = 0; i < 4; ++i) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3)); shader_object_.push_back(EncodeVectorMaskedOperand( D3D10_SB_OPERAND_TYPE_OUTPUT, 0b1111, 1)); shader_object_.push_back(i); ++stat_.dcl_count; } } // Depth output. if (writes_depth()) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_OUTPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); shader_object_.push_back( EncodeScalarOperand(D3D10_SB_OPERAND_TYPE_OUTPUT_DEPTH, 0)); ++stat_.dcl_count; } } } // Temporary registers - guest general-purpose registers if not using dynamic // indexing and Xenia internal registers. stat_.temp_register_count = system_temp_count_max_; if (!is_depth_only_pixel_shader_ && !uses_register_dynamic_addressing()) { stat_.temp_register_count += register_count(); } if (stat_.temp_register_count != 0) { shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_TEMPS) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); shader_object_.push_back(stat_.temp_register_count); } // General-purpose registers if using dynamic indexing (x0). if (!is_depth_only_pixel_shader_ && uses_register_dynamic_addressing()) { assert_true(register_count() != 0); shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INDEXABLE_TEMP) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(4)); // x0. shader_object_.push_back(0); shader_object_.push_back(register_count()); // 4 components in each. shader_object_.push_back(4); stat_.temp_array_count += register_count(); } // Write the translated shader code. size_t code_size_dwords = shader_code_.size(); // So [] won't crash in case the size is zero somehow. if (code_size_dwords != 0) { shader_object_.resize(shader_object_.size() + code_size_dwords); std::memcpy(&shader_object_[shader_object_.size() - code_size_dwords], shader_code_.data(), code_size_dwords * sizeof(uint32_t)); } // Write the length. shader_object_[chunk_position_dwords + 1] = uint32_t(shader_object_.size()) - chunk_position_dwords; } } // namespace gpu } // namespace xe