void GenerateSamplingShaderModule()

in renderdoc/driver/vulkan/vk_shaderdebug.cpp [2223:2937]


  void GenerateSamplingShaderModule(rdcarray<uint32_t> &spirv, bool depthTex, bool uintTex,
                                    bool sintTex)
  {
    // this could be done as a glsl shader, but glslang has some bugs compiling the specialisation
    // constants, so we generate it by hand - which isn't too hard

    rdcspv::Editor editor(spirv);

    // create as SPIR-V 1.0 for best compatibility
    editor.CreateEmpty(1, 0);

    editor.AddCapability(rdcspv::Capability::Shader);
    editor.AddCapability(rdcspv::Capability::ImageQuery);
    editor.AddCapability(rdcspv::Capability::Sampled1D);
    editor.AddCapability(rdcspv::Capability::SampledBuffer);

    if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
      editor.AddCapability(rdcspv::Capability::MinLod);
    if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended)
      editor.AddCapability(rdcspv::Capability::ImageGatherExtended);

    const bool cubeArray = (m_pDriver->GetDeviceEnabledFeatures().imageCubeArray != VK_FALSE);

    rdcspv::Id entryId = editor.MakeId();

    editor.AddOperation(
        editor.Begin(rdcspv::Section::MemoryModel),
        rdcspv::OpMemoryModel(rdcspv::AddressingModel::Logical, rdcspv::MemoryModel::GLSL450));

    rdcspv::Id u32 = editor.DeclareType(rdcspv::scalar<uint32_t>());
    rdcspv::Id i32 = editor.DeclareType(rdcspv::scalar<int32_t>());
    rdcspv::Id f32 = editor.DeclareType(rdcspv::scalar<float>());

    rdcspv::Id v2i32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<int32_t>(), 2));
    rdcspv::Id v3i32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<int32_t>(), 3));
    rdcspv::Id v2f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 2));
    rdcspv::Id v3f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 3));
    rdcspv::Id v4f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));

    // int2[4]
    rdcspv::Id a4v2i32 = editor.AddType(
        rdcspv::OpTypeArray(editor.MakeId(), v2i32, editor.AddConstantImmediate<uint32_t>(4)));

    rdcspv::Scalar base = rdcspv::scalar<float>();
    if(uintTex)
      base = rdcspv::scalar<uint32_t>();
    else if(sintTex)
      base = rdcspv::scalar<int32_t>();

    rdcspv::Id resultType = editor.DeclareType(rdcspv::Vector(base, 4));
    rdcspv::Id scalarResultType = editor.DeclareType(base);

// add specialisation constants for all the parameters
#define MEMBER_IDX(struct, name) uint32_t(offsetof(struct, name) / sizeof(uint32_t))

