[Vulkan] Implement extended range for f32 to f16 conversion in memexport

This commit is contained in:
Herman S.
2026-03-03 17:16:37 +09:00
parent 02c6bc6e61
commit dbfe216754

View File

@@ -650,7 +650,95 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) {
add_format_case(fixed16_packed, 3);
}
// TODO(Triang3l): Use the extended range float16 conversion.
// Xbox 360 float16 uses extended range: exponent 31 is NOT Inf/NaN
// but a valid large value (up to +/-131008). Standard PackHalf2x16
// clamps to +/-65504 and produces Inf for larger values. This helper
// detects where standard conversion overflowed to Inf/NaN and
// re-encodes those values using the extended range representation:
// halve the value, convert to standard f16 (giving exponent <= 30),
// then increment the exponent by 1 (placing it in the exponent 31
// slot that Xbox 360 treats as a normal value, not Inf/NaN).
// Operates on a float2 packed via PackHalf2x16 into a uint32, with
// per-lane overflow detection and selection.
auto pack_half_2x16_extended_range = [&](spv::Id float2_value) -> spv::Id {
// Standard f32 to f16 conversion (handles +/-0..65504 correctly).
spv::Id standard =
builder_->createUnaryBuiltinCall(type_uint_, ext_inst_glsl_std_450_,
GLSLstd450PackHalf2x16, float2_value);
// Detect where standard conversion produced Inf or NaN
// (exponent field = 31, i.e. bits [14:10] all set = 0x7C00)
// in each 16-bit lane of the packed result.
spv::Id const_0x7C00 = builder_->makeUintConstant(0x7C00);
spv::Id lower_exp = builder_->createBinOp(spv::OpBitwiseAnd, type_uint_,
standard, const_0x7C00);
spv::Id lower_overflow = builder_->createBinOp(spv::OpIEqual, type_bool_,
lower_exp, const_0x7C00);
spv::Id upper_bits =
builder_->createBinOp(spv::OpShiftRightLogical, type_uint_, standard,
builder_->makeUintConstant(16));
spv::Id upper_exp = builder_->createBinOp(spv::OpBitwiseAnd, type_uint_,
upper_bits, const_0x7C00);
spv::Id upper_overflow = builder_->createBinOp(spv::OpIEqual, type_bool_,
upper_exp, const_0x7C00);
// For values that overflowed, compute extended range encoding:
// 1. Clamp to +/-131008.0 (max extended float16 can represent).
spv::Id const_131008 = builder_->makeFloatConstant(131008.0f);
spv::Id const_neg_131008 = builder_->makeFloatConstant(-131008.0f);
id_vector_temp_.clear();
id_vector_temp_.push_back(const_neg_131008);
id_vector_temp_.push_back(const_neg_131008);
spv::Id const_neg_131008_vec2 =
builder_->makeCompositeConstant(type_float2_, id_vector_temp_);
id_vector_temp_.clear();
id_vector_temp_.push_back(const_131008);
id_vector_temp_.push_back(const_131008);
spv::Id const_131008_vec2 =
builder_->makeCompositeConstant(type_float2_, id_vector_temp_);
spv::Id clamped = builder_->createTriBuiltinCall(
type_float2_, ext_inst_glsl_std_450_, GLSLstd450FClamp, float2_value,
const_neg_131008_vec2, const_131008_vec2);
// 2. Halve to bring into standard float16 range (max 65504).
spv::Id const_half = builder_->makeFloatConstant(0.5f);
id_vector_temp_.clear();
id_vector_temp_.push_back(const_half);
id_vector_temp_.push_back(const_half);
spv::Id const_half_vec2 =
builder_->makeCompositeConstant(type_float2_, id_vector_temp_);
spv::Id halved = builder_->createBinOp(spv::OpFMul, type_float2_, clamped,
const_half_vec2);
// 3. Convert the halved value (will have exponent <= 30).
spv::Id halved_packed = builder_->createUnaryBuiltinCall(
type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16, halved);
// 4. Increment exponent by 1 in both lanes (add 0x0400 to each
// 16-bit half = 0x04000400) to compensate for the halving.
spv::Id extended =
builder_->createBinOp(spv::OpIAdd, type_uint_, halved_packed,
builder_->makeUintConstant(0x04000400));
// Select: use extended result where standard gave Inf/NaN,
// keep standard result otherwise. Per-lane selection via masking.
spv::Id const_0xFFFF = builder_->makeUintConstant(0xFFFF);
spv::Id const_0xFFFF0000 = builder_->makeUintConstant(0xFFFF0000);
spv::Id result_lower = builder_->createTriOp(
spv::OpSelect, type_uint_, lower_overflow,
builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, extended,
const_0xFFFF),
builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, standard,
const_0xFFFF));
spv::Id result_upper = builder_->createTriOp(
spv::OpSelect, type_uint_, upper_overflow,
builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, extended,
const_0xFFFF0000),
builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, standard,
const_0xFFFF0000));
return builder_->createBinOp(spv::OpBitwiseOr, type_uint_, result_lower,
result_upper);
};
// k_16_FLOAT
format_switch.makeBeginCase(
@@ -662,8 +750,7 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) {
id_vector_temp_.push_back(builder_->createCompositeExtract(
eM_swapped[eM_index], type_float_, 0));
id_vector_temp_.push_back(const_float_0_);
spv::Id format_packed_16_float_x = builder_->createUnaryBuiltinCall(
type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16,
spv::Id format_packed_16_float_x = pack_half_2x16_extended_range(
builder_->createCompositeConstruct(type_float2_, id_vector_temp_));
id_vector_temp_.clear();
id_vector_temp_.resize(4, const_uint_0_);
@@ -683,11 +770,10 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) {
uint_vector_temp_.clear();
uint_vector_temp_.push_back(0);
uint_vector_temp_.push_back(1);
spv::Id format_packed_16_16_float_xy = builder_->createUnaryBuiltinCall(
type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16,
builder_->createRvalueSwizzle(spv::NoPrecision, type_float2_,
eM_swapped[eM_index],
uint_vector_temp_));
spv::Id format_packed_16_16_float_xy =
pack_half_2x16_extended_range(builder_->createRvalueSwizzle(
spv::NoPrecision, type_float2_, eM_swapped[eM_index],
uint_vector_temp_));
id_vector_temp_.clear();
id_vector_temp_.resize(4, const_uint_0_);
id_vector_temp_.front() = format_packed_16_16_float_xy;
@@ -710,11 +796,9 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) {
uint_vector_temp_.push_back(2 * component_index);
uint_vector_temp_.push_back(2 * component_index + 1);
format_packed_16_16_16_16_float_xy_zw[component_index] =
builder_->createUnaryBuiltinCall(
type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16,
builder_->createRvalueSwizzle(spv::NoPrecision, type_float2_,
eM_swapped[eM_index],
uint_vector_temp_));
pack_half_2x16_extended_range(builder_->createRvalueSwizzle(
spv::NoPrecision, type_float2_, eM_swapped[eM_index],
uint_vector_temp_));
}
id_vector_temp_.clear();
id_vector_temp_.push_back(format_packed_16_16_16_16_float_xy_zw[0]);