diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 4e8786789..ce70d3402 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -816,6 +816,15 @@ class DxbcShaderTranslator : public ShaderTranslator { // Adds the surviving coverage MSAA counts from ROV params to the active ZPD // counter slot after the final PS depth/stencil decision. void ROV_AddPassedMSAASamplesToZPD(); + // Converts the float32 components of the register to extended-range float16 + // in their low 16 bits. Exponent 31 holds finite values up to 131008 of + // either sign on the Xbox 360 instead of Inf or NaN, and NaN maps to 0. + // Pushes and pops its own temporary registers. + void Float32ToF16ExtendedRange(uint32_t reg, uint32_t components); + // Converts extended-range float16 in the low 16 bits of the components of + // the register, with zeros above, back to float32. Pushes and pops its own + // temporary registers. + void Float16ExtendedRangeTo32(uint32_t reg, uint32_t components); // Unpacks a 32bpp or a 64bpp color in packed_temp.packed_temp_components to // color_temp, using 2 temporary VGPRs. void ROV_UnpackColor(uint32_t rt_index, uint32_t packed_temp, diff --git a/src/xenia/gpu/dxbc_shader_translator_memexport.cc b/src/xenia/gpu/dxbc_shader_translator_memexport.cc index f24afa337..c2f3b20e7 100644 --- a/src/xenia/gpu/dxbc_shader_translator_memexport.cc +++ b/src/xenia/gpu/dxbc_shader_translator_memexport.cc @@ -487,64 +487,16 @@ void DxbcShaderTranslator::ExportToMemory(uint8_t export_eM) { } a_.OpBreak(); - // Xbox 360 float16 uses extended range: exponent 31 is NOT Inf/NaN - // but a valid large value (up to ±131008). Standard DXBC f32tof16 - // 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). + // Convert to extended-range float16 with the helper shared with the ROV + // render target packing. auto f32_to_f16_extended_range = [&](uint32_t components) { - uint32_t ext_original_temp = PushSystemTemp(); - uint32_t ext_mask_temp = PushSystemTemp(); - uint8_t eM_remaining_ext = export_eM; uint32_t eM_index_ext; while (xe::bit_scan_forward(eM_remaining_ext, &eM_index_ext)) { eM_remaining_ext &= ~(uint8_t(1) << eM_index_ext); - uint32_t eM = system_temps_memexport_data_[eM_index_ext]; - - // Save original float32 values before conversion. - a_.OpMov(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(eM)); - - // Standard f32 to f16 conversion (handles ±0..65504 correctly). - a_.OpF32ToF16(dxbc::Dest::R(eM, components), dxbc::Src::R(eM)); - - // Detect where standard conversion produced Inf or NaN - // (exponent field = 31, i.e. bits [14:10] all set = 0x7C00). - a_.OpAnd(dxbc::Dest::R(ext_mask_temp, components), dxbc::Src::R(eM), - dxbc::Src::LU(0x7C00)); - a_.OpIEq(dxbc::Dest::R(ext_mask_temp, components), - dxbc::Src::R(ext_mask_temp), dxbc::Src::LU(0x7C00)); - - // For values that overflowed, compute extended range encoding - // from the saved original float32 values: - // 1. Clamp to ±131008.0 (max extended float16 can represent). - a_.OpMin(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(ext_original_temp), dxbc::Src::LF(131008.0f)); - a_.OpMax(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(ext_original_temp), dxbc::Src::LF(-131008.0f)); - // 2. Halve to bring into standard float16 range (max 65504). - a_.OpMul(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(ext_original_temp), dxbc::Src::LF(0.5f)); - // 3. Convert the halved value (will have exponent <= 30). - a_.OpF32ToF16(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(ext_original_temp)); - // 4. Increment exponent by 1 (add 1 << 10 = 0x0400) to - // compensate for the halving → places value at exponent 31. - a_.OpIAdd(dxbc::Dest::R(ext_original_temp, components), - dxbc::Src::R(ext_original_temp), dxbc::Src::LU(0x0400)); - - // Select: use extended result where standard gave Inf/NaN, - // keep standard result otherwise. - a_.OpMovC(dxbc::Dest::R(eM, components), dxbc::Src::R(ext_mask_temp), - dxbc::Src::R(ext_original_temp), dxbc::Src::R(eM)); + Float32ToF16ExtendedRange(system_temps_memexport_data_[eM_index_ext], + components); } - - // Release ext_mask_temp and ext_original_temp. - PopSystemTemp(2); }; a_.OpCase(dxbc::Src::LU(uint32_t(xenos::ColorFormat::k_16_FLOAT))); diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index 5673422a2..3216ba9cf 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -1294,8 +1294,9 @@ void DxbcShaderTranslator::ROV_UnpackColor( dxbc::Src::LU(0, 16, 0, 16), dxbc::Src::R(packed_temp, 0b01010000 + packed_temp_components * 0b01010101)); - // Convert from 16-bit float. - a_.OpF16ToF32(color_components_dest, dxbc::Src::R(color_temp)); + // Convert from extended-range float16, exponent 31 holds finite values on + // the guest. + Float16ExtendedRangeTo32(color_temp, i ? 0b1111 : 0b0011); a_.OpBreak(); } @@ -1465,18 +1466,20 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor( a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags( i ? xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT : xenos::ColorRenderTargetFormat::k_16_16_FLOAT))); + // Convert to extended-range float16 instead of the plain IEEE conversion, + // exponent 31 holds finite values on the guest. + Float32ToF16ExtendedRange(color_temp, i ? 0b1111 : 0b0011); for (uint32_t j = 0; j < (uint32_t(2) << i); ++j) { dxbc::Dest packed_dest_half( dxbc::Dest::R(packed_temp, 1 << (packed_temp_components + (j >> 1)))); - // Convert to 16-bit float. - a_.OpF32ToF16((j & 1) ? temp1_dest : packed_dest_half, - dxbc::Src::R(color_temp).Select(j)); - // Pack green or alpha. + // Pack green or alpha into the high half. if (j & 1) { a_.OpBFI(packed_dest_half, dxbc::Src::LU(16), dxbc::Src::LU(16), - temp1_src, + dxbc::Src::R(color_temp).Select(j), dxbc::Src::R(packed_temp) .Select(packed_temp_components + (j >> 1))); + } else { + a_.OpMov(packed_dest_half, dxbc::Src::R(color_temp).Select(j)); } } a_.OpBreak(); @@ -3458,6 +3461,73 @@ void DxbcShaderTranslator::CompletePixelShader() { } } +void DxbcShaderTranslator::Float32ToF16ExtendedRange(uint32_t reg, + uint32_t components) { + uint32_t original_temp = PushSystemTemp(); + uint32_t mask_temp = PushSystemTemp(); + // The Xbox 360 float16 has no NaN, map it to 0 before converting. Otherwise + // the overflow check below reads its exponent as an overflow, and since min + // and max drop the NaN operand in DXBC, the clamp would turn it into + // +131008. + a_.OpNE(dxbc::Dest::R(mask_temp, components), dxbc::Src::R(reg), + dxbc::Src::R(reg)); + a_.OpMovC(dxbc::Dest::R(reg, components), dxbc::Src::R(mask_temp), + dxbc::Src::LF(0.0f), dxbc::Src::R(reg)); + // Keep the original float32 values for the overflow path. + a_.OpMov(dxbc::Dest::R(original_temp, components), dxbc::Src::R(reg)); + // The standard conversion covers magnitudes up to 65504, anything larger + // becomes Inf with all five exponent bits set. + a_.OpF32ToF16(dxbc::Dest::R(reg, components), dxbc::Src::R(reg)); + // Find the lanes that overflowed. + a_.OpAnd(dxbc::Dest::R(mask_temp, components), dxbc::Src::R(reg), + dxbc::Src::LU(0x7C00)); + a_.OpIEq(dxbc::Dest::R(mask_temp, components), dxbc::Src::R(mask_temp), + dxbc::Src::LU(0x7C00)); + // Encode the overflowed lanes into exponent 31, which the Xbox 360 treats + // as finite. Clamp to the 131008 limit, halve so the exponent lands at 30 + // or below, convert, then add one to the exponent to undo the halving. + a_.OpMin(dxbc::Dest::R(original_temp, components), + dxbc::Src::R(original_temp), dxbc::Src::LF(131008.0f)); + a_.OpMax(dxbc::Dest::R(original_temp, components), + dxbc::Src::R(original_temp), dxbc::Src::LF(-131008.0f)); + a_.OpMul(dxbc::Dest::R(original_temp, components), + dxbc::Src::R(original_temp), dxbc::Src::LF(0.5f)); + a_.OpF32ToF16(dxbc::Dest::R(original_temp, components), + dxbc::Src::R(original_temp)); + a_.OpIAdd(dxbc::Dest::R(original_temp, components), + dxbc::Src::R(original_temp), dxbc::Src::LU(0x0400)); + // Choose the encoded value for the overflowed lanes. + a_.OpMovC(dxbc::Dest::R(reg, components), dxbc::Src::R(mask_temp), + dxbc::Src::R(original_temp), dxbc::Src::R(reg)); + // Release original_temp and mask_temp. + PopSystemTemp(2); +} + +void DxbcShaderTranslator::Float16ExtendedRangeTo32(uint32_t reg, + uint32_t components) { + uint32_t reduced_temp = PushSystemTemp(); + uint32_t mask_temp = PushSystemTemp(); + // Find the exponent 31 lanes, which hold finite values on the Xbox 360 + // rather than Inf or NaN. + a_.OpAnd(dxbc::Dest::R(mask_temp, components), dxbc::Src::R(reg), + dxbc::Src::LU(0x7C00)); + a_.OpIEq(dxbc::Dest::R(mask_temp, components), dxbc::Src::R(mask_temp), + dxbc::Src::LU(0x7C00)); + // Lower their exponent by one so the conversion sees a normal float16, then + // double the result to undo it. + a_.OpIAdd(dxbc::Dest::R(reduced_temp, components), dxbc::Src::R(reg), + dxbc::Src::LI(-0x0400)); + a_.OpMovC(dxbc::Dest::R(reg, components), dxbc::Src::R(mask_temp), + dxbc::Src::R(reduced_temp), dxbc::Src::R(reg)); + a_.OpF16ToF32(dxbc::Dest::R(reg, components), dxbc::Src::R(reg)); + a_.OpMul(dxbc::Dest::R(reduced_temp, components), dxbc::Src::R(reg), + dxbc::Src::LF(2.0f)); + a_.OpMovC(dxbc::Dest::R(reg, components), dxbc::Src::R(mask_temp), + dxbc::Src::R(reduced_temp), dxbc::Src::R(reg)); + // Release reduced_temp and mask_temp. + PopSystemTemp(2); +} + void DxbcShaderTranslator::PreClampedFloat32To7e3( dxbc::Assembler& a, uint32_t f10_temp, uint32_t f10_temp_component, uint32_t f32_temp, uint32_t f32_temp_component, uint32_t temp_temp, diff --git a/src/xenia/gpu/render_target_cache.cc b/src/xenia/gpu/render_target_cache.cc index 28c68668e..17af8b42a 100644 --- a/src/xenia/gpu/render_target_cache.cc +++ b/src/xenia/gpu/render_target_cache.cc @@ -282,11 +282,11 @@ void RenderTargetCache::GetPSIColorFormatInfo( break; case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - // No NaNs on the Xbox 360 GPU, though can't use the extended range with - // Direct3D and Vulkan conversions. - // TODO(Triang3l): Use the extended-range encoding in all implementations. - clamp_rgb_low = clamp_alpha_low = -65504.0f; - clamp_rgb_high = clamp_alpha_high = 65504.0f; + // No NaNs on the Xbox 360 GPU. The interlock paths of both backends + // emulate the extended-range encoding in their pack and unpack, so the + // whole guest range survives the clamp. + clamp_rgb_low = clamp_alpha_low = -131008.0f; + clamp_rgb_high = clamp_alpha_high = 131008.0f; if (!(write_mask & 0b0001)) { keep_mask_low |= 0xFFFFu; } diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index ca4d7d5d0..c189020f3 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -729,6 +729,10 @@ class SpirvShaderTranslator : public ShaderTranslator { // must be called with absolute values of operands - use GetAbsoluteOperand! spv::Id ZeroIfAnyOperandIsZero(spv::Id value, spv::Id operand_0_abs, spv::Id operand_1_abs); + // Pack/unpack two floats as Xbox 360 extended-range float16, where exponent + // 31 is a large finite value (up to +-131008), not Inf/NaN. + spv::Id PackFloat16x2ExtendedRange(spv::Id float2_value); + spv::Id UnpackFloat16x2ExtendedRange(spv::Id packed_uint); // Conditionally discard the current fragment. Changes the build point. void KillPixel(spv::Id condition, uint8_t memexport_eM_potentially_written_before); diff --git a/src/xenia/gpu/spirv_shader_translator_memexport.cc b/src/xenia/gpu/spirv_shader_translator_memexport.cc index ea4460d7b..874a0a498 100644 --- a/src/xenia/gpu/spirv_shader_translator_memexport.cc +++ b/src/xenia/gpu/spirv_shader_translator_memexport.cc @@ -650,95 +650,8 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) { add_format_case(fixed16_packed, 3); } - // 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); - }; + // Xbox 360 float16 uses extended range: exponent 31 is a large finite value, + // not Inf/NaN. See PackFloat16x2ExtendedRange. // k_16_FLOAT format_switch.makeBeginCase( @@ -750,7 +663,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 = pack_half_2x16_extended_range( + spv::Id format_packed_16_float_x = PackFloat16x2ExtendedRange( builder_->createCompositeConstruct(type_float2_, id_vector_temp_)); id_vector_temp_.clear(); id_vector_temp_.resize(4, const_uint_0_); @@ -771,7 +684,7 @@ void SpirvShaderTranslator::ExportToMemory(uint8_t export_eM) { uint_vector_temp_.push_back(0); uint_vector_temp_.push_back(1); spv::Id format_packed_16_16_float_xy = - pack_half_2x16_extended_range(builder_->createRvalueSwizzle( + PackFloat16x2ExtendedRange(builder_->createRvalueSwizzle( spv::NoPrecision, type_float2_, eM_swapped[eM_index], uint_vector_temp_)); id_vector_temp_.clear(); @@ -796,7 +709,7 @@ 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] = - pack_half_2x16_extended_range(builder_->createRvalueSwizzle( + PackFloat16x2ExtendedRange(builder_->createRvalueSwizzle( spv::NoPrecision, type_float2_, eM_swapped[eM_index], uint_vector_temp_)); } diff --git a/src/xenia/gpu/spirv_shader_translator_rb.cc b/src/xenia/gpu/spirv_shader_translator_rb.cc index 266f20d4a..f2be2b72b 100644 --- a/src/xenia/gpu/spirv_shader_translator_rb.cc +++ b/src/xenia/gpu/spirv_shader_translator_rb.cc @@ -2680,6 +2680,132 @@ void SpirvShaderTranslator::FSI_DepthStencilTest( } } +spv::Id SpirvShaderTranslator::PackFloat16x2ExtendedRange( + spv::Id float2_value) { + // The Xbox 360 float16 has no NaN, map it to 0. Also keeps the overflow + // detection below from misreading a NaN's exponent 31 as a finite extended + // value, and FClamp's NaN result is undefined. + float2_value = builder_->createTriOp( + spv::OpSelect, type_float2_, + builder_->createUnaryOp(spv::OpIsNan, type_bool2_, float2_value), + const_float2_0_, float2_value); + // The standard conversion covers magnitudes up to 65504. Anything larger + // overflows to Inf (exponent field 0x7C00). Re-encode the overflowed lanes + // using the extended range: halve into the standard range, convert + // (exponent <= 30), then bump the exponent by 1 into the exponent 31 slot + // the Xbox 360 treats as finite. + spv::Id standard = builder_->createUnaryBuiltinCall( + type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16, float2_value); + spv::Id const_0x7C00 = builder_->makeUintConstant(0x7C00); + spv::Id lower_overflow = + builder_->createBinOp(spv::OpIEqual, type_bool_, + builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, + standard, const_0x7C00), + const_0x7C00); + spv::Id upper_overflow = builder_->createBinOp( + spv::OpIEqual, type_bool_, + builder_->createBinOp( + spv::OpBitwiseAnd, type_uint_, + builder_->createBinOp(spv::OpShiftRightLogical, type_uint_, standard, + builder_->makeUintConstant(16)), + const_0x7C00), + const_0x7C00); + id_vector_temp_.clear(); + id_vector_temp_.resize(2, builder_->makeFloatConstant(-131008.0f)); + spv::Id const_neg_131008 = + builder_->makeCompositeConstant(type_float2_, id_vector_temp_); + id_vector_temp_.clear(); + id_vector_temp_.resize(2, builder_->makeFloatConstant(131008.0f)); + spv::Id const_131008 = + 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, const_131008); + id_vector_temp_.clear(); + id_vector_temp_.resize(2, builder_->makeFloatConstant(0.5f)); + spv::Id halved = builder_->createBinOp( + spv::OpFMul, type_float2_, clamped, + builder_->makeCompositeConstant(type_float2_, id_vector_temp_)); + spv::Id halved_packed = builder_->createUnaryBuiltinCall( + type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16, halved); + // halved_packed has exponent <= 30 in both lanes, so adding 0x0400 per lane + // bumps the exponent without ever carrying across lanes. + spv::Id extended = + builder_->createBinOp(spv::OpIAdd, type_uint_, halved_packed, + builder_->makeUintConstant(0x04000400)); + 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); +} + +spv::Id SpirvShaderTranslator::UnpackFloat16x2ExtendedRange( + spv::Id packed_uint) { + // Inverse of PackFloat16x2ExtendedRange. Exponent 31 lanes are large finite + // values rather than Inf or NaN. Decrement their exponent by 1 into the + // standard range, unpack, then double to compensate. + spv::Id const_0x7C00 = builder_->makeUintConstant(0x7C00); + spv::Id lower_overflow = + builder_->createBinOp(spv::OpIEqual, type_bool_, + builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, + packed_uint, const_0x7C00), + const_0x7C00); + spv::Id upper_overflow = builder_->createBinOp( + spv::OpIEqual, type_bool_, + builder_->createBinOp( + spv::OpBitwiseAnd, type_uint_, + builder_->createBinOp(spv::OpShiftRightLogical, type_uint_, + packed_uint, builder_->makeUintConstant(16)), + const_0x7C00), + const_0x7C00); + spv::Id standard = + builder_->createUnaryBuiltinCall(type_float2_, ext_inst_glsl_std_450_, + GLSLstd450UnpackHalf2x16, packed_uint); + // Decrement the exponent only in overflowed lanes (0x0400 in the low lane, + // 0x04000000 in the high one) so the subtraction never borrows across lanes. + spv::Id sub_lower = + builder_->createTriOp(spv::OpSelect, type_uint_, lower_overflow, + builder_->makeUintConstant(0x0400), const_uint_0_); + spv::Id sub_upper = builder_->createTriOp( + spv::OpSelect, type_uint_, upper_overflow, + builder_->makeUintConstant(0x04000000), const_uint_0_); + spv::Id reduced = + builder_->createBinOp(spv::OpISub, type_uint_, packed_uint, + builder_->createBinOp(spv::OpBitwiseOr, type_uint_, + sub_lower, sub_upper)); + spv::Id reduced_unpacked = builder_->createUnaryBuiltinCall( + type_float2_, ext_inst_glsl_std_450_, GLSLstd450UnpackHalf2x16, reduced); + id_vector_temp_.clear(); + id_vector_temp_.resize(2, builder_->makeFloatConstant(2.0f)); + spv::Id extended = builder_->createBinOp( + spv::OpFMul, type_float2_, reduced_unpacked, + builder_->makeCompositeConstant(type_float2_, id_vector_temp_)); + spv::Id result_x = builder_->createTriOp( + spv::OpSelect, type_float_, lower_overflow, + builder_->createCompositeExtract(extended, type_float_, 0), + builder_->createCompositeExtract(standard, type_float_, 0)); + spv::Id result_y = builder_->createTriOp( + spv::OpSelect, type_float_, upper_overflow, + builder_->createCompositeExtract(extended, type_float_, 1), + builder_->createCompositeExtract(standard, type_float_, 1)); + id_vector_temp_.clear(); + id_vector_temp_.push_back(result_x); + id_vector_temp_.push_back(result_y); + return builder_->createCompositeConstruct(type_float2_, id_vector_temp_); +} + std::array SpirvShaderTranslator::FSI_ClampAndPackColor( spv::Id color_float4, spv::Id format_with_flags) { spv::Block& block_format_head = *builder_->getBuildPoint(); @@ -2977,31 +3103,14 @@ std::array SpirvShaderTranslator::FSI_ClampAndPackColor( std::array packed_16_float; { builder_->setBuildPoint(&block_format_16_float); - // TODO(Triang3l): Xenos extended-range float16. - id_vector_temp_.clear(); - id_vector_temp_.resize(4, builder_->makeFloatConstant(-65504.0f)); - spv::Id const_float4_minus_float16_max = - builder_->makeCompositeConstant(type_float4_, id_vector_temp_); - id_vector_temp_.clear(); - id_vector_temp_.resize(4, builder_->makeFloatConstant(65504.0f)); - spv::Id const_float4_float16_max = - builder_->makeCompositeConstant(type_float4_, id_vector_temp_); - // NaN to 0, not to -max. - spv::Id color_clamped = builder_->createTriBuiltinCall( - type_float4_, ext_inst_glsl_std_450_, GLSLstd450FClamp, - builder_->createTriOp( - spv::OpSelect, type_float4_, - builder_->createUnaryOp(spv::OpIsNan, type_bool4_, color_float4), - const_float4_0_, color_float4), - const_float4_minus_float16_max, const_float4_float16_max); + // NaN is flushed to 0 inside PackFloat16x2ExtendedRange. for (uint32_t i = 0; i < 2; ++i) { uint_vector_temp_.clear(); uint_vector_temp_.push_back(2 * i); uint_vector_temp_.push_back(2 * i + 1); - packed_16_float[i] = builder_->createUnaryBuiltinCall( - type_uint_, ext_inst_glsl_std_450_, GLSLstd450PackHalf2x16, - builder_->createRvalueSwizzle(spv::NoPrecision, type_float2_, - color_clamped, uint_vector_temp_)); + packed_16_float[i] = + PackFloat16x2ExtendedRange(builder_->createRvalueSwizzle( + spv::NoPrecision, type_float2_, color_float4, uint_vector_temp_)); } builder_->createBranch(&block_format_merge); } @@ -3290,11 +3399,9 @@ std::array SpirvShaderTranslator::FSI_UnpackColor( for (uint32_t i = 0; i < 2; ++i) { builder_->setBuildPoint(i ? &block_format_16_16_16_16_float : &block_format_16_16_float); - // TODO(Triang3l): Xenos extended-range float16. for (uint32_t j = 0; j <= i; ++j) { - spv::Id components_float2 = builder_->createUnaryBuiltinCall( - type_float2_, ext_inst_glsl_std_450_, GLSLstd450UnpackHalf2x16, - color_packed[j]); + spv::Id components_float2 = + UnpackFloat16x2ExtendedRange(color_packed[j]); for (uint32_t k = 0; k < 2; ++k) { unpacked_16_float[i][2 * j + k] = builder_->createCompositeExtract( components_float2, type_float_, k);