/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/spirv_shader_translator.h" #include #include #include "third_party/fmt/include/fmt/format.h" #include "third_party/glslang/SPIRV/GLSL.std.450.h" #include "xenia/base/assert.h" #include "xenia/base/math.h" #include "xenia/gpu/spirv_compatibility.h" namespace xe { namespace gpu { void SpirvShaderTranslator::ProcessVertexFetchInstruction( const ParsedVertexFetchInstruction& instr) { UpdateInstructionPredication(instr.is_predicated, instr.predicate_condition); uint32_t used_result_components = instr.result.GetUsedResultComponents(); uint32_t needed_words = xenos::GetVertexFormatNeededWords( instr.attributes.data_format, used_result_components); // If this is vfetch_full, the address may still be needed for vfetch_mini - // don't exit before calculating the address. if (!needed_words && instr.is_mini_fetch) { // Nothing to load - just constant 0/1 writes, or the swizzle includes only // components that don't exist in the format (writing zero instead of them). // Unpacking assumes at least some word is needed. StoreResult(instr.result, spv::NoResult); return; } EnsureBuildPointAvailable(); uint32_t fetch_constant_word_0_index = instr.operands[1].storage_index << 1; spv::Id address; if (instr.is_mini_fetch) { // `base + index * stride` loaded by vfetch_full. address = builder_->createLoad(var_main_vfetch_address_, spv::NoPrecision); } else { // Get the base address in dwords from the bits 2:31 of the first fetch // constant word. id_vector_temp_.clear(); // The only element of the fetch constant buffer. id_vector_temp_.push_back(const_int_0_); // Vector index. id_vector_temp_.push_back( builder_->makeIntConstant(int(fetch_constant_word_0_index >> 2))); // Component index. id_vector_temp_.push_back( builder_->makeIntConstant(int(fetch_constant_word_0_index & 3))); spv::Id fetch_constant_word_0 = builder_->createLoad( builder_->createAccessChain(spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision); // TODO(Triang3l): Verify the fetch constant type (that it's a vertex fetch, // not a texture fetch) here instead of dropping draws with invalid vertex // fetch constants on the CPU when proper bound checks are added - vfetch // may be conditional, so fetch constants may also be used conditionally. address = builder_->createUnaryOp( spv::OpBitcast, type_int_, builder_->createBinOp(spv::OpShiftRightLogical, type_uint_, fetch_constant_word_0, builder_->makeUintConstant(2))); if (instr.attributes.stride) { // Convert the index to an integer by flooring or by rounding to the // nearest (as floor(index + 0.5) because rounding to the nearest even // makes no sense for addressing, both 1.5 and 2.5 would be 2). spv::Id index = GetOperandComponents( LoadOperandStorage(instr.operands[0]), instr.operands[0], 0b0001); if (instr.attributes.is_index_rounded) { index = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, index, builder_->makeFloatConstant(0.5f)); } index = builder_->createUnaryOp( spv::OpConvertFToS, type_int_, builder_->createUnaryBuiltinCall(type_float_, ext_inst_glsl_std_450_, GLSLstd450Floor, index)); if (instr.attributes.stride > 1) { index = builder_->createBinOp( spv::OpIMul, type_int_, index, builder_->makeIntConstant(int(instr.attributes.stride))); } address = builder_->createBinOp(spv::OpIAdd, type_int_, address, index); } // Store the address for the subsequent vfetch_mini. builder_->createStore(address, var_main_vfetch_address_); } if (!needed_words) { // The vfetch_full address has been loaded for the subsequent vfetch_mini, // but there's no data to load. StoreResult(instr.result, spv::NoResult); return; } // Load the needed words. unsigned int word_composite_indices[4] = {}; spv::Id word_composite_constituents[4]; uint32_t word_count = 0; uint32_t words_remaining = needed_words; uint32_t word_index; while (xe::bit_scan_forward(words_remaining, &word_index)) { words_remaining &= ~(1 << word_index); spv::Id word_address = address; // Add the word offset from the instruction (signed), plus the offset of the // word within the element. int32_t word_offset = instr.attributes.offset + word_index; if (word_offset) { word_address = builder_->createBinOp(spv::OpIAdd, type_int_, word_address, builder_->makeIntConstant(int(word_offset))); } word_composite_indices[word_index] = word_count; // FIXME(Triang3l): Bound checking is not done here, but haven't encountered // any games relying on out-of-bounds access. On Adreno 200 on Android (LG // P705), however, words (not full elements) out of glBufferData bounds // contain 0. word_composite_constituents[word_count++] = LoadUint32FromSharedMemory(word_address); } spv::Id words; if (word_count > 1) { // Copying from the array to id_vector_temp_ now, not in the loop above, // because of the LoadUint32FromSharedMemory call (potentially using // id_vector_temp_ internally). id_vector_temp_.clear(); id_vector_temp_.insert(id_vector_temp_.cend(), word_composite_constituents, word_composite_constituents + word_count); words = builder_->createCompositeConstruct( type_uint_vectors_[word_count - 1], id_vector_temp_); } else { words = word_composite_constituents[0]; } // Endian swap the words, getting the endianness from bits 0:1 of the second // fetch constant word. uint32_t fetch_constant_word_1_index = fetch_constant_word_0_index + 1; id_vector_temp_.clear(); // The only element of the fetch constant buffer. id_vector_temp_.push_back(const_int_0_); // Vector index. id_vector_temp_.push_back( builder_->makeIntConstant(int(fetch_constant_word_1_index >> 2))); // Component index. id_vector_temp_.push_back( builder_->makeIntConstant(int(fetch_constant_word_1_index & 3))); spv::Id fetch_constant_word_1 = builder_->createLoad( builder_->createAccessChain(spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision); words = EndianSwap32Uint( words, builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, fetch_constant_word_1, builder_->makeUintConstant(0b11))); spv::Id result = spv::NoResult; // Convert the format. uint32_t used_format_components = used_result_components & ((1 << xenos::GetVertexFormatComponentCount( instr.attributes.data_format)) - 1); // If needed_words is not zero (checked in the beginning), this must not be // zero too. For simplicity, it's assumed that something will be unpacked // here. assert_not_zero(used_format_components); uint32_t used_format_component_count = xe::bit_count(used_format_components); spv::Id result_type = type_float_vectors_[used_format_component_count - 1]; bool format_is_packed = false; int packed_widths[4] = {}, packed_offsets[4] = {}; uint32_t packed_words[4] = {}; switch (instr.attributes.data_format) { case xenos::VertexFormat::k_8_8_8_8: format_is_packed = true; packed_widths[0] = packed_widths[1] = packed_widths[2] = packed_widths[3] = 8; packed_offsets[1] = 8; packed_offsets[2] = 16; packed_offsets[3] = 24; break; case xenos::VertexFormat::k_2_10_10_10: format_is_packed = true; packed_widths[0] = packed_widths[1] = packed_widths[2] = 10; packed_widths[3] = 2; packed_offsets[1] = 10; packed_offsets[2] = 20; packed_offsets[3] = 30; break; case xenos::VertexFormat::k_10_11_11: format_is_packed = true; packed_widths[0] = packed_widths[1] = 11; packed_widths[2] = 10; packed_offsets[1] = 11; packed_offsets[2] = 22; break; case xenos::VertexFormat::k_11_11_10: format_is_packed = true; packed_widths[0] = 10; packed_widths[1] = packed_widths[2] = 11; packed_offsets[1] = 10; packed_offsets[2] = 21; break; case xenos::VertexFormat::k_16_16: format_is_packed = true; packed_widths[0] = packed_widths[1] = 16; packed_offsets[1] = 16; break; case xenos::VertexFormat::k_16_16_16_16: format_is_packed = true; packed_widths[0] = packed_widths[1] = packed_widths[2] = packed_widths[3] = 16; packed_offsets[1] = packed_offsets[3] = 16; packed_words[2] = packed_words[3] = 1; break; case xenos::VertexFormat::k_16_16_FLOAT: case xenos::VertexFormat::k_16_16_16_16_FLOAT: { // FIXME(Triang3l): This converts from GLSL float16 with NaNs instead of // Xbox 360 float16 with extended range. However, haven't encountered // games relying on that yet. spv::Id word_needed_component_values[2] = {}; for (uint32_t i = 0; i < 2; ++i) { uint32_t word_needed_components = (used_format_components >> (i * 2)) & 0b11; if (!word_needed_components) { continue; } spv::Id word; if (word_count > 1) { word = builder_->createCompositeExtract(words, type_uint_, word_composite_indices[i]); } else { word = words; } word = builder_->createUnaryBuiltinCall(type_float2_, ext_inst_glsl_std_450_, GLSLstd450UnpackHalf2x16, word); if (word_needed_components != 0b11) { // If only one of two components is needed, extract it. word = builder_->createCompositeExtract( word, type_float_, (word_needed_components & 0b01) ? 0 : 1); } word_needed_component_values[i] = word; } if (word_needed_component_values[1] == spv::NoResult) { result = word_needed_component_values[0]; } else if (word_needed_component_values[0] == spv::NoResult) { result = word_needed_component_values[1]; } else { // Bypassing the assertion in spv::Builder::createCompositeConstruct as // of November 5, 2020 - can construct vectors by concatenating vectors, // not just from individual scalars. std::unique_ptr composite_construct_op = std::make_unique(builder_->getUniqueId(), result_type, spv::OpCompositeConstruct); composite_construct_op->addIdOperand(word_needed_component_values[0]); composite_construct_op->addIdOperand(word_needed_component_values[1]); result = composite_construct_op->getResultId(); builder_->getBuildPoint()->addInstruction( std::move(composite_construct_op)); } } break; case xenos::VertexFormat::k_32: case xenos::VertexFormat::k_32_32: case xenos::VertexFormat::k_32_32_32_32: assert_true(used_format_components == needed_words); if (instr.attributes.is_signed) { result = builder_->createUnaryOp( spv::OpBitcast, type_int_vectors_[used_format_component_count - 1], words); result = builder_->createUnaryOp(spv::OpConvertSToF, result_type, result); } else { result = builder_->createUnaryOp(spv::OpConvertUToF, result_type, words); } if (!instr.attributes.is_integer) { if (instr.attributes.is_signed) { switch (instr.attributes.signed_rf_mode) { case xenos::SignedRepeatingFractionMode::kZeroClampMinusOne: result = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, result_type, result, builder_->makeFloatConstant(1.0f / 2147483647.0f)); // No need to clamp to -1 if signed - 1/(2^31-1) is rounded to // 1/(2^31) as float32. break; case xenos::SignedRepeatingFractionMode::kNoZero: { result = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, result_type, result, builder_->makeFloatConstant(1.0f / 2147483647.5f)); spv::Id const_no_zero = builder_->makeFloatConstant(0.5f / 2147483647.5f); if (used_format_component_count > 1) { id_vector_temp_.clear(); id_vector_temp_.insert(id_vector_temp_.cend(), used_format_component_count, const_no_zero); const_no_zero = builder_->makeCompositeConstant( result_type, id_vector_temp_); } result = builder_->createNoContractionBinOp( spv::OpFAdd, result_type, result, const_no_zero); } break; default: assert_unhandled_case(instr.attributes.signed_rf_mode); } } else { result = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, result_type, result, builder_->makeFloatConstant(1.0f / 4294967295.0f)); } } break; case xenos::VertexFormat::k_32_FLOAT: case xenos::VertexFormat::k_32_32_FLOAT: case xenos::VertexFormat::k_32_32_32_32_FLOAT: case xenos::VertexFormat::k_32_32_32_FLOAT: assert_true(used_format_components == needed_words); result = builder_->createUnaryOp( spv::OpBitcast, type_float_vectors_[word_count - 1], words); break; default: assert_unhandled_case(instr.attributes.data_format); } if (format_is_packed) { assert_true(result == spv::NoResult); // Extract the components from the words as individual ints or uints. if (instr.attributes.is_signed) { // Sign-extending extraction - in GLSL the sign-extending overload accepts // int. words = builder_->createUnaryOp(spv::OpBitcast, type_int_vectors_[word_count - 1], words); } int extracted_widths[4] = {}; spv::Id extracted_components[4] = {}; uint32_t extracted_component_count = 0; unsigned int extraction_word_current_index = UINT_MAX; // Default is `words` itself if 1 word loaded. spv::Id extraction_word_current = words; for (uint32_t i = 0; i < 4; ++i) { if (!(used_format_components & (1 << i))) { continue; } if (word_count > 1) { unsigned int extraction_word_new_index = word_composite_indices[packed_words[i]]; if (extraction_word_current_index != extraction_word_new_index) { extraction_word_current_index = extraction_word_new_index; extraction_word_current = builder_->createCompositeExtract( words, instr.attributes.is_signed ? type_int_ : type_uint_, extraction_word_new_index); } } int extraction_width = packed_widths[i]; assert_not_zero(extraction_width); extracted_widths[extracted_component_count] = extraction_width; extracted_components[extracted_component_count] = builder_->createTriOp( instr.attributes.is_signed ? spv::OpBitFieldSExtract : spv::OpBitFieldUExtract, instr.attributes.is_signed ? type_int_ : type_uint_, extraction_word_current, builder_->makeIntConstant(packed_offsets[i]), builder_->makeIntConstant(extraction_width)); ++extracted_component_count; } // Combine extracted components into a vector. assert_true(extracted_component_count == used_format_component_count); if (used_format_component_count > 1) { id_vector_temp_.clear(); id_vector_temp_.insert( id_vector_temp_.cend(), extracted_components, extracted_components + used_format_component_count); result = builder_->createCompositeConstruct( instr.attributes.is_signed ? type_int_vectors_[used_format_component_count - 1] : type_uint_vectors_[used_format_component_count - 1], id_vector_temp_); } else { result = extracted_components[0]; } // Convert to floating-point. result = builder_->createUnaryOp( instr.attributes.is_signed ? spv::OpConvertSToF : spv::OpConvertUToF, result_type, result); // Normalize. if (!instr.attributes.is_integer) { float packed_scales[4]; bool packed_scales_same = true; for (uint32_t i = 0; i < used_format_component_count; ++i) { int extracted_width = extracted_widths[i]; // The signed case would result in 1.0 / 0.0 for 1-bit components, but // there are no Xenos formats with them. assert_true(extracted_width >= 2); packed_scales_same &= extracted_width != extracted_widths[0]; float packed_scale_inv; if (instr.attributes.is_signed) { packed_scale_inv = float((uint32_t(1) << (extracted_width - 1)) - 1); if (instr.attributes.signed_rf_mode == xenos::SignedRepeatingFractionMode::kNoZero) { packed_scale_inv += 0.5f; } } else { packed_scale_inv = float((uint32_t(1) << extracted_width) - 1); } packed_scales[i] = 1.0f / packed_scale_inv; } spv::Id const_packed_scale = builder_->makeFloatConstant(packed_scales[0]); spv::Op packed_scale_mul_op; if (used_format_component_count > 1) { if (packed_scales_same) { packed_scale_mul_op = spv::OpVectorTimesScalar; } else { packed_scale_mul_op = spv::OpFMul; id_vector_temp_.clear(); id_vector_temp_.push_back(const_packed_scale); for (uint32_t i = 1; i < used_format_component_count; ++i) { id_vector_temp_.push_back( builder_->makeFloatConstant(packed_scales[i])); } const_packed_scale = builder_->makeCompositeConstant(result_type, id_vector_temp_); } } else { packed_scale_mul_op = spv::OpFMul; } result = builder_->createNoContractionBinOp( packed_scale_mul_op, result_type, result, const_packed_scale); if (instr.attributes.is_signed) { switch (instr.attributes.signed_rf_mode) { case xenos::SignedRepeatingFractionMode::kZeroClampMinusOne: { // Treat both -(2^(n-1)) and -(2^(n-1)-1) as -1. Using regular FMax, // not NMax, because the number is known not to be NaN. spv::Id const_minus_1 = builder_->makeFloatConstant(-1.0f); if (used_format_component_count > 1) { id_vector_temp_.clear(); id_vector_temp_.resize(used_format_component_count, const_minus_1); const_minus_1 = builder_->makeCompositeConstant(result_type, id_vector_temp_); } result = builder_->createBinBuiltinCall( result_type, ext_inst_glsl_std_450_, GLSLstd450FMax, result, const_minus_1); } break; case xenos::SignedRepeatingFractionMode::kNoZero: id_vector_temp_.clear(); for (uint32_t i = 0; i < used_format_component_count; ++i) { id_vector_temp_.push_back( builder_->makeFloatConstant(0.5f * packed_scales[i])); } result = builder_->createNoContractionBinOp( spv::OpFAdd, result_type, result, used_format_component_count > 1 ? builder_->makeCompositeConstant(result_type, id_vector_temp_) : id_vector_temp_[0]); break; default: assert_unhandled_case(instr.attributes.signed_rf_mode); } } } } if (result != spv::NoResult) { // Apply the exponent bias. if (instr.attributes.exp_adjust) { result = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, builder_->getTypeId(result), result, builder_->makeFloatConstant( std::ldexp(1.0f, instr.attributes.exp_adjust))); } // If any components not present in the format were requested, pad the // resulting vector with zeros. uint32_t used_missing_components = used_result_components & ~used_format_components; if (used_missing_components) { // Bypassing the assertion in spv::Builder::createCompositeConstruct as of // November 5, 2020 - can construct vectors by concatenating vectors, not // just from individual scalars. std::unique_ptr composite_construct_op = std::make_unique( builder_->getUniqueId(), type_float_vectors_[xe::bit_count(used_result_components) - 1], spv::OpCompositeConstruct); composite_construct_op->addIdOperand(result); composite_construct_op->addIdOperand( const_float_vectors_0_[xe::bit_count(used_missing_components) - 1]); result = composite_construct_op->getResultId(); builder_->getBuildPoint()->addInstruction( std::move(composite_construct_op)); } } StoreResult(instr.result, result); } void SpirvShaderTranslator::ProcessTextureFetchInstruction( const ParsedTextureFetchInstruction& instr) { UpdateInstructionPredication(instr.is_predicated, instr.predicate_condition); EnsureBuildPointAvailable(); // Handle the instructions for setting the register LOD. switch (instr.opcode) { case ucode::FetchOpcode::kSetTextureLod: builder_->createStore( GetOperandComponents(LoadOperandStorage(instr.operands[0]), instr.operands[0], 0b0001), var_main_tfetch_lod_); return; case ucode::FetchOpcode::kSetTextureGradientsHorz: builder_->createStore( GetOperandComponents(LoadOperandStorage(instr.operands[0]), instr.operands[0], 0b0111), var_main_tfetch_gradients_h_); return; case ucode::FetchOpcode::kSetTextureGradientsVert: builder_->createStore( GetOperandComponents(LoadOperandStorage(instr.operands[0]), instr.operands[0], 0b0111), var_main_tfetch_gradients_v_); return; default: break; } // Handle instructions that store something. uint32_t used_result_components = instr.result.GetUsedResultComponents(); uint32_t used_result_nonzero_components = instr.GetNonZeroResultComponents(); switch (instr.opcode) { case ucode::FetchOpcode::kTextureFetch: break; case ucode::FetchOpcode::kGetTextureBorderColorFrac: // TODO(Triang3l): Bind a black texture with a white border to calculate // the border color fraction (in the X component of the result). assert_always(); EmitTranslationError("getBCF is unimplemented", false); used_result_nonzero_components = 0; break; case ucode::FetchOpcode::kGetTextureComputedLod: break; case ucode::FetchOpcode::kGetTextureGradients: break; case ucode::FetchOpcode::kGetTextureWeights: // FIXME(Triang3l): Currently disregarding the LOD completely in // getWeights because the needed code would be very complicated, while // getWeights is mostly used for things like PCF of shadow maps, that // don't have mips. The LOD would be needed for the mip lerp factor in W // of the return value and to choose the LOD where interpolation would // take place for XYZ. That would require either implementing the LOD // calculation algorithm using the ALU (since the `lod` instruction is // limited to pixel shaders and can't be used when there's control flow // divergence, unlike explicit gradients), or sampling a texture filled // with LOD numbers (easier and more consistent - unclamped LOD doesn't // make sense for getWeights anyway). The same applies to offsets. used_result_nonzero_components &= ~uint32_t(0b1000); break; default: assert_unhandled_case(instr.opcode); EmitTranslationError("Unknown texture fetch operation"); used_result_nonzero_components = 0; } uint32_t used_result_component_count = xe::bit_count(used_result_components); if (!used_result_nonzero_components) { // Nothing to fetch, only constant 0/1 writes - simplify the rest of the // function so it doesn't have to handle this case. if (used_result_components) { StoreResult(instr.result, const_float_vectors_0_[used_result_component_count - 1]); } return; } spv::Id result[] = {const_float_0_, const_float_0_, const_float_0_, const_float_0_}; if (instr.opcode == ucode::FetchOpcode::kGetTextureGradients) { // Doesn't need the texture, handle separately. spv::Id operand_0_storage = LoadOperandStorage(instr.operands[0]); bool derivative_function_x_used = (used_result_nonzero_components & 0b0011) != 0; bool derivative_function_y_used = (used_result_nonzero_components & 0b1100) != 0; spv::Id derivative_function_x = spv::NoResult; spv::Id derivative_function_y = spv::NoResult; if (derivative_function_x_used && derivative_function_y_used) { spv::Id derivative_function = GetOperandComponents(operand_0_storage, instr.operands[0], 0b0011); derivative_function_x = builder_->createCompositeExtract(derivative_function, type_float_, 0); derivative_function_y = builder_->createCompositeExtract(derivative_function, type_float_, 1); } else { if (derivative_function_x_used) { derivative_function_x = GetOperandComponents(operand_0_storage, instr.operands[0], 0b0001); } if (derivative_function_y_used) { derivative_function_y = GetOperandComponents(operand_0_storage, instr.operands[0], 0b0010); } } builder_->addCapability(spv::CapabilityDerivativeControl); uint32_t derivative_components_remaining = used_result_nonzero_components; uint32_t derivative_component_index; while (xe::bit_scan_forward(derivative_components_remaining, &derivative_component_index)) { derivative_components_remaining &= ~(UINT32_C(1) << derivative_component_index); result[derivative_component_index] = builder_->createUnaryOp( (derivative_component_index & 0b01) ? spv::OpDPdyCoarse : spv::OpDPdxCoarse, type_float_, (derivative_component_index & 0b10) ? derivative_function_y : derivative_function_x); } } else { // kTextureFetch, kGetTextureComputedLod or kGetTextureWeights. // Whether to use gradients (implicit or explicit) for LOD calculation. bool use_computed_lod = instr.attributes.use_computed_lod && (is_pixel_shader() || instr.attributes.use_register_gradients); if (instr.opcode == ucode::FetchOpcode::kGetTextureComputedLod && (!use_computed_lod || instr.attributes.use_register_gradients)) { assert_always(); EmitTranslationError( "getCompTexLOD used with explicit LOD or gradients - contradicts " "MSDN", false); StoreResult(instr.result, const_float_vectors_0_[used_result_component_count - 1]); return; } uint32_t fetch_constant_index = instr.operands[1].storage_index; uint32_t fetch_constant_word_0_index = 6 * fetch_constant_index; spv::Id sampler = spv::NoResult; spv::Id image_2d_array_or_cube_unsigned = spv::NoResult; spv::Id image_2d_array_or_cube_signed = spv::NoResult; spv::Id image_3d_unsigned = spv::NoResult; spv::Id image_3d_signed = spv::NoResult; if (instr.opcode != ucode::FetchOpcode::kGetTextureWeights) { bool bindings_set_up = true; // While GL_ARB_texture_query_lod specifies the value for // GL_NEAREST_MIPMAP_NEAREST and GL_LINEAR_MIPMAP_NEAREST minifying // functions as rounded (unlike the `lod` instruction in Direct3D 10.1+, // which is not defined for point sampling), the XNA assembler doesn't // accept MipFilter overrides for getCompTexLOD - probably should be // linear only, though not known exactly. // // 4D5307F2 uses vertex displacement map textures for tessellated models // like the beehive tree with explicit LOD with point sampling (they store // values packed in two components), however, the fetch constant has // anisotropic filtering enabled. However, Direct3D 12 doesn't allow // mixing anisotropic and point filtering. Possibly anistropic filtering // should be disabled when explicit LOD is used - do this here. size_t sampler_index = FindOrAddSamplerBinding( fetch_constant_index, instr.attributes.mag_filter, instr.attributes.min_filter, instr.opcode == ucode::FetchOpcode::kGetTextureComputedLod ? xenos::TextureFilter::kLinear : instr.attributes.mip_filter, use_computed_lod ? instr.attributes.aniso_filter : xenos::AnisoFilter::kDisabled); xenos::FetchOpDimension dimension_2d_array_or_cube = instr.dimension == xenos::FetchOpDimension::k3DOrStacked ? xenos::FetchOpDimension::k2D : instr.dimension; size_t image_2d_array_or_cube_unsigned_index = FindOrAddTextureBinding( fetch_constant_index, dimension_2d_array_or_cube, false); size_t image_2d_array_or_cube_signed_index = FindOrAddTextureBinding( fetch_constant_index, dimension_2d_array_or_cube, true); if (sampler_index == SIZE_MAX || image_2d_array_or_cube_unsigned_index == SIZE_MAX || image_2d_array_or_cube_signed_index == SIZE_MAX) { bindings_set_up = false; } size_t image_3d_unsigned_index = SIZE_MAX; size_t image_3d_signed_index = SIZE_MAX; if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) { image_3d_unsigned_index = FindOrAddTextureBinding( fetch_constant_index, xenos::FetchOpDimension::k3DOrStacked, false); image_3d_signed_index = FindOrAddTextureBinding( fetch_constant_index, xenos::FetchOpDimension::k3DOrStacked, true); if (image_3d_unsigned_index == SIZE_MAX || image_3d_signed_index == SIZE_MAX) { bindings_set_up = false; } } if (!bindings_set_up) { // Too many image or sampler bindings used. StoreResult(instr.result, const_float_vectors_0_[used_result_component_count - 1]); return; } sampler = builder_->createLoad(sampler_bindings_[sampler_index].variable, spv::NoPrecision); const TextureBinding& image_2d_array_or_cube_unsigned_binding = texture_bindings_[image_2d_array_or_cube_unsigned_index]; image_2d_array_or_cube_unsigned = builder_->createLoad( image_2d_array_or_cube_unsigned_binding.variable, spv::NoPrecision); const TextureBinding& image_2d_array_or_cube_signed_binding = texture_bindings_[image_2d_array_or_cube_signed_index]; image_2d_array_or_cube_signed = builder_->createLoad( image_2d_array_or_cube_signed_binding.variable, spv::NoPrecision); if (image_3d_unsigned_index != SIZE_MAX) { const TextureBinding& image_3d_unsigned_binding = texture_bindings_[image_3d_unsigned_index]; image_3d_unsigned = builder_->createLoad( image_3d_unsigned_binding.variable, spv::NoPrecision); } if (image_3d_signed_index != SIZE_MAX) { const TextureBinding& image_3d_signed_binding = texture_bindings_[image_3d_signed_index]; image_3d_signed = builder_->createLoad(image_3d_signed_binding.variable, spv::NoPrecision); } } // Get offsets applied to the coordinates before sampling. // FIXME(Triang3l): Offsets need to be applied at the LOD being fetched, not // at LOD 0. However, since offsets have granularity of 0.5, not 1, on the // Xenos, they can't be passed directly as ConstOffset to the image sample // instruction (plus-minus 0.5 offsets are very common in games). But // offsetting at mip levels is a rare usage case, mostly offsets are used // for things like shadow maps and blur, where there are no mips. float offset_values[3] = {}; // MSDN doesn't list offsets as getCompTexLOD parameters. if (instr.opcode != ucode::FetchOpcode::kGetTextureComputedLod) { // Add a small epsilon to the offset (1.5/4 the fixed-point texture // coordinate ULP with 8-bit subtexel precision - shouldn't significantly // effect the fixed-point conversion; 1/4 is also not enough with 3x // resolution scaling very noticeably on the weapon in 4D5307E6, at least // on the Direct3D 12 backend) to resolve ambiguity when fetching // point-sampled textures between texels. This applies to both normalized // (58410954 Xbox Live Arcade logo, coordinates interpolated between // vertices with half-pixel offset) and unnormalized (4D5307E6 lighting // G-buffer reading, ps_param_gen pixels) coordinates. On Nvidia Pascal, // without this adjustment, blockiness is visible in both cases. Possibly // there is a better way, however, an attempt was made to error-correct // division by adding the difference between original and re-denormalized // coordinates, but on Nvidia, `mul` (on Direct3D 12) and internal // multiplication in texture sampling apparently round differently, so // `mul` gives a value that would be floored as expected, but the // left/upper pixel is still sampled instead. constexpr float kRoundingOffset = 1.5f / 1024.0f; switch (instr.dimension) { case xenos::FetchOpDimension::k1D: offset_values[0] = instr.attributes.offset_x + kRoundingOffset; if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { // For coordinate lerp factors. This needs to be done separately for // point mag/min filters, but they're currently not handled here // anyway. offset_values[0] -= 0.5f; } break; case xenos::FetchOpDimension::k2D: offset_values[0] = instr.attributes.offset_x + kRoundingOffset; offset_values[1] = instr.attributes.offset_y + kRoundingOffset; if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { offset_values[0] -= 0.5f; offset_values[1] -= 0.5f; } break; case xenos::FetchOpDimension::k3DOrStacked: offset_values[0] = instr.attributes.offset_x + kRoundingOffset; offset_values[1] = instr.attributes.offset_y + kRoundingOffset; offset_values[2] = instr.attributes.offset_z + kRoundingOffset; if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { offset_values[0] -= 0.5f; offset_values[1] -= 0.5f; offset_values[2] -= 0.5f; } break; case xenos::FetchOpDimension::kCube: // Applying the rounding epsilon to cube maps too for potential game // passes processing cube map faces themselves. offset_values[0] = instr.attributes.offset_x + kRoundingOffset; offset_values[1] = instr.attributes.offset_y + kRoundingOffset; if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { offset_values[0] -= 0.5f; offset_values[1] -= 0.5f; // The logic for ST weights is the same for all faces. // FIXME(Triang3l): If LOD calculation is added to getWeights, face // offset probably will need to be handled too (if the hardware // supports it at all, though MSDN lists OffsetZ in tfetchCube). } else { offset_values[2] = instr.attributes.offset_z; } break; } } uint32_t offsets_not_zero = 0b000; for (uint32_t i = 0; i < 3; ++i) { if (offset_values[i]) { offsets_not_zero |= 1 << i; } } // Fetch constant word usage: // - 2: Size (needed only once). // - 3: Exponent adjustment (needed only once). // - 4: Conditionally for 3D kTextureFetch: stacked texture filtering modes. // Unconditionally LOD kTextureFetch: LOD and gradient exponent bias, // result exponent bias. // - 5: Dimensionality (3D or 2D stacked - needed only once). // Load the texture size and whether it's 3D or stacked if needed. // 1D: X - width. // 2D, cube: X - width, Y - height (cube maps probably can be only square, // but for simplicity). // 3D: X - width, Y - height, Z - depth. uint32_t size_needed_components = 0b000; bool data_is_3d_needed = false; if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { // Size needed for denormalization for coordinate lerp factor. // FIXME(Triang3l): Currently disregarding the LOD completely in // getWeights. However, if the LOD lerp factor and the LOD where filtering // would happen are ever calculated, all components of the size may be // needed for ALU LOD calculation with normalized coordinates (or, if a // texture filled with LOD indices is used, coordinates will need to be // normalized as normally). if (!instr.attributes.unnormalized_coordinates) { switch (instr.dimension) { case xenos::FetchOpDimension::k1D: size_needed_components |= used_result_nonzero_components & 0b0001; break; case xenos::FetchOpDimension::k2D: case xenos::FetchOpDimension::kCube: size_needed_components |= used_result_nonzero_components & 0b0011; break; case xenos::FetchOpDimension::k3DOrStacked: size_needed_components |= used_result_nonzero_components & 0b0111; break; } } } else { // Size needed for normalization (or, for stacked texture layers, // denormalization) and for offsets. size_needed_components |= offsets_not_zero; switch (instr.dimension) { case xenos::FetchOpDimension::k1D: if (instr.attributes.unnormalized_coordinates) { size_needed_components |= 0b0001; } break; case xenos::FetchOpDimension::k2D: if (instr.attributes.unnormalized_coordinates) { size_needed_components |= 0b0011; } break; case xenos::FetchOpDimension::k3DOrStacked: // Stacked and 3D textures are fetched from different bindings - the // check is always needed. data_is_3d_needed = true; if (instr.attributes.unnormalized_coordinates) { // Need to normalize all (if 3D). size_needed_components |= 0b0111; } else { // Need to denormalize Z (if stacked). size_needed_components |= 0b0100; } break; case xenos::FetchOpDimension::kCube: if (instr.attributes.unnormalized_coordinates) { size_needed_components |= 0b0011; } // The size is not needed for face ID offset. size_needed_components &= 0b0011; break; } } if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked && size_needed_components) { // Stacked and 3D textures have different size packing - need to get // whether the texture is 3D unconditionally. data_is_3d_needed = true; } spv::Id data_is_3d = spv::NoResult; if (data_is_3d_needed) { // Get the data dimensionality from the bits 9:10 of the fetch constant // word 5. id_vector_temp_.clear(); id_vector_temp_.push_back(const_int_0_); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 5) >> 2))); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 5) & 3))); spv::Id fetch_constant_word_5 = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision); spv::Id data_dimension = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_5, builder_->makeUintConstant(9), builder_->makeUintConstant(2)); data_is_3d = builder_->createBinOp( spv::OpIEqual, type_bool_, data_dimension, builder_->makeUintConstant( static_cast(xenos::DataDimension::k3D))); } spv::Id size[3] = {}; if (size_needed_components) { // Get the size from the fetch constant word 2. id_vector_temp_.clear(); id_vector_temp_.push_back(const_int_0_); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 2) >> 2))); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 2) & 3))); spv::Id fetch_constant_word_2 = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision); switch (instr.dimension) { case xenos::FetchOpDimension::k1D: { if (size_needed_components & 0b1) { size[0] = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, const_uint_0_, builder_->makeUintConstant(xenos::kTexture1DMaxWidthLog2)); } assert_zero(size_needed_components & 0b110); } break; case xenos::FetchOpDimension::k2D: case xenos::FetchOpDimension::kCube: { if (size_needed_components & 0b1) { size[0] = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, const_uint_0_, builder_->makeUintConstant( xenos::kTexture2DCubeMaxWidthHeightLog2)); } if (size_needed_components & 0b10) { spv::Id width_height_bit_count = builder_->makeUintConstant( xenos::kTexture2DCubeMaxWidthHeightLog2); size[1] = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, width_height_bit_count, width_height_bit_count); } assert_zero(size_needed_components & 0b100); } break; case xenos::FetchOpDimension::k3DOrStacked: { if (size_needed_components & 0b1) { spv::Id size_3d = builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, const_uint_0_, builder_->makeUintConstant( xenos::kTexture3DMaxWidthHeightLog2)); spv::Id size_2d = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, const_uint_0_, builder_->makeUintConstant( xenos::kTexture2DCubeMaxWidthHeightLog2)); assert_true(data_is_3d != spv::NoResult); size[0] = builder_->createTriOp(spv::OpSelect, type_uint_, data_is_3d, size_3d, size_2d); } if (size_needed_components & 0b10) { spv::Id width_height_bit_count_3d = builder_->makeUintConstant(xenos::kTexture3DMaxWidthHeightLog2); spv::Id size_3d = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, width_height_bit_count_3d, width_height_bit_count_3d); spv::Id width_height_bit_count_2d = builder_->makeUintConstant( xenos::kTexture2DCubeMaxWidthHeightLog2); spv::Id size_2d = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, width_height_bit_count_2d, width_height_bit_count_2d); assert_true(data_is_3d != spv::NoResult); size[1] = builder_->createTriOp(spv::OpSelect, type_uint_, data_is_3d, size_3d, size_2d); } if (size_needed_components & 0b100) { spv::Id size_3d = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, builder_->makeUintConstant(xenos::kTexture3DMaxWidthHeightLog2 * 2), builder_->makeUintConstant(xenos::kTexture3DMaxDepthLog2)); spv::Id size_2d = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, builder_->makeUintConstant( xenos::kTexture2DCubeMaxWidthHeightLog2 * 2), builder_->makeUintConstant(xenos::kTexture2DMaxStackDepthLog2)); assert_true(data_is_3d != spv::NoResult); size[2] = builder_->createTriOp(spv::OpSelect, type_uint_, data_is_3d, size_3d, size_2d); } } break; } { uint32_t size_remaining_components = size_needed_components; uint32_t size_component_index; while (xe::bit_scan_forward(size_remaining_components, &size_component_index)) { size_remaining_components &= ~(UINT32_C(1) << size_component_index); spv::Id& size_component_ref = size[size_component_index]; // Fetch constants store size minus 1 - add 1. size_component_ref = builder_->createBinOp(spv::OpIAdd, type_uint_, size_component_ref, builder_->makeUintConstant(1)); // Convert the size to float for multiplication or division. size_component_ref = builder_->createUnaryOp( spv::OpConvertUToF, type_float_, size_component_ref); } } } // FIXME(Triang3l): Mip lerp factor needs to be calculated, and the // coordinate lerp factors should be calculated at the mip level texels // would be sampled from. That would require some way of calculating the // LOD that would be applicable to explicit gradients and vertex shaders. // Also, with point sampling, possibly lerp factors need to be 0. W (mip // lerp factor) should have been masked out previously because it's not // supported currently. assert_false(instr.opcode == ucode::FetchOpcode::kGetTextureWeights && (used_result_nonzero_components & 0b1000)); // Load the needed original values of the coordinates operand. uint32_t coordinates_needed_components = instr.opcode == ucode::FetchOpcode::kGetTextureWeights ? used_result_nonzero_components : ((UINT32_C(1) << xenos::GetFetchOpDimensionComponentCount(instr.dimension)) - 1); assert_not_zero(coordinates_needed_components); spv::Id coordinates_operand = GetOperandComponents(LoadOperandStorage(instr.operands[0]), instr.operands[0], coordinates_needed_components); spv::Id coordinates[] = {const_float_0_, const_float_0_, const_float_0_}; if (xe::bit_count(coordinates_needed_components) > 1) { uint32_t coordinates_remaining_components = coordinates_needed_components; uint32_t coordinate_component_index; uint32_t coordinate_operand_component_index = 0; while (xe::bit_scan_forward(coordinates_remaining_components, &coordinate_component_index)) { coordinates_remaining_components &= ~(UINT32_C(1) << coordinate_component_index); coordinates[coordinate_component_index] = builder_->createCompositeExtract( coordinates_operand, type_float_, coordinate_operand_component_index++); } } else { uint32_t coordinate_component_index; xe::bit_scan_forward(coordinates_needed_components, &coordinate_component_index); coordinates[coordinate_component_index] = coordinates_operand; } // Resolution scale doesn't need reverting for texture weights - weights are // calculated from fractional parts of coordinates which are // scale-independent. if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) { // FIXME(Triang3l): Filtering modes should possibly be taken into account, // but for simplicity, not doing that - from a high level point of view, // would be useless to get weights that will always be zero. uint32_t coordinates_remaining_components = coordinates_needed_components; uint32_t coordinate_component_index; while (xe::bit_scan_forward(coordinates_remaining_components, &coordinate_component_index)) { coordinates_remaining_components &= ~(UINT32_C(1) << coordinate_component_index); spv::Id result_component = coordinates[coordinate_component_index]; // Need unnormalized coordinates. if (!instr.attributes.unnormalized_coordinates) { spv::Id size_component = size[coordinate_component_index]; assert_true(size_component != spv::NoResult); result_component = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, result_component, size_component); } float component_offset = offset_values[coordinate_component_index]; if (component_offset) { result_component = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, result_component, builder_->makeFloatConstant(component_offset)); } // 0.5 has already been subtracted via offsets previously. result_component = builder_->createUnaryBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450Fract, result_component); result[coordinate_component_index] = result_component; } } else { // kTextureFetch or kGetTextureComputedLod. // Normalize the XY coordinates, and apply the offset. for (uint32_t i = 0; i <= uint32_t(instr.dimension != xenos::FetchOpDimension::k1D); ++i) { spv::Id& coordinate_ref = coordinates[i]; spv::Id component_offset = offset_values[i] ? builder_->makeFloatConstant(offset_values[i]) : spv::NoResult; spv::Id size_component = size[i]; if (instr.attributes.unnormalized_coordinates) { if (component_offset != spv::NoResult) { coordinate_ref = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, coordinate_ref, component_offset); } assert_true(size_component != spv::NoResult); coordinate_ref = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, coordinate_ref, size_component); } else { if (component_offset != spv::NoResult) { assert_true(size_component != spv::NoResult); spv::Id component_offset_normalized = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, component_offset, size_component); coordinate_ref = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, coordinate_ref, component_offset_normalized); } } } if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) { spv::Id& z_coordinate_ref = coordinates[2]; spv::Id z_offset = offset_values[2] ? builder_->makeFloatConstant(offset_values[2]) : spv::NoResult; spv::Id z_size = size[2]; if (instr.attributes.unnormalized_coordinates) { // Apply the offset, and normalize the Z coordinate for a 3D texture. if (z_offset != spv::NoResult) { z_coordinate_ref = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, z_coordinate_ref, z_offset); } assert_true(data_is_3d != spv::NoResult); SpirvBuilder::IfBuilder if_data_is_3d( data_is_3d, spv::SelectionControlDontFlattenMask, *builder_); spv::Id z_3d; { assert_true(z_size != spv::NoResult); z_3d = builder_->createNoContractionBinOp(spv::OpFDiv, type_float_, z_coordinate_ref, z_size); } if_data_is_3d.makeEndIf(); z_coordinate_ref = if_data_is_3d.createMergePhi(z_3d, z_coordinate_ref); } else { // Denormalize the Z coordinate for a stacked texture, and apply the // offset. spv::Block& block_dimension_head = *builder_->getBuildPoint(); spv::Block& block_dimension_merge = builder_->makeNewBlock(); spv::Block* block_dimension_3d = z_offset != spv::NoResult ? &builder_->makeNewBlock() : nullptr; spv::Block& block_dimension_stacked = builder_->makeNewBlock(); builder_->createSelectionMerge(&block_dimension_merge, spv::SelectionControlDontFlattenMask); assert_true(data_is_3d != spv::NoResult); builder_->createConditionalBranch( data_is_3d, block_dimension_3d ? block_dimension_3d : &block_dimension_merge, &block_dimension_stacked); // 3D case. spv::Id z_3d = z_coordinate_ref; if (block_dimension_3d) { builder_->setBuildPoint(block_dimension_3d); if (z_offset != spv::NoResult) { assert_true(z_size != spv::NoResult); spv::Id z_offset_normalized = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, z_offset, z_size); z_3d = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, z_3d, z_offset_normalized); } builder_->createBranch(&block_dimension_merge); } // Stacked case. builder_->setBuildPoint(&block_dimension_stacked); spv::Id z_stacked = z_coordinate_ref; assert_true(z_size != spv::NoResult); z_stacked = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, z_stacked, z_size); if (z_offset != spv::NoResult) { z_stacked = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, z_stacked, z_offset); } builder_->createBranch(&block_dimension_merge); // Select one of the two. builder_->setBuildPoint(&block_dimension_merge); { std::unique_ptr z_phi_op = std::make_unique(builder_->getUniqueId(), type_float_, spv::OpPhi); z_phi_op->addIdOperand(z_3d); z_phi_op->addIdOperand((block_dimension_3d ? *block_dimension_3d : block_dimension_head) .getId()); z_phi_op->addIdOperand(z_stacked); z_phi_op->addIdOperand(block_dimension_stacked.getId()); z_coordinate_ref = z_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction(std::move(z_phi_op)); } } } else if (instr.dimension == xenos::FetchOpDimension::kCube) { // Transform the cube coordinates from 2D to 3D. // Move SC/TC from 1...2 to -1...1. spv::Id const_float_2 = builder_->makeFloatConstant(2.0f); spv::Id const_float_minus_3 = builder_->makeFloatConstant(-3.0f); for (uint32_t i = 0; i < 2; ++i) { coordinates[i] = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, builder_->createNoContractionBinOp(spv::OpFMul, type_float_, coordinates[i], const_float_2), const_float_minus_3); } // Get the face index (floored, within 0...5 - OpConvertFToU is // undefined for out-of-range values, so clamping from both sides // manually). spv::Id face = coordinates[2]; if (offset_values[2]) { face = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, face, builder_->makeFloatConstant(offset_values[2])); } face = builder_->createUnaryOp( spv::OpConvertFToU, type_uint_, builder_->createTriBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450NClamp, face, const_float_0_, builder_->makeFloatConstant(5.0f))); // Split the face index into the axis and the sign. spv::Id const_uint_1 = builder_->makeUintConstant(1); spv::Id face_axis = builder_->createBinOp( spv::OpShiftRightLogical, type_uint_, face, const_uint_1); spv::Id face_is_negative = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, face, const_uint_1), const_uint_0_); spv::Id face_sign = builder_->createTriOp(spv::OpSelect, type_float_, face_is_negative, builder_->makeFloatConstant(-1.0f), builder_->makeFloatConstant(1.0f)); // Remap the axes in a way opposite to the ALU cube instruction. spv::Id sc_negated = builder_->createNoContractionUnaryOp( spv::OpFNegate, type_float_, coordinates[0]); spv::Id tc_negated = builder_->createNoContractionUnaryOp( spv::OpFNegate, type_float_, coordinates[1]); spv::Block& block_ma_head = *builder_->getBuildPoint(); spv::Block& block_ma_x = builder_->makeNewBlock(); spv::Block& block_ma_y = builder_->makeNewBlock(); spv::Block& block_ma_z = builder_->makeNewBlock(); spv::Block& block_ma_merge = builder_->makeNewBlock(); builder_->createSelectionMerge(&block_ma_merge, spv::SelectionControlMaskNone); { std::unique_ptr ma_switch_op = std::make_unique(spv::OpSwitch); ma_switch_op->addIdOperand(face_axis); // Make Z the default. ma_switch_op->addIdOperand(block_ma_z.getId()); ma_switch_op->addImmediateOperand(0); ma_switch_op->addIdOperand(block_ma_x.getId()); ma_switch_op->addImmediateOperand(1); ma_switch_op->addIdOperand(block_ma_y.getId()); builder_->getBuildPoint()->addInstruction(std::move(ma_switch_op)); } block_ma_x.addPredecessor(&block_ma_head); block_ma_y.addPredecessor(&block_ma_head); block_ma_z.addPredecessor(&block_ma_head); // X is the major axis case. builder_->setBuildPoint(&block_ma_x); spv::Id ma_x_y = tc_negated; spv::Id ma_x_z = builder_->createTriOp(spv::OpSelect, type_float_, face_is_negative, coordinates[0], sc_negated); builder_->createBranch(&block_ma_merge); // Y is the major axis case. builder_->setBuildPoint(&block_ma_y); spv::Id ma_y_x = coordinates[0]; spv::Id ma_y_z = builder_->createTriOp(spv::OpSelect, type_float_, face_is_negative, tc_negated, coordinates[1]); builder_->createBranch(&block_ma_merge); // Z is the major axis case. builder_->setBuildPoint(&block_ma_z); spv::Id ma_z_x = builder_->createTriOp(spv::OpSelect, type_float_, face_is_negative, sc_negated, coordinates[0]); spv::Id ma_z_y = tc_negated; builder_->createBranch(&block_ma_merge); // Gather the coordinate components from the branches. builder_->setBuildPoint(&block_ma_merge); { std::unique_ptr x_phi_op = std::make_unique(builder_->getUniqueId(), type_float_, spv::OpPhi); x_phi_op->addIdOperand(face_sign); x_phi_op->addIdOperand(block_ma_x.getId()); x_phi_op->addIdOperand(ma_y_x); x_phi_op->addIdOperand(block_ma_y.getId()); x_phi_op->addIdOperand(ma_z_x); x_phi_op->addIdOperand(block_ma_z.getId()); coordinates[0] = x_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction(std::move(x_phi_op)); } { std::unique_ptr y_phi_op = std::make_unique(builder_->getUniqueId(), type_float_, spv::OpPhi); y_phi_op->addIdOperand(ma_x_y); y_phi_op->addIdOperand(block_ma_x.getId()); y_phi_op->addIdOperand(face_sign); y_phi_op->addIdOperand(block_ma_y.getId()); y_phi_op->addIdOperand(ma_z_y); y_phi_op->addIdOperand(block_ma_z.getId()); coordinates[1] = y_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction(std::move(y_phi_op)); } { std::unique_ptr z_phi_op = std::make_unique(builder_->getUniqueId(), type_float_, spv::OpPhi); z_phi_op->addIdOperand(ma_x_z); z_phi_op->addIdOperand(block_ma_x.getId()); z_phi_op->addIdOperand(ma_y_z); z_phi_op->addIdOperand(block_ma_y.getId()); z_phi_op->addIdOperand(face_sign); z_phi_op->addIdOperand(block_ma_z.getId()); coordinates[2] = z_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction(std::move(z_phi_op)); } } id_vector_temp_.clear(); id_vector_temp_.push_back( builder_->makeIntConstant(kSystemConstantTextureSwizzledSigns)); id_vector_temp_.push_back( builder_->makeIntConstant(fetch_constant_index >> 4)); id_vector_temp_.push_back( builder_->makeIntConstant((fetch_constant_index >> 2) & 3)); // All 32 bits containing the values for 4 fetch constants (use // OpBitFieldUExtract to get the signednesses for the specific components // of this texture). spv::Id swizzled_signs_word = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_system_constants_, id_vector_temp_), spv::NoPrecision); uint32_t swizzled_signs_word_offset = 8 * (fetch_constant_index & 3); spv::Builder::TextureParameters texture_parameters = {}; if (instr.opcode == ucode::FetchOpcode::kGetTextureComputedLod) { // kGetTextureComputedLod. // Check if the signed binding is needs to be accessed rather than the // unsigned (if all signednesses are signed). spv::Id swizzled_signs_all_signed = builder_->createBinOp( spv::OpIEqual, type_bool_, builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, swizzled_signs_word, builder_->makeUintConstant(swizzled_signs_word_offset), builder_->makeUintConstant(8)), builder_->makeUintConstant(uint32_t(xenos::TextureSign::kSigned) * 0b01010101)); // OpImageQueryLod doesn't need the array layer component. // So, 3 coordinate components for 3D cube, 2 in other cases (including // 1D, which are emulated as 2D arrays). // OpSampledImage must be in the same block as where its result is used. if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) { // Check if the texture is 3D or stacked. assert_true(data_is_3d != spv::NoResult); SpirvBuilder::IfBuilder if_data_is_3d( data_is_3d, spv::SelectionControlDontFlattenMask, *builder_); spv::Id lod_3d; { // 3D. id_vector_temp_.clear(); for (uint32_t i = 0; i < 3; ++i) { id_vector_temp_.push_back(coordinates[i]); } texture_parameters.coords = builder_->createCompositeConstruct( type_float3_, id_vector_temp_); lod_3d = QueryTextureLod(texture_parameters, image_3d_unsigned, image_3d_signed, sampler, swizzled_signs_all_signed); } if_data_is_3d.makeBeginElse(); spv::Id lod_stacked; { // 2D stacked. id_vector_temp_.clear(); for (uint32_t i = 0; i < 2; ++i) { id_vector_temp_.push_back(coordinates[i]); } texture_parameters.coords = builder_->createCompositeConstruct( type_float2_, id_vector_temp_); lod_stacked = QueryTextureLod(texture_parameters, image_2d_array_or_cube_unsigned, image_2d_array_or_cube_signed, sampler, swizzled_signs_all_signed); } if_data_is_3d.makeEndIf(); result[0] = if_data_is_3d.createMergePhi(lod_3d, lod_stacked); } else { uint32_t lod_query_coordinate_component_count = instr.dimension == xenos::FetchOpDimension::kCube ? 3 : 2; id_vector_temp_.clear(); for (uint32_t i = 0; i < lod_query_coordinate_component_count; ++i) { id_vector_temp_.push_back(coordinates[i]); } texture_parameters.coords = builder_->createCompositeConstruct( type_float_vectors_[lod_query_coordinate_component_count - 1], id_vector_temp_); result[0] = QueryTextureLod(texture_parameters, image_2d_array_or_cube_unsigned, image_2d_array_or_cube_signed, sampler, swizzled_signs_all_signed); } } else { // kTextureFetch. assert_true(instr.opcode == ucode::FetchOpcode::kTextureFetch); // Extract the signedness for each component of the swizzled result, and // get which bindings (unsigned and signed) are needed. spv::Id swizzled_signs[4] = {}; spv::Id result_is_signed[4] = {}; spv::Id is_all_signed = spv::NoResult; spv::Id is_any_signed = spv::NoResult; spv::Id const_uint_2 = builder_->makeUintConstant(2); spv::Id const_uint_sign_signed = builder_->makeUintConstant(uint32_t(xenos::TextureSign::kSigned)); { uint32_t result_remaining_components = used_result_nonzero_components; uint32_t result_component_index; while (xe::bit_scan_forward(result_remaining_components, &result_component_index)) { result_remaining_components &= ~(UINT32_C(1) << result_component_index); spv::Id result_component_sign = builder_->createTriOp( spv::OpBitFieldUExtract, type_uint_, swizzled_signs_word, builder_->makeUintConstant(swizzled_signs_word_offset + 2 * result_component_index), const_uint_2); swizzled_signs[result_component_index] = result_component_sign; spv::Id is_component_signed = builder_->createBinOp( spv::OpIEqual, type_bool_, result_component_sign, const_uint_sign_signed); result_is_signed[result_component_index] = is_component_signed; if (is_all_signed != spv::NoResult) { is_all_signed = builder_->createBinOp(spv::OpLogicalAnd, type_bool_, is_all_signed, is_component_signed); } else { is_all_signed = is_component_signed; } if (is_any_signed != spv::NoResult) { is_any_signed = builder_->createBinOp(spv::OpLogicalOr, type_bool_, is_any_signed, is_component_signed); } else { is_any_signed = is_component_signed; } } } spv::Id is_any_unsigned = builder_->createUnaryOp( spv::OpLogicalNot, type_bool_, is_all_signed); // Load the fetch constant word 3, needed for result exponent biasing. // exp_adjust is in word 3, bits 13:18 (6-bit signed). id_vector_temp_.clear(); id_vector_temp_.push_back(const_int_0_); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 3) >> 2))); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 3) & 3))); spv::Id fetch_constant_word_3_signed = builder_->createUnaryOp( spv::OpBitcast, type_int_, builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision)); // Load the fetch constant word 4, needed unconditionally for LOD // biasing, and conditionally for stacked texture filtering. id_vector_temp_.clear(); id_vector_temp_.push_back(const_int_0_); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 4) >> 2))); id_vector_temp_.push_back(builder_->makeIntConstant( int((fetch_constant_word_0_index + 4) & 3))); spv::Id fetch_constant_word_4 = builder_->createLoad(builder_->createAccessChain( spv::StorageClassUniform, uniform_fetch_constants_, id_vector_temp_), spv::NoPrecision); spv::Id fetch_constant_word_4_signed = builder_->createUnaryOp( spv::OpBitcast, type_int_, fetch_constant_word_4); // Accumulate the explicit LOD (or LOD bias) sources (in D3D11.3 // specification order: specified LOD + sampler LOD bias + instruction // LOD bias). // Fetch constant LOD (bits 12:21 of the word 4). spv::Id lod = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, builder_->createUnaryOp( spv::OpConvertSToF, type_float_, builder_->createTriOp(spv::OpBitFieldSExtract, type_int_, fetch_constant_word_4_signed, builder_->makeUintConstant(12), builder_->makeUintConstant(10))), builder_->makeFloatConstant(1.0f / 32.0f)); // Register LOD. if (instr.attributes.use_register_lod) { lod = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, builder_->createLoad(var_main_tfetch_lod_, spv::NoPrecision), lod); } // Instruction LOD bias. if (instr.attributes.lod_bias) { lod = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, lod, builder_->makeFloatConstant(instr.attributes.lod_bias)); } // Calculate the gradients for sampling the texture if needed. // 2D vectors for k1D (because 1D images are emulated as 2D arrays), // k2D. // 3D vectors for k3DOrStacked, kCube. spv::Id gradients_h = spv::NoResult, gradients_v = spv::NoResult; if (use_computed_lod) { // TODO(Triang3l): Gradient exponent adjustment is currently not done // in getCompTexLOD, so not doing it here too for now. Apply the // gradient exponent biases from the word 4 of the fetch constant in // the future when it's handled in getCompTexLOD somehow. spv::Id lod_gradient_scale = builder_->createUnaryBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450Exp2, lod); switch (instr.dimension) { case xenos::FetchOpDimension::k1D: { spv::Id gradient_h_1d, gradient_v_1d; if (instr.attributes.use_register_gradients) { id_vector_temp_.clear(); // First component. id_vector_temp_.push_back(const_int_0_); gradient_h_1d = builder_->createLoad( builder_->createAccessChain(spv::StorageClassFunction, var_main_tfetch_gradients_h_, id_vector_temp_), spv::NoPrecision); gradient_v_1d = builder_->createLoad( builder_->createAccessChain(spv::StorageClassFunction, var_main_tfetch_gradients_v_, id_vector_temp_), spv::NoPrecision); if (instr.attributes.unnormalized_coordinates) { // Normalize the gradients. assert_true(size[0] != spv::NoResult); gradient_h_1d = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, gradient_h_1d, size[0]); gradient_v_1d = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, gradient_v_1d, size[0]); } } else { builder_->addCapability(spv::CapabilityDerivativeControl); gradient_h_1d = builder_->createUnaryOp( spv::OpDPdxCoarse, type_float_, coordinates[0]); gradient_v_1d = builder_->createUnaryOp( spv::OpDPdyCoarse, type_float_, coordinates[0]); } gradient_h_1d = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, gradient_h_1d, lod_gradient_scale); gradient_v_1d = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, gradient_v_1d, lod_gradient_scale); // 1D textures are sampled as 2D arrays - need 2-component // gradients. id_vector_temp_.clear(); id_vector_temp_.push_back(gradient_h_1d); id_vector_temp_.push_back(const_float_0_); gradients_h = builder_->createCompositeConstruct(type_float2_, id_vector_temp_); id_vector_temp_[0] = gradient_v_1d; gradients_v = builder_->createCompositeConstruct(type_float2_, id_vector_temp_); } break; case xenos::FetchOpDimension::k2D: { if (instr.attributes.use_register_gradients) { for (uint32_t i = 0; i < 2; ++i) { spv::Id register_gradient_3d = builder_->createLoad(i ? var_main_tfetch_gradients_h_ : var_main_tfetch_gradients_v_, spv::NoPrecision); spv::Id register_gradient_x = builder_->createCompositeExtract(register_gradient_3d, type_float_, 0); spv::Id register_gradient_y = builder_->createCompositeExtract(register_gradient_3d, type_float_, 1); if (instr.attributes.unnormalized_coordinates) { // Normalize the gradients. assert_true(size[0] != spv::NoResult); register_gradient_x = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, register_gradient_x, size[0]); assert_true(size[1] != spv::NoResult); register_gradient_y = builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, register_gradient_y, size[1]); } id_vector_temp_.clear(); id_vector_temp_.push_back(register_gradient_x); id_vector_temp_.push_back(register_gradient_y); (i ? gradients_v : gradients_h) = builder_->createCompositeConstruct(type_float2_, id_vector_temp_); } } else { id_vector_temp_.clear(); for (uint32_t i = 0; i < 2; ++i) { id_vector_temp_.push_back(coordinates[i]); } spv::Id gradient_coordinate_vector = builder_->createCompositeConstruct(type_float2_, id_vector_temp_); builder_->addCapability(spv::CapabilityDerivativeControl); gradients_h = builder_->createUnaryOp(spv::OpDPdxCoarse, type_float2_, gradient_coordinate_vector); gradients_v = builder_->createUnaryOp(spv::OpDPdyCoarse, type_float2_, gradient_coordinate_vector); } gradients_h = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float2_, gradients_h, lod_gradient_scale); gradients_v = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float2_, gradients_v, lod_gradient_scale); } break; case xenos::FetchOpDimension::k3DOrStacked: { if (instr.attributes.use_register_gradients) { gradients_h = builder_->createLoad(var_main_tfetch_gradients_h_, spv::NoPrecision); gradients_v = builder_->createLoad(var_main_tfetch_gradients_v_, spv::NoPrecision); if (instr.attributes.unnormalized_coordinates) { // Normalize the gradients. for (uint32_t i = 0; i < 2; ++i) { spv::Id& gradient_ref = i ? gradients_v : gradients_h; id_vector_temp_.clear(); for (uint32_t j = 0; j < 3; ++j) { assert_true(size[j] != spv::NoResult); id_vector_temp_.push_back( builder_->createNoContractionBinOp( spv::OpFDiv, type_float_, builder_->createCompositeExtract(gradient_ref, type_float_, j), size[j])); } gradient_ref = builder_->createCompositeConstruct( type_float3_, id_vector_temp_); } } } else { id_vector_temp_.clear(); for (uint32_t i = 0; i < 3; ++i) { id_vector_temp_.push_back(coordinates[i]); } spv::Id gradient_coordinate_vector = builder_->createCompositeConstruct(type_float3_, id_vector_temp_); builder_->addCapability(spv::CapabilityDerivativeControl); gradients_h = builder_->createUnaryOp(spv::OpDPdxCoarse, type_float3_, gradient_coordinate_vector); gradients_v = builder_->createUnaryOp(spv::OpDPdyCoarse, type_float3_, gradient_coordinate_vector); } gradients_h = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float3_, gradients_h, lod_gradient_scale); gradients_v = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float3_, gradients_v, lod_gradient_scale); } break; case xenos::FetchOpDimension::kCube: { if (instr.attributes.use_register_gradients) { // Register gradients are already in the cube space for cube // maps. // TODO(Triang3l): Are cube map register gradients unnormalized // if the coordinates themselves are unnormalized? gradients_h = builder_->createLoad(var_main_tfetch_gradients_h_, spv::NoPrecision); gradients_v = builder_->createLoad(var_main_tfetch_gradients_v_, spv::NoPrecision); } else { id_vector_temp_.clear(); for (uint32_t i = 0; i < 3; ++i) { id_vector_temp_.push_back(coordinates[i]); } spv::Id gradient_coordinate_vector = builder_->createCompositeConstruct(type_float3_, id_vector_temp_); builder_->addCapability(spv::CapabilityDerivativeControl); gradients_h = builder_->createUnaryOp(spv::OpDPdxCoarse, type_float3_, gradient_coordinate_vector); gradients_v = builder_->createUnaryOp(spv::OpDPdyCoarse, type_float3_, gradient_coordinate_vector); } gradients_h = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float3_, gradients_h, lod_gradient_scale); gradients_v = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float3_, gradients_v, lod_gradient_scale); } break; } } // Sample the texture. spv::ImageOperandsMask image_operands_mask = use_computed_lod ? spv::ImageOperandsGradMask : spv::ImageOperandsLodMask; spv::Id sample_result_unsigned, sample_result_signed; if (!use_computed_lod) { texture_parameters.lod = lod; } if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) { // 3D (3 coordinate components, 3 gradient components, single fetch) // or 2D stacked (2 coordinate components + 1 array layer coordinate // component, 2 gradient components, two fetches if the Z axis is // linear-filtered). assert_true(data_is_3d != spv::NoResult); SpirvBuilder::IfBuilder if_data_is_3d( data_is_3d, spv::SelectionControlDontFlattenMask, *builder_); spv::Id sample_result_unsigned_3d, sample_result_signed_3d; { // 3D. if (use_computed_lod) { texture_parameters.gradX = gradients_h; texture_parameters.gradY = gradients_v; } id_vector_temp_.clear(); for (uint32_t i = 0; i < 3; ++i) { id_vector_temp_.push_back(coordinates[i]); } texture_parameters.coords = builder_->createCompositeConstruct( type_float3_, id_vector_temp_); SampleTexture(texture_parameters, image_operands_mask, image_3d_unsigned, image_3d_signed, sampler, is_any_unsigned, is_any_signed, sample_result_unsigned_3d, sample_result_signed_3d); } if_data_is_3d.makeBeginElse(); spv::Id sample_result_unsigned_stacked, sample_result_signed_stacked; { // 2D stacked. if (use_computed_lod) { // Extract 2D gradients for stacked textures which are 2D arrays. uint_vector_temp_.clear(); uint_vector_temp_.push_back(0); uint_vector_temp_.push_back(1); texture_parameters.gradX = builder_->createRvalueSwizzle(spv::NoPrecision, type_float2_, gradients_h, uint_vector_temp_); texture_parameters.gradY = builder_->createRvalueSwizzle(spv::NoPrecision, type_float2_, gradients_v, uint_vector_temp_); } // Check if linear filtering is needed. bool vol_mag_filter_is_fetch_const = instr.attributes.vol_mag_filter == xenos::TextureFilter::kUseFetchConst; bool vol_min_filter_is_fetch_const = instr.attributes.vol_min_filter == xenos::TextureFilter::kUseFetchConst; bool vol_mag_filter_is_linear = instr.attributes.vol_mag_filter == xenos::TextureFilter::kLinear; bool vol_min_filter_is_linear = instr.attributes.vol_min_filter == xenos::TextureFilter::kLinear; spv::Id vol_filter_is_linear = spv::NoResult; if (use_computed_lod && (vol_mag_filter_is_fetch_const || vol_min_filter_is_fetch_const || vol_mag_filter_is_linear != vol_min_filter_is_linear)) { // Check if minifying along layers (derivative > 1 along any // axis). spv::Id layer_max_gradient = builder_->createBinBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450NMax, builder_->createCompositeExtract(gradients_h, type_float_, 2), builder_->createCompositeExtract(gradients_v, type_float_, 2)); if (!instr.attributes.unnormalized_coordinates) { // Denormalize the gradient if provided as normalized. assert_true(size[2] != spv::NoResult); layer_max_gradient = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, layer_max_gradient, size[2]); } // For NaN, considering that magnification is being done. spv::Id is_minifying_z = builder_->createBinOp( spv::OpFOrdLessThan, type_bool_, layer_max_gradient, builder_->makeFloatConstant(1.0f)); // Choose what filter is actually used, the minification or the // magnification one. spv::Id vol_mag_filter_is_linear_loaded = vol_mag_filter_is_fetch_const ? builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, fetch_constant_word_4, builder_->makeUintConstant(UINT32_C(1) << 0)), const_uint_0_) : builder_->makeBoolConstant(vol_mag_filter_is_linear); spv::Id vol_min_filter_is_linear_loaded = vol_min_filter_is_fetch_const ? builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, fetch_constant_word_4, builder_->makeUintConstant(UINT32_C(1) << 1)), const_uint_0_) : builder_->makeBoolConstant(vol_min_filter_is_linear); vol_filter_is_linear = builder_->createTriOp( spv::OpSelect, type_bool_, is_minifying_z, vol_min_filter_is_linear_loaded, vol_mag_filter_is_linear_loaded); } else { // No gradients, or using the same filter overrides for magnifying // and minifying. Assume always magnifying if no gradients (LOD 0, // always <= 0). LOD is within 2D layers, not between them (unlike // in 3D textures, which have mips with depth reduced), so it // shouldn't have effect on filtering between layers. if (vol_mag_filter_is_fetch_const) { vol_filter_is_linear = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, fetch_constant_word_4, builder_->makeUintConstant(UINT32_C(1) << 0)), const_uint_0_); } } spv::Id layer_coordinate = coordinates[2]; // Linear filtering may be needed either based on a dynamic // condition (the filtering mode is taken from the fetch constant, // or it's different for magnification and minification), or on a // static one (with gradients - specified in the instruction for // both magnification and minification as linear, without // gradients - specified for magnification as linear). // If the filter is linear, subtract 0.5 from the Z coordinate of // the first layer in filtering because 0.5 is in the middle of it. if (vol_filter_is_linear != spv::NoResult) { layer_coordinate = builder_->createTriOp( spv::OpSelect, type_float_, vol_filter_is_linear, builder_->createNoContractionBinOp( spv::OpFSub, type_float_, layer_coordinate, builder_->makeFloatConstant(0.5f)), layer_coordinate); } else if (vol_mag_filter_is_linear) { layer_coordinate = builder_->createNoContractionBinOp( spv::OpFSub, type_float_, layer_coordinate, builder_->makeFloatConstant(0.5f)); } // Sample the first layer, needed regardless of whether filtering is // needed. // Floor the array layer (Vulkan does rounding to nearest or + 0.5 // and floor even for the layer index, but on the Xenos, addressing // is similar to that of 3D textures). This is needed for both point // and linear filtering (with linear, 0.5 was subtracted // previously). spv::Id layer_0_coordinate = builder_->createUnaryBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450Floor, layer_coordinate); id_vector_temp_.clear(); id_vector_temp_.push_back(coordinates[0]); id_vector_temp_.push_back(coordinates[1]); id_vector_temp_.push_back(layer_0_coordinate); texture_parameters.coords = builder_->createCompositeConstruct( type_float3_, id_vector_temp_); SampleTexture( texture_parameters, image_operands_mask, image_2d_array_or_cube_unsigned, image_2d_array_or_cube_signed, sampler, is_any_unsigned, is_any_signed, sample_result_unsigned_stacked, sample_result_signed_stacked); // Sample the second layer if linear filtering is potentially needed // (conditionally or unconditionally, depending on whether the // filter needs to be chosen at runtime), and filter. if (vol_filter_is_linear != spv::NoResult || vol_mag_filter_is_linear) { spv::Block& block_z_head = *builder_->getBuildPoint(); spv::Block& block_z_linear = (vol_filter_is_linear != spv::NoResult) ? builder_->makeNewBlock() : block_z_head; spv::Block& block_z_merge = (vol_filter_is_linear != spv::NoResult) ? builder_->makeNewBlock() : block_z_head; if (vol_filter_is_linear != spv::NoResult) { builder_->createSelectionMerge( &block_z_merge, spv::SelectionControlDontFlattenMask); builder_->createConditionalBranch( vol_filter_is_linear, &block_z_linear, &block_z_merge); builder_->setBuildPoint(&block_z_linear); } spv::Id layer_1_coordinate = builder_->createBinOp( spv::OpFAdd, type_float_, layer_0_coordinate, builder_->makeFloatConstant(1.0f)); id_vector_temp_.clear(); id_vector_temp_.push_back(coordinates[0]); id_vector_temp_.push_back(coordinates[1]); id_vector_temp_.push_back(layer_1_coordinate); texture_parameters.coords = builder_->createCompositeConstruct( type_float3_, id_vector_temp_); spv::Id layer_lerp_factor = builder_->createUnaryBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450Fract, layer_coordinate); spv::Id sample_result_unsigned_stacked_filtered; spv::Id sample_result_signed_stacked_filtered; SampleTexture( texture_parameters, image_operands_mask, image_2d_array_or_cube_unsigned, image_2d_array_or_cube_signed, sampler, is_any_unsigned, is_any_signed, sample_result_unsigned_stacked_filtered, sample_result_signed_stacked_filtered, layer_lerp_factor, sample_result_unsigned_stacked, sample_result_signed_stacked); if (vol_filter_is_linear != spv::NoResult) { // Get the actual build point after the SampleTexture call for // phi. spv::Block& block_z_linear_end = *builder_->getBuildPoint(); builder_->createBranch(&block_z_merge); builder_->setBuildPoint(&block_z_merge); { std::unique_ptr filter_phi_op = std::make_unique( builder_->getUniqueId(), type_float4_, spv::OpPhi); filter_phi_op->addIdOperand( sample_result_unsigned_stacked_filtered); filter_phi_op->addIdOperand(block_z_linear_end.getId()); filter_phi_op->addIdOperand(sample_result_unsigned_stacked); filter_phi_op->addIdOperand(block_z_head.getId()); sample_result_unsigned_stacked = filter_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction( std::move(filter_phi_op)); } { std::unique_ptr filter_phi_op = std::make_unique( builder_->getUniqueId(), type_float4_, spv::OpPhi); filter_phi_op->addIdOperand( sample_result_signed_stacked_filtered); filter_phi_op->addIdOperand(block_z_linear_end.getId()); filter_phi_op->addIdOperand(sample_result_signed_stacked); filter_phi_op->addIdOperand(block_z_head.getId()); sample_result_signed_stacked = filter_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction( std::move(filter_phi_op)); } } else { sample_result_unsigned_stacked = sample_result_unsigned_stacked_filtered; sample_result_signed_stacked = sample_result_signed_stacked_filtered; } } } if_data_is_3d.makeEndIf(); sample_result_unsigned = if_data_is_3d.createMergePhi( sample_result_unsigned_3d, sample_result_unsigned_stacked); sample_result_signed = if_data_is_3d.createMergePhi( sample_result_signed_3d, sample_result_signed_stacked); } else { if (use_computed_lod) { texture_parameters.gradX = gradients_h; texture_parameters.gradY = gradients_v; } id_vector_temp_.clear(); for (uint32_t i = 0; i < 3; ++i) { id_vector_temp_.push_back(coordinates[i]); } texture_parameters.coords = builder_->createCompositeConstruct(type_float3_, id_vector_temp_); SampleTexture(texture_parameters, image_operands_mask, image_2d_array_or_cube_unsigned, image_2d_array_or_cube_signed, sampler, is_any_unsigned, is_any_signed, sample_result_unsigned, sample_result_signed); } // Swizzle the result components manually if needed, to `result`. // Because the same host format component may be replicated into // multiple guest components (such as for formats with less than 4 // components), yet the signedness is per-guest-component, it's not // possible to apply the signedness to host components before swizzling, // so doing it during (for unsigned vs. signed) and after (for biased // and gamma) swizzling. if (!features_.image_view_format_swizzle) { id_vector_temp_.clear(); id_vector_temp_.push_back( builder_->makeIntConstant(kSystemConstantTextureSwizzles)); id_vector_temp_.push_back( builder_->makeIntConstant(fetch_constant_index >> 3)); id_vector_temp_.push_back( builder_->makeIntConstant((fetch_constant_index >> 1) & 3)); // All 32 bits containing the values (24 bits) for 2 fetch constants. spv::Id swizzle_word = builder_->createLoad( builder_->createAccessChain(spv::StorageClassUniform, uniform_system_constants_, id_vector_temp_), spv::NoPrecision); uint32_t swizzle_word_offset = 3 * 4 * (fetch_constant_index & 1); spv::Id const_float_1 = builder_->makeFloatConstant(1.0f); uint32_t result_remaining_components = used_result_nonzero_components; uint32_t result_component_index; while (xe::bit_scan_forward(result_remaining_components, &result_component_index)) { result_remaining_components &= ~(UINT32_C(1) << result_component_index); uint32_t swizzle_bit_0_value = UINT32_C(1) << (swizzle_word_offset + 3 * result_component_index); spv::Id swizzle_bit_0 = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, swizzle_word, builder_->makeUintConstant(swizzle_bit_0_value)), const_uint_0_); // Bit 2 - X/Y/Z/W or 0/1. spv::Id swizzle_bit_2 = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, swizzle_word, builder_->makeUintConstant(swizzle_bit_0_value << 2)), const_uint_0_); SpirvBuilder::IfBuilder if_swizzle_constant( swizzle_bit_2, spv::SelectionControlDontFlattenMask, *builder_); spv::Id swizzle_result_constant; { // Constant values. // Bit 0 - 0 or 1. swizzle_result_constant = builder_->createTriOp( spv::OpSelect, type_float_, swizzle_bit_0, const_float_1, const_float_0_); } if_swizzle_constant.makeBeginElse(); spv::Id swizzle_result_component; { // Fetched components. // Select whether the result is signed or unsigned (or biased or // gamma-corrected) based on the post-swizzle signedness. spv::Id swizzle_sample_result = builder_->createTriOp( spv::OpSelect, type_float4_, builder_->smearScalar( spv::NoPrecision, result_is_signed[result_component_index], type_bool4_), sample_result_signed, sample_result_unsigned); // Bit 0 - X or Y, Z or W, 0 or 1. spv::Id swizzle_x_or_y = builder_->createTriOp( spv::OpSelect, type_float_, swizzle_bit_0, builder_->createCompositeExtract(swizzle_sample_result, type_float_, 1), builder_->createCompositeExtract(swizzle_sample_result, type_float_, 0)); spv::Id swizzle_z_or_w = builder_->createTriOp( spv::OpSelect, type_float_, swizzle_bit_0, builder_->createCompositeExtract(swizzle_sample_result, type_float_, 3), builder_->createCompositeExtract(swizzle_sample_result, type_float_, 2)); // Bit 1 - X/Y or Z/W. spv::Id swizzle_bit_1 = builder_->createBinOp( spv::OpINotEqual, type_bool_, builder_->createBinOp( spv::OpBitwiseAnd, type_uint_, swizzle_word, builder_->makeUintConstant(swizzle_bit_0_value << 1)), const_uint_0_); swizzle_result_component = builder_->createTriOp( spv::OpSelect, type_float_, swizzle_bit_1, swizzle_z_or_w, swizzle_x_or_y); } if_swizzle_constant.makeEndIf(); // Select between the constants and the fetched components. result[result_component_index] = if_swizzle_constant.createMergePhi( swizzle_result_constant, swizzle_result_component); } } // Apply the signednesses to all the needed components. If swizzling is // done in the shader rather than via the image view, unsigned or signed // source has already been selected into `result` - only need to bias or // to gamma-correct. spv::Id const_float_2 = builder_->makeFloatConstant(2.0f); spv::Id const_float_minus_1 = builder_->makeFloatConstant(-1.0f); { uint32_t result_remaining_components = used_result_nonzero_components; uint32_t result_component_index; while (xe::bit_scan_forward(result_remaining_components, &result_component_index)) { result_remaining_components &= ~(UINT32_C(1) << result_component_index); spv::Id sample_result_component_unsigned = features_.image_view_format_swizzle ? builder_->createCompositeExtract(sample_result_unsigned, type_float_, result_component_index) : result[result_component_index]; spv::Block& block_sign_head = *builder_->getBuildPoint(); spv::Block* block_sign_signed = features_.image_view_format_swizzle ? &builder_->makeNewBlock() : nullptr; spv::Block& block_sign_unsigned_biased = builder_->makeNewBlock(); spv::Block& block_sign_gamma_start = builder_->makeNewBlock(); spv::Block& block_sign_merge = builder_->makeNewBlock(); builder_->createSelectionMerge( &block_sign_merge, spv::SelectionControlDontFlattenMask); { std::unique_ptr sign_switch_op = std::make_unique(spv::OpSwitch); sign_switch_op->addIdOperand( swizzled_signs[result_component_index]); // Make unsigned (do nothing, take the unsigned component in the // phi) the default, and also, if unsigned or signed has already // been selected in swizzling, make signed the default to since // it, just like unsigned, doesn't need any transformations. sign_switch_op->addIdOperand(block_sign_merge.getId()); if (block_sign_signed) { sign_switch_op->addImmediateOperand( uint32_t(xenos::TextureSign::kSigned)); sign_switch_op->addIdOperand(block_sign_signed->getId()); } sign_switch_op->addImmediateOperand( uint32_t(xenos::TextureSign::kUnsignedBiased)); sign_switch_op->addIdOperand(block_sign_unsigned_biased.getId()); sign_switch_op->addImmediateOperand( uint32_t(xenos::TextureSign::kGamma)); sign_switch_op->addIdOperand(block_sign_gamma_start.getId()); builder_->getBuildPoint()->addInstruction( std::move(sign_switch_op)); } if (block_sign_signed) { block_sign_signed->addPredecessor(&block_sign_head); } block_sign_unsigned_biased.addPredecessor(&block_sign_head); block_sign_gamma_start.addPredecessor(&block_sign_head); block_sign_merge.addPredecessor(&block_sign_head); // Signed. spv::Id sample_result_component_signed = sample_result_component_unsigned; if (block_sign_signed) { builder_->setBuildPoint(block_sign_signed); sample_result_component_signed = builder_->createCompositeExtract( sample_result_signed, type_float_, result_component_index); builder_->createBranch(&block_sign_merge); } // Unsigned biased. builder_->setBuildPoint(&block_sign_unsigned_biased); spv::Id sample_result_component_unsigned_biased = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, sample_result_component_unsigned, const_float_2); sample_result_component_unsigned_biased = builder_->createNoContractionBinOp( spv::OpFAdd, type_float_, sample_result_component_unsigned_biased, const_float_minus_1); builder_->createBranch(&block_sign_merge); // Gamma. builder_->setBuildPoint(&block_sign_gamma_start); // TODO(Triang3l): Gamma resolve target as sRGB sampling. spv::Id sample_result_component_gamma = PWLGammaToLinear(sample_result_component_unsigned, false); // Get the current build point for the phi operation not to assume // that it will be the same as before PWLGammaToLinear. spv::Block& block_sign_gamma_end = *builder_->getBuildPoint(); builder_->createBranch(&block_sign_merge); // Merge. builder_->setBuildPoint(&block_sign_merge); { std::unique_ptr sign_phi_op = std::make_unique(builder_->getUniqueId(), type_float_, spv::OpPhi); if (block_sign_signed) { sign_phi_op->addIdOperand(sample_result_component_signed); sign_phi_op->addIdOperand(block_sign_signed->getId()); } sign_phi_op->addIdOperand( sample_result_component_unsigned_biased); sign_phi_op->addIdOperand(block_sign_unsigned_biased.getId()); sign_phi_op->addIdOperand(sample_result_component_gamma); sign_phi_op->addIdOperand(block_sign_gamma_end.getId()); sign_phi_op->addIdOperand(sample_result_component_unsigned); sign_phi_op->addIdOperand(block_sign_head.getId()); result[result_component_index] = sign_phi_op->getResultId(); builder_->getBuildPoint()->addInstruction(std::move(sign_phi_op)); } } } // Apply the exponent bias from the bits 13:18 of the fetch constant // word 3. spv::Id result_exponent_bias = builder_->createBinBuiltinCall( type_float_, ext_inst_glsl_std_450_, GLSLstd450Ldexp, const_float_1_, builder_->createTriOp(spv::OpBitFieldSExtract, type_int_, fetch_constant_word_3_signed, builder_->makeUintConstant(13), builder_->makeUintConstant(6))); { uint32_t result_remaining_components = used_result_nonzero_components; uint32_t result_component_index; while (xe::bit_scan_forward(result_remaining_components, &result_component_index)) { result_remaining_components &= ~(UINT32_C(1) << result_component_index); result[result_component_index] = builder_->createNoContractionBinOp( spv::OpFMul, type_float_, result[result_component_index], result_exponent_bias); } } } } } // Store the needed components of the result. spv::Id result_vector; if (used_result_component_count > 1) { id_vector_temp_.clear(); uint32_t result_components_remaining = used_result_components; uint32_t result_component_index; while (xe::bit_scan_forward(result_components_remaining, &result_component_index)) { result_components_remaining &= ~(UINT32_C(1) << result_component_index); id_vector_temp_.push_back(result[result_component_index]); } result_vector = builder_->createCompositeConstruct( type_float_vectors_[used_result_component_count - 1], id_vector_temp_); } else { uint32_t result_component_index; xe::bit_scan_forward(used_result_components, &result_component_index); result_vector = result[result_component_index]; } StoreResult(instr.result, result_vector); } size_t SpirvShaderTranslator::FindOrAddTextureBinding( uint32_t fetch_constant, xenos::FetchOpDimension dimension, bool is_signed) { // 1D and 2D textures (including stacked ones) are treated as 2D arrays for // binding and coordinate simplicity. if (dimension == xenos::FetchOpDimension::k1D) { dimension = xenos::FetchOpDimension::k2D; } for (size_t i = 0; i < texture_bindings_.size(); ++i) { const TextureBinding& texture_binding = texture_bindings_[i]; if (texture_binding.fetch_constant == fetch_constant && texture_binding.dimension == dimension && texture_binding.is_signed == is_signed) { return i; } } // TODO(Triang3l): Limit the total count to that actually supported by the // implementation. size_t new_texture_binding_index = texture_bindings_.size(); TextureBinding& new_texture_binding = texture_bindings_.emplace_back(); new_texture_binding.fetch_constant = fetch_constant; new_texture_binding.dimension = dimension; new_texture_binding.is_signed = is_signed; spv::Dim type_dimension; bool is_array; const char* dimension_name; switch (dimension) { case xenos::FetchOpDimension::k3DOrStacked: type_dimension = spv::Dim3D; is_array = false; dimension_name = "3d"; break; case xenos::FetchOpDimension::kCube: type_dimension = spv::DimCube; is_array = false; dimension_name = "cube"; break; default: type_dimension = spv::Dim2D; is_array = true; dimension_name = "2d"; } new_texture_binding.variable = builder_->createVariable( spv::NoPrecision, spv::StorageClassUniformConstant, builder_->makeImageType(type_float_, type_dimension, false, is_array, false, 1, spv::ImageFormatUnknown), fmt::format("xe_texture{}_{}_{}", fetch_constant, dimension_name, is_signed ? 's' : 'u') .c_str()); builder_->addDecoration( new_texture_binding.variable, spv::DecorationDescriptorSet, int(is_vertex_shader() ? kDescriptorSetTexturesVertex : kDescriptorSetTexturesPixel)); builder_->addDecoration(new_texture_binding.variable, spv::DecorationBinding, int(new_texture_binding_index)); if (features_.spirv_version >= spv::Spv_1_4) { main_interface_.push_back(new_texture_binding.variable); } return new_texture_binding_index; } size_t SpirvShaderTranslator::FindOrAddSamplerBinding( uint32_t fetch_constant, xenos::TextureFilter mag_filter, xenos::TextureFilter min_filter, xenos::TextureFilter mip_filter, xenos::AnisoFilter aniso_filter) { if (aniso_filter != xenos::AnisoFilter::kUseFetchConst) { // TODO(Triang3l): Limit to what's actually supported by the implementation. aniso_filter = std::min(aniso_filter, xenos::AnisoFilter::kMax_16_1); } for (size_t i = 0; i < sampler_bindings_.size(); ++i) { const SamplerBinding& sampler_binding = sampler_bindings_[i]; if (sampler_binding.fetch_constant == fetch_constant && sampler_binding.mag_filter == mag_filter && sampler_binding.min_filter == min_filter && sampler_binding.mip_filter == mip_filter && sampler_binding.aniso_filter == aniso_filter) { return i; } } // TODO(Triang3l): Limit the total count to that actually supported by the // implementation. size_t new_sampler_binding_index = sampler_bindings_.size(); SamplerBinding& new_sampler_binding = sampler_bindings_.emplace_back(); new_sampler_binding.fetch_constant = fetch_constant; new_sampler_binding.mag_filter = mag_filter; new_sampler_binding.min_filter = min_filter; new_sampler_binding.mip_filter = mip_filter; new_sampler_binding.aniso_filter = aniso_filter; std::ostringstream name; static constexpr char kFilterSuffixes[] = {'p', 'l', 'b', 'f'}; name << "xe_sampler" << fetch_constant << '_' << kFilterSuffixes[uint32_t(mag_filter)] << kFilterSuffixes[uint32_t(min_filter)] << kFilterSuffixes[uint32_t(mip_filter)]; if (aniso_filter != xenos::AnisoFilter::kUseFetchConst) { if (aniso_filter == xenos::AnisoFilter::kDisabled) { name << "_a0"; } else { name << "_a" << (UINT32_C(1) << (uint32_t(aniso_filter) - 1)); } } new_sampler_binding.variable = builder_->createVariable( spv::NoPrecision, spv::StorageClassUniformConstant, builder_->makeSamplerType(), name.str().c_str()); builder_->addDecoration( new_sampler_binding.variable, spv::DecorationDescriptorSet, int(is_vertex_shader() ? kDescriptorSetTexturesVertex : kDescriptorSetTexturesPixel)); // The binding indices will be specified later after all textures are added as // samplers are located after images in the descriptor set. if (features_.spirv_version >= spv::Spv_1_4) { main_interface_.push_back(new_sampler_binding.variable); } return new_sampler_binding_index; } void SpirvShaderTranslator::SampleTexture( spv::Builder::TextureParameters& texture_parameters, spv::ImageOperandsMask image_operands_mask, spv::Id image_unsigned, spv::Id image_signed, spv::Id sampler, spv::Id is_any_unsigned, spv::Id is_any_signed, spv::Id& result_unsigned_out, spv::Id& result_signed_out, spv::Id lerp_factor, spv::Id lerp_first_unsigned, spv::Id lerp_first_signed) { for (uint32_t i = 0; i < 2; ++i) { SpirvBuilder::IfBuilder sign_if(i ? is_any_signed : is_any_unsigned, spv::SelectionControlDontFlattenMask, *builder_); spv::Id sign_result; { spv::Id image = i ? image_signed : image_unsigned; // OpSampledImage must be in the same block as where its result is used. texture_parameters.sampler = builder_->createBinOp( spv::OpSampledImage, builder_->makeSampledImageType(builder_->getTypeId(image)), image, sampler); sign_result = builder_->createTextureCall( spv::NoPrecision, type_float4_, false, false, false, false, false, texture_parameters, image_operands_mask); if (lerp_factor != spv::NoResult) { spv::Id lerp_first = i ? lerp_first_signed : lerp_first_unsigned; if (lerp_first != spv::NoResult) { spv::Id lerp_difference = builder_->createNoContractionBinOp( spv::OpVectorTimesScalar, type_float4_, builder_->createNoContractionBinOp(spv::OpFSub, type_float4_, sign_result, lerp_first), lerp_factor); sign_result = builder_->createNoContractionBinOp( spv::OpFAdd, type_float4_, sign_result, lerp_difference); } } } sign_if.makeEndIf(); // This may overwrite the first lerp endpoint for the sign (such usage of // this function is allowed). (i ? result_signed_out : result_unsigned_out) = sign_if.createMergePhi(sign_result, const_float4_0_); } } spv::Id SpirvShaderTranslator::QueryTextureLod( spv::Builder::TextureParameters& texture_parameters, spv::Id image_unsigned, spv::Id image_signed, spv::Id sampler, spv::Id is_all_signed) { // OpSampledImage must be in the same block as where its result is used. SpirvBuilder::IfBuilder if_signed( is_all_signed, spv::SelectionControlDontFlattenMask, *builder_); spv::Id lod_signed; { texture_parameters.sampler = builder_->createBinOp( spv::OpSampledImage, builder_->makeSampledImageType(builder_->getTypeId(image_signed)), image_signed, sampler); lod_signed = builder_->createCompositeExtract( builder_->createTextureQueryCall(spv::OpImageQueryLod, texture_parameters, false), type_float_, 1); } if_signed.makeBeginElse(); spv::Id lod_unsigned; { texture_parameters.sampler = builder_->createBinOp( spv::OpSampledImage, builder_->makeSampledImageType(builder_->getTypeId(image_unsigned)), image_unsigned, sampler); lod_unsigned = builder_->createCompositeExtract( builder_->createTextureQueryCall(spv::OpImageQueryLod, texture_parameters, false), type_float_, 1); } if_signed.makeEndIf(); return if_signed.createMergePhi(lod_signed, lod_unsigned); } } // namespace gpu } // namespace xe