/**
 ******************************************************************************
 * 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 <climits>
#include <cmath>

#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/gpu_flags.h"
#include "xenia/gpu/render_target_cache.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));
      } else if (cvars::ac6_ground_fix) {
        // UGLY HACK for AC6 copied from the DXBC translator.
        // Proper fix requires accurate RCP implementation.
        index = builder_->createNoContractionBinOp(
            spv::OpFAdd, type_float_, index,
            builder_->makeFloatConstant(0.00025f));
      }
      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<spv::Instruction> composite_construct_op =
            std::make_unique<spv::Instruction>(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<spv::Instruction> composite_construct_op =
          std::make_unique<spv::Instruction>(
              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<unsigned int>(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);
        }
      }
    }

    // Check if this texture is from a resolution-scaled resolve operation.
    // This affects both size and offset calculations.
    spv::Id is_texture_resolved = spv::NoResult;
    if (cvars::draw_resolution_scaled_texture_offsets &&
        (draw_resolution_scale_x_ > 1 || draw_resolution_scale_y_ > 1)) {
      // Load textures_resolved from system constants.
      id_vector_temp_.clear();
      id_vector_temp_.push_back(
          builder_->makeIntConstant(kSystemConstantTexturesResolved));
      spv::Id textures_resolved =
          builder_->createLoad(builder_->createAccessChain(
                                   spv::StorageClassUniform,
                                   uniform_system_constants_, id_vector_temp_),
                               spv::NoPrecision);
      // Check if this texture is resolved:
      // (textures_resolved >> fetch_constant_index) & 1
      assert_true(fetch_constant_index < 32);
      is_texture_resolved = builder_->createBinOp(
          spv::OpINotEqual, type_bool_,
          builder_->createBinOp(
              spv::OpBitwiseAnd, type_uint_, textures_resolved,
              builder_->makeUintConstant(UINT32_C(1) << fetch_constant_index)),
          const_uint_0_);
    }

    // Scale the size for resolution-scaled textures.
    // When a texture is from a resolve operation (scaled), its actual host
    // dimensions are larger than the guest dimensions in the fetch constant.
    // The size must be scaled so that coordinate normalization and offset
    // calculations use the correct host dimensions.
    if (is_texture_resolved != spv::NoResult && size_needed_components) {
      if (size[0] != spv::NoResult && draw_resolution_scale_x_ > 1) {
        spv::Id scaled_size_x = builder_->createNoContractionBinOp(
            spv::OpFMul, type_float_, size[0],
            builder_->makeFloatConstant(float(draw_resolution_scale_x_)));
        size[0] =
            builder_->createTriOp(spv::OpSelect, type_float_,
                                  is_texture_resolved, scaled_size_x, size[0]);
      }
      if (size[1] != spv::NoResult && draw_resolution_scale_y_ > 1) {
        spv::Id scaled_size_y = builder_->createNoContractionBinOp(
            spv::OpFMul, type_float_, size[1],
            builder_->makeFloatConstant(float(draw_resolution_scale_y_)));
        size[1] =
            builder_->createTriOp(spv::OpSelect, type_float_,
                                  is_texture_resolved, scaled_size_y, size[1]);
      }
      // Z size is not scaled (depth/layers don't change with resolution).
    }

    // 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.
      // When a texture is from a resolution-scaled resolve, offsets are in
      // guest texels but the size is in host texels. We need to scale offsets
      // to compensate:
      // - For normalized coords: coord + (offset * scale) / size_scaled
      // - For unnormalized coords: (coord + offset) * scale / size_scaled
      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);
          }
          // For resolution-scaled textures with unnormalized coords, we need
          // to scale the coordinate (which now includes offset) before
          // dividing by the scaled size. This ensures:
          // (coord + offset) * scale / size_scaled = (coord + offset) /
          // guest_size
          if (is_texture_resolved != spv::NoResult &&
              ((i == 0 && draw_resolution_scale_x_ > 1) ||
               (i == 1 && draw_resolution_scale_y_ > 1))) {
            float scale = (i == 0) ? float(draw_resolution_scale_x_)
                                   : float(draw_resolution_scale_y_);
            spv::Id scaled_coord = builder_->createNoContractionBinOp(
                spv::OpFMul, type_float_, coordinate_ref,
                builder_->makeFloatConstant(scale));
            coordinate_ref = builder_->createTriOp(
                spv::OpSelect, type_float_, is_texture_resolved, scaled_coord,
                coordinate_ref);
          }
          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);
            // For resolution-scaled textures with normalized coords, scale the
            // offset before normalizing. This ensures:
            // coord + (offset * scale) / size_scaled = coord + offset /
            // guest_size
            spv::Id effective_offset = component_offset;
            if (is_texture_resolved != spv::NoResult &&
                ((i == 0 && draw_resolution_scale_x_ > 1) ||
                 (i == 1 && draw_resolution_scale_y_ > 1))) {
              float scale = (i == 0) ? float(draw_resolution_scale_x_)
                                     : float(draw_resolution_scale_y_);
              spv::Id scaled_offset = builder_->createNoContractionBinOp(
                  spv::OpFMul, type_float_, component_offset,
                  builder_->makeFloatConstant(scale));
              effective_offset = builder_->createTriOp(
                  spv::OpSelect, type_float_, is_texture_resolved,
                  scaled_offset, component_offset);
            }
            spv::Id component_offset_normalized =
                builder_->createNoContractionBinOp(
                    spv::OpFDiv, type_float_, effective_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<spv::Instruction> z_phi_op =
                std::make_unique<spv::Instruction>(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<spv::Instruction> ma_switch_op =
              std::make_unique<spv::Instruction>(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<spv::Instruction> x_phi_op =
              std::make_unique<spv::Instruction>(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<spv::Instruction> y_phi_op =
              std::make_unique<spv::Instruction>(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<spv::Instruction> z_phi_op =
              std::make_unique<spv::Instruction>(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<spv::Instruction> filter_phi_op =
                      std::make_unique<spv::Instruction>(
                          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<spv::Instruction> filter_phi_op =
                      std::make_unique<spv::Instruction>(
                          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<spv::Instruction> sign_switch_op =
                  std::make_unique<spv::Instruction>(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);
            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<spv::Instruction> sign_phi_op =
                  std::make_unique<spv::Instruction>(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));
            }
          }
        }

        // num_format is applied after signs/gamma. Fixed textures sample as
        // normalized host values, so integer num_format scales them back to
        // guest integer units here.
        id_vector_temp_.clear();
        id_vector_temp_.push_back(
            builder_->makeIntConstant(kSystemConstantTextureIntegerScaleBits));
        id_vector_temp_.push_back(
            builder_->makeIntConstant(int32_t(fetch_constant_index >> 2)));
        id_vector_temp_.push_back(
            builder_->makeIntConstant(int32_t(fetch_constant_index & 3)));
        spv::Id integer_scale_bits_packed = builder_->createLoad(
            builder_->createAccessChain(spv::StorageClassUniform,
                                        uniform_system_constants_,
                                        id_vector_temp_),
            spv::NoPrecision);
        {
          // Uniform early out. Zero means leave the sample alone. Only integer
          // num_format on fixed textures has scale bits.
          spv::Id integer_scale_active = builder_->createBinOp(
              spv::OpINotEqual, type_bool_, integer_scale_bits_packed,
              builder_->makeUintConstant(0));
          SpirvBuilder::IfBuilder if_integer_scale(
              integer_scale_active, spv::SelectionControlMaskNone, *builder_);
          spv::Id scaled_result[4] = {};
          {
            spv::Id const_uint_1 = builder_->makeUintConstant(1);
            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 scale_bits = builder_->createTriOp(
                  spv::OpBitFieldUExtract, type_uint_,
                  integer_scale_bits_packed,
                  builder_->makeUintConstant(result_component_index * 5),
                  builder_->makeUintConstant(5));
              spv::Id scale_shift = builder_->createBinOp(
                  spv::OpIAdd, type_uint_,
                  builder_->createBinOp(spv::OpBitwiseAnd, type_uint_,
                                        scale_bits,
                                        builder_->makeUintConstant(0xF)),
                  const_uint_1);
              scale_shift = builder_->createBinOp(
                  spv::OpISub, type_uint_, scale_shift,
                  builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_,
                                        scale_bits,
                                        builder_->makeUintConstant(4),
                                        builder_->makeUintConstant(1)));
              spv::Id scale_uint = builder_->createBinOp(
                  spv::OpISub, type_uint_,
                  builder_->createBinOp(spv::OpShiftLeftLogical, type_uint_,
                                        const_uint_1, scale_shift),
                  const_uint_1);
              scaled_result[result_component_index] =
                  builder_->createNoContractionBinOp(
                      spv::OpFMul, type_float_, result[result_component_index],
                      builder_->createUnaryOp(spv::OpConvertUToF, type_float_,
                                              scale_uint));
            }
          }
          if_integer_scale.makeEndIf();
          // Keep the original result when the scale branch is skipped.
          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] = if_integer_scale.createMergePhi(
                scaled_result[result_component_index],
                result[result_component_index]);
          }
        }

        // 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