#define DECL_SPECID(type, name, value)                                                     \
  rdcspv::Id name =                                                                        \
      editor.AddSpecConstantImmediate<type>(0U, MEMBER_IDX(ShaderConstParameters, value)); \
  editor.SetName(name, "spec_" #name);

    DECL_SPECID(uint32_t, operation, operation);
    DECL_SPECID(bool, useGradOrGatherOffsets, useGradOrGatherOffsets);
    DECL_SPECID(uint32_t, dim, dim);
    DECL_SPECID(int32_t, gatherChannel, gatherChannel);
    DECL_SPECID(int32_t, gather_u0, gatherOffsets.u0);
    DECL_SPECID(int32_t, gather_v0, gatherOffsets.v0);
    DECL_SPECID(int32_t, gather_u1, gatherOffsets.u1);
    DECL_SPECID(int32_t, gather_v1, gatherOffsets.v1);
    DECL_SPECID(int32_t, gather_u2, gatherOffsets.u2);
    DECL_SPECID(int32_t, gather_v2, gatherOffsets.v2);
    DECL_SPECID(int32_t, gather_u3, gatherOffsets.u3);
    DECL_SPECID(int32_t, gather_v3, gatherOffsets.v3);

    struct StructMember
    {
      rdcspv::Id loadedType;
      rdcspv::Id ptrType;
      rdcspv::Id loadedId;
      const char *name;
      uint32_t memberIndex;
    };

    rdcarray<rdcspv::Id> memberIds;
    rdcarray<StructMember> cbufferMembers;

    rdcspv::Id type_int32_t = editor.DeclareType(rdcspv::scalar<int32_t>());
    rdcspv::Id type_float = editor.DeclareType(rdcspv::scalar<float>());

    rdcspv::Id uniformptr_int32_t =
        editor.DeclareType(rdcspv::Pointer(type_int32_t, rdcspv::StorageClass::Uniform));
    rdcspv::Id uniformptr_float =
        editor.DeclareType(rdcspv::Pointer(type_float, rdcspv::StorageClass::Uniform));

#define DECL_UNIFORM(type, name, value)                                                  \
  rdcspv::Id name = editor.MakeId();                                                     \
  editor.SetName(name, "uniform_" #name);                                                \
  cbufferMembers.push_back({CONCAT(type_, type), CONCAT(uniformptr_, type), name, #name, \
                            MEMBER_IDX(ShaderUniformParameters, value)});                \
  memberIds.push_back(CONCAT(type_, type));
    DECL_UNIFORM(int32_t, texel_u, texel_uvw.x);
    DECL_UNIFORM(int32_t, texel_v, texel_uvw.y);
    DECL_UNIFORM(int32_t, texel_w, texel_uvw.z);
    DECL_UNIFORM(int32_t, texel_lod, texel_lod);
    DECL_UNIFORM(float, u, uvwa[0]);
    DECL_UNIFORM(float, v, uvwa[1]);
    DECL_UNIFORM(float, w, uvwa[2]);
    DECL_UNIFORM(float, cube_a, uvwa[3]);
    DECL_UNIFORM(float, dudx, ddx[0]);
    DECL_UNIFORM(float, dvdx, ddx[1]);
    DECL_UNIFORM(float, dwdx, ddx[2]);
    DECL_UNIFORM(float, dudy, ddy[0]);
    DECL_UNIFORM(float, dvdy, ddy[1]);
    DECL_UNIFORM(float, dwdy, ddy[2]);
    DECL_UNIFORM(int32_t, dynoffset_u, offset.x);
    DECL_UNIFORM(int32_t, dynoffset_v, offset.y);
    DECL_UNIFORM(int32_t, dynoffset_w, offset.z);
    DECL_UNIFORM(int32_t, sampleIdx, sampleIdx);
    DECL_UNIFORM(float, compare, compare);
    DECL_UNIFORM(float, lod, lod);
    DECL_UNIFORM(float, minlod, minlod);

    rdcspv::Id cbufferStructID = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), memberIds));
    editor.AddDecoration(rdcspv::OpDecorate(cbufferStructID, rdcspv::Decoration::Block));
    for(const StructMember &m : cbufferMembers)
    {
      editor.AddDecoration(rdcspv::OpMemberDecorate(
          cbufferStructID, m.memberIndex,
          rdcspv::DecorationParam<rdcspv::Decoration::Offset>(m.memberIndex * sizeof(uint32_t))));
      editor.SetMemberName(cbufferStructID, m.memberIndex, m.name);
    }

    rdcspv::Id constoffset_u = gather_u0;
    rdcspv::Id constoffset_uv = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u0, gather_v0}));
    rdcspv::Id constoffset_uvw = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v3i32, editor.MakeId(), {gather_u0, gather_v0, gather_u1}));
    editor.SetName(constoffset_u, "constoffset_u");
    editor.SetName(constoffset_uv, "constoffset_uv");
    editor.SetName(constoffset_uvw, "constoffset_uvw");

    rdcspv::Id gather_0 = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u0, gather_v0}));
    rdcspv::Id gather_1 = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u1, gather_v1}));
    rdcspv::Id gather_2 = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u2, gather_v2}));
    rdcspv::Id gather_3 = editor.AddConstant(
        rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u3, gather_v3}));

    rdcspv::Id gatherOffsets = editor.AddConstant(rdcspv::OpSpecConstantComposite(
        a4v2i32, editor.MakeId(), {gather_0, gather_1, gather_2, gather_3}));

    editor.SetName(gatherOffsets, "gatherOffsets");

    // create the output. It's always a 4-wide vector
    rdcspv::Id outPtrType =
        editor.DeclareType(rdcspv::Pointer(resultType, rdcspv::StorageClass::Output));

    rdcspv::Id outVar = editor.AddVariable(
        rdcspv::OpVariable(outPtrType, editor.MakeId(), rdcspv::StorageClass::Output));
    editor.AddDecoration(
        rdcspv::OpDecorate(outVar, rdcspv::DecorationParam<rdcspv::Decoration::Location>(0)));

    editor.SetName(outVar, "output");

    rdcspv::ImageFormat unk = rdcspv::ImageFormat::Unknown;

    // create the five textures and sampler
    rdcspv::Id texSampTypes[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_1D, 0, 1, 0, 1, unk)),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_2D, 0, 1, 0, 1, unk)),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_3D, 0, 0, 0, 1, unk)),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_2D, 0, 1, 1, 1, unk)),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::Cube, 0, cubeArray ? 1 : 0, 0, 1, unk)),
        editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::Buffer, 0, 0, 0, 1, unk)),
        editor.DeclareType(rdcspv::Sampler()),
        cbufferStructID,
    };
    rdcspv::Id bindVars[(uint32_t)ShaderDebugBind::Count];
    rdcspv::Id texSampCombinedTypes[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[1])),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[2])),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[3])),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[4])),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[5])),
        editor.DeclareType(rdcspv::SampledImage(texSampTypes[6])),
        rdcspv::Id(),
        rdcspv::Id(),
    };

    for(size_t i = (size_t)ShaderDebugBind::First; i < (size_t)ShaderDebugBind::Count; i++)
    {
      rdcspv::StorageClass storageClass = rdcspv::StorageClass::UniformConstant;

      if(depthTex)
      {
        if(i == (size_t)ShaderDebugBind::Tex3D && !m_pDriver->GetReplay()->Depth3DSupported())
          continue;
        else if(i == (size_t)ShaderDebugBind::TexCube && !m_pDriver->GetReplay()->DepthCubeSupported())
          continue;
      }

      if(i == (size_t)ShaderDebugBind::Constants)
        storageClass = rdcspv::StorageClass::Uniform;

      rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(texSampTypes[i], storageClass));

      bindVars[i] = editor.AddVariable(rdcspv::OpVariable(ptrType, editor.MakeId(), storageClass));

      editor.AddDecoration(rdcspv::OpDecorate(
          bindVars[i], rdcspv::DecorationParam<rdcspv::Decoration::DescriptorSet>(0U)));
      editor.AddDecoration(rdcspv::OpDecorate(
          bindVars[i], rdcspv::DecorationParam<rdcspv::Decoration::Binding>((uint32_t)i)));
    }

    editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex1D], "Tex1D");
    editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex2D], "Tex2D");
    if(bindVars[(size_t)ShaderDebugBind::Tex3D] != rdcspv::Id())
      editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex3D], "Tex3D");
    editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex2DMS], "Tex2DMS");
    if(bindVars[(size_t)ShaderDebugBind::TexCube] != rdcspv::Id())
      editor.SetName(bindVars[(size_t)ShaderDebugBind::TexCube], "TexCube");
    editor.SetName(bindVars[(size_t)ShaderDebugBind::Buffer], "Buffer");
    editor.SetName(bindVars[(size_t)ShaderDebugBind::Sampler], "Sampler");
    editor.SetName(bindVars[(size_t)ShaderDebugBind::Constants], "CBuffer");

    rdcspv::Id uvwa_ptr = editor.DeclareType(rdcspv::Pointer(v4f32, rdcspv::StorageClass::Input));
    rdcspv::Id input_uvwa_var = editor.AddVariable(
        rdcspv::OpVariable(uvwa_ptr, editor.MakeId(), rdcspv::StorageClass::Input));
    editor.AddDecoration(rdcspv::OpDecorate(
        input_uvwa_var, rdcspv::DecorationParam<rdcspv::Decoration::Location>(0)));

    editor.SetName(input_uvwa_var, "input_uvwa");

    // register the entry point
    editor.AddOperation(editor.Begin(rdcspv::Section::EntryPoints),
                        rdcspv::OpEntryPoint(rdcspv::ExecutionModel::Fragment, entryId, "main",
                                             {input_uvwa_var, outVar}));
    editor.AddExecutionMode(rdcspv::OpExecutionMode(entryId, rdcspv::ExecutionMode::OriginUpperLeft));

    rdcspv::Id voidType = editor.DeclareType(rdcspv::scalar<void>());
    rdcspv::Id funcType = editor.DeclareType(rdcspv::FunctionType(voidType, {}));

    rdcspv::OperationList func;
    func.add(rdcspv::OpFunction(voidType, entryId, rdcspv::FunctionControl::None, funcType));
    func.add(rdcspv::OpLabel(editor.MakeId()));

    // access chain and load all the cbuffer variables
    for(const StructMember &m : cbufferMembers)
    {
      rdcspv::Id ptr = func.add(rdcspv::OpAccessChain(
          m.ptrType, editor.MakeId(), bindVars[(size_t)ShaderDebugBind::Constants],
          {editor.AddConstantImmediate<uint32_t>(m.memberIndex)}));
      func.add(rdcspv::OpLoad(m.loadedType, m.loadedId, ptr));
    }

    // declare cbuffer composites
    rdcspv::Id texel_uv =
        func.add(rdcspv::OpCompositeConstruct(v2i32, editor.MakeId(), {texel_u, texel_v}));
    rdcspv::Id texel_uvw =
        func.add(rdcspv::OpCompositeConstruct(v3i32, editor.MakeId(), {texel_u, texel_v, texel_w}));

    editor.SetName(texel_uv, "texel_uv");
    editor.SetName(texel_uvw, "texel_uvw");

    rdcspv::Id uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {u, v}));
    rdcspv::Id uvw = func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {u, v, w}));
    rdcspv::Id uvwa =
        func.add(rdcspv::OpCompositeConstruct(v4f32, editor.MakeId(), {u, v, w, cube_a}));

    editor.SetName(uv, "uv");
    editor.SetName(uvw, "uvw");
    editor.SetName(uvwa, "uvwa");

    rdcspv::Id ddx_uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {dudx, dvdx}));
    rdcspv::Id ddx_uvw =
        func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {dudx, dvdx, dwdx}));

    editor.SetName(ddx_uv, "ddx_uv");
    editor.SetName(ddx_uvw, "ddx_uvw");

    rdcspv::Id ddy_uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {dudy, dvdy}));
    rdcspv::Id ddy_uvw =
        func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {dudy, dvdy, dwdy}));

    editor.SetName(ddy_uv, "ddy_uv");
    editor.SetName(ddy_uvw, "ddy_uvw");

    rdcspv::Id dynoffset_uv =
        func.add(rdcspv::OpCompositeConstruct(v2i32, editor.MakeId(), {dynoffset_u, dynoffset_v}));
    rdcspv::Id dynoffset_uvw = func.add(rdcspv::OpCompositeConstruct(
        v3i32, editor.MakeId(), {dynoffset_u, dynoffset_v, dynoffset_w}));

    editor.SetName(dynoffset_uv, "dynoffset_uv");
    editor.SetName(dynoffset_uvw, "dynoffset_uvw");

    rdcspv::Id input_uvwa = func.add(rdcspv::OpLoad(v4f32, editor.MakeId(), input_uvwa_var));
    rdcspv::Id input_uvw =
        func.add(rdcspv::OpVectorShuffle(v3f32, editor.MakeId(), input_uvwa, input_uvwa, {0, 1, 2}));
    rdcspv::Id input_uv =
        func.add(rdcspv::OpVectorShuffle(v2f32, editor.MakeId(), input_uvw, input_uvw, {0, 1}));
    rdcspv::Id input_u = func.add(rdcspv::OpCompositeExtract(f32, editor.MakeId(), input_uvw, {0}));

    // first store NULL data in, so the output is always initialised

    rdcspv::Id breakLabel = editor.MakeId();
    rdcspv::Id defaultLabel = editor.MakeId();

    // combine the operation with the image type:
    // operation * 10 + dim
    RDCCOMPILE_ASSERT(size_t(ShaderDebugBind::Count) < 10, "Combining value ranges will overlap!");
    rdcspv::Id switchVal = func.add(rdcspv::OpIMul(u32, editor.MakeId(), operation,
                                                   editor.AddConstantImmediate<uint32_t>(10U)));
    switchVal = func.add(rdcspv::OpIAdd(u32, editor.MakeId(), switchVal, dim));

    // switch on the combined operation and image type value
    rdcarray<rdcspv::SwitchPairU32LiteralId> targets;

    rdcspv::OperationList cases;

    rdcspv::Id texel_coord[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        texel_uv,        // 1D - u and array
        texel_uvw,       // 2D - u,v and array
        texel_uvw,       // 3D - u,v,w
        texel_uvw,       // 2DMS - u,v and array
        rdcspv::Id(),    // Cube
        texel_u,         // Buffer - u
    };

    // only used for QueryLod, so we can ignore MSAA/Buffer
    rdcspv::Id input_coord[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        input_u,         // 1D - u
        input_uv,        // 2D - u,v
        input_uvw,       // 3D - u,v,w
        rdcspv::Id(),    // 2DMS
        input_uvw,       // Cube - u,v,w
        rdcspv::Id(),    // Buffer
    };

    rdcspv::Id coord[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        uv,                        // 1D - u and array
        uvw,                       // 2D - u,v and array
        uvw,                       // 3D - u,v,w
        uvw,                       // 2DMS - u,v and array
        cubeArray ? uvwa : uvw,    // Cube - u,v,w and array (if supported)
        u,                         // Buffer - u
    };

    rdcspv::Id constoffset[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        constoffset_u,      // 1D - u
        constoffset_uv,     // 2D - u,v
        constoffset_uvw,    // 3D - u,v,w
        constoffset_uv,     // 2DMS - u,v
        rdcspv::Id(),       // Cube - not valid
        constoffset_u,      // Buffer - u
    };

    rdcspv::Id dynoffset[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        dynoffset_u,      // 1D - u
        dynoffset_uv,     // 2D - u,v
        dynoffset_uvw,    // 3D - u,v,w
        dynoffset_uv,     // 2DMS - u,v
        rdcspv::Id(),     // Cube - not valid
        dynoffset_u,      // Buffer - u
    };

    rdcspv::Id ddxs[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        dudx,       // 1D - u
        ddx_uv,     // 2D - u,v
        ddx_uvw,    // 3D - u,v,w
        ddx_uv,     // 2DMS - u,v
        ddx_uvw,    // Cube - u,v,w
        dudx,       // Buffer - u
    };

    rdcspv::Id ddys[(uint32_t)ShaderDebugBind::Count] = {
        rdcspv::Id(),
        dudy,       // 1D - u
        ddy_uv,     // 2D - u,v
        ddy_uvw,    // 3D - u,v,w
        ddy_uv,     // 2DMS - u,v
        ddy_uvw,    // Cube - u,v,w
        dudy,       // Buffer - u
    };

    uint32_t sampIdx = (uint32_t)ShaderDebugBind::Sampler;

    rdcspv::Id zerof = editor.AddConstantImmediate<float>(0.0f);

    for(uint32_t i = (uint32_t)ShaderDebugBind::First; i < (uint32_t)ShaderDebugBind::Count; i++)
    {
      if(i == sampIdx || i == (uint32_t)ShaderDebugBind::Constants)
        continue;

      if(bindVars[i] == rdcspv::Id())
        continue;

      rdcspv::ImageOperandsAndParamDatas imageOperandsWithOffsets;

      // most operations support offsets, set the operands commonly here.
      // with the shaderImageGatherExtended feature, gather opcodes will always get their operands
      // via uniforms to cut down on pipeline specialisations a little, but all other cases the
      // offsets must be constant.
      if(constoffset[i] != rdcspv::Id())
        imageOperandsWithOffsets.setConstOffset(constoffset[i]);

      // can't fetch from cubemaps
      if(i != (uint32_t)ShaderDebugBind::TexCube)
      {
        rdcspv::Op op = rdcspv::Op::ImageFetch;

        rdcspv::Id label = editor.MakeId();
        targets.push_back({(uint32_t)op * 10 + i, label});

        rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;

        if(i != (uint32_t)ShaderDebugBind::Buffer && i != (uint32_t)ShaderDebugBind::Tex2DMS)
          operands.setLod(texel_lod);

        if(i == (uint32_t)ShaderDebugBind::Tex2DMS)
          operands.setSample(sampleIdx);

        cases.add(rdcspv::OpLabel(label));
        rdcspv::Id loaded = cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
        rdcspv::Id sampleResult = cases.add(
            rdcspv::OpImageFetch(resultType, editor.MakeId(), loaded, texel_coord[i], operands));
        cases.add(rdcspv::OpStore(outVar, sampleResult));
        cases.add(rdcspv::OpBranch(breakLabel));
      }

      // buffers and multisampled images don't support sampling, so skip the other operations at
      // this point
      if(i == (uint32_t)ShaderDebugBind::Buffer || i == (uint32_t)ShaderDebugBind::Tex2DMS)
        continue;

      {
        rdcspv::Op op = rdcspv::Op::ImageQueryLod;

        rdcspv::Id label = editor.MakeId();
        targets.push_back({(uint32_t)op * 10 + i, label});

        cases.add(rdcspv::OpLabel(label));
        rdcspv::Id loadedImage =
            cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
        rdcspv::Id loadedSampler =
            cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));

        rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
            texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

        rdcspv::Id sampleResult =
            cases.add(rdcspv::OpImageQueryLod(v2f32, editor.MakeId(), combined, input_coord[i]));
        sampleResult = cases.add(rdcspv::OpVectorShuffle(v4f32, editor.MakeId(), sampleResult,
                                                         sampleResult, {0, 1, 0, 1}));

        // if we're sampling from an integer texture the output variable will be the same type.
        // Just bitcast the float bits into it, which will come out the other side the right type.
        if(uintTex || sintTex)
          sampleResult = cases.add(rdcspv::OpBitcast(resultType, editor.MakeId(), sampleResult));

        cases.add(rdcspv::OpStore(outVar, sampleResult));
        cases.add(rdcspv::OpBranch(breakLabel));
      }

      for(rdcspv::Op op : {rdcspv::Op::ImageSampleExplicitLod, rdcspv::Op::ImageSampleImplicitLod})
      {
        rdcspv::Id label = editor.MakeId();
        targets.push_back({(uint32_t)op * 10 + i, label});

        cases.add(rdcspv::OpLabel(label));
        rdcspv::Id loadedImage =
            cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
        rdcspv::Id loadedSampler =
            cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));

        rdcspv::Id mergeLabel = editor.MakeId();
        rdcspv::Id gradCase = editor.MakeId();
        rdcspv::Id lodCase = editor.MakeId();
        cases.add(rdcspv::OpSelectionMerge(mergeLabel, rdcspv::SelectionControl::None));
        cases.add(rdcspv::OpBranchConditional(useGradOrGatherOffsets, gradCase, lodCase));

        rdcspv::Id lodResult;
        {
          cases.add(rdcspv::OpLabel(lodCase));
          rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
          operands.setLod(lod);
          rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
              texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

          lodResult = cases.add(rdcspv::OpImageSampleExplicitLod(resultType, editor.MakeId(),
                                                                 combined, coord[i], operands));

          cases.add(rdcspv::OpBranch(mergeLabel));
        }

        rdcspv::Id gradResult;
        {
          cases.add(rdcspv::OpLabel(gradCase));
          rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
          operands.setGrad(ddxs[i], ddys[i]);
          if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
            operands.setMinLod(minlod);
          rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
              texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

          gradResult = cases.add(rdcspv::OpImageSampleExplicitLod(resultType, editor.MakeId(),
                                                                  combined, coord[i], operands));

          cases.add(rdcspv::OpBranch(mergeLabel));
        }

        cases.add(rdcspv::OpLabel(mergeLabel));
        rdcspv::Id sampleResult = cases.add(rdcspv::OpPhi(
            resultType, editor.MakeId(), {{lodResult, lodCase}, {gradResult, gradCase}}));
        cases.add(rdcspv::OpStore(outVar, sampleResult));
        cases.add(rdcspv::OpBranch(breakLabel));
      }

      // on Qualcomm we only emit Dref instructions against 2D textures, otherwise the compiler may
      // crash.
      if(m_pDriver->GetDriverInfo().QualcommDrefNon2DCompileCrash())
        depthTex &= (i == (uint32_t)ShaderDebugBind::Tex2D);

      // VUID-StandaloneSpirv-OpImage-04777
      // OpImage*Dref must not consume an image whose Dim is 3D
      if(i == (uint32_t)ShaderDebugBind::Tex3D)
        depthTex = false;

      // don't emit dref's for uint/sint textures
      if(uintTex || sintTex)
        depthTex = false;

      if(depthTex)
      {
        for(rdcspv::Op op :
            {rdcspv::Op::ImageSampleDrefExplicitLod, rdcspv::Op::ImageSampleDrefImplicitLod})
        {
          rdcspv::Id label = editor.MakeId();
          targets.push_back({(uint32_t)op * 10 + i, label});

          cases.add(rdcspv::OpLabel(label));
          rdcspv::Id loadedImage =
              cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
          rdcspv::Id loadedSampler =
              cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));

          rdcspv::Id mergeLabel = editor.MakeId();
          rdcspv::Id gradCase = editor.MakeId();
          rdcspv::Id lodCase = editor.MakeId();
          cases.add(rdcspv::OpSelectionMerge(mergeLabel, rdcspv::SelectionControl::None));
          cases.add(rdcspv::OpBranchConditional(useGradOrGatherOffsets, gradCase, lodCase));

          rdcspv::Id lodResult;
          {
            cases.add(rdcspv::OpLabel(lodCase));
            rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
            operands.setLod(lod);
            rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
                texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

            lodResult = cases.add(rdcspv::OpImageSampleDrefExplicitLod(
                scalarResultType, editor.MakeId(), combined, coord[i], compare, operands));

            cases.add(rdcspv::OpBranch(mergeLabel));
          }

          rdcspv::Id gradResult;
          {
            cases.add(rdcspv::OpLabel(gradCase));
            rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
            operands.setGrad(ddxs[i], ddys[i]);
            if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
              operands.setMinLod(minlod);
            rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
                texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

            gradResult = cases.add(rdcspv::OpImageSampleDrefExplicitLod(
                scalarResultType, editor.MakeId(), combined, coord[i], compare, operands));

            cases.add(rdcspv::OpBranch(mergeLabel));
          }

          cases.add(rdcspv::OpLabel(mergeLabel));
          rdcspv::Id scalarSampleResult = cases.add(rdcspv::OpPhi(
              scalarResultType, editor.MakeId(), {{lodResult, lodCase}, {gradResult, gradCase}}));
          rdcspv::Id sampleResult = cases.add(rdcspv::OpCompositeConstruct(
              resultType, editor.MakeId(), {scalarSampleResult, zerof, zerof, zerof}));
          cases.add(rdcspv::OpStore(outVar, sampleResult));
          cases.add(rdcspv::OpBranch(breakLabel));
        }
      }

      // can only gather with 2D/Cube textures
      if(i == (uint32_t)ShaderDebugBind::Tex1D || i == (uint32_t)ShaderDebugBind::Tex3D)
        continue;

      for(rdcspv::Op op : {rdcspv::Op::ImageGather, rdcspv::Op::ImageDrefGather})
      {
        if(op == rdcspv::Op::ImageDrefGather && !depthTex)
          continue;

        rdcspv::Id label = editor.MakeId();
        targets.push_back({(uint32_t)op * 10 + i, label});

        cases.add(rdcspv::OpLabel(label));
        rdcspv::Id loadedImage =
            cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
        rdcspv::Id loadedSampler =
            cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));

        rdcspv::Id sampleResult;
        if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended)
        {
          rdcspv::Id mergeLabel = editor.MakeId();
          rdcspv::Id constsCase = editor.MakeId();
          rdcspv::Id baseCase = editor.MakeId();
          cases.add(rdcspv::OpSelectionMerge(mergeLabel, rdcspv::SelectionControl::None));
          cases.add(rdcspv::OpBranchConditional(useGradOrGatherOffsets, constsCase, baseCase));

          rdcspv::Id baseResult;
          {
            cases.add(rdcspv::OpLabel(baseCase));

            rdcspv::ImageOperandsAndParamDatas operands;

            if(dynoffset[i] != rdcspv::Id())
              operands.setOffset(dynoffset[i]);

            rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
                texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

            if(op == rdcspv::Op::ImageGather)
              baseResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
                                                           coord[i], gatherChannel, operands));
            else
              baseResult = cases.add(rdcspv::OpImageDrefGather(
                  resultType, editor.MakeId(), combined, coord[i], compare, operands));

            cases.add(rdcspv::OpBranch(mergeLabel));
          }

          rdcspv::Id constsResult;
          {
            cases.add(rdcspv::OpLabel(constsCase));
            rdcspv::ImageOperandsAndParamDatas operands;    // don't use the offsets above

            // if this feature isn't available, this path will never be exercised (since we only
            // come in here when the actual shader used const offsets) so it's fine to drop it in
            // that case to ensure the module is still legal.
            if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended &&
               i != (uint32_t)ShaderDebugBind::TexCube)
              operands.setConstOffsets(gatherOffsets);

            rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
                texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

            if(op == rdcspv::Op::ImageGather)
              constsResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
                                                             coord[i], gatherChannel, operands));
            else
              constsResult = cases.add(rdcspv::OpImageDrefGather(
                  resultType, editor.MakeId(), combined, coord[i], compare, operands));

            cases.add(rdcspv::OpBranch(mergeLabel));
          }

          cases.add(rdcspv::OpLabel(mergeLabel));
          sampleResult = cases.add(rdcspv::OpPhi(
              resultType, editor.MakeId(), {{baseResult, baseCase}, {constsResult, constsCase}}));
        }
        else
        {
          rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;

          rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
              texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));

          if(op == rdcspv::Op::ImageGather)
            sampleResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
                                                           coord[i], gatherChannel, operands));
          else
            sampleResult = cases.add(rdcspv::OpImageDrefGather(
                resultType, editor.MakeId(), combined, coord[i], compare, operands));
        }

        cases.add(rdcspv::OpStore(outVar, sampleResult));
        cases.add(rdcspv::OpBranch(breakLabel));
      }
    }

    func.add(rdcspv::OpSelectionMerge(breakLabel, rdcspv::SelectionControl::None));
    func.add(rdcspv::OpSwitch32(switchVal, defaultLabel, targets));

    func.append(cases);

    // default: store NULL data
    func.add(rdcspv::OpLabel(defaultLabel));
    func.add(rdcspv::OpStore(
        outVar, editor.AddConstant(rdcspv::OpConstantNull(resultType, editor.MakeId()))));
    func.add(rdcspv::OpBranch(breakLabel));

    func.add(rdcspv::OpLabel(breakLabel));
    func.add(rdcspv::OpReturn());
    func.add(rdcspv::OpFunctionEnd());

    editor.AddFunction(func);
  }