Previously was using word4 which is LOD bias, fixes many graphic issues with Vulkan backend
2454 lines
119 KiB
C++
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
|