Files
Xenia-Canary/src/xenia/gpu/spirv_shader_translator_fetch.cc
Herman S 32889f51be Use fetch_contant_word3 for exponent bias
Previously was using word4 which is LOD bias, fixes many graphic issues
with Vulkan backend
2025-12-06 22:26:40 +01:00

2454 lines
119 KiB
C++

/**
******************************************************************************
* 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"
namespace xe {
namespace gpu {
void SpirvShaderTranslator::ProcessVertexFetchInstruction(
const ParsedVertexFetchInstruction& instr) {
UpdateInstructionPredication(instr.is_predicated, instr.predicate_condition);
uint32_t used_result_components = instr.result.GetUsedResultComponents();
uint32_t needed_words = xenos::GetVertexFormatNeededWords(
instr.attributes.data_format, used_result_components);
// If this is vfetch_full, the address may still be needed for vfetch_mini -
// don't exit before calculating the address.
if (!needed_words && instr.is_mini_fetch) {
// Nothing to load - just constant 0/1 writes, or the swizzle includes only
// components that don't exist in the format (writing zero instead of them).
// Unpacking assumes at least some word is needed.
StoreResult(instr.result, spv::NoResult);
return;
}
EnsureBuildPointAvailable();
uint32_t fetch_constant_word_0_index = instr.operands[1].storage_index << 1;
spv::Id address;
if (instr.is_mini_fetch) {
// `base + index * stride` loaded by vfetch_full.
address = builder_->createLoad(var_main_vfetch_address_, spv::NoPrecision);
} else {
// Get the base address in dwords from the bits 2:31 of the first fetch
// constant word.
id_vector_temp_.clear();
// The only element of the fetch constant buffer.
id_vector_temp_.push_back(const_int_0_);
// Vector index.
id_vector_temp_.push_back(
builder_->makeIntConstant(int(fetch_constant_word_0_index >> 2)));
// Component index.
id_vector_temp_.push_back(
builder_->makeIntConstant(int(fetch_constant_word_0_index & 3)));
spv::Id fetch_constant_word_0 = builder_->createLoad(
builder_->createAccessChain(spv::StorageClassUniform,
uniform_fetch_constants_, id_vector_temp_),
spv::NoPrecision);
// TODO(Triang3l): Verify the fetch constant type (that it's a vertex fetch,
// not a texture fetch) here instead of dropping draws with invalid vertex
// fetch constants on the CPU when proper bound checks are added - vfetch
// may be conditional, so fetch constants may also be used conditionally.
address = builder_->createUnaryOp(
spv::OpBitcast, type_int_,
builder_->createBinOp(spv::OpShiftRightLogical, type_uint_,
fetch_constant_word_0,
builder_->makeUintConstant(2)));
if (instr.attributes.stride) {
// Convert the index to an integer by flooring or by rounding to the
// nearest (as floor(index + 0.5) because rounding to the nearest even
// makes no sense for addressing, both 1.5 and 2.5 would be 2).
spv::Id index = GetOperandComponents(
LoadOperandStorage(instr.operands[0]), instr.operands[0], 0b0001);
if (instr.attributes.is_index_rounded) {
index = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, index, builder_->makeFloatConstant(0.5f));
}
index = builder_->createUnaryOp(
spv::OpConvertFToS, type_int_,
builder_->createUnaryBuiltinCall(type_float_, ext_inst_glsl_std_450_,
GLSLstd450Floor, index));
if (instr.attributes.stride > 1) {
index = builder_->createBinOp(
spv::OpIMul, type_int_, index,
builder_->makeIntConstant(int(instr.attributes.stride)));
}
address = builder_->createBinOp(spv::OpIAdd, type_int_, address, index);
}
// Store the address for the subsequent vfetch_mini.
builder_->createStore(address, var_main_vfetch_address_);
}
if (!needed_words) {
// The vfetch_full address has been loaded for the subsequent vfetch_mini,
// but there's no data to load.
StoreResult(instr.result, spv::NoResult);
return;
}
// Load the needed words.
unsigned int word_composite_indices[4] = {};
spv::Id word_composite_constituents[4];
uint32_t word_count = 0;
uint32_t words_remaining = needed_words;
uint32_t word_index;
while (xe::bit_scan_forward(words_remaining, &word_index)) {
words_remaining &= ~(1 << word_index);
spv::Id word_address = address;
// Add the word offset from the instruction (signed), plus the offset of the
// word within the element.
int32_t word_offset = instr.attributes.offset + word_index;
if (word_offset) {
word_address =
builder_->createBinOp(spv::OpIAdd, type_int_, word_address,
builder_->makeIntConstant(int(word_offset)));
}
word_composite_indices[word_index] = word_count;
// FIXME(Triang3l): Bound checking is not done here, but haven't encountered
// any games relying on out-of-bounds access. On Adreno 200 on Android (LG
// P705), however, words (not full elements) out of glBufferData bounds
// contain 0.
word_composite_constituents[word_count++] =
LoadUint32FromSharedMemory(word_address);
}
spv::Id words;
if (word_count > 1) {
// Copying from the array to id_vector_temp_ now, not in the loop above,
// because of the LoadUint32FromSharedMemory call (potentially using
// id_vector_temp_ internally).
id_vector_temp_.clear();
id_vector_temp_.insert(id_vector_temp_.cend(), word_composite_constituents,
word_composite_constituents + word_count);
words = builder_->createCompositeConstruct(
type_uint_vectors_[word_count - 1], id_vector_temp_);
} else {
words = word_composite_constituents[0];
}
// Endian swap the words, getting the endianness from bits 0:1 of the second
// fetch constant word.
uint32_t fetch_constant_word_1_index = fetch_constant_word_0_index + 1;
id_vector_temp_.clear();
// The only element of the fetch constant buffer.
id_vector_temp_.push_back(const_int_0_);
// Vector index.
id_vector_temp_.push_back(
builder_->makeIntConstant(int(fetch_constant_word_1_index >> 2)));
// Component index.
id_vector_temp_.push_back(
builder_->makeIntConstant(int(fetch_constant_word_1_index & 3)));
spv::Id fetch_constant_word_1 = builder_->createLoad(
builder_->createAccessChain(spv::StorageClassUniform,
uniform_fetch_constants_, id_vector_temp_),
spv::NoPrecision);
words = EndianSwap32Uint(
words, builder_->createBinOp(spv::OpBitwiseAnd, type_uint_,
fetch_constant_word_1,
builder_->makeUintConstant(0b11)));
spv::Id result = spv::NoResult;
// Convert the format.
uint32_t used_format_components =
used_result_components & ((1 << xenos::GetVertexFormatComponentCount(
instr.attributes.data_format)) -
1);
// If needed_words is not zero (checked in the beginning), this must not be
// zero too. For simplicity, it's assumed that something will be unpacked
// here.
assert_not_zero(used_format_components);
uint32_t used_format_component_count = xe::bit_count(used_format_components);
spv::Id result_type = type_float_vectors_[used_format_component_count - 1];
bool format_is_packed = false;
int packed_widths[4] = {}, packed_offsets[4] = {};
uint32_t packed_words[4] = {};
switch (instr.attributes.data_format) {
case xenos::VertexFormat::k_8_8_8_8:
format_is_packed = true;
packed_widths[0] = packed_widths[1] = packed_widths[2] =
packed_widths[3] = 8;
packed_offsets[1] = 8;
packed_offsets[2] = 16;
packed_offsets[3] = 24;
break;
case xenos::VertexFormat::k_2_10_10_10:
format_is_packed = true;
packed_widths[0] = packed_widths[1] = packed_widths[2] = 10;
packed_widths[3] = 2;
packed_offsets[1] = 10;
packed_offsets[2] = 20;
packed_offsets[3] = 30;
break;
case xenos::VertexFormat::k_10_11_11:
format_is_packed = true;
packed_widths[0] = packed_widths[1] = 11;
packed_widths[2] = 10;
packed_offsets[1] = 11;
packed_offsets[2] = 22;
break;
case xenos::VertexFormat::k_11_11_10:
format_is_packed = true;
packed_widths[0] = 10;
packed_widths[1] = packed_widths[2] = 11;
packed_offsets[1] = 10;
packed_offsets[2] = 21;
break;
case xenos::VertexFormat::k_16_16:
format_is_packed = true;
packed_widths[0] = packed_widths[1] = 16;
packed_offsets[1] = 16;
break;
case xenos::VertexFormat::k_16_16_16_16:
format_is_packed = true;
packed_widths[0] = packed_widths[1] = packed_widths[2] =
packed_widths[3] = 16;
packed_offsets[1] = packed_offsets[3] = 16;
packed_words[2] = packed_words[3] = 1;
break;
case xenos::VertexFormat::k_16_16_FLOAT:
case xenos::VertexFormat::k_16_16_16_16_FLOAT: {
// FIXME(Triang3l): This converts from GLSL float16 with NaNs instead of
// Xbox 360 float16 with extended range. However, haven't encountered
// games relying on that yet.
spv::Id word_needed_component_values[2] = {};
for (uint32_t i = 0; i < 2; ++i) {
uint32_t word_needed_components =
(used_format_components >> (i * 2)) & 0b11;
if (!word_needed_components) {
continue;
}
spv::Id word;
if (word_count > 1) {
word = builder_->createCompositeExtract(words, type_uint_,
word_composite_indices[i]);
} else {
word = words;
}
word = builder_->createUnaryBuiltinCall(type_float2_,
ext_inst_glsl_std_450_,
GLSLstd450UnpackHalf2x16, word);
if (word_needed_components != 0b11) {
// If only one of two components is needed, extract it.
word = builder_->createCompositeExtract(
word, type_float_, (word_needed_components & 0b01) ? 0 : 1);
}
word_needed_component_values[i] = word;
}
if (word_needed_component_values[1] == spv::NoResult) {
result = word_needed_component_values[0];
} else if (word_needed_component_values[0] == spv::NoResult) {
result = word_needed_component_values[1];
} else {
// Bypassing the assertion in spv::Builder::createCompositeConstruct as
// of November 5, 2020 - can construct vectors by concatenating vectors,
// not just from individual scalars.
std::unique_ptr<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);
}
}
}
// FIXME(Triang3l): Mip lerp factor needs to be calculated, and the
// coordinate lerp factors should be calculated at the mip level texels
// would be sampled from. That would require some way of calculating the
// LOD that would be applicable to explicit gradients and vertex shaders.
// Also, with point sampling, possibly lerp factors need to be 0. W (mip
// lerp factor) should have been masked out previously because it's not
// supported currently.
assert_false(instr.opcode == ucode::FetchOpcode::kGetTextureWeights &&
(used_result_nonzero_components & 0b1000));
// Load the needed original values of the coordinates operand.
uint32_t coordinates_needed_components =
instr.opcode == ucode::FetchOpcode::kGetTextureWeights
? used_result_nonzero_components
: ((UINT32_C(1)
<< xenos::GetFetchOpDimensionComponentCount(instr.dimension)) -
1);
assert_not_zero(coordinates_needed_components);
spv::Id coordinates_operand =
GetOperandComponents(LoadOperandStorage(instr.operands[0]),
instr.operands[0], coordinates_needed_components);
spv::Id coordinates[] = {const_float_0_, const_float_0_, const_float_0_};
if (xe::bit_count(coordinates_needed_components) > 1) {
uint32_t coordinates_remaining_components = coordinates_needed_components;
uint32_t coordinate_component_index;
uint32_t coordinate_operand_component_index = 0;
while (xe::bit_scan_forward(coordinates_remaining_components,
&coordinate_component_index)) {
coordinates_remaining_components &=
~(UINT32_C(1) << coordinate_component_index);
coordinates[coordinate_component_index] =
builder_->createCompositeExtract(
coordinates_operand, type_float_,
coordinate_operand_component_index++);
}
} else {
uint32_t coordinate_component_index;
xe::bit_scan_forward(coordinates_needed_components,
&coordinate_component_index);
coordinates[coordinate_component_index] = coordinates_operand;
}
// Resolution scale doesn't need reverting for texture weights - weights are
// calculated from fractional parts of coordinates which are
// scale-independent.
if (instr.opcode == ucode::FetchOpcode::kGetTextureWeights) {
// FIXME(Triang3l): Filtering modes should possibly be taken into account,
// but for simplicity, not doing that - from a high level point of view,
// would be useless to get weights that will always be zero.
uint32_t coordinates_remaining_components = coordinates_needed_components;
uint32_t coordinate_component_index;
while (xe::bit_scan_forward(coordinates_remaining_components,
&coordinate_component_index)) {
coordinates_remaining_components &=
~(UINT32_C(1) << coordinate_component_index);
spv::Id result_component = coordinates[coordinate_component_index];
// Need unnormalized coordinates.
if (!instr.attributes.unnormalized_coordinates) {
spv::Id size_component = size[coordinate_component_index];
assert_true(size_component != spv::NoResult);
result_component = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, result_component, size_component);
}
float component_offset = offset_values[coordinate_component_index];
if (component_offset) {
result_component = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, result_component,
builder_->makeFloatConstant(component_offset));
}
// 0.5 has already been subtracted via offsets previously.
result_component = builder_->createUnaryBuiltinCall(
type_float_, ext_inst_glsl_std_450_, GLSLstd450Fract,
result_component);
result[coordinate_component_index] = result_component;
}
} else {
// kTextureFetch or kGetTextureComputedLod.
// Normalize the XY coordinates, and apply the offset.
for (uint32_t i = 0;
i <= uint32_t(instr.dimension != xenos::FetchOpDimension::k1D);
++i) {
spv::Id& coordinate_ref = coordinates[i];
spv::Id component_offset =
offset_values[i] ? builder_->makeFloatConstant(offset_values[i])
: spv::NoResult;
spv::Id size_component = size[i];
if (instr.attributes.unnormalized_coordinates) {
if (component_offset != spv::NoResult) {
coordinate_ref = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, coordinate_ref, component_offset);
}
assert_true(size_component != spv::NoResult);
coordinate_ref = builder_->createNoContractionBinOp(
spv::OpFDiv, type_float_, coordinate_ref, size_component);
} else {
if (component_offset != spv::NoResult) {
assert_true(size_component != spv::NoResult);
spv::Id component_offset_normalized =
builder_->createNoContractionBinOp(
spv::OpFDiv, type_float_, component_offset, size_component);
coordinate_ref = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, coordinate_ref,
component_offset_normalized);
}
}
}
if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) {
spv::Id& z_coordinate_ref = coordinates[2];
spv::Id z_offset = offset_values[2]
? builder_->makeFloatConstant(offset_values[2])
: spv::NoResult;
spv::Id z_size = size[2];
if (instr.attributes.unnormalized_coordinates) {
// Apply the offset, and normalize the Z coordinate for a 3D texture.
if (z_offset != spv::NoResult) {
z_coordinate_ref = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, z_coordinate_ref, z_offset);
}
assert_true(data_is_3d != spv::NoResult);
SpirvBuilder::IfBuilder if_data_is_3d(
data_is_3d, spv::SelectionControlDontFlattenMask, *builder_);
spv::Id z_3d;
{
assert_true(z_size != spv::NoResult);
z_3d = builder_->createNoContractionBinOp(spv::OpFDiv, type_float_,
z_coordinate_ref, z_size);
}
if_data_is_3d.makeEndIf();
z_coordinate_ref =
if_data_is_3d.createMergePhi(z_3d, z_coordinate_ref);
} else {
// Denormalize the Z coordinate for a stacked texture, and apply the
// offset.
spv::Block& block_dimension_head = *builder_->getBuildPoint();
spv::Block& block_dimension_merge = builder_->makeNewBlock();
spv::Block* block_dimension_3d =
z_offset != spv::NoResult ? &builder_->makeNewBlock() : nullptr;
spv::Block& block_dimension_stacked = builder_->makeNewBlock();
builder_->createSelectionMerge(&block_dimension_merge,
spv::SelectionControlDontFlattenMask);
assert_true(data_is_3d != spv::NoResult);
builder_->createConditionalBranch(
data_is_3d,
block_dimension_3d ? block_dimension_3d : &block_dimension_merge,
&block_dimension_stacked);
// 3D case.
spv::Id z_3d = z_coordinate_ref;
if (block_dimension_3d) {
builder_->setBuildPoint(block_dimension_3d);
if (z_offset != spv::NoResult) {
assert_true(z_size != spv::NoResult);
spv::Id z_offset_normalized = builder_->createNoContractionBinOp(
spv::OpFDiv, type_float_, z_offset, z_size);
z_3d = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, z_3d, z_offset_normalized);
}
builder_->createBranch(&block_dimension_merge);
}
// Stacked case.
builder_->setBuildPoint(&block_dimension_stacked);
spv::Id z_stacked = z_coordinate_ref;
assert_true(z_size != spv::NoResult);
z_stacked = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, z_stacked, z_size);
if (z_offset != spv::NoResult) {
z_stacked = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, z_stacked, z_offset);
}
builder_->createBranch(&block_dimension_merge);
// Select one of the two.
builder_->setBuildPoint(&block_dimension_merge);
{
std::unique_ptr<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);
// TODO(Triang3l): Gamma resolve target as sRGB sampling.
spv::Id sample_result_component_gamma =
PWLGammaToLinear(sample_result_component_unsigned, false);
// Get the current build point for the phi operation not to assume
// that it will be the same as before PWLGammaToLinear.
spv::Block& block_sign_gamma_end = *builder_->getBuildPoint();
builder_->createBranch(&block_sign_merge);
// Merge.
builder_->setBuildPoint(&block_sign_merge);
{
std::unique_ptr<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));
}
}
}
// 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