Implement data unpacking intrinsics

* Add support for data unpacking intrinsics
  * spir-v reader
  * type determiner
  * intrinsic table
  * spir-v, hlsl and msl writers

Bug: tint:341
Change-Id: I8f40d19d59a4699af75cd579fe8398c735a77a59
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41320
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Alan Baker <alanbaker@google.com>
This commit is contained in:
Alan Baker 2021-02-09 21:23:00 +00:00 committed by Commit Bot service account
parent 2a284b2a13
commit cd17ea88e3
14 changed files with 600 additions and 131 deletions

View File

@ -886,127 +886,132 @@ Impl::Impl() {
// clang-format off
// name return type parameter types open type constraints // NOLINT
Register(I::kAbs, T, {T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kAbs, vecN_T, {vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kAcos, f32, {f32} ); // NOLINT
Register(I::kAcos, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAll, bool_, {vecN_bool} ); // NOLINT
Register(I::kAny, bool_, {vecN_bool} ); // NOLINT
Register(I::kArrayLength, u32, {array_T} ); // NOLINT
Register(I::kAsin, f32, {f32} ); // NOLINT
Register(I::kAsin, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAtan, f32, {f32} ); // NOLINT
Register(I::kAtan, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAtan2, f32, {f32, f32} ); // NOLINT
Register(I::kAtan2, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kCeil, f32, {f32} ); // NOLINT
Register(I::kCeil, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kClamp, T, {T, T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kClamp, vecN_T, {vecN_T, vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kCos, f32, {f32} ); // NOLINT
Register(I::kCos, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kCosh, f32, {f32} ); // NOLINT
Register(I::kCosh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kCountOneBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kCountOneBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kCross, vec3_f32, {vec3_f32, vec3_f32} ); // NOLINT
Register(I::kDeterminant, f32, {matNxN_f32} ); // NOLINT
Register(I::kDistance, f32, {f32, f32} ); // NOLINT
Register(I::kDistance, f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kDot, f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kDpdx, f32, {f32} ); // NOLINT
Register(I::kDpdx, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdxCoarse, f32, {f32} ); // NOLINT
Register(I::kDpdxCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdxFine, f32, {f32} ); // NOLINT
Register(I::kDpdxFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdy, f32, {f32} ); // NOLINT
Register(I::kDpdy, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdyCoarse, f32, {f32} ); // NOLINT
Register(I::kDpdyCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdyFine, f32, {f32} ); // NOLINT
Register(I::kDpdyFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kExp, f32, {f32} ); // NOLINT
Register(I::kExp, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kExp2, f32, {f32} ); // NOLINT
Register(I::kExp2, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFaceForward, f32, {f32, f32, f32} ); // NOLINT
Register(I::kFaceForward, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kFloor, f32, {f32} ); // NOLINT
Register(I::kFloor, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFma, f32, {f32, f32, f32} ); // NOLINT
Register(I::kFma, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kFract, f32, {f32} ); // NOLINT
Register(I::kFract, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFrexp, f32, {f32, ptr_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kFrexp, vecN_f32, {vecN_f32, ptr_vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kFwidth, f32, {f32} ); // NOLINT
Register(I::kFwidth, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFwidthCoarse, f32, {f32} ); // NOLINT
Register(I::kFwidthCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFwidthFine, f32, {f32} ); // NOLINT
Register(I::kFwidthFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kInverseSqrt, f32, {f32} ); // NOLINT
Register(I::kInverseSqrt, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kIsFinite, bool_, {f32} ); // NOLINT
Register(I::kIsFinite, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsInf, bool_, {f32} ); // NOLINT
Register(I::kIsInf, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsNan, bool_, {f32} ); // NOLINT
Register(I::kIsNan, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsNormal, bool_, {f32} ); // NOLINT
Register(I::kIsNormal, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kLdexp, f32, {f32, T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kLdexp, vecN_f32, {vecN_f32, vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kLength, f32, {f32} ); // NOLINT
Register(I::kLength, f32, {vecN_f32} ); // NOLINT
Register(I::kLog, f32, {f32} ); // NOLINT
Register(I::kLog, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kLog2, f32, {f32} ); // NOLINT
Register(I::kLog2, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kMax, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMax, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMin, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMin, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMix, f32, {f32, f32, f32} ); // NOLINT
Register(I::kMix, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kModf, f32, {f32, ptr_f32} ); // NOLINT
Register(I::kModf, vecN_f32, {vecN_f32, ptr_vecN_f32} ); // NOLINT
Register(I::kNormalize, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kPack2x16Float, u32, {vec2_f32} ); // NOLINT
Register(I::kPack2x16Snorm, u32, {vec2_f32} ); // NOLINT
Register(I::kPack2x16Unorm, u32, {vec2_f32} ); // NOLINT
Register(I::kPack4x8Snorm, u32, {vec4_f32} ); // NOLINT
Register(I::kPack4x8Unorm, u32, {vec4_f32} ); // NOLINT
Register(I::kPow, f32, {f32, f32} ); // NOLINT
Register(I::kPow, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kReflect, f32, {f32, f32} ); // NOLINT
Register(I::kReflect, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kReverseBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kReverseBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kRound, f32, {f32} ); // NOLINT
Register(I::kRound, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSelect, T, {T, T, bool_}, {OpenType::T, scalar} ); // NOLINT
Register(I::kSelect, vecN_T, {vecN_T, vecN_T, vecN_bool}, {OpenType::T, scalar} ); // NOLINT
Register(I::kSign, f32, {f32} ); // NOLINT
Register(I::kSign, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSin, f32, {f32} ); // NOLINT
Register(I::kSin, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSinh, f32, {f32} ); // NOLINT
Register(I::kSinh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSmoothStep, f32, {f32, f32, f32} ); // NOLINT
Register(I::kSmoothStep, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kSqrt, f32, {f32} ); // NOLINT
Register(I::kSqrt, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kStep, f32, {f32, f32} ); // NOLINT
Register(I::kStep, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kTan, f32, {f32} ); // NOLINT
Register(I::kTan, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kTanh, f32, {f32} ); // NOLINT
Register(I::kTanh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kTrunc, f32, {f32} ); // NOLINT
Register(I::kTrunc, vecN_f32, {vecN_f32} ); // NOLINT
// name return type parameter types open type constraints // NOLINT
Register(I::kAbs, T, {T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kAbs, vecN_T, {vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kAcos, f32, {f32} ); // NOLINT
Register(I::kAcos, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAll, bool_, {vecN_bool} ); // NOLINT
Register(I::kAny, bool_, {vecN_bool} ); // NOLINT
Register(I::kArrayLength, u32, {array_T} ); // NOLINT
Register(I::kAsin, f32, {f32} ); // NOLINT
Register(I::kAsin, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAtan, f32, {f32} ); // NOLINT
Register(I::kAtan, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kAtan2, f32, {f32, f32} ); // NOLINT
Register(I::kAtan2, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kCeil, f32, {f32} ); // NOLINT
Register(I::kCeil, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kClamp, T, {T, T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kClamp, vecN_T, {vecN_T, vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kCos, f32, {f32} ); // NOLINT
Register(I::kCos, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kCosh, f32, {f32} ); // NOLINT
Register(I::kCosh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kCountOneBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kCountOneBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kCross, vec3_f32, {vec3_f32, vec3_f32} ); // NOLINT
Register(I::kDeterminant, f32, {matNxN_f32} ); // NOLINT
Register(I::kDistance, f32, {f32, f32} ); // NOLINT
Register(I::kDistance, f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kDot, f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kDpdx, f32, {f32} ); // NOLINT
Register(I::kDpdx, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdxCoarse, f32, {f32} ); // NOLINT
Register(I::kDpdxCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdxFine, f32, {f32} ); // NOLINT
Register(I::kDpdxFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdy, f32, {f32} ); // NOLINT
Register(I::kDpdy, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdyCoarse, f32, {f32} ); // NOLINT
Register(I::kDpdyCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kDpdyFine, f32, {f32} ); // NOLINT
Register(I::kDpdyFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kExp, f32, {f32} ); // NOLINT
Register(I::kExp, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kExp2, f32, {f32} ); // NOLINT
Register(I::kExp2, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFaceForward, f32, {f32, f32, f32} ); // NOLINT
Register(I::kFaceForward, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kFloor, f32, {f32} ); // NOLINT
Register(I::kFloor, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFma, f32, {f32, f32, f32} ); // NOLINT
Register(I::kFma, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kFract, f32, {f32} ); // NOLINT
Register(I::kFract, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFrexp, f32, {f32, ptr_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kFrexp, vecN_f32, {vecN_f32, ptr_vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kFwidth, f32, {f32} ); // NOLINT
Register(I::kFwidth, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFwidthCoarse, f32, {f32} ); // NOLINT
Register(I::kFwidthCoarse, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kFwidthFine, f32, {f32} ); // NOLINT
Register(I::kFwidthFine, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kInverseSqrt, f32, {f32} ); // NOLINT
Register(I::kInverseSqrt, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kIsFinite, bool_, {f32} ); // NOLINT
Register(I::kIsFinite, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsInf, bool_, {f32} ); // NOLINT
Register(I::kIsInf, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsNan, bool_, {f32} ); // NOLINT
Register(I::kIsNan, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kIsNormal, bool_, {f32} ); // NOLINT
Register(I::kIsNormal, vecN_bool, {vecN_f32} ); // NOLINT
Register(I::kLdexp, f32, {f32, T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kLdexp, vecN_f32, {vecN_f32, vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kLength, f32, {f32} ); // NOLINT
Register(I::kLength, f32, {vecN_f32} ); // NOLINT
Register(I::kLog, f32, {f32} ); // NOLINT
Register(I::kLog, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kLog2, f32, {f32} ); // NOLINT
Register(I::kLog2, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kMax, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMax, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMin, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMin, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
Register(I::kMix, f32, {f32, f32, f32} ); // NOLINT
Register(I::kMix, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kModf, f32, {f32, ptr_f32} ); // NOLINT
Register(I::kModf, vecN_f32, {vecN_f32, ptr_vecN_f32} ); // NOLINT
Register(I::kNormalize, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kPack2x16Float, u32, {vec2_f32} ); // NOLINT
Register(I::kPack2x16Snorm, u32, {vec2_f32} ); // NOLINT
Register(I::kPack2x16Unorm, u32, {vec2_f32} ); // NOLINT
Register(I::kPack4x8Snorm, u32, {vec4_f32} ); // NOLINT
Register(I::kPack4x8Unorm, u32, {vec4_f32} ); // NOLINT
Register(I::kPow, f32, {f32, f32} ); // NOLINT
Register(I::kPow, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kReflect, f32, {f32, f32} ); // NOLINT
Register(I::kReflect, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kReverseBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kReverseBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
Register(I::kRound, f32, {f32} ); // NOLINT
Register(I::kRound, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSelect, T, {T, T, bool_}, {OpenType::T, scalar} ); // NOLINT
Register(I::kSelect, vecN_T, {vecN_T, vecN_T, vecN_bool}, {OpenType::T, scalar} ); // NOLINT
Register(I::kSign, f32, {f32} ); // NOLINT
Register(I::kSign, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSin, f32, {f32} ); // NOLINT
Register(I::kSin, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSinh, f32, {f32} ); // NOLINT
Register(I::kSinh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kSmoothStep, f32, {f32, f32, f32} ); // NOLINT
Register(I::kSmoothStep, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
Register(I::kSqrt, f32, {f32} ); // NOLINT
Register(I::kSqrt, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kStep, f32, {f32, f32} ); // NOLINT
Register(I::kStep, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
Register(I::kTan, f32, {f32} ); // NOLINT
Register(I::kTan, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kTanh, f32, {f32} ); // NOLINT
Register(I::kTanh, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kTrunc, f32, {f32} ); // NOLINT
Register(I::kTrunc, vecN_f32, {vecN_f32} ); // NOLINT
Register(I::kUnpack2x16Float, vec2_f32, {u32} ); // NOLINT
Register(I::kUnpack2x16Snorm, vec2_f32, {u32} ); // NOLINT
Register(I::kUnpack2x16Unorm, vec2_f32, {u32} ); // NOLINT
Register(I::kUnpack4x8Snorm, vec4_f32, {u32} ); // NOLINT
Register(I::kUnpack4x8Unorm, vec4_f32, {u32} ); // NOLINT
// clang-format on
auto* tex_1d_f32 = sampled_texture(Dim::k1d, f32);

View File

@ -425,6 +425,16 @@ std::string GetGlslStd450FuncName(uint32_t ext_opcode) {
return "tanh";
case GLSLstd450Trunc:
return "trunc";
case GLSLstd450UnpackSnorm4x8:
return "unpack4x8snorm";
case GLSLstd450UnpackUnorm4x8:
return "unpack4x8unorm";
case GLSLstd450UnpackSnorm2x16:
return "unpack2x16snorm";
case GLSLstd450UnpackUnorm2x16:
return "unpack2x16unorm";
case GLSLstd450UnpackHalf2x16:
return "unpack2x16float";
default:
// TODO(dneto) - The following are not implemented.
@ -448,11 +458,6 @@ std::string GetGlslStd450FuncName(uint32_t ext_opcode) {
case GLSLstd450FrexpStruct:
case GLSLstd450PackDouble2x32:
case GLSLstd450UnpackSnorm2x16:
case GLSLstd450UnpackUnorm2x16:
case GLSLstd450UnpackHalf2x16:
case GLSLstd450UnpackSnorm4x8:
case GLSLstd450UnpackUnorm4x8:
case GLSLstd450UnpackDouble2x32:
case GLSLstd450Refract:

View File

@ -1548,6 +1548,50 @@ INSTANTIATE_TEST_SUITE_P(Samples,
{"PackUnorm2x16", "pack2x16unorm", 2},
{"PackHalf2x16", "pack2x16float", 2}}));
using SpvParserTest_GlslStd450_DataUnpacking =
SpvParserTestBase<::testing::TestWithParam<DataPackingCase>>;
TEST_P(SpvParserTest_GlslStd450_DataUnpacking, Valid) {
auto param = GetParam();
const auto assembly = Preamble() + R"(
%1 = OpExtInst )" + (param.vec_size == 2 ? "%v2float" : "%v4float") +
std::string(" %glsl ") + param.opcode + R"( %u1
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
const auto body = ToString(p->builder(), fe.ast_body());
EXPECT_THAT(body, HasSubstr(R"(
VariableConst{
x_1
none
)" + std::string(param.vec_size == 2 ? "__vec_2__f32" : "__vec_4__f32") +
R"(
{
Call[not set]{
Identifier[not set]{)" +
param.wgsl_func + R"(}
(
Identifier[not set]{u1}
)
}
}
})"))
<< body;
}
INSTANTIATE_TEST_SUITE_P(Samples,
SpvParserTest_GlslStd450_DataUnpacking,
::testing::ValuesIn(std::vector<DataPackingCase>{
{"UnpackSnorm4x8", "unpack4x8snorm", 4},
{"UnpackUnorm4x8", "unpack4x8unorm", 4},
{"UnpackSnorm2x16", "unpack2x16snorm", 2},
{"UnpackUnorm2x16", "unpack2x16unorm", 2},
{"UnpackHalf2x16", "unpack2x16float", 2}}));
} // namespace
} // namespace spirv
} // namespace reader

View File

@ -101,7 +101,12 @@ enum class IntrinsicType {
kTextureSampleGrad,
kTextureSampleLevel,
kTextureStore,
kTrunc
kTrunc,
kUnpack4x8Snorm,
kUnpack4x8Unorm,
kUnpack2x16Snorm,
kUnpack2x16Unorm,
kUnpack2x16Float,
};
/// @returns the name of the intrinsic function type. The spelling, including
@ -143,6 +148,11 @@ bool IsImageQueryIntrinsic(IntrinsicType i);
/// @returns true if the given `i` is a data packing intrinsic
bool IsDataPackingIntrinsic(IntrinsicType i);
/// Determines if the given `i` is a data unpacking intrinsic
/// @param i the intrinsic
/// @returns true if the given `i` is a data unpacking intrinsic
bool IsDataUnpackingIntrinsic(IntrinsicType i);
/// Intrinsic holds the semantic information for an intrinsic function.
class Intrinsic : public Castable<Intrinsic, CallTarget> {
public:
@ -185,6 +195,9 @@ class Intrinsic : public Castable<Intrinsic, CallTarget> {
/// @returns true if intrinsic is a data packing intrinsic
bool IsDataPacking() const;
/// @returns true if intrinsic is a data unpacking intrinsic
bool IsDataUnpacking() const;
private:
IntrinsicType const type_;
};

View File

@ -188,6 +188,16 @@ const char* str(IntrinsicType i) {
return "textureStore";
case IntrinsicType::kTrunc:
return "trunc";
case IntrinsicType::kUnpack4x8Snorm:
return "unpack4x8snorm";
case IntrinsicType::kUnpack4x8Unorm:
return "unpack4x8unorm";
case IntrinsicType::kUnpack2x16Snorm:
return "unpack2x16snorm";
case IntrinsicType::kUnpack2x16Unorm:
return "unpack2x16unorm";
case IntrinsicType::kUnpack2x16Float:
return "unpack2x16float";
}
return "<unknown>";
}
@ -238,6 +248,14 @@ bool IsDataPackingIntrinsic(IntrinsicType i) {
i == IntrinsicType::kPack2x16Float;
}
bool IsDataUnpackingIntrinsic(IntrinsicType i) {
return i == IntrinsicType::kUnpack4x8Snorm ||
i == IntrinsicType::kUnpack4x8Unorm ||
i == IntrinsicType::kUnpack2x16Snorm ||
i == IntrinsicType::kUnpack2x16Unorm ||
i == IntrinsicType::kUnpack2x16Float;
}
Intrinsic::Intrinsic(IntrinsicType type,
type::Type* return_type,
const ParameterList& parameters)
@ -273,5 +291,9 @@ bool Intrinsic::IsDataPacking() const {
return IsDataPackingIntrinsic(type_);
}
bool Intrinsic::IsDataUnpacking() const {
return IsDataUnpackingIntrinsic(type_);
}
} // namespace semantic
} // namespace tint

View File

@ -717,6 +717,16 @@ IntrinsicType TypeDeterminer::MatchIntrinsicType(const std::string& name) {
return IntrinsicType::kTextureSampleLevel;
} else if (name == "trunc") {
return IntrinsicType::kTrunc;
} else if (name == "unpack4x8snorm") {
return IntrinsicType::kUnpack4x8Snorm;
} else if (name == "unpack4x8unorm") {
return IntrinsicType::kUnpack4x8Unorm;
} else if (name == "unpack2x16snorm") {
return IntrinsicType::kUnpack2x16Snorm;
} else if (name == "unpack2x16unorm") {
return IntrinsicType::kUnpack2x16Unorm;
} else if (name == "unpack2x16float") {
return IntrinsicType::kUnpack2x16Float;
}
return IntrinsicType::kNone;
}

View File

@ -1794,6 +1794,36 @@ INSTANTIATE_TEST_SUITE_P(
IntrinsicData{"pack2x16unorm", IntrinsicType::kPack2x16Unorm},
IntrinsicData{"pack2x16float", IntrinsicType::kPack2x16Float}));
using ImportData_DataUnpackingTest = TypeDeterminerTestWithParam<IntrinsicData>;
TEST_P(ImportData_DataUnpackingTest, InferType) {
auto param = GetParam();
bool pack4 = param.intrinsic == IntrinsicType::kUnpack4x8Snorm ||
param.intrinsic == IntrinsicType::kUnpack4x8Unorm;
auto* call = Call(param.name, 1u);
WrapInFunction(call);
EXPECT_TRUE(td()->Determine()) << td()->error();
ASSERT_NE(TypeOf(call), nullptr);
EXPECT_TRUE(TypeOf(call)->is_float_vector());
if (pack4) {
EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 4u);
} else {
EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 2u);
}
}
INSTANTIATE_TEST_SUITE_P(
TypeDeterminerTest,
ImportData_DataUnpackingTest,
testing::Values(
IntrinsicData{"unpack4x8snorm", IntrinsicType::kUnpack4x8Snorm},
IntrinsicData{"unpack4x8unorm", IntrinsicType::kUnpack4x8Unorm},
IntrinsicData{"unpack2x16snorm", IntrinsicType::kUnpack2x16Snorm},
IntrinsicData{"unpack2x16unorm", IntrinsicType::kUnpack2x16Unorm},
IntrinsicData{"unpack2x16float", IntrinsicType::kUnpack2x16Float}));
using ImportData_SingleParamTest = TypeDeterminerTestWithParam<IntrinsicData>;
TEST_P(ImportData_SingleParamTest, Scalar) {
auto param = GetParam();

View File

@ -556,6 +556,8 @@ bool GeneratorImpl::EmitCall(std::ostream& pre,
return false;
} else if (intrinsic->IsDataPacking()) {
return EmitDataPackingCall(pre, out, expr, intrinsic);
} else if (intrinsic->IsDataUnpacking()) {
return EmitDataUnpackingCall(pre, out, expr, intrinsic);
}
auto name = generate_builtin_name(intrinsic);
if (name.empty()) {
@ -694,6 +696,77 @@ bool GeneratorImpl::EmitDataPackingCall(std::ostream& pre,
return true;
}
bool GeneratorImpl::EmitDataUnpackingCall(
std::ostream& pre,
std::ostream& out,
ast::CallExpression* expr,
const semantic::Intrinsic* intrinsic) {
auto* param = expr->params()[0];
auto tmp_name = generate_name(kTempNamePrefix);
std::ostringstream expr_out;
if (!EmitExpression(pre, expr_out, param)) {
return false;
}
uint32_t dims = 2;
bool is_signed = false;
uint32_t scale = 65535;
if (intrinsic->Type() == semantic::IntrinsicType::kUnpack4x8Snorm ||
intrinsic->Type() == semantic::IntrinsicType::kUnpack4x8Unorm) {
dims = 4;
scale = 255;
}
if (intrinsic->Type() == semantic::IntrinsicType::kUnpack4x8Snorm ||
intrinsic->Type() == semantic::IntrinsicType::kUnpack2x16Snorm) {
is_signed = true;
scale = (scale - 1) / 2;
}
switch (intrinsic->Type()) {
case semantic::IntrinsicType::kUnpack4x8Snorm:
case semantic::IntrinsicType::kUnpack2x16Snorm: {
auto tmp_name2 = generate_name(kTempNamePrefix);
pre << "int " << tmp_name2 << " = int(" << expr_out.str() << ");\n";
// Perform sign extension on the converted values.
pre << "int" << dims << " " << tmp_name << " = int" << dims << "(";
if (dims == 2) {
pre << tmp_name2 << " << 16, " << tmp_name2 << ") >> 16";
} else {
pre << tmp_name2 << " << 24, " << tmp_name2 << " << 16, " << tmp_name2
<< " << 8, " << tmp_name2 << ") >> 24";
}
pre << ";\n";
out << "clamp(float" << dims << "(" << tmp_name << ") / " << scale
<< ".0, " << (is_signed ? "-1.0" : "0.0") << ", 1.0)";
break;
}
case semantic::IntrinsicType::kUnpack4x8Unorm:
case semantic::IntrinsicType::kUnpack2x16Unorm: {
auto tmp_name2 = generate_name(kTempNamePrefix);
pre << "uint " << tmp_name2 << " = " << expr_out.str() << ";\n";
pre << "uint" << dims << " " << tmp_name << " = uint" << dims << "(";
pre << tmp_name2 << " & " << (dims == 2 ? "0xffff" : "0xff") << ", ";
if (dims == 4) {
pre << "(" << tmp_name2 << " >> " << (32 / dims) << ") & 0xff, ("
<< tmp_name2 << " >> 16) & 0xff, " << tmp_name2 << " >> 24";
} else {
pre << tmp_name2 << " >> " << (32 / dims);
}
pre << ");\n";
out << "float" << dims << "(" << tmp_name << ") / " << scale << ".0";
break;
}
case semantic::IntrinsicType::kUnpack2x16Float:
pre << "uint " << tmp_name << " = " << expr_out.str() << ";\n";
out << "f16tof32(uint2(" << tmp_name << " & 0xffff, " << tmp_name
<< " >> 16))";
break;
default:
error_ = "Internal error: unhandled data packing intrinsic";
return false;
}
return true;
}
bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
std::ostream& out,
ast::CallExpression* expr,

View File

@ -169,6 +169,16 @@ class GeneratorImpl {
std::ostream& out,
ast::CallExpression* expr,
const semantic::Intrinsic* intrinsic);
/// Handles generating a call to data unpacking intrinsic
/// @param pre the preamble of the expression stream
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the texture intrinsic
/// @returns true if the call expression is emitted
bool EmitDataUnpackingCall(std::ostream& pre,
std::ostream& out,
ast::CallExpression* expr,
const semantic::Intrinsic* intrinsic);
/// Handles a case statement
/// @param out the output stream
/// @param stmt the statement

View File

@ -330,7 +330,7 @@ TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16Unorm) {
EXPECT_THAT(result(), HasSubstr("(_tint_tmp.x | _tint_tmp.y << 16)"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16float) {
TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16Float) {
auto* call = Call("pack2x16float", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.vec2<f32>());
WrapInFunction(call);
@ -342,6 +342,85 @@ TEST_F(HlslGeneratorImplTest_Intrinsic, Pack2x16float) {
EXPECT_THAT(result(), HasSubstr("(_tint_tmp.x | _tint_tmp.y << 16)"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack4x8Snorm) {
auto* call = Call("unpack4x8snorm", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
EXPECT_THAT(pre_result(), HasSubstr("int _tint_tmp_0 = int(p1);"));
EXPECT_THAT(pre_result(),
HasSubstr("int4 _tint_tmp = int4(_tint_tmp_0 << 24, _tint_tmp_0 "
"<< 16, _tint_tmp_0 << 8, _tint_tmp_0) >> 24;"));
EXPECT_THAT(result(),
HasSubstr("clamp(float4(_tint_tmp) / 127.0, -1.0, 1.0)"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack4x8Unorm) {
auto* call = Call("unpack4x8unorm", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
EXPECT_THAT(pre_result(), HasSubstr("uint _tint_tmp_0 = p1;"));
EXPECT_THAT(
pre_result(),
HasSubstr("uint4 _tint_tmp = uint4(_tint_tmp_0 & 0xff, (_tint_tmp_0 >> "
"8) & 0xff, (_tint_tmp_0 >> 16) & 0xff, _tint_tmp_0 >> 24);"));
EXPECT_THAT(result(), HasSubstr("float4(_tint_tmp) / 255.0"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack2x16Snorm) {
auto* call = Call("unpack2x16snorm", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
EXPECT_THAT(pre_result(), HasSubstr("int _tint_tmp_0 = int(p1);"));
EXPECT_THAT(
pre_result(),
HasSubstr(
"int2 _tint_tmp = int2(_tint_tmp_0 << 16, _tint_tmp_0) >> 16;"));
EXPECT_THAT(result(),
HasSubstr("clamp(float2(_tint_tmp) / 32767.0, -1.0, 1.0)"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack2x16Unorm) {
auto* call = Call("unpack2x16unorm", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
EXPECT_THAT(pre_result(), HasSubstr("uint _tint_tmp_0 = p1;"));
EXPECT_THAT(
pre_result(),
HasSubstr(
"uint2 _tint_tmp = uint2(_tint_tmp_0 & 0xffff, _tint_tmp_0 >> 16);"));
EXPECT_THAT(result(), HasSubstr("float2(_tint_tmp) / 65535.0"));
}
TEST_F(HlslGeneratorImplTest_Intrinsic, Unpack2x16Float) {
auto* call = Call("unpack2x16float", "p1");
Global("p1", ast::StorageClass::kPrivate, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(pre, out, call)) << gen.error();
EXPECT_THAT(pre_result(), HasSubstr("uint _tint_tmp = p1;"));
EXPECT_THAT(
result(),
HasSubstr("f16tof32(uint2(_tint_tmp & 0xffff, _tint_tmp >> 16))"));
}
} // namespace
} // namespace hlsl
} // namespace writer

View File

@ -449,9 +449,14 @@ bool GeneratorImpl::EmitCall(ast::CallExpression* expr) {
if (intrinsic->IsTexture()) {
return EmitTextureCall(expr, intrinsic);
}
if (intrinsic->Type() == semantic::IntrinsicType::kPack2x16Float) {
if (intrinsic->Type() == semantic::IntrinsicType::kPack2x16Float ||
intrinsic->Type() == semantic::IntrinsicType::kUnpack2x16Float) {
make_indent();
out_ << "as_type<uint>(half2(";
if (intrinsic->Type() == semantic::IntrinsicType::kPack2x16Float) {
out_ << "as_type<uint>(half2(";
} else {
out_ << "float2(as_type<half2>(";
}
if (!EmitExpression(expr->params()[0])) {
return false;
}
@ -900,6 +905,18 @@ std::string GeneratorImpl::generate_builtin_name(
case semantic::IntrinsicType::kInverseSqrt:
out += "rsqrt";
break;
case semantic::IntrinsicType::kUnpack4x8Snorm:
out += "unpack_snorm4x8_to_float";
break;
case semantic::IntrinsicType::kUnpack4x8Unorm:
out += "unpack_unorm4x8_to_float";
break;
case semantic::IntrinsicType::kUnpack2x16Snorm:
out += "unpack_snorm2x16_to_float";
break;
case semantic::IntrinsicType::kUnpack2x16Unorm:
out += "unpack_unorm2x16_to_float";
break;
default:
error_ = "Unknown import method: " + std::string(intrinsic->str());
return "";

View File

@ -157,6 +157,11 @@ ast::CallExpression* GenerateCall(IntrinsicType intrinsic,
case IntrinsicType::kPack4x8Snorm:
case IntrinsicType::kPack4x8Unorm:
return builder->Call(str.str(), "f4");
case IntrinsicType::kUnpack4x8Snorm:
case IntrinsicType::kUnpack4x8Unorm:
case IntrinsicType::kUnpack2x16Snorm:
case IntrinsicType::kUnpack2x16Unorm:
return builder->Call(str.str(), "u1");
default:
break;
}
@ -174,6 +179,7 @@ TEST_P(MslIntrinsicTest, Emit) {
Global("f2", ast::StorageClass::kFunction, ty.vec2<float>());
Global("f3", ast::StorageClass::kFunction, ty.vec3<float>());
Global("f4", ast::StorageClass::kFunction, ty.vec4<float>());
Global("u1", ast::StorageClass::kFunction, ty.u32());
Global("u2", ast::StorageClass::kFunction, ty.vec2<unsigned int>());
Global("b2", ast::StorageClass::kFunction, ty.vec2<bool>());
Global("m2x2", ast::StorageClass::kFunction, ty.mat2x2<float>());
@ -276,7 +282,15 @@ INSTANTIATE_TEST_SUITE_P(
IntrinsicData{IntrinsicType::kStep, ParamType::kF32, "metal::step"},
IntrinsicData{IntrinsicType::kTan, ParamType::kF32, "metal::tan"},
IntrinsicData{IntrinsicType::kTanh, ParamType::kF32, "metal::tanh"},
IntrinsicData{IntrinsicType::kTrunc, ParamType::kF32, "metal::trunc"}));
IntrinsicData{IntrinsicType::kTrunc, ParamType::kF32, "metal::trunc"},
IntrinsicData{IntrinsicType::kUnpack4x8Snorm, ParamType::kU32,
"metal::unpack_snorm4x8_to_float"},
IntrinsicData{IntrinsicType::kUnpack4x8Unorm, ParamType::kU32,
"metal::unpack_unorm4x8_to_float"},
IntrinsicData{IntrinsicType::kUnpack2x16Snorm, ParamType::kU32,
"metal::unpack_snorm2x16_to_float"},
IntrinsicData{IntrinsicType::kUnpack2x16Unorm, ParamType::kU32,
"metal::unpack_unorm2x16_to_float"}));
TEST_F(MslGeneratorImplTest, Intrinsic_Call) {
Global("param1", ast::StorageClass::kFunction, ty.vec2<f32>());
@ -304,6 +318,18 @@ TEST_F(MslGeneratorImplTest, Pack2x16Float) {
EXPECT_EQ(gen.result(), " as_type<uint>(half2(p1))");
}
TEST_F(MslGeneratorImplTest, Unpack2x16Float) {
auto* call = Call("unpack2x16float", "p1");
Global("p1", ast::StorageClass::kFunction, ty.u32());
WrapInFunction(call);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitExpression(call)) << gen.error();
EXPECT_EQ(gen.result(), " float2(as_type<half2>(p1))");
}
} // namespace
} // namespace msl
} // namespace writer

View File

@ -284,6 +284,16 @@ uint32_t intrinsic_to_glsl_method(const semantic::Intrinsic* intrinsic) {
return GLSLstd450Tanh;
case IntrinsicType::kTrunc:
return GLSLstd450Trunc;
case IntrinsicType::kUnpack4x8Snorm:
return GLSLstd450UnpackSnorm4x8;
case IntrinsicType::kUnpack4x8Unorm:
return GLSLstd450UnpackUnorm4x8;
case IntrinsicType::kUnpack2x16Snorm:
return GLSLstd450UnpackSnorm2x16;
case IntrinsicType::kUnpack2x16Unorm:
return GLSLstd450UnpackUnorm2x16;
case IntrinsicType::kUnpack2x16Float:
return GLSLstd450UnpackHalf2x16;
default:
break;
}

View File

@ -1506,6 +1506,131 @@ TEST_F(IntrinsicBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
Validate(b);
}
using Intrinsic_Builtin_DataPacking_Test =
IntrinsicBuilderTestWithParam<IntrinsicData>;
TEST_P(Intrinsic_Builtin_DataPacking_Test, Binary) {
auto param = GetParam();
bool pack4 = param.name == "pack4x8snorm" || param.name == "pack4x8unorm";
auto* call = pack4 ? Call(param.name, vec4<float>(1.0f, 1.0f, 1.0f, 1.0f))
: Call(param.name, vec2<float>(1.0f, 1.0f));
WrapInFunction(call);
auto* func = Func("a_func", ast::VariableList{}, ty.void_(),
ast::StatementList{}, ast::FunctionDecorationList{});
spirv::Builder& b = Build();
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
EXPECT_EQ(b.GenerateCallExpression(call), 5u) << b.error();
if (pack4) {
EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
OpName %3 "a_func"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%6 = OpTypeInt 32 0
%9 = OpTypeFloat 32
%8 = OpTypeVector %9 4
%10 = OpConstant %9 1
%11 = OpConstantComposite %8 %10 %10 %10 %10
%3 = OpFunction %2 None %1
%4 = OpLabel
%5 = OpExtInst %6 %7 )" + param.op +
R"( %11
OpReturn
OpFunctionEnd
)");
} else {
EXPECT_EQ(DumpBuilder(b), R"(%7 = OpExtInstImport "GLSL.std.450"
OpName %3 "a_func"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%6 = OpTypeInt 32 0
%9 = OpTypeFloat 32
%8 = OpTypeVector %9 2
%10 = OpConstant %9 1
%11 = OpConstantComposite %8 %10 %10
%3 = OpFunction %2 None %1
%4 = OpLabel
%5 = OpExtInst %6 %7 )" + param.op +
R"( %11
OpReturn
OpFunctionEnd
)");
}
}
INSTANTIATE_TEST_SUITE_P(
IntrinsicBuilderTest,
Intrinsic_Builtin_DataPacking_Test,
testing::Values(IntrinsicData{"pack4x8snorm", "PackSnorm4x8"},
IntrinsicData{"pack4x8unorm", "PackUnorm4x8"},
IntrinsicData{"pack2x16snorm", "PackSnorm2x16"},
IntrinsicData{"pack2x16unorm", "PackUnorm2x16"},
IntrinsicData{"pack2x16float", "PackHalf2x16"}));
using Intrinsic_Builtin_DataUnpacking_Test =
IntrinsicBuilderTestWithParam<IntrinsicData>;
TEST_P(Intrinsic_Builtin_DataUnpacking_Test, Binary) {
auto param = GetParam();
bool pack4 = param.name == "unpack4x8snorm" || param.name == "unpack4x8unorm";
auto* call = Call(param.name, 1u);
WrapInFunction(call);
auto* func = Func("a_func", ast::VariableList{}, ty.void_(),
ast::StatementList{}, ast::FunctionDecorationList{});
spirv::Builder& b = Build();
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
EXPECT_EQ(b.GenerateCallExpression(call), 5u) << b.error();
if (pack4) {
EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
OpName %3 "a_func"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%7 = OpTypeFloat 32
%6 = OpTypeVector %7 4
%9 = OpTypeInt 32 0
%10 = OpConstant %9 1
%3 = OpFunction %2 None %1
%4 = OpLabel
%5 = OpExtInst %6 %8 )" + param.op +
R"( %10
OpReturn
OpFunctionEnd
)");
} else {
EXPECT_EQ(DumpBuilder(b), R"(%8 = OpExtInstImport "GLSL.std.450"
OpName %3 "a_func"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%7 = OpTypeFloat 32
%6 = OpTypeVector %7 2
%9 = OpTypeInt 32 0
%10 = OpConstant %9 1
%3 = OpFunction %2 None %1
%4 = OpLabel
%5 = OpExtInst %6 %8 )" + param.op +
R"( %10
OpReturn
OpFunctionEnd
)");
}
}
INSTANTIATE_TEST_SUITE_P(
IntrinsicBuilderTest,
Intrinsic_Builtin_DataUnpacking_Test,
testing::Values(IntrinsicData{"unpack4x8snorm", "UnpackSnorm4x8"},
IntrinsicData{"unpack4x8unorm", "UnpackUnorm4x8"},
IntrinsicData{"unpack2x16snorm", "UnpackSnorm2x16"},
IntrinsicData{"unpack2x16unorm", "UnpackUnorm2x16"},
IntrinsicData{"unpack2x16float", "UnpackHalf2x16"}));
} // namespace
} // namespace spirv
} // namespace writer