class DxbcIsgn {
  public:
    DxbcIsgn(DxbcReader reader, DxbcTag tag) {
    uint32_t elementCount = reader.readu32();
    reader.skip(sizeof(uint32_t));

    std::array<DxbcScalarType, 4> componentTypes = {
      DxbcScalarType::Uint32, DxbcScalarType::Uint32,
      DxbcScalarType::Sint32, DxbcScalarType::Float32,
    };

    // https://github.com/DarkStarSword/3d-fixes/blob/master/dx11shaderanalyse.py#L101
    bool hasStream    = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1") || (tag == "OSG5");
    bool hasPrecision = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1");

    for (uint32_t i = 0; i < elementCount; i++) {
      DxbcSgnEntry entry;
      entry.streamId        = hasStream ? reader.readu32() : 0;
      entry.semanticName    = reader.clone(reader.readu32()).readString();
      entry.semanticIndex   = reader.readu32();
      entry.systemValue     = static_cast<DxbcSystemValue>(reader.readu32());
      entry.componentType   = componentTypes.at(reader.readu32());
      entry.registerId      = reader.readu32();

      uint32_t mask = reader.readu32();

      entry.componentMask   = bit::extract(mask, 0, 3);
      entry.componentUsed   = bit::extract(mask, 8, 11);

      if (hasPrecision)
        reader.readu32();

      m_entries.push_back(entry);
    }
  }
    ~DxbcIsgn() {

  }
    const DxbcSgnEntry* findByRegister(
            uint32_t     registerId) const;
    const DxbcSgnEntry* find(
      const std::string& semanticName,
            uint32_t     semanticIndex,
            uint32_t     streamIndex) const;
    DxbcRegMask regMask(
            uint32_t     registerId) const {
    DxbcRegMask mask;

    for (auto e = this->begin(); e != this->end(); e++) {
      if (e->registerId == registerId)
        mask |= e->componentMask;
    }

    return mask;
  }
    uint32_t maxRegisterCount() const {
    uint32_t result = 0;
    for (auto e = this->begin(); e != this->end(); e++)
      result = std::max(result, e->registerId + 1);
    return result;
  }

  private:
    std::vector<DxbcSgnEntry> m_entries;
};

static bool compareSemanticNames(
      const std::string& a,
      const std::string& b) {
    if (a.size() != b.size())
      return false;
    for (size_t i = 0; i < a.size(); i++) {
      char ac = a[i];
      char bc = b[i];
      if (ac != bc) {
        if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A';
        if (bc >= 'A' && bc <= 'Z') bc += 'a' - 'A';
        if (ac != bc)
          return false;
      }
    }
    return true;
  }

class DxbcCompilerHsPhase {
  public:
    DxbcCompiler(
      const std::string&        fileName,
      const DxbcModuleInfo&     moduleInfo,
      const DxbcProgramInfo&    programInfo,
      const Rc<DxbcIsgn>&       isgn,
      const Rc<DxbcIsgn>&       osgn,
      const Rc<DxbcIsgn>&       psgn,
      const DxbcAnalysisInfo&   analysis) {
    // Declare an entry point ID. We'll need it during the
    // initialization phase where the execution mode is set.
    m_entryPointId = m_module.allocateId();

    // Set the shader name so that we recognize it in renderdoc
    m_module.setDebugSource(
      spv::SourceLanguageUnknown, 0,
      m_module.addDebugString(fileName.c_str()),
      nullptr);

    // Set the memory model. This is the same for all shaders.
    m_module.enableCapability(
      spv::CapabilityVulkanMemoryModel);

    m_module.setMemoryModel(
      spv::AddressingModelLogical,
      spv::MemoryModelVulkan);

    // Make sure our interface registers are clear
    for (uint32_t i = 0; i < DxbcMaxInterfaceRegs; i++) {
      m_vRegs.at(i) = DxbcRegisterPointer { };
      m_oRegs.at(i) = DxbcRegisterPointer { };
    }

    this->emitInit();
  }
    ~DxbcCompiler() {

  }
    void processInstruction(
      const DxbcShaderInstruction&  ins) {
    m_lastOp = m_currOp;
    m_currOp = ins.op;

    switch (ins.opClass) {
      case DxbcInstClass::Declaration:
        return this->emitDcl(ins);

      case DxbcInstClass::CustomData:
        return this->emitCustomData(ins);

      case DxbcInstClass::Atomic:
        return this->emitAtomic(ins);

      case DxbcInstClass::AtomicCounter:
        return this->emitAtomicCounter(ins);

      case DxbcInstClass::Barrier:
        return this->emitBarrier(ins);

      case DxbcInstClass::BitExtract:
        return this->emitBitExtract(ins);

      case DxbcInstClass::BitInsert:
        return this->emitBitInsert(ins);

      case DxbcInstClass::BitScan:
        return this->emitBitScan(ins);

      case DxbcInstClass::BufferQuery:
        return this->emitBufferQuery(ins);

      case DxbcInstClass::BufferLoad:
        return this->emitBufferLoad(ins);

      case DxbcInstClass::BufferStore:
        return this->emitBufferStore(ins);

      case DxbcInstClass::ConvertFloat16:
        return this->emitConvertFloat16(ins);

      case DxbcInstClass::ConvertFloat64:
        return this->emitConvertFloat64(ins);

      case DxbcInstClass::ControlFlow:
        return this->emitControlFlow(ins);

      case DxbcInstClass::GeometryEmit:
        return this->emitGeometryEmit(ins);

      case DxbcInstClass::HullShaderPhase:
        return this->emitHullShaderPhase(ins);

      case DxbcInstClass::HullShaderInstCnt:
        return this->emitHullShaderInstCnt(ins);

      case DxbcInstClass::Interpolate:
        return this->emitInterpolate(ins);

      case DxbcInstClass::NoOperation:
        return;

      case DxbcInstClass::SparseCheckAccess:
        return this->emitSparseCheckAccess(ins);

      case DxbcInstClass::TextureQuery:
        return this->emitTextureQuery(ins);

      case DxbcInstClass::TextureQueryLod:
        return this->emitTextureQueryLod(ins);

      case DxbcInstClass::TextureQueryMs:
        return this->emitTextureQueryMs(ins);

      case DxbcInstClass::TextureQueryMsPos:
        return this->emitTextureQueryMsPos(ins);

      case DxbcInstClass::TextureFetch:
        return this->emitTextureFetch(ins);

      case DxbcInstClass::TextureGather:
        return this->emitTextureGather(ins);

      case DxbcInstClass::TextureSample:
        return this->emitTextureSample(ins);

      case DxbcInstClass::TypedUavLoad:
        return this->emitTypedUavLoad(ins);

      case DxbcInstClass::TypedUavStore:
        return this->emitTypedUavStore(ins);

      case DxbcInstClass::VectorAlu:
        return this->emitVectorAlu(ins);

      case DxbcInstClass::VectorCmov:
        return this->emitVectorCmov(ins);

      case DxbcInstClass::VectorCmp:
        return this->emitVectorCmp(ins);

      case DxbcInstClass::VectorDeriv:
        return this->emitVectorDeriv(ins);

      case DxbcInstClass::VectorDot:
        return this->emitVectorDot(ins);

      case DxbcInstClass::VectorIdiv:
        return this->emitVectorIdiv(ins);

      case DxbcInstClass::VectorImul:
        return this->emitVectorImul(ins);

      case DxbcInstClass::VectorMsad:
        return this->emitVectorMsad(ins);

      case DxbcInstClass::VectorShift:
        return this->emitVectorShift(ins);

      case DxbcInstClass::VectorSinCos:
        return this->emitVectorSinCos(ins);

      default:
        Logger::warn(
          str::format("DxbcCompiler: Unhandled opcode class: ",
          ins.op));
    }
  }
    void processXfbPassthrough() {
    m_module.setExecutionMode (m_entryPointId, spv::ExecutionModeInputPoints);
    m_module.setExecutionMode (m_entryPointId, spv::ExecutionModeOutputPoints);
    m_module.setOutputVertices(m_entryPointId, 1);

    for (auto e = m_isgn->begin(); e != m_isgn->end(); e++) {
      emitDclInput(e->registerId, 1,
        e->componentMask, DxbcSystemValue::None,
        DxbcInterpolationMode::Undefined);
    }

    // Figure out which streams to enable
    uint32_t streamMask = 0;

    for (size_t i = 0; i < m_xfbVars.size(); i++)
      streamMask |= 1u << m_xfbVars[i].streamId;

    for (uint32_t streamId : bit::BitMask(streamMask)) {
      emitXfbOutputSetup(streamId, true);
      m_module.opEmitVertex(m_module.constu32(streamId));
    }

    // End the main function
    emitFunctionEnd();

    // For pass-through we always assume points
    m_inputTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
  }
    SpirvCodeBuffer finalize() {
    // Depending on the shader type, this will prepare
    // input registers, call various shader functions
    // and write back the output registers.
    switch (m_programInfo.type()) {
      case DxbcProgramType::VertexShader:   this->emitVsFinalize(); break;
      case DxbcProgramType::HullShader:     this->emitHsFinalize(); break;
      case DxbcProgramType::DomainShader:   this->emitDsFinalize(); break;
      case DxbcProgramType::GeometryShader: this->emitGsFinalize(); break;
      case DxbcProgramType::PixelShader:    this->emitPsFinalize(); break;
      case DxbcProgramType::ComputeShader:  this->emitCsFinalize(); break;
      default: throw DxvkError("Invalid shader stage");
    }

    // Emit float control mode if the extension is supported
    this->emitFloatControl();

    // Declare the entry point, we now have all the
    // information we need, including the interfaces
    m_module.addEntryPoint(m_entryPointId,
      m_programInfo.executionModel(), "main");
    m_module.setDebugName(m_entryPointId, "main");

    return m_module.compile();
  }
  private:
    DxbcModuleInfo      m_moduleInfo;
    DxbcProgramInfo     m_programInfo;
    SpirvModule         m_module;
    Rc<DxbcIsgn>        m_isgn;
    Rc<DxbcIsgn>        m_osgn;
    Rc<DxbcIsgn>        m_psgn;
    const DxbcAnalysisInfo* m_analysis;
    std::vector<DxvkBindingInfo> m_bindings;
    std::vector<uint32_t> m_rRegs;
    std::vector<DxbcXreg> m_xRegs;
    std::vector<DxbcGreg> m_gRegs;
    std::array<
      DxbcRegisterPointer,
      DxbcMaxInterfaceRegs>     m_vRegs;
    std::vector<DxbcSvMapping>  m_vMappings;
    std::array<
      DxbcRegisterPointer,
      DxbcMaxInterfaceRegs>     m_oRegs;
    std::vector<DxbcSvMapping>  m_oMappings;
    std::vector<DxbcXfbVar> m_xfbVars;
    std::vector<DxbcIndexRange> m_indexRanges = { };
    std::array<DxbcConstantBuffer,  16> m_constantBuffers;
    std::array<DxbcSampler,         16> m_samplers;
    std::array<DxbcShaderResource, 128> m_textures;
    std::array<DxbcUav,             64> m_uavs;
    bool m_hasGloballyCoherentUav = false;
    bool m_hasRasterizerOrderedUav = false;
    std::vector<DxbcCfgBlock> m_controlFlowBlocks;
    bool m_topLevelIsUniform = true;
    uint64_t m_uavRdMask = 0u;
    uint64_t m_uavWrMask = 0u;
    bool m_insideFunction = false;
    uint32_t m_vArrayLength   = 0;
    uint32_t m_vArrayLengthId = 0;
    uint32_t m_vArray = 0;
    uint32_t m_positionIn     = 0;
    uint32_t m_positionOut    = 0;
    uint32_t m_clipDistances  = 0;
    uint32_t m_cullDistances  = 0;
    uint32_t m_primitiveIdIn  = 0;
    uint32_t m_primitiveIdOut = 0;
    uint32_t          m_icbArray = 0;
    std::vector<uint32_t> m_icbData;
    uint32_t          m_icbComponents = 0u;
    uint32_t          m_icbSize = 0u;
    uint32_t m_samplePositions = 0;
    uint32_t m_uavCtrStructType  = 0;
    uint32_t m_uavCtrPointerType = 0;
    std::unordered_map<uint32_t, uint32_t> m_subroutines;
    uint32_t              m_entryPointId = 0;
    bool                  m_hasRawAccessChains = false;
    uint32_t m_inputMask = 0u;
    uint32_t m_outputMask = 0u;
    DxbcCompilerVsPart m_vs;
    DxbcCompilerHsPart m_hs;
    DxbcCompilerDsPart m_ds;
    DxbcCompilerGsPart m_gs;
    DxbcCompilerPsPart m_ps;
    DxbcCompilerCsPart m_cs;
    bool m_precise = true;
    DxbcOpcode m_lastOp = DxbcOpcode::Nop;
    DxbcOpcode m_currOp = DxbcOpcode::Nop;
    VkPrimitiveTopology m_inputTopology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
    VkPrimitiveTopology m_outputTopology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
    void emitDcl(
      const DxbcShaderInstruction&  ins) {
    switch (ins.op) {
      case DxbcOpcode::DclGlobalFlags:
        return this->emitDclGlobalFlags(ins);

      case DxbcOpcode::DclIndexRange:
        return this->emitDclIndexRange(ins);

      case DxbcOpcode::DclTemps:
        return this->emitDclTemps(ins);

      case DxbcOpcode::DclIndexableTemp:
        return this->emitDclIndexableTemp(ins);

      case DxbcOpcode::DclInput:
      case DxbcOpcode::DclInputSgv:
      case DxbcOpcode::DclInputSiv:
      case DxbcOpcode::DclInputPs:
      case DxbcOpcode::DclInputPsSgv:
      case DxbcOpcode::DclInputPsSiv:
      case DxbcOpcode::DclOutput:
      case DxbcOpcode::DclOutputSgv:
      case DxbcOpcode::DclOutputSiv:
        return this->emitDclInterfaceReg(ins);

      case DxbcOpcode::DclConstantBuffer:
        return this->emitDclConstantBuffer(ins);

      case DxbcOpcode::DclSampler:
        return this->emitDclSampler(ins);

      case DxbcOpcode::DclStream:
        return this->emitDclStream(ins);

      case DxbcOpcode::DclUavTyped:
      case DxbcOpcode::DclResource:
        return this->emitDclResourceTyped(ins);

      case DxbcOpcode::DclUavRaw:
      case DxbcOpcode::DclResourceRaw:
      case DxbcOpcode::DclUavStructured:
      case DxbcOpcode::DclResourceStructured:
        return this->emitDclResourceRawStructured(ins);

      case DxbcOpcode::DclThreadGroupSharedMemoryRaw:
      case DxbcOpcode::DclThreadGroupSharedMemoryStructured:
        return this->emitDclThreadGroupSharedMemory(ins);

      case DxbcOpcode::DclGsInputPrimitive:
        return this->emitDclGsInputPrimitive(ins);

      case DxbcOpcode::DclGsOutputPrimitiveTopology:
        return this->emitDclGsOutputTopology(ins);

      case DxbcOpcode::DclMaxOutputVertexCount:
        return this->emitDclMaxOutputVertexCount(ins);

      case DxbcOpcode::DclInputControlPointCount:
        return this->emitDclInputControlPointCount(ins);

      case DxbcOpcode::DclOutputControlPointCount:
        return this->emitDclOutputControlPointCount(ins);

      case DxbcOpcode::DclHsMaxTessFactor:
        return this->emitDclHsMaxTessFactor(ins);

      case DxbcOpcode::DclTessDomain:
        return this->emitDclTessDomain(ins);

      case DxbcOpcode::DclTessPartitioning:
        return this->emitDclTessPartitioning(ins);

      case DxbcOpcode::DclTessOutputPrimitive:
        return this->emitDclTessOutputPrimitive(ins);

      case DxbcOpcode::DclThreadGroup:
        return this->emitDclThreadGroup(ins);

      case DxbcOpcode::DclGsInstanceCount:
        return this->emitDclGsInstanceCount(ins);

      default:
        Logger::warn(
          str::format("DxbcCompiler: Unhandled opcode: ",
          ins.op));
    }
  }
    void emitDclGlobalFlags(
      const DxbcShaderInstruction&  ins) {
    const DxbcGlobalFlags flags = ins.controls.globalFlags();

    if (flags.test(DxbcGlobalFlag::RefactoringAllowed))
      m_precise = false;

    if (flags.test(DxbcGlobalFlag::EarlyFragmentTests))
      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeEarlyFragmentTests);
  }
    void emitDclIndexRange(
      const DxbcShaderInstruction&  ins) {
    // dcl_index_range has one operand:
    //    (0) Range start, either an input or output register
    //    (1) Range end
    uint32_t index = ins.dst[0].idxDim - 1u;

    DxbcIndexRange range = { };
    range.type = ins.dst[0].type;
    range.start = ins.dst[0].idx[index].offset;
    range.length = ins.imm[0].u32;

    m_indexRanges.push_back(range);
  }
    void emitDclTemps(
      const DxbcShaderInstruction&  ins) {
    // dcl_temps has one operand:
    //    (imm0) Number of temp registers

    // Ignore this and declare temps on demand.
  }
    void emitDclIndexableTemp(
      const DxbcShaderInstruction&  ins) {
    // dcl_indexable_temps has three operands:
    //    (imm0) Array register index (x#)
    //    (imm1) Number of vectors stored in the array
    //    (imm2) Component count of each individual vector. This is
    //    always 4 in fxc-generated binaries and therefore useless.
    const uint32_t regId = ins.imm[0].u32;

    DxbcRegisterInfo info;
    info.type.ctype   = DxbcScalarType::Float32;
    info.type.ccount  = m_analysis->xRegMasks.at(regId).minComponents();
    info.type.alength = ins.imm[1].u32;
    info.sclass       = spv::StorageClassPrivate;

    if (regId >= m_xRegs.size())
      m_xRegs.resize(regId + 1);

    m_xRegs.at(regId).ccount = info.type.ccount;
    m_xRegs.at(regId).alength = info.type.alength;
    m_xRegs.at(regId).varId  = emitNewVariable(info);

    m_module.setDebugName(m_xRegs.at(regId).varId,
      str::format("x", regId).c_str());
  }
    void emitDclInterfaceReg(
      const DxbcShaderInstruction&  ins) {
    switch (ins.dst[0].type) {
      case DxbcOperandType::InputControlPoint:
        if (m_programInfo.type() != DxbcProgramType::HullShader)
          break;
        [[fallthrough]];

      case DxbcOperandType::Input:
      case DxbcOperandType::Output: {
        // dcl_input and dcl_output instructions
        // have the following operands:
        //    (dst0) The register to declare
        //    (imm0) The system value (optional)
        uint32_t regDim = 0;
        uint32_t regIdx = 0;

        // In the vertex and fragment shader stage, the
        // operand indices will have the following format:
        //    (0) Register index
        //
        // In other stages, the input and output registers
        // may be declared as arrays of a fixed size:
        //    (0) Array length
        //    (1) Register index
        if (ins.dst[0].idxDim == 2) {
          regDim = ins.dst[0].idx[0].offset;
          regIdx = ins.dst[0].idx[1].offset;
        } else if (ins.dst[0].idxDim == 1) {
          regIdx = ins.dst[0].idx[0].offset;
        } else {
          Logger::err(str::format(
            "DxbcCompiler: ", ins.op,
            ": Invalid index dimension"));
          return;
        }

        // This declaration may map an output register to a system
        // value. If that is the case, the system value type will
        // be stored in the second operand.
        const bool hasSv =
            ins.op == DxbcOpcode::DclInputSgv
         || ins.op == DxbcOpcode::DclInputSiv
         || ins.op == DxbcOpcode::DclInputPsSgv
         || ins.op == DxbcOpcode::DclInputPsSiv
         || ins.op == DxbcOpcode::DclOutputSgv
         || ins.op == DxbcOpcode::DclOutputSiv;

        DxbcSystemValue sv = DxbcSystemValue::None;

        if (hasSv)
          sv = static_cast<DxbcSystemValue>(ins.imm[0].u32);

        // In the pixel shader, inputs are declared with an
        // interpolation mode that is part of the op token.
        const bool hasInterpolationMode =
            ins.op == DxbcOpcode::DclInputPs
         || ins.op == DxbcOpcode::DclInputPsSiv;

        DxbcInterpolationMode im = DxbcInterpolationMode::Undefined;

        if (hasInterpolationMode)
          im = ins.controls.interpolation();

        // Declare the actual input/output variable
        switch (ins.op) {
          case DxbcOpcode::DclInput:
          case DxbcOpcode::DclInputSgv:
          case DxbcOpcode::DclInputSiv:
          case DxbcOpcode::DclInputPs:
          case DxbcOpcode::DclInputPsSgv:
          case DxbcOpcode::DclInputPsSiv:
            this->emitDclInput(regIdx, regDim, ins.dst[0].mask, sv, im);
            break;

          case DxbcOpcode::DclOutput:
          case DxbcOpcode::DclOutputSgv:
          case DxbcOpcode::DclOutputSiv:
            this->emitDclOutput(regIdx, regDim, ins.dst[0].mask, sv, im);
            break;

          default:
            Logger::err(str::format(
              "DxbcCompiler: Unexpected opcode: ",
              ins.op));
        }
      } break;

      case DxbcOperandType::InputThreadId: {
        m_cs.builtinGlobalInvocationId = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 3, 0 },
          spv::StorageClassInput },
          spv::BuiltInGlobalInvocationId,
          "vThreadId");
      } break;

      case DxbcOperandType::InputThreadGroupId: {
        m_cs.builtinWorkgroupId = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 3, 0 },
          spv::StorageClassInput },
          spv::BuiltInWorkgroupId,
          "vThreadGroupId");
      } break;

      case DxbcOperandType::InputThreadIdInGroup: {
        m_cs.builtinLocalInvocationId = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 3, 0 },
          spv::StorageClassInput },
          spv::BuiltInLocalInvocationId,
          "vThreadIdInGroup");
      } break;

      case DxbcOperandType::InputThreadIndexInGroup: {
        m_cs.builtinLocalInvocationIndex = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 0 },
          spv::StorageClassInput },
          spv::BuiltInLocalInvocationIndex,
          "vThreadIndexInGroup");
      } break;

      case DxbcOperandType::InputCoverageMask: {
        m_ps.builtinSampleMaskIn = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 1 },
          spv::StorageClassInput },
          spv::BuiltInSampleMask,
          "vCoverage");
      } break;

      case DxbcOperandType::OutputCoverageMask: {
        m_ps.builtinSampleMaskOut = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 1 },
          spv::StorageClassOutput },
          spv::BuiltInSampleMask,
          "oMask");
      } break;

      case DxbcOperandType::OutputDepth: {
        m_module.setExecutionMode(m_entryPointId,
          spv::ExecutionModeDepthReplacing);
        m_ps.builtinDepth = emitNewBuiltinVariable({
          { DxbcScalarType::Float32, 1, 0 },
          spv::StorageClassOutput },
          spv::BuiltInFragDepth,
          "oDepth");
      } break;

      case DxbcOperandType::OutputStencilRef: {
        m_module.enableExtension("SPV_EXT_shader_stencil_export");
        m_module.enableCapability(spv::CapabilityStencilExportEXT);
        m_module.setExecutionMode(m_entryPointId,
          spv::ExecutionModeStencilRefReplacingEXT);
        m_ps.builtinStencilRef = emitNewBuiltinVariable({
          { DxbcScalarType::Sint32, 1, 0 },
          spv::StorageClassOutput },
          spv::BuiltInFragStencilRefEXT,
          "oStencilRef");
      } break;

      case DxbcOperandType::OutputDepthGe: {
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthReplacing);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthGreater);
        m_ps.builtinDepth = emitNewBuiltinVariable({
          { DxbcScalarType::Float32, 1, 0 },
          spv::StorageClassOutput },
          spv::BuiltInFragDepth,
          "oDepthGe");
      } break;

      case DxbcOperandType::OutputDepthLe: {
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthReplacing);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthLess);
        m_ps.builtinDepth = emitNewBuiltinVariable({
          { DxbcScalarType::Float32, 1, 0 },
          spv::StorageClassOutput },
          spv::BuiltInFragDepth,
          "oDepthLe");
      } break;

      case DxbcOperandType::InputPrimitiveId: {
        m_primitiveIdIn = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 0 },
          spv::StorageClassInput },
          spv::BuiltInPrimitiveId,
          "vPrim");
      } break;

      case DxbcOperandType::InputDomainPoint: {
        m_ds.builtinTessCoord = emitNewBuiltinVariable({
          { DxbcScalarType::Float32, 3, 0 },
          spv::StorageClassInput },
          spv::BuiltInTessCoord,
          "vDomain");
      } break;

      case DxbcOperandType::InputForkInstanceId:
      case DxbcOperandType::InputJoinInstanceId: {
        auto phase = this->getCurrentHsForkJoinPhase();

        phase->instanceIdPtr = m_module.newVar(
          m_module.defPointerType(
            m_module.defIntType(32, 0),
            spv::StorageClassFunction),
          spv::StorageClassFunction);

        m_module.opStore(phase->instanceIdPtr, phase->instanceId);
        m_module.setDebugName(phase->instanceIdPtr,
          ins.dst[0].type == DxbcOperandType::InputForkInstanceId
            ? "vForkInstanceId" : "vJoinInstanceId");
      } break;

      case DxbcOperandType::OutputControlPointId: {
        // This system value map to the invocation
        // ID, which has been declared already.
      } break;

      case DxbcOperandType::InputPatchConstant:
      case DxbcOperandType::OutputControlPoint: {
        // These have been declared as global input and
        // output arrays, so there's nothing left to do.
      } break;

      case DxbcOperandType::InputGsInstanceId: {
        m_gs.builtinInvocationId = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 0 },
          spv::StorageClassInput },
          spv::BuiltInInvocationId,
          "vInstanceID");
      } break;

      case DxbcOperandType::InputInnerCoverage: {
        m_module.enableExtension("SPV_EXT_fragment_fully_covered");
        m_module.enableCapability(spv::CapabilityFragmentFullyCoveredEXT);

        // This is bool in SPIR-V but uint32 in DXBC. A bool value of
        // false must be 0, and bit 1 must be set to represent true.
        uint32_t builtinId = emitNewBuiltinVariable({
          { DxbcScalarType::Bool, 1, 0 },
          spv::StorageClassInput },
          spv::BuiltInFullyCoveredEXT,
          nullptr);

        m_ps.builtinInnerCoverageId = emitNewVariable({
          { DxbcScalarType::Uint32, 1, 0 },
          spv::StorageClassPrivate });

        m_module.setDebugName(m_ps.builtinInnerCoverageId, "vInnerCoverage");

        uint32_t boolTypeId = m_module.defBoolType();
        uint32_t uintTypeId = m_module.defIntType(32, 0);

        m_module.opStore(m_ps.builtinInnerCoverageId,
          m_module.opSelect(uintTypeId,
            m_module.opLoad(boolTypeId, builtinId),
            m_module.constu32(1),
            m_module.constu32(0)));
      } break;

      default:
        Logger::err(str::format(
          "DxbcCompiler: Unsupported operand type declaration: ",
          ins.dst[0].type));

    }
  }
    void emitDclInput(
            uint32_t                regIdx,
            uint32_t                regDim,
            DxbcRegMask             regMask,
            DxbcSystemValue         sv,
            DxbcInterpolationMode   im) {
    // Avoid declaring the same variable multiple times.
    // This may happen when multiple system values are
    // mapped to different parts of the same register.
    if (m_vRegs.at(regIdx).id == 0 && sv == DxbcSystemValue::None) {
      const DxbcVectorType regType = getInputRegType(regIdx);

      DxbcRegisterInfo info;
      info.type.ctype   = regType.ctype;
      info.type.ccount  = regType.ccount;
      info.type.alength = regDim;
      info.sclass = spv::StorageClassInput;

      const uint32_t varId = emitNewVariable(info);

      m_module.decorateLocation(varId, regIdx);
      m_module.setDebugName(varId, str::format("v", regIdx).c_str());

      m_vRegs.at(regIdx) = { regType, varId };

      // Interpolation mode, used in pixel shaders
      if (im == DxbcInterpolationMode::Constant)
        m_module.decorate(varId, spv::DecorationFlat);

      if (im == DxbcInterpolationMode::LinearCentroid
       || im == DxbcInterpolationMode::LinearNoPerspectiveCentroid)
        m_module.decorate(varId, spv::DecorationCentroid);

      if (im == DxbcInterpolationMode::LinearNoPerspective
       || im == DxbcInterpolationMode::LinearNoPerspectiveCentroid
       || im == DxbcInterpolationMode::LinearNoPerspectiveSample)
        m_module.decorate(varId, spv::DecorationNoPerspective);

      if (im == DxbcInterpolationMode::LinearSample
       || im == DxbcInterpolationMode::LinearNoPerspectiveSample) {
        m_module.enableCapability(spv::CapabilitySampleRateShading);
        m_module.decorate(varId, spv::DecorationSample);
      }

      if (m_moduleInfo.options.forceSampleRateShading) {
        if (im == DxbcInterpolationMode::Linear
         || im == DxbcInterpolationMode::LinearNoPerspective) {
          m_module.enableCapability(spv::CapabilitySampleRateShading);
          m_module.decorate(varId, spv::DecorationSample);
        }
      }

      // Declare the input slot as defined
      m_inputMask |= 1u << regIdx;
      m_vArrayLength = std::max(m_vArrayLength, regIdx + 1);
    } else if (sv != DxbcSystemValue::None) {
      // Add a new system value mapping if needed
      bool skipSv = sv == DxbcSystemValue::ClipDistance
                 || sv == DxbcSystemValue::CullDistance;

      if (!skipSv)
        m_vMappings.push_back({ regIdx, regMask, sv });
    }
  }
    void emitDclOutput(
            uint32_t                regIdx,
            uint32_t                regDim,
            DxbcRegMask             regMask,
            DxbcSystemValue         sv,
            DxbcInterpolationMode   im) {
    // Add a new system value mapping if needed. Clip
    // and cull distances are handled separately.
    if (sv != DxbcSystemValue::None
     && sv != DxbcSystemValue::ClipDistance
     && sv != DxbcSystemValue::CullDistance)
      m_oMappings.push_back({ regIdx, regMask, sv });

    if (m_programInfo.type() == DxbcProgramType::HullShader) {
      // Hull shaders don't use standard outputs
      if (getCurrentHsForkJoinPhase() != nullptr)
        m_hs.outputPerPatchMask |= 1 << regIdx;
    } else if (m_oRegs.at(regIdx).id == 0) {
      // Avoid declaring the same variable multiple times.
      // This may happen when multiple system values are
      // mapped to different parts of the same register.
      const DxbcVectorType regType = getOutputRegType(regIdx);

      DxbcRegisterInfo info;
      info.type.ctype   = regType.ctype;
      info.type.ccount  = regType.ccount;
      info.type.alength = regDim;
      info.sclass = spv::StorageClassOutput;

      // In xfb mode, we set up the actual
      // output vars when emitting a vertex
      if (m_moduleInfo.xfb != nullptr)
        info.sclass = spv::StorageClassPrivate;

      // In geometry shaders, don't duplicate system value outputs
      // to stay within device limits. The pixel shader will read
      // all GS system value outputs as system value inputs.
      if (m_programInfo.type() == DxbcProgramType::GeometryShader && sv != DxbcSystemValue::None)
        info.sclass = spv::StorageClassPrivate;

      const uint32_t varId = this->emitNewVariable(info);
      m_module.setDebugName(varId, str::format("o", regIdx).c_str());

      if (info.sclass == spv::StorageClassOutput) {
        m_module.decorateLocation(varId, regIdx);

        // Add index decoration for potential dual-source blending
        if (m_programInfo.type() == DxbcProgramType::PixelShader)
          m_module.decorateIndex(varId, 0);

        // Declare vertex positions in all stages as invariant, even if
        // this is not the last stage, to help with potential Z fighting.
        if (sv == DxbcSystemValue::Position && m_moduleInfo.options.invariantPosition)
          m_module.decorate(varId, spv::DecorationInvariant);
      }

      m_oRegs.at(regIdx) = { regType, varId };

      // Declare the output slot as defined
      m_outputMask |= 1u << regIdx;
    }
  }
    void emitDclConstantBuffer(
      const DxbcShaderInstruction&  ins) {
    // dcl_constant_buffer has one operand with two indices:
    //    (0) Constant buffer register ID (cb#)
    //    (1) Number of constants in the buffer
    uint32_t bufferId     = ins.dst[0].idx[0].offset;
    uint32_t elementCount = ins.dst[0].idx[1].offset;

    // With dynamic indexing, games will often index constant buffers
    // out of bounds. Declare an upper bound to stay within spec.
    if (ins.controls.accessType() == DxbcConstantBufferAccessType::DynamicallyIndexed)
      elementCount = 4096;

    this->emitDclConstantBufferVar(bufferId, elementCount, 4u,
      str::format("cb", bufferId).c_str());
  }
    void emitDclConstantBufferVar(
            uint32_t                regIdx,
            uint32_t                numConstants,
            uint32_t                numComponents,
      const char*                   name) {
    // Uniform buffer data is stored as a fixed-size array
    // of 4x32-bit vectors. SPIR-V requires explicit strides.
    const uint32_t arrayType = m_module.defArrayTypeUnique(
      getVectorTypeId({ DxbcScalarType::Float32, numComponents }),
      m_module.constu32(numConstants));
    m_module.decorateArrayStride(arrayType, sizeof(uint32_t) * numComponents);

    // SPIR-V requires us to put that array into a
    // struct and decorate that struct as a block.
    const uint32_t structType = m_module.defStructTypeUnique(1, &arrayType);

    m_module.decorate(structType, spv::DecorationBlock);
    m_module.memberDecorateOffset(structType, 0, 0);

    m_module.setDebugName        (structType, str::format(name, "_t").c_str());
    m_module.setDebugMemberName  (structType, 0, "m");

    // Variable that we'll use to access the buffer
    const uint32_t varId = m_module.newVar(
      m_module.defPointerType(structType, spv::StorageClassUniform),
      spv::StorageClassUniform);

    m_module.setDebugName(varId, name);

    // Compute the DXVK binding slot index for the buffer.
    // D3D11 needs to bind the actual buffers to this slot.
    uint32_t bindingId = computeConstantBufferBinding(
      m_programInfo.type(), regIdx);

    m_module.decorateDescriptorSet(varId, 0);
    m_module.decorateBinding(varId, bindingId);

    DxbcConstantBuffer buf;
    buf.varId  = varId;
    buf.size   = numConstants;
    m_constantBuffers.at(regIdx) = buf;

    // Store descriptor info for the shader interface
    DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER };
    binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
    binding.access = VK_ACCESS_UNIFORM_READ_BIT;
    binding.resourceBinding = bindingId;
    binding.uboSet = true;
    m_bindings.push_back(binding);
  }
    void emitDclSampler(
      const DxbcShaderInstruction&  ins) {
    // dclSampler takes one operand:
    //    (dst0) The sampler register to declare
    const uint32_t samplerId = ins.dst[0].idx[0].offset;

    // The sampler type is opaque, but we still have to
    // define a pointer and a variable in oder to use it
    const uint32_t samplerType = m_module.defSamplerType();
    const uint32_t samplerPtrType = m_module.defPointerType(
      samplerType, spv::StorageClassUniformConstant);

    // Define the sampler variable
    const uint32_t varId = m_module.newVar(samplerPtrType,
      spv::StorageClassUniformConstant);
    m_module.setDebugName(varId,
      str::format("s", samplerId).c_str());

    m_samplers.at(samplerId).varId  = varId;
    m_samplers.at(samplerId).typeId = samplerType;

    // Compute binding slot index for the sampler
    uint32_t bindingId = computeSamplerBinding(
      m_programInfo.type(), samplerId);

    m_module.decorateDescriptorSet(varId, 0);
    m_module.decorateBinding(varId, bindingId);

    // Store descriptor info for the shader interface
    DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_SAMPLER };
    binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
    binding.resourceBinding = bindingId;
    m_bindings.push_back(binding);
  }
    void emitDclStream(
      const DxbcShaderInstruction&  ins) {
    if (ins.dst[0].idx[0].offset != 0 && m_moduleInfo.xfb == nullptr)
      Logger::err("Dxbc: Multiple streams not supported");
  }
    void emitDclResourceTyped(
      const DxbcShaderInstruction&  ins) {
    // dclResource takes two operands:
    //    (dst0) The resource register ID
    //    (imm0) The resource return type
    const uint32_t registerId = ins.dst[0].idx[0].offset;

    // We also handle unordered access views here
    const bool isUav = ins.op == DxbcOpcode::DclUavTyped;

    if (isUav) {
      if (m_moduleInfo.options.supportsTypedUavLoadR32)
        m_module.enableCapability(spv::CapabilityStorageImageReadWithoutFormat);
      m_module.enableCapability(spv::CapabilityStorageImageWriteWithoutFormat);
    }

    // Defines the type of the resource (texture2D, ...)
    const DxbcResourceDim resourceType = ins.controls.resourceDim();

    // Defines the type of a read operation. DXBC has the ability
    // to define four different types whereas SPIR-V only allows
    // one, but in practice this should not be much of a problem.
    auto xType = static_cast<DxbcResourceReturnType>(
      bit::extract(ins.imm[0].u32, 0, 3));
    auto yType = static_cast<DxbcResourceReturnType>(
      bit::extract(ins.imm[0].u32, 4, 7));
    auto zType = static_cast<DxbcResourceReturnType>(
      bit::extract(ins.imm[0].u32, 8, 11));
    auto wType = static_cast<DxbcResourceReturnType>(
      bit::extract(ins.imm[0].u32, 12, 15));

    if ((xType != yType) || (xType != zType) || (xType != wType))
      Logger::warn("DxbcCompiler: dcl_resource: Ignoring resource return types");

    // Declare the actual sampled type
    const DxbcScalarType sampledType = [xType] {
      switch (xType) {
        // FIXME is this correct? There's no documentation about it
        case DxbcResourceReturnType::Mixed: return DxbcScalarType::Uint32;
        // FIXME do we have to manually clamp writes to SNORM/UNORM resources?
        case DxbcResourceReturnType::Snorm: return DxbcScalarType::Float32;
        case DxbcResourceReturnType::Unorm: return DxbcScalarType::Float32;
        case DxbcResourceReturnType::Float: return DxbcScalarType::Float32;
        case DxbcResourceReturnType::Sint:  return DxbcScalarType::Sint32;
        case DxbcResourceReturnType::Uint:  return DxbcScalarType::Uint32;
        default: throw DxvkError(str::format("DxbcCompiler: Invalid sampled type: ", xType));
      }
    }();

    // Declare the resource type
    const uint32_t sampledTypeId = getScalarTypeId(sampledType);
    const DxbcImageInfo typeInfo = getResourceType(resourceType, isUav);

    // Declare additional capabilities if necessary
    switch (resourceType) {
      case DxbcResourceDim::Buffer:
        m_module.enableCapability(isUav
          ? spv::CapabilityImageBuffer
          : spv::CapabilitySampledBuffer);
        break;

      case DxbcResourceDim::Texture1D:
      case DxbcResourceDim::Texture1DArr:
        m_module.enableCapability(isUav
          ? spv::CapabilityImage1D
          : spv::CapabilitySampled1D);
        break;

      case DxbcResourceDim::TextureCubeArr:
        m_module.enableCapability(
          spv::CapabilitySampledCubeArray);
        break;

      default:
        // No additional capabilities required
        break;
    }

    // If the read-without-format capability is not set and this
    // image is access via a typed load, or if atomic operations
    // are used,, we must define the image format explicitly.
    spv::ImageFormat imageFormat = spv::ImageFormatUnknown;

    if (isUav) {
      if ((m_analysis->uavInfos[registerId].accessAtomicOp)
       || (m_analysis->uavInfos[registerId].accessTypedLoad
        && !m_moduleInfo.options.supportsTypedUavLoadR32))
        imageFormat = getScalarImageFormat(sampledType);
    }

    // We do not know whether the image is going to be used as
    // a color image or a depth image yet, but we can pick the
    // correct type when creating a sampled image object.
    const uint32_t imageTypeId = m_module.defImageType(sampledTypeId,
      typeInfo.dim, 0, typeInfo.array, typeInfo.ms, typeInfo.sampled,
      imageFormat);

    // We'll declare the texture variable with the color type
    // and decide which one to use when the texture is sampled.
    const uint32_t resourcePtrType = m_module.defPointerType(
      imageTypeId, spv::StorageClassUniformConstant);

    const uint32_t varId = m_module.newVar(resourcePtrType,
      spv::StorageClassUniformConstant);

    m_module.setDebugName(varId,
      str::format(isUav ? "u" : "t", registerId).c_str());

    // Compute the DXVK binding slot index for the resource.
    // D3D11 needs to bind the actual resource to this slot.
    uint32_t bindingId = isUav
      ? computeUavBinding(m_programInfo.type(), registerId)
      : computeSrvBinding(m_programInfo.type(), registerId);

    m_module.decorateDescriptorSet(varId, 0);
    m_module.decorateBinding(varId, bindingId);

    // Declare a specialization constant which will
    // store whether or not the resource is bound.
    if (isUav) {
      DxbcUav uav;
      uav.type          = DxbcResourceType::Typed;
      uav.imageInfo     = typeInfo;
      uav.varId         = varId;
      uav.ctrId         = 0;
      uav.sampledType   = sampledType;
      uav.sampledTypeId = sampledTypeId;
      uav.imageTypeId   = imageTypeId;
      uav.structStride  = 0;
      uav.coherence     = getUavCoherence(registerId, ins.controls.uavFlags());
      uav.isRawSsbo     = false;
      m_uavs.at(registerId) = uav;
    } else {
      DxbcShaderResource res;
      res.type          = DxbcResourceType::Typed;
      res.imageInfo     = typeInfo;
      res.varId         = varId;
      res.sampledType   = sampledType;
      res.sampledTypeId = sampledTypeId;
      res.imageTypeId   = imageTypeId;
      res.colorTypeId   = imageTypeId;
      res.depthTypeId   = 0;
      res.structStride  = 0;
      res.isRawSsbo     = false;

      if ((sampledType == DxbcScalarType::Float32)
       && (resourceType == DxbcResourceDim::Texture1D
        || resourceType == DxbcResourceDim::Texture1DArr
        || resourceType == DxbcResourceDim::Texture2D
        || resourceType == DxbcResourceDim::Texture2DArr
        || resourceType == DxbcResourceDim::TextureCube
        || resourceType == DxbcResourceDim::TextureCubeArr)) {
        res.depthTypeId = m_module.defImageType(sampledTypeId,
          typeInfo.dim, 1, typeInfo.array, typeInfo.ms, typeInfo.sampled,
          spv::ImageFormatUnknown);
      }

      m_textures.at(registerId) = res;
    }

    // Store descriptor info for the shader interface
    DxvkBindingInfo binding = { };
    binding.viewType = typeInfo.vtype;
    binding.resourceBinding = bindingId;
    binding.isMultisampled = typeInfo.ms;

    if (isUav) {
      binding.descriptorType = resourceType == DxbcResourceDim::Buffer
        ? VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
        : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
      binding.access = m_analysis->uavInfos[registerId].accessFlags;

      if (!m_analysis->uavInfos[registerId].nonInvariantAccess)
        binding.accessOp = m_analysis->uavInfos[registerId].accessOp;

      if (!(binding.access & VK_ACCESS_SHADER_WRITE_BIT))
        m_module.decorate(varId, spv::DecorationNonWritable);
      if (!(binding.access & VK_ACCESS_SHADER_READ_BIT))
        m_module.decorate(varId, spv::DecorationNonReadable);
    } else {
      binding.descriptorType = resourceType == DxbcResourceDim::Buffer
        ? VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
        : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
      binding.access = VK_ACCESS_SHADER_READ_BIT;
    }

    m_bindings.push_back(binding);
  }
    void emitDclResourceRawStructured(
      const DxbcShaderInstruction&  ins) {
    // dcl_resource_raw and dcl_uav_raw take one argument:
    //    (dst0) The resource register ID
    // dcl_resource_structured and dcl_uav_structured take two arguments:
    //    (dst0) The resource register ID
    //    (imm0) Structure stride, in bytes
    const uint32_t registerId = ins.dst[0].idx[0].offset;

    const bool isUav = ins.op == DxbcOpcode::DclUavRaw
                    || ins.op == DxbcOpcode::DclUavStructured;

    const bool isStructured = ins.op == DxbcOpcode::DclUavStructured
                           || ins.op == DxbcOpcode::DclResourceStructured;

    const DxbcScalarType sampledType = DxbcScalarType::Uint32;
    const uint32_t sampledTypeId = getScalarTypeId(sampledType);

    const DxbcImageInfo typeInfo = { spv::DimBuffer, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_MAX_ENUM };

    // Declare the resource type
    uint32_t resTypeId = 0;
    uint32_t varId     = 0;

    // Write back resource info
    DxbcResourceType resType = isStructured
      ? DxbcResourceType::Structured
      : DxbcResourceType::Raw;

    uint32_t resStride = isStructured
      ? ins.imm[0].u32
      : 0;

    uint32_t resAlign = isStructured
      ? (resStride & -resStride)
      : 16;

    // Compute the DXVK binding slot index for the resource.
    uint32_t bindingId = isUav
      ? computeUavBinding(m_programInfo.type(), registerId)
      : computeSrvBinding(m_programInfo.type(), registerId);

    // Test whether we should use a raw SSBO for this resource
    bool hasSparseFeedback = isUav
      ? m_analysis->uavInfos[registerId].sparseFeedback
      : m_analysis->srvInfos[registerId].sparseFeedback;

    bool useRawSsbo = m_moduleInfo.options.minSsboAlignment <= resAlign && !hasSparseFeedback;

    if (useRawSsbo) {
      uint32_t elemType   = getScalarTypeId(DxbcScalarType::Uint32);
      uint32_t arrayType  = m_module.defRuntimeArrayTypeUnique(elemType);
      uint32_t structType = m_module.defStructTypeUnique(1, &arrayType);
      uint32_t ptrType    = m_module.defPointerType(structType, spv::StorageClassStorageBuffer);

      resTypeId = m_module.defPointerType(elemType, spv::StorageClassStorageBuffer);
      varId     = m_module.newVar(ptrType, spv::StorageClassStorageBuffer);

      m_module.decorateArrayStride(arrayType, sizeof(uint32_t));
      m_module.decorate(structType, spv::DecorationBlock);
      m_module.memberDecorateOffset(structType, 0, 0);

      m_module.setDebugName(structType,
        str::format(isUav ? "u" : "t", registerId, "_t").c_str());
      m_module.setDebugMemberName(structType, 0, "m");
    } else {
      // Structured and raw buffers are represented as
      // texel buffers consisting of 32-bit integers.
      m_module.enableCapability(isUav
        ? spv::CapabilityImageBuffer
        : spv::CapabilitySampledBuffer);

      resTypeId = m_module.defImageType(sampledTypeId,
        typeInfo.dim, 0, typeInfo.array, typeInfo.ms, typeInfo.sampled,
        spv::ImageFormatR32ui);

      varId = m_module.newVar(
        m_module.defPointerType(resTypeId, spv::StorageClassUniformConstant),
        spv::StorageClassUniformConstant);
    }

    m_module.setDebugName(varId,
      str::format(isUav ? "u" : "t", registerId).c_str());

    m_module.decorateDescriptorSet(varId, 0);
    m_module.decorateBinding(varId, bindingId);

    if (isUav) {
      DxbcUav uav;
      uav.type          = resType;
      uav.imageInfo     = typeInfo;
      uav.varId         = varId;
      uav.ctrId         = 0;
      uav.sampledType   = sampledType;
      uav.sampledTypeId = sampledTypeId;
      uav.imageTypeId   = resTypeId;
      uav.structStride  = resStride;
      uav.coherence     = getUavCoherence(registerId, ins.controls.uavFlags());
      uav.isRawSsbo     = useRawSsbo;
      m_uavs.at(registerId) = uav;
    } else {
      DxbcShaderResource res;
      res.type          = resType;
      res.imageInfo     = typeInfo;
      res.varId         = varId;
      res.sampledType   = sampledType;
      res.sampledTypeId = sampledTypeId;
      res.imageTypeId   = resTypeId;
      res.colorTypeId   = resTypeId;
      res.depthTypeId   = 0;
      res.structStride  = resStride;
      res.isRawSsbo     = useRawSsbo;
      m_textures.at(registerId) = res;
    }

    // Store descriptor info for the shader interface
    DxvkBindingInfo binding = { };
    binding.descriptorType = useRawSsbo
      ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
      : (isUav ? VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER);
    binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
    binding.resourceBinding = bindingId;
    binding.access = VK_ACCESS_SHADER_READ_BIT;

    if (isUav) {
      binding.access = m_analysis->uavInfos[registerId].accessFlags;

      if (!m_analysis->uavInfos[registerId].nonInvariantAccess)
        binding.accessOp = m_analysis->uavInfos[registerId].accessOp;
    }

    if (useRawSsbo || isUav) {
      if (!(binding.access & VK_ACCESS_SHADER_WRITE_BIT))
        m_module.decorate(varId, spv::DecorationNonWritable);
      if (!(binding.access & VK_ACCESS_SHADER_READ_BIT))
        m_module.decorate(varId, spv::DecorationNonReadable);
    }

    m_bindings.push_back(binding);

    // If supported, we'll be using raw access chains to access this
    if (!m_hasRawAccessChains && m_moduleInfo.options.supportsRawAccessChains) {
      m_module.enableExtension("SPV_NV_raw_access_chains");
      m_module.enableCapability(spv::CapabilityRawAccessChainsNV);

      m_hasRawAccessChains = true;
    }
  }
    void emitDclThreadGroupSharedMemory(
      const DxbcShaderInstruction&  ins) {
    // dcl_tgsm_raw takes two arguments:
    //    (dst0) The resource register ID
    //    (imm0) Block size, in bytes
    // dcl_tgsm_structured takes three arguments:
    //    (dst0) The resource register ID
    //    (imm0) Structure stride, in bytes
    //    (imm1) Structure count
    const bool isStructured = ins.op == DxbcOpcode::DclThreadGroupSharedMemoryStructured;

    const uint32_t regId = ins.dst[0].idx[0].offset;

    if (regId >= m_gRegs.size())
      m_gRegs.resize(regId + 1);

    const uint32_t elementStride = isStructured ? ins.imm[0].u32 : 0;
    const uint32_t elementCount  = isStructured ? ins.imm[1].u32 : ins.imm[0].u32;

    DxbcRegisterInfo varInfo;
    varInfo.type.ctype   = DxbcScalarType::Uint32;
    varInfo.type.ccount  = 1;
    varInfo.type.alength = isStructured
      ? elementCount * elementStride / 4
      : elementCount / 4;
    varInfo.sclass = spv::StorageClassWorkgroup;

    m_gRegs[regId].type = isStructured
      ? DxbcResourceType::Structured
      : DxbcResourceType::Raw;
    m_gRegs[regId].elementStride = elementStride;
    m_gRegs[regId].elementCount  = elementCount;
    m_gRegs[regId].varId = emitNewVariable(varInfo);

    m_module.setDebugName(m_gRegs[regId].varId,
      str::format("g", regId).c_str());
  }
    void emitDclGsInputPrimitive(
      const DxbcShaderInstruction&  ins) {
    // The input primitive type is stored within in the
    // control bits of the opcode token. In SPIR-V, we
    // have to define an execution mode.
    const auto mode = [&] {
      switch (ins.controls.primitive()) {
        case DxbcPrimitive::Point:       return std::make_pair(VK_PRIMITIVE_TOPOLOGY_POINT_LIST,                   spv::ExecutionModeInputPoints);
        case DxbcPrimitive::Line:        return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST,                    spv::ExecutionModeInputLines);
        case DxbcPrimitive::Triangle:    return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,                spv::ExecutionModeTriangles);
        case DxbcPrimitive::LineAdj:     return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,     spv::ExecutionModeInputLinesAdjacency);
        case DxbcPrimitive::TriangleAdj: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, spv::ExecutionModeInputTrianglesAdjacency);
        default: throw DxvkError("DxbcCompiler: Unsupported primitive type");
      }
    }();

    m_gs.inputPrimitive = ins.controls.primitive();
    m_module.setExecutionMode(m_entryPointId, mode.second);
    m_inputTopology = mode.first;

    emitDclInputArray(primitiveVertexCount(m_gs.inputPrimitive));
  }
    void emitDclGsOutputTopology(
      const DxbcShaderInstruction&  ins) {
    // The input primitive topology is stored within in the
    // control bits of the opcode token. In SPIR-V, we have
    // to define an execution mode.
    auto mode = [&] {
      switch (ins.controls.primitiveTopology()) {
        case DxbcPrimitiveTopology::PointList:     return std::make_pair(VK_PRIMITIVE_TOPOLOGY_POINT_LIST,    spv::ExecutionModeOutputPoints);
        case DxbcPrimitiveTopology::LineStrip:     return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST,     spv::ExecutionModeOutputLineStrip);
        case DxbcPrimitiveTopology::TriangleStrip: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeOutputTriangleStrip);
        default: throw DxvkError("DxbcCompiler: Unsupported primitive topology");
      }
    }();

    m_outputTopology = mode.first;
    m_module.setExecutionMode(m_entryPointId, mode.second);
  }
    void emitDclMaxOutputVertexCount(
      const DxbcShaderInstruction&  ins) {
    // dcl_max_output_vertex_count has one operand:
    //    (imm0) The maximum number of vertices
    m_gs.outputVertexCount = ins.imm[0].u32;

    m_module.setOutputVertices(m_entryPointId, m_gs.outputVertexCount);
  }
    void emitDclInputControlPointCount(
      const DxbcShaderInstruction&  ins) {
    // dcl_input_control_points has the control point
    // count embedded within the opcode token.
    if (m_programInfo.type() == DxbcProgramType::HullShader) {
      m_hs.vertexCountIn = ins.controls.controlPointCount();

      emitDclInputArray(m_hs.vertexCountIn);
    } else {
      m_ds.vertexCountIn = ins.controls.controlPointCount();

      m_ds.inputPerPatch  = emitTessInterfacePerPatch (spv::StorageClassInput);
      m_ds.inputPerVertex = emitTessInterfacePerVertex(spv::StorageClassInput, m_ds.vertexCountIn);
    }
  }
    void emitDclOutputControlPointCount(
      const DxbcShaderInstruction&  ins) {
    // dcl_output_control_points has the control point
    // count embedded within the opcode token.
    m_hs.vertexCountOut = ins.controls.controlPointCount();

    m_hs.outputPerPatch  = emitTessInterfacePerPatch(spv::StorageClassPrivate);
    m_hs.outputPerVertex = emitTessInterfacePerVertex(spv::StorageClassOutput, m_hs.vertexCountOut);

    m_module.setOutputVertices(m_entryPointId, m_hs.vertexCountOut);
  }
    void emitDclHsMaxTessFactor(
      const DxbcShaderInstruction&  ins) {
    m_hs.maxTessFactor = ins.imm[0].f32;
  }
    void emitDclTessDomain(
      const DxbcShaderInstruction&  ins) {
    auto mode = [&] {
      switch (ins.controls.tessDomain()) {
        case DxbcTessDomain::Isolines:  return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST,     spv::ExecutionModeIsolines);
        case DxbcTessDomain::Triangles: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeTriangles);
        case DxbcTessDomain::Quads:     return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeQuads);
        default: throw DxvkError("Dxbc: Invalid tess domain");
      }
    }();

    m_outputTopology = mode.first;
    m_module.setExecutionMode(m_entryPointId, mode.second);
  }
    void emitDclTessPartitioning(
      const DxbcShaderInstruction&  ins) {
    const spv::ExecutionMode executionMode = [&] {
      switch (ins.controls.tessPartitioning()) {
        case DxbcTessPartitioning::Pow2:
        case DxbcTessPartitioning::Integer:   return spv::ExecutionModeSpacingEqual;
        case DxbcTessPartitioning::FractOdd:  return spv::ExecutionModeSpacingFractionalOdd;
        case DxbcTessPartitioning::FractEven: return spv::ExecutionModeSpacingFractionalEven;
        default: throw DxvkError("Dxbc: Invalid tess partitioning");
      }
    }();

    m_module.setExecutionMode(m_entryPointId, executionMode);
  }
    void emitDclTessOutputPrimitive(
      const DxbcShaderInstruction&  ins) {
    switch (ins.controls.tessOutputPrimitive()) {
      case DxbcTessOutputPrimitive::Point:
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModePointMode);
        break;

      case DxbcTessOutputPrimitive::Line:
        break;

      case DxbcTessOutputPrimitive::TriangleCw:
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeVertexOrderCw);
        break;

      case DxbcTessOutputPrimitive::TriangleCcw:
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeVertexOrderCcw);
        break;

      default:
        throw DxvkError("Dxbc: Invalid tess output primitive");
    }
  }
    void emitDclThreadGroup(
      const DxbcShaderInstruction&  ins) {
    // dcl_thread_group has three operands:
    //    (imm0) Number of threads in X dimension
    //    (imm1) Number of threads in Y dimension
    //    (imm2) Number of threads in Z dimension
    m_cs.workgroupSizeX = ins.imm[0].u32;
    m_cs.workgroupSizeY = ins.imm[1].u32;
    m_cs.workgroupSizeZ = ins.imm[2].u32;

    m_module.setLocalSize(m_entryPointId,
      ins.imm[0].u32, ins.imm[1].u32, ins.imm[2].u32);
  }
    void emitDclGsInstanceCount(
      const DxbcShaderInstruction&  ins) {
    // dcl_gs_instance_count has one operand:
    //    (imm0) Number of geometry shader invocations
    m_module.setInvocations(m_entryPointId, ins.imm[0].u32);
    m_gs.invocationCount = ins.imm[0].u32;
  }
    uint32_t emitDclUavCounter(
            uint32_t                regId) {
    // Declare a structure type which holds the UAV counter
    if (m_uavCtrStructType == 0) {
      const uint32_t t_u32    = m_module.defIntType(32, 0);
      const uint32_t t_struct = m_module.defStructTypeUnique(1, &t_u32);

      m_module.decorate(t_struct, spv::DecorationBlock);
      m_module.memberDecorateOffset(t_struct, 0, 0);

      m_module.setDebugName      (t_struct, "uav_meta");
      m_module.setDebugMemberName(t_struct, 0, "ctr");

      m_uavCtrStructType  = t_struct;
      m_uavCtrPointerType = m_module.defPointerType(
        t_struct, spv::StorageClassStorageBuffer);
    }

    // Declare the buffer variable
    const uint32_t varId = m_module.newVar(
      m_uavCtrPointerType, spv::StorageClassStorageBuffer);

    m_module.setDebugName(varId,
      str::format("u", regId, "_meta").c_str());

    uint32_t bindingId = computeUavCounterBinding(
      m_programInfo.type(), regId);

    m_module.decorateDescriptorSet(varId, 0);
    m_module.decorateBinding(varId, bindingId);

    // Declare the storage buffer binding
    DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER };
    binding.resourceBinding = bindingId;
    binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
    binding.access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
    m_bindings.push_back(binding);

    return varId;
  }
    void emitDclImmediateConstantBuffer(
      const DxbcShaderInstruction&  ins) {
    if (m_icbArray)
      throw DxvkError("DxbcCompiler: Immediate constant buffer already declared");

    if ((ins.customDataSize & 0x3) != 0)
      throw DxvkError("DxbcCompiler: Immediate constant buffer size not a multiple of four DWORDs");

    // A lot of the time we'll be dealing with a scalar or vec2
    // array here, there's no reason to emit all those zeroes.
    uint32_t componentCount = 1u;

    for (uint32_t i = 0; i < ins.customDataSize; i += 4u) {
      for (uint32_t c = componentCount; c < 4u; c++) {
        if (ins.customData[i + c])
          componentCount = c + 1u;
      }

      if (componentCount == 4u)
        break;
    }

    uint32_t vectorCount = (ins.customDataSize / 4u);
    uint32_t dwordCount = vectorCount * componentCount;

    if (dwordCount <= Icb_MaxBakedDwords) {
      this->emitDclImmediateConstantBufferBaked(
        ins.customDataSize, ins.customData, componentCount);
    } else {
      this->emitDclImmediateConstantBufferUbo(
        ins.customDataSize, ins.customData, componentCount);
    }
  }
    void emitDclImmediateConstantBufferBaked(
            uint32_t                dwordCount,
      const uint32_t*               dwordArray,
            uint32_t                componentCount) {
    // Declare individual vector constants as 4x32-bit vectors
    small_vector<uint32_t, Icb_MaxBakedDwords> vectorIds;

    DxbcVectorType vecType;
    vecType.ctype  = DxbcScalarType::Uint32;
    vecType.ccount = componentCount;

    uint32_t vectorTypeId = getVectorTypeId(vecType);

    for (uint32_t i = 0; i < dwordCount; i += 4u) {
      std::array<uint32_t, 4> scalarIds = { };

      for (uint32_t c = 0; c < componentCount; c++)
        scalarIds[c] = m_module.constu32(dwordArray[i + c]);

      uint32_t id = scalarIds[0];

      if (componentCount > 1u)
        id = m_module.constComposite(vectorTypeId, componentCount, scalarIds.data());

      vectorIds.push_back(id);
    }

    // Pad array with one entry of zeroes so that we can
    // handle out-of-bounds accesses more conveniently.
    vectorIds.push_back(emitBuildZeroVector(vecType).id);

    // Declare the array that contains all the vectors
    DxbcArrayType arrInfo;
    arrInfo.ctype   = DxbcScalarType::Uint32;
    arrInfo.ccount  = componentCount;
    arrInfo.alength = vectorIds.size();

    uint32_t arrayTypeId = getArrayTypeId(arrInfo);
    uint32_t arrayId = m_module.constComposite(
      arrayTypeId, vectorIds.size(), vectorIds.data());

    // Declare the variable that will hold the constant
    // data and initialize it with the constant array.
    uint32_t pointerTypeId = m_module.defPointerType(
      arrayTypeId, spv::StorageClassPrivate);

    m_icbArray = m_module.newVarInit(
      pointerTypeId, spv::StorageClassPrivate,
      arrayId);

    m_module.setDebugName(m_icbArray, "icb");
    m_module.decorate(m_icbArray, spv::DecorationNonWritable);

    m_icbComponents = componentCount;
    m_icbSize = dwordCount / 4u;
  }
    void emitDclImmediateConstantBufferUbo(
            uint32_t                dwordCount,
      const uint32_t*               dwordArray,
            uint32_t                componentCount) {
    uint32_t vectorCount = dwordCount / 4u;

    // Tightly pack vec2 or scalar arrays if possible. Don't bother with
    // vec3 since we'd rather have properly vectorized loads in that case.
    if (m_moduleInfo.options.supportsTightIcbPacking && componentCount <= 2u)
      m_icbComponents = componentCount;
    else
      m_icbComponents = 4u;

    // Immediate constant buffer can be read out of bounds, declare
    // it with the maximum possible size and rely on robustness.
    this->emitDclConstantBufferVar(Icb_BindingSlotId, 4096u, m_icbComponents, "icb");

    m_icbData.reserve(vectorCount * componentCount);

    for (uint32_t i = 0; i < dwordCount; i += 4u) {
      for (uint32_t c = 0; c < m_icbComponents; c++)
        m_icbData.push_back(dwordArray[i + c]);
    }

    m_icbSize = vectorCount;
  }
    void emitCustomData(
      const DxbcShaderInstruction&  ins) {
    switch (ins.customDataType) {
      case DxbcCustomDataClass::ImmConstBuf:
        return emitDclImmediateConstantBuffer(ins);

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unsupported custom data block: ",
          ins.customDataType));
    }
  }
    void emitVectorAlu(
      const DxbcShaderInstruction&  ins) {
    std::array<DxbcRegisterValue, DxbcMaxOperandCount> src;

    for (uint32_t i = 0; i < ins.srcCount; i++)
      src.at(i) = emitRegisterLoad(ins.src[i], ins.dst[0].mask);

    DxbcRegisterValue dst;
    dst.type.ctype  = ins.dst[0].dataType;
    dst.type.ccount = ins.dst[0].mask.popCount();

    if (isDoubleType(ins.dst[0].dataType))
      dst.type.ccount /= 2;

    const uint32_t typeId = getVectorTypeId(dst.type);

    switch (ins.op) {
      /////////////////////
      // Move instructions
      case DxbcOpcode::Mov:
      case DxbcOpcode::DMov:
        dst.id = src.at(0).id;
        break;

      /////////////////////////////////////
      // ALU operations on float32 numbers
      case DxbcOpcode::Add:
      case DxbcOpcode::DAdd:
        dst.id = m_module.opFAdd(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Div:
      case DxbcOpcode::DDiv:
        dst.id = m_module.opFDiv(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Exp:
        dst.id = m_module.opExp2(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Frc:
        dst.id = m_module.opFract(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Log:
        dst.id = m_module.opLog2(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Mad:
      case DxbcOpcode::DFma:
        if (ins.controls.precise()) {
          // FXC only emits precise mad if the shader explicitly uses
          // the HLSL mad()/fma() intrinsics, let's preserve that.
          dst.id = m_module.opFFma(typeId,
            src.at(0).id, src.at(1).id, src.at(2).id);
        } else {
          dst.id = m_module.opFMul(typeId, src.at(0).id, src.at(1).id);
          dst.id = m_module.opFAdd(typeId, dst.id, src.at(2).id);
        }
        break;

      case DxbcOpcode::Max:
      case DxbcOpcode::DMax:
        dst.id = m_module.opNMax(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Min:
      case DxbcOpcode::DMin:
        dst.id = m_module.opNMin(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Mul:
      case DxbcOpcode::DMul:
        dst.id = m_module.opFMul(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Rcp:
        dst.id = m_module.opFDiv(typeId,
          emitBuildConstVecf32(
            1.0f, 1.0f, 1.0f, 1.0f,
            ins.dst[0].mask).id,
          src.at(0).id);
        break;

      case DxbcOpcode::DRcp:
        dst.id = m_module.opFDiv(typeId,
          emitBuildConstVecf64(1.0, 1.0,
            ins.dst[0].mask).id,
          src.at(0).id);
        break;

      case DxbcOpcode::RoundNe:
        dst.id = m_module.opRoundEven(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::RoundNi:
        dst.id = m_module.opFloor(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::RoundPi:
        dst.id = m_module.opCeil(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::RoundZ:
        dst.id = m_module.opTrunc(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Rsq:
        dst.id = m_module.opInverseSqrt(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Sqrt:
        dst.id = m_module.opSqrt(
          typeId, src.at(0).id);
        break;

      /////////////////////////////////////
      // ALU operations on signed integers
      case DxbcOpcode::IAdd:
        dst.id = m_module.opIAdd(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::IMad:
      case DxbcOpcode::UMad:
        dst.id = m_module.opIAdd(typeId,
          m_module.opIMul(typeId,
            src.at(0).id, src.at(1).id),
          src.at(2).id);
        break;

      case DxbcOpcode::IMax:
        dst.id = m_module.opSMax(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::IMin:
        dst.id = m_module.opSMin(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::INeg:
        dst.id = m_module.opSNegate(
          typeId, src.at(0).id);
        break;

      ///////////////////////////////////////
      // ALU operations on unsigned integers
      case DxbcOpcode::UMax:
        dst.id = m_module.opUMax(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::UMin:
        dst.id = m_module.opUMin(typeId,
          src.at(0).id, src.at(1).id);
        break;

      ///////////////////////////////////////
      // Bit operations on unsigned integers
      case DxbcOpcode::And:
        dst.id = m_module.opBitwiseAnd(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Not:
        dst.id = m_module.opNot(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::Or:
        dst.id = m_module.opBitwiseOr(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Xor:
        dst.id = m_module.opBitwiseXor(typeId,
          src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::CountBits:
        dst.id = m_module.opBitCount(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::BfRev:
        dst.id = m_module.opBitReverse(
          typeId, src.at(0).id);
        break;

      ///////////////////////////
      // Conversion instructions
      case DxbcOpcode::ItoF:
        dst.id = m_module.opConvertStoF(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::UtoF:
        dst.id = m_module.opConvertUtoF(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::FtoI:
        dst.id = m_module.opConvertFtoS(
          typeId, src.at(0).id);
        break;

      case DxbcOpcode::FtoU:
        dst.id = m_module.opConvertFtoU(
          typeId, src.at(0).id);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    if (ins.controls.precise() || m_precise)
      m_module.decorate(dst.id, spv::DecorationNoContraction);

    // Store computed value
    dst = emitDstOperandModifiers(dst, ins.modifiers);
    emitRegisterStore(ins.dst[0], dst);
  }
    void emitVectorCmov(
      const DxbcShaderInstruction&  ins) {
    // movc and swapc have the following operands:
    //    (dst0) The first destination register
    //    (dst1) The second destination register (swapc only)
    //    (src0) The condition vector
    //    (src1) Vector to select from if the condition is not 0
    //    (src2) Vector to select from if the condition is 0
    DxbcRegMask condMask = ins.dst[0].mask;

    if (ins.dst[0].dataType == DxbcScalarType::Float64) {
      condMask = DxbcRegMask(
        condMask[0] && condMask[1],
        condMask[2] && condMask[3],
        false, false);
    }

    const DxbcRegisterValue condition   = emitRegisterLoad(ins.src[0], condMask);
    const DxbcRegisterValue selectTrue  = emitRegisterLoad(ins.src[1], ins.dst[0].mask);
    const DxbcRegisterValue selectFalse = emitRegisterLoad(ins.src[2], ins.dst[0].mask);

    uint32_t componentCount = condMask.popCount();

    // We'll compare against a vector of zeroes to generate a
    // boolean vector, which in turn will be used by OpSelect
    uint32_t zeroType = m_module.defIntType(32, 0);
    uint32_t boolType = m_module.defBoolType();

    uint32_t zero = m_module.constu32(0);

    if (componentCount > 1) {
      zeroType = m_module.defVectorType(zeroType, componentCount);
      boolType = m_module.defVectorType(boolType, componentCount);

      const std::array<uint32_t, 4> zeroVec = { zero, zero, zero, zero };
      zero = m_module.constComposite(zeroType, componentCount, zeroVec.data());
    }

    // In case of swapc, the second destination operand receives
    // the output that a cmov instruction would normally get
    const uint32_t trueIndex = ins.op == DxbcOpcode::Swapc ? 1 : 0;

    for (uint32_t i = 0; i < ins.dstCount; i++) {
      DxbcRegisterValue result;
      result.type.ctype  = ins.dst[i].dataType;
      result.type.ccount = componentCount;
      result.id = m_module.opSelect(
        getVectorTypeId(result.type),
        m_module.opINotEqual(boolType, condition.id, zero),
        i == trueIndex ? selectTrue.id : selectFalse.id,
        i != trueIndex ? selectTrue.id : selectFalse.id);

      result = emitDstOperandModifiers(result, ins.modifiers);
      emitRegisterStore(ins.dst[i], result);
    }
  }
    void emitVectorCmp(
      const DxbcShaderInstruction&  ins) {
    // Compare instructions have three operands:
    //    (dst0) The destination register
    //    (src0) The first vector to compare
    //    (src1) The second vector to compare
    uint32_t componentCount = ins.dst[0].mask.popCount();

    // For 64-bit operations, we'll return a 32-bit
    // vector, so we have to adjust the read mask
    DxbcRegMask srcMask = ins.dst[0].mask;

    if (isDoubleType(ins.src[0].dataType)) {
      srcMask = DxbcRegMask(
        componentCount > 0, componentCount > 0,
        componentCount > 1, componentCount > 1);
    }

    const std::array<DxbcRegisterValue, 2> src = {
      emitRegisterLoad(ins.src[0], srcMask),
      emitRegisterLoad(ins.src[1], srcMask),
    };

    // Condition, which is a boolean vector used
    // to select between the ~0u and 0u vectors.
    uint32_t condition     = 0;
    uint32_t conditionType = m_module.defBoolType();

    if (componentCount > 1)
      conditionType = m_module.defVectorType(conditionType, componentCount);

    bool invert = false;

    switch (ins.op) {
      case DxbcOpcode::Ne:
      case DxbcOpcode::DNe:
        invert = true;
        [[fallthrough]];

      case DxbcOpcode::Eq:
      case DxbcOpcode::DEq:
        condition = m_module.opFOrdEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Ge:
      case DxbcOpcode::DGe:
        condition = m_module.opFOrdGreaterThanEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::Lt:
      case DxbcOpcode::DLt:
        condition = m_module.opFOrdLessThan(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::IEq:
        condition = m_module.opIEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::IGe:
        condition = m_module.opSGreaterThanEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::ILt:
        condition = m_module.opSLessThan(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::INe:
        condition = m_module.opINotEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::UGe:
        condition = m_module.opUGreaterThanEqual(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      case DxbcOpcode::ULt:
        condition = m_module.opULessThan(
          conditionType, src.at(0).id, src.at(1).id);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    // Generate constant vectors for selection
    uint32_t sFalse = m_module.constu32( 0u);
    uint32_t sTrue  = m_module.constu32(~0u);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = componentCount;

    const uint32_t typeId = getVectorTypeId(result.type);

    if (componentCount > 1) {
      const std::array<uint32_t, 4> vFalse = { sFalse, sFalse, sFalse, sFalse };
      const std::array<uint32_t, 4> vTrue  = { sTrue,  sTrue,  sTrue,  sTrue  };

      sFalse = m_module.constComposite(typeId, componentCount, vFalse.data());
      sTrue  = m_module.constComposite(typeId, componentCount, vTrue .data());
    }

    if (invert)
      std::swap(sFalse, sTrue);

    // Perform component-wise mask selection
    // based on the condition evaluated above.
    result.id = m_module.opSelect(
      typeId, condition, sTrue, sFalse);

    emitRegisterStore(ins.dst[0], result);
  }
    void emitVectorDeriv(
      const DxbcShaderInstruction&  ins) {
    // Derivative instructions have two operands:
    //    (dst0) Destination register for the derivative
    //    (src0) The operand to compute the derivative of
    DxbcRegisterValue value = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
    const uint32_t typeId = getVectorTypeId(value.type);

    switch (ins.op) {
      case DxbcOpcode::DerivRtx:
        value.id = m_module.opDpdx(typeId, value.id);
        break;

      case DxbcOpcode::DerivRty:
        value.id = m_module.opDpdy(typeId, value.id);
        break;

      case DxbcOpcode::DerivRtxCoarse:
        value.id = m_module.opDpdxCoarse(typeId, value.id);
        break;

      case DxbcOpcode::DerivRtyCoarse:
        value.id = m_module.opDpdyCoarse(typeId, value.id);
        break;

      case DxbcOpcode::DerivRtxFine:
        value.id = m_module.opDpdxFine(typeId, value.id);
        break;

      case DxbcOpcode::DerivRtyFine:
        value.id = m_module.opDpdyFine(typeId, value.id);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    value = emitDstOperandModifiers(value, ins.modifiers);
    emitRegisterStore(ins.dst[0], value);
  }
    void emitVectorDot(
      const DxbcShaderInstruction&  ins) {
    const DxbcRegMask srcMask(true,
      ins.op >= DxbcOpcode::Dp2,
      ins.op >= DxbcOpcode::Dp3,
      ins.op >= DxbcOpcode::Dp4);

    const std::array<DxbcRegisterValue, 2> src = {
      emitRegisterLoad(ins.src[0], srcMask),
      emitRegisterLoad(ins.src[1], srcMask),
    };

    DxbcRegisterValue dst;
    dst.type.ctype  = ins.dst[0].dataType;
    dst.type.ccount = 1;
    dst.id = 0;

    uint32_t componentType = getVectorTypeId(dst.type);
    uint32_t componentCount = srcMask.popCount();

    for (uint32_t i = 0; i < componentCount; i++) {
      if (dst.id) {
        dst.id = m_module.opFFma(componentType,
          m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i),
          m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i),
          dst.id);
      } else {
        dst.id = m_module.opFMul(componentType,
          m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i),
          m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i));
      }

      // Unconditionally mark as precise since the exact order of operation
      // matters for some games, even if the instruction itself is not marked
      // as precise.
      m_module.decorate(dst.id, spv::DecorationNoContraction);
    }

    dst = emitDstOperandModifiers(dst, ins.modifiers);
    emitRegisterStore(ins.dst[0], dst);
  }
    void emitVectorIdiv(
      const DxbcShaderInstruction&  ins) {
    // udiv has four operands:
    //    (dst0) Quotient destination register
    //    (dst1) Remainder destination register
    //    (src0) The first vector to compare
    //    (src1) The second vector to compare
    if (ins.dst[0].type == DxbcOperandType::Null
     && ins.dst[1].type == DxbcOperandType::Null)
      return;

    // FIXME support this if applications require it
    if (ins.dst[0].type != DxbcOperandType::Null
     && ins.dst[1].type != DxbcOperandType::Null
     && ins.dst[0].mask != ins.dst[1].mask) {
      Logger::warn("DxbcCompiler: Idiv with different destination masks not supported");
      return;
    }

    // Load source operands as integers with the
    // mask of one non-NULL destination operand
    const DxbcRegMask srcMask =
      ins.dst[0].type != DxbcOperandType::Null
        ? ins.dst[0].mask
        : ins.dst[1].mask;

    const std::array<DxbcRegisterValue, 2> src = {
      emitRegisterLoad(ins.src[0], srcMask),
      emitRegisterLoad(ins.src[1], srcMask),
    };

    // Division by zero will return 0xffffffff for both results
    auto bvecId = getVectorTypeId({ DxbcScalarType::Bool, srcMask.popCount() });

    DxbcRegisterValue const0  = emitBuildConstVecu32( 0u,  0u,  0u,  0u, srcMask);
    DxbcRegisterValue constff = emitBuildConstVecu32(~0u, ~0u, ~0u, ~0u, srcMask);

    uint32_t cmpValue = m_module.opINotEqual(bvecId, src.at(1).id, const0.id);

    // Compute results only if the destination
    // operands are not NULL.
    if (ins.dst[0].type != DxbcOperandType::Null) {
      DxbcRegisterValue quotient;
      quotient.type.ctype  = ins.dst[0].dataType;
      quotient.type.ccount = ins.dst[0].mask.popCount();

      quotient.id = m_module.opUDiv(
        getVectorTypeId(quotient.type),
        src.at(0).id, src.at(1).id);

      quotient.id = m_module.opSelect(
        getVectorTypeId(quotient.type),
        cmpValue, quotient.id, constff.id);

      quotient = emitDstOperandModifiers(quotient, ins.modifiers);
      emitRegisterStore(ins.dst[0], quotient);
    }

    if (ins.dst[1].type != DxbcOperandType::Null) {
      DxbcRegisterValue remainder;
      remainder.type.ctype  = ins.dst[1].dataType;
      remainder.type.ccount = ins.dst[1].mask.popCount();

      remainder.id = m_module.opUMod(
        getVectorTypeId(remainder.type),
        src.at(0).id, src.at(1).id);

      remainder.id = m_module.opSelect(
        getVectorTypeId(remainder.type),
        cmpValue, remainder.id, constff.id);

      remainder = emitDstOperandModifiers(remainder, ins.modifiers);
      emitRegisterStore(ins.dst[1], remainder);
    }
  }
    void emitVectorImul(
      const DxbcShaderInstruction&  ins) {
    // imul and umul have four operands:
    //    (dst0) High destination register
    //    (dst1) Low destination register
    //    (src0) The first vector to compare
    //    (src1) The second vector to compare
    if (ins.dst[0].type == DxbcOperandType::Null) {
      if (ins.dst[1].type == DxbcOperandType::Null)
        return;

      // If dst0 is NULL, this instruction behaves just
      // like any other three-operand ALU instruction
      const std::array<DxbcRegisterValue, 2> src = {
        emitRegisterLoad(ins.src[0], ins.dst[1].mask),
        emitRegisterLoad(ins.src[1], ins.dst[1].mask),
      };

      DxbcRegisterValue result;
      result.type.ctype  = ins.dst[1].dataType;
      result.type.ccount = ins.dst[1].mask.popCount();
      result.id = m_module.opIMul(
        getVectorTypeId(result.type),
        src.at(0).id, src.at(1).id);

      result = emitDstOperandModifiers(result, ins.modifiers);
      emitRegisterStore(ins.dst[1], result);
    } else {
      // TODO implement this
      Logger::warn("DxbcCompiler: Extended Imul not yet supported");
    }
  }
    void emitVectorMsad(
      const DxbcShaderInstruction&  ins) {
    // msad has four operands:
    //    (dst0) Destination
    //    (src0) Reference (packed uint8)
    //    (src1) Source (packed uint8)
    //    (src2) Accumulator
    DxbcRegisterValue refReg = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
    DxbcRegisterValue srcReg = emitRegisterLoad(ins.src[1], ins.dst[0].mask);
    DxbcRegisterValue result = emitRegisterLoad(ins.src[2], ins.dst[0].mask);

    auto typeId = getVectorTypeId(result.type);
    auto bvecId = getVectorTypeId({ DxbcScalarType::Bool, result.type.ccount });

    for (uint32_t i = 0; i < 4; i++) {
      auto shift = m_module.constu32(8 * i);
      auto count = m_module.constu32(8);

      auto ref = m_module.opBitFieldUExtract(typeId, refReg.id, shift, count);
      auto src = m_module.opBitFieldUExtract(typeId, srcReg.id, shift, count);

      auto zero = emitBuildConstVecu32(0, 0, 0, 0, ins.dst[0].mask);
      auto mask = m_module.opINotEqual(bvecId, ref, zero.id);

      auto diff = m_module.opSAbs(typeId, m_module.opISub(typeId, ref, src));
      result.id = m_module.opSelect(typeId, mask, m_module.opIAdd(typeId, result.id, diff), result.id);
    }

    result = emitDstOperandModifiers(result, ins.modifiers);
    emitRegisterStore(ins.dst[0], result);
  }
    void emitVectorShift(
      const DxbcShaderInstruction&  ins) {
    // Shift operations have three operands:
    //    (dst0) The destination register
    //    (src0) The register to shift
    //    (src1) The shift amount (scalar)
    DxbcRegisterValue shiftReg = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
    DxbcRegisterValue countReg = emitRegisterLoad(ins.src[1], ins.dst[0].mask);

    if (ins.src[1].type != DxbcOperandType::Imm32)
      countReg = emitRegisterMaskBits(countReg, 0x1F);

    if (countReg.type.ccount == 1)
      countReg = emitRegisterExtend(countReg, shiftReg.type.ccount);

    DxbcRegisterValue result;
    result.type.ctype  = ins.dst[0].dataType;
    result.type.ccount = ins.dst[0].mask.popCount();

    switch (ins.op) {
      case DxbcOpcode::IShl:
        result.id = m_module.opShiftLeftLogical(
          getVectorTypeId(result.type),
          shiftReg.id, countReg.id);
        break;

      case DxbcOpcode::IShr:
        result.id = m_module.opShiftRightArithmetic(
          getVectorTypeId(result.type),
          shiftReg.id, countReg.id);
        break;

      case DxbcOpcode::UShr:
        result.id = m_module.opShiftRightLogical(
          getVectorTypeId(result.type),
          shiftReg.id, countReg.id);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    result = emitDstOperandModifiers(result, ins.modifiers);
    emitRegisterStore(ins.dst[0], result);
  }
    void emitVectorSinCos(
      const DxbcShaderInstruction&  ins) {
    // sincos has three operands:
    //    (dst0) Destination register for sin(x)
    //    (dst1) Destination register for cos(x)
    //    (src0) Source operand x

    // Load source operand as 32-bit float vector.
    const DxbcRegisterValue srcValue = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, true, true, true));

    uint32_t typeId = getScalarTypeId(srcValue.type.ctype);

    DxbcRegisterValue sinVector = { };
    sinVector.type.ctype = DxbcScalarType::Float32;

    DxbcRegisterValue cosVector = { };
    cosVector.type.ctype = DxbcScalarType::Float32;

    // Only compute sincos for enabled components
    std::array<uint32_t, 4> sinIds = { };
    std::array<uint32_t, 4> cosIds = { };

    for (uint32_t i = 0; i < 4; i++) {
      const uint32_t sinIndex = 0u;
      const uint32_t cosIndex = 1u;

      if (ins.dst[0].mask[i] || ins.dst[1].mask[i]) {
        uint32_t sincosId = m_module.opSinCos(m_module.opCompositeExtract(typeId, srcValue.id, 1u, &i), !m_moduleInfo.options.sincosEmulation);

        if (ins.dst[0].type != DxbcOperandType::Null && ins.dst[0].mask[i])
          sinIds[sinVector.type.ccount++] = m_module.opCompositeExtract(typeId, sincosId, 1u, &sinIndex);

        if (ins.dst[1].type != DxbcOperandType::Null && ins.dst[1].mask[i])
          cosIds[cosVector.type.ccount++] = m_module.opCompositeExtract(typeId, sincosId, 1u, &cosIndex);
      }
    }

    if (sinVector.type.ccount) {
      sinVector.id = sinVector.type.ccount > 1u
        ? m_module.opCompositeConstruct(getVectorTypeId(sinVector.type), sinVector.type.ccount, sinIds.data())
        : sinIds[0];

      emitRegisterStore(ins.dst[0], sinVector);
    }

    if (cosVector.type.ccount) {
      cosVector.id = cosVector.type.ccount > 1u
        ? m_module.opCompositeConstruct(getVectorTypeId(cosVector.type), cosVector.type.ccount, cosIds.data())
        : cosIds[0];

      emitRegisterStore(ins.dst[1], cosVector);
    }
  }
    void emitGeometryEmit(
      const DxbcShaderInstruction&  ins) {
    // In xfb mode we might have multiple streams, so
    // we have to figure out which stream to write to
    uint32_t streamId  = 0;
    uint32_t streamVar = 0;

    if (m_moduleInfo.xfb != nullptr) {
      streamId  = ins.dstCount > 0 ? ins.dst[0].idx[0].offset : 0;
      streamVar = m_module.constu32(streamId);
    }

    // Checking the negation is easier for EmitThenCut/EmitThenCutStream
    bool doEmit = ins.op != DxbcOpcode::Cut && ins.op != DxbcOpcode::CutStream;
    bool doCut = ins.op != DxbcOpcode::Emit && ins.op != DxbcOpcode::EmitStream;

    if (doEmit) {
      if (m_gs.needsOutputSetup)
        emitOutputSetup();
      emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances);
      emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances);
      emitXfbOutputSetup(streamId, false);
      m_module.opEmitVertex(streamVar);
    }

    if (doCut)
      m_module.opEndPrimitive(streamVar);
  }
    void emitAtomic(
      const DxbcShaderInstruction&  ins) {
    // atomic_* operations have the following operands:
    //    (dst0) Destination u# or g# register
    //    (src0) Index into the texture or buffer
    //    (src1) The source value for the operation
    //    (src2) Second source operand (optional)
    // imm_atomic_* operations have the following operands:
    //    (dst0) Register that receives the result
    //    (dst1) Destination u# or g# register
    //    (srcX) As above
    const DxbcBufferInfo bufferInfo = getBufferInfo(ins.dst[ins.dstCount - 1]);

    bool isImm = ins.dstCount == 2;
    bool isUav = ins.dst[ins.dstCount - 1].type == DxbcOperandType::UnorderedAccessView;
    bool isSsbo = bufferInfo.isSsbo;

    // Retrieve destination pointer for the atomic operation>
    const DxbcRegisterPointer pointer = emitGetAtomicPointer(
      ins.dst[ins.dstCount - 1], ins.src[0]);

    // Load source values
    std::array<DxbcRegisterValue, 2> src;

    for (uint32_t i = 1; i < ins.srcCount; i++) {
      src[i - 1] = emitRegisterBitcast(
        emitRegisterLoad(ins.src[i], DxbcRegMask(true, false, false, false)),
        pointer.type.ctype);
    }

    // Define memory scope and semantics based on the operands
    uint32_t scope     = 0;
    uint32_t semantics = 0;

    if (isUav) {
      scope     = spv::ScopeQueueFamily;
      semantics = spv::MemorySemanticsAcquireReleaseMask;

      semantics |= isSsbo
                ? spv::MemorySemanticsUniformMemoryMask
                : spv::MemorySemanticsImageMemoryMask;
    } else {
      scope     = spv::ScopeWorkgroup;
      semantics = spv::MemorySemanticsWorkgroupMemoryMask
                | spv::MemorySemanticsAcquireReleaseMask;
    }

    const uint32_t scopeId     = m_module.constu32(scope);
    const uint32_t semanticsId = m_module.constu32(semantics);

    // Perform the atomic operation on the given pointer
    DxbcRegisterValue value;
    value.type = pointer.type;
    value.id   = 0;

    // The result type, which is a scalar integer
    const uint32_t typeId = getVectorTypeId(value.type);

    switch (ins.op) {
      case DxbcOpcode::AtomicCmpStore:
      case DxbcOpcode::ImmAtomicCmpExch:
        value.id = m_module.opAtomicCompareExchange(
          typeId, pointer.id, scopeId, semanticsId,
          m_module.constu32(spv::MemorySemanticsMaskNone),
          src[1].id, src[0].id);
        break;

      case DxbcOpcode::ImmAtomicExch:
        value.id = m_module.opAtomicExchange(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicIAdd:
      case DxbcOpcode::ImmAtomicIAdd:
        value.id = m_module.opAtomicIAdd(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicAnd:
      case DxbcOpcode::ImmAtomicAnd:
        value.id = m_module.opAtomicAnd(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicOr:
      case DxbcOpcode::ImmAtomicOr:
        value.id = m_module.opAtomicOr(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicXor:
      case DxbcOpcode::ImmAtomicXor:
        value.id = m_module.opAtomicXor(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicIMin:
      case DxbcOpcode::ImmAtomicIMin:
        value.id = m_module.opAtomicSMin(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicIMax:
      case DxbcOpcode::ImmAtomicIMax:
        value.id = m_module.opAtomicSMax(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicUMin:
      case DxbcOpcode::ImmAtomicUMin:
        value.id = m_module.opAtomicUMin(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      case DxbcOpcode::AtomicUMax:
      case DxbcOpcode::ImmAtomicUMax:
        value.id = m_module.opAtomicUMax(typeId,
          pointer.id, scopeId, semanticsId,
          src[0].id);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    // Write back the result to the destination
    // register if this is an imm_atomic_* opcode.
    if (isImm)
      emitRegisterStore(ins.dst[0], value);
  }
    void emitAtomicCounter(
      const DxbcShaderInstruction&  ins) {
    // imm_atomic_alloc and imm_atomic_consume have the following operands:
    //    (dst0) The register that will hold the old counter value
    //    (dst1) The UAV whose counter is going to be modified
    const uint32_t registerId = ins.dst[1].idx[0].offset;

    if (m_uavs.at(registerId).ctrId == 0)
      m_uavs.at(registerId).ctrId = emitDclUavCounter(registerId);

    // Get a pointer to the atomic counter in question
    DxbcRegisterInfo ptrType;
    ptrType.type.ctype   = DxbcScalarType::Uint32;
    ptrType.type.ccount  = 1;
    ptrType.type.alength = 0;
    ptrType.sclass = spv::StorageClassStorageBuffer;

    uint32_t zeroId = m_module.consti32(0);
    uint32_t ptrId  = m_module.opAccessChain(
      getPointerTypeId(ptrType),
      m_uavs.at(registerId).ctrId,
      1, &zeroId);

    // Define memory scope and semantics based on the operands
    uint32_t scope     = spv::ScopeQueueFamily;
    uint32_t semantics = spv::MemorySemanticsUniformMemoryMask
                       | spv::MemorySemanticsAcquireReleaseMask;

    uint32_t scopeId     = m_module.constu32(scope);
    uint32_t semanticsId = m_module.constu32(semantics);

    // Compute the result value
    DxbcRegisterValue value;
    value.type.ctype  = DxbcScalarType::Uint32;
    value.type.ccount = 1;

    uint32_t typeId = getVectorTypeId(value.type);

    switch (ins.op) {
      case DxbcOpcode::ImmAtomicAlloc:
        value.id = m_module.opAtomicIAdd(typeId, ptrId,
          scopeId, semanticsId, m_module.constu32(1));
        break;

      case DxbcOpcode::ImmAtomicConsume:
        value.id = m_module.opAtomicISub(typeId, ptrId,
          scopeId, semanticsId, m_module.constu32(1));
        value.id = m_module.opISub(typeId, value.id,
          m_module.constu32(1));
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    // Store the result
    emitRegisterStore(ins.dst[0], value);
  }
    void emitBarrier(
      const DxbcShaderInstruction&  ins) {
    // sync takes no operands. Instead, the synchronization
    // scope is defined by the operand control bits.
    const DxbcSyncFlags flags = ins.controls.syncFlags();

    uint32_t executionScope   = spv::ScopeInvocation;
    uint32_t memoryScope      = spv::ScopeInvocation;
    uint32_t memorySemantics  = 0;

    if (flags.test(DxbcSyncFlag::ThreadsInGroup))
      executionScope   = spv::ScopeWorkgroup;

    if (flags.test(DxbcSyncFlag::ThreadGroupSharedMemory)) {
      memoryScope      = spv::ScopeWorkgroup;
      memorySemantics |= spv::MemorySemanticsWorkgroupMemoryMask
                      |  spv::MemorySemanticsAcquireReleaseMask
                      |  spv::MemorySemanticsMakeAvailableMask
                      |  spv::MemorySemanticsMakeVisibleMask;
    }

    if (flags.test(DxbcSyncFlag::UavMemoryGroup)) {
      memoryScope      = spv::ScopeWorkgroup;
      memorySemantics |= spv::MemorySemanticsImageMemoryMask
                      |  spv::MemorySemanticsUniformMemoryMask
                      |  spv::MemorySemanticsAcquireReleaseMask
                      |  spv::MemorySemanticsMakeAvailableMask
                      |  spv::MemorySemanticsMakeVisibleMask;
    }

    if (flags.test(DxbcSyncFlag::UavMemoryGlobal)) {
      memoryScope      = spv::ScopeQueueFamily;

      if (m_programInfo.type() == DxbcProgramType::ComputeShader && !m_hasGloballyCoherentUav)
        memoryScope    = spv::ScopeWorkgroup;

      memorySemantics |= spv::MemorySemanticsImageMemoryMask
                      |  spv::MemorySemanticsUniformMemoryMask
                      |  spv::MemorySemanticsAcquireReleaseMask
                      |  spv::MemorySemanticsMakeAvailableMask
                      |  spv::MemorySemanticsMakeVisibleMask;
    }

    if (executionScope != spv::ScopeInvocation) {
      m_module.opControlBarrier(
        m_module.constu32(executionScope),
        m_module.constu32(memoryScope),
        m_module.constu32(memorySemantics));
    } else if (memoryScope != spv::ScopeInvocation) {
      m_module.opMemoryBarrier(
        m_module.constu32(memoryScope),
        m_module.constu32(memorySemantics));
    } else {
      Logger::warn("DxbcCompiler: sync instruction has no effect");
    }
  }
    void emitBitExtract(
      const DxbcShaderInstruction&  ins) {
    // ibfe and ubfe take the following arguments:
    //    (dst0) The destination register
    //    (src0) Number of bits to extact
    //    (src1) Offset of the bits to extract
    //    (src2) Register to extract bits from
    const bool isSigned = ins.op == DxbcOpcode::IBfe;

    DxbcRegisterValue bitCnt = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
    DxbcRegisterValue bitOfs = emitRegisterLoad(ins.src[1], ins.dst[0].mask);

    if (ins.src[0].type != DxbcOperandType::Imm32)
      bitCnt = emitRegisterMaskBits(bitCnt, 0x1F);

    if (ins.src[1].type != DxbcOperandType::Imm32)
      bitOfs = emitRegisterMaskBits(bitOfs, 0x1F);

    const DxbcRegisterValue src = emitRegisterLoad(ins.src[2], ins.dst[0].mask);

    const uint32_t componentCount  = src.type.ccount;
    std::array<uint32_t, 4> componentIds = {{ 0, 0, 0, 0 }};

    for (uint32_t i = 0; i < componentCount; i++) {
      const DxbcRegisterValue currBitCnt = emitRegisterExtract(bitCnt, DxbcRegMask::select(i));
      const DxbcRegisterValue currBitOfs = emitRegisterExtract(bitOfs, DxbcRegMask::select(i));
      const DxbcRegisterValue currSrc    = emitRegisterExtract(src,    DxbcRegMask::select(i));

      const uint32_t typeId = getVectorTypeId(currSrc.type);

      componentIds[i] = isSigned
        ? m_module.opBitFieldSExtract(typeId, currSrc.id, currBitOfs.id, currBitCnt.id)
        : m_module.opBitFieldUExtract(typeId, currSrc.id, currBitOfs.id, currBitCnt.id);
    }

    DxbcRegisterValue result;
    result.type = src.type;
    result.id   = componentCount > 1
      ? m_module.opCompositeConstruct(
          getVectorTypeId(result.type),
          componentCount, componentIds.data())
      : componentIds[0];
    emitRegisterStore(ins.dst[0], result);
  }
    void emitBitInsert(
      const DxbcShaderInstruction&  ins) {
    // ibfe and ubfe take the following arguments:
    //    (dst0) The destination register
    //    (src0) Number of bits to extact
    //    (src1) Offset of the bits to extract
    //    (src2) Register to take bits from
    //    (src3) Register to replace bits in
    DxbcRegisterValue bitCnt = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
    DxbcRegisterValue bitOfs = emitRegisterLoad(ins.src[1], ins.dst[0].mask);

    if (ins.src[0].type != DxbcOperandType::Imm32)
      bitCnt = emitRegisterMaskBits(bitCnt, 0x1F);

    if (ins.src[1].type != DxbcOperandType::Imm32)
      bitOfs = emitRegisterMaskBits(bitOfs, 0x1F);

    const DxbcRegisterValue insert = emitRegisterLoad(ins.src[2], ins.dst[0].mask);
    const DxbcRegisterValue base   = emitRegisterLoad(ins.src[3], ins.dst[0].mask);

    const uint32_t componentCount  = base.type.ccount;
    std::array<uint32_t, 4> componentIds = {{ 0, 0, 0, 0 }};

    for (uint32_t i = 0; i < componentCount; i++) {
      const DxbcRegisterValue currBitCnt = emitRegisterExtract(bitCnt, DxbcRegMask::select(i));
      const DxbcRegisterValue currBitOfs = emitRegisterExtract(bitOfs, DxbcRegMask::select(i));
      const DxbcRegisterValue currInsert = emitRegisterExtract(insert, DxbcRegMask::select(i));
      const DxbcRegisterValue currBase   = emitRegisterExtract(base,   DxbcRegMask::select(i));

      componentIds[i] = m_module.opBitFieldInsert(
        getVectorTypeId(currBase.type),
        currBase.id,   currInsert.id,
        currBitOfs.id, currBitCnt.id);
    }

    DxbcRegisterValue result;
    result.type = base.type;
    result.id   = componentCount > 1
      ? m_module.opCompositeConstruct(
          getVectorTypeId(result.type),
          componentCount, componentIds.data())
      : componentIds[0];
    emitRegisterStore(ins.dst[0], result);
  }
    void emitBitScan(
      const DxbcShaderInstruction&  ins) {
    // firstbit(lo|hi|shi) have two operands:
    //    (dst0) The destination operant
    //    (src0) Source operand to scan
    DxbcRegisterValue src = emitRegisterLoad(ins.src[0], ins.dst[0].mask);

    DxbcRegisterValue dst;
    dst.type.ctype  = ins.dst[0].dataType;
    dst.type.ccount = ins.dst[0].mask.popCount();

    // Result type, should be an unsigned integer
    const uint32_t typeId = getVectorTypeId(dst.type);

    switch (ins.op) {
      case DxbcOpcode::FirstBitLo:  dst.id = m_module.opFindILsb(typeId, src.id); break;
      case DxbcOpcode::FirstBitHi:  dst.id = m_module.opFindUMsb(typeId, src.id); break;
      case DxbcOpcode::FirstBitShi: dst.id = m_module.opFindSMsb(typeId, src.id); break;
      default: Logger::warn(str::format("DxbcCompiler: Unhandled instruction: ", ins.op)); return;
    }

    // The 'Hi' variants are counted from the MSB in DXBC
    // rather than the LSB, so we have to invert the number
    if (ins.op == DxbcOpcode::FirstBitHi || ins.op == DxbcOpcode::FirstBitShi) {
      uint32_t boolTypeId = m_module.defBoolType();

      if (dst.type.ccount > 1)
        boolTypeId = m_module.defVectorType(boolTypeId, dst.type.ccount);

      DxbcRegisterValue const31 = emitBuildConstVecu32(31u, 31u, 31u, 31u, ins.dst[0].mask);
      DxbcRegisterValue constff = emitBuildConstVecu32(~0u, ~0u, ~0u, ~0u, ins.dst[0].mask);

      dst.id = m_module.opSelect(typeId,
        m_module.opINotEqual(boolTypeId, dst.id, constff.id),
        m_module.opISub(typeId, const31.id, dst.id),
        constff.id);
    }

    // No modifiers are supported
    emitRegisterStore(ins.dst[0], dst);
  }
    void emitBufferQuery(
      const DxbcShaderInstruction&  ins) {
    // bufinfo takes two arguments
    //    (dst0) The destination register
    //    (src0) The buffer register to query
    const DxbcBufferInfo bufferInfo = getBufferInfo(ins.src[0]);
    bool isSsbo = bufferInfo.isSsbo;

    // We'll store this as a scalar unsigned integer
    DxbcRegisterValue result = isSsbo
      ? emitQueryBufferSize(ins.src[0])
      : emitQueryTexelBufferSize(ins.src[0]);

    uint32_t typeId = getVectorTypeId(result.type);

    // Adjust returned size if this is a raw or structured
    // buffer, as emitQueryTexelBufferSize only returns the
    // number of typed elements in the buffer.
    if (bufferInfo.type == DxbcResourceType::Raw) {
      result.id = m_module.opIMul(typeId,
        result.id, m_module.constu32(4));
    } else if (bufferInfo.type == DxbcResourceType::Structured) {
      result.id = m_module.opUDiv(typeId, result.id,
        m_module.constu32(bufferInfo.stride / 4));
    }

    // Store the result. The scalar will be extended to a
    // vector if the write mask consists of more than one
    // component, which is the desired behaviour.
    emitRegisterStore(ins.dst[0], result);
  }
    void emitBufferLoad(
      const DxbcShaderInstruction&  ins) {
    // ld_raw takes three arguments:
    //    (dst0) Destination register
    //    (src0) Byte offset
    //    (src1) Source register
    // ld_structured takes four arguments:
    //    (dst0) Destination register
    //    (src0) Structure index
    //    (src1) Byte offset
    //    (src2) Source register
    const bool isStructured = ins.op == DxbcOpcode::LdStructured
                           || ins.op == DxbcOpcode::LdStructuredS;

    // Source register. The exact way we access
    // the data depends on the register type.
    const DxbcRegister& dstReg = ins.dst[0];
    const DxbcRegister& srcReg = isStructured ? ins.src[2] : ins.src[1];

    if (dstReg.type == DxbcOperandType::UnorderedAccessView)
      emitUavBarrier(uint64_t(1u) << srcReg.idx[0].offset, 0u);

    // Retrieve common info about the buffer
    const DxbcBufferInfo bufferInfo = getBufferInfo(srcReg);

    // Shared memory is the only type of buffer that
    // is not accessed through a texel buffer view
    bool isTgsm = srcReg.type == DxbcOperandType::ThreadGroupSharedMemory;
    bool isSsbo = bufferInfo.isSsbo;

    // Common types and IDs used while loading the data
    uint32_t bufferId = isTgsm || isSsbo ? 0 : m_module.opLoad(bufferInfo.typeId, bufferInfo.varId);

    uint32_t vectorTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 4 });
    uint32_t scalarTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 1 });

    // Since all data is represented as a sequence of 32-bit
    // integers, we have to load each component individually.
    std::array<uint32_t, 4> ccomps = { 0, 0, 0, 0 };
    std::array<uint32_t, 4> scomps = { 0, 0, 0, 0 };
    uint32_t                scount = 0;

    // The sparse feedback ID will be non-zero for sparse
    // instructions on input. We need to reset it to 0.
    SpirvMemoryOperands memoryOperands;
    SpirvImageOperands imageOperands;
    imageOperands.sparse = ins.dstCount == 2;

    uint32_t coherence = bufferInfo.coherence;

    if (isTgsm && m_moduleInfo.options.forceVolatileTgsmAccess) {
      memoryOperands.flags |= spv::MemoryAccessVolatileMask;
      coherence = spv::ScopeWorkgroup;
    }

    if (coherence) {
      memoryOperands.flags |= spv::MemoryAccessNonPrivatePointerMask;

      if (coherence != spv::ScopeInvocation) {
        memoryOperands.flags |= spv::MemoryAccessMakePointerVisibleMask;
        memoryOperands.makeVisible = m_module.constu32(coherence);

        imageOperands.flags = spv::ImageOperandsNonPrivateTexelMask
                            | spv::ImageOperandsMakeTexelVisibleMask;
        imageOperands.makeVisible = m_module.constu32(coherence);
      }
    }

    uint32_t sparseFeedbackId = 0;

    bool useRawAccessChains = m_hasRawAccessChains && isSsbo && !imageOperands.sparse;

    DxbcRegisterValue index = emitRegisterLoad(ins.src[0], DxbcRegMask(true, false, false, false));
    DxbcRegisterValue offset = index;

    if (isStructured)
      offset = emitRegisterLoad(ins.src[1], DxbcRegMask(true, false, false, false));

    DxbcRegisterValue elementIndex = { };

    uint32_t baseAlignment = sizeof(uint32_t);

    if (useRawAccessChains) {
      memoryOperands.flags |= spv::MemoryAccessAlignedMask;

      if (isStructured && ins.src[1].type == DxbcOperandType::Imm32) {
        baseAlignment = bufferInfo.stride | ins.src[1].imm.u32_1;
        baseAlignment = baseAlignment & -baseAlignment;
        baseAlignment = std::min(baseAlignment, uint32_t(m_moduleInfo.options.minSsboAlignment));
      }
    } else {
      elementIndex = isStructured
        ? emitCalcBufferIndexStructured(index, offset, bufferInfo.stride)
        : emitCalcBufferIndexRaw(offset);
    }

    uint32_t readMask = 0u;

    for (uint32_t i = 0; i < 4; i++) {
      if (dstReg.mask[i])
        readMask |= 1u << srcReg.swizzle[i];
    }

    while (readMask) {
      uint32_t sindex = bit::tzcnt(readMask);
      uint32_t scount = bit::tzcnt(~(readMask >> sindex));
      uint32_t zero = 0;

      if (useRawAccessChains) {
        uint32_t alignment = baseAlignment;
        uint32_t offsetId = offset.id;

        if (sindex) {
          offsetId = m_module.opIAdd(scalarTypeId,
            offsetId, m_module.constu32(sizeof(uint32_t) * sindex));
          alignment |= sizeof(uint32_t) * sindex;
        }

        DxbcRegisterInfo storeInfo;
        storeInfo.type.ctype = DxbcScalarType::Uint32;
        storeInfo.type.ccount = scount;
        storeInfo.type.alength = 0;
        storeInfo.sclass = spv::StorageClassStorageBuffer;

        uint32_t loadTypeId = getArrayTypeId(storeInfo.type);
        uint32_t ptrTypeId = getPointerTypeId(storeInfo);

        uint32_t accessChain = isStructured
          ? m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId,
              m_module.constu32(bufferInfo.stride), index.id, offsetId,
              spv::RawAccessChainOperandsRobustnessPerElementNVMask)
          : m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId,
              m_module.constu32(0), m_module.constu32(0), offsetId,
              spv::RawAccessChainOperandsRobustnessPerComponentNVMask);

        memoryOperands.alignment = alignment & -alignment;

        uint32_t vectorId = m_module.opLoad(loadTypeId, accessChain, memoryOperands);

        for (uint32_t i = 0; i < scount; i++) {
          ccomps[sindex + i] = vectorId;

          if (scount > 1) {
            ccomps[sindex + i] = m_module.opCompositeExtract(
              scalarTypeId, vectorId, 1, &i);
          }
        }

        readMask &= ~(((1u << scount) - 1u) << sindex);
      } else {
        uint32_t elementIndexAdjusted = m_module.opIAdd(
          getVectorTypeId(elementIndex.type), elementIndex.id,
          m_module.consti32(sindex));

        if (isTgsm) {
          ccomps[sindex] = m_module.opLoad(scalarTypeId,
            m_module.opAccessChain(bufferInfo.typeId,
              bufferInfo.varId, 1, &elementIndexAdjusted),
            memoryOperands);
        } else if (isSsbo) {
          uint32_t indices[2] = { m_module.constu32(0), elementIndexAdjusted };
          ccomps[sindex] = m_module.opLoad(scalarTypeId,
            m_module.opAccessChain(bufferInfo.typeId,
              bufferInfo.varId, 2, indices),
            memoryOperands);
        } else {
          uint32_t resultTypeId = vectorTypeId;
          uint32_t resultId = 0;

          if (imageOperands.sparse)
            resultTypeId = getSparseResultTypeId(vectorTypeId);

          if (srcReg.type == DxbcOperandType::Resource) {
            resultId = m_module.opImageFetch(resultTypeId,
              bufferId, elementIndexAdjusted, imageOperands);
          } else if (srcReg.type == DxbcOperandType::UnorderedAccessView) {
            resultId = m_module.opImageRead(resultTypeId,
              bufferId, elementIndexAdjusted, imageOperands);
          } else {
            throw DxvkError("DxbcCompiler: Invalid operand type for strucured/raw load");
          }

          // Only read sparse feedback once. This may be somewhat inaccurate
          // for reads that straddle pages, but we can't easily emulate this.
          if (imageOperands.sparse) {
            imageOperands.sparse = false;
            sparseFeedbackId = resultId;

            resultId = emitExtractSparseTexel(vectorTypeId, resultId);
          }

          ccomps[sindex] = m_module.opCompositeExtract(scalarTypeId, resultId, 1, &zero);
        }

        readMask &= readMask - 1;
      }
    }

    for (uint32_t i = 0; i < 4; i++) {
      uint32_t sindex = srcReg.swizzle[i];

      if (dstReg.mask[i])
        scomps[scount++] = ccomps[sindex];
    }

    DxbcRegisterValue result =  { };
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = scount;
    result.id = scomps[0];

    if (scount > 1) {
      result.id = m_module.opCompositeConstruct(
        getVectorTypeId(result.type),
        scount, scomps.data());
    }

    emitRegisterStore(dstReg, result);

    if (sparseFeedbackId)
      emitStoreSparseFeedback(ins.dst[1], sparseFeedbackId);
  }
    void emitBufferStore(
      const DxbcShaderInstruction&  ins) {
    // store_raw takes three arguments:
    //    (dst0) Destination register
    //    (src0) Byte offset
    //    (src1) Source register
    // store_structured takes four arguments:
    //    (dst0) Destination register
    //    (src0) Structure index
    //    (src1) Byte offset
    //    (src2) Source register
    const bool isStructured = ins.op == DxbcOpcode::StoreStructured;

    // Source register. The exact way we access
    // the data depends on the register type.
    const DxbcRegister& dstReg = ins.dst[0];
    const DxbcRegister& srcReg = isStructured ? ins.src[2] : ins.src[1];

    if (dstReg.type == DxbcOperandType::UnorderedAccessView)
      emitUavBarrier(0u, uint64_t(1u) << dstReg.idx[0].offset);

    DxbcRegisterValue value = emitRegisterLoad(srcReg, dstReg.mask);
    value = emitRegisterBitcast(value, DxbcScalarType::Uint32);

    // Retrieve common info about the buffer
    const DxbcBufferInfo bufferInfo = getBufferInfo(dstReg);

    // Thread Group Shared Memory is not accessed through a texel buffer view
    bool isTgsm = dstReg.type == DxbcOperandType::ThreadGroupSharedMemory;
    bool isSsbo = bufferInfo.isSsbo;

    uint32_t bufferId = isTgsm || isSsbo ? 0 : m_module.opLoad(bufferInfo.typeId, bufferInfo.varId);

    uint32_t scalarTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 1 });
    uint32_t vectorTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 4 });

    // Set memory operands according to resource properties
    SpirvMemoryOperands memoryOperands;
    SpirvImageOperands imageOperands;

    uint32_t coherence = bufferInfo.coherence;

    if (isTgsm && m_moduleInfo.options.forceVolatileTgsmAccess) {
      memoryOperands.flags |= spv::MemoryAccessVolatileMask;
      coherence = spv::ScopeWorkgroup;
    }

    if (coherence) {
      memoryOperands.flags |= spv::MemoryAccessNonPrivatePointerMask;

      if (coherence != spv::ScopeInvocation) {
        memoryOperands.flags |= spv::MemoryAccessMakePointerAvailableMask;
        memoryOperands.makeAvailable = m_module.constu32(coherence);

        imageOperands.flags = spv::ImageOperandsNonPrivateTexelMask
                            | spv::ImageOperandsMakeTexelAvailableMask;
        imageOperands.makeAvailable = m_module.constu32(coherence);
      }
    }

    // Compute flat element index as necessary
    bool useRawAccessChains = isSsbo && m_hasRawAccessChains;

    DxbcRegisterValue index = emitRegisterLoad(ins.src[0], DxbcRegMask(true, false, false, false));
    DxbcRegisterValue offset = index;

    if (isStructured)
      offset = emitRegisterLoad(ins.src[1], DxbcRegMask(true, false, false, false));

    DxbcRegisterValue elementIndex = { };

    uint32_t baseAlignment = sizeof(uint32_t);

    if (useRawAccessChains) {
      memoryOperands.flags |= spv::MemoryAccessAlignedMask;

      if (isStructured && ins.src[1].type == DxbcOperandType::Imm32) {
        baseAlignment = bufferInfo.stride | ins.src[1].imm.u32_1;
        baseAlignment = baseAlignment & -baseAlignment;
        baseAlignment = std::min(baseAlignment, uint32_t(m_moduleInfo.options.minSsboAlignment));
      }
    } else {
      elementIndex = isStructured
        ? emitCalcBufferIndexStructured(index, offset, bufferInfo.stride)
        : emitCalcBufferIndexRaw(offset);
    }

    uint32_t writeMask = dstReg.mask.raw();

    while (writeMask) {
      uint32_t sindex = bit::tzcnt(writeMask);
      uint32_t scount = bit::tzcnt(~(writeMask >> sindex));

      if (useRawAccessChains) {
        uint32_t alignment = baseAlignment;
        uint32_t offsetId = offset.id;

        if (sindex) {
          offsetId = m_module.opIAdd(scalarTypeId,
            offsetId, m_module.constu32(sizeof(uint32_t) * sindex));
          alignment = alignment | (sizeof(uint32_t) * sindex);
        }

        DxbcRegisterInfo storeInfo;
        storeInfo.type.ctype = DxbcScalarType::Uint32;
        storeInfo.type.ccount = scount;
        storeInfo.type.alength = 0;
        storeInfo.sclass = spv::StorageClassStorageBuffer;

        uint32_t storeTypeId = getArrayTypeId(storeInfo.type);
        uint32_t ptrTypeId = getPointerTypeId(storeInfo);

        uint32_t accessChain = isStructured
          ? m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId,
              m_module.constu32(bufferInfo.stride), index.id, offsetId,
              spv::RawAccessChainOperandsRobustnessPerElementNVMask)
          : m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId,
              m_module.constu32(0), m_module.constu32(0), offsetId,
              spv::RawAccessChainOperandsRobustnessPerComponentNVMask);

        uint32_t valueId = value.id;

        if (scount < value.type.ccount) {
          if (scount == 1) {
            valueId = m_module.opCompositeExtract(storeTypeId, value.id, 1, &sindex);
          } else {
            std::array<uint32_t, 4> indices = { sindex, sindex + 1u, sindex + 2u, sindex + 3u };
            valueId = m_module.opVectorShuffle(storeTypeId, value.id, value.id, scount, indices.data());
          }
        }

        memoryOperands.alignment = alignment & -alignment;
        m_module.opStore(accessChain, valueId, memoryOperands);

        writeMask &= ~(((1u << scount) - 1u) << sindex);
      } else {
        uint32_t srcComponentId = value.type.ccount > 1
          ? m_module.opCompositeExtract(scalarTypeId,
              value.id, 1, &sindex)
          : value.id;

        uint32_t elementIndexAdjusted = sindex != 0
          ? m_module.opIAdd(getVectorTypeId(elementIndex.type),
              elementIndex.id, m_module.consti32(sindex))
          : elementIndex.id;

        if (isTgsm) {
          m_module.opStore(
            m_module.opAccessChain(bufferInfo.typeId,
              bufferInfo.varId, 1, &elementIndexAdjusted),
            srcComponentId, memoryOperands);
        } else if (isSsbo) {
          uint32_t indices[2] = { m_module.constu32(0), elementIndexAdjusted };
          m_module.opStore(
            m_module.opAccessChain(bufferInfo.typeId,
              bufferInfo.varId, 2, indices),
            srcComponentId, memoryOperands);
        } else if (dstReg.type == DxbcOperandType::UnorderedAccessView) {
          const std::array<uint32_t, 4> srcVectorIds = {
            srcComponentId, srcComponentId,
            srcComponentId, srcComponentId,
          };

          m_module.opImageWrite(
            bufferId, elementIndexAdjusted,
            m_module.opCompositeConstruct(vectorTypeId,
              4, srcVectorIds.data()),
            imageOperands);
        } else {
          throw DxvkError("DxbcCompiler: Invalid operand type for strucured/raw store");
        }

        writeMask &= writeMask - 1u;
      }
    }
  }
    void emitConvertFloat16(
      const DxbcShaderInstruction&  ins) {
    // f32tof16 takes two operands:
    //    (dst0) Destination register as a uint32 vector
    //    (src0) Source register as a float32 vector
    // f16tof32 takes two operands:
    //    (dst0) Destination register as a float32 vector
    //    (src0) Source register as a uint32 vector
    const DxbcRegisterValue src = emitRegisterLoad(ins.src[0], ins.dst[0].mask);

    // We handle both packing and unpacking here
    const bool isPack = ins.op == DxbcOpcode::F32toF16;

    // The conversion instructions do not map very well to the
    // SPIR-V pack instructions, which operate on 2D vectors.
    std::array<uint32_t, 4> scalarIds  = {{ 0, 0, 0, 0 }};

    const uint32_t componentCount = src.type.ccount;

    // These types are used in both pack and unpack operations
    const uint32_t t_u32   = getVectorTypeId({ DxbcScalarType::Uint32,  1 });
    const uint32_t t_f32   = getVectorTypeId({ DxbcScalarType::Float32, 1 });
    const uint32_t t_f32v2 = getVectorTypeId({ DxbcScalarType::Float32, 2 });

    // Constant zero-bit pattern, used for packing
    const uint32_t zerof32 = isPack ? m_module.constf32(0.0f) : 0;

    for (uint32_t i = 0; i < componentCount; i++) {
      const DxbcRegisterValue componentValue
        = emitRegisterExtract(src, DxbcRegMask::select(i));

      if (isPack) {  // f32tof16
        const std::array<uint32_t, 2> packIds =
          {{ componentValue.id, zerof32 }};

        scalarIds[i] = m_module.opPackHalf2x16(t_u32,
          m_module.opCompositeConstruct(t_f32v2, packIds.size(), packIds.data()));
      } else {  // f16tof32
        const uint32_t zeroIndex = 0;

        scalarIds[i] = m_module.opCompositeExtract(t_f32,
          m_module.opUnpackHalf2x16(t_f32v2, componentValue.id),
          1, &zeroIndex);
      }
    }

    DxbcRegisterValue result;
    result.type.ctype  = ins.dst[0].dataType;
    result.type.ccount = componentCount;

    uint32_t typeId = getVectorTypeId(result.type);
    result.id = componentCount > 1
      ? m_module.opCompositeConstruct(typeId,
          componentCount, scalarIds.data())
      : scalarIds[0];

    if (isPack) {
      // Some drivers return infinity if the input value is above a certain
      // threshold, but D3D wants us to return infinity only if the input is
      // actually infinite. Fix this up to return the maximum representable
      // 16-bit floating point number instead, but preserve input infinity.
      uint32_t t_bvec = getVectorTypeId({ DxbcScalarType::Bool, componentCount });
      uint32_t f16Infinity = m_module.constuReplicant(0x7C00, componentCount);
      uint32_t f16Unsigned = m_module.constuReplicant(0x7FFF, componentCount);

      uint32_t isInputInf = m_module.opIsInf(t_bvec, src.id);
      uint32_t isValueInf = m_module.opIEqual(t_bvec, f16Infinity,
        m_module.opBitwiseAnd(typeId, result.id, f16Unsigned));

      result.id = m_module.opSelect(getVectorTypeId(result.type),
        m_module.opLogicalAnd(t_bvec, isValueInf, m_module.opLogicalNot(t_bvec, isInputInf)),
        m_module.opISub(typeId, result.id, m_module.constuReplicant(1, componentCount)),
        result.id);
    }

    // Store result in the destination register
    emitRegisterStore(ins.dst[0], result);
  }
    void emitConvertFloat64(
      const DxbcShaderInstruction&  ins) {
    // ftod and dtof take the following operands:
    //  (dst0) Destination operand
    //  (src0) Number to convert
    uint32_t dstBits = ins.dst[0].mask.popCount();

    DxbcRegMask srcMask = isDoubleType(ins.dst[0].dataType)
      ? DxbcRegMask(dstBits >= 2, dstBits >= 4, false, false)
      : DxbcRegMask(dstBits >= 1, dstBits >= 1, dstBits >= 2, dstBits >= 2);

    // Perform actual conversion, destination modifiers are not applied
    DxbcRegisterValue val = emitRegisterLoad(ins.src[0], srcMask);

    DxbcRegisterValue result;
    result.type.ctype  = ins.dst[0].dataType;
    result.type.ccount = val.type.ccount;

    switch (ins.op) {
      case DxbcOpcode::DtoF:
      case DxbcOpcode::FtoD:
        result.id = m_module.opFConvert(
          getVectorTypeId(result.type), val.id);
        break;

      case DxbcOpcode::DtoI:
        result.id = m_module.opConvertFtoS(
          getVectorTypeId(result.type), val.id);
        break;

      case DxbcOpcode::DtoU:
        result.id = m_module.opConvertFtoU(
          getVectorTypeId(result.type), val.id);
        break;

      case DxbcOpcode::ItoD:
        result.id = m_module.opConvertStoF(
          getVectorTypeId(result.type), val.id);
        break;

      case DxbcOpcode::UtoD:
        result.id = m_module.opConvertUtoF(
          getVectorTypeId(result.type), val.id);
        break;

      default:
        Logger::warn(str::format("DxbcCompiler: Unhandled instruction: ", ins.op));
        return;
    }

    emitRegisterStore(ins.dst[0], result);
  }
    void emitHullShaderPhase(
      const DxbcShaderInstruction&  ins) {
    switch (ins.op) {
      case DxbcOpcode::HsDecls: {
        if (m_hs.currPhaseType != DxbcCompilerHsPhase::None)
          Logger::err("DXBC: HsDecls not the first phase in hull shader");

        m_hs.currPhaseType = DxbcCompilerHsPhase::Decl;
      } break;

      case DxbcOpcode::HsControlPointPhase: {
        m_hs.cpPhase = this->emitNewHullShaderControlPointPhase();

        m_hs.currPhaseType = DxbcCompilerHsPhase::ControlPoint;
        m_hs.currPhaseId   = 0;

        m_module.setDebugName(m_hs.cpPhase.functionId, "hs_control_point");
      } break;

      case DxbcOpcode::HsForkPhase: {
        auto phase = this->emitNewHullShaderForkJoinPhase();
        m_hs.forkPhases.push_back(phase);

        m_hs.currPhaseType = DxbcCompilerHsPhase::Fork;
        m_hs.currPhaseId   = m_hs.forkPhases.size() - 1;

        m_module.setDebugName(phase.functionId,
          str::format("hs_fork_", m_hs.currPhaseId).c_str());
      } break;

      case DxbcOpcode::HsJoinPhase: {
        auto phase = this->emitNewHullShaderForkJoinPhase();
        m_hs.joinPhases.push_back(phase);

        m_hs.currPhaseType = DxbcCompilerHsPhase::Join;
        m_hs.currPhaseId   = m_hs.joinPhases.size() - 1;

        m_module.setDebugName(phase.functionId,
          str::format("hs_join_", m_hs.currPhaseId).c_str());
      } break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
    }
  }
    void emitHullShaderInstCnt(
      const DxbcShaderInstruction&  ins) {
    this->getCurrentHsForkJoinPhase()->instanceCount = ins.imm[0].u32;
  }
    void emitInterpolate(
      const DxbcShaderInstruction&  ins) {
    m_module.enableCapability(spv::CapabilityInterpolationFunction);

    // The SPIR-V instructions operate on input variable pointers,
    // which are all declared as four-component float vectors.
    uint32_t registerId = ins.src[0].idx[0].offset;

    DxbcRegisterValue result;
    result.type = getInputRegType(registerId);

    switch (ins.op) {
      case DxbcOpcode::EvalCentroid: {
        result.id = m_module.opInterpolateAtCentroid(
          getVectorTypeId(result.type),
          m_vRegs.at(registerId).id);
      } break;

      case DxbcOpcode::EvalSampleIndex: {
        const DxbcRegisterValue sampleIndex = emitRegisterLoad(
          ins.src[1], DxbcRegMask(true, false, false, false));

        result.id = m_module.opInterpolateAtSample(
          getVectorTypeId(result.type),
          m_vRegs.at(registerId).id,
          sampleIndex.id);
      } break;

      case DxbcOpcode::EvalSnapped: {
        // The offset is encoded as a 4-bit fixed point value
        DxbcRegisterValue offset = emitRegisterLoad(
          ins.src[1], DxbcRegMask(true, true, false, false));
        offset.id = m_module.opBitFieldSExtract(
          getVectorTypeId(offset.type), offset.id,
          m_module.consti32(0), m_module.consti32(4));

        offset.type.ctype = DxbcScalarType::Float32;
        offset.id = m_module.opConvertStoF(
          getVectorTypeId(offset.type), offset.id);

        offset.id = m_module.opFMul(
          getVectorTypeId(offset.type), offset.id,
          m_module.constvec2f32(1.0f / 16.0f, 1.0f / 16.0f));

        result.id = m_module.opInterpolateAtOffset(
          getVectorTypeId(result.type),
          m_vRegs.at(registerId).id,
          offset.id);
      } break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
        return;
    }

    result = emitRegisterSwizzle(result,
      ins.src[0].swizzle, ins.dst[0].mask);
    emitRegisterStore(ins.dst[0], result);
  }
    void emitSparseCheckAccess(
      const DxbcShaderInstruction&  ins) {
    // check_access_mapped has two operands:
    //    (dst0) The destination register
    //    (src0) The residency code
    m_module.enableCapability(spv::CapabilitySparseResidency);

    DxbcRegisterValue srcValue = emitRegisterLoad(ins.src[0], ins.dst[0].mask);

    uint32_t boolId = m_module.opImageSparseTexelsResident(
      m_module.defBoolType(), srcValue.id);

    DxbcRegisterValue dstValue;
    dstValue.type = { DxbcScalarType::Uint32, 1 };
    dstValue.id = m_module.opSelect(getScalarTypeId(DxbcScalarType::Uint32),
      boolId, m_module.constu32(~0u), m_module.constu32(0));

    emitRegisterStore(ins.dst[0], dstValue);
  }
    void emitTextureQuery(
      const DxbcShaderInstruction&  ins) {
    // resinfo has three operands:
    //    (dst0) The destination register
    //    (src0) Resource LOD to query
    //    (src1) Resource to query
    const DxbcBufferInfo resourceInfo = getBufferInfo(ins.src[1]);
    const DxbcResinfoType resinfoType = ins.controls.resinfoType();

    // Read the exact LOD for the image query
    const DxbcRegisterValue mipLod = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    const DxbcScalarType returnType = resinfoType == DxbcResinfoType::Uint
      ? DxbcScalarType::Uint32 : DxbcScalarType::Float32;

    // Query the size of the selected mip level, as well as the
    // total number of mip levels. We will have to combine the
    // result into a four-component vector later.
    DxbcRegisterValue imageSize   = emitQueryTextureSize(ins.src[1], mipLod);
    DxbcRegisterValue imageLevels = emitQueryTextureLods(ins.src[1]);

    // If the mip level is out of bounds, D3D requires us to return
    // zero before applying modifiers, whereas SPIR-V is undefined,
    // so we need to fix it up manually here.
    imageSize.id = m_module.opSelect(getVectorTypeId(imageSize.type),
      m_module.opULessThan(m_module.defBoolType(), mipLod.id, imageLevels.id),
      imageSize.id, emitBuildZeroVector(imageSize.type).id);

    // Convert intermediates to the requested type
    if (returnType == DxbcScalarType::Float32) {
      imageSize.type.ctype = DxbcScalarType::Float32;
      imageSize.id = m_module.opConvertUtoF(
        getVectorTypeId(imageSize.type),
        imageSize.id);

      imageLevels.type.ctype = DxbcScalarType::Float32;
      imageLevels.id = m_module.opConvertUtoF(
        getVectorTypeId(imageLevels.type),
        imageLevels.id);
    }

    // If the selected return type is rcpFloat, we need
    // to compute the reciprocal of the image dimensions,
    // but not the array size, so we need to separate it.
    const uint32_t imageCoordDim = imageSize.type.ccount;

    DxbcRegisterValue imageLayers;
    imageLayers.type = imageSize.type;
    imageLayers.id   = 0;

    if (resinfoType == DxbcResinfoType::RcpFloat && resourceInfo.image.array) {
      imageLayers = emitRegisterExtract(imageSize, DxbcRegMask::select(imageCoordDim - 1));
      imageSize   = emitRegisterExtract(imageSize, DxbcRegMask::firstN(imageCoordDim - 1));
    }

    if (resinfoType == DxbcResinfoType::RcpFloat) {
      imageSize.id = m_module.opFDiv(
        getVectorTypeId(imageSize.type),
        emitBuildConstVecf32(1.0f, 1.0f, 1.0f, 1.0f,
          DxbcRegMask::firstN(imageSize.type.ccount)).id,
        imageSize.id);
    }

    // Concatenate result vectors and scalars to form a
    // 4D vector. Unused components will be set to zero.
    std::array<uint32_t, 4> vectorIds = { imageSize.id, 0, 0, 0 };
    uint32_t numVectorIds = 1;

    if (imageLayers.id != 0)
      vectorIds[numVectorIds++] = imageLayers.id;

    if (imageCoordDim < 3) {
      const uint32_t zero = returnType == DxbcScalarType::Uint32
        ? m_module.constu32(0)
        : m_module.constf32(0.0f);

      for (uint32_t i = imageCoordDim; i < 3; i++)
        vectorIds[numVectorIds++] = zero;
    }

    vectorIds[numVectorIds++] = imageLevels.id;

    // Create the actual result vector
    DxbcRegisterValue result;
    result.type.ctype  = returnType;
    result.type.ccount = 4;
    result.id = m_module.opCompositeConstruct(
      getVectorTypeId(result.type),
      numVectorIds, vectorIds.data());

    // Swizzle components using the resource swizzle
    // and the destination operand's write mask
    result = emitRegisterSwizzle(result,
      ins.src[1].swizzle, ins.dst[0].mask);
    emitRegisterStore(ins.dst[0], result);
  }
    void emitTextureQueryLod(
      const DxbcShaderInstruction&  ins) {
    // All sample instructions have at least these operands:
    //    (dst0) The destination register
    //    (src0) Texture coordinates
    //    (src1) The texture itself
    //    (src2) The sampler object
    const DxbcRegister& texCoordReg = ins.src[0];
    const DxbcRegister& textureReg  = ins.src[1];
    const DxbcRegister& samplerReg  = ins.src[2];

    // Texture and sampler register IDs
    const auto& texture = m_textures.at(textureReg.idx[0].offset);
    const auto& sampler = m_samplers.at(samplerReg.idx[0].offset);

    // Load texture coordinates
    const DxbcRegisterValue coord = emitRegisterLoad(texCoordReg,
      DxbcRegMask::firstN(getTexLayerDim(texture.imageInfo)));

    // Query the LOD. The result is a two-dimensional float32
    // vector containing the mip level and virtual LOD numbers.
    const uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, false);
    const uint32_t queriedLodId = m_module.opImageQueryLod(
      getVectorTypeId({ DxbcScalarType::Float32, 2 }),
      sampledImageId, coord.id);

    // Build the result array vector by filling up
    // the remaining two components with zeroes.
    const uint32_t zero = m_module.constf32(0.0f);
    const std::array<uint32_t, 3> resultIds
      = {{ queriedLodId, zero, zero }};

    DxbcRegisterValue result;
    result.type = DxbcVectorType { DxbcScalarType::Float32, 4 };
    result.id   = m_module.opCompositeConstruct(
      getVectorTypeId(result.type),
      resultIds.size(), resultIds.data());

    result = emitRegisterSwizzle(result, ins.src[1].swizzle, ins.dst[0].mask);
    emitRegisterStore(ins.dst[0], result);
  }
    void emitTextureQueryMs(
      const DxbcShaderInstruction&  ins) {
    // sampleinfo has two operands:
    //    (dst0) The destination register
    //    (src0) Resource to query
    DxbcRegisterValue sampleCount = emitQueryTextureSamples(ins.src[0]);

    if (ins.controls.returnType() != DxbcInstructionReturnType::Uint) {
      sampleCount.type = { DxbcScalarType::Float32, 1 };
      sampleCount.id = m_module.opConvertUtoF(
        getVectorTypeId(sampleCount.type),
        sampleCount.id);
    }

    emitRegisterStore(ins.dst[0], sampleCount);
  }
    void emitTextureQueryMsPos(
      const DxbcShaderInstruction&  ins) {
    // samplepos has three operands:
    //    (dst0) The destination register
    //    (src0) Resource to query
    //    (src1) Sample index
    if (m_samplePositions == 0)
      m_samplePositions = emitSamplePosArray();

    // The lookup index is qual to the sample count plus the
    // sample index, or 0 if the resource cannot be queried.
    DxbcRegisterValue sampleCount = emitQueryTextureSamples(ins.src[0]);
    DxbcRegisterValue sampleIndex = emitRegisterLoad(
      ins.src[1], DxbcRegMask(true, false, false, false));

    uint32_t lookupIndex = m_module.opIAdd(
      getVectorTypeId(sampleCount.type),
      sampleCount.id, sampleIndex.id);

    // Validate the parameters
    uint32_t sampleCountValid = m_module.opULessThanEqual(
      m_module.defBoolType(),
      sampleCount.id,
      m_module.constu32(16));

    uint32_t sampleIndexValid = m_module.opULessThan(
      m_module.defBoolType(),
      sampleIndex.id,
      sampleCount.id);

    // If the lookup cannot be performed, set the lookup
    // index to zero, which will return a zero vector.
    lookupIndex = m_module.opSelect(
      getVectorTypeId(sampleCount.type),
      m_module.opLogicalAnd(
        m_module.defBoolType(),
        sampleCountValid,
        sampleIndexValid),
      lookupIndex,
      m_module.constu32(0));

    // Load sample pos vector and write the masked
    // components to the destination register.
    DxbcRegisterPointer samplePos;
    samplePos.type.ctype  = DxbcScalarType::Float32;
    samplePos.type.ccount = 2;
    samplePos.id = m_module.opAccessChain(
      m_module.defPointerType(
        getVectorTypeId(samplePos.type),
        spv::StorageClassPrivate),
      m_samplePositions, 1, &lookupIndex);

    // Expand to vec4 by appending zeroes
    DxbcRegisterValue result = emitValueLoad(samplePos);

    DxbcRegisterValue zero;
    zero.type.ctype  = DxbcScalarType::Float32;
    zero.type.ccount = 2;
    zero.id = m_module.constvec2f32(0.0f, 0.0f);

    result = emitRegisterConcat(result, zero);

    emitRegisterStore(ins.dst[0],
      emitRegisterSwizzle(result,
        ins.src[0].swizzle,
        ins.dst[0].mask));
  }
    void emitTextureFetch(
      const DxbcShaderInstruction&  ins) {
    // ld has three operands:
    //    (dst0) The destination register
    //    (src0) Source address
    //    (src1) Source texture
    // ld2dms has four operands:
    //    (dst0) The destination register
    //    (src0) Source address
    //    (src1) Source texture
    //    (src2) Sample number
    const auto& texture = m_textures.at(ins.src[1].idx[0].offset);
    const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo);

    bool isMultisampled = ins.op == DxbcOpcode::LdMs
                       || ins.op == DxbcOpcode::LdMsS;

    // Load the texture coordinates. The last component
    // contains the LOD if the resource is an image.
    const DxbcRegisterValue address = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, true, true, true));

    // Additional image operands. This will store
    // the LOD and the address offset if present.
    SpirvImageOperands imageOperands;
    imageOperands.sparse = ins.dstCount == 2;

    if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) {
      const std::array<uint32_t, 3> offsetIds = {
        imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0,
        imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0,
        imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0,
      };

      imageOperands.flags |= spv::ImageOperandsConstOffsetMask;
      imageOperands.sConstOffset = offsetIds[0];

      if (imageLayerDim > 1) {
        imageOperands.sConstOffset = m_module.constComposite(
          getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }),
          imageLayerDim, offsetIds.data());
      }
    }

    // The LOD is not present when reading from
    // a buffer or from a multisample texture.
    if (texture.imageInfo.dim != spv::DimBuffer && texture.imageInfo.ms == 0) {
      DxbcRegisterValue imageLod;

      if (!isMultisampled) {
        imageLod = emitRegisterExtract(
          address, DxbcRegMask(false, false, false, true));
      } else {
        // If we force-disabled MSAA, fetch from LOD 0
        imageLod.type = { DxbcScalarType::Uint32, 1 };
        imageLod.id = m_module.constu32(0);
      }

      imageOperands.flags |= spv::ImageOperandsLodMask;
      imageOperands.sLod = imageLod.id;
    }

    // The ld2dms instruction has a sample index, but we
    // are only allowed to set it for multisample views
    if (isMultisampled && texture.imageInfo.ms == 1) {
      DxbcRegisterValue sampleId = emitRegisterLoad(
        ins.src[2], DxbcRegMask(true, false, false, false));

      imageOperands.flags |= spv::ImageOperandsSampleMask;
      imageOperands.sSampleId = sampleId.id;
    }

    // Extract coordinates from address
    const DxbcRegisterValue coord = emitCalcTexCoord(address, texture.imageInfo);

    // Reading a typed image or buffer view
    // always returns a four-component vector.
    const uint32_t imageId = m_module.opLoad(texture.imageTypeId, texture.varId);

    DxbcVectorType texelType;
    texelType.ctype = texture.sampledType;
    texelType.ccount = 4;

    uint32_t texelTypeId = getVectorTypeId(texelType);
    uint32_t resultTypeId = texelTypeId;
    uint32_t resultId = 0;

    if (imageOperands.sparse)
      resultTypeId = getSparseResultTypeId(texelTypeId);

    resultId = m_module.opImageFetch(resultTypeId,
      imageId, coord.id, imageOperands);

    DxbcRegisterValue result;
    result.type = texelType;
    result.id = imageOperands.sparse
      ? emitExtractSparseTexel(texelTypeId, resultId)
      : resultId;

    // Swizzle components using the texture swizzle
    // and the destination operand's write mask
    result = emitRegisterSwizzle(result,
      ins.src[1].swizzle, ins.dst[0].mask);

    emitRegisterStore(ins.dst[0], result);

    if (imageOperands.sparse)
      emitStoreSparseFeedback(ins.dst[1], resultId);
  }
    void emitTextureGather(
      const DxbcShaderInstruction&  ins) {
    // Gather4 takes the following operands:
    //    (dst0) The destination register
    //    (dst1) The residency code for sparse ops
    //    (src0) Texture coordinates
    //    (src1) The texture itself
    //    (src2) The sampler, with a component selector
    // Gather4C takes the following additional operand:
    //    (src3) The depth reference value
    // The Gather4Po variants take an additional operand
    // which defines an extended constant offset.
    // TODO reduce code duplication by moving some common code
    // in both sample() and gather() into separate methods
    const bool isExtendedGather = ins.op == DxbcOpcode::Gather4Po
                               || ins.op == DxbcOpcode::Gather4PoC
                               || ins.op == DxbcOpcode::Gather4PoS
                               || ins.op == DxbcOpcode::Gather4PoCS;

    const DxbcRegister& texCoordReg = ins.src[0];
    const DxbcRegister& textureReg  = ins.src[1 + isExtendedGather];
    const DxbcRegister& samplerReg  = ins.src[2 + isExtendedGather];

    // Texture and sampler register IDs
    const auto& texture = m_textures.at(textureReg.idx[0].offset);
    const auto& sampler = m_samplers.at(samplerReg.idx[0].offset);

    // Image type, which stores the image dimensions etc.
    const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo);

    // Load the texture coordinates. SPIR-V allows these
    // to be float4 even if not all components are used.
    DxbcRegisterValue coord = emitLoadTexCoord(texCoordReg, texture.imageInfo);

    // Load reference value for depth-compare operations
    const bool isDepthCompare = ins.op == DxbcOpcode::Gather4C
                             || ins.op == DxbcOpcode::Gather4PoC
                             || ins.op == DxbcOpcode::Gather4CS
                             || ins.op == DxbcOpcode::Gather4PoCS;

    const DxbcRegisterValue referenceValue = isDepthCompare
      ? emitRegisterLoad(ins.src[3 + isExtendedGather],
          DxbcRegMask(true, false, false, false))
      : DxbcRegisterValue();

    // Accumulate additional image operands.
    SpirvImageOperands imageOperands;
    imageOperands.sparse = ins.dstCount == 2;

    if (isExtendedGather) {
      m_module.enableCapability(spv::CapabilityImageGatherExtended);

      DxbcRegisterValue gatherOffset = emitRegisterLoad(
        ins.src[1], DxbcRegMask::firstN(imageLayerDim));

      imageOperands.flags |= spv::ImageOperandsOffsetMask;
      imageOperands.gOffset = gatherOffset.id;
    } else if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) {
      const std::array<uint32_t, 3> offsetIds = {
        imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0,
        imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0,
        imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0,
      };

      imageOperands.flags |= spv::ImageOperandsConstOffsetMask;
      imageOperands.sConstOffset = offsetIds[0];

      if (imageLayerDim > 1) {
        imageOperands.sConstOffset = m_module.constComposite(
          getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }),
          imageLayerDim, offsetIds.data());
      }
    }

    // Gathering texels always returns a four-component
    // vector, even for the depth-compare variants.
    uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, isDepthCompare);

    DxbcVectorType texelType;
    texelType.ctype = texture.sampledType;
    texelType.ccount = 4;

    uint32_t texelTypeId = getVectorTypeId(texelType);
    uint32_t resultTypeId = texelTypeId;
    uint32_t resultId = 0;

    if (imageOperands.sparse)
      resultTypeId = getSparseResultTypeId(texelTypeId);

    if (sampledImageId) {
      switch (ins.op) {
        // Simple image gather operation
        case DxbcOpcode::Gather4:
        case DxbcOpcode::Gather4S:
        case DxbcOpcode::Gather4Po:
        case DxbcOpcode::Gather4PoS: {
          resultId = m_module.opImageGather(
            resultTypeId, sampledImageId, coord.id,
            m_module.consti32(samplerReg.swizzle[0]),
            imageOperands);
        } break;

        // Depth-compare operation
        case DxbcOpcode::Gather4C:
        case DxbcOpcode::Gather4CS:
        case DxbcOpcode::Gather4PoC:
        case DxbcOpcode::Gather4PoCS: {
          resultId = m_module.opImageDrefGather(
            resultTypeId, sampledImageId, coord.id,
            referenceValue.id, imageOperands);
        } break;

        default:
          Logger::warn(str::format(
            "DxbcCompiler: Unhandled instruction: ",
            ins.op));
          return;
      }
    } else {
      Logger::warn(str::format("DxbcCompiler: ", ins.op, ": Unsupported image type"));
      resultId = m_module.constNull(resultTypeId);
    }

    // If necessary, deal with the sparse result
    DxbcRegisterValue result;
    result.type = texelType;
    result.id = imageOperands.sparse
      ? emitExtractSparseTexel(texelTypeId, resultId)
      : resultId;

    // Swizzle components using the texture swizzle
    // and the destination operand's write mask
    result = emitRegisterSwizzle(result,
      textureReg.swizzle, ins.dst[0].mask);

    emitRegisterStore(ins.dst[0], result);

    if (imageOperands.sparse)
      emitStoreSparseFeedback(ins.dst[1], resultId);
  }
    void emitTextureSample(
      const DxbcShaderInstruction&  ins) {
    // All sample instructions have at least these operands:
    //    (dst0) The destination register
    //    (src0) Texture coordinates
    //    (src1) The texture itself
    //    (src2) The sampler object
    const DxbcRegister& texCoordReg = ins.src[0];
    const DxbcRegister& textureReg  = ins.src[1];
    const DxbcRegister& samplerReg  = ins.src[2];

    // Texture and sampler register IDs
    const auto& texture = m_textures.at(textureReg.idx[0].offset);
    const auto& sampler = m_samplers.at(samplerReg.idx[0].offset);
    const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo);

    // Load the texture coordinates. SPIR-V allows these
    // to be float4 even if not all components are used.
    DxbcRegisterValue coord = emitLoadTexCoord(texCoordReg, texture.imageInfo);

    // Load reference value for depth-compare operations
    const bool isDepthCompare = ins.op == DxbcOpcode::SampleC
                             || ins.op == DxbcOpcode::SampleClz
                             || ins.op == DxbcOpcode::SampleCClampS
                             || ins.op == DxbcOpcode::SampleClzS;

    const DxbcRegisterValue referenceValue = isDepthCompare
      ? emitRegisterLoad(ins.src[3], DxbcRegMask(true, false, false, false))
      : DxbcRegisterValue();

    // Load explicit gradients for sample operations that require them
    const bool hasExplicitGradients = ins.op == DxbcOpcode::SampleD
                                   || ins.op == DxbcOpcode::SampleDClampS;

    const DxbcRegisterValue explicitGradientX = hasExplicitGradients
      ? emitRegisterLoad(ins.src[3], DxbcRegMask::firstN(imageLayerDim))
      : DxbcRegisterValue();

    const DxbcRegisterValue explicitGradientY = hasExplicitGradients
      ? emitRegisterLoad(ins.src[4], DxbcRegMask::firstN(imageLayerDim))
      : DxbcRegisterValue();

    // LOD for certain sample operations
    const bool hasLod = ins.op == DxbcOpcode::SampleL
                     || ins.op == DxbcOpcode::SampleLS
                     || ins.op == DxbcOpcode::SampleB
                     || ins.op == DxbcOpcode::SampleBClampS;

    const DxbcRegisterValue lod = hasLod
      ? emitRegisterLoad(ins.src[3], DxbcRegMask(true, false, false, false))
      : DxbcRegisterValue();

    // Min LOD for certain sparse operations
    const bool hasMinLod = ins.op == DxbcOpcode::SampleClampS
                        || ins.op == DxbcOpcode::SampleBClampS
                        || ins.op == DxbcOpcode::SampleDClampS
                        || ins.op == DxbcOpcode::SampleCClampS;

    const DxbcRegisterValue minLod = hasMinLod && ins.src[ins.srcCount - 1].type != DxbcOperandType::Null
      ? emitRegisterLoad(ins.src[ins.srcCount - 1], DxbcRegMask(true, false, false, false))
      : DxbcRegisterValue();

    // Accumulate additional image operands. These are
    // not part of the actual operand token in SPIR-V.
    SpirvImageOperands imageOperands;
    imageOperands.sparse = ins.dstCount == 2;

    if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) {
      const std::array<uint32_t, 3> offsetIds = {
        imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0,
        imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0,
        imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0,
      };

      imageOperands.flags |= spv::ImageOperandsConstOffsetMask;
      imageOperands.sConstOffset = offsetIds[0];

      if (imageLayerDim > 1) {
        imageOperands.sConstOffset = m_module.constComposite(
          getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }),
          imageLayerDim, offsetIds.data());
      }
    }

    if (hasMinLod) {
      m_module.enableCapability(spv::CapabilityMinLod);

      imageOperands.flags |= spv::ImageOperandsMinLodMask;
      imageOperands.sMinLod = minLod.id;
    }

    // Combine the texture and the sampler into a sampled image
    uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, isDepthCompare);

    // Sampling an image always returns a four-component
    // vector, whereas depth-compare ops return a scalar.
    DxbcVectorType texelType;
    texelType.ctype  = texture.sampledType;
    texelType.ccount = isDepthCompare ? 1 : 4;

    uint32_t texelTypeId = getVectorTypeId(texelType);
    uint32_t resultTypeId = texelTypeId;
    uint32_t resultId = 0;

    if (imageOperands.sparse)
      resultTypeId = getSparseResultTypeId(texelTypeId);

    if (sampledImageId) {
      switch (ins.op) {
        // Simple image sample operation
        case DxbcOpcode::Sample:
        case DxbcOpcode::SampleClampS: {
          resultId = m_module.opImageSampleImplicitLod(
            resultTypeId, sampledImageId, coord.id,
            imageOperands);
        } break;

        // Depth-compare operation
        case DxbcOpcode::SampleC:
        case DxbcOpcode::SampleCClampS: {
          resultId = m_module.opImageSampleDrefImplicitLod(
            resultTypeId, sampledImageId, coord.id,
            referenceValue.id, imageOperands);
        } break;

        // Depth-compare operation on mip level zero
        case DxbcOpcode::SampleClz:
        case DxbcOpcode::SampleClzS: {
          imageOperands.flags |= spv::ImageOperandsLodMask;
          imageOperands.sLod = m_module.constf32(0.0f);

          resultId = m_module.opImageSampleDrefExplicitLod(
            resultTypeId, sampledImageId, coord.id,
            referenceValue.id, imageOperands);
        } break;

        // Sample operation with explicit gradients
        case DxbcOpcode::SampleD:
        case DxbcOpcode::SampleDClampS: {
          imageOperands.flags |= spv::ImageOperandsGradMask;
          imageOperands.sGradX = explicitGradientX.id;
          imageOperands.sGradY = explicitGradientY.id;

          resultId = m_module.opImageSampleExplicitLod(
            resultTypeId, sampledImageId, coord.id,
            imageOperands);
        } break;

        // Sample operation with explicit LOD
        case DxbcOpcode::SampleL:
        case DxbcOpcode::SampleLS: {
          imageOperands.flags |= spv::ImageOperandsLodMask;
          imageOperands.sLod = lod.id;

          resultId = m_module.opImageSampleExplicitLod(
            resultTypeId, sampledImageId, coord.id,
            imageOperands);
        } break;

        // Sample operation with LOD bias
        case DxbcOpcode::SampleB:
        case DxbcOpcode::SampleBClampS: {
          imageOperands.flags |= spv::ImageOperandsBiasMask;
          imageOperands.sLodBias = lod.id;

          resultId = m_module.opImageSampleImplicitLod(
            resultTypeId, sampledImageId, coord.id,
            imageOperands);
        } break;

        default:
          Logger::warn(str::format(
            "DxbcCompiler: Unhandled instruction: ",
            ins.op));
          return;
      }
    } else {
      Logger::warn(str::format("DxbcCompiler: ", ins.op, ": Unsupported image type"));
      resultId = m_module.constNull(resultTypeId);
    }

    DxbcRegisterValue result;
    result.type = texelType;
    result.id = imageOperands.sparse
      ? emitExtractSparseTexel(texelTypeId, resultId)
      : resultId;

    // Swizzle components using the texture swizzle
    // and the destination operand's write mask
    if (result.type.ccount != 1) {
      result = emitRegisterSwizzle(result,
        textureReg.swizzle, ins.dst[0].mask);
    }

    emitRegisterStore(ins.dst[0], result);

    if (imageOperands.sparse)
      emitStoreSparseFeedback(ins.dst[1], resultId);
  }
    void emitTypedUavLoad(
      const DxbcShaderInstruction&  ins) {
    // load_uav_typed has three operands:
    //    (dst0) The destination register
    //    (src0) The texture or buffer coordinates
    //    (src1) The UAV to load from
    const uint32_t registerId = ins.src[1].idx[0].offset;
    const DxbcUav uavInfo = m_uavs.at(registerId);

    emitUavBarrier(uint64_t(1u) << registerId, 0u);

    // Load texture coordinates
    DxbcRegisterValue texCoord = emitLoadTexCoord(
      ins.src[0], uavInfo.imageInfo);

    SpirvImageOperands imageOperands;
    imageOperands.sparse = ins.dstCount == 2;

    if (uavInfo.coherence) {
      imageOperands.flags |= spv::ImageOperandsNonPrivateTexelMask
                          |  spv::ImageOperandsMakeTexelVisibleMask;
      imageOperands.makeVisible = m_module.constu32(uavInfo.coherence);
    }

    DxbcVectorType texelType;
    texelType.ctype = uavInfo.sampledType;
    texelType.ccount = 4;

    uint32_t texelTypeId = getVectorTypeId(texelType);
    uint32_t resultTypeId = texelTypeId;
    uint32_t resultId = 0;

    if (imageOperands.sparse)
      resultTypeId = getSparseResultTypeId(texelTypeId);

    // Load source value from the UAV
    resultId = m_module.opImageRead(resultTypeId,
      m_module.opLoad(uavInfo.imageTypeId, uavInfo.varId),
      texCoord.id, imageOperands);

    // Apply component swizzle and mask
    DxbcRegisterValue uavValue;
    uavValue.type = texelType;
    uavValue.id = imageOperands.sparse
      ? emitExtractSparseTexel(texelTypeId, resultId)
      : resultId;

    uavValue = emitRegisterSwizzle(uavValue,
      ins.src[1].swizzle, ins.dst[0].mask);

    emitRegisterStore(ins.dst[0], uavValue);

    if (imageOperands.sparse)
      emitStoreSparseFeedback(ins.dst[1], resultId);
  }
    void emitTypedUavStore(
      const DxbcShaderInstruction&  ins) {
    // store_uav_typed has three operands:
    //    (dst0) The destination UAV
    //    (src0) The texture or buffer coordinates
    //    (src1) The value to store
    const DxbcBufferInfo uavInfo = getBufferInfo(ins.dst[0]);
    emitUavBarrier(0u, uint64_t(1u) << ins.dst[0].idx[0].offset);

    // Set image operands for coherent access if necessary
    SpirvImageOperands imageOperands;

    if (uavInfo.coherence) {
      imageOperands.flags |= spv::ImageOperandsNonPrivateTexelMask
                          |  spv::ImageOperandsMakeTexelAvailableMask;
      imageOperands.makeAvailable = m_module.constu32(uavInfo.coherence);
    }

    // Load texture coordinates
    DxbcRegisterValue texCoord = emitLoadTexCoord(ins.src[0], uavInfo.image);

    // Load the value that will be written to the image. We'll
    // have to cast it to the component type of the image.
    const DxbcRegisterValue texValue = emitRegisterBitcast(
      emitRegisterLoad(ins.src[1], DxbcRegMask(true, true, true, true)),
      uavInfo.stype);

    // Write the given value to the image
    m_module.opImageWrite(
      m_module.opLoad(uavInfo.typeId, uavInfo.varId),
      texCoord.id, texValue.id, imageOperands);
  }
    void emitControlFlowIf(
      const DxbcShaderInstruction&  ins) {
    // Load the first component of the condition
    // operand and perform a zero test on it.
    const DxbcRegisterValue condition = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    // Declare the 'if' block. We do not know if there
    // will be an 'else' block or not, so we'll assume
    // that there is one and leave it empty otherwise.
    DxbcCfgBlock block;
    block.type = DxbcCfgBlockType::If;
    block.b_if.ztestId   = emitRegisterZeroTest(condition, ins.controls.zeroTest()).id;
    block.b_if.labelIf   = m_module.allocateId();
    block.b_if.labelElse = 0;
    block.b_if.labelEnd  = m_module.allocateId();
    block.b_if.headerPtr = m_module.getInsertionPtr();
    m_controlFlowBlocks.push_back(block);

    // We'll insert the branch instruction when closing
    // the block, since we don't know whether or not an
    // else block is needed right now.
    m_module.opLabel(block.b_if.labelIf);
  }
    void emitControlFlowElse(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If
     || m_controlFlowBlocks.back().b_if.labelElse != 0)
      throw DxvkError("DxbcCompiler: 'Else' without 'If' found");

    // Set the 'Else' flag so that we do
    // not insert a dummy block on 'EndIf'
    DxbcCfgBlock& block = m_controlFlowBlocks.back();
    block.b_if.labelElse = m_module.allocateId();

    // Close the 'If' block by branching to
    // the merge block we declared earlier
    m_module.opBranch(block.b_if.labelEnd);
    m_module.opLabel (block.b_if.labelElse);
  }
    void emitControlFlowEndIf(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If)
      throw DxvkError("DxbcCompiler: 'EndIf' without 'If' found");

    // Remove the block from the stack, it's closed
    DxbcCfgBlock block = m_controlFlowBlocks.back();
    m_controlFlowBlocks.pop_back();

    // Write out the 'if' header
    m_module.beginInsertion(block.b_if.headerPtr);

    m_module.opSelectionMerge(
      block.b_if.labelEnd,
      spv::SelectionControlMaskNone);

    m_module.opBranchConditional(
      block.b_if.ztestId,
      block.b_if.labelIf,
      block.b_if.labelElse != 0
        ? block.b_if.labelElse
        : block.b_if.labelEnd);

    m_module.endInsertion();

    // End the active 'if' or 'else' block
    m_module.opBranch(block.b_if.labelEnd);
    m_module.opLabel (block.b_if.labelEnd);
  }
    void emitControlFlowSwitch(
      const DxbcShaderInstruction&  ins) {
    // Load the selector as a scalar unsigned integer
    const DxbcRegisterValue selector = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    // Declare switch block. We cannot insert the switch
    // instruction itself yet because the number of case
    // statements and blocks is unknown at this point.
    DxbcCfgBlock block;
    block.type = DxbcCfgBlockType::Switch;
    block.b_switch.insertPtr    = m_module.getInsertionPtr();
    block.b_switch.selectorId   = selector.id;
    block.b_switch.labelBreak   = m_module.allocateId();
    block.b_switch.labelCase    = m_module.allocateId();
    block.b_switch.labelDefault = 0;
    block.b_switch.labelCases   = nullptr;
    m_controlFlowBlocks.push_back(block);

    // Define the first 'case' label
    m_module.opLabel(block.b_switch.labelCase);
  }
    void emitControlFlowCase(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
      throw DxvkError("DxbcCompiler: 'Case' without 'Switch' found");

    // The source operand must be a 32-bit immediate.
    if (ins.src[0].type != DxbcOperandType::Imm32)
      throw DxvkError("DxbcCompiler: Invalid operand type for 'Case'");

    // Use the last label allocated for 'case'.
    DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch;

    if (caseBlockIsFallthrough()) {
      block->labelCase = m_module.allocateId();

      m_module.opBranch(block->labelCase);
      m_module.opLabel (block->labelCase);
    }

    DxbcSwitchLabel label;
    label.desc.literal = ins.src[0].imm.u32_1;
    label.desc.labelId = block->labelCase;
    label.next         = block->labelCases;
    block->labelCases = new DxbcSwitchLabel(label);
  }
    void emitControlFlowDefault(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
      throw DxvkError("DxbcCompiler: 'Default' without 'Switch' found");

    DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch;

    if (caseBlockIsFallthrough()) {
      block->labelCase = m_module.allocateId();

      m_module.opBranch(block->labelCase);
      m_module.opLabel (block->labelCase);
    }

    // Set the last label allocated for 'case' as the default label.
    block->labelDefault = block->labelCase;
  }
    void emitControlFlowEndSwitch(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
      throw DxvkError("DxbcCompiler: 'EndSwitch' without 'Switch' found");

    // Remove the block from the stack, it's closed
    DxbcCfgBlock block = m_controlFlowBlocks.back();
    m_controlFlowBlocks.pop_back();

    if (!block.b_switch.labelDefault) {
      block.b_switch.labelDefault = caseBlockIsFallthrough()
        ? block.b_switch.labelBreak
        : block.b_switch.labelCase;
    }

    // Close the current 'case' block
    m_module.opBranch(block.b_switch.labelBreak);

    // Insert the 'switch' statement. For that, we need to
    // gather all the literal-label pairs for the construct.
    m_module.beginInsertion(block.b_switch.insertPtr);
    m_module.opSelectionMerge(
      block.b_switch.labelBreak,
      spv::SelectionControlMaskNone);

    // We'll restore the original order of the case labels here
    std::vector<SpirvSwitchCaseLabel> jumpTargets;
    for (auto i = block.b_switch.labelCases; i != nullptr; i = i->next)
      jumpTargets.insert(jumpTargets.begin(), i->desc);

    m_module.opSwitch(
      block.b_switch.selectorId,
      block.b_switch.labelDefault,
      jumpTargets.size(),
      jumpTargets.data());
    m_module.endInsertion();

    // Destroy the list of case labels
    // FIXME we're leaking memory if compilation fails.
    DxbcSwitchLabel* caseLabel = block.b_switch.labelCases;

    while (caseLabel != nullptr)
      delete std::exchange(caseLabel, caseLabel->next);

    // Begin new block after switch blocks
    m_module.opLabel(block.b_switch.labelBreak);
  }
    void emitControlFlowLoop(
      const DxbcShaderInstruction&  ins) {
    // Declare the 'loop' block
    DxbcCfgBlock block;
    block.type = DxbcCfgBlockType::Loop;
    block.b_loop.labelHeader   = m_module.allocateId();
    block.b_loop.labelBegin    = m_module.allocateId();
    block.b_loop.labelContinue = m_module.allocateId();
    block.b_loop.labelBreak    = m_module.allocateId();
    m_controlFlowBlocks.push_back(block);

    m_module.opBranch(block.b_loop.labelHeader);
    m_module.opLabel (block.b_loop.labelHeader);

    m_module.opLoopMerge(
      block.b_loop.labelBreak,
      block.b_loop.labelContinue,
      spv::LoopControlMaskNone);

    m_module.opBranch(block.b_loop.labelBegin);
    m_module.opLabel (block.b_loop.labelBegin);
  }
    void emitControlFlowEndLoop(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() == 0
     || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Loop)
      throw DxvkError("DxbcCompiler: 'EndLoop' without 'Loop' found");

    // Remove the block from the stack, it's closed
    const DxbcCfgBlock block = m_controlFlowBlocks.back();
    m_controlFlowBlocks.pop_back();

    // Declare the continue block
    m_module.opBranch(block.b_loop.labelContinue);
    m_module.opLabel (block.b_loop.labelContinue);

    // Declare the merge block
    m_module.opBranch(block.b_loop.labelHeader);
    m_module.opLabel (block.b_loop.labelBreak);
  }
    void emitControlFlowBreak(
      const DxbcShaderInstruction&  ins) {
    const bool isBreak = ins.op == DxbcOpcode::Break;

    DxbcCfgBlock* cfgBlock = isBreak
      ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch })
      : cfgFindBlock({ DxbcCfgBlockType::Loop });

    if (cfgBlock == nullptr)
      throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' or 'Switch' found");

    if (cfgBlock->type == DxbcCfgBlockType::Loop) {
      m_module.opBranch(isBreak
        ? cfgBlock->b_loop.labelBreak
        : cfgBlock->b_loop.labelContinue);
    } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ {
      m_module.opBranch(cfgBlock->b_switch.labelBreak);
    }

    // Subsequent instructions assume that there is an open block
    const uint32_t labelId = m_module.allocateId();
    m_module.opLabel(labelId);

    // If this is on the same level as a switch-case construct,
    // rather than being nested inside an 'if' statement, close
    // the current 'case' block.
    if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch)
      cfgBlock->b_switch.labelCase = labelId;
  }
    void emitControlFlowBreakc(
      const DxbcShaderInstruction&  ins) {
    const bool isBreak = ins.op == DxbcOpcode::Breakc;

    DxbcCfgBlock* cfgBlock = isBreak
      ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch })
      : cfgFindBlock({ DxbcCfgBlockType::Loop });

    if (cfgBlock == nullptr)
      throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' or 'Switch' found");

    // Perform zero test on the first component of the condition
    const DxbcRegisterValue condition = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    const DxbcRegisterValue zeroTest = emitRegisterZeroTest(
      condition, ins.controls.zeroTest());

    // We basically have to wrap this into an 'if' block
    const uint32_t breakBlock = m_module.allocateId();
    const uint32_t mergeBlock = m_module.allocateId();

    m_module.opSelectionMerge(mergeBlock,
      spv::SelectionControlMaskNone);

    m_module.opBranchConditional(
      zeroTest.id, breakBlock, mergeBlock);

    m_module.opLabel(breakBlock);

    if (cfgBlock->type == DxbcCfgBlockType::Loop) {
      m_module.opBranch(isBreak
        ? cfgBlock->b_loop.labelBreak
        : cfgBlock->b_loop.labelContinue);
    } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ {
      m_module.opBranch(cfgBlock->b_switch.labelBreak);
    }

    m_module.opLabel(mergeBlock);
  }
    void emitControlFlowRet(
      const DxbcShaderInstruction&  ins) {
    if (m_controlFlowBlocks.size() != 0) {
      uint32_t labelId = m_module.allocateId();

      m_module.opReturn();
      m_module.opLabel(labelId);

      // return can be used in place of break to terminate a case block
      if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch)
        m_controlFlowBlocks.back().b_switch.labelCase = labelId;

      m_topLevelIsUniform = false;
    } else {
      // Last instruction in the current function
      this->emitFunctionEnd();
    }
  }
    void emitControlFlowRetc(
      const DxbcShaderInstruction&  ins) {
    // Perform zero test on the first component of the condition
    const DxbcRegisterValue condition = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    const DxbcRegisterValue zeroTest = emitRegisterZeroTest(
      condition, ins.controls.zeroTest());

    // We basically have to wrap this into an 'if' block
    const uint32_t returnLabel = m_module.allocateId();
    const uint32_t continueLabel = m_module.allocateId();

    m_module.opSelectionMerge(continueLabel,
      spv::SelectionControlMaskNone);

    m_module.opBranchConditional(
      zeroTest.id, returnLabel, continueLabel);

    m_module.opLabel(returnLabel);
    m_module.opReturn();

    m_module.opLabel(continueLabel);

    // The return condition may be non-uniform
    m_topLevelIsUniform = false;
  }
    void emitControlFlowDiscard(
      const DxbcShaderInstruction&  ins) {
    // Discard actually has an operand that determines
    // whether or not the fragment should be discarded
    const DxbcRegisterValue condition = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    const DxbcRegisterValue zeroTest = emitRegisterZeroTest(
      condition, ins.controls.zeroTest());

    DxbcConditional cond;
    cond.labelIf  = m_module.allocateId();
    cond.labelEnd = m_module.allocateId();

    m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone);
    m_module.opBranchConditional(zeroTest.id, cond.labelIf, cond.labelEnd);

    m_module.opLabel(cond.labelIf);
    m_module.opDemoteToHelperInvocation();
    m_module.opBranch(cond.labelEnd);

    m_module.opLabel(cond.labelEnd);

    m_module.enableCapability(spv::CapabilityDemoteToHelperInvocation);

    // Discard is just retc in a trenchcoat
    m_topLevelIsUniform = false;
  }
    void emitControlFlowLabel(
      const DxbcShaderInstruction&  ins) {
    uint32_t functionNr = ins.dst[0].idx[0].offset;
    uint32_t functionId = getFunctionId(functionNr);

    this->emitFunctionBegin(
      functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));

    m_module.opLabel(m_module.allocateId());
    m_module.setDebugName(functionId, str::format("label", functionNr).c_str());

    m_insideFunction = true;

    // We have to assume that this function gets
    // called from non-uniform control flow
    m_topLevelIsUniform = false;
  }
    void emitControlFlowCall(
      const DxbcShaderInstruction&  ins) {
    uint32_t functionNr = ins.src[0].idx[0].offset;
    uint32_t functionId = getFunctionId(functionNr);

    m_module.opFunctionCall(
      m_module.defVoidType(),
      functionId, 0, nullptr);
  }
    void emitControlFlowCallc(
      const DxbcShaderInstruction&  ins) {
    uint32_t functionNr = ins.src[1].idx[0].offset;
    uint32_t functionId = getFunctionId(functionNr);

    // Perform zero test on the first component of the condition
    const DxbcRegisterValue condition = emitRegisterLoad(
      ins.src[0], DxbcRegMask(true, false, false, false));

    const DxbcRegisterValue zeroTest = emitRegisterZeroTest(
      condition, ins.controls.zeroTest());

    // We basically have to wrap this into an 'if' block
    const uint32_t callLabel = m_module.allocateId();
    const uint32_t skipLabel = m_module.allocateId();

    m_module.opSelectionMerge(skipLabel,
      spv::SelectionControlMaskNone);

    m_module.opBranchConditional(
      zeroTest.id, callLabel, skipLabel);

    m_module.opLabel(callLabel);
    m_module.opFunctionCall(
      m_module.defVoidType(),
      functionId, 0, nullptr);

    m_module.opBranch(skipLabel);
    m_module.opLabel(skipLabel);
  }
    void emitControlFlow(
      const DxbcShaderInstruction&  ins) {
    switch (ins.op) {
      case DxbcOpcode::If:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowIf(ins);
        break;

      case DxbcOpcode::Else:
        this->emitControlFlowElse(ins);
        break;

      case DxbcOpcode::EndIf:
        this->emitControlFlowEndIf(ins);
        this->emitUavBarrier(0, 0);
        break;

      case DxbcOpcode::Switch:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowSwitch(ins);
        break;

      case DxbcOpcode::Case:
        this->emitControlFlowCase(ins);
        break;

      case DxbcOpcode::Default:
        this->emitControlFlowDefault(ins);
        break;

      case DxbcOpcode::EndSwitch:
        this->emitControlFlowEndSwitch(ins);
        this->emitUavBarrier(0, 0);
        break;

      case DxbcOpcode::Loop:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowLoop(ins);
        break;

      case DxbcOpcode::EndLoop:
        this->emitControlFlowEndLoop(ins);
        this->emitUavBarrier(0, 0);
        break;

      case DxbcOpcode::Break:
      case DxbcOpcode::Continue:
        this->emitControlFlowBreak(ins);
        break;

      case DxbcOpcode::Breakc:
      case DxbcOpcode::Continuec:
        this->emitControlFlowBreakc(ins);
        break;

      case DxbcOpcode::Ret:
        this->emitControlFlowRet(ins);
        break;

      case DxbcOpcode::Retc:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowRetc(ins);
        break;

      case DxbcOpcode::Discard:
        this->emitControlFlowDiscard(ins);
        break;

      case DxbcOpcode::Label:
        this->emitControlFlowLabel(ins);
        break;

      case DxbcOpcode::Call:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowCall(ins);
        this->emitUavBarrier(-1, -1);
        break;

      case DxbcOpcode::Callc:
        this->emitUavBarrier(0, 0);
        this->emitControlFlowCallc(ins);
        this->emitUavBarrier(-1, -1);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled instruction: ",
          ins.op));
    }
  }
    DxbcRegisterValue emitBuildConstVecf32(
            float                   x,
            float                   y,
            float                   z,
            float                   w,
      const DxbcRegMask&            writeMask) {
    // TODO refactor these functions into one single template
    std::array<uint32_t, 4> ids = { 0, 0, 0, 0 };
    uint32_t componentIndex = 0;

    if (writeMask[0]) ids[componentIndex++] = m_module.constf32(x);
    if (writeMask[1]) ids[componentIndex++] = m_module.constf32(y);
    if (writeMask[2]) ids[componentIndex++] = m_module.constf32(z);
    if (writeMask[3]) ids[componentIndex++] = m_module.constf32(w);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Float32;
    result.type.ccount = componentIndex;
    result.id = componentIndex > 1
      ? m_module.constComposite(
          getVectorTypeId(result.type),
          componentIndex, ids.data())
      : ids[0];
    return result;
  }
    DxbcRegisterValue emitBuildConstVecu32(
            uint32_t                x,
            uint32_t                y,
            uint32_t                z,
            uint32_t                w,
      const DxbcRegMask&            writeMask) {
    std::array<uint32_t, 4> ids = { 0, 0, 0, 0 };
    uint32_t componentIndex = 0;

    if (writeMask[0]) ids[componentIndex++] = m_module.constu32(x);
    if (writeMask[1]) ids[componentIndex++] = m_module.constu32(y);
    if (writeMask[2]) ids[componentIndex++] = m_module.constu32(z);
    if (writeMask[3]) ids[componentIndex++] = m_module.constu32(w);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = componentIndex;
    result.id = componentIndex > 1
      ? m_module.constComposite(
          getVectorTypeId(result.type),
          componentIndex, ids.data())
      : ids[0];
    return result;
  }
    DxbcRegisterValue emitBuildConstVeci32(
            int32_t                 x,
            int32_t                 y,
            int32_t                 z,
            int32_t                 w,
      const DxbcRegMask&            writeMask) {
    std::array<uint32_t, 4> ids = { 0, 0, 0, 0 };
    uint32_t componentIndex = 0;

    if (writeMask[0]) ids[componentIndex++] = m_module.consti32(x);
    if (writeMask[1]) ids[componentIndex++] = m_module.consti32(y);
    if (writeMask[2]) ids[componentIndex++] = m_module.consti32(z);
    if (writeMask[3]) ids[componentIndex++] = m_module.consti32(w);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Sint32;
    result.type.ccount = componentIndex;
    result.id = componentIndex > 1
      ? m_module.constComposite(
          getVectorTypeId(result.type),
          componentIndex, ids.data())
      : ids[0];
    return result;
  }
    DxbcRegisterValue emitBuildConstVecf64(
            double                  xy,
            double                  zw,
      const DxbcRegMask&            writeMask) {
    std::array<uint32_t, 2> ids = { 0, 0 };
    uint32_t componentIndex = 0;

    if (writeMask[0] && writeMask[1]) ids[componentIndex++] = m_module.constf64(xy);
    if (writeMask[2] && writeMask[3]) ids[componentIndex++] = m_module.constf64(zw);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Float64;
    result.type.ccount = componentIndex;
    result.id = componentIndex > 1
      ? m_module.constComposite(
          getVectorTypeId(result.type),
          componentIndex, ids.data())
      : ids[0];
    return result;
  }
    DxbcRegisterValue emitBuildVector(
            DxbcRegisterValue       scalar,
            uint32_t                count) {
    if (count == 1)
      return scalar;

    std::array<uint32_t, 4> scalarIds =
      { scalar.id, scalar.id, scalar.id, scalar.id };

    DxbcRegisterValue result;
    result.type.ctype = scalar.type.ctype;
    result.type.ccount = count;
    result.id = m_module.constComposite(
      getVectorTypeId(result.type),
      count, scalarIds.data());
    return result;
  }
    DxbcRegisterValue emitBuildZeroVector(
            DxbcVectorType          type) {
    DxbcRegisterValue result;
    result.type.ctype = type.ctype;
    result.type.ccount = 1;

    switch (type.ctype) {
      case DxbcScalarType::Float32: result.id = m_module.constf32(0.0f); break;
      case DxbcScalarType::Uint32:  result.id = m_module.constu32(0u); break;
      case DxbcScalarType::Sint32:  result.id = m_module.consti32(0); break;
      default: throw DxvkError("DxbcCompiler: Invalid scalar type");
    }

    return emitBuildVector(result, type.ccount);
  }
    DxbcRegisterValue emitRegisterBitcast(
            DxbcRegisterValue       srcValue,
            DxbcScalarType          dstType) {
    DxbcScalarType srcType = srcValue.type.ctype;

    if (srcType == dstType)
      return srcValue;

    DxbcRegisterValue result;
    result.type.ctype  = dstType;
    result.type.ccount = srcValue.type.ccount;

    if (isDoubleType(srcType)) result.type.ccount *= 2;
    if (isDoubleType(dstType)) result.type.ccount /= 2;

    result.id = m_module.opBitcast(
      getVectorTypeId(result.type),
      srcValue.id);
    return result;
  }
    DxbcRegisterValue emitRegisterSwizzle(
            DxbcRegisterValue       value,
            DxbcRegSwizzle          swizzle,
            DxbcRegMask             writeMask) {
    if (value.type.ccount == 1)
      return emitRegisterExtend(value, writeMask.popCount());

    std::array<uint32_t, 4> indices;

    uint32_t dstIndex = 0;

    for (uint32_t i = 0; i < 4; i++) {
      if (writeMask[i])
        indices[dstIndex++] = swizzle[i];
    }

    // If the swizzle combined with the mask can be reduced
    // to a no-op, we don't need to insert any instructions.
    bool isIdentitySwizzle = dstIndex == value.type.ccount;

    for (uint32_t i = 0; i < dstIndex && isIdentitySwizzle; i++)
      isIdentitySwizzle &= indices[i] == i;

    if (isIdentitySwizzle)
      return value;

    // Use OpCompositeExtract if the resulting vector contains
    // only one component, and OpVectorShuffle if it is a vector.
    DxbcRegisterValue result;
    result.type.ctype  = value.type.ctype;
    result.type.ccount = dstIndex;

    const uint32_t typeId = getVectorTypeId(result.type);

    if (dstIndex == 1) {
      result.id = m_module.opCompositeExtract(
        typeId, value.id, 1, indices.data());
    } else {
      result.id = m_module.opVectorShuffle(
        typeId, value.id, value.id,
        dstIndex, indices.data());
    }

    return result;
  }
    DxbcRegisterValue emitRegisterExtract(
            DxbcRegisterValue       value,
            DxbcRegMask             mask) {
    return emitRegisterSwizzle(value,
      DxbcRegSwizzle(0, 1, 2, 3), mask);
  }
    DxbcRegisterValue emitRegisterInsert(
            DxbcRegisterValue       dstValue,
            DxbcRegisterValue       srcValue,
            DxbcRegMask             srcMask) {
    DxbcRegisterValue result;
    result.type = dstValue.type;

    const uint32_t typeId = getVectorTypeId(result.type);

    if (srcMask.popCount() == 0) {
      // Nothing to do if the insertion mask is empty
      result.id = dstValue.id;
    } else if (dstValue.type.ccount == 1) {
      // Both values are scalar, so the first component
      // of the write mask decides which one to take.
      result.id = srcMask[0] ? srcValue.id : dstValue.id;
    } else if (srcValue.type.ccount == 1) {
      // The source value is scalar. Since OpVectorShuffle
      // requires both arguments to be vectors, we have to
      // use OpCompositeInsert to modify the vector instead.
      const uint32_t componentId = srcMask.firstSet();

      result.id = m_module.opCompositeInsert(typeId,
        srcValue.id, dstValue.id, 1, &componentId);
    } else {
      // Both arguments are vectors. We can determine which
      // components to take from which vector and use the
      // OpVectorShuffle instruction.
      std::array<uint32_t, 4> components;
      uint32_t srcComponentId = dstValue.type.ccount;

      for (uint32_t i = 0; i < dstValue.type.ccount; i++)
        components.at(i) = srcMask[i] ? srcComponentId++ : i;

      result.id = m_module.opVectorShuffle(
        typeId, dstValue.id, srcValue.id,
        dstValue.type.ccount, components.data());
    }

    return result;
  }
    DxbcRegisterValue emitRegisterConcat(
            DxbcRegisterValue       value1,
            DxbcRegisterValue       value2) {
    std::array<uint32_t, 2> ids =
      {{ value1.id, value2.id }};

    DxbcRegisterValue result;
    result.type.ctype  = value1.type.ctype;
    result.type.ccount = value1.type.ccount + value2.type.ccount;
    result.id = m_module.opCompositeConstruct(
      getVectorTypeId(result.type),
      ids.size(), ids.data());
    return result;
  }
    DxbcRegisterValue emitRegisterExtend(
            DxbcRegisterValue       value,
            uint32_t                size) {
    if (size == 1)
      return value;

    std::array<uint32_t, 4> ids = {{
      value.id, value.id,
      value.id, value.id,
    }};

    DxbcRegisterValue result;
    result.type.ctype  = value.type.ctype;
    result.type.ccount = size;
    result.id = m_module.opCompositeConstruct(
      getVectorTypeId(result.type),
      size, ids.data());
    return result;
  }
    DxbcRegisterValue emitRegisterAbsolute(
            DxbcRegisterValue       value) {
    const uint32_t typeId = getVectorTypeId(value.type);

    switch (value.type.ctype) {
      case DxbcScalarType::Float32: value.id = m_module.opFAbs(typeId, value.id); break;
      case DxbcScalarType::Float64: value.id = m_module.opFAbs(typeId, value.id); break;
      case DxbcScalarType::Sint32:  value.id = m_module.opSAbs(typeId, value.id); break;
      case DxbcScalarType::Sint64:  value.id = m_module.opSAbs(typeId, value.id); break;
      default: Logger::warn("DxbcCompiler: Cannot get absolute value for given type");
    }

    return value;
  }
    DxbcRegisterValue emitRegisterNegate(
            DxbcRegisterValue       value) {
    const uint32_t typeId = getVectorTypeId(value.type);

    switch (value.type.ctype) {
      case DxbcScalarType::Float32: value.id = m_module.opFNegate(typeId, value.id); break;
      case DxbcScalarType::Float64: value.id = m_module.opFNegate(typeId, value.id); break;
      case DxbcScalarType::Sint32:  value.id = m_module.opSNegate(typeId, value.id); break;
      case DxbcScalarType::Sint64:  value.id = m_module.opSNegate(typeId, value.id); break;
      default: Logger::warn("DxbcCompiler: Cannot negate given type");
    }

    return value;
  }
    DxbcRegisterValue emitRegisterZeroTest(
            DxbcRegisterValue       value,
            DxbcZeroTest            test) {
    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Bool;
    result.type.ccount = 1;

    const uint32_t zeroId = m_module.constu32(0u);
    const uint32_t typeId = getVectorTypeId(result.type);

    result.id = test == DxbcZeroTest::TestZ
      ? m_module.opIEqual   (typeId, value.id, zeroId)
      : m_module.opINotEqual(typeId, value.id, zeroId);
    return result;
  }
    DxbcRegisterValue emitRegisterMaskBits(
            DxbcRegisterValue       value,
            uint32_t                mask) {
    DxbcRegisterValue maskVector = emitBuildConstVecu32(
      mask, mask, mask, mask, DxbcRegMask::firstN(value.type.ccount));

    DxbcRegisterValue result;
    result.type = value.type;
    result.id = m_module.opBitwiseAnd(
      getVectorTypeId(result.type),
      value.id, maskVector.id);
    return result;
  }
    DxbcRegisterValue emitSrcOperandModifiers(
            DxbcRegisterValue       value,
            DxbcRegModifiers        modifiers) {
    if (modifiers.test(DxbcRegModifier::Abs))
      value = emitRegisterAbsolute(value);

    if (modifiers.test(DxbcRegModifier::Neg))
      value = emitRegisterNegate(value);
    return value;
  }
    DxbcRegisterValue emitDstOperandModifiers(
            DxbcRegisterValue       value,
            DxbcOpModifiers         modifiers) {
    const uint32_t typeId = getVectorTypeId(value.type);

    if (modifiers.saturate) {
      DxbcRegMask mask;
      DxbcRegisterValue vec0, vec1;

      if (value.type.ctype == DxbcScalarType::Float32) {
        mask = DxbcRegMask::firstN(value.type.ccount);
        vec0 = emitBuildConstVecf32(0.0f, 0.0f, 0.0f, 0.0f, mask);
        vec1 = emitBuildConstVecf32(1.0f, 1.0f, 1.0f, 1.0f, mask);
      } else if (value.type.ctype == DxbcScalarType::Float64) {
        mask = DxbcRegMask::firstN(value.type.ccount * 2);
        vec0 = emitBuildConstVecf64(0.0, 0.0, mask);
        vec1 = emitBuildConstVecf64(1.0, 1.0, mask);
      }

      if (mask)
        value.id = m_module.opNClamp(typeId, value.id, vec0.id, vec1.id);
    }

    return value;
  }
    uint32_t emitExtractSparseTexel(
            uint32_t          texelTypeId,
            uint32_t          resultId) {
    uint32_t index = 1;

    return m_module.opCompositeExtract(
      texelTypeId, resultId, 1, &index);
  }
    void emitStoreSparseFeedback(
      const DxbcRegister&     feedbackRegister,
            uint32_t          resultId) {
    if (feedbackRegister.type != DxbcOperandType::Null) {
      uint32_t index = 0;

      DxbcRegisterValue result;
      result.type = { DxbcScalarType::Uint32, 1 };
      result.id = m_module.opCompositeExtract(
        getScalarTypeId(DxbcScalarType::Uint32),
        resultId, 1, &index);

      emitRegisterStore(feedbackRegister, result);
    }
  }
    DxbcRegisterPointer emitArrayAccess(
            DxbcRegisterPointer     pointer,
            spv::StorageClass       sclass,
            uint32_t                index) {
    uint32_t ptrTypeId = m_module.defPointerType(
      getVectorTypeId(pointer.type), sclass);

    DxbcRegisterPointer result;
    result.type = pointer.type;
    result.id = m_module.opAccessChain(
      ptrTypeId, pointer.id, 1, &index);
    return result;
  }
    uint32_t emitLoadSampledImage(
      const DxbcShaderResource&     textureResource,
      const DxbcSampler&            samplerResource,
            bool                    isDepthCompare) {
    uint32_t baseId = isDepthCompare
      ? textureResource.depthTypeId
      : textureResource.colorTypeId;

    if (!baseId)
      return 0;

    uint32_t sampledImageType = m_module.defSampledImageType(baseId);

    return m_module.opSampledImage(sampledImageType,
      m_module.opLoad(textureResource.imageTypeId, textureResource.varId),
      m_module.opLoad(samplerResource.typeId,      samplerResource.varId));
  }
    DxbcRegisterPointer emitGetTempPtr(
      const DxbcRegister&           operand) {
    // r# regs are indexed as follows:
    //    (0) register index (immediate)
    uint32_t regIdx = operand.idx[0].offset;

    if (regIdx >= m_rRegs.size())
      m_rRegs.resize(regIdx + 1, 0u);

    if (!m_rRegs.at(regIdx)) {
      DxbcRegisterInfo info;
      info.type.ctype   = DxbcScalarType::Float32;
      info.type.ccount  = 4;
      info.type.alength = 0;
      info.sclass       = spv::StorageClassPrivate;

      uint32_t varId = emitNewVariable(info);
      m_rRegs.at(regIdx) = varId;

      m_module.setDebugName(varId,
        str::format("r", regIdx).c_str());
    }

    DxbcRegisterPointer result;
    result.type.ctype  = DxbcScalarType::Float32;
    result.type.ccount = 4;
    result.id = m_rRegs.at(regIdx);
    return result;
  }
    DxbcRegisterPointer emitGetIndexableTempPtr(
      const DxbcRegister&           operand) {
    return getIndexableTempPtr(operand, emitIndexLoad(operand.idx[1]));
  }
    DxbcRegisterPointer emitGetInputPtr(
      const DxbcRegister&           operand) {
    // In the vertex and pixel stages,
    // v# regs are indexed as follows:
    //    (0) register index (relative)
    //
    // In the tessellation and geometry
    // stages, the index has two dimensions:
    //    (0) vertex index (relative)
    //    (1) register index (relative)
    DxbcRegisterPointer result;
    result.type.ctype  = DxbcScalarType::Float32;
    result.type.ccount = 4;

    std::array<uint32_t, 2> indices = {{ 0, 0 }};

    for (uint32_t i = 0; i < operand.idxDim; i++)
      indices.at(i) = emitIndexLoad(operand.idx[i]).id;

    // Pick the input array depending on
    // the program type and operand type
    struct InputArray {
      uint32_t          id;
      spv::StorageClass sclass;
    };

    const InputArray array = [&] () -> InputArray {
      switch (operand.type) {
        case DxbcOperandType::InputControlPoint:
          return m_programInfo.type() == DxbcProgramType::HullShader
                  ? InputArray { m_vArray,             spv::StorageClassPrivate }
                  : InputArray { m_ds.inputPerVertex,  spv::StorageClassInput   };
        case DxbcOperandType::InputPatchConstant:
          return m_programInfo.type() == DxbcProgramType::HullShader
                  ? InputArray { m_hs.outputPerPatch, spv::StorageClassPrivate }
                  : InputArray { m_ds.inputPerPatch,  spv::StorageClassInput   };
        case DxbcOperandType::OutputControlPoint:
          return InputArray { m_hs.outputPerVertex, spv::StorageClassOutput };
        default:
          return { m_vArray, spv::StorageClassPrivate };
      }
    }();

    DxbcRegisterInfo info;
    info.type.ctype   = result.type.ctype;
    info.type.ccount  = result.type.ccount;
    info.type.alength = 0;
    info.sclass = array.sclass;

    result.id = m_module.opAccessChain(
      getPointerTypeId(info), array.id,
      operand.idxDim, indices.data());

    return result;
  }
    DxbcRegisterPointer emitGetOutputPtr(
      const DxbcRegister&           operand) {
    if (m_programInfo.type() == DxbcProgramType::HullShader) {
      // Hull shaders are special in that they have two sets of
      // output registers, one for per-patch values and one for
      // per-vertex values.
      DxbcRegisterPointer result;
      result.type.ctype  = DxbcScalarType::Float32;
      result.type.ccount = 4;

      uint32_t registerId = emitIndexLoad(operand.idx[0]).id;

      if (m_hs.currPhaseType == DxbcCompilerHsPhase::ControlPoint) {
        std::array<uint32_t, 2> indices = {{
          m_module.opLoad(m_module.defIntType(32, 0), m_hs.builtinInvocationId),
          registerId,
        }};

        uint32_t ptrTypeId  = m_module.defPointerType(
          getVectorTypeId(result.type),
          spv::StorageClassOutput);

        result.id = m_module.opAccessChain(
          ptrTypeId, m_hs.outputPerVertex,
          indices.size(), indices.data());
      } else {
        uint32_t ptrTypeId  = m_module.defPointerType(
          getVectorTypeId(result.type),
          spv::StorageClassPrivate);

        result.id = m_module.opAccessChain(
          ptrTypeId, m_hs.outputPerPatch,
          1, &registerId);
      }

      return result;
    } else {
      // Regular shaders have their output
      // registers set up at declaration time
      return m_oRegs.at(operand.idx[0].offset);
    }
  }
    DxbcRegisterPointer emitGetConstBufPtr(
      const DxbcRegister&           operand);
    DxbcRegisterPointer emitGetImmConstBufPtr(
      const DxbcRegister&           operand) {
    DxbcRegisterValue constId = emitIndexLoad(operand.idx[0]);

    if (m_icbArray) {
      // We pad the icb array with an extra zero vector, so we can
      // clamp the index and get correct robustness behaviour.
      constId.id = m_module.opUMin(getVectorTypeId(constId.type),
        constId.id, m_module.constu32(m_icbSize));

      DxbcRegisterInfo ptrInfo;
      ptrInfo.type.ctype   = DxbcScalarType::Uint32;
      ptrInfo.type.ccount  = m_icbComponents;
      ptrInfo.type.alength = 0;
      ptrInfo.sclass = spv::StorageClassPrivate;

      DxbcRegisterPointer result;
      result.type.ctype  = ptrInfo.type.ctype;
      result.type.ccount = ptrInfo.type.ccount;
      result.id = m_module.opAccessChain(
        getPointerTypeId(ptrInfo),
        m_icbArray, 1, &constId.id);
      return result;
    } else if (m_constantBuffers.at(Icb_BindingSlotId).varId != 0) {
      const std::array<uint32_t, 2> indices =
        {{ m_module.consti32(0), constId.id }};

      DxbcRegisterInfo ptrInfo;
      ptrInfo.type.ctype   = DxbcScalarType::Float32;
      ptrInfo.type.ccount  = m_icbComponents;
      ptrInfo.type.alength = 0;
      ptrInfo.sclass = spv::StorageClassUniform;

      DxbcRegisterPointer result;
      result.type.ctype  = ptrInfo.type.ctype;
      result.type.ccount = ptrInfo.type.ccount;
      result.id = m_module.opAccessChain(
        getPointerTypeId(ptrInfo),
        m_constantBuffers.at(Icb_BindingSlotId).varId,
        indices.size(), indices.data());
      return result;
    } else {
      throw DxvkError("DxbcCompiler: Immediate constant buffer not defined");
    }
  }
    DxbcRegisterPointer emitGetOperandPtr(
      const DxbcRegister&           operand) {
    switch (operand.type) {
      case DxbcOperandType::Temp:
        return emitGetTempPtr(operand);

      case DxbcOperandType::IndexableTemp:
        return emitGetIndexableTempPtr(operand);

      case DxbcOperandType::Input:
      case DxbcOperandType::InputControlPoint:
      case DxbcOperandType::InputPatchConstant:
      case DxbcOperandType::OutputControlPoint:
        return emitGetInputPtr(operand);

      case DxbcOperandType::Output:
        return emitGetOutputPtr(operand);

      case DxbcOperandType::ImmediateConstantBuffer:
        return emitGetImmConstBufPtr(operand);

      case DxbcOperandType::InputThreadId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 3 },
          m_cs.builtinGlobalInvocationId };

      case DxbcOperandType::InputThreadGroupId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 3 },
          m_cs.builtinWorkgroupId };

      case DxbcOperandType::InputThreadIdInGroup:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 3 },
          m_cs.builtinLocalInvocationId };

      case DxbcOperandType::InputThreadIndexInGroup:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          m_cs.builtinLocalInvocationIndex };

      case DxbcOperandType::InputCoverageMask: {
        const std::array<uint32_t, 1> indices
          = {{ m_module.constu32(0) }};

        DxbcRegisterPointer result;
        result.type.ctype  = DxbcScalarType::Uint32;
        result.type.ccount = 1;
        result.id = m_module.opAccessChain(
          m_module.defPointerType(
            getVectorTypeId(result.type),
            spv::StorageClassInput),
          m_ps.builtinSampleMaskIn,
          indices.size(), indices.data());
        return result;
      }

      case DxbcOperandType::OutputCoverageMask: {
        const std::array<uint32_t, 1> indices
          = {{ m_module.constu32(0) }};

        DxbcRegisterPointer result;
        result.type.ctype  = DxbcScalarType::Uint32;
        result.type.ccount = 1;
        result.id = m_module.opAccessChain(
          m_module.defPointerType(
            getVectorTypeId(result.type),
            spv::StorageClassOutput),
          m_ps.builtinSampleMaskOut,
          indices.size(), indices.data());
        return result;
      }

      case DxbcOperandType::OutputDepth:
      case DxbcOperandType::OutputDepthGe:
      case DxbcOperandType::OutputDepthLe:
        return DxbcRegisterPointer {
          { DxbcScalarType::Float32, 1 },
          m_ps.builtinDepth };

      case DxbcOperandType::OutputStencilRef:
        return DxbcRegisterPointer {
          { DxbcScalarType::Sint32, 1 },
          m_ps.builtinStencilRef };

      case DxbcOperandType::InputPrimitiveId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          m_primitiveIdIn };

      case DxbcOperandType::InputDomainPoint:
        return DxbcRegisterPointer {
          { DxbcScalarType::Float32, 3 },
          m_ds.builtinTessCoord };

      case DxbcOperandType::OutputControlPointId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          m_hs.builtinInvocationId };

      case DxbcOperandType::InputForkInstanceId:
      case DxbcOperandType::InputJoinInstanceId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          getCurrentHsForkJoinPhase()->instanceIdPtr };

      case DxbcOperandType::InputGsInstanceId:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          m_gs.builtinInvocationId };

      case DxbcOperandType::InputInnerCoverage:
        return DxbcRegisterPointer {
          { DxbcScalarType::Uint32, 1 },
          m_ps.builtinInnerCoverageId };

      default:
        throw DxvkError(str::format(
          "DxbcCompiler: Unhandled operand type: ",
          operand.type));
    }
  }
    DxbcRegisterPointer emitGetAtomicPointer(
      const DxbcRegister&           operand,
      const DxbcRegister&           address) {
    // Query information about the resource itself
    const uint32_t registerId = operand.idx[0].offset;
    const DxbcBufferInfo resourceInfo = getBufferInfo(operand);

    // For UAVs and shared memory, different methods
    // of obtaining the final pointer are used.
    bool isTgsm = operand.type == DxbcOperandType::ThreadGroupSharedMemory;
    bool isSsbo = resourceInfo.isSsbo;

    // Compute the actual address into the resource
    const DxbcRegisterValue addressValue = [&] {
      switch (resourceInfo.type) {
        case DxbcResourceType::Raw:
          return emitCalcBufferIndexRaw(emitRegisterLoad(
            address, DxbcRegMask(true, false, false, false)));

        case DxbcResourceType::Structured: {
          const DxbcRegisterValue addressComponents = emitRegisterLoad(
            address, DxbcRegMask(true, true, false, false));

          return emitCalcBufferIndexStructured(
            emitRegisterExtract(addressComponents, DxbcRegMask(true, false, false, false)),
            emitRegisterExtract(addressComponents, DxbcRegMask(false, true, false, false)),
            resourceInfo.stride);
        };

        case DxbcResourceType::Typed: {
          if (isTgsm)
            throw DxvkError("DxbcCompiler: TGSM cannot be typed");

          return emitLoadTexCoord(address,
            m_uavs.at(registerId).imageInfo);
        }

        default:
          throw DxvkError("DxbcCompiler: Unhandled resource type");
      }
    }();

    // Compute the actual pointer
    DxbcRegisterPointer result;
    result.type.ctype  = resourceInfo.stype;
    result.type.ccount = 1;

    if (isTgsm) {
      result.id = m_module.opAccessChain(resourceInfo.typeId,
        resourceInfo.varId, 1, &addressValue.id);
    } else if (isSsbo) {
      uint32_t indices[2] = { m_module.constu32(0), addressValue.id };
      result.id = m_module.opAccessChain(resourceInfo.typeId,
        resourceInfo.varId, 2, indices);
    } else {
      result.id = m_module.opImageTexelPointer(
        m_module.defPointerType(getVectorTypeId(result.type), spv::StorageClassImage),
        resourceInfo.varId, addressValue.id, m_module.constu32(0));
    }

    return result;
  }
    DxbcRegisterValue emitQueryBufferSize(
      const DxbcRegister&           resource) {
    const DxbcBufferInfo bufferInfo = getBufferInfo(resource);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = 1;
    result.id = m_module.opArrayLength(
      getVectorTypeId(result.type),
      bufferInfo.varId, 0);

    return result;
  }
    DxbcRegisterValue emitQueryTexelBufferSize(
      const DxbcRegister&           resource) {
    // Load the texel buffer object. This cannot be used with
    // constant buffers or any other type of resource.
    const DxbcBufferInfo bufferInfo = getBufferInfo(resource);

    const uint32_t bufferId = m_module.opLoad(
      bufferInfo.typeId, bufferInfo.varId);

    // We'll store this as a scalar unsigned integer
    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = 1;
    result.id = m_module.opImageQuerySize(
      getVectorTypeId(result.type), bufferId);

    return result;
  }
    DxbcRegisterValue emitQueryTextureLods(
      const DxbcRegister&           resource) {
    const DxbcBufferInfo info = getBufferInfo(resource);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = 1;

    if (info.image.ms == 0 && info.image.sampled == 1) {
      result.id = m_module.opImageQueryLevels(
        getVectorTypeId(result.type),
        m_module.opLoad(info.typeId, info.varId));
    } else {
      // Report one LOD in case of UAVs or multisampled images
      result.id = m_module.constu32(1);
    }

    return result;
  }
    DxbcRegisterValue emitQueryTextureSamples(
      const DxbcRegister&           resource) {
    if (resource.type == DxbcOperandType::Rasterizer) {
      // SPIR-V has no gl_NumSamples equivalent, so we
      // have to work around it using a push constant
      if (!m_ps.pushConstantId)
        m_ps.pushConstantId = emitPushConstants();

      uint32_t uintTypeId = m_module.defIntType(32, 0);
      uint32_t ptrTypeId = m_module.defPointerType(uintTypeId, spv::StorageClassPushConstant);
      uint32_t index = m_module.constu32(0);

      DxbcRegisterValue result;
      result.type.ctype  = DxbcScalarType::Uint32;
      result.type.ccount = 1;
      result.id = m_module.opLoad(uintTypeId,
        m_module.opAccessChain(ptrTypeId, m_ps.pushConstantId, 1, &index));
      return result;
    } else {
      DxbcBufferInfo info = getBufferInfo(resource);

      DxbcRegisterValue result;
      result.type.ctype  = DxbcScalarType::Uint32;
      result.type.ccount = 1;

      if (info.image.ms) {
        result.id = m_module.opImageQuerySamples(
          getVectorTypeId(result.type),
          m_module.opLoad(info.typeId, info.varId));
      } else {
        // OpImageQuerySamples requires MSAA images
        result.id = m_module.constu32(1);
      }

      return result;
    }
  }
    DxbcRegisterValue emitQueryTextureSize(
      const DxbcRegister&           resource,
            DxbcRegisterValue       lod) {
    const DxbcBufferInfo info = getBufferInfo(resource);

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Uint32;
    result.type.ccount = getTexSizeDim(info.image);

    if (info.image.ms == 0 && info.image.sampled == 1) {
      result.id = m_module.opImageQuerySizeLod(
        getVectorTypeId(result.type),
        m_module.opLoad(info.typeId, info.varId),
        lod.id);
    } else {
      result.id = m_module.opImageQuerySize(
        getVectorTypeId(result.type),
        m_module.opLoad(info.typeId, info.varId));
    }

    return result;
  }
    DxbcRegisterValue emitCalcBufferIndexStructured(
            DxbcRegisterValue       structId,
            DxbcRegisterValue       structOffset,
            uint32_t                structStride) {
    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Sint32;
    result.type.ccount = 1;

    uint32_t typeId = getVectorTypeId(result.type);
    uint32_t offset = m_module.opShiftRightLogical(typeId, structOffset.id, m_module.consti32(2));

    result.id = m_module.opIAdd(typeId,
      m_module.opIMul(typeId, structId.id, m_module.consti32(structStride / 4)),
      offset);
    return result;
  }
    DxbcRegisterValue emitCalcBufferIndexRaw(
            DxbcRegisterValue       byteOffset) {
    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Sint32;
    result.type.ccount = 1;

    uint32_t typeId = getVectorTypeId(result.type);
    result.id = m_module.opShiftRightLogical(typeId, byteOffset.id, m_module.consti32(2));
    return result;
  }
    DxbcRegisterValue emitCalcTexCoord(
            DxbcRegisterValue       coordVector,
      const DxbcImageInfo&          imageInfo) {
    const uint32_t dim = getTexCoordDim(imageInfo);

    if (dim != coordVector.type.ccount) {
      coordVector = emitRegisterExtract(
        coordVector, DxbcRegMask::firstN(dim));
    }

    return coordVector;
  }
    DxbcRegisterValue emitLoadTexCoord(
      const DxbcRegister&           coordReg,
      const DxbcImageInfo&          imageInfo) {
    return emitCalcTexCoord(emitRegisterLoad(coordReg,
      DxbcRegMask(true, true, true, true)), imageInfo);
  }
    DxbcRegisterValue emitIndexLoad(
            DxbcRegIndex            index) {
    if (index.relReg != nullptr) {
      DxbcRegisterValue result = emitRegisterLoad(
        *index.relReg, DxbcRegMask(true, false, false, false));

      if (index.offset != 0) {
        result.id = m_module.opIAdd(
          getVectorTypeId(result.type), result.id,
          m_module.consti32(index.offset));
      }

      return result;
    } else {
      DxbcRegisterValue result;
      result.type.ctype  = DxbcScalarType::Sint32;
      result.type.ccount = 1;
      result.id = m_module.consti32(index.offset);
      return result;
    }
  }
    DxbcRegisterValue emitValueLoad(
            DxbcRegisterPointer     ptr) {
    DxbcRegisterValue result;
    result.type = ptr.type;
    result.id   = m_module.opLoad(
      getVectorTypeId(result.type),
      ptr.id);
    return result;
  }
    void emitValueStore(
            DxbcRegisterPointer     ptr,
            DxbcRegisterValue       value,
            DxbcRegMask             writeMask) {
    // If the component types are not compatible,
    // we need to bit-cast the source variable.
    if (value.type.ctype != ptr.type.ctype)
      value = emitRegisterBitcast(value, ptr.type.ctype);

    // If the source value consists of only one component,
    // it is stored in all components of the destination.
    if (value.type.ccount == 1)
      value = emitRegisterExtend(value, writeMask.popCount());

    if (ptr.type.ccount == writeMask.popCount()) {
      // Simple case: We write to the entire register
      m_module.opStore(ptr.id, value.id);
    } else {
      // We only write to part of the destination
      // register, so we need to load and modify it
      DxbcRegisterValue tmp = emitValueLoad(ptr);
      tmp = emitRegisterInsert(tmp, value, writeMask);

      m_module.opStore(ptr.id, tmp.id);
    }
  }
    DxbcRegisterValue emitRegisterLoadRaw(
      const DxbcRegister&           reg) {
    // Try to find index range for the given register
    const DxbcIndexRange* indexRange = nullptr;

    if (reg.idxDim && reg.idx[reg.idxDim - 1u].relReg) {
      uint32_t offset = reg.idx[reg.idxDim - 1u].offset;

      for (const auto& range : m_indexRanges) {
        if (reg.type == range.type && offset >= range.start && offset < range.start + range.length)
          indexRange = ⦥
      }
    }

    if (reg.type == DxbcOperandType::IndexableTemp || indexRange) {
      bool doBoundsCheck = reg.idx[reg.idxDim - 1u].relReg != nullptr;

      if (doBoundsCheck) {
        DxbcRegisterValue indexId = emitIndexLoad(reg.idx[reg.idxDim - 1u]);
        uint32_t boundsCheck = 0u;

        if (reg.type == DxbcOperandType::IndexableTemp) {
          boundsCheck = m_module.opULessThan(
            m_module.defBoolType(), indexId.id,
            m_module.constu32(m_xRegs.at(reg.idx[0].offset).alength));
        } else {
          uint32_t adjustedId = m_module.opISub(getVectorTypeId(indexId.type),
            indexId.id, m_module.consti32(indexRange->start));

          boundsCheck = m_module.opULessThan(
            m_module.defBoolType(), adjustedId,
            m_module.constu32(indexRange->length));
        }

        // Kind of ugly to have an empty else block here but there's no
        // way for us to know the current block ID for the phi below
        DxbcConditional cond;
        cond.labelIf   = m_module.allocateId();
        cond.labelElse = m_module.allocateId();
        cond.labelEnd  = m_module.allocateId();

        m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone);
        m_module.opBranchConditional(boundsCheck, cond.labelIf, cond.labelElse);

        m_module.opLabel(cond.labelIf);

        DxbcRegisterValue returnValue = emitValueLoad(emitGetOperandPtr(reg));

        m_module.opBranch(cond.labelEnd);
        m_module.opLabel (cond.labelElse);

        DxbcRegisterValue zeroValue = emitBuildZeroVector(returnValue.type);

        m_module.opBranch(cond.labelEnd);
        m_module.opLabel (cond.labelEnd);

        std::array<SpirvPhiLabel, 2> phiLabels = {{
          { returnValue.id, cond.labelIf   },
          { zeroValue.id,   cond.labelElse },
        }};

        returnValue.id = m_module.opPhi(
          getVectorTypeId(returnValue.type),
          phiLabels.size(), phiLabels.data());
        return returnValue;
      }
    }

    DxbcRegisterValue value = emitValueLoad(emitGetOperandPtr(reg));

    // Pad icb values to a vec4 since the app may access components that are always 0
    if (reg.type == DxbcOperandType::ImmediateConstantBuffer && value.type.ccount < 4u) {
      DxbcVectorType zeroType;
      zeroType.ctype = value.type.ctype;
      zeroType.ccount = 4u - value.type.ccount;

      uint32_t zeroVector = emitBuildZeroVector(zeroType).id;

      std::array<uint32_t, 2> constituents = { value.id, zeroVector };

      value.type.ccount = 4u;
      value.id = m_module.opCompositeConstruct(getVectorTypeId(value.type),
        constituents.size(), constituents.data());
    }

    return value;
  }
    DxbcRegisterValue emitConstantBufferLoad(
      const DxbcRegister&           reg,
            DxbcRegMask             writeMask) {
    // Constant buffers take a two-dimensional index:
    //    (0) register index (immediate)
    //    (1) constant offset (relative)
    DxbcRegisterInfo info;
    info.type.ctype   = DxbcScalarType::Float32;
    info.type.ccount  = 4;
    info.type.alength = 0;
    info.sclass = spv::StorageClassUniform;

    uint32_t regId = reg.idx[0].offset;
    DxbcRegisterValue constId = emitIndexLoad(reg.idx[1]);

    uint32_t ptrTypeId = getPointerTypeId(info);

    const std::array<uint32_t, 2> indices =
      {{ m_module.consti32(0), constId.id }};

    DxbcRegisterPointer ptr;
    ptr.type.ctype  = info.type.ctype;
    ptr.type.ccount = info.type.ccount;
    ptr.id = m_module.opAccessChain(ptrTypeId,
      m_constantBuffers.at(regId).varId,
      indices.size(), indices.data());

    // Load individual components from buffer
    std::array<uint32_t, 4> ccomps = { 0, 0, 0, 0 };
    std::array<uint32_t, 4> scomps = { 0, 0, 0, 0 };
    uint32_t                scount = 0;

    for (uint32_t i = 0; i < 4; i++) {
      uint32_t sindex = reg.swizzle[i];

      if (!writeMask[i] || ccomps[sindex])
        continue;

      uint32_t componentId = m_module.constu32(sindex);
      uint32_t componentPtr = m_module.opAccessChain(
        m_module.defPointerType(
          getScalarTypeId(DxbcScalarType::Float32),
          spv::StorageClassUniform),
        ptr.id, 1, &componentId);

      ccomps[sindex] = m_module.opLoad(
        getScalarTypeId(DxbcScalarType::Float32),
        componentPtr);
    }

    for (uint32_t i = 0; i < 4; i++) {
      uint32_t sindex = reg.swizzle[i];

      if (writeMask[i])
        scomps[scount++] = ccomps[sindex];
    }

    DxbcRegisterValue result;
    result.type.ctype  = DxbcScalarType::Float32;
    result.type.ccount = scount;
    result.id = scomps[0];

    if (scount > 1) {
      result.id = m_module.opCompositeConstruct(
        getVectorTypeId(result.type),
        scount, scomps.data());
    }

    // Apply any post-processing that might be necessary
    result = emitRegisterBitcast(result, reg.dataType);
    result = emitSrcOperandModifiers(result, reg.modifiers);
    return result;
  }
    DxbcRegisterValue emitRegisterLoad(
      const DxbcRegister&           reg,
            DxbcRegMask             writeMask) {
    if (reg.type == DxbcOperandType::Imm32
     || reg.type == DxbcOperandType::Imm64) {
      DxbcRegisterValue result;

      if (reg.componentCount == DxbcComponentCount::Component1) {
        // Create one single u32 constant
        result.type.ctype  = DxbcScalarType::Uint32;
        result.type.ccount = 1;
        result.id = m_module.constu32(reg.imm.u32_1);

        result = emitRegisterExtend(result, writeMask.popCount());
      } else if (reg.componentCount == DxbcComponentCount::Component4) {
        // Create a u32 vector with as many components as needed
        std::array<uint32_t, 4> indices = { };
        uint32_t indexId = 0;

        for (uint32_t i = 0; i < indices.size(); i++) {
          if (writeMask[i]) {
            indices.at(indexId++) =
              m_module.constu32(reg.imm.u32_4[i]);
          }
        }

        result.type.ctype  = DxbcScalarType::Uint32;
        result.type.ccount = writeMask.popCount();
        result.id = indices.at(0);

        if (indexId > 1) {
          result.id = m_module.constComposite(
            getVectorTypeId(result.type),
            result.type.ccount, indices.data());
        }

      } else {
        // Something went horribly wrong in the decoder or the shader is broken
        throw DxvkError("DxbcCompiler: Invalid component count for immediate operand");
      }

      // Cast constants to the requested type
      return emitRegisterBitcast(result, reg.dataType);
    } else if (reg.type == DxbcOperandType::ConstantBuffer) {
      return emitConstantBufferLoad(reg, writeMask);
    } else {
      // Load operand from the operand pointer
      DxbcRegisterValue result = emitRegisterLoadRaw(reg);

      // Apply operand swizzle to the operand value
      result = emitRegisterSwizzle(result, reg.swizzle, writeMask);

      // Cast it to the requested type. We need to do
      // this after the swizzling for 64-bit types.
      result = emitRegisterBitcast(result, reg.dataType);

      // Apply operand modifiers
      result = emitSrcOperandModifiers(result, reg.modifiers);
      return result;
    }
  }
    void emitRegisterStore(
      const DxbcRegister&           reg,
            DxbcRegisterValue       value) {
    if (reg.type == DxbcOperandType::IndexableTemp) {
      bool doBoundsCheck = reg.idx[1].relReg != nullptr;
      DxbcRegisterValue vectorId = emitIndexLoad(reg.idx[1]);

      if (doBoundsCheck) {
        uint32_t boundsCheck = m_module.opULessThan(
          m_module.defBoolType(), vectorId.id,
          m_module.constu32(m_xRegs.at(reg.idx[0].offset).alength));

        DxbcConditional cond;
        cond.labelIf  = m_module.allocateId();
        cond.labelEnd = m_module.allocateId();

        m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone);
        m_module.opBranchConditional(boundsCheck, cond.labelIf, cond.labelEnd);

        m_module.opLabel(cond.labelIf);

        emitValueStore(getIndexableTempPtr(reg, vectorId), value, reg.mask);

        m_module.opBranch(cond.labelEnd);
        m_module.opLabel (cond.labelEnd);
      } else {
        emitValueStore(getIndexableTempPtr(reg, vectorId), value, reg.mask);
      }
    } else {
      emitValueStore(emitGetOperandPtr(reg), value, reg.mask);
    }
  }
    void emitInputSetup() {
    m_module.setLateConst(m_vArrayLengthId, &m_vArrayLength);

    // Copy all defined v# registers into the input array. Note
    // that the outer index of the array is the vertex index.
    const uint32_t vecTypeId    = m_module.defVectorType(m_module.defFloatType(32), 4);
    const uint32_t dstPtrTypeId = m_module.defPointerType(vecTypeId, spv::StorageClassPrivate);

    for (uint32_t i = 0; i < m_vRegs.size(); i++) {
      if (m_vRegs.at(i).id != 0) {
        const uint32_t registerId = m_module.consti32(i);

        for (uint32_t v = 0; v < vertexCount; v++) {
          std::array<uint32_t, 2> indices
            = {{ m_module.consti32(v), registerId }};

          DxbcRegisterPointer srcPtr;
          srcPtr.type = m_vRegs.at(i).type;
          srcPtr.id = m_module.opAccessChain(
            m_module.defPointerType(getVectorTypeId(srcPtr.type), spv::StorageClassInput),
            m_vRegs.at(i).id, 1, indices.data());

          DxbcRegisterValue srcValue = emitRegisterBitcast(
            emitValueLoad(srcPtr), DxbcScalarType::Float32);

          DxbcRegisterPointer dstPtr;
          dstPtr.type = { DxbcScalarType::Float32, 4 };
          dstPtr.id = m_module.opAccessChain(
            dstPtrTypeId, m_vArray, 2, indices.data());

          emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount));
        }
      }
    }

    // Copy all system value registers into the array,
    // preserving any previously written contents.
    for (const DxbcSvMapping& map : m_vMappings) {
      const uint32_t registerId = m_module.consti32(map.regId);

      for (uint32_t v = 0; v < vertexCount; v++) {
        const DxbcRegisterValue value = [&] {
          switch (m_programInfo.type()) {
            case DxbcProgramType::GeometryShader: return emitGsSystemValueLoad(map.sv, map.regMask, v);
            default: throw DxvkError(str::format("DxbcCompiler: Unexpected stage: ", m_programInfo.type()));
          }
        }();

        std::array<uint32_t, 2> indices = {
          m_module.consti32(v), registerId,
        };

        DxbcRegisterPointer inputReg;
        inputReg.type.ctype  = DxbcScalarType::Float32;
        inputReg.type.ccount = 4;
        inputReg.id = m_module.opAccessChain(dstPtrTypeId,
          m_vArray, indices.size(), indices.data());
        emitValueStore(inputReg, value, map.regMask);
      }
    }
  }
    void emitInputSetup(uint32_t vertexCount) {
    m_module.setLateConst(m_vArrayLengthId, &m_vArrayLength);

    // Copy all defined v# registers into the input array. Note
    // that the outer index of the array is the vertex index.
    const uint32_t vecTypeId    = m_module.defVectorType(m_module.defFloatType(32), 4);
    const uint32_t dstPtrTypeId = m_module.defPointerType(vecTypeId, spv::StorageClassPrivate);

    for (uint32_t i = 0; i < m_vRegs.size(); i++) {
      if (m_vRegs.at(i).id != 0) {
        const uint32_t registerId = m_module.consti32(i);

        for (uint32_t v = 0; v < vertexCount; v++) {
          std::array<uint32_t, 2> indices
            = {{ m_module.consti32(v), registerId }};

          DxbcRegisterPointer srcPtr;
          srcPtr.type = m_vRegs.at(i).type;
          srcPtr.id = m_module.opAccessChain(
            m_module.defPointerType(getVectorTypeId(srcPtr.type), spv::StorageClassInput),
            m_vRegs.at(i).id, 1, indices.data());

          DxbcRegisterValue srcValue = emitRegisterBitcast(
            emitValueLoad(srcPtr), DxbcScalarType::Float32);

          DxbcRegisterPointer dstPtr;
          dstPtr.type = { DxbcScalarType::Float32, 4 };
          dstPtr.id = m_module.opAccessChain(
            dstPtrTypeId, m_vArray, 2, indices.data());

          emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount));
        }
      }
    }

    // Copy all system value registers into the array,
    // preserving any previously written contents.
    for (const DxbcSvMapping& map : m_vMappings) {
      const uint32_t registerId = m_module.consti32(map.regId);

      for (uint32_t v = 0; v < vertexCount; v++) {
        const DxbcRegisterValue value = [&] {
          switch (m_programInfo.type()) {
            case DxbcProgramType::GeometryShader: return emitGsSystemValueLoad(map.sv, map.regMask, v);
            default: throw DxvkError(str::format("DxbcCompiler: Unexpected stage: ", m_programInfo.type()));
          }
        }();

        std::array<uint32_t, 2> indices = {
          m_module.consti32(v), registerId,
        };

        DxbcRegisterPointer inputReg;
        inputReg.type.ctype  = DxbcScalarType::Float32;
        inputReg.type.ccount = 4;
        inputReg.id = m_module.opAccessChain(dstPtrTypeId,
          m_vArray, indices.size(), indices.data());
        emitValueStore(inputReg, value, map.regMask);
      }
    }
  }
    void emitOutputSetup() {
    for (const DxbcSvMapping& svMapping : m_oMappings) {
      DxbcRegisterPointer outputReg = m_oRegs.at(svMapping.regId);

      if (m_programInfo.type() == DxbcProgramType::HullShader) {
        uint32_t registerIndex = m_module.constu32(svMapping.regId);

        outputReg.type = { DxbcScalarType::Float32, 4 };
        outputReg.id = m_module.opAccessChain(
          m_module.defPointerType(
            getVectorTypeId(outputReg.type),
            spv::StorageClassPrivate),
          m_hs.outputPerPatch,
          1, &registerIndex);
      }

      auto sv    = svMapping.sv;
      auto mask  = svMapping.regMask;
      auto value = emitValueLoad(outputReg);

      switch (m_programInfo.type()) {
        case DxbcProgramType::VertexShader:   emitVsSystemValueStore(sv, mask, value); break;
        case DxbcProgramType::GeometryShader: emitGsSystemValueStore(sv, mask, value); break;
        case DxbcProgramType::HullShader:     emitHsSystemValueStore(sv, mask, value); break;
        case DxbcProgramType::DomainShader:   emitDsSystemValueStore(sv, mask, value); break;
        case DxbcProgramType::PixelShader:    emitPsSystemValueStore(sv, mask, value); break;
        default: break;
      }
    }
  }
    void emitOutputDepthClamp() {
    // HACK: Some drivers do not clamp FragDepth to [minDepth..maxDepth]
    // before writing to the depth attachment, but we do not have acccess
    // to those. Clamp to [0..1] instead.
    if (m_ps.builtinDepth) {
      DxbcRegisterPointer ptr;
      ptr.type = { DxbcScalarType::Float32, 1 };
      ptr.id = m_ps.builtinDepth;

      DxbcRegisterValue value = emitValueLoad(ptr);

      value.id = m_module.opNClamp(
        getVectorTypeId(ptr.type),
        value.id,
        m_module.constf32(0.0f),
        m_module.constf32(1.0f));

      emitValueStore(ptr, value,
        DxbcRegMask::firstN(1));
    }
  }
    void emitInitWorkgroupMemory() {
    bool hasTgsm = false;

    SpirvMemoryOperands memoryOperands;
    memoryOperands.flags = spv::MemoryAccessNonPrivatePointerMask;

    for (uint32_t i = 0; i < m_gRegs.size(); i++) {
      if (!m_gRegs[i].varId)
        continue;

      if (!m_cs.builtinLocalInvocationIndex) {
        m_cs.builtinLocalInvocationIndex = emitNewBuiltinVariable({
          { DxbcScalarType::Uint32, 1, 0 },
          spv::StorageClassInput },
          spv::BuiltInLocalInvocationIndex,
          "vThreadIndexInGroup");
      }

      uint32_t intTypeId = getScalarTypeId(DxbcScalarType::Uint32);
      uint32_t ptrTypeId = m_module.defPointerType(
        intTypeId, spv::StorageClassWorkgroup);

      uint32_t numElements = m_gRegs[i].type == DxbcResourceType::Structured
        ? m_gRegs[i].elementCount * m_gRegs[i].elementStride / 4
        : m_gRegs[i].elementCount / 4;

      uint32_t numThreads = m_cs.workgroupSizeX *
        m_cs.workgroupSizeY * m_cs.workgroupSizeZ;

      uint32_t numElementsPerThread = numElements / numThreads;
      uint32_t numElementsRemaining = numElements % numThreads;

      uint32_t threadId = m_module.opLoad(
        intTypeId, m_cs.builtinLocalInvocationIndex);
      uint32_t zeroId = m_module.constu32(0);

      for (uint32_t e = 0; e < numElementsPerThread; e++) {
        uint32_t ofsId = m_module.opIAdd(intTypeId, threadId,
          m_module.constu32(numThreads * e));

        uint32_t ptrId = m_module.opAccessChain(
          ptrTypeId, m_gRegs[i].varId, 1, &ofsId);

        m_module.opStore(ptrId, zeroId, memoryOperands);
      }

      if (numElementsRemaining) {
        uint32_t condition = m_module.opULessThan(
          m_module.defBoolType(), threadId,
          m_module.constu32(numElementsRemaining));

        DxbcConditional cond;
        cond.labelIf  = m_module.allocateId();
        cond.labelEnd = m_module.allocateId();

        m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone);
        m_module.opBranchConditional(condition, cond.labelIf, cond.labelEnd);

        m_module.opLabel(cond.labelIf);

        uint32_t ofsId = m_module.opIAdd(intTypeId, threadId,
          m_module.constu32(numThreads * numElementsPerThread));

        uint32_t ptrId = m_module.opAccessChain(
          ptrTypeId, m_gRegs[i].varId, 1, &ofsId);

        m_module.opStore(ptrId, zeroId, memoryOperands);

        m_module.opBranch(cond.labelEnd);
        m_module.opLabel (cond.labelEnd);
      }

      hasTgsm = true;
    }

    if (hasTgsm) {
      m_module.opControlBarrier(
        m_module.constu32(spv::ScopeWorkgroup),
        m_module.constu32(spv::ScopeWorkgroup),
        m_module.constu32(spv::MemorySemanticsWorkgroupMemoryMask
                        | spv::MemorySemanticsAcquireReleaseMask
                        | spv::MemorySemanticsMakeAvailableMask
                        | spv::MemorySemanticsMakeVisibleMask));
    }
  }
    DxbcRegisterValue emitVsSystemValueLoad(
            DxbcSystemValue         sv,
            DxbcRegMask             mask) {
    switch (sv) {
      case DxbcSystemValue::VertexId: {
        const uint32_t typeId = getScalarTypeId(DxbcScalarType::Uint32);

        if (m_vs.builtinVertexId == 0) {
          m_vs.builtinVertexId = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInVertexIndex,
            "vs_vertex_index");
        }

        if (m_vs.builtinBaseVertex == 0) {
          m_vs.builtinBaseVertex = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInBaseVertex,
            "vs_base_vertex");
        }

        DxbcRegisterValue result;
        result.type.ctype   = DxbcScalarType::Uint32;
        result.type.ccount  = 1;
        result.id = m_module.opISub(typeId,
          m_module.opLoad(typeId, m_vs.builtinVertexId),
          m_module.opLoad(typeId, m_vs.builtinBaseVertex));
        return result;
      } break;

      case DxbcSystemValue::InstanceId: {
        const uint32_t typeId = getScalarTypeId(DxbcScalarType::Uint32);

        if (m_vs.builtinInstanceId == 0) {
          m_vs.builtinInstanceId = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInInstanceIndex,
            "vs_instance_index");
        }

        if (m_vs.builtinBaseInstance == 0) {
          m_vs.builtinBaseInstance = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInBaseInstance,
            "vs_base_instance");
        }

        DxbcRegisterValue result;
        result.type.ctype   = DxbcScalarType::Uint32;
        result.type.ccount  = 1;
        result.id = m_module.opISub(typeId,
          m_module.opLoad(typeId, m_vs.builtinInstanceId),
          m_module.opLoad(typeId, m_vs.builtinBaseInstance));
        return result;
      } break;

      default:
        throw DxvkError(str::format(
          "DxbcCompiler: Unhandled VS SV input: ", sv));
    }
  }
    DxbcRegisterValue emitGsSystemValueLoad(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
            uint32_t                vertexId) {
    switch (sv) {
      case DxbcSystemValue::Position: {
        uint32_t arrayIndex = m_module.consti32(vertexId);

        if (!m_positionIn) {
          m_positionIn = emitNewBuiltinVariable({
            { DxbcScalarType::Float32, 4, primitiveVertexCount(m_gs.inputPrimitive) },
            spv::StorageClassInput },
            spv::BuiltInPosition,
            "in_position");
        }

        DxbcRegisterPointer ptrIn;
        ptrIn.type.ctype  = DxbcScalarType::Float32;
        ptrIn.type.ccount = 4;
        ptrIn.id = m_module.opAccessChain(
          m_module.defPointerType(getVectorTypeId(ptrIn.type), spv::StorageClassInput),
          m_positionIn, 1, &arrayIndex);

        return emitRegisterExtract(emitValueLoad(ptrIn), mask);
      } break;

      default:
        throw DxvkError(str::format(
          "DxbcCompiler: Unhandled GS SV input: ", sv));
    }
  }
    DxbcRegisterValue emitPsSystemValueLoad(
            DxbcSystemValue         sv,
            DxbcRegMask             mask) {
    switch (sv) {
      case DxbcSystemValue::Position: {
        if (m_ps.builtinFragCoord == 0) {
          m_ps.builtinFragCoord = emitNewBuiltinVariable({
            { DxbcScalarType::Float32, 4, 0 },
            spv::StorageClassInput },
            spv::BuiltInFragCoord,
            "ps_frag_coord");
        }

        DxbcRegisterPointer ptrIn;
        ptrIn.type = { DxbcScalarType::Float32, 4 };
        ptrIn.id = m_ps.builtinFragCoord;

        // The X, Y and Z components of the SV_POSITION semantic
        // are identical to Vulkan's FragCoord builtin, but we
        // need to compute the reciprocal of the W component.
        DxbcRegisterValue fragCoord = emitValueLoad(ptrIn);

        uint32_t componentIndex = 3;
        uint32_t t_f32   = m_module.defFloatType(32);
        uint32_t v_wComp = m_module.opCompositeExtract(t_f32, fragCoord.id, 1, &componentIndex);
                 v_wComp = m_module.opFDiv(t_f32, m_module.constf32(1.0f), v_wComp);

        fragCoord.id = m_module.opCompositeInsert(
          getVectorTypeId(fragCoord.type),
          v_wComp, fragCoord.id,
          1, &componentIndex);

        return emitRegisterExtract(fragCoord, mask);
      } break;

      case DxbcSystemValue::IsFrontFace: {
        if (m_ps.builtinIsFrontFace == 0) {
          m_ps.builtinIsFrontFace = emitNewBuiltinVariable({
            { DxbcScalarType::Bool, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInFrontFacing,
            "ps_is_front_face");
        }

        DxbcRegisterValue result;
        result.type.ctype  = DxbcScalarType::Uint32;
        result.type.ccount = 1;
        result.id = m_module.opSelect(
          getVectorTypeId(result.type),
          m_module.opLoad(
            m_module.defBoolType(),
            m_ps.builtinIsFrontFace),
          m_module.constu32(0xFFFFFFFF),
          m_module.constu32(0x00000000));
        return result;
      } break;

      case DxbcSystemValue::PrimitiveId: {
        if (m_primitiveIdIn == 0) {
          m_module.enableCapability(spv::CapabilityGeometry);

          m_primitiveIdIn = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInPrimitiveId,
            "ps_primitive_id");
        }

        DxbcRegisterPointer ptrIn;
        ptrIn.type = { DxbcScalarType::Uint32, 1 };
        ptrIn.id   = m_primitiveIdIn;

        return emitValueLoad(ptrIn);
      } break;

      case DxbcSystemValue::SampleIndex: {
        if (m_ps.builtinSampleId == 0) {
          m_module.enableCapability(spv::CapabilitySampleRateShading);

          m_ps.builtinSampleId = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInSampleId,
            "ps_sample_id");
        }

        DxbcRegisterPointer ptrIn;
        ptrIn.type.ctype   = DxbcScalarType::Uint32;
        ptrIn.type.ccount  = 1;
        ptrIn.id = m_ps.builtinSampleId;

        return emitValueLoad(ptrIn);
      } break;

      case DxbcSystemValue::RenderTargetId: {
        if (m_ps.builtinLayer == 0) {
          m_module.enableCapability(spv::CapabilityGeometry);

          m_ps.builtinLayer = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInLayer,
            "v_layer");
        }

        DxbcRegisterPointer ptr;
        ptr.type.ctype   = DxbcScalarType::Uint32;
        ptr.type.ccount  = 1;
        ptr.id = m_ps.builtinLayer;

        return emitValueLoad(ptr);
      } break;

      case DxbcSystemValue::ViewportId: {
        if (m_ps.builtinViewportId == 0) {
          m_module.enableCapability(spv::CapabilityMultiViewport);

          m_ps.builtinViewportId = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassInput },
            spv::BuiltInViewportIndex,
            "v_viewport");
        }

        DxbcRegisterPointer ptr;
        ptr.type.ctype   = DxbcScalarType::Uint32;
        ptr.type.ccount  = 1;
        ptr.id = m_ps.builtinViewportId;

        return emitValueLoad(ptr);
      } break;

      default:
        throw DxvkError(str::format(
          "DxbcCompiler: Unhandled PS SV input: ", sv));
    }
  }
    void emitVsSystemValueStore(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
      const DxbcRegisterValue&      value) {
    switch (sv) {
      case DxbcSystemValue::Position: {
        if (!m_positionOut) {
          m_positionOut = emitNewBuiltinVariable({
            { DxbcScalarType::Float32, 4, 0 },
            spv::StorageClassOutput },
            spv::BuiltInPosition,
            "out_position");
        }

        DxbcRegisterPointer ptr;
        ptr.type.ctype  = DxbcScalarType::Float32;
        ptr.type.ccount = 4;
        ptr.id = m_positionOut;

        emitValueStore(ptr, value, mask);
      } break;

      case DxbcSystemValue::RenderTargetId: {
        if (m_programInfo.type() != DxbcProgramType::GeometryShader)
          m_module.enableCapability(spv::CapabilityShaderLayer);

        if (m_gs.builtinLayer == 0) {
          m_module.enableCapability(spv::CapabilityGeometry);

          m_gs.builtinLayer = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassOutput },
            spv::BuiltInLayer,
            "o_layer");
        }

        DxbcRegisterPointer ptr;
        ptr.type = { DxbcScalarType::Uint32, 1 };
        ptr.id   = m_gs.builtinLayer;

        emitValueStore(
          ptr, emitRegisterExtract(value, mask),
          DxbcRegMask(true, false, false, false));
      } break;

      case DxbcSystemValue::ViewportId: {
        if (m_programInfo.type() != DxbcProgramType::GeometryShader)
          m_module.enableCapability(spv::CapabilityShaderViewportIndex);

        if (m_gs.builtinViewportId == 0) {
          m_module.enableCapability(spv::CapabilityMultiViewport);

          m_gs.builtinViewportId = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassOutput },
            spv::BuiltInViewportIndex,
            "o_viewport");
        }

        DxbcRegisterPointer ptr;
        ptr.type = { DxbcScalarType::Uint32, 1};
        ptr.id   = m_gs.builtinViewportId;

        emitValueStore(
          ptr, emitRegisterExtract(value, mask),
          DxbcRegMask(true, false, false, false));
      } break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled VS SV output: ", sv));
    }
  }
    void emitHsSystemValueStore(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
      const DxbcRegisterValue&      value) {
    if (sv >= DxbcSystemValue::FinalQuadUeq0EdgeTessFactor
     && sv <= DxbcSystemValue::FinalLineDensityTessFactor) {
      struct TessFactor {
        uint32_t array = 0;
        uint32_t index = 0;
      };

      static const std::array<TessFactor, 12> s_tessFactors = {{
        { m_hs.builtinTessLevelOuter, 0 },  // FinalQuadUeq0EdgeTessFactor
        { m_hs.builtinTessLevelOuter, 1 },  // FinalQuadVeq0EdgeTessFactor
        { m_hs.builtinTessLevelOuter, 2 },  // FinalQuadUeq1EdgeTessFactor
        { m_hs.builtinTessLevelOuter, 3 },  // FinalQuadVeq1EdgeTessFactor
        { m_hs.builtinTessLevelInner, 0 },  // FinalQuadUInsideTessFactor
        { m_hs.builtinTessLevelInner, 1 },  // FinalQuadVInsideTessFactor
        { m_hs.builtinTessLevelOuter, 0 },  // FinalTriUeq0EdgeTessFactor
        { m_hs.builtinTessLevelOuter, 1 },  // FinalTriVeq0EdgeTessFactor
        { m_hs.builtinTessLevelOuter, 2 },  // FinalTriWeq0EdgeTessFactor
        { m_hs.builtinTessLevelInner, 0 },  // FinalTriInsideTessFactor
        { m_hs.builtinTessLevelOuter, 0 },  // FinalLineDensityTessFactor
        { m_hs.builtinTessLevelOuter, 1 },  // FinalLineDetailTessFactor
      }};

      const TessFactor tessFactor = s_tessFactors.at(uint32_t(sv)
        - uint32_t(DxbcSystemValue::FinalQuadUeq0EdgeTessFactor));

      const uint32_t tessFactorArrayIndex
        = m_module.constu32(tessFactor.index);

      // Apply global tess factor limit
      float maxTessFactor = m_hs.maxTessFactor;

      if (m_moduleInfo.tess != nullptr) {
        if (m_moduleInfo.tess->maxTessFactor < maxTessFactor)
          maxTessFactor = m_moduleInfo.tess->maxTessFactor;
      }

      DxbcRegisterValue tessValue = emitRegisterExtract(value, mask);
      tessValue.id = m_module.opNClamp(getVectorTypeId(tessValue.type),
        tessValue.id, m_module.constf32(0.0f),
        m_module.constf32(maxTessFactor));

      DxbcRegisterPointer ptr;
      ptr.type.ctype  = DxbcScalarType::Float32;
      ptr.type.ccount = 1;
      ptr.id = m_module.opAccessChain(
        m_module.defPointerType(
          getVectorTypeId(ptr.type),
          spv::StorageClassOutput),
        tessFactor.array, 1,
        &tessFactorArrayIndex);

      emitValueStore(ptr, tessValue,
        DxbcRegMask(true, false, false, false));
    } else {
      Logger::warn(str::format(
        "DxbcCompiler: Unhandled HS SV output: ", sv));
    }
  }
    void emitDsSystemValueStore(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
      const DxbcRegisterValue&      value) {
    switch (sv) {
      case DxbcSystemValue::Position:
      case DxbcSystemValue::CullDistance:
      case DxbcSystemValue::ClipDistance:
      case DxbcSystemValue::RenderTargetId:
      case DxbcSystemValue::ViewportId:
        emitVsSystemValueStore(sv, mask, value);
        break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled DS SV output: ", sv));
    }
  }
    void emitGsSystemValueStore(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
      const DxbcRegisterValue&      value) {
    switch (sv) {
      case DxbcSystemValue::Position:
      case DxbcSystemValue::CullDistance:
      case DxbcSystemValue::ClipDistance:
      case DxbcSystemValue::RenderTargetId:
      case DxbcSystemValue::ViewportId:
        emitVsSystemValueStore(sv, mask, value);
        break;

      case DxbcSystemValue::PrimitiveId: {
        if (m_primitiveIdOut == 0) {
          m_primitiveIdOut = emitNewBuiltinVariable({
            { DxbcScalarType::Uint32, 1, 0 },
            spv::StorageClassOutput },
            spv::BuiltInPrimitiveId,
            "gs_primitive_id");
        }

        DxbcRegisterPointer ptr;
        ptr.type = { DxbcScalarType::Uint32, 1};
        ptr.id   = m_primitiveIdOut;

        emitValueStore(
          ptr, emitRegisterExtract(value, mask),
          DxbcRegMask(true, false, false, false));
      } break;

      default:
        Logger::warn(str::format(
          "DxbcCompiler: Unhandled GS SV output: ", sv));
    }
  }
    void emitPsSystemValueStore(
            DxbcSystemValue         sv,
            DxbcRegMask             mask,
      const DxbcRegisterValue&      value) {
    Logger::warn(str::format(
      "DxbcCompiler: Unhandled PS SV output: ", sv));
  }
    void emitClipCullStore(
            DxbcSystemValue         sv,
            uint32_t                dstArray) {
    uint32_t offset = 0;

    if (dstArray == 0)
      return;

    for (auto e = m_osgn->begin(); e != m_osgn->end(); e++) {
      if (e->systemValue == sv) {
        DxbcRegisterPointer srcPtr = m_oRegs.at(e->registerId);
        DxbcRegisterValue srcValue = emitValueLoad(srcPtr);

        for (uint32_t i = 0; i < 4; i++) {
          if (e->componentMask[i]) {
            uint32_t offsetId = m_module.consti32(offset++);

            DxbcRegisterValue component = emitRegisterExtract(
              srcValue, DxbcRegMask::select(i));

            DxbcRegisterPointer dstPtr;
            dstPtr.type = { DxbcScalarType::Float32, 1 };
            dstPtr.id = m_module.opAccessChain(
              m_module.defPointerType(
                getVectorTypeId(dstPtr.type),
                spv::StorageClassOutput),
              dstArray, 1, &offsetId);

            emitValueStore(dstPtr, component,
              DxbcRegMask(true, false, false, false));
          }
        }
      }
    }
  }
    void emitClipCullLoad(
            DxbcSystemValue         sv,
            uint32_t                srcArray) {
    uint32_t offset = 0;

    if (srcArray == 0)
      return;

    for (auto e = m_isgn->begin(); e != m_isgn->end(); e++) {
      if (e->systemValue == sv) {
        // Load individual components from the source array
        uint32_t                componentIndex = 0;
        std::array<uint32_t, 4> componentIds   = {{ 0, 0, 0, 0 }};

        for (uint32_t i = 0; i < 4; i++) {
          if (e->componentMask[i]) {
            uint32_t offsetId = m_module.consti32(offset++);

            DxbcRegisterPointer srcPtr;
            srcPtr.type = { DxbcScalarType::Float32, 1 };
            srcPtr.id = m_module.opAccessChain(
              m_module.defPointerType(
                getVectorTypeId(srcPtr.type),
                spv::StorageClassInput),
              srcArray, 1, &offsetId);

            componentIds[componentIndex++]
              = emitValueLoad(srcPtr).id;
          }
        }

        // Put everything into one vector
        DxbcRegisterValue dstValue;
        dstValue.type = { DxbcScalarType::Float32, componentIndex };
        dstValue.id = componentIds[0];

        if (componentIndex > 1) {
          dstValue.id = m_module.opCompositeConstruct(
            getVectorTypeId(dstValue.type),
            componentIndex, componentIds.data());
        }

        // Store vector to the input array
        uint32_t registerId = m_module.consti32(e->registerId);

        DxbcRegisterPointer dstInput;
        dstInput.type = { DxbcScalarType::Float32, 4 };
        dstInput.id = m_module.opAccessChain(
          m_module.defPointerType(
            getVectorTypeId(dstInput.type),
            spv::StorageClassPrivate),
          m_vArray, 1, &registerId);

        emitValueStore(dstInput, dstValue, e->componentMask);
      }
    }
  }
    void emitPointSizeStore() {
    if (m_moduleInfo.options.needsPointSizeExport) {
      uint32_t pointSizeId = emitNewBuiltinVariable(DxbcRegisterInfo {
        { DxbcScalarType::Float32, 1, 0 },
        spv::StorageClassOutput },
        spv::BuiltInPointSize,
        "point_size");

      m_module.opStore(pointSizeId, m_module.constf32(1.0f));
    }
  }
    void emitInit() {
    // Set up common capabilities for all shaders
    m_module.enableCapability(spv::CapabilityShader);
    m_module.enableCapability(spv::CapabilityImageQuery);

    // Initialize the shader module with capabilities
    // etc. Each shader type has its own peculiarities.
    switch (m_programInfo.type()) {
      case DxbcProgramType::VertexShader:   emitVsInit(); break;
      case DxbcProgramType::HullShader:     emitHsInit(); break;
      case DxbcProgramType::DomainShader:   emitDsInit(); break;
      case DxbcProgramType::GeometryShader: emitGsInit(); break;
      case DxbcProgramType::PixelShader:    emitPsInit(); break;
      case DxbcProgramType::ComputeShader:  emitCsInit(); break;
      default: throw DxvkError("Invalid shader stage");
    }
  }
    void emitFunctionBegin(
            uint32_t                entryPoint,
            uint32_t                returnType,
            uint32_t                funcType) {
    this->emitFunctionEnd();

    m_module.functionBegin(
      returnType, entryPoint, funcType,
      spv::FunctionControlMaskNone);

    m_insideFunction = true;
  }
    void emitFunctionEnd() {
    if (m_insideFunction) {
      m_module.opReturn();
      m_module.functionEnd();
    }

    m_insideFunction = false;
  }
    void emitFunctionLabel() {
    m_module.opLabel(m_module.allocateId());
  }
    void emitMainFunctionBegin() {
    this->emitFunctionBegin(
      m_entryPointId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitVsInit() {
    m_module.enableCapability(spv::CapabilityClipDistance);
    m_module.enableCapability(spv::CapabilityCullDistance);
    m_module.enableCapability(spv::CapabilityDrawParameters);

    // Standard input array
    emitDclInputArray(0);

    // Cull/clip distances as outputs
    m_clipDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numClipPlanes,
      spv::BuiltInClipDistance,
      spv::StorageClassOutput);

    m_cullDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numCullPlanes,
      spv::BuiltInCullDistance,
      spv::StorageClassOutput);

    // Main function of the vertex shader
    m_vs.functionId = m_module.allocateId();
    m_module.setDebugName(m_vs.functionId, "vs_main");

    this->emitFunctionBegin(
      m_vs.functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitHsInit() {
    m_module.enableCapability(spv::CapabilityTessellation);
    m_module.enableCapability(spv::CapabilityClipDistance);
    m_module.enableCapability(spv::CapabilityCullDistance);

    m_hs.builtinInvocationId = emitNewBuiltinVariable(
      DxbcRegisterInfo {
        { DxbcScalarType::Uint32, 1, 0 },
        spv::StorageClassInput },
      spv::BuiltInInvocationId,
      "vOutputControlPointId");

    m_hs.builtinTessLevelOuter = emitBuiltinTessLevelOuter(spv::StorageClassOutput);
    m_hs.builtinTessLevelInner = emitBuiltinTessLevelInner(spv::StorageClassOutput);
  }
    void emitDsInit() {
    m_module.enableCapability(spv::CapabilityTessellation);
    m_module.enableCapability(spv::CapabilityClipDistance);
    m_module.enableCapability(spv::CapabilityCullDistance);

    m_ds.builtinTessLevelOuter = emitBuiltinTessLevelOuter(spv::StorageClassInput);
    m_ds.builtinTessLevelInner = emitBuiltinTessLevelInner(spv::StorageClassInput);

    // Cull/clip distances as outputs
    m_clipDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numClipPlanes,
      spv::BuiltInClipDistance,
      spv::StorageClassOutput);

    m_cullDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numCullPlanes,
      spv::BuiltInCullDistance,
      spv::StorageClassOutput);

    // Main function of the domain shader
    m_ds.functionId = m_module.allocateId();
    m_module.setDebugName(m_ds.functionId, "ds_main");

    this->emitFunctionBegin(
      m_ds.functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitGsInit() {
    m_module.enableCapability(spv::CapabilityGeometry);
    m_module.enableCapability(spv::CapabilityClipDistance);
    m_module.enableCapability(spv::CapabilityCullDistance);

    // Enable capabilities for xfb mode if necessary
    if (m_moduleInfo.xfb) {
      m_module.enableCapability(spv::CapabilityGeometryStreams);
      m_module.enableCapability(spv::CapabilityTransformFeedback);

      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeXfb);
    }

    // We only need outputs if rasterization is enabled
    m_gs.needsOutputSetup = !m_moduleInfo.xfb
      || m_moduleInfo.xfb->rasterizedStream >= 0;

    // Cull/clip distances as outputs
    m_clipDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numClipPlanes,
      spv::BuiltInClipDistance,
      spv::StorageClassOutput);

    m_cullDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullOut.numCullPlanes,
      spv::BuiltInCullDistance,
      spv::StorageClassOutput);

    // Emit Xfb variables if necessary
    if (m_moduleInfo.xfb)
      emitXfbOutputDeclarations();

    // Main function of the vertex shader
    m_gs.functionId = m_module.allocateId();
    m_module.setDebugName(m_gs.functionId, "gs_main");

    this->emitFunctionBegin(
      m_gs.functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitPsInit() {
    m_module.enableCapability(spv::CapabilityDerivativeControl);

    m_module.setExecutionMode(m_entryPointId,
      spv::ExecutionModeOriginUpperLeft);

    // Standard input array
    emitDclInputArray(0);

    // Cull/clip distances as inputs
    m_clipDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullIn.numClipPlanes,
      spv::BuiltInClipDistance,
      spv::StorageClassInput);

    m_cullDistances = emitDclClipCullDistanceArray(
      m_analysis->clipCullIn.numCullPlanes,
      spv::BuiltInCullDistance,
      spv::StorageClassInput);

    // Main function of the pixel shader
    m_ps.functionId = m_module.allocateId();
    m_module.setDebugName(m_ps.functionId, "ps_main");

    this->emitFunctionBegin(
      m_ps.functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitCsInit() {
    // Main function of the compute shader
    m_cs.functionId = m_module.allocateId();
    m_module.setDebugName(m_cs.functionId, "cs_main");

    this->emitFunctionBegin(
      m_cs.functionId,
      m_module.defVoidType(),
      m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr));
    this->emitFunctionLabel();
  }
    void emitVsFinalize() {
    this->emitMainFunctionBegin();
    this->emitInputSetup();
    m_module.opFunctionCall(
      m_module.defVoidType(),
      m_vs.functionId, 0, nullptr);
    this->emitOutputSetup();
    this->emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances);
    this->emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances);
    this->emitPointSizeStore();
    this->emitFunctionEnd();
  }
    void emitHsFinalize() {
    if (m_hs.cpPhase.functionId == 0)
      m_hs.cpPhase = this->emitNewHullShaderPassthroughPhase();

    // Control point phase
    this->emitMainFunctionBegin();
    this->emitInputSetup(m_hs.vertexCountIn);
    this->emitHsControlPointPhase(m_hs.cpPhase);
    this->emitHsPhaseBarrier();

    // Fork-join phases and output setup
    this->emitHsInvocationBlockBegin(1);

    for (const auto& phase : m_hs.forkPhases)
      this->emitHsForkJoinPhase(phase);

    for (const auto& phase : m_hs.joinPhases)
      this->emitHsForkJoinPhase(phase);

    this->emitOutputSetup();
    this->emitHsOutputSetup();
    this->emitHsInvocationBlockEnd();
    this->emitFunctionEnd();
  }
    void emitDsFinalize() {
    this->emitMainFunctionBegin();
    m_module.opFunctionCall(
      m_module.defVoidType(),
      m_ds.functionId, 0, nullptr);
    this->emitOutputSetup();
    this->emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances);
    this->emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances);
    this->emitFunctionEnd();
  }
    void emitGsFinalize() {
    if (!m_gs.invocationCount)
      m_module.setInvocations(m_entryPointId, 1);

    this->emitMainFunctionBegin();
    this->emitInputSetup(
      primitiveVertexCount(m_gs.inputPrimitive));
    m_module.opFunctionCall(
      m_module.defVoidType(),
      m_gs.functionId, 0, nullptr);
    // No output setup at this point as that was
    // already done during the EmitVertex step
    this->emitFunctionEnd();
  }
    void emitPsFinalize() {
    this->emitMainFunctionBegin();
    this->emitInputSetup();
    this->emitClipCullLoad(DxbcSystemValue::ClipDistance, m_clipDistances);
    this->emitClipCullLoad(DxbcSystemValue::CullDistance, m_cullDistances);

    if (m_hasRasterizerOrderedUav) {
      // For simplicity, just lock the entire fragment shader
      // if there are any rasterizer ordered views.
      m_module.enableExtension("SPV_EXT_fragment_shader_interlock");

      if (m_module.hasCapability(spv::CapabilitySampleRateShading)
       && m_moduleInfo.options.enableSampleShadingInterlock) {
        m_module.enableCapability(spv::CapabilityFragmentShaderSampleInterlockEXT);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSampleInterlockOrderedEXT);
      } else {
        m_module.enableCapability(spv::CapabilityFragmentShaderPixelInterlockEXT);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModePixelInterlockOrderedEXT);
      }

      m_module.opBeginInvocationInterlock();
    }

    m_module.opFunctionCall(
      m_module.defVoidType(),
      m_ps.functionId, 0, nullptr);

    if (m_hasRasterizerOrderedUav)
      m_module.opEndInvocationInterlock();

    this->emitOutputSetup();

    if (m_moduleInfo.options.useDepthClipWorkaround)
      this->emitOutputDepthClamp();

    this->emitFunctionEnd();
  }
    void emitCsFinalize() {
    this->emitMainFunctionBegin();

    if (m_moduleInfo.options.zeroInitWorkgroupMemory)
      this->emitInitWorkgroupMemory();

    m_module.opFunctionCall(
      m_module.defVoidType(),
      m_cs.functionId, 0, nullptr);

    this->emitFunctionEnd();
  }
    void emitXfbOutputDeclarations() {
    for (uint32_t i = 0; i < m_moduleInfo.xfb->entryCount; i++) {
      const DxbcXfbEntry* xfbEntry = m_moduleInfo.xfb->entries + i;
      const DxbcSgnEntry* sigEntry = m_osgn->find(
        xfbEntry->semanticName,
        xfbEntry->semanticIndex,
        xfbEntry->streamId);

      if (sigEntry == nullptr)
        continue;

      DxbcRegisterInfo varInfo;
      varInfo.type.ctype = DxbcScalarType::Float32;
      varInfo.type.ccount = xfbEntry->componentCount;
      varInfo.type.alength = 0;
      varInfo.sclass = spv::StorageClassOutput;

      uint32_t dstComponentMask = (1 << xfbEntry->componentCount) - 1;
      uint32_t srcComponentMask = dstComponentMask
        << sigEntry->componentMask.firstSet()
        << xfbEntry->componentIndex;

      DxbcXfbVar xfbVar;
      xfbVar.varId = emitNewVariable(varInfo);
      xfbVar.streamId = xfbEntry->streamId;
      xfbVar.outputId = sigEntry->registerId;
      xfbVar.srcMask = DxbcRegMask(srcComponentMask);
      xfbVar.dstMask = DxbcRegMask(dstComponentMask);
      m_xfbVars.push_back(xfbVar);

      m_module.setDebugName(xfbVar.varId,
        str::format("xfb", i).c_str());

      m_module.decorateXfb(xfbVar.varId,
        xfbEntry->streamId, xfbEntry->bufferId, xfbEntry->offset,
        m_moduleInfo.xfb->strides[xfbEntry->bufferId]);
    }

    // TODO Compact location/component assignment
    for (uint32_t i = 0; i < m_xfbVars.size(); i++) {
      m_xfbVars[i].location  = i;
      m_xfbVars[i].component = 0;
    }

    for (uint32_t i = 0; i < m_xfbVars.size(); i++) {
      const DxbcXfbVar* var = &m_xfbVars[i];

      m_module.decorateLocation (var->varId, var->location);
      m_module.decorateComponent(var->varId, var->component);
    }
  }
    void emitXfbOutputSetup(
            uint32_t                          streamId,
            bool                              passthrough) {
    for (size_t i = 0; i < m_xfbVars.size(); i++) {
      if (m_xfbVars[i].streamId == streamId) {
        DxbcRegisterPointer srcPtr = passthrough
          ? m_vRegs[m_xfbVars[i].outputId]
          : m_oRegs[m_xfbVars[i].outputId];

        if (passthrough) {
          srcPtr = emitArrayAccess(srcPtr,
            spv::StorageClassInput,
            m_module.constu32(0));
        }

        DxbcRegisterPointer dstPtr;
        dstPtr.type.ctype  = DxbcScalarType::Float32;
        dstPtr.type.ccount = m_xfbVars[i].dstMask.popCount();
        dstPtr.id = m_xfbVars[i].varId;

        DxbcRegisterValue value = emitRegisterExtract(
          emitValueLoad(srcPtr), m_xfbVars[i].srcMask);
        emitValueStore(dstPtr, value, m_xfbVars[i].dstMask);
      }
    }
  }
    void emitHsControlPointPhase(
      const DxbcCompilerHsControlPointPhase&  phase) {
    m_module.opFunctionCall(
      m_module.defVoidType(),
      phase.functionId, 0, nullptr);
  }
    void emitHsForkJoinPhase(
      const DxbcCompilerHsForkJoinPhase&      phase) {
    for (uint32_t i = 0; i < phase.instanceCount; i++) {
      uint32_t invocationId = m_module.constu32(i);

      m_module.opFunctionCall(
        m_module.defVoidType(),
        phase.functionId, 1,
        &invocationId);
    }
  }
    void emitHsPhaseBarrier() {
    uint32_t exeScopeId = m_module.constu32(spv::ScopeWorkgroup);
    uint32_t memScopeId = m_module.constu32(spv::ScopeWorkgroup);
    uint32_t semanticId = m_module.constu32(
      spv::MemorySemanticsOutputMemoryMask |
      spv::MemorySemanticsAcquireReleaseMask |
      spv::MemorySemanticsMakeAvailableMask |
      spv::MemorySemanticsMakeVisibleMask);

    m_module.opControlBarrier(exeScopeId, memScopeId, semanticId);
  }
    void emitHsInvocationBlockBegin(
            uint32_t                          count) {
    uint32_t invocationId = m_module.opLoad(
      getScalarTypeId(DxbcScalarType::Uint32),
      m_hs.builtinInvocationId);

    uint32_t condition = m_module.opULessThan(
      m_module.defBoolType(), invocationId,
      m_module.constu32(count));

    m_hs.invocationBlockBegin = m_module.allocateId();
    m_hs.invocationBlockEnd   = m_module.allocateId();

    m_module.opSelectionMerge(
      m_hs.invocationBlockEnd,
      spv::SelectionControlMaskNone);

    m_module.opBranchConditional(
      condition,
      m_hs.invocationBlockBegin,
      m_hs.invocationBlockEnd);

    m_module.opLabel(
      m_hs.invocationBlockBegin);
  }
    void emitHsInvocationBlockEnd() {
    m_module.opBranch (m_hs.invocationBlockEnd);
    m_module.opLabel  (m_hs.invocationBlockEnd);

    m_hs.invocationBlockBegin = 0;
    m_hs.invocationBlockEnd   = 0;
  }
    void emitHsOutputSetup() {
    uint32_t outputPerPatch = emitTessInterfacePerPatch(spv::StorageClassOutput);

    if (!outputPerPatch)
      return;

    uint32_t vecType = getVectorTypeId({ DxbcScalarType::Float32, 4 });

    uint32_t srcPtrType = m_module.defPointerType(vecType, spv::StorageClassPrivate);
    uint32_t dstPtrType = m_module.defPointerType(vecType, spv::StorageClassOutput);

    for (uint32_t i = 0; i < 32; i++) {
      if (m_hs.outputPerPatchMask & (1 << i)) {
        uint32_t index = m_module.constu32(i);

        uint32_t srcPtr = m_module.opAccessChain(srcPtrType, m_hs.outputPerPatch, 1, &index);
        uint32_t dstPtr = m_module.opAccessChain(dstPtrType, outputPerPatch,      1, &index);

        m_module.opStore(dstPtr, m_module.opLoad(vecType, srcPtr));
      }
    }
  }
    uint32_t emitTessInterfacePerPatch(
            spv::StorageClass                 storageClass) {
    const char* name = "vPatch";

    if (storageClass == spv::StorageClassPrivate)
      name = "rPatch";
    if (storageClass == spv::StorageClassOutput)
      name = "oPatch";

    uint32_t arrLen  = m_psgn != nullptr ? m_psgn->maxRegisterCount() : 0;

    if (!arrLen)
      return 0;

    uint32_t vecType = m_module.defVectorType (m_module.defFloatType(32), 4);
    uint32_t arrType = m_module.defArrayType  (vecType, m_module.constu32(arrLen));
    uint32_t ptrType = m_module.defPointerType(arrType, storageClass);
    uint32_t varId   = m_module.newVar        (ptrType, storageClass);

    m_module.setDebugName     (varId, name);

    if (storageClass != spv::StorageClassPrivate) {
      m_module.decorate         (varId, spv::DecorationPatch);
      m_module.decorateLocation (varId, 0);
    }

    return varId;
  }
    uint32_t emitTessInterfacePerVertex(
            spv::StorageClass                 storageClass,
            uint32_t                          vertexCount) {
    const bool isInput = storageClass == spv::StorageClassInput;

    uint32_t arrLen = isInput
      ? (m_isgn != nullptr ? m_isgn->maxRegisterCount() : 0)
      : (m_osgn != nullptr ? m_osgn->maxRegisterCount() : 0);

    if (!arrLen)
      return 0;

    uint32_t locIdx = m_psgn != nullptr
      ? m_psgn->maxRegisterCount()
      : 0;

    uint32_t vecType      = m_module.defVectorType (m_module.defFloatType(32), 4);
    uint32_t arrTypeInner = m_module.defArrayType  (vecType,      m_module.constu32(arrLen));
    uint32_t arrTypeOuter = m_module.defArrayType  (arrTypeInner, m_module.constu32(vertexCount));
    uint32_t ptrType      = m_module.defPointerType(arrTypeOuter, storageClass);
    uint32_t varId        = m_module.newVar        (ptrType,      storageClass);

    m_module.setDebugName     (varId, isInput ? "vVertex" : "oVertex");
    m_module.decorateLocation (varId, locIdx);
    return varId;
  }
    void emitDclInputArray(
            uint32_t          vertexCount) {
    DxbcVectorType info;
    info.ctype   = DxbcScalarType::Float32;
    info.ccount  = 4;

    // Define the array type. This will be two-dimensional
    // in some shaders, with the outer index representing
    // the vertex ID within an invocation.
    m_vArrayLength   = m_isgn != nullptr ? std::max(1u, m_isgn->maxRegisterCount()) : 1;
    m_vArrayLengthId = m_module.lateConst32(getScalarTypeId(DxbcScalarType::Uint32));

    uint32_t vectorTypeId = getVectorTypeId(info);
    uint32_t arrayTypeId = m_module.defArrayType(vectorTypeId, m_vArrayLengthId);

    if (vertexCount != 0) {
      arrayTypeId = m_module.defArrayType(
        arrayTypeId, m_module.constu32(vertexCount));
    }

    // Define the actual variable. Note that this is private
    // because we will copy input registers and some system
    // variables to the array during the setup phase.
    const uint32_t ptrTypeId = m_module.defPointerType(
      arrayTypeId, spv::StorageClassPrivate);

    const uint32_t varId = m_module.newVar(
      ptrTypeId, spv::StorageClassPrivate);

    m_module.setDebugName(varId, "shader_in");
    m_vArray = varId;
  }
    uint32_t emitDclClipCullDistanceArray(
            uint32_t          length,
            spv::BuiltIn      builtIn,
            spv::StorageClass storageClass) {
    if (length == 0)
      return 0;

    uint32_t t_f32 = m_module.defFloatType(32);
    uint32_t t_arr = m_module.defArrayType(t_f32, m_module.constu32(length));
    uint32_t t_ptr = m_module.defPointerType(t_arr, storageClass);
    uint32_t varId = m_module.newVar(t_ptr, storageClass);

    m_module.decorateBuiltIn(varId, builtIn);
    m_module.setDebugName(varId,
      builtIn == spv::BuiltInClipDistance
        ? "clip_distances"
        : "cull_distances");

    return varId;
  }
    DxbcCompilerHsControlPointPhase emitNewHullShaderControlPointPhase() {
    uint32_t funTypeId = m_module.defFunctionType(
      m_module.defVoidType(), 0, nullptr);

    uint32_t funId = m_module.allocateId();

    this->emitFunctionBegin(funId,
      m_module.defVoidType(),
      funTypeId);
    this->emitFunctionLabel();

    DxbcCompilerHsControlPointPhase result;
    result.functionId = funId;
    return result;
  }
    DxbcCompilerHsControlPointPhase emitNewHullShaderPassthroughPhase() {
    uint32_t funTypeId = m_module.defFunctionType(
      m_module.defVoidType(), 0, nullptr);

    // Begin passthrough function
    uint32_t funId = m_module.allocateId();
    m_module.setDebugName(funId, "hs_passthrough");

    this->emitFunctionBegin(funId,
      m_module.defVoidType(),
      funTypeId);
    this->emitFunctionLabel();

    // We'll basically copy each input variable to the corresponding
    // output, using the shader's invocation ID as the array index.
    const uint32_t invocationId = m_module.opLoad(
      getScalarTypeId(DxbcScalarType::Uint32),
      m_hs.builtinInvocationId);

    for (auto i = m_isgn->begin(); i != m_isgn->end(); i++) {
      this->emitDclInput(
        i->registerId, m_hs.vertexCountIn,
        i->componentMask,
        DxbcSystemValue::None,
        DxbcInterpolationMode::Undefined);

      // Vector type index
      const std::array<uint32_t, 2> dstIndices
        = {{ invocationId, m_module.constu32(i->registerId) }};

      DxbcRegisterPointer srcPtr;
      srcPtr.type = m_vRegs.at(i->registerId).type;
      srcPtr.id = m_module.opAccessChain(
        m_module.defPointerType(getVectorTypeId(srcPtr.type), spv::StorageClassInput),
        m_vRegs.at(i->registerId).id, 1, &invocationId);

      DxbcRegisterValue srcValue = emitRegisterBitcast(
        emitValueLoad(srcPtr), DxbcScalarType::Float32);

      DxbcRegisterPointer dstPtr;
      dstPtr.type = { DxbcScalarType::Float32, 4 };
      dstPtr.id = m_module.opAccessChain(
        m_module.defPointerType(getVectorTypeId(dstPtr.type), spv::StorageClassOutput),
        m_hs.outputPerVertex, dstIndices.size(), dstIndices.data());

      emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount));
    }

    // End function
    this->emitFunctionEnd();

    DxbcCompilerHsControlPointPhase result;
    result.functionId = funId;
    return result;
  }
    DxbcCompilerHsForkJoinPhase emitNewHullShaderForkJoinPhase() {
    uint32_t argTypeId = m_module.defIntType(32, 0);
    uint32_t funTypeId = m_module.defFunctionType(
      m_module.defVoidType(), 1, &argTypeId);

    uint32_t funId = m_module.allocateId();

    this->emitFunctionBegin(funId,
      m_module.defVoidType(),
      funTypeId);

    uint32_t argId = m_module.functionParameter(argTypeId);
    this->emitFunctionLabel();

    DxbcCompilerHsForkJoinPhase result;
    result.functionId = funId;
    result.instanceId = argId;
    return result;
  }
    uint32_t emitSamplePosArray() {
    const std::array<uint32_t, 32> samplePosVectors = {{
      // Invalid sample count / unbound resource
      m_module.constvec2f32( 0.0f, 0.0f),
      // VK_SAMPLE_COUNT_1_BIT
      m_module.constvec2f32( 0.0f, 0.0f),
      // VK_SAMPLE_COUNT_2_BIT
      m_module.constvec2f32( 0.25f, 0.25f),
      m_module.constvec2f32(-0.25f,-0.25f),
      // VK_SAMPLE_COUNT_4_BIT
      m_module.constvec2f32(-0.125f,-0.375f),
      m_module.constvec2f32( 0.375f,-0.125f),
      m_module.constvec2f32(-0.375f, 0.125f),
      m_module.constvec2f32( 0.125f, 0.375f),
      // VK_SAMPLE_COUNT_8_BIT
      m_module.constvec2f32( 0.0625f,-0.1875f),
      m_module.constvec2f32(-0.0625f, 0.1875f),
      m_module.constvec2f32( 0.3125f, 0.0625f),
      m_module.constvec2f32(-0.1875f,-0.3125f),
      m_module.constvec2f32(-0.3125f, 0.3125f),
      m_module.constvec2f32(-0.4375f,-0.0625f),
      m_module.constvec2f32( 0.1875f, 0.4375f),
      m_module.constvec2f32( 0.4375f,-0.4375f),
      // VK_SAMPLE_COUNT_16_BIT
      m_module.constvec2f32( 0.0625f, 0.0625f),
      m_module.constvec2f32(-0.0625f,-0.1875f),
      m_module.constvec2f32(-0.1875f, 0.1250f),
      m_module.constvec2f32( 0.2500f,-0.0625f),
      m_module.constvec2f32(-0.3125f,-0.1250f),
      m_module.constvec2f32( 0.1250f, 0.3125f),
      m_module.constvec2f32( 0.3125f, 0.1875f),
      m_module.constvec2f32( 0.1875f,-0.3125f),
      m_module.constvec2f32(-0.1250f, 0.3750f),
      m_module.constvec2f32( 0.0000f,-0.4375f),
      m_module.constvec2f32(-0.2500f,-0.3750f),
      m_module.constvec2f32(-0.3750f, 0.2500f),
      m_module.constvec2f32(-0.5000f, 0.0000f),
      m_module.constvec2f32( 0.4375f,-0.2500f),
      m_module.constvec2f32( 0.3750f, 0.4375f),
      m_module.constvec2f32(-0.4375f,-0.5000f),
    }};

    uint32_t arrayTypeId = getArrayTypeId({
      DxbcScalarType::Float32, 2,
      static_cast<uint32_t>(samplePosVectors.size()) });

    uint32_t samplePosArray = m_module.constComposite(
      arrayTypeId,
      samplePosVectors.size(),
      samplePosVectors.data());

    uint32_t varId = m_module.newVarInit(
      m_module.defPointerType(arrayTypeId, spv::StorageClassPrivate),
      spv::StorageClassPrivate, samplePosArray);

    m_module.setDebugName(varId, "g_sample_pos");
    m_module.decorate(varId, spv::DecorationNonWritable);
    return varId;
  }
    void emitFloatControl() {
    DxbcFloatControlFlags flags = m_moduleInfo.options.floatControl;

    if (flags.isClear())
      return;

    const uint32_t width32 = 32;
    const uint32_t width64 = 64;

    if (flags.test(DxbcFloatControlFlag::DenormFlushToZero32)) {
      m_module.enableCapability(spv::CapabilityDenormFlushToZero);
      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDenormFlushToZero, 1, &width32);
    }

    if (flags.test(DxbcFloatControlFlag::PreserveNan32)) {
      m_module.enableCapability(spv::CapabilitySignedZeroInfNanPreserve);
      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSignedZeroInfNanPreserve, 1, &width32);
    }

    if (m_module.hasCapability(spv::CapabilityFloat64)) {
      if (flags.test(DxbcFloatControlFlag::DenormPreserve64)) {
        m_module.enableCapability(spv::CapabilityDenormPreserve);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDenormPreserve, 1, &width64);
      }

      if (flags.test(DxbcFloatControlFlag::PreserveNan64)) {
        m_module.enableCapability(spv::CapabilitySignedZeroInfNanPreserve);
        m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSignedZeroInfNanPreserve, 1, &width64);
      }
    }
  }
    uint32_t emitNewVariable(
      const DxbcRegisterInfo& info) {
    const uint32_t ptrTypeId = this->getPointerTypeId(info);
    return m_module.newVar(ptrTypeId, info.sclass);
  }
    uint32_t emitNewBuiltinVariable(
      const DxbcRegisterInfo& info,
            spv::BuiltIn      builtIn,
      const char*             name) {
    const uint32_t varId = emitNewVariable(info);

    if (name)
      m_module.setDebugName(varId, name);

    m_module.decorateBuiltIn(varId, builtIn);

    if (m_programInfo.type() == DxbcProgramType::PixelShader
     && info.type.ctype != DxbcScalarType::Float32
     && info.type.ctype != DxbcScalarType::Bool
     && info.sclass == spv::StorageClassInput)
      m_module.decorate(varId, spv::DecorationFlat);

    return varId;
  }
    uint32_t emitBuiltinTessLevelOuter(
            spv::StorageClass storageClass) {
    uint32_t id = emitNewBuiltinVariable(
      DxbcRegisterInfo {
        { DxbcScalarType::Float32, 0, 4 },
        storageClass },
      spv::BuiltInTessLevelOuter,
      "bTessLevelOuter");

    m_module.decorate(id, spv::DecorationPatch);
    return id;
  }
    uint32_t emitBuiltinTessLevelInner(
            spv::StorageClass storageClass) {
    uint32_t id = emitNewBuiltinVariable(
      DxbcRegisterInfo {
        { DxbcScalarType::Float32, 0, 2 },
        storageClass },
      spv::BuiltInTessLevelInner,
      "bTessLevelInner");

    m_module.decorate(id, spv::DecorationPatch);
    return id;
  }
    uint32_t emitPushConstants() {
    uint32_t uintTypeId = m_module.defIntType(32, 0);
    uint32_t structTypeId = m_module.defStructTypeUnique(1, &uintTypeId);

    m_module.setDebugName(structTypeId, "pc_t");
    m_module.setDebugMemberName(structTypeId, 0, "RasterizerSampleCount");
    m_module.memberDecorateOffset(structTypeId, 0, 0);

    uint32_t ptrTypeId = m_module.defPointerType(structTypeId, spv::StorageClassPushConstant);
    uint32_t varId = m_module.newVar(ptrTypeId, spv::StorageClassPushConstant);

    m_module.setDebugName(varId, "pc");
    return varId;
  }
    DxbcCfgBlock* cfgFindBlock(
      const std::initializer_list<DxbcCfgBlockType>& types);
    DxbcBufferInfo getBufferInfo(
      const DxbcRegister& reg) {
    const uint32_t registerId = reg.idx[0].offset;

    switch (reg.type) {
      case DxbcOperandType::Resource: {
        const auto& texture = m_textures.at(registerId);

        DxbcBufferInfo result;
        result.image  = texture.imageInfo;
        result.stype  = texture.sampledType;
        result.type   = texture.type;
        result.typeId = texture.imageTypeId;
        result.varId  = texture.varId;
        result.stride = texture.structStride;
        result.coherence = 0;
        result.isSsbo = texture.isRawSsbo;
        return result;
      } break;

      case DxbcOperandType::UnorderedAccessView: {
        const auto& uav = m_uavs.at(registerId);

        DxbcBufferInfo result;
        result.image  = uav.imageInfo;
        result.stype  = uav.sampledType;
        result.type   = uav.type;
        result.typeId = uav.imageTypeId;
        result.varId  = uav.varId;
        result.stride = uav.structStride;
        result.coherence = uav.coherence;
        result.isSsbo = uav.isRawSsbo;
        return result;
      } break;

      case DxbcOperandType::ThreadGroupSharedMemory: {
        DxbcBufferInfo result;
        result.image  = { spv::DimBuffer, 0, 0, 0 };
        result.stype  = DxbcScalarType::Uint32;
        result.type   = m_gRegs.at(registerId).type;
        result.typeId = m_module.defPointerType(
          getScalarTypeId(DxbcScalarType::Uint32),
          spv::StorageClassWorkgroup);
        result.varId  = m_gRegs.at(registerId).varId;
        result.stride = m_gRegs.at(registerId).elementStride;
        result.coherence = spv::ScopeInvocation;
        result.isSsbo = false;
        return result;
      } break;

      default:
        throw DxvkError(str::format("DxbcCompiler: Invalid operand type for buffer: ", reg.type));
    }
  }
    uint32_t getTexSizeDim(
      const DxbcImageInfo& imageType) const {
    switch (imageType.dim) {
      case spv::DimBuffer:  return 1 + imageType.array;
      case spv::Dim1D:      return 1 + imageType.array;
      case spv::Dim2D:      return 2 + imageType.array;
      case spv::Dim3D:      return 3 + imageType.array;
      case spv::DimCube:    return 2 + imageType.array;
      default: throw DxvkError("DxbcCompiler: getTexLayerDim: Unsupported image dimension");
    }
  }
    uint32_t getTexLayerDim(
      const DxbcImageInfo& imageType) const {
    switch (imageType.dim) {
      case spv::DimBuffer:  return 1;
      case spv::Dim1D:      return 1;
      case spv::Dim2D:      return 2;
      case spv::Dim3D:      return 3;
      case spv::DimCube:    return 3;
      default: throw DxvkError("DxbcCompiler: getTexLayerDim: Unsupported image dimension");
    }
  }
    uint32_t getTexCoordDim(
      const DxbcImageInfo& imageType) const {
    return getTexLayerDim(imageType) + imageType.array;
  }
    DxbcRegMask getTexCoordMask(
      const DxbcImageInfo& imageType) const {
    return DxbcRegMask::firstN(getTexCoordDim(imageType));
  }
    DxbcVectorType getInputRegType(
            uint32_t regIdx) const {
    switch (m_programInfo.type()) {
      case DxbcProgramType::VertexShader: {
        const DxbcSgnEntry* entry = m_isgn->findByRegister(regIdx);

        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;

        if (entry != nullptr) {
          result.ctype  = entry->componentType;
          result.ccount = entry->componentMask.popCount();
        }

        return result;
      }

      case DxbcProgramType::DomainShader: {
        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;
        return result;
      }

      default: {
        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;

        if (m_isgn == nullptr || !m_isgn->findByRegister(regIdx))
          return result;

        DxbcRegMask mask(0u);
        DxbcRegMask used(0u);

        for (const auto& e : *m_isgn) {
          if (e.registerId == regIdx && !ignoreInputSystemValue(e.systemValue)) {
            mask |= e.componentMask;
            used |= e.componentUsed;
          }
        }

        if (m_programInfo.type() == DxbcProgramType::PixelShader) {
          if ((used.raw() & mask.raw()) == used.raw())
            mask = used;
        }

        result.ccount = mask.minComponents();
        return result;
      }
    }
  }
    DxbcVectorType getOutputRegType(
            uint32_t regIdx) const {
    switch (m_programInfo.type()) {
      case DxbcProgramType::PixelShader: {
        const DxbcSgnEntry* entry = m_osgn->findByRegister(regIdx);

        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;

        if (entry != nullptr) {
          result.ctype  = entry->componentType;
          result.ccount = entry->componentMask.popCount();
        }

        return result;
      }

      case DxbcProgramType::HullShader: {
        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;
        return result;
      }

      default: {
        DxbcVectorType result;
        result.ctype  = DxbcScalarType::Float32;
        result.ccount = 4;

        if (m_osgn->findByRegister(regIdx))
          result.ccount = m_osgn->regMask(regIdx).minComponents();
        return result;
      }
    }
  }
    DxbcImageInfo getResourceType(
            DxbcResourceDim   resourceType,
            bool              isUav) const {
    uint32_t ms = m_moduleInfo.options.disableMsaa ? 0 : 1;

    switch (resourceType) {
      case DxbcResourceDim::Buffer:         return { spv::DimBuffer, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_MAX_ENUM   };
      case DxbcResourceDim::Texture1D:      return { spv::Dim1D,     0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_1D         };
      case DxbcResourceDim::Texture1DArr:   return { spv::Dim1D,     1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_1D_ARRAY   };
      case DxbcResourceDim::Texture2D:      return { spv::Dim2D,     0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D         };
      case DxbcResourceDim::Texture2DArr:   return { spv::Dim2D,     1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D_ARRAY   };
      case DxbcResourceDim::Texture2DMs:    return { spv::Dim2D,     0, ms,isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D         };
      case DxbcResourceDim::Texture2DMsArr: return { spv::Dim2D,     1, ms,isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D_ARRAY   };
      case DxbcResourceDim::Texture3D:      return { spv::Dim3D,     0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_3D         };
      case DxbcResourceDim::TextureCube:    return { spv::DimCube,   0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_CUBE       };
      case DxbcResourceDim::TextureCubeArr: return { spv::DimCube,   1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY };
      default: throw DxvkError(str::format("DxbcCompiler: Unsupported resource type: ", resourceType));
    }
  }
    spv::ImageFormat getScalarImageFormat(
            DxbcScalarType type) const {
    switch (type) {
      case DxbcScalarType::Float32: return spv::ImageFormatR32f;
      case DxbcScalarType::Sint32:  return spv::ImageFormatR32i;
      case DxbcScalarType::Uint32:  return spv::ImageFormatR32ui;
      default: throw DxvkError("DxbcCompiler: Unhandled scalar resource type");
    }
  }
    bool isDoubleType(
            DxbcScalarType type) const {
    return type == DxbcScalarType::Sint64
        || type == DxbcScalarType::Uint64
        || type == DxbcScalarType::Float64;
  }
    DxbcRegisterPointer getIndexableTempPtr(
      const DxbcRegister&           operand,
            DxbcRegisterValue       vectorId) {
    // x# regs are indexed as follows:
    //    (0) register index (immediate)
    //    (1) element index (relative)
    const uint32_t regId = operand.idx[0].offset;

    DxbcRegisterInfo info;
    info.type.ctype   = DxbcScalarType::Float32;
    info.type.ccount  = m_xRegs[regId].ccount;
    info.type.alength = 0;
    info.sclass       = spv::StorageClassPrivate;

    DxbcRegisterPointer result;
    result.type.ctype  = info.type.ctype;
    result.type.ccount = info.type.ccount;
    result.id = m_module.opAccessChain(
      getPointerTypeId(info),
      m_xRegs.at(regId).varId,
      1, &vectorId.id);

    return result;
  }
    bool caseBlockIsFallthrough() const {
    return m_lastOp != DxbcOpcode::Case
        && m_lastOp != DxbcOpcode::Default
        && m_lastOp != DxbcOpcode::Break
        && m_lastOp != DxbcOpcode::Ret;
  }
    uint32_t getUavCoherence(
            uint32_t                registerId,
            DxbcUavFlags            flags) {
    // For any ROV with write access, we must ensure that
    // availability operations happen within the locked scope.
    if (flags.test(DxbcUavFlag::RasterizerOrdered)
     && (m_analysis->uavInfos[registerId].accessFlags & VK_ACCESS_SHADER_WRITE_BIT)) {
      m_hasGloballyCoherentUav = true;
      m_hasRasterizerOrderedUav = true;
      return spv::ScopeQueueFamily;
    }

    // Ignore any resources that can't both be read and written in
    // the current shader, explicit availability/visibility operands
    // are not useful in that case.
    if (m_analysis->uavInfos[registerId].accessFlags != (VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT))
      return 0;

    // If the globally coherent flag is set, the resource must be
    // coherent across multiple workgroups of the same dispatch
    if (flags.test(DxbcUavFlag::GloballyCoherent)) {
      m_hasGloballyCoherentUav = true;
      return spv::ScopeQueueFamily;
    }

    // In compute shaders, UAVs are implicitly workgroup coherent,
    // but we can rely on memory barrier instructions to make any
    // access available and visible to the entire workgroup.
    if (m_programInfo.type() == DxbcProgramType::ComputeShader)
      return spv::ScopeInvocation;

    return 0;
  }
    bool ignoreInputSystemValue(
            DxbcSystemValue         sv) const {
    switch (sv) {
      case DxbcSystemValue::Position:
      case DxbcSystemValue::IsFrontFace:
      case DxbcSystemValue::SampleIndex:
      case DxbcSystemValue::PrimitiveId:
      case DxbcSystemValue::Coverage:
        return m_programInfo.type() == DxbcProgramType::PixelShader;

      default:
        return false;
    }
  }
    void emitUavBarrier(
            uint64_t                readMask,
            uint64_t                writeMask) {
    if (!m_moduleInfo.options.forceComputeUavBarriers
     || m_programInfo.type() != DxbcProgramType::ComputeShader)
      return;

    // If both masks are 0, emit a barrier in case at least one read-write UAV
    // has a pending unsynchronized access. Only consider read-after-write and
    // write-after-read hazards, assume that back-to-back stores are safe and
    // do not overlap in memory. Atomics are also completely ignored here.
    uint64_t rdMask = m_uavRdMask;
    uint64_t wrMask = m_uavWrMask;

    bool insertBarrier = bool(rdMask & wrMask);

    if (readMask || writeMask) {
      rdMask &= m_uavWrMask;
      wrMask &= m_uavRdMask;
    }

    for (auto uav : bit::BitMask(rdMask | wrMask)) {
      constexpr VkAccessFlags rwAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
      insertBarrier |= (m_analysis->uavInfos[uav].accessFlags & rwAccess) == rwAccess;
    }

    // Need to be in uniform top-level control flow, or otherwise
    // it is not safe to insert control barriers.
    if (insertBarrier && m_controlFlowBlocks.empty() && m_topLevelIsUniform) {
      m_module.opControlBarrier(
        m_module.constu32(spv::ScopeWorkgroup),
        m_module.constu32(m_hasGloballyCoherentUav ? spv::ScopeQueueFamily : spv::ScopeWorkgroup),
        m_module.constu32(spv::MemorySemanticsWorkgroupMemoryMask
                        | spv::MemorySemanticsImageMemoryMask
                        | spv::MemorySemanticsUniformMemoryMask
                        | spv::MemorySemanticsAcquireReleaseMask
                        | spv::MemorySemanticsMakeAvailableMask
                        | spv::MemorySemanticsMakeVisibleMask));

      m_uavWrMask = 0u;
      m_uavRdMask = 0u;
    }

    // Mark pending accesses
    m_uavWrMask |= writeMask;
    m_uavRdMask |= readMask;
  }
    uint32_t getScalarTypeId(
            DxbcScalarType type) {
    if (type == DxbcScalarType::Float64)
      m_module.enableCapability(spv::CapabilityFloat64);

    if (type == DxbcScalarType::Sint64 || type == DxbcScalarType::Uint64)
      m_module.enableCapability(spv::CapabilityInt64);

    switch (type) {
      case DxbcScalarType::Uint32:  return m_module.defIntType(32, 0);
      case DxbcScalarType::Uint64:  return m_module.defIntType(64, 0);
      case DxbcScalarType::Sint32:  return m_module.defIntType(32, 1);
      case DxbcScalarType::Sint64:  return m_module.defIntType(64, 1);
      case DxbcScalarType::Float32: return m_module.defFloatType(32);
      case DxbcScalarType::Float64: return m_module.defFloatType(64);
      case DxbcScalarType::Bool:    return m_module.defBoolType();
    }

    throw DxvkError("DxbcCompiler: Invalid scalar type");
  }
    uint32_t getVectorTypeId(
      const DxbcVectorType& type) {
    uint32_t typeId = this->getScalarTypeId(type.ctype);

    if (type.ccount > 1)
      typeId = m_module.defVectorType(typeId, type.ccount);

    return typeId;
  }
    uint32_t getArrayTypeId(
      const DxbcArrayType& type) {
    DxbcVectorType vtype;
    vtype.ctype  = type.ctype;
    vtype.ccount = type.ccount;

    uint32_t typeId = this->getVectorTypeId(vtype);

    if (type.alength != 0) {
      typeId = m_module.defArrayType(typeId,
        m_module.constu32(type.alength));
    }

    return typeId;
  }
    uint32_t getPointerTypeId(
      const DxbcRegisterInfo& type) {
    return m_module.defPointerType(
      this->getArrayTypeId(type.type),
      type.sclass);
  }
    uint32_t getSparseResultTypeId(
            uint32_t baseType) {
    m_module.enableCapability(spv::CapabilitySparseResidency);

    uint32_t uintType = getScalarTypeId(DxbcScalarType::Uint32);
    std::array<uint32_t, 2> typeIds = { uintType, baseType };
    return m_module.defStructType(typeIds.size(), typeIds.data());
  }
    uint32_t getFunctionId(
            uint32_t          functionNr) {
    auto entry = m_subroutines.find(functionNr);
    if (entry != m_subroutines.end())
      return entry->second;

    uint32_t functionId = m_module.allocateId();
    m_subroutines.insert({ functionNr, functionId });
    return functionId;
  }
    DxbcCompilerHsForkJoinPhase* getCurrentHsForkJoinPhase();
};

class DxbcAnalyzer {
  public:
    DxbcModule(DxbcReader& reader) {
    for (uint32_t i = 0; i < m_header.numChunks(); i++) {

      // The chunk tag is stored at the beginning of each chunk
      auto chunkReader = reader.clone(m_header.chunkOffset(i));
      auto tag         = chunkReader.readTag();

      // The chunk size follows right after the four-character
      // code. This does not include the eight bytes that are
      // consumed by the FourCC and chunk length entry.
      auto chunkLength = chunkReader.readu32();

      chunkReader = chunkReader.clone(8);
      chunkReader = chunkReader.resize(chunkLength);

      if ((tag == "SHDR") || (tag == "SHEX"))
        m_shexChunk = new DxbcShex(chunkReader);

      if ((tag == "ISGN") || (tag == "ISG1"))
        m_isgnChunk = new DxbcIsgn(chunkReader, tag);

      if ((tag == "OSGN") || (tag == "OSG5") || (tag == "OSG1"))
        m_osgnChunk = new DxbcIsgn(chunkReader, tag);

      if ((tag == "PCSG") || (tag == "PSG1"))
        m_psgnChunk = new DxbcIsgn(chunkReader, tag);
    }
  }
    ~DxbcModule() {

  }
    SpirvCodeBuffer compile(
      const DxbcModuleInfo& moduleInfo,
      const std::string&    fileName) {
    if (m_shexChunk == nullptr)
      throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk");

    DxbcAnalysisInfo analysisInfo;

    DxbcAnalyzer analyzer(moduleInfo,
      m_shexChunk->programInfo(),
      m_isgnChunk, m_osgnChunk,
      m_psgnChunk, analysisInfo);

    this->runAnalyzer(analyzer, m_shexChunk->slice());

    m_bindings = std::make_optional(analysisInfo.bindings);

    DxbcCompiler compiler(
      fileName, moduleInfo,
      m_shexChunk->programInfo(),
      m_isgnChunk, m_osgnChunk,
      m_psgnChunk, analysisInfo);

    this->runCompiler(compiler, m_shexChunk->slice());

    m_icb = compiler.getIcbData();

    return compiler.finalize();
  }
    SpirvCodeBuffer compilePassthroughShader(
      const DxbcModuleInfo& moduleInfo,
      const std::string&    fileName) const {
    if (m_shexChunk == nullptr)
      throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk");

    DxbcAnalysisInfo analysisInfo;

    DxbcCompiler compiler(
      fileName, moduleInfo,
      DxbcProgramType::GeometryShader,
      m_osgnChunk, m_osgnChunk,
      m_psgnChunk, analysisInfo);

    compiler.processXfbPassthrough();
    return compiler.finalize();
  }
  private:
    DxbcHeader   m_header;
    Rc<DxbcIsgn> m_isgnChunk;
    Rc<DxbcIsgn> m_osgnChunk;
    Rc<DxbcIsgn> m_psgnChunk;
    Rc<DxbcShex> m_shexChunk;
    std::vector<uint32_t> m_icb;
    std::optional<DxbcBindingMask> m_bindings;
    void runAnalyzer(
            DxbcAnalyzer&       analyzer,
            DxbcCodeSlice       slice) const {
    DxbcDecodeContext decoder;

    while (!slice.atEnd()) {
      decoder.decodeInstruction(slice);

      analyzer.processInstruction(
        decoder.getInstruction());
    }
  }
    void runCompiler(
            DxbcCompiler&       compiler,
            DxbcCodeSlice       slice) const {
    DxbcDecodeContext decoder;

    while (!slice.atEnd()) {
      decoder.decodeInstruction(slice);

      compiler.processInstruction(
        decoder.getInstruction());
    }
  }
};

class DxbcAnalyzer {
  public:
    DxbcAnalyzer(
      const DxbcModuleInfo&     moduleInfo,
      const DxbcProgramInfo&    programInfo,
      const Rc<DxbcIsgn>&       isgn,
      const Rc<DxbcIsgn>&       osgn,
      const Rc<DxbcIsgn>&       psgn,
            DxbcAnalysisInfo&   analysis) {
    // Get number of clipping and culling planes from the
    // input and output signatures. We will need this to
    // declare the shader input and output interfaces.
    m_analysis->clipCullIn  = getClipCullInfo(m_isgn);
    m_analysis->clipCullOut = getClipCullInfo(m_osgn);
  }
    ~DxbcAnalyzer() {

  }
    void processInstruction(
      const DxbcShaderInstruction&  ins) {
    switch (ins.opClass) {
      case DxbcInstClass::Atomic: {
        const uint32_t operandId = ins.dstCount - 1;

        if (ins.dst[operandId].type == DxbcOperandType::UnorderedAccessView) {
          const uint32_t registerId = ins.dst[operandId].idx[0].offset;
          m_analysis->uavInfos[registerId].accessAtomicOp = true;
          m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;

          // Check whether the atomic operation is order-invariant
          DxvkAccessOp op = DxvkAccessOp::None;

          switch (ins.op) {
            case DxbcOpcode::AtomicAnd:  op = DxvkAccessOp::And;  break;
            case DxbcOpcode::AtomicOr:   op = DxvkAccessOp::Or;   break;
            case DxbcOpcode::AtomicXor:  op = DxvkAccessOp::Xor;  break;
            case DxbcOpcode::AtomicIAdd: op = DxvkAccessOp::Add;  break;
            case DxbcOpcode::AtomicIMax: op = DxvkAccessOp::IMax; break;
            case DxbcOpcode::AtomicIMin: op = DxvkAccessOp::IMin; break;
            case DxbcOpcode::AtomicUMax: op = DxvkAccessOp::UMax; break;
            case DxbcOpcode::AtomicUMin: op = DxvkAccessOp::UMin; break;
            default: break;
          }

          setUavAccessOp(registerId, op);
        }
      } break;

      case DxbcInstClass::TextureSample:
      case DxbcInstClass::TextureGather:
      case DxbcInstClass::TextureQueryLod:
      case DxbcInstClass::VectorDeriv: {
        m_analysis->usesDerivatives = true;
      } break;

      case DxbcInstClass::ControlFlow: {
        if (ins.op == DxbcOpcode::Discard)
          m_analysis->usesKill = true;
      } break;

      case DxbcInstClass::BufferLoad: {
        uint32_t operandId = ins.op == DxbcOpcode::LdStructured ? 2 : 1;
        bool sparseFeedback = ins.dstCount == 2;

        if (ins.src[operandId].type == DxbcOperandType::UnorderedAccessView) {
          const uint32_t registerId = ins.src[operandId].idx[0].offset;
          m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT;
          m_analysis->uavInfos[registerId].sparseFeedback |= sparseFeedback;

          setUavAccessOp(registerId, DxvkAccessOp::None);
        } else if (ins.src[operandId].type == DxbcOperandType::Resource) {
          const uint32_t registerId = ins.src[operandId].idx[0].offset;
          m_analysis->srvInfos[registerId].sparseFeedback |= sparseFeedback;
        }
      } break;

      case DxbcInstClass::BufferStore: {
        if (ins.dst[0].type == DxbcOperandType::UnorderedAccessView) {
          const uint32_t registerId = ins.dst[0].idx[0].offset;
          m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT;

          setUavAccessOp(registerId, getStoreAccessOp(ins.dst[0].mask, ins.src[ins.srcCount - 1u]));
        }
      } break;

      case DxbcInstClass::TypedUavLoad: {
        const uint32_t registerId = ins.src[1].idx[0].offset;
        m_analysis->uavInfos[registerId].accessTypedLoad = true;
        m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT;

        setUavAccessOp(registerId, DxvkAccessOp::None);
      } break;

      case DxbcInstClass::TypedUavStore: {
        const uint32_t registerId = ins.dst[0].idx[0].offset;
        m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT;

        // The UAV format may change between dispatches, so be conservative here
        // and only allow this optimization when the app is writing zeroes.
        DxvkAccessOp storeOp = getStoreAccessOp(DxbcRegMask(0xf), ins.src[1u]);

        if (storeOp != DxvkAccessOp(DxvkAccessOp::OpType::StoreUi, 0u))
          storeOp = DxvkAccessOp::None;

        setUavAccessOp(registerId, storeOp);
      } break;

      case DxbcInstClass::Declaration: {
        switch (ins.op) {
          case DxbcOpcode::DclConstantBuffer: {
            uint32_t registerId = ins.dst[0].idx[0].offset;

            if (registerId < DxbcConstBufBindingCount)
              m_analysis->bindings.cbvMask |= 1u << registerId;
          } break;

          case DxbcOpcode::DclSampler: {
            uint32_t registerId = ins.dst[0].idx[0].offset;

            if (registerId < DxbcSamplerBindingCount)
              m_analysis->bindings.samplerMask |= 1u << registerId;
          } break;

          case DxbcOpcode::DclResource:
          case DxbcOpcode::DclResourceRaw:
          case DxbcOpcode::DclResourceStructured: {
            uint32_t registerId = ins.dst[0].idx[0].offset;

            uint32_t idx = registerId / 64u;
            uint32_t bit = registerId % 64u;

            if (registerId < DxbcResourceBindingCount)
              m_analysis->bindings.srvMask[idx] |= uint64_t(1u) << bit;
          } break;

          case DxbcOpcode::DclUavTyped:
          case DxbcOpcode::DclUavRaw:
          case DxbcOpcode::DclUavStructured: {
            uint32_t registerId = ins.dst[0].idx[0].offset;

            if (registerId < DxbcUavBindingCount)
              m_analysis->bindings.uavMask |= uint64_t(1u) << registerId;
          } break;

          default: ;
        }
      } break;

      default:
        break;
    }

    for (uint32_t i = 0; i < ins.dstCount; i++) {
      if (ins.dst[i].type == DxbcOperandType::IndexableTemp) {
        uint32_t index = ins.dst[i].idx[0].offset;
        m_analysis->xRegMasks[index] |= ins.dst[i].mask;
      }
    }
  }
  private:
    Rc<DxbcIsgn> m_isgn;
    Rc<DxbcIsgn> m_osgn;
    Rc<DxbcIsgn> m_psgn;
    DxbcAnalysisInfo* m_analysis = nullptr;
    DxbcClipCullInfo getClipCullInfo(
      const Rc<DxbcIsgn>& sgn) const {
    DxbcClipCullInfo result;

    if (sgn != nullptr) {
      for (auto e = sgn->begin(); e != sgn->end(); e++) {
        const uint32_t componentCount = e->componentMask.popCount();

        if (e->systemValue == DxbcSystemValue::ClipDistance)
          result.numClipPlanes += componentCount;
        if (e->systemValue == DxbcSystemValue::CullDistance)
          result.numCullPlanes += componentCount;
      }
    }

    return result;
  }
    void setUavAccessOp(uint32_t uav, DxvkAccessOp op) {
    if (m_analysis->uavInfos[uav].accessOp == DxvkAccessOp::None)
      m_analysis->uavInfos[uav].accessOp = op;

    // Maintain ordering if the UAV is accessed via other operations as well
    if (op == DxvkAccessOp::None || m_analysis->uavInfos[uav].accessOp != op)
      m_analysis->uavInfos[uav].nonInvariantAccess = true;
  }
    static DxvkAccessOp getStoreAccessOp(DxbcRegMask writeMask, const DxbcRegister& src) {
    if (src.type != DxbcOperandType::Imm32)
      return DxvkAccessOp::None;

    // Trivial case, same value is written to all components
    if (src.componentCount == DxbcComponentCount::Component1)
      return getConstantStoreOp(src.imm.u32_1);

    if (src.componentCount != DxbcComponentCount::Component4)
      return DxvkAccessOp::None;

    // Otherwise, make sure that all written components are equal
    DxvkAccessOp op = DxvkAccessOp::None;

    for (uint32_t i = 0u; i < 4u; i++) {
      if (!writeMask[i])
        continue;

      // If the written value can't be represented, skip
      DxvkAccessOp scalarOp = getConstantStoreOp(src.imm.u32_4[i]);

      if (scalarOp == DxvkAccessOp::None)
        return DxvkAccessOp::None;

      // First component written
      if (op == DxvkAccessOp::None)
        op = scalarOp;

      // Conflicting store ops
      if (op != scalarOp)
        return DxvkAccessOp::None;
    }

    return op;
  }
    static DxvkAccessOp getConstantStoreOp(uint32_t value) {
    constexpr uint32_t mask = 0xfffu;

    uint32_t ubits = value & mask;
    uint32_t fbits = (value >> 20u);

    if (value == ubits)
      return DxvkAccessOp(DxvkAccessOp::OpType::StoreUi, ubits);

    if (value == (ubits | ~mask))
      return DxvkAccessOp(DxvkAccessOp::OpType::StoreSi, ubits);

    if (value == (fbits << 20u))
      return DxvkAccessOp(DxvkAccessOp::OpType::StoreF, fbits);

    return DxvkAccessOp::None;
  }
};

class DxbcRegModifier {
  public:
  private:
    uint8_t m_mask = 0;
  public:
  private:
    uint8_t m_mask = 0;
  public:
  private:
    uint32_t m_bits;
  public:
    const uint32_t* ptrAt(uint32_t id) const;
    uint32_t at(uint32_t id) const {
    if (m_ptr + id >= m_end)
      throw DxvkError("DxbcCodeSlice: End of stream");
    return m_ptr[id];
  }
    uint32_t read() {
    if (m_ptr >= m_end)
      throw DxvkError("DxbcCodeSlice: End of stream");
    return *(m_ptr++);
  }
    DxbcCodeSlice take(uint32_t n) const {
    if (m_ptr + n > m_end)
      throw DxvkError("DxbcCodeSlice: End of stream");
    return DxbcCodeSlice(m_ptr, m_ptr + n);
  }
    DxbcCodeSlice skip(uint32_t n) const {
    if (m_ptr + n > m_end)
      throw DxvkError("DxbcCodeSlice: End of stream");
    return DxbcCodeSlice(m_ptr + n, m_end);
  }
  private:
    const uint32_t* m_ptr = nullptr;
    const uint32_t* m_end = nullptr;
  public:
    void decodeInstruction(DxbcCodeSlice& code) {
    const uint32_t token0 = code.at(0);

    // Initialize the instruction structure. Some of these values
    // may not get written otherwise while decoding the instruction.
    m_instruction.op             = static_cast<DxbcOpcode>(bit::extract(token0, 0, 10));
    m_instruction.opClass        = DxbcInstClass::Undefined;
    m_instruction.sampleControls = { 0, 0, 0 };
    m_instruction.dstCount       = 0;
    m_instruction.srcCount       = 0;
    m_instruction.immCount       = 0;
    m_instruction.dst            = m_dstOperands.data();
    m_instruction.src            = m_srcOperands.data();
    m_instruction.imm            = m_immOperands.data();
    m_instruction.customDataType = DxbcCustomDataClass::Comment;
    m_instruction.customDataSize = 0;
    m_instruction.customData     = nullptr;

    // Reset the index pointer, which may still contain
    // a non-zero value from the previous iteration
    m_indexId = 0;

    // Instruction length, in DWORDs. This includes the token
    // itself and any other prefix that an instruction may have.
    uint32_t length = 0;

    if (m_instruction.op == DxbcOpcode::CustomData) {
      length = code.at(1);
      this->decodeCustomData(code.take(length));
    } else {
      length = bit::extract(token0, 24, 30);
      this->decodeOperation(code.take(length));
    }

    // Advance the caller's slice to the next token so that
    // they can make consecutive calls to decodeInstruction()
    code = code.skip(length);
  }
  private:
    DxbcShaderInstruction m_instruction;
    std::array<DxbcRegister,  8> m_dstOperands;
    std::array<DxbcRegister,  8> m_srcOperands;
    std::array<DxbcImmediate, 4> m_immOperands;
    std::array<DxbcRegister, 12> m_indices;
    uint32_t m_indexId = 0;
    void decodeCustomData(DxbcCodeSlice code) {
    const uint32_t blockLength = code.at(1);

    if (blockLength < 2) {
      Logger::err("DxbcDecodeContext: Invalid custom data block");
      return;
    }

    // Custom data blocks have their own instruction class
    m_instruction.op      = DxbcOpcode::CustomData;
    m_instruction.opClass = DxbcInstClass::CustomData;

    // We'll point into the code buffer rather than making a copy
    m_instruction.customDataType = static_cast<DxbcCustomDataClass>(
      bit::extract(code.at(0), 11, 31));
    m_instruction.customDataSize = blockLength - 2;
    m_instruction.customData     = code.ptrAt(2);
  }
    void decodeOperation(DxbcCodeSlice code) {
    uint32_t token = code.read();

    // Result modifiers, which are applied to common ALU ops
    m_instruction.modifiers.saturate = !!bit::extract(token, 13, 13);
    m_instruction.modifiers.precise  = !!bit::extract(token, 19, 22);

    // Opcode controls. It will depend on the
    // opcode itself which ones are valid.
    m_instruction.controls = DxbcShaderOpcodeControls(token);

    // Process extended opcode tokens
    while (bit::extract(token, 31, 31)) {
      token = code.read();

      const DxbcExtOpcode extOpcode
        = static_cast<DxbcExtOpcode>(bit::extract(token, 0, 5));

      switch (extOpcode) {
        case DxbcExtOpcode::SampleControls: {
          struct {
            int u : 4;
            int v : 4;
            int w : 4;
          } aoffimmi;

          aoffimmi.u = bit::extract(token,  9, 12);
          aoffimmi.v = bit::extract(token, 13, 16);
          aoffimmi.w = bit::extract(token, 17, 20);

          // Four-bit signed numbers, sign-extend them
          m_instruction.sampleControls.u = aoffimmi.u;
          m_instruction.sampleControls.v = aoffimmi.v;
          m_instruction.sampleControls.w = aoffimmi.w;
        } break;

        case DxbcExtOpcode::ResourceDim:
        case DxbcExtOpcode::ResourceReturnType:
          break;  // part of resource description

        default:
          Logger::warn(str::format(
            "DxbcDecodeContext: Unhandled extended opcode: ",
            extOpcode));
      }
    }

    // Retrieve the instruction format in order to parse the
    // operands. Doing this mostly automatically means that
    // the compiler can rely on the operands being valid.
    const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op);
    m_instruction.opClass = format.instructionClass;

    for (uint32_t i = 0; i < format.operandCount; i++)
      this->decodeOperand(code, format.operands[i]);
  }
    void decodeComponentSelection(DxbcRegister& reg, uint32_t token) {
    // Pick the correct component selection mode based on the
    // component count. We'll simplify this here so that the
    // compiler can assume that everything is a 4D vector.
    reg.componentCount = static_cast<DxbcComponentCount>(bit::extract(token, 0, 1));

    switch (reg.componentCount) {
      // No components - used for samplers etc.
      case DxbcComponentCount::Component0:
        reg.mask    = DxbcRegMask(false, false, false, false);
        reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
        break;

      // One component - used for immediates
      // and a few built-in registers.
      case DxbcComponentCount::Component1:
        reg.mask    = DxbcRegMask(true, false, false, false);
        reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
        break;

      // Four components - everything else. This requires us
      // to actually parse the component selection mode.
      case DxbcComponentCount::Component4: {
        const DxbcRegMode componentMode =
          static_cast<DxbcRegMode>(bit::extract(token, 2, 3));

        switch (componentMode) {
          // Write mask for destination operands
          case DxbcRegMode::Mask:
            reg.mask    = bit::extract(token, 4, 7);
            reg.swizzle = DxbcRegSwizzle(0, 1, 2, 3);
            break;

          // Swizzle for source operands (including resources)
          case DxbcRegMode::Swizzle:
            reg.mask    = DxbcRegMask(true, true, true, true);
            reg.swizzle = DxbcRegSwizzle(
              bit::extract(token,  4,  5),
              bit::extract(token,  6,  7),
              bit::extract(token,  8,  9),
              bit::extract(token, 10, 11));
            break;

          // Selection of one component. We can generate both a
          // mask and a swizzle for this so that the compiler
          // won't have to deal with this case specifically.
          case DxbcRegMode::Select1: {
            const uint32_t n = bit::extract(token, 4, 5);
            reg.mask    = DxbcRegMask(n == 0, n == 1, n == 2, n == 3);
            reg.swizzle = DxbcRegSwizzle(n, n, n, n);
          } break;

          default:
            Logger::warn("DxbcDecodeContext: Invalid component selection mode");
        }
      } break;

      default:
        Logger::warn("DxbcDecodeContext: Invalid component count");
    }
  }
    void decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
    while (bit::extract(token, 31, 31)) {
      token = code.read();

      // Type of the extended operand token
      const DxbcOperandExt extTokenType =
        static_cast<DxbcOperandExt>(bit::extract(token, 0, 5));

      switch (extTokenType) {
        // Operand modifiers, which are used to manipulate the
        // value of a source operand during the load operation
        case DxbcOperandExt::OperandModifier:
          reg.modifiers = bit::extract(token, 6, 13);
          break;

        default:
          Logger::warn(str::format(
            "DxbcDecodeContext: Unhandled extended operand token: ",
            extTokenType));
      }
    }
  }
    void decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg) {
    if (reg.type == DxbcOperandType::Imm32
     || reg.type == DxbcOperandType::Imm64) {
      switch (reg.componentCount) {
        // This is commonly used if only one vector
        // component is involved in an operation
        case DxbcComponentCount::Component1: {
          reg.imm.u32_1 = code.read();
        } break;

        // Typical four-component vector
        case DxbcComponentCount::Component4: {
          reg.imm.u32_4[0] = code.read();
          reg.imm.u32_4[1] = code.read();
          reg.imm.u32_4[2] = code.read();
          reg.imm.u32_4[3] = code.read();
        } break;

        default:
          Logger::warn("DxbcDecodeContext: Invalid component count for immediate operand");
      }
    }
  }
    void decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
    reg.idxDim = bit::extract(token, 20, 21);

    for (uint32_t i = 0; i < reg.idxDim; i++) {
      // An index can be encoded in various different ways
      const DxbcOperandIndexRepresentation repr =
        static_cast<DxbcOperandIndexRepresentation>(
          bit::extract(token, 22 + 3 * i, 24 + 3 * i));

      switch (repr) {
        case DxbcOperandIndexRepresentation::Imm32:
          reg.idx[i].offset = static_cast<int32_t>(code.read());
          reg.idx[i].relReg = nullptr;
          break;

        case DxbcOperandIndexRepresentation::Relative:
          reg.idx[i].offset = 0;
          reg.idx[i].relReg = &m_indices.at(m_indexId);

          this->decodeRegister(code,
            m_indices.at(m_indexId++),
            DxbcScalarType::Sint32);
          break;

        case DxbcOperandIndexRepresentation::Imm32Relative:
          reg.idx[i].offset = static_cast<int32_t>(code.read());
          reg.idx[i].relReg = &m_indices.at(m_indexId);

          this->decodeRegister(code,
            m_indices.at(m_indexId++),
            DxbcScalarType::Sint32);
          break;

        default:
          Logger::warn(str::format(
            "DxbcDecodeContext: Unhandled index representation: ",
            repr));
      }
    }
  }
    void decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type) {
    const uint32_t token = code.read();

    reg.type            = static_cast<DxbcOperandType>(bit::extract(token, 12, 19));
    reg.dataType        = type;
    reg.modifiers       = 0;
    reg.idxDim          = 0;

    for (uint32_t i = 0; i < DxbcMaxRegIndexDim; i++) {
      reg.idx[i].relReg = nullptr;
      reg.idx[i].offset = 0;
    }

    this->decodeComponentSelection(reg, token);
    this->decodeOperandExtensions(code, reg, token);
    this->decodeOperandImmediates(code, reg);
    this->decodeOperandIndex(code, reg, token);
  }
    void decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type) {
    imm.u32 = code.read();
  }
    void decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format) {
    switch (format.kind) {
      case DxbcOperandKind::DstReg: {
        const uint32_t operandId = m_instruction.dstCount++;
        this->decodeRegister(code, m_dstOperands.at(operandId), format.type);
      } break;

      case DxbcOperandKind::SrcReg: {
        const uint32_t operandId = m_instruction.srcCount++;
        this->decodeRegister(code, m_srcOperands.at(operandId), format.type);
      } break;

      case DxbcOperandKind::Imm32: {
        const uint32_t operandId = m_instruction.immCount++;
        this->decodeImm32(code, m_immOperands.at(operandId), format.type);
      } break;

      default:
        throw DxvkError("DxbcDecodeContext: Invalid operand format");
    }
  }
};

#pragma once

#if (defined(__x86_64__) && !defined(__arm64ec__)) || (defined(_M_X64) && !defined(_M_ARM64EC)) \
    || defined(__i386__) || defined(_M_IX86) || defined(__e2k__)
  #define DXVK_ARCH_X86
  #if defined(__x86_64__) || defined(_M_X64) || defined(__e2k__)
    #define DXVK_ARCH_X86_64
  #endif
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
  #define DXVK_ARCH_ARM64
#endif

#ifdef DXVK_ARCH_X86
  #ifndef _MSC_VER
    #if defined(_WIN32) && (defined(__AVX__) || defined(__AVX2__))
      #error "AVX-enabled builds not supported due to stack alignment issues."
    #endif
    #if defined(__WINE__) && defined(__clang__)
      #pragma push_macro("_WIN32")
      #undef _WIN32
    #endif
    #include <x86intrin.h>
    #if defined(__WINE__) && defined(__clang__)
      #pragma pop_macro("_WIN32")
    #endif
  #else
    #include <intrin.h>
  #endif
#endif

#include "util_likely.h"
#include "util_math.h"

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <type_traits>
#include <vector>

namespace dxvk::bit {

  template<typename T, typename J>
  T cast(const J& src) {
    static_assert(sizeof(T) == sizeof(J));
    static_assert(std::is_trivially_copyable<J>::value && std::is_trivial<T>::value);

    T dst;
    std::memcpy(&dst, &src, sizeof(T));
    return dst;
  }

  template<typename T>
  T extract(T value, uint32_t fst, uint32_t lst) {
    return (value >> fst) & ~(~T(0) << (lst - fst + 1));
  }

  template<typename T>
  T popcnt(T n) {
    n -= ((n >> 1u) & T(0x5555555555555555ull));
    n = (n & T(0x3333333333333333ull)) + ((n >> 2u) & T(0x3333333333333333ull));
    n = (n + (n >> 4u)) & T(0x0f0f0f0f0f0f0f0full);
    n *= T(0x0101010101010101ull);
    return n >> (8u * (sizeof(T) - 1u));
  }

  inline uint32_t tzcnt(uint32_t n) {
    #if defined(_MSC_VER) && !defined(__clang__)
    if(n == 0)
      return 32;
    return _tzcnt_u32(n);
    #elif defined(__BMI__)
    return __tzcnt_u32(n);
    #elif defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__))
    // tzcnt is encoded as rep bsf, so we can use it on all
    // processors, but the behaviour of zero inputs differs:
    // - bsf:   zf = 1, cf = ?, result = ?
    // - tzcnt: zf = 0, cf = 1, result = 32
    // We'll have to handle this case manually.
    uint32_t res;
    uint32_t tmp;
    asm (
      "tzcnt %2, %0;"
      "mov  $32, %1;"
      "test  %2, %2;"
      "cmovz %1, %0;"
      : "=&r" (res), "=&r" (tmp)
      : "r" (n)
      : "cc");
    return res;
    #elif defined(__GNUC__) || defined(__clang__)
    return n != 0 ? __builtin_ctz(n) : 32;
    #else
    uint32_t r = 31;
    n &= -n;
    r -= (n & 0x0000FFFF) ? 16 : 0;
    r -= (n & 0x00FF00FF) ?  8 : 0;
    r -= (n & 0x0F0F0F0F) ?  4 : 0;
    r -= (n & 0x33333333) ?  2 : 0;
    r -= (n & 0x55555555) ?  1 : 0;
    return n != 0 ? r : 32;
    #endif
  }

  inline uint32_t tzcnt(uint64_t n) {
    #if defined(DXVK_ARCH_X86_64) && defined(_MSC_VER) && !defined(__clang__)
    if(n == 0)
      return 64;
    return (uint32_t)_tzcnt_u64(n);
    #elif defined(DXVK_ARCH_X86_64) && defined(__BMI__)
    return __tzcnt_u64(n);
    #elif defined(DXVK_ARCH_X86_64) && (defined(__GNUC__) || defined(__clang__))
    uint64_t res;
    uint64_t tmp;
    asm (
      "tzcnt %2, %0;"
      "mov  $64, %1;"
      "test  %2, %2;"
      "cmovz %1, %0;"
      : "=&r" (res), "=&r" (tmp)
      : "r" (n)
      : "cc");
    return res;
    #elif defined(__GNUC__) || defined(__clang__)
    return n != 0 ? __builtin_ctzll(n) : 64;
    #else
    uint32_t lo = uint32_t(n);
    if (lo) {
      return tzcnt(lo);
    } else {
      uint32_t hi = uint32_t(n >> 32);
      return tzcnt(hi) + 32;
    }
    #endif
  }

  inline uint32_t bsf(uint32_t n) {
    #if (defined(__GNUC__) || defined(__clang__)) && !defined(__BMI__) && defined(DXVK_ARCH_X86)
    uint32_t res;
    asm ("tzcnt %1,%0"
    : "=r" (res)
    : "r" (n)
    : "cc");
    return res;
    #else
    return tzcnt(n);
    #endif
  }

  inline uint32_t bsf(uint64_t n) {
    #if (defined(__GNUC__) || defined(__clang__)) && !defined(__BMI__) && defined(DXVK_ARCH_X86_64)
    uint64_t res;
    asm ("tzcnt %1,%0"
    : "=r" (res)
    : "r" (n)
    : "cc");
    return res;
    #else
    return tzcnt(n);
    #endif
  }

  inline uint32_t lzcnt(uint32_t n) {
    #if defined(_MSC_VER) && !defined(__clang__) && !defined(__LZCNT__)
    unsigned long bsr;
    if(n == 0)
      return 32;
    _BitScanReverse(&bsr, n);
    return 31-bsr;
    #elif (defined(_MSC_VER) && !defined(__clang__)) || defined(__LZCNT__)
    return _lzcnt_u32(n);
    #elif defined(__GNUC__) || defined(__clang__)
    return n != 0 ? __builtin_clz(n) : 32;
    #else
    uint32_t r = 0;

    if (n == 0)	return 32;

    if (n <= 0x0000FFFF) { r += 16; n <<= 16; }
    if (n <= 0x00FFFFFF) { r += 8;  n <<= 8; }
    if (n <= 0x0FFFFFFF) { r += 4;  n <<= 4; }
    if (n <= 0x3FFFFFFF) { r += 2;  n <<= 2; }
    if (n <= 0x7FFFFFFF) { r += 1;  n <<= 1; }

    return r;
    #endif
  }

  inline uint32_t lzcnt(uint64_t n) {
    #if defined(_MSC_VER) && !defined(__clang__) && !defined(__LZCNT__) && defined(DXVK_ARCH_X86_64)
    unsigned long bsr;
    if(n == 0)
      return 64;
    _BitScanReverse64(&bsr, n);
    return 63-bsr;
    #elif defined(DXVK_ARCH_X86_64) && ((defined(_MSC_VER) && !defined(__clang__)) && defined(__LZCNT__))
    return _lzcnt_u64(n);
    #elif defined(DXVK_ARCH_X86_64) && (defined(__GNUC__) || defined(__clang__))
    return n != 0 ? __builtin_clzll(n) : 64;
    #else
    uint32_t lo = uint32_t(n);
    uint32_t hi = uint32_t(n >> 32u);
    return hi ? lzcnt(hi) : lzcnt(lo) + 32u;
    #endif
  }

  template<typename T>
  uint32_t pack(T& dst, uint32_t& shift, T src, uint32_t count) {
    constexpr uint32_t Bits = 8 * sizeof(T);
    if (likely(shift < Bits))
      dst |= src << shift;
    shift += count;
    return shift > Bits ? shift - Bits : 0;
  }

  template<typename T>
  uint32_t unpack(T& dst, T src, uint32_t& shift, uint32_t count) {
    constexpr uint32_t Bits = 8 * sizeof(T);
    if (likely(shift < Bits))
      dst = (src >> shift) & ((T(1) << count) - 1);
    shift += count;
    return shift > Bits ? shift - Bits : 0;
  }

  /**
   * \brief Clears cache lines of memory
   *
   * Uses non-temporal stores. The memory region offset
   * and size are assumed to be aligned to 64 bytes.
   * \param [in] mem Memory region to clear
   * \param [in] size Number of bytes to clear
   */
  inline void bclear(void* mem, size_t size) {
    #if defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER))
    auto zero = _mm_setzero_si128();

    #if defined(__clang__)
    #pragma nounroll
    #elif defined(__GNUC__)
    #pragma GCC unroll 0
    #endif
    for (size_t i = 0; i < size; i += 64u) {
      auto* ptr = reinterpret_cast<__m128i*>(mem) + i / sizeof(zero);
      _mm_stream_si128(ptr + 0u, zero);
      _mm_stream_si128(ptr + 1u, zero);
      _mm_stream_si128(ptr + 2u, zero);
      _mm_stream_si128(ptr + 3u, zero);
    }
    #else
    std::memset(mem, 0, size);
    #endif
  }

  /**
   * \brief Compares two aligned structs bit by bit
   *
   * \param [in] a First struct
   * \param [in] b Second struct
   * \returns \c true if the structs are equal
   */
  template<typename T>
  bool bcmpeq(const T* a, const T* b) {
    static_assert(alignof(T) >= 16);
    #if defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER))
    auto ai = reinterpret_cast<const __m128i*>(a);
    auto bi = reinterpret_cast<const __m128i*>(b);

    size_t i = 0;

    #if defined(__clang__)
    #pragma nounroll
    #elif defined(__GNUC__)
    #pragma GCC unroll 0
    #endif

    for ( ; i < 2 * (sizeof(T) / 32); i += 2) {
      __m128i eq0 = _mm_cmpeq_epi8(
        _mm_load_si128(ai + i),
        _mm_load_si128(bi + i));
      __m128i eq1 = _mm_cmpeq_epi8(
        _mm_load_si128(ai + i + 1),
        _mm_load_si128(bi + i + 1));
      __m128i eq = _mm_and_si128(eq0, eq1);

      int mask = _mm_movemask_epi8(eq);
      if (mask != 0xFFFF)
        return false;
    }

    for ( ; i < sizeof(T) / 16; i++) {
      __m128i eq = _mm_cmpeq_epi8(
        _mm_load_si128(ai + i),
        _mm_load_si128(bi + i));

      int mask = _mm_movemask_epi8(eq);
      if (mask != 0xFFFF)
        return false;
    }

    return true;
    #else
    return !std::memcmp(a, b, sizeof(T));
    #endif
  }

  template <size_t Bits>
  class bitset {
    static constexpr size_t Dwords = align(Bits, 32) / 32;
  public:

    constexpr bitset()
      : m_dwords() {

    }

    constexpr bool get(uint32_t idx) const {
      uint32_t dword = 0;
      uint32_t bit   = idx;

      // Compiler doesn't remove this otherwise.
      if constexpr (Dwords > 1) {
        dword = idx / 32;
        bit   = idx % 32;
      }

      return m_dwords[dword] & (1u << bit);
    }

    constexpr void set(uint32_t idx, bool value) {
      uint32_t dword = 0;
      uint32_t bit   = idx;

      // Compiler doesn't remove this otherwise.
      if constexpr (Dwords > 1) {
        dword = idx / 32;
        bit   = idx % 32;
      }

      if (value)
        m_dwords[dword] |= 1u << bit;
      else
        m_dwords[dword] &= ~(1u << bit);
    }

    constexpr bool exchange(uint32_t idx, bool value) {
      bool oldValue = get(idx);
      set(idx, value);
      return oldValue;
    }

    constexpr void flip(uint32_t idx) {
      uint32_t dword = 0;
      uint32_t bit   = idx;

      // Compiler doesn't remove this otherwise.
      if constexpr (Dwords > 1) {
        dword = idx / 32;
        bit   = idx % 32;
      }

      m_dwords[dword] ^= 1u << bit;
    }

    constexpr void setAll() {
      if constexpr (Bits % 32 == 0) {
        for (size_t i = 0; i < Dwords; i++)
          m_dwords[i] = std::numeric_limits<uint32_t>::max();
      }
      else {
        for (size_t i = 0; i < Dwords - 1; i++)
          m_dwords[i] = std::numeric_limits<uint32_t>::max();

        m_dwords[Dwords - 1] = (1u << (Bits % 32)) - 1;
      }
    }

    constexpr void clearAll() {
      for (size_t i = 0; i < Dwords; i++)
        m_dwords[i] = 0;
    }

    constexpr bool any() const {
      for (size_t i = 0; i < Dwords; i++) {
        if (m_dwords[i] != 0)
          return true;
      }

      return false;
    }

    constexpr uint32_t& dword(uint32_t idx) {
      return m_dwords[idx];
    }

    constexpr size_t bitCount() {
      return Bits;
    }

    constexpr size_t dwordCount() {
      return Dwords;
    }

    constexpr bool operator [] (uint32_t idx) const {
      return get(idx);
    }

    constexpr void setN(uint32_t bits) {
      uint32_t fullDwords = bits / 32;
      uint32_t offset = bits % 32;

      for (size_t i = 0; i < fullDwords; i++)
        m_dwords[i] = std::numeric_limits<uint32_t>::max();

      if (offset > 0)
        m_dwords[fullDwords] = (1u << offset) - 1;
    }

  private:

    uint32_t m_dwords[Dwords];

  };

  class bitvector {
  public:

    bool get(uint32_t idx) const {
      uint32_t dword = idx / 32;
      uint32_t bit   = idx % 32;

      return m_dwords[dword] & (1u << bit);
    }

    void ensureSize(uint32_t bitCount) {
      uint32_t dword = bitCount / 32;
      if (unlikely(dword >= m_dwords.size())) {
        m_dwords.resize(dword + 1);
      }
      m_bitCount = std::max(m_bitCount, bitCount);
    }

    void set(uint32_t idx, bool value) {
      ensureSize(idx + 1);

      uint32_t dword = 0;
      uint32_t bit   = idx;

      if (value)
        m_dwords[dword] |= 1u << bit;
      else
        m_dwords[dword] &= ~(1u << bit);
    }

    bool exchange(uint32_t idx, bool value) {
      ensureSize(idx + 1);

      bool oldValue = get(idx);
      set(idx, value);
      return oldValue;
    }

    void flip(uint32_t idx) {
      ensureSize(idx + 1);

      uint32_t dword = idx / 32;
      uint32_t bit   = idx % 32;

      m_dwords[dword] ^= 1u << bit;
    }

    void setAll() {
      if (m_bitCount % 32 == 0) {
        for (size_t i = 0; i < m_dwords.size(); i++)
          m_dwords[i] = std::numeric_limits<uint32_t>::max();
      }
      else {
        for (size_t i = 0; i < m_dwords.size() - 1; i++)
          m_dwords[i] = std::numeric_limits<uint32_t>::max();

        m_dwords[m_dwords.size() - 1] = (1u << (m_bitCount % 32)) - 1;
      }
    }

    void clearAll() {
      for (size_t i = 0; i < m_dwords.size(); i++)
        m_dwords[i] = 0;
    }

    bool any() const {
      for (size_t i = 0; i < m_dwords.size(); i++) {
        if (m_dwords[i] != 0)
          return true;
      }

      return false;
    }

    uint32_t& dword(uint32_t idx) {
      return m_dwords[idx];
    }

    size_t bitCount() const {
      return m_bitCount;
    }

    size_t dwordCount() const {
      return m_dwords.size();
    }

    bool operator [] (uint32_t idx) const {
      return get(idx);
    }

    void setN(uint32_t bits) {
      ensureSize(bits);

      uint32_t fullDwords = bits / 32;
      uint32_t offset = bits % 32;

      for (size_t i = 0; i < fullDwords; i++)
        m_dwords[i] = std::numeric_limits<uint32_t>::max();

      if (offset > 0)
        m_dwords[fullDwords] = (1u << offset) - 1;
    }

  private:

    std::vector<uint32_t> m_dwords;
    uint32_t              m_bitCount = 0;

  };

  template<typename T>
  class BitMask {

  public:

    class iterator {
    public:
      using iterator_category = std::input_iterator_tag;
      using value_type = T;
      using difference_type = T;
      using pointer = const T*;
      using reference = T;

      explicit iterator(T flags)
        : m_mask(flags) { }

      iterator& operator ++ () {
        m_mask &= m_mask - 1;
        return *this;
      }

      iterator operator ++ (int) {
        iterator retval = *this;
        m_mask &= m_mask - 1;
        return retval;
      }

      T operator * () const {
        return bsf(m_mask);
      }

      bool operator == (iterator other) const { return m_mask == other.m_mask; }
      bool operator != (iterator other) const { return m_mask != other.m_mask; }

    private:

      T m_mask;

    };

    BitMask()
      : m_mask(0) { }

    explicit BitMask(T n)
      : m_mask(n) { }

    iterator begin() {
      return iterator(m_mask);
    }

    iterator end() {
      return iterator(0);
    }

  private:

    T m_mask;

  };

  /**
   * \brief Encodes float as fixed point
   *
   * Rounds away from zero. If this is not suitable for
   * certain use cases, implement round to nearest even.
   * \tparam T Integer type, may be signed
   * \tparam I Integer bits
   * \tparam F Fractional bits
   * \param n Float to encode
   * \returns Encoded fixed-point value
   */
  template<typename T, int32_t I, int32_t F>
  T encodeFixed(float n) {
    if (n != n)
      return 0u;

    n *= float(1u << F);

    if constexpr (std::is_signed_v<T>) {
      n = std::max(n, -float(1u << (I + F - 1u)));
      n = std::min(n,  float(1u << (I + F - 1u)) - 1.0f);
      n += n < 0.0f ? -0.5f : 0.5f;
    } else {
      n = std::max(n, 0.0f);
      n = std::min(n, float(1u << (I + F)) - 1.0f);
      n += 0.5f;
    }

    T result = T(n);

    if constexpr (std::is_signed_v<T>)
      result &= ((T(1u) << (I + F)) - 1u);

    return result;
  }

  /**
   * \brief Decodes fixed-point integer to float
   *
   * \tparam T Integer type, may be signed
   * \tparam I Integer bits
   * \tparam F Fractional bits
   * \param n Number to decode
   * \returns Decoded  number
   */
  template<typename T, int32_t I, int32_t F>
  float decodeFixed(T n) {
    // Sign-extend as necessary
    if constexpr (std::is_signed_v<T>)
      n -= (n & (T(1u) << (I + F - 1u))) << 1u;

    return float(n) / float(1u << F);
  }

  /**
   * \brief Inserts one null bit after each bit
   */
  inline uint32_t split2(uint32_t c) {
    c = (c ^ (c << 8u)) & 0x00ff00ffu;
    c = (c ^ (c << 4u)) & 0x0f0f0f0fu;
    c = (c ^ (c << 2u)) & 0x33333333u;
    c = (c ^ (c << 1u)) & 0x55555555u;
    return c;
  }

  /**
   * \brief Inserts two null bits after each bit
   */
  inline uint64_t split3(uint64_t c) {
    c = (c | c << 32u) & 0x001f00000000ffffull;
    c = (c | c << 16u) & 0x001f0000ff0000ffull;
    c = (c | c <<  8u) & 0x100f00f00f00f00full;
    c = (c | c <<  4u) & 0x10c30c30c30c30c3ull;
    c = (c | c <<  2u) & 0x1249249249249249ull;
    return c;
  }

  /**
   * \brief Interleaves bits from two integers
   *
   * Both numbers must fit into 16 bits.
   * \param [in] x X coordinate
   * \param [in] y Y coordinate
   * \returns Morton code of x and y
   */
  inline uint32_t interleave(uint16_t x, uint16_t y) {
    return split2(x) | (split2(y) << 1u);
  }

  /**
   * \brief Interleaves bits from three integers
   *
   * All three numbers must fit into 16 bits.
   */
  inline uint64_t interleave(uint16_t x, uint16_t y, uint16_t z) {
    return split3(x) | (split3(y) << 1u) | (split3(z) << 2u);
  }

  /**
   * \brief 48-bit integer storage type
   */
  struct uint48_t {
    explicit uint48_t(uint64_t n)
    : a(uint16_t(n)), b(uint16_t(n >> 16)), c(uint16_t(n >> 32)) { }

    uint16_t a;
    uint16_t b;
    uint16_t c;

    explicit operator uint64_t () const {
      // GCC generates worse code if we promote to uint64 directly
      uint32_t lo = uint32_t(a) | (uint32_t(b) << 16);
      return uint64_t(lo) | (uint64_t(c) << 32);
    }
  };

}

class generates {
  public:
    explicit SpirvModule(uint32_t version) {
    this->instImportGlsl450();
  }
    ~SpirvModule() {

  }
    SpirvCodeBuffer compile() {
    SpirvCodeBuffer result;
    result.putHeader(m_version, m_id);
    result.append(m_capabilities);
    result.append(m_extensions);
    result.append(m_instExt);
    result.append(m_memoryModel);
    result.append(m_entryPoints);
    result.append(m_execModeInfo);
    result.append(m_debugNames);
    result.append(m_annotations);
    result.append(m_typeConstDefs);
    result.append(m_variables);

    // Perform some crude dead code elimination. In some cases, our compilers
    // may emit invalid code, such as an unreachable block branching to a loop's
    // continue block, but those cases cannot be reasonably detected up-front.
    std::unordered_set<uint32_t> reachableBlocks;
    std::unordered_set<uint32_t> mergeBlocks;

    classifyBlocks(reachableBlocks, mergeBlocks);

    bool reachable = true;

    for (auto ins : m_code) {
      if (ins.opCode() == spv::OpFunctionEnd) {
        reachable = true;
        result.append(ins);
      } else if (ins.opCode() == spv::OpLabel) {
        uint32_t labelId = ins.arg(1);

        if ((reachable = reachableBlocks.find(labelId) != reachableBlocks.end())) {
          result.append(ins);
        } else if (mergeBlocks.find(labelId) != mergeBlocks.end()) {
          result.append(ins);
          result.putIns(spv::OpUnreachable, 1);
        }
      } else if (reachable) {
        result.append(ins);
      }
    }

    return result;
  }
    uint32_t allocateId() {
    return m_id++;
  }
    bool hasCapability(
            spv::Capability         capability) {
    for (auto ins : m_capabilities) {
      if (ins.opCode() == spv::OpCapability && ins.arg(1) == capability)
        return true;
    }

    return false;
  }
    void enableCapability(
            spv::Capability         capability) {
    // Scan the generated instructions to check
    // whether we already enabled the capability.
    if (!hasCapability(capability)) {
      m_capabilities.putIns (spv::OpCapability, 2);
      m_capabilities.putWord(capability);
    }
  }
    void enableExtension(
      const char*                   extensionName) {
    m_extensions.putIns (spv::OpExtension, 1 + m_extensions.strLen(extensionName));
    m_extensions.putStr (extensionName);
  }
    void addEntryPoint(
            uint32_t                entryPointId,
            spv::ExecutionModel     executionModel,
      const char*                   name) {
    m_entryPoints.putIns  (spv::OpEntryPoint, 3 + m_entryPoints.strLen(name) + m_interfaceVars.size());
    m_entryPoints.putWord (executionModel);
    m_entryPoints.putWord (entryPointId);
    m_entryPoints.putStr  (name);

    for (uint32_t varId : m_interfaceVars)
      m_entryPoints.putWord(varId);
  }
    void setMemoryModel(
            spv::AddressingModel    addressModel,
            spv::MemoryModel        memoryModel) {
    m_memoryModel.putIns  (spv::OpMemoryModel, 3);
    m_memoryModel.putWord (addressModel);
    m_memoryModel.putWord (memoryModel);
  }
    void setExecutionMode(
            uint32_t                entryPointId,
            spv::ExecutionMode      executionMode) {
    m_execModeInfo.putIns (spv::OpExecutionMode, 3 + argCount);
    m_execModeInfo.putWord(entryPointId);
    m_execModeInfo.putWord(executionMode);

    for (uint32_t i = 0; i < argCount; i++)
      m_execModeInfo.putWord(args[i]);
  }
    void setExecutionMode(
            uint32_t                entryPointId,
            spv::ExecutionMode      executionMode,
            uint32_t                argCount,
      const uint32_t*               args) {
    m_execModeInfo.putIns (spv::OpExecutionMode, 3 + argCount);
    m_execModeInfo.putWord(entryPointId);
    m_execModeInfo.putWord(executionMode);

    for (uint32_t i = 0; i < argCount; i++)
      m_execModeInfo.putWord(args[i]);
  }
    void setInvocations(
            uint32_t                entryPointId,
            uint32_t                invocations) {
    m_execModeInfo.putIns  (spv::OpExecutionMode, 4);
    m_execModeInfo.putWord (entryPointId);
    m_execModeInfo.putWord (spv::ExecutionModeInvocations);
    m_execModeInfo.putInt32(invocations);
  }
    void setLocalSize(
            uint32_t                entryPointId,
            uint32_t                x,
            uint32_t                y,
            uint32_t                z) {
    m_execModeInfo.putIns  (spv::OpExecutionMode, 6);
    m_execModeInfo.putWord (entryPointId);
    m_execModeInfo.putWord (spv::ExecutionModeLocalSize);
    m_execModeInfo.putInt32(x);
    m_execModeInfo.putInt32(y);
    m_execModeInfo.putInt32(z);
  }
    void setOutputVertices(
            uint32_t                entryPointId,
            uint32_t                vertexCount) {
    m_execModeInfo.putIns (spv::OpExecutionMode, 4);
    m_execModeInfo.putWord(entryPointId);
    m_execModeInfo.putWord(spv::ExecutionModeOutputVertices);
    m_execModeInfo.putWord(vertexCount);
  }
    uint32_t addDebugString(
      const char*                   string) {
    uint32_t resultId = this->allocateId();

    m_debugNames.putIns (spv::OpString,
      2 + m_debugNames.strLen(string));
    m_debugNames.putWord(resultId);
    m_debugNames.putStr (string);
    return resultId;
  }
    void setDebugSource(
            spv::SourceLanguage     language,
            uint32_t                version,
            uint32_t                file,
      const char*                   source) {
    uint32_t strLen = source != nullptr
      ? m_debugNames.strLen(source) : 0;

    m_debugNames.putIns (spv::OpSource, 4 + strLen);
    m_debugNames.putWord(language);
    m_debugNames.putWord(version);
    m_debugNames.putWord(file);

    if (source != nullptr)
      m_debugNames.putStr(source);
  }
    void setDebugName(
            uint32_t                expressionId,
      const char*                   debugName) {
    m_debugNames.putIns (spv::OpName, 2 + m_debugNames.strLen(debugName));
    m_debugNames.putWord(expressionId);
    m_debugNames.putStr (debugName);
  }
    void setDebugMemberName(
            uint32_t                structId,
            uint32_t                memberId,
      const char*                   debugName) {
    m_debugNames.putIns (spv::OpMemberName, 3 + m_debugNames.strLen(debugName));
    m_debugNames.putWord(structId);
    m_debugNames.putWord(memberId);
    m_debugNames.putStr (debugName);
  }
    uint32_t constBool(
            bool                    v) {
    return this->defConst(v
        ? spv::OpConstantTrue
        : spv::OpConstantFalse,
      this->defBoolType(),
      0, nullptr);
  }
    uint32_t consti32(
            int32_t                 v) {
    std::array<uint32_t, 1> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defIntType(32, 1),
      data.size(),
      data.data());
  }
    uint32_t consti64(
            int64_t                 v) {
    std::array<uint32_t, 2> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defIntType(64, 1),
      data.size(),
      data.data());
  }
    uint32_t constu32(
            uint32_t                v) {
    std::array<uint32_t, 1> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defIntType(32, 0),
      data.size(),
      data.data());
  }
    uint32_t constu64(
            uint64_t                v) {
    std::array<uint32_t, 2> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defIntType(64, 0),
      data.size(),
      data.data());
  }
    uint32_t constf32(
            float                   v) {
    std::array<uint32_t, 1> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defFloatType(32),
      data.size(),
      data.data());
  }
    uint32_t constf64(
            double                  v) {
    std::array<uint32_t, 2> data;
    std::memcpy(data.data(), &v, sizeof(v));

    return this->defConst(
      spv::OpConstant,
      this->defFloatType(64),
      data.size(),
      data.data());
  }
    uint32_t constvec4i32(
            int32_t                 x,
            int32_t                 y,
            int32_t                 z,
            int32_t                 w) {
    std::array<uint32_t, 4> args = {{
      this->consti32(x), this->consti32(y),
      this->consti32(z), this->consti32(w),
    }};

    uint32_t scalarTypeId = this->defIntType(32, 1);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constvec4b32(
            bool                    x,
            bool                    y,
            bool                    z,
            bool                    w) {
    std::array<uint32_t, 4> args = {{
      this->constBool(x), this->constBool(y),
      this->constBool(z), this->constBool(w),
    }};

    uint32_t scalarTypeId = this->defBoolType();
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constvec4u32(
            uint32_t                x,
            uint32_t                y,
            uint32_t                z,
            uint32_t                w) {
    std::array<uint32_t, 4> args = {{
      this->constu32(x), this->constu32(y),
      this->constu32(z), this->constu32(w),
    }};

    uint32_t scalarTypeId = this->defIntType(32, 0);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constvec2f32(
            float                   x,
            float                   y) {
    std::array<uint32_t, 2> args = {{
      this->constf32(x), this->constf32(y),
    }};

    uint32_t scalarTypeId = this->defFloatType(32);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 2);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constvec3f32(
            float                   x,
            float                   y,
            float                   z) {
    std::array<uint32_t, 3> args = {{
      this->constf32(x), this->constf32(y),
      this->constf32(z),
    }};

    uint32_t scalarTypeId = this->defFloatType(32);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 3);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constvec4f32(
            float                   x,
            float                   y,
            float                   z,
            float                   w) {
    std::array<uint32_t, 4> args = {{
      this->constf32(x), this->constf32(y),
      this->constf32(z), this->constf32(w),
    }};

    uint32_t scalarTypeId = this->defFloatType(32);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4);

    return this->constComposite(vectorTypeId, args.size(), args.data());
  }
    uint32_t constfReplicant(
            float                   replicant,
            uint32_t                count) {
    uint32_t value = this->constf32(replicant);

    std::array<uint32_t, 4> args = { value, value, value, value };

    // Can't make a scalar composite.
    if (count == 1)
      return args[0];

    uint32_t scalarTypeId = this->defFloatType(32);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count);

    return this->constComposite(vectorTypeId, count, args.data());
  }
    uint32_t constbReplicant(
            bool                    replicant,
            uint32_t                count) {
    uint32_t value = this->constBool(replicant);

    std::array<uint32_t, 4> args = { value, value, value, value };

    // Can't make a scalar composite.
    if (count == 1)
      return args[0];

    uint32_t scalarTypeId = this->defBoolType();
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count);

    return this->constComposite(vectorTypeId, count, args.data());
  }
    uint32_t constiReplicant(
            int32_t                 replicant,
            uint32_t                count) {
    uint32_t value = this->consti32(replicant);

    std::array<uint32_t, 4> args = { value, value, value, value };

    // Can't make a scalar composite.
    if (count == 1)
      return args[0];

    uint32_t scalarTypeId = this->defIntType(32, 1);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count);

    return this->constComposite(vectorTypeId, count, args.data());
  }
    uint32_t constuReplicant(
            int32_t                 replicant,
            uint32_t                count) {
    uint32_t value = this->constu32(replicant);

    std::array<uint32_t, 4> args = { value, value, value, value };

    // Can't make a scalar composite.
    if (count == 1)
      return args[0];

    uint32_t scalarTypeId = this->defIntType(32, 0);
    uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count);

    return this->constComposite(vectorTypeId, count, args.data());
  }
    uint32_t constComposite(
            uint32_t                typeId,
            uint32_t                constCount,
      const uint32_t*               constIds) {
    return this->defConst(
      spv::OpConstantComposite,
      typeId, constCount, constIds);
  }
    uint32_t constUndef(
            uint32_t                typeId) {
    return this->defConst(spv::OpUndef,
      typeId, 0, nullptr);
  }
    uint32_t constNull(
            uint32_t                typeId) {
    return this->defConst(spv::OpConstantNull,
      typeId, 0, nullptr);
  }
    uint32_t lateConst32(
            uint32_t                typeId) {
    uint32_t resultId = this->allocateId();
    m_lateConsts.insert(resultId);

    m_typeConstDefs.putIns (spv::OpConstant, 4);
    m_typeConstDefs.putWord(typeId);
    m_typeConstDefs.putWord(resultId);
    m_typeConstDefs.putWord(0);
    return resultId;
  }
    void setLateConst(
            uint32_t                constId,
      const uint32_t*               argIds) {
    for (auto ins : m_typeConstDefs) {
      if (ins.opCode() != spv::OpConstant
       && ins.opCode() != spv::OpConstantComposite)
        continue;

      if (ins.arg(2) != constId)
        continue;

      for (uint32_t i = 3; i < ins.length(); i++)
        ins.setArg(i, argIds[i - 3]);

      return;
    }
  }
    uint32_t specConstBool(
            bool                    v) {
    uint32_t typeId   = this->defBoolType();
    uint32_t resultId = this->allocateId();

    const spv::Op op = v
      ? spv::OpSpecConstantTrue
      : spv::OpSpecConstantFalse;

    m_typeConstDefs.putIns  (op, 3);
    m_typeConstDefs.putWord (typeId);
    m_typeConstDefs.putWord (resultId);
    return resultId;
  }
    uint32_t specConst32(
            uint32_t                typeId,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_typeConstDefs.putIns  (spv::OpSpecConstant, 4);
    m_typeConstDefs.putWord (typeId);
    m_typeConstDefs.putWord (resultId);
    m_typeConstDefs.putWord (value);
    return resultId;
  }
    void decorate(
            uint32_t                object,
            spv::Decoration         decoration) {
    m_annotations.putIns  (spv::OpDecorate, 3);
    m_annotations.putWord (object);
    m_annotations.putWord (decoration);
  }
    void decorateArrayStride(
            uint32_t                object,
            uint32_t                stride) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationArrayStride);
    m_annotations.putInt32(stride);
  }
    void decorateBinding(
            uint32_t                object,
            uint32_t                binding) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationBinding);
    m_annotations.putInt32(binding);
  }
    void decorateBlock(
            uint32_t                object) {
    m_annotations.putIns  (spv::OpDecorate, 3);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationBlock);
  }
    void decorateBuiltIn(
            uint32_t                object,
            spv::BuiltIn            builtIn) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationBuiltIn);
    m_annotations.putWord (builtIn);
  }
    void decorateComponent(
            uint32_t                object,
            uint32_t                location) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationComponent);
    m_annotations.putInt32(location);
  }
    void decorateDescriptorSet(
            uint32_t                object,
            uint32_t                set) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationDescriptorSet);
    m_annotations.putInt32(set);
  }
    void decorateIndex(
            uint32_t                object,
            uint32_t                index) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationIndex);
    m_annotations.putInt32(index);
  }
    void decorateLocation(
            uint32_t                object,
            uint32_t                location) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationLocation);
    m_annotations.putInt32(location);
  }
    void decorateSpecId(
            uint32_t                object,
            uint32_t                specId) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationSpecId);
    m_annotations.putInt32(specId);
  }
    void decorateXfb(
            uint32_t                object,
            uint32_t                streamId,
            uint32_t                bufferId,
            uint32_t                offset,
            uint32_t                stride) {
    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationStream);
    m_annotations.putInt32(streamId);

    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationXfbBuffer);
    m_annotations.putInt32(bufferId);

    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationXfbStride);
    m_annotations.putInt32(stride);

    m_annotations.putIns  (spv::OpDecorate, 4);
    m_annotations.putWord (object);
    m_annotations.putWord (spv::DecorationOffset);
    m_annotations.putInt32(offset);
  }
    void memberDecorateBuiltIn(
            uint32_t                structId,
            uint32_t                memberId,
            spv::BuiltIn            builtIn) {
    m_annotations.putIns  (spv::OpMemberDecorate, 5);
    m_annotations.putWord (structId);
    m_annotations.putWord (memberId);
    m_annotations.putWord (spv::DecorationBuiltIn);
    m_annotations.putWord (builtIn);
  }
    void memberDecorate(
            uint32_t                structId,
            uint32_t                memberId,
            spv::Decoration         decoration) {
    m_annotations.putIns  (spv::OpMemberDecorate, 4);
    m_annotations.putWord (structId);
    m_annotations.putWord (memberId);
    m_annotations.putWord (decoration);
  }
    void memberDecorateMatrixStride(
            uint32_t                structId,
            uint32_t                memberId,
            uint32_t                stride) {
    m_annotations.putIns  (spv::OpMemberDecorate, 5);
    m_annotations.putWord (structId);
    m_annotations.putWord (memberId);
    m_annotations.putWord (spv::DecorationMatrixStride);
    m_annotations.putWord (stride);
  }
    void memberDecorateOffset(
            uint32_t                structId,
            uint32_t                memberId,
            uint32_t                offset) {
    m_annotations.putIns  (spv::OpMemberDecorate, 5);
    m_annotations.putWord (structId);
    m_annotations.putWord (memberId);
    m_annotations.putWord (spv::DecorationOffset);
    m_annotations.putWord (offset);
  }
    uint32_t defVoidType() {
    return this->defType(spv::OpTypeVoid, 0, nullptr);
  }
    uint32_t defBoolType() {
    return this->defType(spv::OpTypeBool, 0, nullptr);
  }
    uint32_t defIntType(
            uint32_t                width,
            uint32_t                isSigned) {
    std::array<uint32_t, 2> args = {{ width, isSigned }};
    return this->defType(spv::OpTypeInt,
      args.size(), args.data());
  }
    uint32_t defFloatType(
            uint32_t                width) {
    std::array<uint32_t, 1> args = {{ width }};
    return this->defType(spv::OpTypeFloat,
      args.size(), args.data());
  }
    uint32_t defVectorType(
            uint32_t                elementType,
            uint32_t                elementCount) {
    std::array<uint32_t, 2> args =
      {{ elementType, elementCount }};

    return this->defType(spv::OpTypeVector,
      args.size(), args.data());
  }
    uint32_t defMatrixType(
            uint32_t                columnType,
            uint32_t                columnCount) {
    std::array<uint32_t, 2> args =
      {{ columnType, columnCount }};

    return this->defType(spv::OpTypeMatrix,
      args.size(), args.data());
  }
    uint32_t defArrayType(
            uint32_t                typeId,
            uint32_t                length) {
    std::array<uint32_t, 2> args = {{ typeId, length }};

    return this->defType(spv::OpTypeArray,
      args.size(), args.data());
  }
    uint32_t defArrayTypeUnique(
            uint32_t                typeId,
            uint32_t                length) {
    uint32_t resultId = this->allocateId();

    m_typeConstDefs.putIns (spv::OpTypeArray, 4);
    m_typeConstDefs.putWord(resultId);
    m_typeConstDefs.putWord(typeId);
    m_typeConstDefs.putWord(length);
    return resultId;
  }
    uint32_t defRuntimeArrayType(
            uint32_t                typeId) {
    std::array<uint32_t, 1> args = { typeId };

    return this->defType(spv::OpTypeRuntimeArray,
      args.size(), args.data());
  }
    uint32_t defRuntimeArrayTypeUnique(
            uint32_t                typeId) {
    uint32_t resultId = this->allocateId();

    m_typeConstDefs.putIns (spv::OpTypeRuntimeArray, 3);
    m_typeConstDefs.putWord(resultId);
    m_typeConstDefs.putWord(typeId);
    return resultId;
  }
    uint32_t defFunctionType(
            uint32_t                returnType,
            uint32_t                argCount,
      const uint32_t*               argTypes) {
    std::vector<uint32_t> args;
    args.push_back(returnType);

    for (uint32_t i = 0; i < argCount; i++)
      args.push_back(argTypes[i]);

    return this->defType(spv::OpTypeFunction,
      args.size(), args.data());
  }
    uint32_t defStructType(
            uint32_t                memberCount,
      const uint32_t*               memberTypes) {
    return this->defType(spv::OpTypeStruct,
      memberCount, memberTypes);
  }
    uint32_t defStructTypeUnique(
            uint32_t                memberCount,
      const uint32_t*               memberTypes) {
    uint32_t resultId = this->allocateId();

    m_typeConstDefs.putIns (spv::OpTypeStruct, 2 + memberCount);
    m_typeConstDefs.putWord(resultId);

    for (uint32_t i = 0; i < memberCount; i++)
      m_typeConstDefs.putWord(memberTypes[i]);
    return resultId;
  }
    uint32_t defPointerType(
            uint32_t                variableType,
            spv::StorageClass       storageClass) {
    std::array<uint32_t, 2> args = {{
      static_cast<uint32_t>(storageClass),
      variableType,
    }};

    return this->defType(spv::OpTypePointer,
      args.size(), args.data());
  }
    uint32_t defSamplerType() {
    return this->defType(spv::OpTypeSampler, 0, nullptr);
  }
    uint32_t defImageType(
            uint32_t                sampledType,
            spv::Dim                dimensionality,
            uint32_t                depth,
            uint32_t                arrayed,
            uint32_t                multisample,
            uint32_t                sampled,
            spv::ImageFormat        format) {
    std::array<uint32_t, 7> args = {{
      sampledType,
    static_cast<uint32_t>(dimensionality),
      depth, arrayed,
      multisample,
      sampled,
    static_cast<uint32_t>(format)
    }};

    return this->defType(spv::OpTypeImage,
      args.size(), args.data());
  }
    uint32_t defSampledImageType(
            uint32_t                imageType) {
    return this->defType(spv::OpTypeSampledImage, 1, &imageType);
  }
    uint32_t newVar(
            uint32_t                pointerType,
            spv::StorageClass       storageClass) {
    uint32_t resultId = this->allocateId();

    if (isInterfaceVar(storageClass))
      m_interfaceVars.push_back(resultId);

    auto& code = storageClass != spv::StorageClassFunction
      ? m_variables : m_code;

    code.putIns  (spv::OpVariable, 4);
    code.putWord (pointerType);
    code.putWord (resultId);
    code.putWord (storageClass);
    return resultId;
  }
    uint32_t newVarInit(
            uint32_t                pointerType,
            spv::StorageClass       storageClass,
            uint32_t                initialValue) {
    uint32_t resultId = this->allocateId();

    if (isInterfaceVar(storageClass))
      m_interfaceVars.push_back(resultId);

    auto& code = storageClass != spv::StorageClassFunction
      ? m_variables : m_code;

    code.putIns  (spv::OpVariable, 5);
    code.putWord (pointerType);
    code.putWord (resultId);
    code.putWord (storageClass);
    code.putWord (initialValue);
    return resultId;
  }
    void functionBegin(
            uint32_t                returnType,
            uint32_t                functionId,
            uint32_t                functionType,
      spv::FunctionControlMask      functionControl) {
    m_code.putIns (spv::OpFunction, 5);
    m_code.putWord(returnType);
    m_code.putWord(functionId);
    m_code.putWord(functionControl);
    m_code.putWord(functionType);
  }
    uint32_t functionParameter(
            uint32_t                parameterType) {
    uint32_t parameterId = this->allocateId();

    m_code.putIns (spv::OpFunctionParameter, 3);
    m_code.putWord(parameterType);
    m_code.putWord(parameterId);
    return parameterId;
  }
    void functionEnd() {
    m_code.putIns (spv::OpFunctionEnd, 1);
  }
    uint32_t opAccessChain(
            uint32_t                resultType,
            uint32_t                composite,
            uint32_t                indexCount,
      const uint32_t*               indexArray) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAccessChain, 4 + indexCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(composite);

    for (uint32_t i = 0; i < indexCount; i++)
      m_code.putInt32(indexArray[i]);
    return resultId;
  }
    uint32_t opArrayLength(
            uint32_t                resultType,
            uint32_t                structure,
            uint32_t                memberId) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpArrayLength, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(structure);
    m_code.putWord(memberId);
    return resultId;
  }
    uint32_t opAny(
            uint32_t                resultType,
            uint32_t                vector) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAny, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector);
    return resultId;
  }
    uint32_t opAll(
            uint32_t                resultType,
            uint32_t                vector) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAll, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector);
    return resultId;
  }
    uint32_t opAtomicLoad(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicLoad, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    return resultId;
  }
    void opAtomicStore(
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    m_code.putIns (spv::OpAtomicStore, 5);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
  }
    uint32_t opAtomicExchange(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicExchange, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicCompareExchange(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                equal,
            uint32_t                unequal,
            uint32_t                value,
            uint32_t                comparator) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicCompareExchange, 9);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(equal);
    m_code.putWord(unequal);
    m_code.putWord(value);
    m_code.putWord(comparator);
    return resultId;
  }
    uint32_t opAtomicIIncrement(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicIIncrement, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    return resultId;
  }
    uint32_t opAtomicIDecrement(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicIDecrement, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    return resultId;
  }
    uint32_t opAtomicIAdd(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicIAdd, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicISub(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicISub, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicSMin(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicSMin, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicSMax(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicSMax, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicUMin(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicUMin, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicUMax(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicUMax, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicAnd(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicAnd, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicOr(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicOr, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opAtomicXor(
            uint32_t                resultType,
            uint32_t                pointer,
            uint32_t                scope,
            uint32_t                semantics,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpAtomicXor, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(pointer);
    m_code.putWord(scope);
    m_code.putWord(semantics);
    m_code.putWord(value);
    return resultId;
  }
    uint32_t opBitcast(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitcast, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opBitCount(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitCount, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opBitReverse(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitReverse, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFindILsb(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FindILsb);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFindUMsb(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FindUMsb);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFindSMsb(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FindSMsb);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opBitFieldInsert(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                insert,
            uint32_t                offset,
            uint32_t                count) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitFieldInsert, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(insert);
    m_code.putWord(offset);
    m_code.putWord(count);
    return resultId;
  }
    uint32_t opBitFieldSExtract(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                offset,
            uint32_t                count) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitFieldSExtract, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(offset);
    m_code.putWord(count);
    return resultId;
  }
    uint32_t opBitFieldUExtract(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                offset,
            uint32_t                count) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitFieldUExtract, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(offset);
    m_code.putWord(count);
    return resultId;
  }
    uint32_t opBitwiseAnd(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitwiseAnd, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opBitwiseOr(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitwiseOr, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opBitwiseXor(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpBitwiseXor, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opNot(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpNot, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opShiftLeftLogical(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                shift) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpShiftLeftLogical, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(shift);
    return resultId;
  }
    uint32_t opShiftRightArithmetic(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                shift) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpShiftRightArithmetic, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(shift);
    return resultId;
  }
    uint32_t opShiftRightLogical(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                shift) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpShiftRightLogical, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(shift);
    return resultId;
  }
    uint32_t opConvertFtoS(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpConvertFToS, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opConvertFtoU(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpConvertFToU, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opConvertStoF(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpConvertSToF, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opConvertUtoF(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpConvertUToF, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opCompositeConstruct(
            uint32_t                resultType,
            uint32_t                valueCount,
      const uint32_t*               valueArray) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpCompositeConstruct, 3 + valueCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);

    for (uint32_t i = 0; i < valueCount; i++)
      m_code.putWord(valueArray[i]);
    return resultId;
  }
    uint32_t opCompositeExtract(
            uint32_t                resultType,
            uint32_t                composite,
            uint32_t                indexCount,
      const uint32_t*               indexArray) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpCompositeExtract, 4 + indexCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(composite);

    for (uint32_t i = 0; i < indexCount; i++)
      m_code.putInt32(indexArray[i]);
    return resultId;
  }
    uint32_t opCompositeInsert(
            uint32_t                resultType,
            uint32_t                object,
            uint32_t                composite,
            uint32_t                indexCount,
      const uint32_t*               indexArray) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpCompositeInsert, 5 + indexCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(object);
    m_code.putWord(composite);

    for (uint32_t i = 0; i < indexCount; i++)
      m_code.putInt32(indexArray[i]);
    return resultId;
  }
    uint32_t opDpdx(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdx, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDpdy(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdy, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDpdxCoarse(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdxCoarse, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDpdyCoarse(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdyCoarse, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDpdxFine(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdxFine, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDpdyFine(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDPdyFine, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opVectorExtractDynamic(
            uint32_t                resultType,
            uint32_t                vector,
            uint32_t                index) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpVectorExtractDynamic, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector);
    m_code.putWord(index);
    return resultId;
  }
    uint32_t opVectorShuffle(
            uint32_t                resultType,
            uint32_t                vectorLeft,
            uint32_t                vectorRight,
            uint32_t                indexCount,
      const uint32_t*               indexArray) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpVectorShuffle, 5 + indexCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vectorLeft);
    m_code.putWord(vectorRight);

    for (uint32_t i = 0; i < indexCount; i++)
      m_code.putInt32(indexArray[i]);
    return resultId;
  }
    uint32_t opSNegate(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSNegate, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFNegate(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFNegate, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opSAbs(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450SAbs);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFAbs(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FAbs);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFSign(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FSign);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFMix(
            uint32_t                resultType,
            uint32_t                x,
            uint32_t                y,
            uint32_t                a) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 8);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FMix);
    m_code.putWord(x);
    m_code.putWord(y);
    m_code.putWord(a);
    return resultId;
  }
    uint32_t opCross(
            uint32_t                resultType,
            uint32_t                x,
            uint32_t                y) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Cross);
    m_code.putWord(x);
    m_code.putWord(y);
    return resultId;
  }
    uint32_t opIAdd(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpIAdd, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opISub(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpISub, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFAdd(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFAdd, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFSub(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFSub, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opSDiv(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSDiv, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opUDiv(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpUDiv, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opSRem(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSRem, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opUMod(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpUMod, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFDiv(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFDiv, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opIMul(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpIMul, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFMul(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFMul, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opVectorTimesScalar(
            uint32_t                resultType,
            uint32_t                vector,
            uint32_t                scalar) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpVectorTimesScalar, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector);
    m_code.putWord(scalar);
    return resultId;
  }
    uint32_t opMatrixTimesMatrix(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpMatrixTimesMatrix, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opMatrixTimesVector(
            uint32_t                resultType,
            uint32_t                matrix,
            uint32_t                vector) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpMatrixTimesVector, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(matrix);
    m_code.putWord(vector);
    return resultId;
  }
    uint32_t opVectorTimesMatrix(
            uint32_t                resultType,
            uint32_t                vector,
            uint32_t                matrix) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpVectorTimesMatrix, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector);
    m_code.putWord(matrix);
    return resultId;
  }
    uint32_t opTranspose(
            uint32_t                resultType,
            uint32_t                matrix) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpTranspose, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(matrix);
    return resultId;
  }
    uint32_t opInverse(
            uint32_t                resultType,
            uint32_t                matrix) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450MatrixInverse);
    m_code.putWord(matrix);
    return resultId;
  }
    uint32_t opFFma(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b,
            uint32_t                c) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 8);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Fma);
    m_code.putWord(a);
    m_code.putWord(b);
    m_code.putWord(c);
    return resultId;
  }
    uint32_t opFMax(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FMax);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFMin(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FMin);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opNMax(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450NMax);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opNMin(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450NMin);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opSMax(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450SMax);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opSMin(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450SMin);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opUMax(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450UMax);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opUMin(
            uint32_t                resultType,
            uint32_t                a,
            uint32_t                b) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450UMin);
    m_code.putWord(a);
    m_code.putWord(b);
    return resultId;
  }
    uint32_t opFClamp(
            uint32_t                resultType,
            uint32_t                x,
            uint32_t                minVal,
            uint32_t                maxVal) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 8);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450FClamp);
    m_code.putWord(x);
    m_code.putWord(minVal);
    m_code.putWord(maxVal);
    return resultId;
  }
    uint32_t opNClamp(
            uint32_t                resultType,
            uint32_t                x,
            uint32_t                minVal,
            uint32_t                maxVal) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 8);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450NClamp);
    m_code.putWord(x);
    m_code.putWord(minVal);
    m_code.putWord(maxVal);
    return resultId;
  }
    uint32_t opIEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpIEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opINotEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpINotEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opSLessThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSLessThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opSLessThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSLessThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opSGreaterThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSGreaterThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opSGreaterThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSGreaterThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opULessThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpULessThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opULessThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpULessThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opUGreaterThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpUGreaterThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opUGreaterThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpUGreaterThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFOrdEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFOrdEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFUnordNotEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFUnordNotEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFOrdLessThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFOrdLessThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFOrdLessThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFOrdLessThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFOrdGreaterThan(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFOrdGreaterThan, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opFOrdGreaterThanEqual(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFOrdGreaterThanEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opLogicalEqual(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLogicalEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opLogicalNotEqual(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLogicalNotEqual, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opLogicalAnd(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLogicalAnd, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opLogicalOr(
            uint32_t                resultType,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLogicalOr, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opLogicalNot(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLogicalNot, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opDot(
            uint32_t                resultType,
            uint32_t                vector1,
            uint32_t                vector2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpDot, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(vector1);
    m_code.putWord(vector2);
    return resultId;
  }
    uint32_t opSin(
            uint32_t                resultType,
            uint32_t                vector) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Sin);
    m_code.putWord(vector);
    return resultId;
  }
    uint32_t opCos(
            uint32_t                resultType,
            uint32_t                vector) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Cos);
    m_code.putWord(vector);
    return resultId;
  }
    uint32_t opSqrt(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Sqrt);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opInverseSqrt(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450InverseSqrt);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opNormalize(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Normalize);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opRawAccessChain(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                stride,
            uint32_t                index,
            uint32_t                offset,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpRawAccessChainNV, operand ? 8 : 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(base);
    m_code.putWord(stride);
    m_code.putWord(index);
    m_code.putWord(offset);

    if (operand)
      m_code.putWord(operand);

    return resultId;
  }
    uint32_t opReflect(
            uint32_t                resultType,
            uint32_t                incident,
            uint32_t                normal) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Reflect);
    m_code.putWord(incident);
    m_code.putWord(normal);
    return resultId;
  }
    uint32_t opLength(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Length);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opExp2(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Exp2);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opExp(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Exp);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opLog2(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Log2);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opPow(
            uint32_t                resultType,
            uint32_t                base,
            uint32_t                exponent) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Pow);
    m_code.putWord(base);
    m_code.putWord(exponent);
    return resultId;
  }
    uint32_t opFract(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Fract);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opCeil(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Ceil);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFloor(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Floor);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opRound(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Round);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opRoundEven(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450RoundEven);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opTrunc(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450Trunc);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFConvert(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFConvert, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opPackHalf2x16(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450PackHalf2x16);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opUnpackHalf2x16(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450UnpackHalf2x16);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opSelect(
            uint32_t                resultType,
            uint32_t                condition,
            uint32_t                operand1,
            uint32_t                operand2) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSelect, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(condition);
    m_code.putWord(operand1);
    m_code.putWord(operand2);
    return resultId;
  }
    uint32_t opIsNan(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpIsNan, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opIsInf(
            uint32_t                resultType,
            uint32_t                operand) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpIsInf, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(operand);
    return resultId;
  }
    uint32_t opFunctionCall(
            uint32_t                resultType,
            uint32_t                functionId,
            uint32_t                argCount,
      const uint32_t*               argIds) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpFunctionCall, 4 + argCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(functionId);

    for (uint32_t i = 0; i < argCount; i++)
      m_code.putWord(argIds[i]);
    return resultId;
  }
    void opLabel(
            uint32_t                labelId) {
    m_code.putIns (spv::OpLabel, 2);
    m_code.putWord(labelId);

    m_blockId = labelId;
  }
    uint32_t opLoad(
            uint32_t                typeId,
            uint32_t                pointerId) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLoad, 4 + getMemoryOperandWordCount(operands));
    m_code.putWord(typeId);
    m_code.putWord(resultId);
    m_code.putWord(pointerId);

    putMemoryOperands(operands);
    return resultId;
  }
    uint32_t opLoad(
            uint32_t                typeId,
            uint32_t                pointerId,
      const SpirvMemoryOperands&    operands) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpLoad, 4 + getMemoryOperandWordCount(operands));
    m_code.putWord(typeId);
    m_code.putWord(resultId);
    m_code.putWord(pointerId);

    putMemoryOperands(operands);
    return resultId;
  }
    void opStore(
            uint32_t                pointerId,
            uint32_t                valueId) {
    m_code.putIns (spv::OpStore, 3 + getMemoryOperandWordCount(operands));
    m_code.putWord(pointerId);
    m_code.putWord(valueId);

    putMemoryOperands(operands);
  }
    void opStore(
            uint32_t                pointerId,
            uint32_t                valueId,
      const SpirvMemoryOperands&    operands) {
    m_code.putIns (spv::OpStore, 3 + getMemoryOperandWordCount(operands));
    m_code.putWord(pointerId);
    m_code.putWord(valueId);

    putMemoryOperands(operands);
  }
    uint32_t opInterpolateAtCentroid(
            uint32_t                resultType,
            uint32_t                interpolant) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450InterpolateAtCentroid);
    m_code.putWord(interpolant);
    return resultId;
  }
    uint32_t opInterpolateAtSample(
            uint32_t                resultType,
            uint32_t                interpolant,
            uint32_t                sample) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450InterpolateAtSample);
    m_code.putWord(interpolant);
    m_code.putWord(sample);
    return resultId;
  }
    uint32_t opInterpolateAtOffset(
            uint32_t                resultType,
            uint32_t                interpolant,
            uint32_t                offset) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpExtInst, 7);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(m_instExtGlsl450);
    m_code.putWord(GLSLstd450InterpolateAtOffset);
    m_code.putWord(interpolant);
    m_code.putWord(offset);
    return resultId;
  }
    uint32_t opImage(
            uint32_t                resultType,
            uint32_t                sampledImage) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpImage, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    return resultId;
  }
    uint32_t opImageRead(
            uint32_t                resultType,
            uint32_t                image,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseRead
      : spv::OpImageRead;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    void opImageWrite(
            uint32_t                image,
            uint32_t                coordinates,
            uint32_t                texel,
      const SpirvImageOperands&     operands) {
    m_code.putIns (spv::OpImageWrite,
      4 + getImageOperandWordCount(operands));
    m_code.putWord(image);
    m_code.putWord(coordinates);
    m_code.putWord(texel);

    putImageOperands(operands);
  }
    uint32_t opImageSparseTexelsResident(
            uint32_t                resultType,
            uint32_t                residentCode) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageSparseTexelsResident, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(residentCode);

    return resultId;
  }
    uint32_t opImageTexelPointer(
            uint32_t                resultType,
            uint32_t                image,
            uint32_t                coordinates,
            uint32_t                sample) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageTexelPointer, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    m_code.putWord(coordinates);
    m_code.putWord(sample);
    return resultId;
  }
    uint32_t opSampledImage(
            uint32_t                resultType,
            uint32_t                image,
            uint32_t                sampler) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpSampledImage, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    m_code.putWord(sampler);
    return resultId;
  }
    uint32_t opImageQuerySizeLod(
            uint32_t                resultType,
            uint32_t                image,
            uint32_t                lod) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageQuerySizeLod, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    m_code.putWord(lod);
    return resultId;
  }
    uint32_t opImageQuerySize(
            uint32_t                resultType,
            uint32_t                image) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageQuerySize, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    return resultId;
  }
    uint32_t opImageQueryLevels(
            uint32_t                resultType,
            uint32_t                image) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageQueryLevels, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    return resultId;
  }
    uint32_t opImageQueryLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageQueryLod, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    return resultId;
  }
    uint32_t opImageQuerySamples(
            uint32_t                resultType,
            uint32_t                image) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpImageQuerySamples, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    return resultId;
  }
    uint32_t opImageFetch(
            uint32_t                resultType,
            uint32_t                image,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseFetch
      : spv::OpImageFetch;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(image);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageGather(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                component,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseGather
      : spv::OpImageGather;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(component);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageDrefGather(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                reference,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseDrefGather
      : spv::OpImageDrefGather;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(reference);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleImplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleImplicitLod
      : spv::OpImageSampleImplicitLod;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleExplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleExplicitLod
      : spv::OpImageSampleExplicitLod;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleProjImplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleProjImplicitLod
      : spv::OpImageSampleProjImplicitLod;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleProjExplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleProjExplicitLod
      : spv::OpImageSampleProjExplicitLod;

    m_code.putIns(op, 5 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleDrefImplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                reference,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleDrefImplicitLod
      : spv::OpImageSampleDrefImplicitLod;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(reference);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleDrefExplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                reference,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleDrefExplicitLod
      : spv::OpImageSampleDrefExplicitLod;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(reference);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleProjDrefImplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                reference,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleProjDrefImplicitLod
      : spv::OpImageSampleProjDrefImplicitLod;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(reference);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opImageSampleProjDrefExplicitLod(
            uint32_t                resultType,
            uint32_t                sampledImage,
            uint32_t                coordinates,
            uint32_t                reference,
      const SpirvImageOperands&     operands) {
    uint32_t resultId = this->allocateId();

    spv::Op op = operands.sparse
      ? spv::OpImageSparseSampleProjDrefExplicitLod
      : spv::OpImageSampleProjDrefExplicitLod;

    m_code.putIns(op, 6 + getImageOperandWordCount(operands));
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(sampledImage);
    m_code.putWord(coordinates);
    m_code.putWord(reference);

    putImageOperands(operands);
    return resultId;
  }
    uint32_t opGroupNonUniformBallot(
            uint32_t                resultType,
            uint32_t                execution,
            uint32_t                predicate) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpGroupNonUniformBallot, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(execution);
    m_code.putWord(predicate);
    return resultId;
  }
    uint32_t opGroupNonUniformBallotBitCount(
            uint32_t                resultType,
            uint32_t                execution,
            uint32_t                operation,
            uint32_t                ballot) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpGroupNonUniformBallotBitCount, 6);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(execution);
    m_code.putWord(operation);
    m_code.putWord(ballot);
    return resultId;
  }
    uint32_t opGroupNonUniformElect(
            uint32_t                resultType,
            uint32_t                execution) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpGroupNonUniformElect, 4);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(execution);
    return resultId;
  }
    uint32_t opGroupNonUniformBroadcastFirst(
            uint32_t                resultType,
            uint32_t                execution,
            uint32_t                value) {
    uint32_t resultId = this->allocateId();

    m_code.putIns(spv::OpGroupNonUniformBroadcastFirst, 5);
    m_code.putWord(resultType);
    m_code.putWord(resultId);
    m_code.putWord(execution);
    m_code.putWord(value);
    return resultId;
  }
    void opControlBarrier(
            uint32_t                execution,
            uint32_t                memory,
            uint32_t                semantics) {
    m_code.putIns (spv::OpControlBarrier, 4);
    m_code.putWord(execution);
    m_code.putWord(memory);
    m_code.putWord(semantics);
  }
    void opMemoryBarrier(
            uint32_t                memory,
            uint32_t                semantics) {
    m_code.putIns (spv::OpMemoryBarrier, 3);
    m_code.putWord(memory);
    m_code.putWord(semantics);
  }
    void opLoopMerge(
            uint32_t                mergeBlock,
            uint32_t                continueTarget,
            uint32_t                loopControl) {
    m_code.putIns (spv::OpLoopMerge, 4);
    m_code.putWord(mergeBlock);
    m_code.putWord(continueTarget);
    m_code.putWord(loopControl);
  }
    void opSelectionMerge(
            uint32_t                mergeBlock,
            uint32_t                selectionControl) {
    m_code.putIns (spv::OpSelectionMerge, 3);
    m_code.putWord(mergeBlock);
    m_code.putWord(selectionControl);
  }
    void opBranch(
            uint32_t                label) {
    m_code.putIns (spv::OpBranch, 2);
    m_code.putWord(label);

    m_blockId = 0;
  }
    void opBranchConditional(
            uint32_t                condition,
            uint32_t                trueLabel,
            uint32_t                falseLabel) {
    m_code.putIns (spv::OpBranchConditional, 4);
    m_code.putWord(condition);
    m_code.putWord(trueLabel);
    m_code.putWord(falseLabel);

    m_blockId = 0;
  }
    void opSwitch(
            uint32_t                selector,
            uint32_t                jumpDefault,
            uint32_t                caseCount,
      const SpirvSwitchCaseLabel*   caseLabels) {
    m_code.putIns (spv::OpSwitch, 3 + 2 * caseCount);
    m_code.putWord(selector);
    m_code.putWord(jumpDefault);

    for (uint32_t i = 0; i < caseCount; i++) {
      m_code.putWord(caseLabels[i].literal);
      m_code.putWord(caseLabels[i].labelId);
    }

    m_blockId = 0;
  }
    uint32_t opPhi(
            uint32_t                resultType,
            uint32_t                sourceCount,
      const SpirvPhiLabel*          sourceLabels) {
    uint32_t resultId = this->allocateId();

    m_code.putIns (spv::OpPhi, 3 + 2 * sourceCount);
    m_code.putWord(resultType);
    m_code.putWord(resultId);

    for (uint32_t i = 0; i < sourceCount; i++) {
      m_code.putWord(sourceLabels[i].varId);
      m_code.putWord(sourceLabels[i].labelId);
    }

    return resultId;
  }
    void opReturn() {
    m_code.putIns (spv::OpReturn, 1);
    m_blockId = 0;
  }
    void opDemoteToHelperInvocation() {
    m_code.putIns (spv::OpDemoteToHelperInvocation, 1);
  }
    void opEmitVertex(
            uint32_t                streamId) {
    if (streamId == 0) {
      m_code.putIns (spv::OpEmitVertex, 1);
    } else {
      m_code.putIns (spv::OpEmitStreamVertex, 2);
      m_code.putWord(streamId);
    }
  }
    void opEndPrimitive(
            uint32_t                streamId) {
    if (streamId == 0) {
      m_code.putIns (spv::OpEndPrimitive, 1);
    } else {
      m_code.putIns (spv::OpEndStreamPrimitive, 2);
      m_code.putWord(streamId);
    }
  }
    void opBeginInvocationInterlock() {
    m_code.putIns(spv::OpBeginInvocationInterlockEXT, 1);
  }
    void opEndInvocationInterlock() {
    m_code.putIns(spv::OpEndInvocationInterlockEXT, 1);
  }
    uint32_t opSinCos(
            uint32_t                x,
            bool                    useBuiltIn) {
    // We only operate on 32-bit floats here
    uint32_t floatType = defFloatType(32);
    uint32_t resultType = defVectorType(floatType, 2u);

    if (useBuiltIn) {
      std::array<uint32_t, 2> members = { opSin(floatType, x), opCos(floatType, x) };
      return opCompositeConstruct(resultType, members.size(), members.data());
    } else {
      uint32_t uintType = defIntType(32, false);
      uint32_t sintType = defIntType(32, true);
      uint32_t boolType = defBoolType();

      // Normalize input to multiple of pi/4
      uint32_t xNorm = opFMul(floatType, opFAbs(floatType, x), constf32(4.0 / pi));

      uint32_t xTrunc = opTrunc(floatType, xNorm);
      uint32_t xFract = opFSub(floatType, xNorm, xTrunc);

      uint32_t xInt = opConvertFtoU(uintType, xTrunc);

      // Mirror input along x axis as necessary
      uint32_t mirror = opINotEqual(boolType, opBitwiseAnd(uintType, xInt, constu32(1u)), constu32(0u));
      xFract = opSelect(floatType, mirror, opFSub(floatType, constf32(1.0f), xFract), xFract);

      // Compute taylor series for fractional part
      uint32_t xFract_2 = opFMul(floatType, xFract, xFract);
      uint32_t xFract_4 = opFMul(floatType, xFract_2, xFract_2);
      uint32_t xFract_6 = opFMul(floatType, xFract_4, xFract_2);

      uint32_t taylor = opFMul(floatType, xFract_6, constf32(-sincosTaylorFactor(7)));
      decorate(taylor, spv::DecorationNoContraction);

      taylor = opFFma(floatType, xFract_4, constf32(sincosTaylorFactor(5)), taylor);
      decorate(taylor, spv::DecorationNoContraction);

      taylor = opFFma(floatType, xFract_2, constf32(-sincosTaylorFactor(3)), taylor);
      decorate(taylor, spv::DecorationNoContraction);

      taylor = opFAdd(floatType, constf32(sincosTaylorFactor(1)), taylor);
      decorate(taylor, spv::DecorationNoContraction);

      taylor = opFMul(floatType, taylor, xFract);
      decorate(taylor, spv::DecorationNoContraction);

      // Compute co-function based on sin^2 + cos^2 = 1
      uint32_t coFunc = opSqrt(floatType, opFSub(floatType, constf32(1.0f), opFMul(floatType, taylor, taylor)));

      // Determine whether the taylor series was used for sine or cosine and assign the correct result
      uint32_t funcIsSin = opIEqual(boolType, opBitwiseAnd(uintType, opIAdd(uintType, xInt, constu32(1u)), constu32(2u)), constu32(0u));

      uint32_t sin = opSelect(floatType, funcIsSin, taylor, coFunc);
      uint32_t cos = opSelect(floatType, funcIsSin, coFunc, taylor);

      // Determine whether sine is negative. Interpret the input as a
      // signed integer in order to propagate signed zeroes properly.
      uint32_t inputNeg = opSLessThan(boolType, opBitcast(sintType, x), consti32(0));

      uint32_t sinNeg = opINotEqual(boolType, opBitwiseAnd(uintType, xInt, constu32(4u)), constu32(0u));
               sinNeg = opLogicalNotEqual(boolType, sinNeg, inputNeg);

      // Determine whether cosine is negative
      uint32_t cosNeg = opINotEqual(boolType, opBitwiseAnd(uintType, opIAdd(uintType, xInt, constu32(2u)), constu32(4u)), constu32(0u));

      sin = opSelect(floatType, sinNeg, opFNegate(floatType, sin), sin);
      cos = opSelect(floatType, cosNeg, opFNegate(floatType, cos), cos);

      std::array<uint32_t, 2> members = { sin, cos };
      return opCompositeConstruct(resultType, members.size(), members.data());
    }
  }
  private:
    uint32_t m_version;
    uint32_t m_id             = 1;
    uint32_t m_instExtGlsl450 = 0;
    uint32_t m_blockId        = 0;
    SpirvCodeBuffer m_capabilities;
    SpirvCodeBuffer m_extensions;
    SpirvCodeBuffer m_instExt;
    SpirvCodeBuffer m_memoryModel;
    SpirvCodeBuffer m_entryPoints;
    SpirvCodeBuffer m_execModeInfo;
    SpirvCodeBuffer m_debugNames;
    SpirvCodeBuffer m_annotations;
    SpirvCodeBuffer m_typeConstDefs;
    SpirvCodeBuffer m_variables;
    SpirvCodeBuffer m_code;
    std::unordered_set<uint32_t> m_lateConsts;
    std::vector<uint32_t> m_interfaceVars;
    uint32_t defType(
            spv::Op                 op,
            uint32_t                argCount,
      const uint32_t*               argIds) {
    // Since the type info is stored in the code buffer,
    // we can use the code buffer to look up type IDs as
    // well. Result IDs are always stored as argument 1.
    for (auto ins : m_typeConstDefs) {
      bool match = ins.opCode() == op
                && ins.length() == 2 + argCount;

      for (uint32_t i = 0; i < argCount && match; i++)
        match &= ins.arg(2 + i) == argIds[i];

      if (match)
        return ins.arg(1);
    }

    // Type not yet declared, create a new one.
    uint32_t resultId = this->allocateId();
    m_typeConstDefs.putIns (op, 2 + argCount);
    m_typeConstDefs.putWord(resultId);

    for (uint32_t i = 0; i < argCount; i++)
      m_typeConstDefs.putWord(argIds[i]);
    return resultId;
  }
    uint32_t defConst(
            spv::Op                 op,
            uint32_t                typeId,
            uint32_t                argCount,
      const uint32_t*               argIds) {
    // Avoid declaring constants multiple times
    for (auto ins : m_typeConstDefs) {
      bool match = ins.opCode() == op
                && ins.length() == 3 + argCount
                && ins.arg(1)   == typeId;

      for (uint32_t i = 0; i < argCount && match; i++)
        match &= ins.arg(3 + i) == argIds[i];

      if (!match)
        continue;

      uint32_t id = ins.arg(2);

      if (m_lateConsts.find(id) == m_lateConsts.end())
        return id;
    }

    // Constant not yet declared, make a new one
    uint32_t resultId = this->allocateId();
    m_typeConstDefs.putIns (op, 3 + argCount);
    m_typeConstDefs.putWord(typeId);
    m_typeConstDefs.putWord(resultId);

    for (uint32_t i = 0; i < argCount; i++)
      m_typeConstDefs.putWord(argIds[i]);
    return resultId;
  }
    void instImportGlsl450() {
    m_instExtGlsl450 = this->allocateId();
    const char* name = "GLSL.std.450";

    m_instExt.putIns (spv::OpExtInstImport, 2 + m_instExt.strLen(name));
    m_instExt.putWord(m_instExtGlsl450);
    m_instExt.putStr (name);
  }
    uint32_t getMemoryOperandWordCount(
      const SpirvMemoryOperands&    op) const {
    const uint32_t result
      = ((op.flags & spv::MemoryAccessAlignedMask)              ? 1 : 0)
      + ((op.flags & spv::MemoryAccessMakePointerAvailableMask) ? 1 : 0)
      + ((op.flags & spv::MemoryAccessMakePointerVisibleMask)   ? 1 : 0);

    return op.flags ? result + 1 : 0;
  }
    void putMemoryOperands(
      const SpirvMemoryOperands&    op) {
    if (op.flags) {
      m_code.putWord(op.flags);

      if (op.flags & spv::MemoryAccessAlignedMask)
        m_code.putWord(op.alignment);

      if (op.flags & spv::MemoryAccessMakePointerAvailableMask)
        m_code.putWord(op.makeAvailable);

      if (op.flags & spv::MemoryAccessMakePointerVisibleMask)
        m_code.putWord(op.makeVisible);
    }
  }
    uint32_t getImageOperandWordCount(
      const SpirvImageOperands&     op) const {
    // Each flag may add one or more operands
    const uint32_t result
      = ((op.flags & spv::ImageOperandsBiasMask)                ? 1 : 0)
      + ((op.flags & spv::ImageOperandsLodMask)                 ? 1 : 0)
      + ((op.flags & spv::ImageOperandsConstOffsetMask)         ? 1 : 0)
      + ((op.flags & spv::ImageOperandsGradMask)                ? 2 : 0)
      + ((op.flags & spv::ImageOperandsOffsetMask)              ? 1 : 0)
      + ((op.flags & spv::ImageOperandsConstOffsetsMask)        ? 1 : 0)
      + ((op.flags & spv::ImageOperandsSampleMask)              ? 1 : 0)
      + ((op.flags & spv::ImageOperandsMinLodMask)              ? 1 : 0)
      + ((op.flags & spv::ImageOperandsMakeTexelAvailableMask)  ? 1 : 0)
      + ((op.flags & spv::ImageOperandsMakeTexelVisibleMask)    ? 1 : 0);

    // Add a DWORD for the operand mask if it is non-zero
    return op.flags ? result + 1 : 0;
  }
    void putImageOperands(
      const SpirvImageOperands&     op) {
    if (op.flags) {
      m_code.putWord(op.flags);

      if (op.flags & spv::ImageOperandsBiasMask)
        m_code.putWord(op.sLodBias);

      if (op.flags & spv::ImageOperandsLodMask)
        m_code.putWord(op.sLod);

      if (op.flags & spv::ImageOperandsGradMask) {
        m_code.putWord(op.sGradX);
        m_code.putWord(op.sGradY);
      }

      if (op.flags & spv::ImageOperandsConstOffsetMask)
        m_code.putWord(op.sConstOffset);

      if (op.flags & spv::ImageOperandsOffsetMask)
        m_code.putWord(op.gOffset);

      if (op.flags & spv::ImageOperandsConstOffsetsMask)
        m_code.putWord(op.gConstOffsets);

      if (op.flags & spv::ImageOperandsSampleMask)
        m_code.putWord(op.sSampleId);

      if (op.flags & spv::ImageOperandsMinLodMask)
        m_code.putWord(op.sMinLod);

      if (op.flags & spv::ImageOperandsMakeTexelAvailableMask)
        m_code.putWord(op.makeAvailable);

      if (op.flags & spv::ImageOperandsMakeTexelVisibleMask)
        m_code.putWord(op.makeVisible);
    }
  }
    bool isInterfaceVar(
            spv::StorageClass       sclass) const {
    if (m_version < spvVersion(1, 4)) {
      return sclass == spv::StorageClassInput
          || sclass == spv::StorageClassOutput;
    } else {
      // All global variables need to be declared
      return sclass != spv::StorageClassFunction;
    }
  }
    void classifyBlocks(
            std::unordered_set<uint32_t>& reachableBlocks,
            std::unordered_set<uint32_t>& mergeBlocks) {
    std::unordered_multimap<uint32_t, uint32_t> branches;
    std::queue<uint32_t> blockQueue;

    uint32_t blockId = 0;

    for (auto ins : m_code) {
      switch (ins.opCode()) {
        case spv::OpLabel: {
          uint32_t id = ins.arg(1);

          if (!blockId)
            branches.insert({ 0u, id });

          blockId = id;
        } break;

        case spv::OpFunction: {
          blockId = 0u;
        } break;

        case spv::OpBranch: {
          branches.insert({ blockId, ins.arg(1) });
        } break;

        case spv::OpBranchConditional: {
          branches.insert({ blockId, ins.arg(2) });
          branches.insert({ blockId, ins.arg(3) });
        } break;

        case spv::OpSwitch: {
          branches.insert({ blockId, ins.arg(2) });

          for (uint32_t i = 4; i < ins.length(); i += 2)
            branches.insert({ blockId, ins.arg(i) });
        } break;

        case spv::OpSelectionMerge: {
          mergeBlocks.insert(ins.arg(1));
        } break;

        case spv::OpLoopMerge: {
          mergeBlocks.insert(ins.arg(1));

          // It is possible for the continue block to be unreachable in
          // practice, but we still need to emit it if we are not going
          // to eliminate this loop. Since the current block dominates
          // the loop, use it to keep the continue block intact.
          branches.insert({ blockId, ins.arg(2) });
        } break;

        default:;
      }
    }

    blockQueue.push(0);

    while (!blockQueue.empty()) {
      uint32_t id = blockQueue.front();

      auto range = branches.equal_range(id);

      for (auto i = range.first; i != range.second; i++) {
        if (reachableBlocks.insert(i->second).second)
          blockQueue.push(i->second);
      }

      blockQueue.pop();
    }
  }
};

class DxbcHeader {
  public:
    DxbcHeader(DxbcReader& reader) {
    // FourCC at the start of the file, must be 'DXBC'
    DxbcTag fourcc = reader.readTag();

    if (fourcc != "DXBC")
      throw DxvkError("DxbcHeader::DxbcHeader: Invalid fourcc, expected 'DXBC'");

    // Stuff we don't actually need to store
    reader.skip(4 * sizeof(uint32_t)); // Check sum
    reader.skip(1 * sizeof(uint32_t)); // Constant 1
    reader.skip(1 * sizeof(uint32_t)); // Bytecode length

    // Number of chunks in the file
    uint32_t chunkCount = reader.readu32();

    // Chunk offsets are stored immediately after
    for (uint32_t i = 0; i < chunkCount; i++)
      m_chunkOffsets.push_back(reader.readu32());
  }
    ~DxbcHeader() {

  }
  private:
    std::vector<uint32_t> m_chunkOffsets;
};

class for {
  public:
    SpirvCodeBuffer() {
    stream.ignore(std::numeric_limits<std::streamsize>::max());
    std::streamsize length = stream.gcount();
    stream.clear();
    stream.seekg(0, std::ios_base::beg);

    std::vector<char> buffer(length);
    stream.read(buffer.data(), length);
    buffer.resize(stream.gcount());

    m_code.resize(buffer.size() / sizeof(uint32_t));
    std::memcpy(reinterpret_cast<char*>(m_code.data()),
      buffer.data(), m_code.size() * sizeof(uint32_t));

    m_ptr = m_code.size();
  }
    explicit SpirvCodeBuffer(uint32_t size) {
    stream.ignore(std::numeric_limits<std::streamsize>::max());
    std::streamsize length = stream.gcount();
    stream.clear();
    stream.seekg(0, std::ios_base::beg);

    std::vector<char> buffer(length);
    stream.read(buffer.data(), length);
    buffer.resize(stream.gcount());

    m_code.resize(buffer.size() / sizeof(uint32_t));
    std::memcpy(reinterpret_cast<char*>(m_code.data()),
      buffer.data(), m_code.size() * sizeof(uint32_t));

    m_ptr = m_code.size();
  }
    SpirvCodeBuffer(uint32_t size, const uint32_t* data) {
    stream.ignore(std::numeric_limits<std::streamsize>::max());
    std::streamsize length = stream.gcount();
    stream.clear();
    stream.seekg(0, std::ios_base::beg);

    std::vector<char> buffer(length);
    stream.read(buffer.data(), length);
    buffer.resize(stream.gcount());

    m_code.resize(buffer.size() / sizeof(uint32_t));
    std::memcpy(reinterpret_cast<char*>(m_code.data()),
      buffer.data(), m_code.size() * sizeof(uint32_t));

    m_ptr = m_code.size();
  }
    SpirvCodeBuffer(std::istream& stream) {
    stream.ignore(std::numeric_limits<std::streamsize>::max());
    std::streamsize length = stream.gcount();
    stream.clear();
    stream.seekg(0, std::ios_base::beg);

    std::vector<char> buffer(length);
    stream.read(buffer.data(), length);
    buffer.resize(stream.gcount());

    m_code.resize(buffer.size() / sizeof(uint32_t));
    std::memcpy(reinterpret_cast<char*>(m_code.data()),
      buffer.data(), m_code.size() * sizeof(uint32_t));

    m_ptr = m_code.size();
  }
    template<size_t N>
    SpirvCodeBuffer(const uint32_t (&data)[N])
    : SpirvCodeBuffer(N, data) { }
    ~SpirvCodeBuffer() { }
    uint32_t allocId() {
    constexpr size_t BoundIdsOffset = 3;

    if (m_code.size() <= BoundIdsOffset)
      return 0;

    return m_code[BoundIdsOffset]++;
  }
    void append(const SpirvInstruction& ins) {
    if (other.size() != 0) {
      const size_t size = m_code.size();
      m_code.resize(size + other.m_code.size());

            uint32_t* dst = this->m_code.data();
      const uint32_t* src = other.m_code.data();

      std::memcpy(dst + size, src, other.size());
      m_ptr += other.m_code.size();
    }
  }
    void append(const SpirvCodeBuffer& other) {
    if (other.size() != 0) {
      const size_t size = m_code.size();
      m_code.resize(size + other.m_code.size());

            uint32_t* dst = this->m_code.data();
      const uint32_t* src = other.m_code.data();

      std::memcpy(dst + size, src, other.size());
      m_ptr += other.m_code.size();
    }
  }
    void putWord(uint32_t word) {
    m_code.insert(m_code.begin() + m_ptr, word);
    m_ptr += 1;
  }
    void putIns(spv::Op opCode, uint16_t wordCount) {
    this->putWord(
        (static_cast<uint32_t>(opCode)    <<  0)
      | (static_cast<uint32_t>(wordCount) << 16));
  }
    void putInt32(uint32_t word) {
    this->putWord(word);
  }
    void putInt64(uint64_t value) {
    this->putWord(value >>  0);
    this->putWord(value >> 32);
  }
    void putFloat32(float value) {
    uint32_t tmp;
    static_assert(sizeof(tmp) == sizeof(value));
    std::memcpy(&tmp, &value, sizeof(value));
    this->putInt32(tmp);
  }
    void putFloat64(double value) {
    uint64_t tmp;
    static_assert(sizeof(tmp) == sizeof(value));
    std::memcpy(&tmp, &value, sizeof(value));
    this->putInt64(tmp);
  }
    void putStr(const char* str) {
    uint32_t word = 0;
    uint32_t nbit = 0;

    for (uint32_t i = 0; str[i] != '\0'; str++) {
      word |= (static_cast<uint32_t>(str[i]) & 0xFF) << nbit;

      if ((nbit += 8) == 32) {
        this->putWord(word);
        word = 0;
        nbit = 0;
      }
    }

    // Commit current word
    this->putWord(word);
  }
    void putHeader(uint32_t version, uint32_t boundIds) {
    this->putWord(spv::MagicNumber);
    this->putWord(version);
    this->putWord(0); // Generator
    this->putWord(boundIds);
    this->putWord(0); // Schema
  }
    void erase(size_t size) {
    m_code.erase(
      m_code.begin() + m_ptr,
      m_code.begin() + m_ptr + size);
  }
    uint32_t strLen(const char* str) {
    // Null-termination plus padding
    return (std::strlen(str) + 4) / 4;
  }
    void store(std::ostream& stream) const {
    stream.write(
      reinterpret_cast<const char*>(m_code.data()),
      sizeof(uint32_t) * m_code.size());
  }
  private:
    std::vector<uint32_t> m_code;
    size_t m_ptr = 0;
};

#include "context.hpp"
#include "config/config.hpp"
#include "common/exception.hpp"
#include "extract/extract.hpp"
#include "extract/trans.hpp"
#include "utils/utils.hpp"
#include "hooks.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>
#include <lsfg_3_1.hpp>
#include <lsfg_3_1p.hpp>

#include <filesystem>
#include <exception>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <memory>
#include <string>
#include <thread>
#include <array>

LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
        VkExtent2D extent, const std::vector<VkImage>& swapchainImages)
        : swapchain(swapchain), swapchainImages(swapchainImages),
          extent(extent) {
    // get updated configuration
    auto& conf = Config::activeConf;
    if (!conf.config_file.empty()
            && (
                    !std::filesystem::exists(conf.config_file)
                  || conf.timestamp != std::filesystem::last_write_time(conf.config_file)
            )) {
        std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(100));

        // reread configuration
        const std::string file = Utils::getConfigFile();
        const auto name = Utils::getProcessName();
        try {
            Config::updateConfig(file);
            conf = Config::getConfig(name);
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n";
            std::cerr << "- " << e.what() << '\n';
        }

        LSFG_3_1P::finalize();
        LSFG_3_1::finalize();

        // print config
        std::cerr << "lsfg-vk: Reloaded configuration for " << name.second << ":\n";
        if (!conf.dll.empty()) std::cerr << "  Using DLL from: " << conf.dll << '\n';
        std::cerr << "  Multiplier: " << conf.multiplier << '\n';
        std::cerr << "  Flow Scale: " << conf.flowScale << '\n';
        std::cerr << "  Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n';
        std::cerr << "  HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n';
        if (conf.e_present != 2) std::cerr << "  ! Present Mode: " << conf.e_present << '\n';

        if (conf.multiplier <= 1) return;
    }
    // we could take the format from the swapchain,
    // but honestly this is safer.
    const VkFormat format = conf.hdr
        ? VK_FORMAT_R8G8B8A8_UNORM
        : VK_FORMAT_R16G16B16A16_SFLOAT;

    // prepare textures for lsfg
    std::array<int, 2> fds{};
    this->frame_0 = Mini::Image(info.device, info.physicalDevice,
        extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
        &fds.at(0));
    this->frame_1 = Mini::Image(info.device, info.physicalDevice,
        extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
        &fds.at(1));

    std::vector<int> outFds(conf.multiplier - 1);
    for (size_t i = 0; i < (conf.multiplier - 1); ++i)
        this->out_n.emplace_back(info.device, info.physicalDevice,
            extent, format,
            VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
            &outFds.at(i));

    // initialize lsfg
    auto* lsfgInitialize = LSFG_3_1::initialize;
    auto* lsfgCreateContext = LSFG_3_1::createContext;
    auto* lsfgDeleteContext = LSFG_3_1::deleteContext;
    if (conf.performance) {
        lsfgInitialize = LSFG_3_1P::initialize;
        lsfgCreateContext = LSFG_3_1P::createContext;
        lsfgDeleteContext = LSFG_3_1P::deleteContext;
    }

    setenv("DISABLE_LSFG", "1", 1); // NOLINT

    lsfgInitialize(
        Utils::getDeviceUUID(info.physicalDevice),
        conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1,
        [](const std::string& name) {
            auto dxbc = Extract::getShader(name);
            auto spirv = Extract::translateShader(dxbc);
            return spirv;
        }
    );

    this->lsfgCtxId = std::shared_ptr<int32_t>(
        new int32_t(lsfgCreateContext(fds.at(0), fds.at(1), outFds, extent, format)),
        [lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) {
            lsfgDeleteContext(*id);
        }
    );

    unsetenv("DISABLE_LSFG"); // NOLINT

    // prepare render passes
    this->cmdPool = Mini::CommandPool(info.device, info.queue.first);
    for (size_t i = 0; i < 8; i++) {
        auto& pass = this->passInfos.at(i);
        pass.renderSemaphores.resize(conf.multiplier - 1);
        pass.acquireSemaphores.resize(conf.multiplier - 1);
        pass.postCopyBufs.resize(conf.multiplier - 1);
        pass.postCopySemaphores.resize(conf.multiplier - 1);
        pass.prevPostCopySemaphores.resize(conf.multiplier - 1);
    }
}

VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue,
        const std::vector<VkSemaphore>& gameRenderSemaphores, uint32_t presentIdx) {
    const auto& conf = Config::activeConf;
    auto& pass = this->passInfos.at(this->frameIdx % 8);

    // 1. copy swapchain image to frame_0/frame_1
    int preCopySemaphoreFd{};
    pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd);
    pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device);
    pass.preCopyBuf = Mini::CommandBuffer(info.device, this->cmdPool);
    pass.preCopyBuf.begin();

    Utils::copyImage(pass.preCopyBuf.handle(),
        this->swapchainImages.at(presentIdx),
        this->frameIdx % 2 == 0 ? this->frame_0.handle() : this->frame_1.handle(),
        this->extent.width, this->extent.height,
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
        true, false);

    pass.preCopyBuf.end();

    std::vector<VkSemaphore> gameRenderSemaphores2 = gameRenderSemaphores;
    if (this->frameIdx > 0)
        gameRenderSemaphores2.emplace_back(this->passInfos.at((this->frameIdx - 1) % 8)
            .preCopySemaphores.at(1).handle());
    pass.preCopyBuf.submit(info.queue.second,
        gameRenderSemaphores2,
        { pass.preCopySemaphores.at(0).handle(),
          pass.preCopySemaphores.at(1).handle() });

    // 2. render intermediary frames
    std::vector<int> renderSemaphoreFds(conf.multiplier - 1);
    for (size_t i = 0; i < (conf.multiplier - 1); ++i)
        pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i));

    if (conf.performance)
        LSFG_3_1P::presentContext(*this->lsfgCtxId,
            preCopySemaphoreFd,
            renderSemaphoreFds);
    else
        LSFG_3_1::presentContext(*this->lsfgCtxId,
            preCopySemaphoreFd,
            renderSemaphoreFds);

    for (size_t i = 0; i < (conf.multiplier - 1); i++) {
        // 3. acquire next swapchain image
        pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device);
        uint32_t imageIdx{};
        auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX,
            pass.acquireSemaphores.at(i).handle(), VK_NULL_HANDLE, &imageIdx);
        if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
            throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image");

        // 4. copy output image to swapchain image
        pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device);
        pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device);
        pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool);
        pass.postCopyBufs.at(i).begin();

        Utils::copyImage(pass.postCopyBufs.at(i).handle(),
            this->out_n.at(i).handle(),
            this->swapchainImages.at(imageIdx),
            this->extent.width, this->extent.height,
            VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
            false, true);

        pass.postCopyBufs.at(i).end();
        pass.postCopyBufs.at(i).submit(info.queue.second,
            { pass.acquireSemaphores.at(i).handle(),
              pass.renderSemaphores.at(i).handle() },
            { pass.postCopySemaphores.at(i).handle(),
              pass.prevPostCopySemaphores.at(i).handle() });

        // 5. present swapchain image
        std::vector<VkSemaphore> waitSemaphores{ pass.postCopySemaphores.at(i).handle() };
        if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle());

        const VkPresentInfoKHR presentInfo{
            .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
            .pNext = i == 0 ? pNext : nullptr, // only set on first present
            .waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()),
            .pWaitSemaphores = waitSemaphores.data(),
            .swapchainCount = 1,
            .pSwapchains = &this->swapchain,
            .pImageIndices = &imageIdx,
        };
        res = Layer::ovkQueuePresentKHR(queue, &presentInfo);
        if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
            throw LSFG::vulkan_error(res, "Failed to present swapchain image");
    }

    // 6. present actual next frame
    VkSemaphore lastPrevPostCopySemaphore =
        pass.prevPostCopySemaphores.at(conf.multiplier - 1 - 1).handle();
    const VkPresentInfoKHR presentInfo{
        .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = &lastPrevPostCopySemaphore,
        .swapchainCount = 1,
        .pSwapchains = &this->swapchain,
        .pImageIndices = &presentIdx,
    };
    auto res = Layer::ovkQueuePresentKHR(queue, &presentInfo);
    if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
        throw LSFG::vulkan_error(res, "Failed to present swapchain image");

    this->frameIdx++;
    return res;
}

#pragma once

#include "dxbc_include.h"

namespace dxvk {

  /**
   * \brief Instruction code listing
   */
  enum class DxbcOpcode : uint32_t {
    Add                                  = 0,
    And                                  = 1,
    Break                                = 2,
    Breakc                               = 3,
    Call                                 = 4,
    Callc                                = 5,
    Case                                 = 6,
    Continue                             = 7,
    Continuec                            = 8,
    Cut                                  = 9,
    Default                              = 10,
    DerivRtx                             = 11,
    DerivRty                             = 12,
    Discard                              = 13,
    Div                                  = 14,
    Dp2                                  = 15,
    Dp3                                  = 16,
    Dp4                                  = 17,
    Else                                 = 18,
    Emit                                 = 19,
    EmitThenCut                          = 20,
    EndIf                                = 21,
    EndLoop                              = 22,
    EndSwitch                            = 23,
    Eq                                   = 24,
    Exp                                  = 25,
    Frc                                  = 26,
    FtoI                                 = 27,
    FtoU                                 = 28,
    Ge                                   = 29,
    IAdd                                 = 30,
    If                                   = 31,
    IEq                                  = 32,
    IGe                                  = 33,
    ILt                                  = 34,
    IMad                                 = 35,
    IMax                                 = 36,
    IMin                                 = 37,
    IMul                                 = 38,
    INe                                  = 39,
    INeg                                 = 40,
    IShl                                 = 41,
    IShr                                 = 42,
    ItoF                                 = 43,
    Label                                = 44,
    Ld                                   = 45,
    LdMs                                 = 46,
    Log                                  = 47,
    Loop                                 = 48,
    Lt                                   = 49,
    Mad                                  = 50,
    Min                                  = 51,
    Max                                  = 52,
    CustomData                           = 53,
    Mov                                  = 54,
    Movc                                 = 55,
    Mul                                  = 56,
    Ne                                   = 57,
    Nop                                  = 58,
    Not                                  = 59,
    Or                                   = 60,
    ResInfo                              = 61,
    Ret                                  = 62,
    Retc                                 = 63,
    RoundNe                              = 64,
    RoundNi                              = 65,
    RoundPi                              = 66,
    RoundZ                               = 67,
    Rsq                                  = 68,
    Sample                               = 69,
    SampleC                              = 70,
    SampleClz                            = 71,
    SampleL                              = 72,
    SampleD                              = 73,
    SampleB                              = 74,
    Sqrt                                 = 75,
    Switch                               = 76,
    SinCos                               = 77,
    UDiv                                 = 78,
    ULt                                  = 79,
    UGe                                  = 80,
    UMul                                 = 81,
    UMad                                 = 82,
    UMax                                 = 83,
    UMin                                 = 84,
    UShr                                 = 85,
    UtoF                                 = 86,
    Xor                                  = 87,
    DclResource                          = 88,
    DclConstantBuffer                    = 89,
    DclSampler                           = 90,
    DclIndexRange                        = 91,
    DclGsOutputPrimitiveTopology         = 92,
    DclGsInputPrimitive                  = 93,
    DclMaxOutputVertexCount              = 94,
    DclInput                             = 95,
    DclInputSgv                          = 96,
    DclInputSiv                          = 97,
    DclInputPs                           = 98,
    DclInputPsSgv                        = 99,
    DclInputPsSiv                        = 100,
    DclOutput                            = 101,
    DclOutputSgv                         = 102,
    DclOutputSiv                         = 103,
    DclTemps                             = 104,
    DclIndexableTemp                     = 105,
    DclGlobalFlags                       = 106,
    Reserved0                            = 107,
    Lod                                  = 108,
    Gather4                              = 109,
    SamplePos                            = 110,
    SampleInfo                           = 111,
    Reserved1                            = 112,
    HsDecls                              = 113,
    HsControlPointPhase                  = 114,
    HsForkPhase                          = 115,
    HsJoinPhase                          = 116,
    EmitStream                           = 117,
    CutStream                            = 118,
    EmitThenCutStream                    = 119,
    InterfaceCall                        = 120,
    BufInfo                              = 121,
    DerivRtxCoarse                       = 122,
    DerivRtxFine                         = 123,
    DerivRtyCoarse                       = 124,
    DerivRtyFine                         = 125,
    Gather4C                             = 126,
    Gather4Po                            = 127,
    Gather4PoC                           = 128,
    Rcp                                  = 129,
    F32toF16                             = 130,
    F16toF32                             = 131,
    UAddc                                = 132,
    USubb                                = 133,
    CountBits                            = 134,
    FirstBitHi                           = 135,
    FirstBitLo                           = 136,
    FirstBitShi                          = 137,
    UBfe                                 = 138,
    IBfe                                 = 139,
    Bfi                                  = 140,
    BfRev                                = 141,
    Swapc                                = 142,
    DclStream                            = 143,
    DclFunctionBody                      = 144,
    DclFunctionTable                     = 145,
    DclInterface                         = 146,
    DclInputControlPointCount            = 147,
    DclOutputControlPointCount           = 148,
    DclTessDomain                        = 149,
    DclTessPartitioning                  = 150,
    DclTessOutputPrimitive               = 151,
    DclHsMaxTessFactor                   = 152,
    DclHsForkPhaseInstanceCount          = 153,
    DclHsJoinPhaseInstanceCount          = 154,
    DclThreadGroup                       = 155,
    DclUavTyped                          = 156,
    DclUavRaw                            = 157,
    DclUavStructured                     = 158,
    DclThreadGroupSharedMemoryRaw        = 159,
    DclThreadGroupSharedMemoryStructured = 160,
    DclResourceRaw                       = 161,
    DclResourceStructured                = 162,
    LdUavTyped                           = 163,
    StoreUavTyped                        = 164,
    LdRaw                                = 165,
    StoreRaw                             = 166,
    LdStructured                         = 167,
    StoreStructured                      = 168,
    AtomicAnd                            = 169,
    AtomicOr                             = 170,
    AtomicXor                            = 171,
    AtomicCmpStore                       = 172,
    AtomicIAdd                           = 173,
    AtomicIMax                           = 174,
    AtomicIMin                           = 175,
    AtomicUMax                           = 176,
    AtomicUMin                           = 177,
    ImmAtomicAlloc                       = 178,
    ImmAtomicConsume                     = 179,
    ImmAtomicIAdd                        = 180,
    ImmAtomicAnd                         = 181,
    ImmAtomicOr                          = 182,
    ImmAtomicXor                         = 183,
    ImmAtomicExch                        = 184,
    ImmAtomicCmpExch                     = 185,
    ImmAtomicIMax                        = 186,
    ImmAtomicIMin                        = 187,
    ImmAtomicUMax                        = 188,
    ImmAtomicUMin                        = 189,
    Sync                                 = 190,
    DAdd                                 = 191,
    DMax                                 = 192,
    DMin                                 = 193,
    DMul                                 = 194,
    DEq                                  = 195,
    DGe                                  = 196,
    DLt                                  = 197,
    DNe                                  = 198,
    DMov                                 = 199,
    DMovc                                = 200,
    DtoF                                 = 201,
    FtoD                                 = 202,
    EvalSnapped                          = 203,
    EvalSampleIndex                      = 204,
    EvalCentroid                         = 205,
    DclGsInstanceCount                   = 206,
    Abort                                = 207,
    DebugBreak                           = 208,
    ReservedBegin11_1                    = 209,
    DDiv                                 = 210,
    DFma                                 = 211,
    DRcp                                 = 212,
    Msad                                 = 213,
    DtoI                                 = 214,
    DtoU                                 = 215,
    ItoD                                 = 216,
    UtoD                                 = 217,
    ReservedBegin11_2                    = 218,
    Gather4S                             = 219,
    Gather4CS                            = 220,
    Gather4PoS                           = 221,
    Gather4PoCS                          = 222,
    LdS                                  = 223,
    LdMsS                                = 224,
    LdUavTypedS                          = 225,
    LdRawS                               = 226,
    LdStructuredS                        = 227,
    SampleLS                             = 228,
    SampleClzS                           = 229,
    SampleClampS                         = 230,
    SampleBClampS                        = 231,
    SampleDClampS                        = 232,
    SampleCClampS                        = 233,
    CheckAccessFullyMapped               = 234,
  };

  /**
   * \brief Extended opcode
   */
  enum class DxbcExtOpcode : uint32_t {
    Empty                                = 0,
    SampleControls                       = 1,
    ResourceDim                          = 2,
    ResourceReturnType                   = 3,
  };

  /**
   * \brief Operand type
   *
   * Selects the 'register file' from which
   * to retrieve an operand's value.
   */
  enum class DxbcOperandType : uint32_t {
    Temp                    = 0,
    Input                   = 1,
    Output                  = 2,
    IndexableTemp           = 3,
    Imm32                   = 4,
    Imm64                   = 5,
    Sampler                 = 6,
    Resource                = 7,
    ConstantBuffer          = 8,
    ImmediateConstantBuffer = 9,
    Label                   = 10,
    InputPrimitiveId        = 11,
    OutputDepth             = 12,
    Null                    = 13,
    Rasterizer              = 14,
    OutputCoverageMask      = 15,
    Stream                  = 16,
    FunctionBody            = 17,
    FunctionTable           = 18,
    Interface               = 19,
    FunctionInput           = 20,
    FunctionOutput          = 21,
    OutputControlPointId    = 22,
    InputForkInstanceId     = 23,
    InputJoinInstanceId     = 24,
    InputControlPoint       = 25,
    OutputControlPoint      = 26,
    InputPatchConstant      = 27,
    InputDomainPoint        = 28,
    ThisPointer             = 29,
    UnorderedAccessView     = 30,
    ThreadGroupSharedMemory = 31,
    InputThreadId           = 32,
    InputThreadGroupId      = 33,
    InputThreadIdInGroup    = 34,
    InputCoverageMask       = 35,
    InputThreadIndexInGroup = 36,
    InputGsInstanceId       = 37,
    OutputDepthGe           = 38,
    OutputDepthLe           = 39,
    CycleCounter            = 40,
    OutputStencilRef        = 41,
    InputInnerCoverage      = 42,
  };

  /**
   * \brief Number of components
   *
   * Used by operands to determine whether the
   * operand has one, four or zero components.
   */
  enum class DxbcComponentCount : uint32_t {
    Component0 = 0,
    Component1 = 1,
    Component4 = 2,
  };

  /**
   * \brief Component selection mode
   *
   * When an operand has four components, the
   * component selection mode deterines which
   * components are used for the operation.
   */
  enum class DxbcRegMode : uint32_t {
    Mask    = 0,
    Swizzle = 1,
    Select1 = 2,
  };

  /**
   * \brief Index representation
   *
   * Determines how an operand
   * register index is stored.
   */
  enum class DxbcOperandIndexRepresentation : uint32_t {
    Imm32             = 0,
    Imm64             = 1,
    Relative          = 2,
    Imm32Relative     = 3,
    Imm64Relative     = 4,
  };

  /**
   * \brief Extended operand type
   */
  enum class DxbcOperandExt : uint32_t {
    OperandModifier   = 1,
  };

  /**
   * \brief Resource dimension
   * The type of a resource.
   */
  enum class DxbcResourceDim : uint32_t {
    Unknown           = 0,
    Buffer            = 1,
    Texture1D         = 2,
    Texture2D         = 3,
    Texture2DMs       = 4,
    Texture3D         = 5,
    TextureCube       = 6,
    Texture1DArr      = 7,
    Texture2DArr      = 8,
    Texture2DMsArr    = 9,
    TextureCubeArr    = 10,
    RawBuffer         = 11,
    StructuredBuffer  = 12,
  };

  /**
   * \brief Resource return type
   * Data type for resource read ops.
   */
  enum class DxbcResourceReturnType : uint32_t {
    Unorm             = 1,
    Snorm             = 2,
    Sint              = 3,
    Uint              = 4,
    Float             = 5,
    Mixed             = 6,  /// ?
    Double            = 7,
    Continued         = 8,  /// ?
    Unused            = 9,  /// ?
  };

  /**
   * \brief Register component type
   * Data type of a register component.
   */
  enum class DxbcRegisterComponentType : uint32_t {
    Unknown           = 0,
    Uint32            = 1,
    Sint32            = 2,
    Float32           = 3,
  };

  /**
   * \brief Instruction return type
   */
  enum class DxbcInstructionReturnType : uint32_t {
    Float             = 0,
    Uint              = 1,
  };

  enum class DxbcSystemValue : uint32_t {
    None                          = 0,
    Position                      = 1,
    ClipDistance                  = 2,
    CullDistance                  = 3,
    RenderTargetId                = 4,
    ViewportId                    = 5,
    VertexId                      = 6,
    PrimitiveId                   = 7,
    InstanceId                    = 8,
    IsFrontFace                   = 9,
    SampleIndex                   = 10,
    FinalQuadUeq0EdgeTessFactor   = 11,
    FinalQuadVeq0EdgeTessFactor   = 12,
    FinalQuadUeq1EdgeTessFactor   = 13,
    FinalQuadVeq1EdgeTessFactor   = 14,
    FinalQuadUInsideTessFactor    = 15,
    FinalQuadVInsideTessFactor    = 16,
    FinalTriUeq0EdgeTessFactor    = 17,
    FinalTriVeq0EdgeTessFactor    = 18,
    FinalTriWeq0EdgeTessFactor    = 19,
    FinalTriInsideTessFactor      = 20,
    FinalLineDetailTessFactor     = 21,
    FinalLineDensityTessFactor    = 22,
    Target                        = 64,
    Depth                         = 65,
    Coverage                      = 66,
    DepthGe                       = 67,
    DepthLe                       = 68
  };

  enum class DxbcInterpolationMode : uint32_t {
    Undefined                   = 0,
    Constant                    = 1,
    Linear                      = 2,
    LinearCentroid              = 3,
    LinearNoPerspective         = 4,
    LinearNoPerspectiveCentroid = 5,
    LinearSample                = 6,
    LinearNoPerspectiveSample   = 7,
  };

  enum class DxbcGlobalFlag : uint32_t {
    RefactoringAllowed    = 0,
    DoublePrecision       = 1,
    EarlyFragmentTests    = 2,
    RawStructuredBuffers  = 3,
  };

  using DxbcGlobalFlags = Flags<DxbcGlobalFlag>;

  enum class DxbcZeroTest : uint32_t {
    TestZ   = 0,
    TestNz  = 1,
  };

  enum class DxbcResinfoType : uint32_t {
    Float     = 0,
    RcpFloat  = 1,
    Uint      = 2,
  };

  enum class DxbcSyncFlag : uint32_t {
    ThreadsInGroup                = 0,
    ThreadGroupSharedMemory       = 1,
    UavMemoryGroup                = 2,
    UavMemoryGlobal               = 3,
  };

  using DxbcSyncFlags = Flags<DxbcSyncFlag>;

  /**
   * \brief Geometry shader input primitive
   */
  enum class DxbcPrimitive : uint32_t {
    Undefined         =  0,
    Point             =  1,
    Line              =  2,
    Triangle          =  3,
    LineAdj           =  6,
    TriangleAdj       =  7,
    Patch1            =  8,
    Patch2            =  9,
    Patch3            = 10,
    Patch4            = 11,
    Patch5            = 12,
    Patch6            = 13,
    Patch7            = 14,
    Patch8            = 15,
    Patch9            = 16,
    Patch10           = 17,
    Patch11           = 18,
    Patch12           = 19,
    Patch13           = 20,
    Patch14           = 21,
    Patch15           = 22,
    Patch16           = 23,
    Patch17           = 24,
    Patch18           = 25,
    Patch19           = 26,
    Patch20           = 27,
    Patch21           = 28,
    Patch22           = 29,
    Patch23           = 30,
    Patch24           = 31,
    Patch25           = 32,
    Patch26           = 33,
    Patch27           = 34,
    Patch28           = 35,
    Patch29           = 36,
    Patch30           = 37,
    Patch31           = 38,
    Patch32           = 39,
  };

  /**
   * \brief Geometry shader output topology
   */
  enum class DxbcPrimitiveTopology : uint32_t {
    Undefined         = 0,
    PointList         = 1,
    LineList          = 2,
    LineStrip         = 3,
    TriangleList      = 4,
    TriangleStrip     = 5,
    LineListAdj       = 10,
    LineStripAdj      = 11,
    TriangleListAdj   = 12,
    TriangleStripAdj  = 13,
  };

  /**
   * \brief Sampler operation mode
   */
  enum class DxbcSamplerMode : uint32_t {
    Default           = 0,
    Comparison        = 1,
    Mono              = 2,
  };

  /**
   * \brief Scalar value type
   *
   * Enumerates possible register component
   * types. Scalar types are represented as
   * a one-component vector type.
   */
  enum class DxbcScalarType : uint32_t {
    Uint32    = 0,
    Uint64    = 1,
    Sint32    = 2,
    Sint64    = 3,
    Float32   = 4,
    Float64   = 5,
    Bool      = 6,
  };

  /**
   * \brief Tessellator domain
   */
  enum class DxbcTessDomain : uint32_t {
    Undefined     = 0,
    Isolines      = 1,
    Triangles     = 2,
    Quads         = 3,
  };

  /**
   * \brief Tessellator partitioning
   */
  enum class DxbcTessPartitioning : uint32_t {
    Undefined     = 0,
    Integer       = 1,
    Pow2          = 2,
    FractOdd      = 3,
    FractEven     = 4,
  };

  /**
   * \brief UAV definition flags
   */
  enum class DxbcUavFlag : uint32_t {
    GloballyCoherent = 0,
    RasterizerOrdered = 1,
  };

  using DxbcUavFlags = Flags<DxbcUavFlag>;

  /**
   * \brief Tessellator output primitive
   */
  enum class DxbcTessOutputPrimitive : uint32_t {
    Undefined     = 0,
    Point         = 1,
    Line          = 2,
    TriangleCw    = 3,
    TriangleCcw   = 4,
  };

  /**
   * \brief Custom data class
   *
   * Stores which type of custom data is
   * referenced by the instruction.
   */
  enum class DxbcCustomDataClass : uint32_t {
    Comment       = 0,
    DebugInfo     = 1,
    Opaque        = 2,
    ImmConstBuf   = 3,
  };

  enum class DxbcResourceType : uint32_t {
    Typed      = 0,
    Raw        = 1,
    Structured = 2,
  };

  enum class DxbcConstantBufferAccessType : uint32_t {
    StaticallyIndexed = 0,
    DynamicallyIndexed = 1,
  };

}

/*
The MIT License (MIT)

Copyright (c) 2013 Andrew Ruef

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include <cstring>
#include <iomanip>
#include <ios>
#include <iostream>
#include <sstream>

#include <pe-parse/parse.h>

#include "vendor/argh.h"

using namespace peparse;

int printExps(void *N,
              const VA &funcAddr,
              std::uint16_t ordinal,
              const std::string &mod,
              const std::string &func,
              const std::string &fwd) {
  static_cast<void>(N);

  auto address = static_cast<std::uint32_t>(funcAddr);

  // save default formatting
  std::ios initial(nullptr);
  initial.copyfmt(std::cout);

  std::cout << "EXP #";
  std::cout << ordinal;
  std::cout << ": ";
  std::cout << mod;
  std::cout << "!";
  std::cout << func;
  std::cout << ": ";
  if (!fwd.empty()) {
    std::cout << fwd;
  } else {
    std::cout << std::showbase << std::hex << address;
  }
  std::cout << "\n";

  // restore default formatting
  std::cout.copyfmt(initial);
  return 0;
}

int printImports(void *N,
                 const VA &impAddr,
                 const std::string &modName,
                 const std::string &symName) {
  static_cast<void>(N);

  auto address = static_cast<std::uint32_t>(impAddr);

  std::cout << "0x" << std::hex << address << " " << modName << "!" << symName;
  std::cout << "\n";
  return 0;
}

int printRelocs(void *N, const VA &relocAddr, const reloc_type &type) {
  static_cast<void>(N);

  std::cout << "TYPE: ";
  switch (type) {
    case RELOC_ABSOLUTE:
      std::cout << "ABSOLUTE";
      break;
    case RELOC_HIGH:
      std::cout << "HIGH";
      break;
    case RELOC_LOW:
      std::cout << "LOW";
      break;
    case RELOC_HIGHLOW:
      std::cout << "HIGHLOW";
      break;
    case RELOC_HIGHADJ:
      std::cout << "HIGHADJ";
      break;
    case RELOC_MIPS_JMPADDR:
      std::cout << "MIPS_JMPADDR";
      break;
    case RELOC_MIPS_JMPADDR16:
      std::cout << "MIPS_JMPADD16";
      break;
    case RELOC_DIR64:
      std::cout << "DIR64";
      break;
    default:
      std::cout << "UNKNOWN";
      break;
  }

  std::cout << " VA: 0x" << std::hex << relocAddr << "\n";

  return 0;
}

int printDebugs(void *N,
                const std::uint32_t &type,
                const bounded_buffer *data) {
  static_cast<void>(N);

  std::cout << "Debug Directory Type: ";
  switch (type) {
    case 0:
      std::cout << "IMAGE_DEBUG_TYPE_UNKNOWN";
      break;
    case 1:
      std::cout << "IMAGE_DEBUG_TYPE_COFF";
      break;
    case 2:
      std::cout << "IMAGE_DEBUG_TYPE_CODEVIEW";
      break;
    case 3:
      std::cout << "IMAGE_DEBUG_TYPE_FPO";
      break;
    case 4:
      std::cout << "IMAGE_DEBUG_TYPE_MISC";
      break;
    case 5:
      std::cout << "IMAGE_DEBUG_TYPE_EXCEPTION";
      break;
    case 6:
      std::cout << "IMAGE_DEBUG_TYPE_FIXUP";
      break;
    case 7:
      std::cout << "IMAGE_DEBUG_TYPE_OMAP_TO_SRC";
      break;
    case 8:
      std::cout << "IMAGE_DEBUG_TYPE_OMAP_FROM_SRC";
      break;
    case 9:
      std::cout << "IMAGE_DEBUG_TYPE_BORLAND";
      break;
    case 10:
      std::cout << "IMAGE_DEBUG_TYPE_RESERVED10";
      break;
    case 11:
      std::cout << "IMAGE_DEBUG_TYPE_CLSID";
      break;
    case 12:
      std::cout << "IMAGE_DEBUG_TYPE_VC_FEATURE";
      break;
    case 13:
      std::cout << "IMAGE_DEBUG_TYPE_POGO";
      break;
    case 14:
      std::cout << "IMAGE_DEBUG_TYPE_ILTCG";
      break;
    case 15:
      std::cout << "IMAGE_DEBUG_TYPE_MPX";
      break;
    case 16:
      std::cout << "IMAGE_DEBUG_TYPE_REPRO";
      break;
    case 20:
      std::cout << "IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS";
      break;
    default:
      std::cout << "INVALID";
      break;
  }
  std::cout << "\n";
  std::cout << "Debug Directory Data: ";
  for (uint32_t i = 0; i < data->bufLen; i++) {
    std::cout << " 0x" << std::hex << static_cast<int>(data->buf[i]);
  }
  std::cout << "\n";

  return 0;
}

int printSymbols(void *N,
                 const std::string &strName,
                 const uint32_t &value,
                 const int16_t &sectionNumber,
                 const uint16_t &type,
                 const uint8_t &storageClass,
                 const uint8_t &numberOfAuxSymbols) {
  static_cast<void>(N);

  std::cout << "Symbol Name: " << strName << "\n";
  std::cout << "Symbol Value: 0x" << std::hex << value << "\n";

  std::cout << "Symbol Section Number: ";
  switch (sectionNumber) {
    case IMAGE_SYM_UNDEFINED:
      std::cout << "UNDEFINED";
      break;
    case IMAGE_SYM_ABSOLUTE:
      std::cout << "ABSOLUTE";
      break;
    case IMAGE_SYM_DEBUG:
      std::cout << "DEBUG";
      break;
    default:
      std::cout << sectionNumber;
      break;
  }
  std::cout << "\n";

  std::cout << "Symbol Type: ";
  switch (type) {
    case IMAGE_SYM_TYPE_NULL:
      std::cout << "NULL";
      break;
    case IMAGE_SYM_TYPE_VOID:
      std::cout << "VOID";
      break;
    case IMAGE_SYM_TYPE_CHAR:
      std::cout << "CHAR";
      break;
    case IMAGE_SYM_TYPE_SHORT:
      std::cout << "SHORT";
      break;
    case IMAGE_SYM_TYPE_INT:
      std::cout << "INT";
      break;
    case IMAGE_SYM_TYPE_LONG:
      std::cout << "LONG";
      break;
    case IMAGE_SYM_TYPE_FLOAT:
      std::cout << "FLOAT";
      break;
    case IMAGE_SYM_TYPE_DOUBLE:
      std::cout << "DOUBLE";
      break;
    case IMAGE_SYM_TYPE_STRUCT:
      std::cout << "STRUCT";
      break;
    case IMAGE_SYM_TYPE_UNION:
      std::cout << "UNION";
      break;
    case IMAGE_SYM_TYPE_ENUM:
      std::cout << "ENUM";
      break;
    case IMAGE_SYM_TYPE_MOE:
      std::cout << "IMAGE_SYM_TYPE_MOE";
      break;
    case IMAGE_SYM_TYPE_BYTE:
      std::cout << "BYTE";
      break;
    case IMAGE_SYM_TYPE_WORD:
      std::cout << "WORD";
      break;
    case IMAGE_SYM_TYPE_UINT:
      std::cout << "UINT";
      break;
    case IMAGE_SYM_TYPE_DWORD:
      std::cout << "DWORD";
      break;
    default:
      std::cout << "UNKNOWN";
      break;
  }
  std::cout << "\n";

  std::cout << "Symbol Storage Class: ";
  switch (storageClass) {
    case IMAGE_SYM_CLASS_END_OF_FUNCTION:
      std::cout << "FUNCTION";
      break;
    case IMAGE_SYM_CLASS_NULL:
      std::cout << "NULL";
      break;
    case IMAGE_SYM_CLASS_AUTOMATIC:
      std::cout << "AUTOMATIC";
      break;
    case IMAGE_SYM_CLASS_EXTERNAL:
      std::cout << "EXTERNAL";
      break;
    case IMAGE_SYM_CLASS_STATIC:
      std::cout << "STATIC";
      break;
    case IMAGE_SYM_CLASS_REGISTER:
      std::cout << "REGISTER";
      break;
    case IMAGE_SYM_CLASS_EXTERNAL_DEF:
      std::cout << "EXTERNAL DEF";
      break;
    case IMAGE_SYM_CLASS_LABEL:
      std::cout << "LABEL";
      break;
    case IMAGE_SYM_CLASS_UNDEFINED_LABEL:
      std::cout << "UNDEFINED LABEL";
      break;
    case IMAGE_SYM_CLASS_MEMBER_OF_STRUCT:
      std::cout << "MEMBER OF STRUCT";
      break;
    default:
      std::cout << "UNKNOWN";
      break;
  }
  std::cout << "\n";

  std::cout << "Symbol Number of Aux Symbols: "
            << static_cast<std::uint32_t>(numberOfAuxSymbols) << "\n";

  return 0;
}

int printRich(void *N, const rich_entry &r) {
  static_cast<void>(N);
  std::cout << std::dec;
  std::cout << std::setw(10) << "ProdId:" << std::setw(7) << r.ProductId;
  std::cout << std::setw(10) << "Build:" << std::setw(7) << r.BuildNumber;
  std::cout << std::setw(10) << "Name:" << std::setw(40)
            << GetRichProductName(r.BuildNumber) << " "
            << GetRichObjectType(r.ProductId);
  std::cout << std::setw(10) << "Count:" << std::setw(7) << r.Count << "\n";
  return 0;
}

int printRsrc(void *N, const resource &r) {
  static_cast<void>(N);

  if (r.type_str.length())
    std::cout << "Type (string): " << r.type_str << "\n";
  else
    std::cout << "Type: 0x" << std::hex << r.type << "\n";

  if (r.name_str.length())
    std::cout << "Name (string): " << r.name_str << "\n";
  else
    std::cout << "Name: 0x" << std::hex << r.name << "\n";

  if (r.lang_str.length())
    std::cout << "Lang (string): " << r.lang_str << "\n";
  else
    std::cout << "Lang: 0x" << std::hex << r.lang << "\n";

  std::cout << "Codepage: 0x" << std::hex << r.codepage << "\n";
  std::cout << "RVA: " << std::dec << r.RVA << "\n";
  std::cout << "Size: " << std::dec << r.size << "\n";
  return 0;
}

int printSecs(void *N,
              const VA &secBase,
              const std::string &secName,
              const image_section_header &s,
              const bounded_buffer *data) {
  static_cast<void>(N);
  static_cast<void>(s);

  std::cout << "Sec Name: " << secName << "\n";
  std::cout << "Sec Base: 0x" << std::hex << secBase << "\n";
  if (data)
    std::cout << "Sec Size: " << std::dec << data->bufLen << "\n";
  else
    std::cout << "Sec Size: 0"
              << "\n";
  return 0;
}

#define DUMP_FIELD(x)           \
  std::cout << "" #x << ": 0x"; \
  std::cout << std::hex << static_cast<std::uint64_t>(p->peHeader.x) << "\n";
#define DUMP_DEC_FIELD(x)     \
  std::cout << "" #x << ": "; \
  std::cout << std::dec << static_cast<std::uint64_t>(p->peHeader.x) << "\n";
#define DUMP_BOOL_FIELD(x)    \
  std::cout << "" #x << ": "; \
  std::cout << std::boolalpha << static_cast<bool>(p->peHeader.x) << "\n";

int main(int argc, char *argv[]) {

  argh::parser cmdl(argv);

  if (cmdl[{"-h", "--help"}] || argc <= 1) {
    std::cout << "dump-pe utility from Trail of Bits\n";
    std::cout << "Repository: https://github.com/trailofbits/pe-parse\n\n";
    std::cout << "Usage:\n\tdump-pe /path/to/executable.exe\n";
    return 0;
  } else if (cmdl[{"-v", "--version"}]) {
    std::cout << "dump-pe (pe-parse) version " << PEPARSE_VERSION << "\n";
    return 0;
  }

  parsed_pe *p = ParsePEFromFile(cmdl[1].c_str());

  if (p == nullptr) {
    std::cout << "Error: " << GetPEErr() << " (" << GetPEErrString() << ")"
              << "\n";
    std::cout << "Location: " << GetPEErrLoc() << "\n";
    return 1;
  }

  if (p != NULL) {
    // Print DOS header
    DUMP_FIELD(dos.e_magic);
    DUMP_FIELD(dos.e_cp);
    DUMP_FIELD(dos.e_crlc);
    DUMP_FIELD(dos.e_cparhdr);
    DUMP_FIELD(dos.e_minalloc);
    DUMP_FIELD(dos.e_maxalloc);
    DUMP_FIELD(dos.e_ss);
    DUMP_FIELD(dos.e_sp);
    DUMP_FIELD(dos.e_csum);
    DUMP_FIELD(dos.e_ip);
    DUMP_FIELD(dos.e_cs);
    DUMP_FIELD(dos.e_lfarlc);
    DUMP_FIELD(dos.e_ovno);
    DUMP_FIELD(dos.e_res[0]);
    DUMP_FIELD(dos.e_res[1]);
    DUMP_FIELD(dos.e_res[2]);
    DUMP_FIELD(dos.e_res[3]);
    DUMP_FIELD(dos.e_oemid);
    DUMP_FIELD(dos.e_oeminfo);
    DUMP_FIELD(dos.e_res2[0]);
    DUMP_FIELD(dos.e_res2[1]);
    DUMP_FIELD(dos.e_res2[2]);
    DUMP_FIELD(dos.e_res2[3]);
    DUMP_FIELD(dos.e_res2[4]);
    DUMP_FIELD(dos.e_res2[5]);
    DUMP_FIELD(dos.e_res2[6]);
    DUMP_FIELD(dos.e_res2[7]);
    DUMP_FIELD(dos.e_res2[8]);
    DUMP_FIELD(dos.e_res2[9]);
    DUMP_FIELD(dos.e_lfanew);
    // Print Rich header info
    DUMP_BOOL_FIELD(rich.isPresent);
    if (p->peHeader.rich.isPresent) {
      DUMP_FIELD(rich.DecryptionKey);
      DUMP_FIELD(rich.Checksum);
      DUMP_BOOL_FIELD(rich.isValid);
      IterRich(p, printRich, NULL);
    }
    // print out some things
    DUMP_FIELD(nt.Signature);
    DUMP_FIELD(nt.FileHeader.Machine);
    DUMP_FIELD(nt.FileHeader.NumberOfSections);
    DUMP_DEC_FIELD(nt.FileHeader.TimeDateStamp);
    DUMP_FIELD(nt.FileHeader.PointerToSymbolTable);
    DUMP_DEC_FIELD(nt.FileHeader.NumberOfSymbols);
    DUMP_FIELD(nt.FileHeader.SizeOfOptionalHeader);
    DUMP_FIELD(nt.FileHeader.Characteristics);
    if (p->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) {
      DUMP_FIELD(nt.OptionalHeader.Magic);
      DUMP_DEC_FIELD(nt.OptionalHeader.MajorLinkerVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader.MinorLinkerVersion);
      DUMP_FIELD(nt.OptionalHeader.SizeOfCode);
      DUMP_FIELD(nt.OptionalHeader.SizeOfInitializedData);
      DUMP_FIELD(nt.OptionalHeader.SizeOfUninitializedData);
      DUMP_FIELD(nt.OptionalHeader.AddressOfEntryPoint);
      DUMP_FIELD(nt.OptionalHeader.BaseOfCode);
      DUMP_FIELD(nt.OptionalHeader.BaseOfData);
      DUMP_FIELD(nt.OptionalHeader.ImageBase);
      DUMP_FIELD(nt.OptionalHeader.SectionAlignment);
      DUMP_FIELD(nt.OptionalHeader.FileAlignment);
      DUMP_DEC_FIELD(nt.OptionalHeader.MajorOperatingSystemVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader.MinorOperatingSystemVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader.Win32VersionValue);
      DUMP_FIELD(nt.OptionalHeader.SizeOfImage);
      DUMP_FIELD(nt.OptionalHeader.SizeOfHeaders);
      DUMP_FIELD(nt.OptionalHeader.CheckSum);
      DUMP_FIELD(nt.OptionalHeader.Subsystem);
      DUMP_FIELD(nt.OptionalHeader.DllCharacteristics);
      DUMP_FIELD(nt.OptionalHeader.SizeOfStackReserve);
      DUMP_FIELD(nt.OptionalHeader.SizeOfStackCommit);
      DUMP_FIELD(nt.OptionalHeader.SizeOfHeapReserve);
      DUMP_FIELD(nt.OptionalHeader.SizeOfHeapCommit);
      DUMP_FIELD(nt.OptionalHeader.LoaderFlags);
      DUMP_DEC_FIELD(nt.OptionalHeader.NumberOfRvaAndSizes);
    } else {
      DUMP_FIELD(nt.OptionalHeader64.Magic);
      DUMP_DEC_FIELD(nt.OptionalHeader64.MajorLinkerVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader64.MinorLinkerVersion);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfCode);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfInitializedData);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfUninitializedData);
      DUMP_FIELD(nt.OptionalHeader64.AddressOfEntryPoint);
      DUMP_FIELD(nt.OptionalHeader64.BaseOfCode);
      DUMP_FIELD(nt.OptionalHeader64.ImageBase);
      DUMP_FIELD(nt.OptionalHeader64.SectionAlignment);
      DUMP_FIELD(nt.OptionalHeader64.FileAlignment);
      DUMP_DEC_FIELD(nt.OptionalHeader64.MajorOperatingSystemVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader64.MinorOperatingSystemVersion);
      DUMP_DEC_FIELD(nt.OptionalHeader64.Win32VersionValue);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfImage);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfHeaders);
      DUMP_FIELD(nt.OptionalHeader64.CheckSum);
      DUMP_FIELD(nt.OptionalHeader64.Subsystem);
      DUMP_FIELD(nt.OptionalHeader64.DllCharacteristics);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfStackReserve);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfStackCommit);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfHeapReserve);
      DUMP_FIELD(nt.OptionalHeader64.SizeOfHeapCommit);
      DUMP_FIELD(nt.OptionalHeader64.LoaderFlags);
      DUMP_DEC_FIELD(nt.OptionalHeader64.NumberOfRvaAndSizes);
    }

#undef DUMP_FIELD
#undef DUMP_DEC_FIELD

    std::cout << "Imports: "
              << "\n";
    IterImpVAString(p, printImports, NULL);
    std::cout << "Relocations: "
              << "\n";
    IterRelocs(p, printRelocs, NULL);
    std::cout << "Debug Directories: "
              << "\n";
    IterDebugs(p, printDebugs, NULL);
    std::cout << "Symbols (symbol table): "
              << "\n";
    IterSymbols(p, printSymbols, NULL);
    std::cout << "Sections: "
              << "\n";
    IterSec(p, printSecs, NULL);
    std::cout << "Exports: "
              << "\n";
    IterExpFull(p, printExps, NULL);

    // read the first 8 bytes from the entry point and print them
    VA entryPoint;
    if (GetEntryPoint(p, entryPoint)) {
      std::cout << "First 8 bytes from entry point (0x";
      std::cout << std::hex << entryPoint << "):"
                << "\n";
      for (std::size_t i = 0; i < 8; i++) {
        std::uint8_t b;
        if (!ReadByteAtVA(p, i + entryPoint, b)) {
          std::cout << " ERR";
        } else {
          std::cout << " 0x" << std::hex << static_cast<int>(b);
        }
      }

      std::cout << "\n";
    }

    std::cout << "Resources: "
              << "\n";
    IterRsrc(p, printRsrc, NULL);

    DestructParsedPE(p);

    return 0;
  }
}

class DxbcReader {
  public:
    template<typename T>
    auto readEnum() {
      using Tx = std::underlying_type_t<T>;
      return static_cast<T>(this->readNum<Tx>());
    }
    DxbcTag readTag() {
    DxbcTag tag;
    this->read(&tag, 4);
    return tag;
  }
    std::string readString() {
    std::string result;

    while (m_data[m_pos] != '\0')
      result.push_back(m_data[m_pos++]);

    m_pos++;
    return result;
  }
    void read(void* dst, size_t n) {
    if (m_pos + n > m_size)
      throw DxvkError("DxbcReader::read: Unexpected end of file");
    std::memcpy(dst, m_data + m_pos, n);
    m_pos += n;
  }
    void skip(size_t n) {
    if (m_pos + n > m_size)
      throw DxvkError("DxbcReader::skip: Unexpected end of file");
    m_pos += n;
  }
    DxbcReader clone(size_t pos) const {
    if (pos > m_size)
      throw DxvkError("DxbcReader::clone: Invalid offset");
    return DxbcReader(m_data + pos, m_size - pos);
  }
    DxbcReader resize(size_t size) const {
    if (size > m_size)
      throw DxvkError("DxbcReader::resize: Invalid size");
    return DxbcReader(m_data, size, m_pos);
  }
    void store(std::ostream&& stream) const {
    stream.write(m_data, m_size);
  }
  private:
    const char* m_data = nullptr;
    size_t      m_size = 0;
    size_t      m_pos  = 0;
    template<typename T>
    T readNum() {
      T result;
      this->read(&result, sizeof(result));
      return result;
    }
};

#include "extract/trans.hpp"

#include <thirdparty/spirv.hpp>

#include <dxbc_modinfo.h>
#include <dxbc_module.h>
#include <dxbc_reader.h>

#include <cstdint>
#include <cstddef>
#include <algorithm>
#include <vector>

using namespace Extract;

struct BindingOffsets {
  uint32_t bindingIndex{};
  uint32_t bindingOffset{};
  uint32_t setIndex{};
  uint32_t setOffset{};
};

std::vector<uint8_t> Extract::translateShader(std::vector<uint8_t> bytecode) {
    // compile the shader
    dxvk::DxbcReader reader(reinterpret_cast<const char*>(bytecode.data()), bytecode.size());
    dxvk::DxbcModule module(reader);
    const dxvk::DxbcModuleInfo info{};
    auto code = module.compile(info, "CS");

    // find all bindings
    std::vector<BindingOffsets> bindingOffsets;
    std::vector<uint32_t> varIds;
    for (auto ins : code) {
        if (ins.opCode() == spv::OpDecorate) {
            if (ins.arg(2) == spv::DecorationBinding) {
                const uint32_t varId = ins.arg(1);
                bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
                bindingOffsets[varId].bindingIndex = ins.arg(3);
                bindingOffsets[varId].bindingOffset = ins.offset() + 3;
                varIds.push_back(varId);
            }

            if (ins.arg(2) == spv::DecorationDescriptorSet) {
                const uint32_t varId = ins.arg(1);
                bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
                bindingOffsets[varId].setIndex = ins.arg(3);
                bindingOffsets[varId].setOffset = ins.offset() + 3;
            }
        }

        if (ins.opCode() == spv::OpFunction)
            break;
    }

    std::vector<BindingOffsets> validBindings;
    for (const auto varId : varIds) {
        auto info = bindingOffsets[varId];

        if (info.bindingOffset)
            validBindings.push_back(info);
    }

    // patch binding offset
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
        for (size_t i = 0; i < validBindings.size(); i++)
            code.data()[validBindings.at(i).bindingOffset] // NOLINT
                = static_cast<uint8_t>(i);
    #pragma clang diagnostic pop

    // return the new bytecode
    std::vector<uint8_t> spirvBytecode(code.size());
    std::copy_n(reinterpret_cast<uint8_t*>(code.data()),
        code.size(), spirvBytecode.data());
    return spirvBytecode;
}

#pragma once

#include "spirv_include.h"

namespace dxvk {

  /**
   * \brief SPIR-V instruction
   *
   * Helps parsing a single instruction, providing
   * access to the op code, instruction length and
   * instruction arguments.
   */
  class SpirvInstruction {

  public:

    SpirvInstruction() { }
    SpirvInstruction(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code(code), m_offset(offset), m_length(length) { }

    /**
     * \brief SPIR-V Op code
     * \returns The op code
     */
    spv::Op opCode() const {
      return static_cast<spv::Op>(
        this->arg(0) & spv::OpCodeMask);
    }

    /**
     * \brief Instruction length
     * \returns Number of DWORDs
     */
    uint32_t length() const {
      return this->arg(0) >> spv::WordCountShift;
    }

    /**
     * \brief Instruction offset
     * \returns Offset in DWORDs
     */
    uint32_t offset() const {
      return m_offset;
    }

    /**
     * \brief Argument value
     *
     * Retrieves an argument DWORD. Note that some instructions
     * take 64-bit arguments which require more than one DWORD.
     * Arguments start at index 1. Calling this method with an
     * argument ID of 0 will return the opcode token.
     * \param [in] idx Argument index, starting at 1
     * \returns The argument value
     */
    uint32_t arg(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? m_code[index] : 0;
    }

    /**
     * \brief Argument string
     *
     * Retrieves a pointer to a UTF-8-encoded string.
     * \param [in] idx Argument index, starting at 1
     * \returns Pointer to the literal string
     */
    const char* chr(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? reinterpret_cast<const char*>(&m_code[index]) : nullptr;
    }

    /**
     * \brief Changes the value of an argument
     *
     * \param [in] idx Argument index, starting at 1
     * \param [in] word New argument word
     */
    void setArg(uint32_t idx, uint32_t word) const {
      if (m_offset + idx < m_length)
        m_code[m_offset + idx] = word;
    }

  private:

    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;

  };

  /**
   * \brief SPIR-V instruction iterator
   *
   * Convenient iterator that can be used
   * to process raw SPIR-V shader code.
   */
  class SpirvInstructionIterator {

  public:

    SpirvInstructionIterator() { }
    SpirvInstructionIterator(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code  (length != 0 ? code   : nullptr),
      m_offset(length != 0 ? offset : 0),
      m_length(length) {
      if ((length >= 5) && (offset == 0) && (m_code[0] == spv::MagicNumber))
        this->advance(5);
    }

    SpirvInstructionIterator& operator ++ () {
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return *this;
    }

    SpirvInstructionIterator operator ++ (int) {
      SpirvInstructionIterator result = *this;
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return result;
    }

    SpirvInstruction operator * () const {
      return SpirvInstruction(m_code, m_offset, m_length);
    }

    bool operator == (const SpirvInstructionIterator& other) const {
      return this->m_code   == other.m_code
          && this->m_offset == other.m_offset
          && this->m_length == other.m_length;
    }

    bool operator != (const SpirvInstructionIterator& other) const {
      return this->m_code   != other.m_code
          || this->m_offset != other.m_offset
          || this->m_length != other.m_length;
    }

  private:

    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;

    void advance(uint32_t n) {
      if (m_offset + n < m_length) {
        m_offset += n;
      } else {
        m_code   = nullptr;
        m_offset = 0;
        m_length = 0;
      }
    }

  };

}

#include "utils/utils.hpp"
#include "common/exception.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>
#include <sys/types.h>
#include <string.h> // NOLINT
#include <unistd.h>

#include <unordered_map>
#include <algorithm>
#include <optional>
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <utility>
#include <fstream>
#include <string>
#include <vector>
#include <array>

using namespace Utils;

std::pair<uint32_t, VkQueue> Utils::findQueue(VkDevice device, VkPhysicalDevice physicalDevice,
        VkDeviceCreateInfo* desc, VkQueueFlags flags) {
    std::vector<VkDeviceQueueCreateInfo> enabledQueues(desc->queueCreateInfoCount);
    std::copy_n(desc->pQueueCreateInfos, enabledQueues.size(), enabledQueues.data());

    uint32_t familyCount{};
    Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr);
    std::vector<VkQueueFamilyProperties> families(familyCount);
    Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount,
        families.data());

    std::optional<uint32_t> idx;
    for (const auto& queueInfo : enabledQueues) {
        if ((queueInfo.queueFamilyIndex < families.size()) &&
            (families[queueInfo.queueFamilyIndex].queueFlags & flags)) {
            idx = queueInfo.queueFamilyIndex;
            break;
        }
    }
    if (!idx.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No suitable queue found");

    VkQueue queue{};
    Layer::ovkGetDeviceQueue(device, *idx, 0, &queue);

    auto res = Layer::ovkSetDeviceLoaderData(device, queue);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to set device loader data for queue");

    return { *idx, queue };
}

uint64_t Utils::getDeviceUUID(VkPhysicalDevice physicalDevice) {
    VkPhysicalDeviceProperties properties{};
    Layer::ovkGetPhysicalDeviceProperties(physicalDevice, &properties);

    return static_cast<uint64_t>(properties.vendorID) << 32 | properties.deviceID;
}

uint32_t Utils::getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) {
    VkSurfaceCapabilitiesKHR capabilities{};
    auto res = Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice,
        surface, &capabilities);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to get surface capabilities");
    if (capabilities.maxImageCount == 0)
        return 999; // :3
    return capabilities.maxImageCount;
}

std::vector<const char*> Utils::addExtensions(const char* const* extensions, size_t count,
        const std::vector<const char*>& requiredExtensions) {
    std::vector<const char*> ext(count);
    std::copy_n(extensions, count, ext.data());

    for (const auto& e : requiredExtensions) {
        auto it = std::ranges::find_if(ext,
            [e](const char* extName) {
                return std::string(extName) == std::string(e);
            });
        if (it == ext.end())
            ext.push_back(e);
    }

    return ext;
}

void Utils::copyImage(VkCommandBuffer buf,
        VkImage src, VkImage dst,
        uint32_t width, uint32_t height,
        VkPipelineStageFlags pre, VkPipelineStageFlags post,
        bool makeSrcPresentable, bool makeDstPresentable) {
    const VkImageMemoryBarrier srcBarrier{
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
        .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
        .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        .image = src,
        .subresourceRange = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .levelCount = 1,
            .layerCount = 1
        }
    };
    const VkImageMemoryBarrier dstBarrier{
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
        .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .image = dst,
        .subresourceRange = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .levelCount = 1,
            .layerCount = 1
        }
    };
    const std::vector<VkImageMemoryBarrier> barriers = { srcBarrier, dstBarrier };
    Layer::ovkCmdPipelineBarrier(buf,
        pre, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
        0, nullptr, 0, nullptr,
        static_cast<uint32_t>(barriers.size()), barriers.data());

    const VkImageBlit imageBlit{
        .srcSubresource = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .layerCount = 1
        },
        .srcOffsets = {
            { 0, 0, 0 },
            { static_cast<int32_t>(width), static_cast<int32_t>(height), 1 }
        },
        .dstSubresource = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .layerCount = 1
        },
        .dstOffsets = {
            { 0, 0, 0 },
            { static_cast<int32_t>(width), static_cast<int32_t>(height), 1 }
        }
    };
    Layer::ovkCmdBlitImage(
        buf,
        src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        1, &imageBlit,
        VK_FILTER_NEAREST
    );

    if (makeSrcPresentable) {
        const VkImageMemoryBarrier presentBarrier{
            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
            .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
            .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
            .image = src,
            .subresourceRange = {
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .levelCount = 1,
                .layerCount = 1
            }
        };
        Layer::ovkCmdPipelineBarrier(buf,
            VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0,
            0, nullptr, 0, nullptr,
            1, &presentBarrier);
    }

    if (makeDstPresentable) {
        const VkImageMemoryBarrier presentBarrier{
            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
            .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
            .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
            .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
            .image = dst,
            .subresourceRange = {
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .levelCount = 1,
                .layerCount = 1
            }
        };
        Layer::ovkCmdPipelineBarrier(buf,
            VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0,
            0, nullptr, 0, nullptr,
            1, &presentBarrier);
    }
}

namespace {
    auto& logCounts() {
        static std::unordered_map<std::string, size_t> map;
        return map;
    }
}

void Utils::logLimitN(const std::string& id, size_t n, const std::string& message) {
    auto& count = logCounts()[id];
    if (count <= n)
        std::cerr << "lsfg-vk: " << message << '\n';
    if (count == n)
        std::cerr << "(above message has been repeated " << n << " times, suppressing further)\n";
    count++;
}

void Utils::resetLimitN(const std::string& id) noexcept {
    logCounts().erase(id);
}

std::pair<std::string, std::string> Utils::getProcessName() {
    const char* process_name = std::getenv("LSFG_PROCESS");
    if (process_name && *process_name != '\0')
        return { process_name, process_name };

    const char* benchmark_flag = std::getenv("LSFG_BENCHMARK");
    if (benchmark_flag)
        return { "benchmark", "benchmark" };
    std::array<char, 4096> exe{};

    const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1);
    if (exe_len <= 0)
        return { "Unknown Process", "unknown" };
    exe.at(static_cast<size_t>(exe_len)) = '\0';

    std::ifstream comm_file("/proc/self/comm");
    if (!comm_file.is_open())
        return { std::string(exe.data()), "unknown" };
    std::array<char, 257> comm{};
    comm_file.read(comm.data(), 256);
    comm.at(static_cast<size_t>(comm_file.gcount())) = '\0';
    std::string comm_str(comm.data());
    if (comm_str.back() == '\n')
        comm_str.pop_back();

    return{ std::string(exe.data()), comm_str };
}

std::string Utils::getConfigFile() {
    const char* configFile = std::getenv("LSFG_CONFIG");
    if (configFile && *configFile != '\0')
        return{configFile};
    const char* xdgPath = std::getenv("XDG_CONFIG_HOME");
    if (xdgPath && *xdgPath != '\0')
        return std::string(xdgPath) + "/lsfg-vk/conf.toml";
    const char* homePath = std::getenv("HOME");
    if (homePath && *homePath != '\0')
        return std::string(homePath) + "/.config/lsfg-vk/conf.toml";
    return "/etc/lsfg-vk/conf.toml";
}

#pragma once

#include <vulkan/vulkan.h>

#include <cstdint>

#include "dxvk_hash.h"

#include "util_math.h"
#include "util_bit.h"
#include "util_flags.h"

namespace dxvk {

  class DxvkDevice;
  class DxvkPipelineManager;

  /**
   * \brief Order-invariant atomic access operation
   *
   * Information used to optimize barriers when a resource
   * is accessed exlusively via order-invariant stores.
   */
  struct DxvkAccessOp {
    enum OpType : uint16_t {
      None      = 0x0u,
      Or        = 0x1u,
      And       = 0x2u,
      Xor       = 0x3u,
      Add       = 0x4u,
      IMin      = 0x5u,
      IMax      = 0x6u,
      UMin      = 0x7u,
      UMax      = 0x8u,

      StoreF    = 0xdu,
      StoreUi   = 0xeu,
      StoreSi   = 0xfu,
    };

    DxvkAccessOp() = default;
    DxvkAccessOp(OpType t)
    : op(uint16_t(t)) { }

    DxvkAccessOp(OpType t, uint16_t constant)
    : op(uint16_t(t) | (constant << 4u)) { }

    uint16_t op = 0u;

    bool operator == (const DxvkAccessOp& t) const { return op == t.op; }
    bool operator != (const DxvkAccessOp& t) const { return op != t.op; }

    template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
    explicit operator T() const { return op; }
  };

  static_assert(sizeof(DxvkAccessOp) == sizeof(uint16_t));

  /**
   * \brief Binding info
   *
   * Stores metadata for a single binding in
   * a given shader, or for the whole pipeline.
   */
  struct DxvkBindingInfo {
    VkDescriptorType      descriptorType  = VK_DESCRIPTOR_TYPE_MAX_ENUM;        ///< Vulkan descriptor type
    uint32_t              resourceBinding = 0u;                                 ///< API binding slot for the resource
    VkImageViewType       viewType        = VK_IMAGE_VIEW_TYPE_MAX_ENUM;        ///< Image view type
    VkShaderStageFlagBits stage           = VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM; ///< Shader stage
    VkAccessFlags         access          = 0u;                                 ///< Access mask for the resource
    DxvkAccessOp          accessOp        = DxvkAccessOp::None;                 ///< Order-invariant store type, if any
    bool                  uboSet          = false;                              ///< Whether to include this in the UBO set
    bool                  isMultisampled  = false;                              ///< Multisampled binding

    /**
     * \brief Computes descriptor set index for the given binding
     *
     * This is determines based on the shader stages that use the binding.
     * \returns Descriptor set index
     */
    uint32_t computeSetIndex() const;

    /**
     * \brief Numeric value of the binding
     *
     * Used when sorting bindings.
     * \returns Numeric value
     */
    uint32_t value() const;

    /**
     * \brief Checks for equality
     *
     * \param [in] other Binding to compare to
     * \returns \c true if both bindings are equal
     */
    bool eq(const DxvkBindingInfo& other) const;

    /**
     * \brief Hashes binding info
     * \returns Binding hash
     */
    size_t hash() const;

  };

}

#pragma once

#include "dxbc_options.h"

namespace dxvk {

  /**
   * \brief Tessellation info
   *
   * Stores the maximum tessellation factor
   * to export from tessellation shaders.
   */
  struct DxbcTessInfo {
    float maxTessFactor;
  };

  /**
   * \brief Xfb capture entry
   *
   * Stores an output variable to capture,
   * as well as the buffer to write it to.
   */
  struct DxbcXfbEntry {
    const char* semanticName;
    uint32_t    semanticIndex;
    uint32_t    componentIndex;
    uint32_t    componentCount;
    uint32_t    streamId;
    uint32_t    bufferId;
    uint32_t    offset;
  };

  /**
   * \brief Xfb info
   *
   * Stores capture entries and output buffer
   * strides. This structure must only be
   * defined if \c entryCount is non-zero.
   */
  struct DxbcXfbInfo {
    uint32_t      entryCount;
    DxbcXfbEntry  entries[128];
    uint32_t      strides[4];
    int32_t       rasterizedStream;
  };

  /**
   * \brief Shader module info
   *
   * Stores information which may affect shader compilation.
   * This data can be supplied by the client API implementation.
   */
  struct DxbcModuleInfo {
    DxbcOptions   options;
    DxbcTessInfo* tess;
    DxbcXfbInfo*  xfb;
  };

}

#pragma once

#include "dxbc_include.h"

namespace dxvk {

  /**
   * \brief Four-character tag
   *
   * Used to identify chunks in the
   * compiled DXBC file by name.
   */
  class DxbcTag {

  public:

    DxbcTag() {
      for (size_t i = 0; i < 4; i++)
        m_chars[i] = '\0';
    }

    DxbcTag(const char* tag) {
      for (size_t i = 0; i < 4; i++)
        m_chars[i] = tag[i];
    }

    bool operator == (const DxbcTag& other) const {
      bool result = true;
      for (size_t i = 0; i < 4; i++)
        result &= m_chars[i] == other.m_chars[i];
      return result;
    }

    bool operator != (const DxbcTag& other) const {
      return !this->operator == (other);
    }

    const char* operator & () const { return m_chars; }
          char* operator & ()       { return m_chars; }

  private:

    char m_chars[4];

  };

}

class DxbcShex {
  public:
    DxbcShex(DxbcReader reader) {
    // The shader version and type are stored in a 32-bit unit,
    // where the first byte contains the major and minor version
    // numbers, and the high word contains the program type.
    reader.skip(2);
    auto pType    = reader.readEnum<DxbcProgramType>();
    m_programInfo = DxbcProgramInfo(pType);

    // Read the actual shader code as an array of DWORDs.
    auto codeLength = reader.readu32() - 2;
    m_code.resize(codeLength);
    reader.read(m_code.data(), codeLength * sizeof(uint32_t));
  }
    ~DxbcShex() {

  }
  private:
    DxbcProgramInfo       m_programInfo;
    std::vector<uint32_t> m_code;
};

/*
 * Copyright (c) 2013, Wesley Shields <wxs@atarininja.org>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <Python.h>
#include <pe-parse/parse.h>
#include <structmember.h>

using namespace peparse;

/* NOTE(ww): These don't necessarily have to be the same, but currently are.
 */
#define PEPY_VERSION PEPARSE_VERSION

/* These are used to across multiple objects. */
#define PEPY_OBJECT_GET(OBJ, ATTR)                                          \
  static PyObject *pepy_##OBJ##_get_##ATTR(PyObject *self, void *closure) { \
    Py_INCREF(((pepy_##OBJ *) self)->ATTR);                                 \
    return ((pepy_##OBJ *) self)->ATTR;                                     \
  }

#define OBJECTGETTER(OBJ, ATTR, DOC)                         \
  {                                                          \
    (char *) #ATTR, (getter) pepy_##OBJ##_get_##ATTR,        \
        (setter) pepy_attr_not_writable, (char *) #DOC, NULL \
  }

/* 'OPTIONAL' references the fact that these are from the Optional Header */
#define OBJECTGETTER_OPTIONAL(ATTR, DOC)                      \
  {                                                           \
    (char *) #ATTR, (getter) pepy_parsed_get_optional_##ATTR, \
        (setter) pepy_attr_not_writable, (char *) #DOC, NULL  \
  }

static PyObject *pepy_error;

struct pepy {
  PyObject_HEAD
};

struct pepy_parsed {
  PyObject_HEAD parsed_pe *pe;
};

struct pepy_section {
  PyObject_HEAD PyObject *name;
  PyObject *base;
  PyObject *length;
  PyObject *virtaddr;
  PyObject *virtsize;
  PyObject *numrelocs;
  PyObject *numlinenums;
  PyObject *characteristics;
  PyObject *data;
};

struct pepy_resource {
  PyObject_HEAD PyObject *type_str;
  PyObject *name_str;
  PyObject *lang_str;
  PyObject *type;
  PyObject *name;
  PyObject *lang;
  PyObject *codepage;
  PyObject *RVA;
  PyObject *size;
  PyObject *data;
};

struct pepy_import {
  PyObject_HEAD PyObject *name;
  PyObject *sym;
  PyObject *addr;
};

struct pepy_export {
  PyObject_HEAD PyObject *mod;
  PyObject *func;
  PyObject *addr;
};

struct pepy_relocation {
  PyObject_HEAD PyObject *type;
  PyObject *addr;
};

/* None of the attributes in these objects are writable. */
static int
pepy_attr_not_writable(PyObject *self, PyObject *value, void *closure) {
  PyErr_SetString(PyExc_TypeError, "Attribute not writable.");
  return -1;
}

static PyObject *
pepy_import_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_import *self;

  self = (pepy_import *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int pepy_import_init(pepy_import *self, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple(
          args, "OOO:pepy_import_init", &self->name, &self->sym, &self->addr))
    return -1;
  return 0;
}

static void pepy_import_dealloc(pepy_import *self) {
  Py_XDECREF(self->name);
  Py_XDECREF(self->sym);
  Py_XDECREF(self->addr);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

PEPY_OBJECT_GET(import, name);
PEPY_OBJECT_GET(import, sym);
PEPY_OBJECT_GET(import, addr);

static PyGetSetDef pepy_import_getseters[] = {
    OBJECTGETTER(import, name, "Name"),
    OBJECTGETTER(import, sym, "Symbol"),
    OBJECTGETTER(import, addr, "Address"),
    {NULL}};

static PyTypeObject pepy_import_type = {
    PyVarObject_HEAD_INIT(NULL, 0)    /* ob_size */
    "pepy.import",                    /* tp_name */
    sizeof(pepy_import),              /* tp_basicsize */
    0,                                /* tp_itemsize */
    (destructor) pepy_import_dealloc, /* tp_dealloc */
    0,                                /* tp_print */
    0,                                /* tp_getattr */
    0,                                /* tp_setattr */
    0,                                /* tp_compare */
    0,                                /* tp_repr */
    0,                                /* tp_as_number */
    0,                                /* tp_as_sequence */
    0,                                /* tp_as_mapping */
    0,                                /* tp_hash */
    0,                                /* tp_call */
    0,                                /* tp_str */
    0,                                /* tp_getattro */
    0,                                /* tp_setattro */
    0,                                /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,               /* tp_flags */
    "pepy import object",             /* tp_doc */
    0,                                /* tp_traverse */
    0,                                /* tp_clear */
    0,                                /* tp_richcompare */
    0,                                /* tp_weaklistoffset */
    0,                                /* tp_iter */
    0,                                /* tp_iternext */
    0,                                /* tp_methods */
    0,                                /* tp_members */
    pepy_import_getseters,            /* tp_getset */
    0,                                /* tp_base */
    0,                                /* tp_dict */
    0,                                /* tp_descr_get */
    0,                                /* tp_descr_set */
    0,                                /* tp_dictoffset */
    (initproc) pepy_import_init,      /* tp_init */
    0,                                /* tp_alloc */
    pepy_import_new                   /* tp_new */
};

static PyObject *
pepy_export_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_export *self;

  self = (pepy_export *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int pepy_export_init(pepy_export *self, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple(
          args, "OOO:pepy_export_init", &self->mod, &self->func, &self->addr))
    return -1;
  return 0;
}

static void pepy_export_dealloc(pepy_export *self) {
  Py_XDECREF(self->mod);
  Py_XDECREF(self->func);
  Py_XDECREF(self->addr);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

PEPY_OBJECT_GET(export, mod);
PEPY_OBJECT_GET(export, func);
PEPY_OBJECT_GET(export, addr);

static PyGetSetDef pepy_export_getseters[] = {
    OBJECTGETTER(export, mod, "Module"),
    OBJECTGETTER(export, func, "Function"),
    OBJECTGETTER(export, addr, "Address"),
    {NULL}};

static PyTypeObject pepy_export_type = {
    PyVarObject_HEAD_INIT(NULL, 0)    /* ob_size */
    "pepy.export",                    /* tp_name */
    sizeof(pepy_export),              /* tp_basicsize */
    0,                                /* tp_itemsize */
    (destructor) pepy_export_dealloc, /* tp_dealloc */
    0,                                /* tp_print */
    0,                                /* tp_getattr */
    0,                                /* tp_setattr */
    0,                                /* tp_compare */
    0,                                /* tp_repr */
    0,                                /* tp_as_number */
    0,                                /* tp_as_sequence */
    0,                                /* tp_as_mapping */
    0,                                /* tp_hash */
    0,                                /* tp_call */
    0,                                /* tp_str */
    0,                                /* tp_getattro */
    0,                                /* tp_setattro */
    0,                                /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,               /* tp_flags */
    "pepy export object",             /* tp_doc */
    0,                                /* tp_traverse */
    0,                                /* tp_clear */
    0,                                /* tp_richcompare */
    0,                                /* tp_weaklistoffset */
    0,                                /* tp_iter */
    0,                                /* tp_iternext */
    0,                                /* tp_methods */
    0,                                /* tp_members */
    pepy_export_getseters,            /* tp_getset */
    0,                                /* tp_base */
    0,                                /* tp_dict */
    0,                                /* tp_descr_get */
    0,                                /* tp_descr_set */
    0,                                /* tp_dictoffset */
    (initproc) pepy_export_init,      /* tp_init */
    0,                                /* tp_alloc */
    pepy_export_new                   /* tp_new */
};

static PyObject *
pepy_relocation_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_relocation *self;

  self = (pepy_relocation *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int
pepy_relocation_init(pepy_relocation *self, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple(
          args, "OO:pepy_relocation_init", &self->type, &self->addr))
    return -1;
  return 0;
}

static void pepy_relocation_dealloc(pepy_relocation *self) {
  Py_XDECREF(self->type);
  Py_XDECREF(self->addr);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

PEPY_OBJECT_GET(relocation, type);
PEPY_OBJECT_GET(relocation, addr);

static PyGetSetDef pepy_relocation_getseters[] = {
    OBJECTGETTER(relocation, type, "Type"),
    OBJECTGETTER(relocation, addr, "Address"),
    {NULL}};

static PyTypeObject pepy_relocation_type = {
    PyVarObject_HEAD_INIT(NULL, 0)        /* ob_size */
    "pepy.relocation",                    /* tp_name */
    sizeof(pepy_relocation),              /* tp_basicsize */
    0,                                    /* tp_itemsize */
    (destructor) pepy_relocation_dealloc, /* tp_dealloc */
    0,                                    /* tp_print */
    0,                                    /* tp_getattr */
    0,                                    /* tp_setattr */
    0,                                    /* tp_compare */
    0,                                    /* tp_repr */
    0,                                    /* tp_as_number */
    0,                                    /* tp_as_sequence */
    0,                                    /* tp_as_mapping */
    0,                                    /* tp_hash */
    0,                                    /* tp_call */
    0,                                    /* tp_str */
    0,                                    /* tp_getattro */
    0,                                    /* tp_setattro */
    0,                                    /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                   /* tp_flags */
    "pepy relocation object",             /* tp_doc */
    0,                                    /* tp_traverse */
    0,                                    /* tp_clear */
    0,                                    /* tp_richcompare */
    0,                                    /* tp_weaklistoffset */
    0,                                    /* tp_iter */
    0,                                    /* tp_iternext */
    0,                                    /* tp_methods */
    0,                                    /* tp_members */
    pepy_relocation_getseters,            /* tp_getset */
    0,                                    /* tp_base */
    0,                                    /* tp_dict */
    0,                                    /* tp_descr_get */
    0,                                    /* tp_descr_set */
    0,                                    /* tp_dictoffset */
    (initproc) pepy_relocation_init,      /* tp_init */
    0,                                    /* tp_alloc */
    pepy_relocation_new                   /* tp_new */
};

static PyObject *
pepy_section_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_section *self;

  self = (pepy_section *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int
pepy_section_init(pepy_section *self, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple(args,
                        "OOOOOOOOO:pepy_section_init",
                        &self->name,
                        &self->base,
                        &self->length,
                        &self->virtaddr,
                        &self->virtsize,
                        &self->numrelocs,
                        &self->numlinenums,
                        &self->characteristics,
                        &self->data))
    return -1;
  return 0;
}

static void pepy_section_dealloc(pepy_section *self) {
  Py_XDECREF(self->name);
  Py_XDECREF(self->base);
  Py_XDECREF(self->length);
  Py_XDECREF(self->virtaddr);
  Py_XDECREF(self->virtsize);
  Py_XDECREF(self->numrelocs);
  Py_XDECREF(self->numlinenums);
  Py_XDECREF(self->characteristics);
  Py_XDECREF(self->data);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

PEPY_OBJECT_GET(section, name);
PEPY_OBJECT_GET(section, base);
PEPY_OBJECT_GET(section, length);
PEPY_OBJECT_GET(section, virtaddr);
PEPY_OBJECT_GET(section, virtsize);
PEPY_OBJECT_GET(section, numrelocs);
PEPY_OBJECT_GET(section, numlinenums);
PEPY_OBJECT_GET(section, characteristics);
PEPY_OBJECT_GET(section, data);

static PyGetSetDef pepy_section_getseters[] = {
    OBJECTGETTER(section, name, "Name"),
    OBJECTGETTER(section, base, "Base address"),
    OBJECTGETTER(section, length, "Length"),
    OBJECTGETTER(section, virtaddr, "Virtual address"),
    OBJECTGETTER(section, virtsize, "Virtual size"),
    OBJECTGETTER(section, numrelocs, "Number of relocations"),
    OBJECTGETTER(section, numlinenums, "Number of line numbers"),
    OBJECTGETTER(section, characteristics, "Characteristics"),
    OBJECTGETTER(section, data, "Section data"),
    {NULL}};

static PyTypeObject pepy_section_type = {
    PyVarObject_HEAD_INIT(NULL, 0)     /* ob_size */
    "pepy.section",                    /* tp_name */
    sizeof(pepy_section),              /* tp_basicsize */
    0,                                 /* tp_itemsize */
    (destructor) pepy_section_dealloc, /* tp_dealloc */
    0,                                 /* tp_print */
    0,                                 /* tp_getattr */
    0,                                 /* tp_setattr */
    0,                                 /* tp_compare */
    0,                                 /* tp_repr */
    0,                                 /* tp_as_number */
    0,                                 /* tp_as_sequence */
    0,                                 /* tp_as_mapping */
    0,                                 /* tp_hash */
    0,                                 /* tp_call */
    0,                                 /* tp_str */
    0,                                 /* tp_getattro */
    0,                                 /* tp_setattro */
    0,                                 /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                /* tp_flags */
    "pepy section object",             /* tp_doc */
    0,                                 /* tp_traverse */
    0,                                 /* tp_clear */
    0,                                 /* tp_richcompare */
    0,                                 /* tp_weaklistoffset */
    0,                                 /* tp_iter */
    0,                                 /* tp_iternext */
    0,                                 /* tp_methods */
    0,                                 /* tp_members */
    pepy_section_getseters,            /* tp_getset */
    0,                                 /* tp_base */
    0,                                 /* tp_dict */
    0,                                 /* tp_descr_get */
    0,                                 /* tp_descr_set */
    0,                                 /* tp_dictoffset */
    (initproc) pepy_section_init,      /* tp_init */
    0,                                 /* tp_alloc */
    pepy_section_new                   /* tp_new */
};

static PyObject *
pepy_resource_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_resource *self;

  self = (pepy_resource *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int
pepy_resource_init(pepy_resource *self, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple(args,
                        "OOOOOOOOOO:pepy_resource_init",
                        &self->type_str,
                        &self->name_str,
                        &self->lang_str,
                        &self->type,
                        &self->name,
                        &self->lang,
                        &self->codepage,
                        &self->RVA,
                        &self->size,
                        &self->data))
    return -1;

  return 0;
}

static void pepy_resource_dealloc(pepy_resource *self) {
  Py_XDECREF(self->type_str);
  Py_XDECREF(self->name_str);
  Py_XDECREF(self->lang_str);
  Py_XDECREF(self->type);
  Py_XDECREF(self->name);
  Py_XDECREF(self->lang);
  Py_XDECREF(self->codepage);
  Py_XDECREF(self->RVA);
  Py_XDECREF(self->size);
  Py_XDECREF(self->data);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

PEPY_OBJECT_GET(resource, type_str);
PEPY_OBJECT_GET(resource, name_str);
PEPY_OBJECT_GET(resource, lang_str);
PEPY_OBJECT_GET(resource, type);
PEPY_OBJECT_GET(resource, name);
PEPY_OBJECT_GET(resource, lang);
PEPY_OBJECT_GET(resource, codepage);
PEPY_OBJECT_GET(resource, RVA);
PEPY_OBJECT_GET(resource, size);
PEPY_OBJECT_GET(resource, data);

static PyObject *pepy_resource_type_as_str(PyObject *self, PyObject *args) {
  PyObject *ret;
  char *str;
  long type;

  type = PyLong_AsUnsignedLong(((pepy_resource *) self)->type);
  if (type == -1) {
    if (PyErr_Occurred()) {
      PyErr_PrintEx(0);
      return NULL;
    }
  }
  switch ((resource_type) type) {
    case (RT_CURSOR):
      str = (char *) "CURSOR";
      break;
    case (RT_BITMAP):
      str = (char *) "BITMAP";
      break;
    case (RT_ICON):
      str = (char *) "ICON";
      break;
    case (RT_MENU):
      str = (char *) "MENU";
      break;
    case (RT_DIALOG):
      str = (char *) "DIALOG";
      break;
    case (RT_STRING):
      str = (char *) "STRING";
      break;
    case (RT_FONTDIR):
      str = (char *) "FONTDIR";
      break;
    case (RT_FONT):
      str = (char *) "FONT";
      break;
    case (RT_ACCELERATOR):
      str = (char *) "ACCELERATOR";
      break;
    case (RT_RCDATA):
      str = (char *) "RCDATA";
      break;
    case (RT_MESSAGETABLE):
      str = (char *) "MESSAGETABLE";
      break;
    case (RT_GROUP_CURSOR):
      str = (char *) "GROUP_CURSOR";
      break;
    case (RT_GROUP_ICON):
      str = (char *) "GROUP_ICON";
      break;
    case (RT_VERSION):
      str = (char *) "VERSION";
      break;
    case (RT_DLGINCLUDE):
      str = (char *) "DLGINCLUDE";
      break;
    case (RT_PLUGPLAY):
      str = (char *) "PLUGPLAY";
      break;
    case (RT_VXD):
      str = (char *) "VXD";
      break;
    case (RT_ANICURSOR):
      str = (char *) "ANICURSOR";
      break;
    case (RT_ANIICON):
      str = (char *) "ANIICON";
      break;
    case (RT_HTML):
      str = (char *) "HTML";
      break;
    case (RT_MANIFEST):
      str = (char *) "MANIFEST";
      break;
    default:
      str = (char *) "UNKNOWN";
      break;
  }

  ret = PyUnicode_FromString(str);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create return string.");
    return NULL;
  }

  return ret;
}

static PyMethodDef pepy_resource_methods[] = {
    {"type_as_str",
     pepy_resource_type_as_str,
     METH_NOARGS,
     "Return the resource type as a string."},
    {NULL}};

static PyGetSetDef pepy_resource_getseters[] = {
    OBJECTGETTER(resource, type_str, "Type string"),
    OBJECTGETTER(resource, name_str, "Name string"),
    OBJECTGETTER(resource, lang_str, "Lang string"),
    OBJECTGETTER(resource, type, "Type"),
    OBJECTGETTER(resource, name, "Name"),
    OBJECTGETTER(resource, lang, "Language"),
    OBJECTGETTER(resource, codepage, "Codepage"),
    OBJECTGETTER(resource, RVA, "RVA"),
    OBJECTGETTER(resource, size, "Size (specified in RDAT)"),
    OBJECTGETTER(resource, data, "Resource data"),
    {NULL}};

static PyTypeObject pepy_resource_type = {
    PyVarObject_HEAD_INIT(NULL, 0)      /* ob_size */
    "pepy.resource",                    /* tp_name */
    sizeof(pepy_resource),              /* tp_basicsize */
    0,                                  /* tp_itemsize */
    (destructor) pepy_resource_dealloc, /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_compare */
    0,                                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    0,                                  /* tp_call */
    0,                                  /* tp_str */
    0,                                  /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
    "pepy resource object",             /* tp_doc */
    0,                                  /* tp_traverse */
    0,                                  /* tp_clear */
    0,                                  /* tp_richcompare */
    0,                                  /* tp_weaklistoffset */
    0,                                  /* tp_iter */
    0,                                  /* tp_iternext */
    pepy_resource_methods,              /* tp_methods */
    0,                                  /* tp_members */
    pepy_resource_getseters,            /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */
    (initproc) pepy_resource_init,      /* tp_init */
    0,                                  /* tp_alloc */
    pepy_resource_new                   /* tp_new */
};

static PyObject *
pepy_parsed_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  pepy_parsed *self;

  self = (pepy_parsed *) type->tp_alloc(type, 0);

  return (PyObject *) self;
}

static int pepy_parsed_init(pepy_parsed *self, PyObject *args, PyObject *kwds) {
  char *pe_path;

  if (!PyArg_ParseTuple(args, "s:pepy_parse", &pe_path))
    return -1;

  if (!pe_path)
    return -1;

  self->pe = ParsePEFromFile(pe_path);
  if (!self->pe) {
    return -2;
  }

  return 0;
}

static void pepy_parsed_dealloc(pepy_parsed *self) {
  DestructParsedPE(self->pe);
  Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *pepy_parsed_get_entry_point(PyObject *self, PyObject *args) {
  VA entrypoint;
  PyObject *ret;

  if (!GetEntryPoint(((pepy_parsed *) self)->pe, entrypoint))
    Py_RETURN_NONE;

  ret = PyLong_FromUnsignedLongLong(entrypoint);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create return object.");
    return NULL;
  }

  return ret;
}

static PyObject *pepy_parsed_get_machine_as_str(PyObject *self,
                                                PyObject *args) {
  PyObject *ret;
  const char *str;

  str = GetMachineAsString(((pepy_parsed *) self)->pe);
  if (!str)
    Py_RETURN_NONE;

  ret = PyUnicode_FromString(str);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create return string.");
    return NULL;
  }

  return ret;
}

static PyObject *pepy_parsed_get_subsystem_as_str(PyObject *self,
                                                  PyObject *args) {
  PyObject *ret;
  const char *str;

  str = GetSubsystemAsString(((pepy_parsed *) self)->pe);
  if (!str)
    Py_RETURN_NONE;

  ret = PyUnicode_FromString(str);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create return string.");
    return NULL;
  }

  return ret;
}

static PyObject *pepy_parsed_get_bytes(PyObject *self, PyObject *args) {
  uint64_t start;
  Py_ssize_t len, idx;
  PyObject *ret;

  if (!PyArg_ParseTuple(args, "KK:pepy_parsed_get_bytes", &start, &len))
    return NULL;

  /*
   * XXX: a new implementation read all bytes in char* and use
   * PybyteArray_FromStringAndSize
   */

  uint8_t *buf = new (std::nothrow) uint8_t[len];
  if (!buf) {
    /* in case allocation failed */
    PyErr_SetString(pepy_error,
                    "Unable to create initial buffer (allocation failure).");
    return NULL;
  }

  for (idx = 0; idx < len; idx++) {
    if (!ReadByteAtVA(((pepy_parsed *) self)->pe, start + idx, buf[idx]))
      break;
  }

  /* use idx as content length, if we get less than asked for */
  ret = PyByteArray_FromStringAndSize(reinterpret_cast<char *>(buf), idx);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new byte array.");
    return NULL;
  }

  delete[] buf;
  return ret;
}

/*
 * This is used to convert bounded buffers into python byte array objects.
 * In case the buffer is NULL, return an empty bytearray.
 */
static PyObject *pepy_data_converter(bounded_buffer *data) {
  PyObject *ret;
  const char *str;
  Py_ssize_t len;

  if (!data || !data->buf) {
    str = "";
    len = 0;
  } else {
    str = (const char *) data->buf;
    len = data->bufLen;
  }

  ret = PyByteArray_FromStringAndSize(str, len);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to convert data to byte array.");
    return NULL;
  }

  return ret;
}

int section_callback(void *cbd,
                     const VA &base,
                     const std::string &name,
                     const image_section_header &s,
                     const bounded_buffer *data) {
  uint32_t buflen;
  PyObject *sect;
  PyObject *tuple;
  PyObject *list = (PyObject *) cbd;

  /*
   * I've seen some interesting binaries with a section where the
   * PointerToRawData and SizeOfRawData are invalid. The parser library
   * handles this by setting sectionData to NULL as returned by splitBuffer().
   * The sectionData (passed in to us as *data) is converted using
   * pepy_data_converter() which will return an empty string object.
   * However, we need to address the fact that we pass an invalid length
   * via data->bufLen.
   */
  if (!data) {
    buflen = 0;
  } else {
    buflen = data->bufLen;
  }

  /*
   * The tuple item order is important here. It is passed into the
   * section type initialization and parsed there.
   */
  tuple = Py_BuildValue("sKKIIHHIO&",
                        name.c_str(),
                        base,
                        buflen,
                        s.VirtualAddress,
                        s.Misc.VirtualSize,
                        s.NumberOfRelocations,
                        s.NumberOfLinenumbers,
                        s.Characteristics,
                        pepy_data_converter,
                        data);
  if (!tuple)
    return 1;

  sect = pepy_section_new(&pepy_section_type, NULL, NULL);
  if (!sect) {
    Py_DECREF(tuple);
    return 1;
  }

  if (pepy_section_init((pepy_section *) sect, tuple, NULL) == -1) {
    PyErr_SetString(pepy_error, "Unable to init new section.");
    return 1;
  }

  if (PyList_Append(list, sect) == -1) {
    Py_DECREF(tuple);
    Py_DECREF(sect);
    return 1;
  }

  return 0;
}

static PyObject *pepy_parsed_get_sections(PyObject *self, PyObject *args) {
  PyObject *ret = PyList_New(0);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new list.");
    return NULL;
  }

  IterSec(((pepy_parsed *) self)->pe, section_callback, ret);

  return ret;
}

int resource_callback(void *cbd, const resource &r) {
  PyObject *rsrc;
  PyObject *tuple;
  PyObject *list = (PyObject *) cbd;

  /*
   * The tuple item order is important here. It is passed into the
   * section type initialization and parsed there.
   */
  tuple = Py_BuildValue("s#s#s#IIIIIIO&",
                        r.type_str.c_str(),
                        r.type_str.length(),
                        r.name_str.c_str(),
                        r.name_str.length(),
                        r.lang_str.c_str(),
                        r.lang_str.length(),
                        r.type,
                        r.name,
                        r.lang,
                        r.codepage,
                        r.RVA,
                        r.size,
                        pepy_data_converter,
                        r.buf);
  if (!tuple)
    return 1;

  rsrc = pepy_resource_new(&pepy_resource_type, NULL, NULL);
  if (!rsrc) {
    Py_DECREF(tuple);
    return 1;
  }

  if (pepy_resource_init((pepy_resource *) rsrc, tuple, NULL) == -1) {
    PyErr_SetString(pepy_error, "Unable to init new resource.");
    return 1;
  }

  if (PyList_Append(list, rsrc) == -1) {
    Py_DECREF(tuple);
    Py_DECREF(rsrc);
    return 1;
  }

  return 0;
}

static PyObject *pepy_parsed_get_resources(PyObject *self, PyObject *args) {
  PyObject *ret = PyList_New(0);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new list.");
    return NULL;
  }

  IterRsrc(((pepy_parsed *) self)->pe, resource_callback, ret);

  return ret;
}

int import_callback(void *cbd,
                    const VA &addr,
                    const std::string &name,
                    const std::string &sym) {
  PyObject *imp;
  PyObject *tuple;
  PyObject *list = (PyObject *) cbd;

  /*
   * The tuple item order is important here. It is passed into the
   * import type initialization and parsed there.
   */
  tuple = Py_BuildValue("ssI", name.c_str(), sym.c_str(), addr);
  if (!tuple)
    return 1;

  imp = pepy_import_new(&pepy_import_type, NULL, NULL);
  if (!imp) {
    Py_DECREF(tuple);
    return 1;
  }

  if (pepy_import_init((pepy_import *) imp, tuple, NULL) == -1) {
    PyErr_SetString(pepy_error, "Unable to init new section.");
    return 1;
  }

  if (PyList_Append(list, imp) == -1) {
    Py_DECREF(tuple);
    Py_DECREF(imp);
    return 1;
  }

  return 0;
}

static PyObject *pepy_parsed_get_imports(PyObject *self, PyObject *args) {
  PyObject *ret = PyList_New(0);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new list.");
    return NULL;
  }

  IterImpVAString(((pepy_parsed *) self)->pe, import_callback, ret);

  return ret;
}

int export_callback(void *cbd,
                    const VA &addr,
                    const std::string &mod,
                    const std::string &func) {
  PyObject *exp;
  PyObject *tuple;
  PyObject *list = (PyObject *) cbd;

  /*
   * The tuple item order is important here. It is passed into the
   * export type initialization and parsed there.
   */
  tuple = Py_BuildValue("ssI", mod.c_str(), func.c_str(), addr);
  if (!tuple)
    return 1;

  exp = pepy_export_new(&pepy_export_type, NULL, NULL);
  if (!exp) {
    Py_DECREF(tuple);
    return 1;
  }

  if (pepy_export_init((pepy_export *) exp, tuple, NULL) == -1) {
    PyErr_SetString(pepy_error, "Unable to init new section.");
    return 1;
  }

  if (PyList_Append(list, exp) == -1) {
    Py_DECREF(tuple);
    Py_DECREF(exp);
    return 1;
  }

  return 0;
}

static PyObject *pepy_parsed_get_exports(PyObject *self, PyObject *args) {
  PyObject *ret = PyList_New(0);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new list.");
    return NULL;
  }

  /*
   * This could use the same callback and object as imports but the names
   * of the attributes would be slightly off.
   */
  IterExpVA(((pepy_parsed *) self)->pe, export_callback, ret);

  return ret;
}

int reloc_callback(void *cbd, const VA &addr, const reloc_type &type) {
  PyObject *reloc;
  PyObject *tuple;
  PyObject *list = (PyObject *) cbd;

  /*
   * The tuple item order is important here. It is passed into the
   * relocation type initialization and parsed there.
   */
  tuple = Py_BuildValue("II", type, addr);
  if (!tuple)
    return 1;

  reloc = pepy_relocation_new(&pepy_relocation_type, NULL, NULL);
  if (!reloc) {
    Py_DECREF(tuple);
    return 1;
  }

  if (pepy_relocation_init((pepy_relocation *) reloc, tuple, NULL) == -1) {
    PyErr_SetString(pepy_error, "Unable to init new section.");
    return 1;
  }

  if (PyList_Append(list, reloc) == -1) {
    Py_DECREF(tuple);
    Py_DECREF(reloc);
    return 1;
  }

  return 0;
}

static PyObject *pepy_parsed_get_relocations(PyObject *self, PyObject *args) {
  PyObject *ret = PyList_New(0);
  if (!ret) {
    PyErr_SetString(pepy_error, "Unable to create new list.");
    return NULL;
  }

  IterRelocs(((pepy_parsed *) self)->pe, reloc_callback, ret);

  return ret;
}

#define PEPY_PARSED_GET(ATTR, VAL)                                         \
  static PyObject *pepy_parsed_get_##ATTR(PyObject *self, void *closure) { \
    PyObject *ret = PyLong_FromUnsignedLongLong(                           \
        ((pepy_parsed *) self)->pe->peHeader.nt.VAL);                      \
    if (!ret)                                                              \
      PyErr_SetString(PyExc_AttributeError, "Error getting attribute.");   \
    return ret;                                                            \
  }

PEPY_PARSED_GET(signature, Signature);
PEPY_PARSED_GET(machine, FileHeader.Machine);
PEPY_PARSED_GET(numberofsections, FileHeader.NumberOfSections);
PEPY_PARSED_GET(timedatestamp, FileHeader.TimeDateStamp);
PEPY_PARSED_GET(numberofsymbols, FileHeader.NumberOfSymbols);
PEPY_PARSED_GET(characteristics, FileHeader.Characteristics);
PEPY_PARSED_GET(magic, OptionalMagic);

/*
 * This is used to get things from the optional header, which can be either
 * the PE32 or PE32+ version, depending upon the magic value. Technically
 * the magic is stored in the OptionalHeader, but to make life easier pe-parse
 * stores the value in nt_header_32 along with the appropriate optional header.
 * This is why "magic" is handled above, and not here.
 */
#define PEPY_PARSED_GET_OPTIONAL(ATTR, VAL)                                \
  static PyObject *pepy_parsed_get_optional_##ATTR(PyObject *self,         \
                                                   void *closure) {        \
    PyObject *ret = NULL;                                                  \
    if (((pepy_parsed *) self)->pe->peHeader.nt.OptionalMagic ==           \
        NT_OPTIONAL_32_MAGIC) {                                            \
      ret = PyLong_FromUnsignedLongLong(                                   \
          ((pepy_parsed *) self)->pe->peHeader.nt.OptionalHeader.VAL);     \
      if (!ret)                                                            \
        PyErr_SetString(PyExc_AttributeError, "Error getting attribute."); \
    } else if (((pepy_parsed *) self)->pe->peHeader.nt.OptionalMagic ==    \
               NT_OPTIONAL_64_MAGIC) {                                     \
      ret = PyLong_FromUnsignedLongLong(                                   \
          ((pepy_parsed *) self)->pe->peHeader.nt.OptionalHeader64.VAL);   \
      if (!ret)                                                            \
        PyErr_SetString(PyExc_AttributeError, "Error getting attribute."); \
    } else {                                                               \
      PyErr_SetString(pepy_error, "Bad magic value.");                     \
    }                                                                      \
    return ret;                                                            \
  }

PEPY_PARSED_GET_OPTIONAL(majorlinkerver, MajorLinkerVersion);
PEPY_PARSED_GET_OPTIONAL(minorlinkerver, MinorLinkerVersion);
PEPY_PARSED_GET_OPTIONAL(codesize, SizeOfCode);
PEPY_PARSED_GET_OPTIONAL(initdatasize, SizeOfInitializedData);
PEPY_PARSED_GET_OPTIONAL(uninitdatasize, SizeOfUninitializedData);
PEPY_PARSED_GET_OPTIONAL(entrypointaddr, AddressOfEntryPoint);
PEPY_PARSED_GET_OPTIONAL(baseofcode, BaseOfCode);
PEPY_PARSED_GET_OPTIONAL(imagebase, ImageBase);
PEPY_PARSED_GET_OPTIONAL(sectionalignement, SectionAlignment);
PEPY_PARSED_GET_OPTIONAL(filealignment, FileAlignment);
PEPY_PARSED_GET_OPTIONAL(majorosver, MajorOperatingSystemVersion);
PEPY_PARSED_GET_OPTIONAL(minorosver, MinorOperatingSystemVersion);
PEPY_PARSED_GET_OPTIONAL(win32ver, Win32VersionValue);
PEPY_PARSED_GET_OPTIONAL(imagesize, SizeOfImage);
PEPY_PARSED_GET_OPTIONAL(headersize, SizeOfHeaders);
PEPY_PARSED_GET_OPTIONAL(checksum, CheckSum);
PEPY_PARSED_GET_OPTIONAL(subsystem, Subsystem);
PEPY_PARSED_GET_OPTIONAL(dllcharacteristics, DllCharacteristics);
PEPY_PARSED_GET_OPTIONAL(stackreservesize, SizeOfStackReserve);
PEPY_PARSED_GET_OPTIONAL(stackcommitsize, SizeOfStackCommit);
PEPY_PARSED_GET_OPTIONAL(heapreservesize, SizeOfHeapReserve);
PEPY_PARSED_GET_OPTIONAL(heapcommitsize, SizeOfHeapCommit);
PEPY_PARSED_GET_OPTIONAL(loaderflags, LoaderFlags);
PEPY_PARSED_GET_OPTIONAL(rvasandsize, NumberOfRvaAndSizes);

/*
 * BaseOfData is only in PE32, not PE32+. Thus, it uses a non-standard
 * getter function compared to the other shared fields.
 */
static PyObject *pepy_parsed_get_optional_baseofdata(PyObject *self,
                                                     void *closure) {
  PyObject *ret = NULL;
  if (((pepy_parsed *) self)->pe->peHeader.nt.OptionalMagic ==
      NT_OPTIONAL_32_MAGIC) {
    ret = PyLong_FromUnsignedLong(
        ((pepy_parsed *) self)->pe->peHeader.nt.OptionalHeader.BaseOfData);
    if (!ret)
      PyErr_SetString(PyExc_AttributeError, "Error getting attribute.");
  } else if (((pepy_parsed *) self)->pe->peHeader.nt.OptionalMagic ==
             NT_OPTIONAL_64_MAGIC) {
    PyErr_SetString(PyExc_AttributeError, "Not available on PE32+.");
  } else {
    PyErr_SetString(pepy_error, "Bad magic value.");
  }
  return ret;
}

static PyGetSetDef pepy_parsed_getseters[] = {
    OBJECTGETTER(parsed, signature, "PE Signature"),
    OBJECTGETTER(parsed, machine, "Machine"),
    OBJECTGETTER(parsed, numberofsections, "Number of sections"),
    OBJECTGETTER(parsed, timedatestamp, "Timedate stamp"),
    OBJECTGETTER(parsed, numberofsymbols, "Number of symbols"),
    OBJECTGETTER(parsed, characteristics, "Characteristics"),
    OBJECTGETTER(parsed, magic, "Magic"),
    OBJECTGETTER_OPTIONAL(majorlinkerver, "Major linker version"),
    OBJECTGETTER_OPTIONAL(minorlinkerver, "Minor linker version"),
    OBJECTGETTER_OPTIONAL(codesize, "Size of code"),
    OBJECTGETTER_OPTIONAL(initdatasize, "Size of initialized data"),
    OBJECTGETTER_OPTIONAL(uninitdatasize, "Size of uninitialized data"),
    OBJECTGETTER_OPTIONAL(entrypointaddr, "Address of entry point"),
    OBJECTGETTER_OPTIONAL(baseofcode, "Base address of code"),
    OBJECTGETTER_OPTIONAL(imagebase, "Image base address"),
    OBJECTGETTER_OPTIONAL(sectionalignement, "Section alignment"),
    OBJECTGETTER_OPTIONAL(filealignment, "File alignment"),
    OBJECTGETTER_OPTIONAL(majorosver, "Major OS version"),
    OBJECTGETTER_OPTIONAL(minorosver, "Minor OS version"),
    OBJECTGETTER_OPTIONAL(win32ver, "Win32 version"),
    OBJECTGETTER_OPTIONAL(imagesize, "Size of image"),
    OBJECTGETTER_OPTIONAL(headersize, "Size of headers"),
    OBJECTGETTER_OPTIONAL(checksum, "Checksum"),
    OBJECTGETTER_OPTIONAL(subsystem, "Subsystem"),
    OBJECTGETTER_OPTIONAL(dllcharacteristics, "DLL characteristics"),
    OBJECTGETTER_OPTIONAL(stackreservesize, "Size of stack reserve"),
    OBJECTGETTER_OPTIONAL(stackcommitsize, "Size of stack commit"),
    OBJECTGETTER_OPTIONAL(heapreservesize, "Size of heap reserve"),
    OBJECTGETTER_OPTIONAL(heapcommitsize, "Size of heap commit"),
    OBJECTGETTER_OPTIONAL(loaderflags, "Loader flags"),
    OBJECTGETTER_OPTIONAL(rvasandsize, "Number of RVA and sizes"),
    /* Base of data is only available in PE32, not PE32+. */
    {(char *) "baseofdata",
     (getter) pepy_parsed_get_optional_baseofdata,
     (setter) pepy_attr_not_writable,
     (char *) "Base address of data",
     NULL},
    {NULL}};

static PyMethodDef pepy_parsed_methods[] = {
    {"get_entry_point",
     pepy_parsed_get_entry_point,
     METH_NOARGS,
     "Return the entry point address."},
    {"get_machine_as_str",
     pepy_parsed_get_machine_as_str,
     METH_NOARGS,
     "Return the machine as a human readable string."},
    {"get_subsystem_as_str",
     pepy_parsed_get_subsystem_as_str,
     METH_NOARGS,
     "Return the subsystem as a human readable string."},
    {"get_bytes",
     pepy_parsed_get_bytes,
     METH_VARARGS,
     "Return the first N bytes at a given address."},
    {"get_sections",
     pepy_parsed_get_sections,
     METH_NOARGS,
     "Return a list of section objects."},
    {"get_imports",
     pepy_parsed_get_imports,
     METH_NOARGS,
     "Return a list of import objects."},
    {"get_exports",
     pepy_parsed_get_exports,
     METH_NOARGS,
     "Return a list of export objects."},
    {"get_relocations",
     pepy_parsed_get_relocations,
     METH_NOARGS,
     "Return a list of relocation objects."},
    {"get_resources",
     pepy_parsed_get_resources,
     METH_NOARGS,
     "Return a list of resource objects."},
    {NULL}};

static PyTypeObject pepy_parsed_type = {
    PyVarObject_HEAD_INIT(NULL, 0)            /* ob_size */
    "pepy.parsed",                            /* tp_name */
    sizeof(pepy_parsed),                      /* tp_basicsize */
    0,                                        /* tp_itemsize */
    (destructor) pepy_parsed_dealloc,         /* tp_dealloc */
    0,                                        /* tp_print */
    0,                                        /* tp_getattr */
    0,                                        /* tp_setattr */
    0,                                        /* tp_compare */
    0,                                        /* tp_repr */
    0,                                        /* tp_as_number */
    0,                                        /* tp_as_sequence */
    0,                                        /* tp_as_mapping */
    0,                                        /* tp_hash */
    0,                                        /* tp_call */
    0,                                        /* tp_str */
    0,                                        /* tp_getattro */
    0,                                        /* tp_setattro */
    0,                                        /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    "pepy parsed object",                     /* tp_doc */
    0,                                        /* tp_traverse */
    0,                                        /* tp_clear */
    0,                                        /* tp_richcompare */
    0,                                        /* tp_weaklistoffset */
    0,                                        /* tp_iter */
    0,                                        /* tp_iternext */
    pepy_parsed_methods,                      /* tp_methods */
    0,                                        /* tp_members */
    pepy_parsed_getseters,                    /* tp_getset */
    0,                                        /* tp_base */
    0,                                        /* tp_dict */
    0,                                        /* tp_descr_get */
    0,                                        /* tp_descr_set */
    0,                                        /* tp_dictoffset */
    (initproc) pepy_parsed_init,              /* tp_init */
    0,                                        /* tp_alloc */
    pepy_parsed_new                           /* tp_new */
};

static PyObject *pepy_parse(PyObject *self, PyObject *args) {
  PyObject *parsed;
  int ret;
  char *err_str = NULL;

  parsed = pepy_parsed_new(&pepy_parsed_type, NULL, NULL);
  if (!parsed) {
    PyErr_SetString(pepy_error, "Unable to make new parsed object.");
    return NULL;
  }

  ret = pepy_parsed_init((pepy_parsed *) parsed, args, NULL);
  if (ret < 0) {
    if (ret == -2) {
      // error (loc)
      size_t len = GetPEErrString().length() + GetPEErrLoc().length() + 4;
      err_str = (char *) malloc(len);
      if (!err_str)
        return PyErr_NoMemory();
      snprintf(err_str,
               len,
               "%s (%s)",
               GetPEErrString().c_str(),
               GetPEErrLoc().c_str());
      PyErr_SetString(pepy_error, err_str);
    } else
      PyErr_SetString(pepy_error, "Unable to init new parsed object.");
    return NULL;
  }

  return parsed;
}

static PyMethodDef pepy_methods[] = {
    {"parse", pepy_parse, METH_VARARGS, "Parse PE from file."}, {NULL}};

PyMODINIT_FUNC PyInit_pepy(void) {
  PyObject *m;

  if (PyType_Ready(&pepy_parsed_type) < 0 ||
      PyType_Ready(&pepy_section_type) < 0 ||
      PyType_Ready(&pepy_import_type) < 0 ||
      PyType_Ready(&pepy_export_type) < 0 ||
      PyType_Ready(&pepy_relocation_type) < 0 ||
      PyType_Ready(&pepy_resource_type) < 0)
    return NULL;

  static struct PyModuleDef moduledef = {
      PyModuleDef_HEAD_INIT,
      "pepy",
      "Python interface to pe-parse.",
      -1,
      pepy_methods,
      NULL,
      NULL,
      NULL,
      NULL,
  };

  m = PyModule_Create(&moduledef);
  if (!m)
    return NULL;

  pepy_error = PyErr_NewException((char *) "pepy.error", NULL, NULL);
  Py_INCREF(pepy_error);
  PyModule_AddObject(m, "error", pepy_error);

  Py_INCREF(&pepy_parsed_type);
  PyModule_AddObject(m, "pepy_parsed", (PyObject *) &pepy_parsed_type);

  Py_INCREF(&pepy_section_type);
  PyModule_AddObject(m, "pepy_section", (PyObject *) &pepy_section_type);

  Py_INCREF(&pepy_import_type);
  PyModule_AddObject(m, "pepy_import", (PyObject *) &pepy_import_type);

  Py_INCREF(&pepy_export_type);
  PyModule_AddObject(m, "pepy_export", (PyObject *) &pepy_export_type);

  Py_INCREF(&pepy_relocation_type);
  PyModule_AddObject(m, "pepy_relocation", (PyObject *) &pepy_relocation_type);

  Py_INCREF(&pepy_resource_type);
  PyModule_AddObject(m, "pepy_resource", (PyObject *) &pepy_resource_type);

  PyModule_AddStringMacro(m, PEPY_VERSION);
  PyModule_AddStringMacro(m, PEPARSE_VERSION);
  PyModule_AddStringConstant(m, "__version__", PEPY_VERSION);

  PyModule_AddIntMacro(m, MZ_MAGIC);
  PyModule_AddIntMacro(m, NT_MAGIC);
  PyModule_AddIntMacro(m, NUM_DIR_ENTRIES);
  PyModule_AddIntMacro(m, NT_OPTIONAL_32_MAGIC);
  PyModule_AddIntMacro(m, NT_SHORT_NAME_LEN);
  PyModule_AddIntMacro(m, DIR_EXPORT);
  PyModule_AddIntMacro(m, DIR_IMPORT);
  PyModule_AddIntMacro(m, DIR_RESOURCE);
  PyModule_AddIntMacro(m, DIR_EXCEPTION);
  PyModule_AddIntMacro(m, DIR_SECURITY);
  PyModule_AddIntMacro(m, DIR_BASERELOC);
  PyModule_AddIntMacro(m, DIR_DEBUG);
  PyModule_AddIntMacro(m, DIR_ARCHITECTURE);
  PyModule_AddIntMacro(m, DIR_GLOBALPTR);
  PyModule_AddIntMacro(m, DIR_TLS);
  PyModule_AddIntMacro(m, DIR_LOAD_CONFIG);
  PyModule_AddIntMacro(m, DIR_BOUND_IMPORT);
  PyModule_AddIntMacro(m, DIR_IAT);
  PyModule_AddIntMacro(m, DIR_DELAY_IMPORT);
  PyModule_AddIntMacro(m, DIR_COM_DESCRIPTOR);

  PyModule_AddIntMacro(m, IMAGE_SCN_TYPE_NO_PAD);
  PyModule_AddIntMacro(m, IMAGE_SCN_CNT_CODE);
  PyModule_AddIntMacro(m, IMAGE_SCN_CNT_INITIALIZED_DATA);
  PyModule_AddIntMacro(m, IMAGE_SCN_CNT_UNINITIALIZED_DATA);
  PyModule_AddIntMacro(m, IMAGE_SCN_LNK_OTHER);
  PyModule_AddIntMacro(m, IMAGE_SCN_LNK_INFO);
  PyModule_AddIntMacro(m, IMAGE_SCN_LNK_REMOVE);
  PyModule_AddIntMacro(m, IMAGE_SCN_LNK_COMDAT);
  PyModule_AddIntMacro(m, IMAGE_SCN_NO_DEFER_SPEC_EXC);
  PyModule_AddIntMacro(m, IMAGE_SCN_GPREL);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_FARDATA);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_PURGEABLE);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_16BIT);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_LOCKED);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_PRELOAD);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_1BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_2BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_4BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_8BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_16BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_32BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_64BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_128BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_256BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_512BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_1024BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_2048BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_4096BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_8192BYTES);
  PyModule_AddIntMacro(m, IMAGE_SCN_ALIGN_MASK);
  PyModule_AddIntMacro(m, IMAGE_SCN_LNK_NRELOC_OVFL);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_DISCARDABLE);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_NOT_CACHED);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_NOT_PAGED);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_SHARED);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_EXECUTE);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_READ);
  PyModule_AddIntMacro(m, IMAGE_SCN_MEM_WRITE);

  return m;
}

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
#include <map>
#include <set>

#include <cassert>

std::optional<std::filesystem::path>
is_include(const std::string& line, const std::filesystem::path& filepath)
{
    // [ws] # [ws] include [ws] \".+\"
    auto iter = line.begin();
    while(iter < line.end())
    {
        if(*iter != ' ' && *iter != '\t')
        {
            break;
        }
        ++iter;
    }
    if(iter == line.end() || *iter != '#') {return std::nullopt;}

    assert(*iter == '#');
    ++iter;

    while(iter < line.end())
    {
        if(*iter != ' ' && *iter != '\t')
        {
            break;
        }
        ++iter;
    }
    if(iter == line.end() || *iter != 'i') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'n') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'c') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'l') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'u') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'd') {return std::nullopt;} else {++iter;}
    if(iter == line.end() || *iter != 'e') {return std::nullopt;} else {++iter;}

    while(iter < line.end())
    {
        if(*iter != ' ' && *iter != '\t')
        {
            break;
        }
        ++iter;
    }
    if(iter == line.end() || *iter != '"') {return std::nullopt;} else {++iter;}

    std::string filename;
    while(iter < line.end())
    {
        if(*iter == '"') {break;}
        filename += *iter;
        ++iter;
    }
    if(iter == line.end() || *iter != '"') {return std::nullopt;} else {++iter;}

    return std::filesystem::canonical(filepath.parent_path() / std::filesystem::path(filename));
}

struct File
{
    File() = default;

    explicit File(std::filesystem::path f)
        : filename(std::move(f))
    {
        std::ifstream ifs(filename);
        if( ! ifs.good())
        {
            throw std::runtime_error("file open error: " + filename.string());
        }

        std::string line;
        while(std::getline(ifs, line))
        {
            if(const auto incl = is_include(line, filename))
            {
                includes.push_back(incl.value());
            }
            else
            {
                content.push_back(line);
            }
        }
    }

    File(std::filesystem::path f, std::vector<std::string> c,
         std::vector<std::filesystem::path> i)
        : filename(std::move(f)), content(std::move(c)), includes(std::move(i))
    {}

    std::filesystem::path filename;
    std::vector<std::string> content; // w/o include
    std::vector<std::filesystem::path> includes;
};

struct Graph
{
    struct Node
    {
        std::vector<std::filesystem::path> included;
        std::vector<std::filesystem::path> includes;
    };

    std::map<std::filesystem::path, Node> nodes;
};

int main(int argc, char** argv)
{
    using namespace std::literals::string_literals;
    if(argc != 2)
    {
        std::cerr << "Usage: ./a.out path/to/toml.hpp > single_include/toml.hpp" << std::endl;
        return 1;
    }

    const auto input_file = std::filesystem::path(std::string(argv[1]));
    assert(input_file.filename() == "toml.hpp");

    const auto include_path = input_file.parent_path();

    // -------------------------------------------------------------------------
    // load files and detect `include "xxx.hpp"`.
    // If the file has `_fwd` and `_impl`, expand those files first.

    std::set<std::string> fwd_impl_files;
    for(const auto& entities : std::filesystem::directory_iterator(include_path/"toml11"/"fwd"))
    {
        if( ! entities.is_regular_file()) {continue;}
        std::string fname = entities.path().filename().string();
        if(fname.ends_with("_fwd.hpp"))
        {
            for(const auto c : "_fwd.hpp"s) {fname.pop_back(); (void)c;}
            fwd_impl_files.insert(std::move(fname));
        }
    }
    for(const auto& entities : std::filesystem::directory_iterator(include_path/"toml11"/"impl"))
    {
        if( ! entities.is_regular_file()) {continue;}
        std::string fname = entities.path().filename().string();
        if(fname.ends_with("_impl.hpp"))
        {
            for(const auto c : "_impl.hpp"s) {fname.pop_back(); (void)c;}
            // all impl files has fwd file
            assert(fwd_impl_files.contains(fname));
        }
    }

    const auto input = File(input_file);

    std::map<std::filesystem::path, File> files;
    files[input_file] = input;

    for(const auto& fname : input.includes)
    {
        if(fwd_impl_files.contains(fname.stem().string()))
        {
            std::cerr << "expanding fwd/impl file of " << fname.string() << std::endl;

            // expand the first include
            std::ifstream ifs(fname);

            std::vector<std::string> content;
            std::vector<std::filesystem::path> includes;

            std::string line;
            while(std::getline(ifs, line))
            {
                // expand _fwd and _impl files first.
                const auto incl = is_include(line, fname);
                if(incl.has_value())
                {
                    // if a file has _fwd/_impl files, it only includes fwd/impl files.
                    assert(incl.value().string().ends_with("_impl.hpp") ||
                           incl.value().string().ends_with("_fwd.hpp")  );

                    const File included(incl.value());
                    for(const auto& l : included.content)
                    {
                        content.push_back(l);
                    }
                    for(const auto& i : included.includes)
                    {
                        includes.push_back(i);
                    }
                }
                else
                {
                    content.push_back(line);
                }
            }
            files[fname] = File(fname, std::move(content), std::move(includes));
        }
        else
        {
            files[fname] = File(fname);
        }
        std::cerr << "file " << fname << " has " << files.at(fname).content.size() << " lines." << std::endl;
    }

    std::cerr << "-------------------------------------------------------------\n";
    std::cerr << "files have been read. next: constructing dependency graph...\n";
    std::cerr << "-------------------------------------------------------------\n";

    // -------------------------------------------------------------------------
    // construct dependency graph

    Graph g;
    for(const auto& [k, v] : files)
    {
        g.nodes[k] = Graph::Node{};
    }

    for(const auto& [fname, file] : files)
    {
        for(auto incl : file.includes)
        {
            auto incl_stem = incl.stem().string();
            if(incl_stem.ends_with("_fwd"))
            {
                for(const char c : "_fwd"s) {incl_stem.pop_back(); (void)c;}

                // include original file instaed
                incl = incl.parent_path() / ".." / std::filesystem::path(incl_stem + ".hpp");
            }
            else if(incl_stem.ends_with("_impl"))
            {
                for(const char c : "_impl"s) {incl_stem.pop_back(); (void)c;}

                // include original file instaed
                incl = incl.parent_path() / ".." / std::filesystem::path(incl_stem + ".hpp");
            }
            incl = std::filesystem::canonical(incl);

            // avoid self include loop
            if(fname != incl)
            {
                std::cerr << fname << " includes " << incl << std::endl;

                g.nodes.at(fname).includes.push_back(incl);
                g.nodes.at(incl) .included.push_back(fname);
            }
        }
    }

    std::cerr << "-------------------------------------------------------------\n";
    std::cerr << "graph has been constructed. flattening...\n";
    std::cerr << "-------------------------------------------------------------\n";

    // -------------------------------------------------------------------------
    // flatten graph by topological sort

    // collect files that does not include
    std::vector<std::filesystem::path> sources;
    for(const auto& [fname, node] : g.nodes)
    {
        if(node.includes.empty())
        {
            sources.push_back(fname);
        }
    }
    assert( ! sources.empty());

    std::vector<std::filesystem::path> sorted;
    while( ! sources.empty())
    {
        const auto file = sources.back();
        sorted.push_back(sources.back());
        sources.pop_back();

        for(const auto& included : g.nodes.at(file).included)
        {
            auto found = std::find(g.nodes.at(included).includes.begin(),
                                   g.nodes.at(included).includes.end(), file);
            g.nodes.at(included).includes.erase(found);

            if(g.nodes.at(included).includes.empty())
            {
                sources.push_back(included);
            }
        }
    }

    std::cerr << "-------------------------------------------------------------\n";
    std::cerr << "flattened. outputting...\n";
    std::cerr << "-------------------------------------------------------------\n";

    // -------------------------------------------------------------------------
    // output all the file in the sorted order

    for(const auto& fname : sorted)
    {
        std::cerr << "expanding: " << fname << std::endl;
        for(const auto& line : files.at(fname).content)
        {
            std::cout << line << '\n';
        }
    }

    return 0;
}

#include "config/config.hpp"
#include "extract/extract.hpp"
#include "utils/benchmark.hpp"
#include "utils/utils.hpp"

#include <unistd.h>

#include <exception>
#include <fstream>
#include <stdexcept>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <thread>

namespace {
    __attribute__((constructor)) void lsfgvk_init() {
        std::cerr << std::unitbuf;

        // read configuration
        const std::string file = Utils::getConfigFile();
        try {
            Config::updateConfig(file);
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n";
            std::cerr << "- " << e.what() << '\n';
            return; // default configuration will unload
        }

        const auto name = Utils::getProcessName();
        try {
            Config::activeConf = Config::getConfig(name);
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, IGNORING:\n";
            std::cerr << e.what() << '\n';
            return; // default configuration will unload
        }

        // exit silently if not enabled
        auto& conf = Config::activeConf;
        if (!conf.enable && name.second != "benchmark")
            return; // default configuration will unload

        // print config
        std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n";
        if (!conf.dll.empty()) std::cerr << "  Using DLL from: " << conf.dll << '\n';
        std::cerr << "  Multiplier: " << conf.multiplier << '\n';
        std::cerr << "  Flow Scale: " << conf.flowScale << '\n';
        std::cerr << "  Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n';
        std::cerr << "  HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n';
        if (conf.e_present != 2) std::cerr << "  ! Present Mode: " << conf.e_present << '\n';

        // remove mesa var in favor of config
        unsetenv("MESA_VK_WSI_PRESENT_MODE"); // NOLINT

        // write latest file
        try {
            std::ofstream latest("/tmp/lsfg-vk_last", std::ios::trunc);
            if (!latest.is_open())
                throw std::runtime_error("Failed to open /tmp/lsfg-vk_last for writing");
            latest << "exe: " << name.first << '\n';
            latest << "comm: " << name.second << '\n';
            latest << "pid: " << getpid() << '\n';
            if (!latest.good())
                throw std::runtime_error("Failed to write to /tmp/lsfg-vk_last");
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occurred while trying to write the latest file, exiting:\n";
            std::cerr << "- " << e.what() << '\n';
            exit(EXIT_FAILURE);
        }

        // load shaders
        try {
            Extract::extractShaders();
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n";
            std::cerr << "- " << e.what() << '\n';
            exit(EXIT_FAILURE);
        }
        std::cerr << "lsfg-vk: Shaders extracted successfully.\n";

        // run benchmark if requested
        const char* benchmark_flag = std::getenv("LSFG_BENCHMARK");
        if (!benchmark_flag)
            return;

        const std::string resolution(benchmark_flag);
        uint32_t width{};
        uint32_t height{};
        try {
            const size_t x = resolution.find('x');
            if (x == std::string::npos)
                throw std::runtime_error("Unable to find 'x' in benchmark string");

            const std::string width_str = resolution.substr(0, x);
            const std::string height_str = resolution.substr(x + 1);
            if (width_str.empty() || height_str.empty())
                throw std::runtime_error("Invalid resolution");

            const int32_t w = std::stoi(width_str);
            const int32_t h = std::stoi(height_str);
            if (w < 0 || h < 0)
                throw std::runtime_error("Resolution cannot be negative");

            width = static_cast<uint32_t>(w);
            height = static_cast<uint32_t>(h);
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n";
            std::cerr << "- " << e.what() << '\n';
            exit(EXIT_FAILURE);
        }

        std::thread benchmark([width, height]() {
            try {
                Benchmark::run(width, height);
            } catch (const std::exception& e) {
                std::cerr << "lsfg-vk: An error occurred during the benchmark:\n";
                std::cerr << "- " << e.what() << '\n';
                exit(EXIT_FAILURE);
            }
        });
        benchmark.detach();
        conf.enable = false;
    }
}

#include "utils/benchmark.hpp"
#include "config/config.hpp"
#include "extract/extract.hpp"
#include "extract/trans.hpp"

#include <vulkan/vulkan_core.h>
#include <lsfg_3_1.hpp>
#include <lsfg_3_1p.hpp>
#include <unistd.h>

#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <iomanip>
#include <thread>
#include <chrono>
#include <string>
#include <vector>

using namespace Benchmark;

void Benchmark::run(uint32_t width, uint32_t height) {
    const auto& conf = Config::activeConf;

    auto* lsfgInitialize = LSFG_3_1::initialize;
    auto* lsfgCreateContext = LSFG_3_1::createContext;
    auto* lsfgPresentContext = LSFG_3_1::presentContext;
    if (conf.performance) {
        lsfgInitialize = LSFG_3_1P::initialize;
        lsfgCreateContext = LSFG_3_1P::createContext;
        lsfgPresentContext = LSFG_3_1P::presentContext;
    }

    // create the benchmark context
    const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID");
    const uint64_t deviceUUID = lsfgDeviceUUID
        ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC;

    setenv("DISABLE_LSFG", "1", 1); // NOLINT

    Extract::extractShaders();
    lsfgInitialize(
        deviceUUID, // some magic number if not given
        conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1,
        [](const std::string& name) -> std::vector<uint8_t> {
            auto dxbc = Extract::getShader(name);
            auto spirv = Extract::translateShader(dxbc);
            return spirv;
        }
    );
    const int32_t ctx = lsfgCreateContext(-1, -1, {},
        { .width = width, .height = height },
        conf.hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM
    );

    unsetenv("DISABLE_LSFG"); // NOLINT

    // run the benchmark (run 8*n + 1 so the fences are waited on)
    const auto now = std::chrono::high_resolution_clock::now();
    const uint64_t iterations = 8 * 500UL;

    std::cerr << "lsfg-vk: Benchmark started, running " << iterations << " iterations...\n";
    for (uint64_t count = 0; count < iterations + 1; count++) {
        lsfgPresentContext(ctx, -1, {});

        if (count % 50 == 0 && count > 0)
            std::cerr << "lsfg-vk: "
                            << std::setprecision(2) << std::fixed
                            << static_cast<float>(count) / static_cast<float>(iterations) * 100.0F
                        << "% done (" << count + 1 << "/" << iterations << ")\r";
    }
    const auto then = std::chrono::high_resolution_clock::now();

    // print results
    const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(then - now).count();

    const auto perIteration = static_cast<float>(ms) / static_cast<float>(iterations);

    const uint64_t totalGen = (conf.multiplier - 1) * iterations;
    const auto genFps = static_cast<float>(totalGen) / (static_cast<float>(ms) / 1000.0F);

    const uint64_t totalFrames = iterations * conf.multiplier;
    const auto totalFps = static_cast<float>(totalFrames) / (static_cast<float>(ms) / 1000.0F);

    std::cerr << "lsfg-vk: Benchmark completed in " << ms << " ms\n";
    std::cerr << "  Time taken per real frame: "
              << std::setprecision(2) << std::fixed << perIteration << " ms\n";
    std::cerr << "  Generated " << totalGen << " frames in total at "
              << std::setprecision(2) << std::fixed << genFps << " FPS\n";
    std::cerr << "  Total of " << totalFrames << " frames presented at "
              << std::setprecision(2) << std::fixed << totalFps << " FPS\n";

    // sleep for a second, then exit
    std::this_thread::sleep_for(std::chrono::seconds(1));
    _exit(0);
}

#include "layer.hpp"
#include "common/exception.hpp"
#include "config/config.hpp"
#include "hooks.hpp"

#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>

#include <unordered_map>
#include <exception>
#include <iostream>
#include <cstdint>
#include <string>

namespace {
    PFN_vkCreateInstance  next_vkCreateInstance{};
    PFN_vkDestroyInstance next_vkDestroyInstance{};

    PFN_vkCreateDevice  next_vkCreateDevice{};
    PFN_vkDestroyDevice next_vkDestroyDevice{};

    PFN_vkSetDeviceLoaderData next_vSetDeviceLoaderData{};

    PFN_vkGetInstanceProcAddr next_vkGetInstanceProcAddr{};
    PFN_vkGetDeviceProcAddr   next_vkGetDeviceProcAddr{};

    PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{};
    PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{};
    PFN_vkGetPhysicalDeviceProperties next_vkGetPhysicalDeviceProperties{};
    PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR{};

    PFN_vkCreateSwapchainKHR  next_vkCreateSwapchainKHR{};
    PFN_vkQueuePresentKHR     next_vkQueuePresentKHR{};
    PFN_vkDestroySwapchainKHR next_vkDestroySwapchainKHR{};
    PFN_vkGetSwapchainImagesKHR  next_vkGetSwapchainImagesKHR{};
    PFN_vkAllocateCommandBuffers next_vkAllocateCommandBuffers{};
    PFN_vkFreeCommandBuffers     next_vkFreeCommandBuffers{};
    PFN_vkBeginCommandBuffer next_vkBeginCommandBuffer{};
    PFN_vkEndCommandBuffer   next_vkEndCommandBuffer{};
    PFN_vkCreateCommandPool  next_vkCreateCommandPool{};
    PFN_vkDestroyCommandPool next_vkDestroyCommandPool{};
    PFN_vkCreateImage  next_vkCreateImage{};
    PFN_vkDestroyImage next_vkDestroyImage{};
    PFN_vkGetImageMemoryRequirements next_vkGetImageMemoryRequirements{};
    PFN_vkBindImageMemory next_vkBindImageMemory{};
    PFN_vkAllocateMemory  next_vkAllocateMemory{};
    PFN_vkFreeMemory next_vkFreeMemory{};
    PFN_vkCreateSemaphore  next_vkCreateSemaphore{};
    PFN_vkDestroySemaphore next_vkDestroySemaphore{};
    PFN_vkGetMemoryFdKHR next_vkGetMemoryFdKHR{};
    PFN_vkGetSemaphoreFdKHR next_vkGetSemaphoreFdKHR{};
    PFN_vkGetDeviceQueue next_vkGetDeviceQueue{};
    PFN_vkQueueSubmit next_vkQueueSubmit{};
    PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{};
    PFN_vkCmdBlitImage next_vkCmdBlitImage{};
    PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{};

    template<typename T>
    bool initInstanceFunc(VkInstance instance, const char* name, T* func) {
        *func = reinterpret_cast<T>(next_vkGetInstanceProcAddr(instance, name));
        if (!*func) {
            std::cerr << "(no function pointer for " << name << ")\n";
            return false;
        }
        return true;
    }

    template<typename T>
    bool initDeviceFunc(VkDevice device, const char* name, T* func) {
        *func = reinterpret_cast<T>(next_vkGetDeviceProcAddr(device, name));
        if (!*func) {
            std::cerr << "(no function pointer for " << name << ")\n";
            return false;
        }
        return true;
    }
}

namespace {
    VkResult layer_vkCreateInstance(
            const VkInstanceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkInstance* pInstance) {
        try {
            // prepare layer | NOLINTBEGIN
            auto* layerDesc = const_cast<VkLayerInstanceCreateInfo*>(
                reinterpret_cast<const VkLayerInstanceCreateInfo*>(pCreateInfo->pNext));
            while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
                    || layerDesc->function != VK_LAYER_LINK_INFO)) {
                layerDesc = const_cast<VkLayerInstanceCreateInfo*>(
                    reinterpret_cast<const VkLayerInstanceCreateInfo*>(layerDesc->pNext));
            }
            if (!layerDesc)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "No layer creation info found in pNext chain");

            next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr;
            layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext;

            bool success = initInstanceFunc(nullptr, "vkCreateInstance", &next_vkCreateInstance);
            if (!success)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "Failed to get instance function pointer for vkCreateInstance");

            // NOLINTEND | skip initialization if the layer is disabled
            if (!Config::activeConf.enable) {
                auto res = next_vkCreateInstance(pCreateInfo, pAllocator, pInstance);
                initInstanceFunc(*pInstance, "vkCreateDevice", &next_vkCreateDevice);
                return res;
            }

            // create instance
            try {
                auto* createInstanceHook = reinterpret_cast<PFN_vkCreateInstance>(
                    Hooks::hooks["vkCreateInstance"]);
                auto res = createInstanceHook(pCreateInfo, pAllocator, pInstance);
                if (res != VK_SUCCESS)
                    throw LSFG::vulkan_error(res, "Unknown error");
            } catch (const std::exception& e) {
                throw LSFG::rethrowable_error("Failed to create Vulkan instance", e);
            }

            // get relevant function pointers from the next layer
            success = true;
            success &= initInstanceFunc(*pInstance,
                "vkDestroyInstance", &next_vkDestroyInstance);
            success &= initInstanceFunc(*pInstance,
                "vkCreateDevice", &next_vkCreateDevice); // workaround mesa bug
            success &= initInstanceFunc(*pInstance,
                "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties);
            success &= initInstanceFunc(*pInstance,
                "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties);
            success &= initInstanceFunc(*pInstance,
                "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties);
            success &= initInstanceFunc(*pInstance,
                "vkGetPhysicalDeviceSurfaceCapabilitiesKHR", &next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
            if (!success)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "Failed to get instance function pointers");

            std::cerr << "lsfg-vk: Vulkan instance layer initialized successfully.\n";
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan instance layer:\n";
            std::cerr << "- " << e.what() << '\n';
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        return VK_SUCCESS;
    }

    VkResult layer_vkCreateDevice( // NOLINTBEGIN
            VkPhysicalDevice physicalDevice,
            const VkDeviceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkDevice* pDevice) {
        try {
            // prepare layer | NOLINTBEGIN
            auto* layerDesc = const_cast<VkLayerDeviceCreateInfo*>(
                reinterpret_cast<const VkLayerDeviceCreateInfo*>(pCreateInfo->pNext));
            while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
                    || layerDesc->function != VK_LAYER_LINK_INFO)) {
                layerDesc = const_cast<VkLayerDeviceCreateInfo*>(
                    reinterpret_cast<const VkLayerDeviceCreateInfo*>(layerDesc->pNext));
            }
            if (!layerDesc)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "No layer creation info found in pNext chain");

            next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr;
            layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext;

            auto* layerDesc2 = const_cast<VkLayerDeviceCreateInfo*>(
                reinterpret_cast<const VkLayerDeviceCreateInfo*>(pCreateInfo->pNext));
            while (layerDesc2 && (layerDesc2->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
                    || layerDesc2->function != VK_LOADER_DATA_CALLBACK)) {
                        layerDesc2 = const_cast<VkLayerDeviceCreateInfo*>(
                            reinterpret_cast<const VkLayerDeviceCreateInfo*>(layerDesc2->pNext));
            }
            if (!layerDesc2)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "No layer device loader data found in pNext chain");

            next_vSetDeviceLoaderData = layerDesc2->u.pfnSetDeviceLoaderData;

            // NOLINTEND | skip initialization if the layer is disabled
            if (!Config::activeConf.enable)
                return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice);

            // create device
            try {
                auto* createDeviceHook = reinterpret_cast<PFN_vkCreateDevice>(
                    Hooks::hooks["vkCreateDevicePre"]);
                auto res = createDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice);
                if (res != VK_SUCCESS)
                    throw LSFG::vulkan_error(res, "Unknown error");
            } catch (const std::exception& e) {
                throw LSFG::rethrowable_error("Failed to create Vulkan device", e);
            }

            // get relevant function pointers from the next layer
            bool success = true;
            success &= initDeviceFunc(*pDevice, "vkDestroyDevice", &next_vkDestroyDevice);
            success &= initDeviceFunc(*pDevice, "vkCreateSwapchainKHR", &next_vkCreateSwapchainKHR);
            success &= initDeviceFunc(*pDevice, "vkQueuePresentKHR", &next_vkQueuePresentKHR);
            success &= initDeviceFunc(*pDevice, "vkDestroySwapchainKHR", &next_vkDestroySwapchainKHR);
            success &= initDeviceFunc(*pDevice, "vkGetSwapchainImagesKHR", &next_vkGetSwapchainImagesKHR);
            success &= initDeviceFunc(*pDevice, "vkAllocateCommandBuffers", &next_vkAllocateCommandBuffers);
            success &= initDeviceFunc(*pDevice, "vkFreeCommandBuffers", &next_vkFreeCommandBuffers);
            success &= initDeviceFunc(*pDevice, "vkBeginCommandBuffer", &next_vkBeginCommandBuffer);
            success &= initDeviceFunc(*pDevice, "vkEndCommandBuffer", &next_vkEndCommandBuffer);
            success &= initDeviceFunc(*pDevice, "vkCreateCommandPool", &next_vkCreateCommandPool);
            success &= initDeviceFunc(*pDevice, "vkDestroyCommandPool", &next_vkDestroyCommandPool);
            success &= initDeviceFunc(*pDevice, "vkCreateImage", &next_vkCreateImage);
            success &= initDeviceFunc(*pDevice, "vkDestroyImage", &next_vkDestroyImage);
            success &= initDeviceFunc(*pDevice, "vkGetImageMemoryRequirements", &next_vkGetImageMemoryRequirements);
            success &= initDeviceFunc(*pDevice, "vkBindImageMemory", &next_vkBindImageMemory);
            success &= initDeviceFunc(*pDevice, "vkGetMemoryFdKHR", &next_vkGetMemoryFdKHR);
            success &= initDeviceFunc(*pDevice, "vkAllocateMemory", &next_vkAllocateMemory);
            success &= initDeviceFunc(*pDevice, "vkFreeMemory", &next_vkFreeMemory);
            success &= initDeviceFunc(*pDevice, "vkCreateSemaphore", &next_vkCreateSemaphore);
            success &= initDeviceFunc(*pDevice, "vkDestroySemaphore", &next_vkDestroySemaphore);
            success &= initDeviceFunc(*pDevice, "vkGetSemaphoreFdKHR", &next_vkGetSemaphoreFdKHR);
            success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue);
            success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit);
            success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier);
            success &= initDeviceFunc(*pDevice, "vkCmdBlitImage", &next_vkCmdBlitImage);
            success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR);
            if (!success)
                throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
                    "Failed to get device function pointers");

            auto postCreateDeviceHook = reinterpret_cast<PFN_vkCreateDevice>(
                Hooks::hooks["vkCreateDevicePost"]);
            auto res = postCreateDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice);
            if (res != VK_SUCCESS)
                throw LSFG::vulkan_error(res, "Unknown error");

            std::cerr << "lsfg-vk: Vulkan device layer initialized successfully.\n";
        } catch (const std::exception& e) {
            std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan device layer:\n";
            std::cerr << "- " << e.what() << '\n';
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        return VK_SUCCESS;
    } // NOLINTEND
}

const std::unordered_map<std::string, PFN_vkVoidFunction> layerFunctions = {
    { "vkCreateInstance",
        reinterpret_cast<PFN_vkVoidFunction>(&layer_vkCreateInstance) },
    { "vkCreateDevice",
        reinterpret_cast<PFN_vkVoidFunction>(&layer_vkCreateDevice) },
    { "vkGetInstanceProcAddr",
        reinterpret_cast<PFN_vkVoidFunction>(&layer_vkGetInstanceProcAddr) },
    { "vkGetDeviceProcAddr",
        reinterpret_cast<PFN_vkVoidFunction>(&layer_vkGetDeviceProcAddr) },
};

PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName) {
    const std::string name(pName);
    auto it = layerFunctions.find(name);
    if (it != layerFunctions.end())
        return it->second;

    it = Hooks::hooks.find(name);
    if (it != Hooks::hooks.end() && Config::activeConf.enable)
        return it->second;

    return next_vkGetInstanceProcAddr(instance, pName);
}

PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) {
    const std::string name(pName);
    auto it = layerFunctions.find(name);
    if (it != layerFunctions.end())
        return it->second;

    it = Hooks::hooks.find(name);
    if (it != Hooks::hooks.end() && Config::activeConf.enable)
        return it->second;

    return next_vkGetDeviceProcAddr(device, pName);
}

// original functions
namespace Layer {
    VkResult ovkCreateInstance(
            const VkInstanceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkInstance* pInstance) {
        return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance);
    }
    void ovkDestroyInstance(
            VkInstance instance,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroyInstance(instance, pAllocator);
    }

    VkResult ovkCreateDevice(
            VkPhysicalDevice physicalDevice,
            const VkDeviceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkDevice* pDevice) {
        return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice);
    }
    void ovkDestroyDevice(
            VkDevice device,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroyDevice(device, pAllocator);
    }

    VkResult ovkSetDeviceLoaderData(VkDevice device, void* object) {
        return next_vSetDeviceLoaderData(device, object);
    }

    PFN_vkVoidFunction ovkGetInstanceProcAddr(
            VkInstance instance,
            const char* pName) {
        return next_vkGetInstanceProcAddr(instance, pName);
    }
    PFN_vkVoidFunction ovkGetDeviceProcAddr(
            VkDevice device,
            const char* pName) {
        return next_vkGetDeviceProcAddr(device, pName);
    }

    void ovkGetPhysicalDeviceQueueFamilyProperties(
            VkPhysicalDevice physicalDevice,
            uint32_t* pQueueFamilyPropertyCount,
            VkQueueFamilyProperties* pQueueFamilyProperties) {
        next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties);
    }
    void ovkGetPhysicalDeviceMemoryProperties(
            VkPhysicalDevice physicalDevice,
            VkPhysicalDeviceMemoryProperties* pMemoryProperties) {
        next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
    }
    void ovkGetPhysicalDeviceProperties(
            VkPhysicalDevice physicalDevice,
            VkPhysicalDeviceProperties* pProperties) {
        next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties);
    }
    VkResult ovkGetPhysicalDeviceSurfaceCapabilitiesKHR(
            VkPhysicalDevice physicalDevice,
            VkSurfaceKHR surface,
            VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) {
        return next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, pSurfaceCapabilities);
    }

    VkResult ovkCreateSwapchainKHR(
            VkDevice device,
            const VkSwapchainCreateInfoKHR* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkSwapchainKHR* pSwapchain) {
        return next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain);
    }
    VkResult ovkQueuePresentKHR(
            VkQueue queue,
            const VkPresentInfoKHR* pPresentInfo) {
        return next_vkQueuePresentKHR(queue, pPresentInfo);
    }
    void ovkDestroySwapchainKHR(
            VkDevice device,
            VkSwapchainKHR swapchain,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroySwapchainKHR(device, swapchain, pAllocator);
    }

    VkResult ovkGetSwapchainImagesKHR(
            VkDevice device,
            VkSwapchainKHR swapchain,
            uint32_t* pSwapchainImageCount,
            VkImage* pSwapchainImages) {
        return next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages);
    }

    VkResult ovkAllocateCommandBuffers(
            VkDevice device,
            const VkCommandBufferAllocateInfo* pAllocateInfo,
            VkCommandBuffer* pCommandBuffers) {
        return next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers);
    }
    void ovkFreeCommandBuffers(
            VkDevice device,
            VkCommandPool commandPool,
            uint32_t commandBufferCount,
            const VkCommandBuffer* pCommandBuffers) {
        next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers);
    }

    VkResult ovkBeginCommandBuffer(
            VkCommandBuffer commandBuffer,
            const VkCommandBufferBeginInfo* pBeginInfo) {
        return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo);
    }
    VkResult ovkEndCommandBuffer(
            VkCommandBuffer commandBuffer) {
        return next_vkEndCommandBuffer(commandBuffer);
    }

    VkResult ovkCreateCommandPool(
            VkDevice device,
            const VkCommandPoolCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkCommandPool* pCommandPool) {
        return  next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool);
    }
    void ovkDestroyCommandPool(
            VkDevice device,
            VkCommandPool commandPool,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroyCommandPool(device, commandPool, pAllocator);
    }

    VkResult ovkCreateImage(
            VkDevice device,
            const VkImageCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkImage* pImage) {
        return next_vkCreateImage(device, pCreateInfo, pAllocator, pImage);
    }
    void ovkDestroyImage(
            VkDevice device,
            VkImage image,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroyImage(device, image, pAllocator);
    }

    void ovkGetImageMemoryRequirements(
            VkDevice device,
            VkImage image,
            VkMemoryRequirements* pMemoryRequirements) {
        next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements);
    }
    VkResult ovkBindImageMemory(
            VkDevice device,
            VkImage image,
            VkDeviceMemory memory,
            VkDeviceSize memoryOffset) {
        return next_vkBindImageMemory(device, image, memory, memoryOffset);
    }

    VkResult ovkAllocateMemory(
            VkDevice device,
            const VkMemoryAllocateInfo* pAllocateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkDeviceMemory* pMemory) {
        return next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
    }
    void ovkFreeMemory(
            VkDevice device,
            VkDeviceMemory memory,
            const VkAllocationCallbacks* pAllocator) {
        next_vkFreeMemory(device, memory, pAllocator);
    }

    VkResult ovkCreateSemaphore(
            VkDevice device,
            const VkSemaphoreCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkSemaphore* pSemaphore) {
        return next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore);
    }
    void ovkDestroySemaphore(
            VkDevice device,
            VkSemaphore semaphore,
            const VkAllocationCallbacks* pAllocator) {
        next_vkDestroySemaphore(device, semaphore, pAllocator);
    }

    VkResult ovkGetMemoryFdKHR(
            VkDevice device,
            const VkMemoryGetFdInfoKHR* pGetFdInfo,
            int* pFd) {
        return next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd);
    }
    VkResult ovkGetSemaphoreFdKHR(
            VkDevice device,
            const VkSemaphoreGetFdInfoKHR* pGetFdInfo,
            int* pFd) {
        return next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd);
    }

    void ovkGetDeviceQueue(
            VkDevice device,
            uint32_t queueFamilyIndex,
            uint32_t queueIndex,
            VkQueue* pQueue) {
        next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue);
    }
    VkResult ovkQueueSubmit(
            VkQueue queue,
            uint32_t submitCount,
            const VkSubmitInfo* pSubmits,
            VkFence fence) {
        return next_vkQueueSubmit(queue, submitCount, pSubmits, fence);
    }

    void ovkCmdPipelineBarrier(
            VkCommandBuffer commandBuffer,
            VkPipelineStageFlags srcStageMask,
            VkPipelineStageFlags dstStageMask,
            VkDependencyFlags dependencyFlags,
            uint32_t memoryBarrierCount,
            const VkMemoryBarrier* pMemoryBarriers,
            uint32_t bufferMemoryBarrierCount,
            const VkBufferMemoryBarrier* pBufferMemoryBarriers,
            uint32_t imageMemoryBarrierCount,
            const VkImageMemoryBarrier* pImageMemoryBarriers) {
        next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags,
            memoryBarrierCount, pMemoryBarriers,
            bufferMemoryBarrierCount, pBufferMemoryBarriers,
            imageMemoryBarrierCount, pImageMemoryBarriers);
    }
    void ovkCmdBlitImage(
            VkCommandBuffer commandBuffer,
            VkImage srcImage,
            VkImageLayout srcImageLayout,
            VkImage dstImage,
            VkImageLayout dstImageLayout,
            uint32_t regionCount,
            const VkImageBlit* pRegions,
            VkFilter filter) {
        next_vkCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter);
    }

    VkResult ovkAcquireNextImageKHR(
            VkDevice device,
            VkSwapchainKHR swapchain,
            uint64_t timeout,
            VkSemaphore semaphore,
            VkFence fence,
            uint32_t* pImageIndex) {
        return next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex);
    }
}

/*
The MIT License (MIT)

Copyright (c) 2013 Andrew Ruef

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#pragma once

#include <cstdint>
#include <string>
#include <vector>

// need to pack these structure definitions

// some constant definitions
// clang-format off
namespace peparse {
constexpr std::uint32_t RICH_MAGIC_END = 0x68636952;
constexpr std::uint32_t RICH_MAGIC_START = 0x536e6144;
constexpr std::uint32_t RICH_OFFSET = 0x80;
constexpr std::uint16_t MZ_MAGIC = 0x5A4D;
constexpr std::uint32_t NT_MAGIC = 0x00004550;
constexpr std::uint16_t NUM_DIR_ENTRIES = 16;
constexpr std::uint16_t NT_OPTIONAL_32_MAGIC = 0x10B;
constexpr std::uint16_t NT_OPTIONAL_64_MAGIC = 0x20B;
constexpr std::uint16_t NT_SHORT_NAME_LEN = 8;
constexpr std::uint16_t SYMTAB_RECORD_LEN = 18;

#ifndef _PEPARSE_WINDOWS_CONFLICTS
// Machine Types
constexpr std::uint16_t IMAGE_FILE_MACHINE_UNKNOWN = 0x0;
constexpr std::uint16_t IMAGE_FILE_MACHINE_ALPHA = 0x184;     // Alpha_AXP
constexpr std::uint16_t IMAGE_FILE_MACHINE_ALPHA64 = 0x284;   // ALPHA64
constexpr std::uint16_t IMAGE_FILE_MACHINE_AM33 = 0x1d3;      // Matsushita AM33
constexpr std::uint16_t IMAGE_FILE_MACHINE_AMD64 = 0x8664;    // x64
constexpr std::uint16_t IMAGE_FILE_MACHINE_ARM = 0x1c0;       // ARM little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_ARM64 = 0xaa64;    // ARM64 little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_ARMNT = 0x1c4;     // ARM Thumb-2 little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_AXP64 = 0x284;     // ALPHA64
constexpr std::uint16_t IMAGE_FILE_MACHINE_CEE = 0xc0ee;
constexpr std::uint16_t IMAGE_FILE_MACHINE_CEF = 0xcef;
constexpr std::uint16_t IMAGE_FILE_MACHINE_EBC = 0xebc;       // EFI byte code
constexpr std::uint16_t IMAGE_FILE_MACHINE_I386 = 0x14c;      // Intel 386 or later processors and compatible processors
constexpr std::uint16_t IMAGE_FILE_MACHINE_IA64 = 0x200;      // Intel Itanium processor family
constexpr std::uint16_t IMAGE_FILE_MACHINE_LOONGARCH32 = 0x6232; // LoongArch 32-bit address space
constexpr std::uint16_t IMAGE_FILE_MACHINE_LOONGARCH64 = 0x6264; // LoongArch 64-bit address space
constexpr std::uint16_t IMAGE_FILE_MACHINE_M32R = 0x9041;     // Mitsubishi M32R little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_MIPS16 = 0x266;    // MIPS16
constexpr std::uint16_t IMAGE_FILE_MACHINE_MIPSFPU = 0x366;   // MIPS with FPU
constexpr std::uint16_t IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466; // MIPS16 with FPU
constexpr std::uint16_t IMAGE_FILE_MACHINE_POWERPC = 0x1f0;   // Power PC little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1; // Power PC with floating point support
constexpr std::uint16_t IMAGE_FILE_MACHINE_POWERPCBE = 0x1f2; // Power PC big endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_R3000 = 0x162;     // MIPS little endian, 0x160 big-endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_R4000 = 0x166;     // MIPS little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_R10000 = 0x168;    // MIPS little endian
constexpr std::uint16_t IMAGE_FILE_MACHINE_RISCV32 = 0x5032;  // RISC-V 32-bit address space
constexpr std::uint16_t IMAGE_FILE_MACHINE_RISCV64 = 0x5064;  // RISC-V 64-bit address space
constexpr std::uint16_t IMAGE_FILE_MACHINE_RISCV128 = 0x5128; // RISC-V 128-bit address space
constexpr std::uint16_t IMAGE_FILE_MACHINE_SH3 = 0x1a2;       // Hitachi SH3
constexpr std::uint16_t IMAGE_FILE_MACHINE_SH3DSP = 0x1a3;    // Hitachi SH3 DSP
constexpr std::uint16_t IMAGE_FILE_MACHINE_SH3E = 0x1a4;      // Hitachi SH3E
constexpr std::uint16_t IMAGE_FILE_MACHINE_SH4 = 0x1a6;       // Hitachi SH4
constexpr std::uint16_t IMAGE_FILE_MACHINE_SH5 = 0x1a8;       // Hitachi SH5
constexpr std::uint16_t IMAGE_FILE_MACHINE_THUMB = 0x1c2;     // Thumb
constexpr std::uint16_t IMAGE_FILE_MACHINE_TRICORE = 0x520;   // Infineon
constexpr std::uint16_t IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169; // MIPS little-endian WCE v2

constexpr std::uint16_t IMAGE_FILE_RELOCS_STRIPPED = 0x0001;
constexpr std::uint16_t IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002;
constexpr std::uint16_t IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004;
constexpr std::uint16_t IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008;
constexpr std::uint16_t IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010;
constexpr std::uint16_t IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020;
constexpr std::uint16_t IMAGE_FILE_BYTES_REVERSED_LO = 0x0080;
constexpr std::uint16_t IMAGE_FILE_32BIT_MACHINE = 0x0100;
constexpr std::uint16_t IMAGE_FILE_DEBUG_STRIPPED = 0x0200;
constexpr std::uint16_t IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400;
constexpr std::uint16_t IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800;
constexpr std::uint16_t IMAGE_FILE_SYSTEM = 0x1000;
constexpr std::uint16_t IMAGE_FILE_DLL = 0x2000;
constexpr std::uint16_t IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000;
constexpr std::uint16_t IMAGE_FILE_BYTES_REVERSED_HI = 0x8000;

constexpr std::uint32_t IMAGE_SCN_TYPE_NO_PAD = 0x00000008;
constexpr std::uint32_t IMAGE_SCN_CNT_CODE = 0x00000020;
constexpr std::uint32_t IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040;
constexpr std::uint32_t IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080;
constexpr std::uint32_t IMAGE_SCN_LNK_OTHER = 0x00000100;
constexpr std::uint32_t IMAGE_SCN_LNK_INFO = 0x00000200;
constexpr std::uint32_t IMAGE_SCN_LNK_REMOVE = 0x00000800;
constexpr std::uint32_t IMAGE_SCN_LNK_COMDAT = 0x00001000;
constexpr std::uint32_t IMAGE_SCN_NO_DEFER_SPEC_EXC = 0x00004000;
constexpr std::uint32_t IMAGE_SCN_GPREL = 0x00008000;
constexpr std::uint32_t IMAGE_SCN_MEM_FARDATA = 0x00008000;
constexpr std::uint32_t IMAGE_SCN_MEM_PURGEABLE = 0x00020000;
constexpr std::uint32_t IMAGE_SCN_MEM_16BIT = 0x00020000;
constexpr std::uint32_t IMAGE_SCN_MEM_LOCKED = 0x00040000;
constexpr std::uint32_t IMAGE_SCN_MEM_PRELOAD = 0x00080000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_1BYTES = 0x00100000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_2BYTES = 0x00200000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_4BYTES = 0x00300000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_8BYTES = 0x00400000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_16BYTES = 0x00500000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_32BYTES = 0x00600000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_64BYTES = 0x00700000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_128BYTES = 0x00800000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_256BYTES = 0x00900000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_512BYTES = 0x00A00000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000;
constexpr std::uint32_t IMAGE_SCN_ALIGN_MASK = 0x00F00000;
constexpr std::uint32_t IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000;
constexpr std::uint32_t IMAGE_SCN_MEM_DISCARDABLE = 0x02000000;
constexpr std::uint32_t IMAGE_SCN_MEM_NOT_CACHED = 0x04000000;
constexpr std::uint32_t IMAGE_SCN_MEM_NOT_PAGED = 0x08000000;
constexpr std::uint32_t IMAGE_SCN_MEM_SHARED = 0x10000000;
constexpr std::uint32_t IMAGE_SCN_MEM_EXECUTE = 0x20000000;
constexpr std::uint32_t IMAGE_SCN_MEM_READ = 0x40000000;
constexpr std::uint32_t IMAGE_SCN_MEM_WRITE = 0x80000000;

constexpr std::uint16_t IMAGE_SUBSYSTEM_UNKNOWN = 0;
constexpr std::uint16_t IMAGE_SUBSYSTEM_NATIVE = 1;
constexpr std::uint16_t IMAGE_SUBSYSTEM_WINDOWS_GUI = 2;
constexpr std::uint16_t IMAGE_SUBSYSTEM_WINDOWS_CUI = 3;
constexpr std::uint16_t IMAGE_SUBSYSTEM_OS2_CUI = 5;
constexpr std::uint16_t IMAGE_SUBSYSTEM_POSIX_CUI = 7;
constexpr std::uint16_t IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8;
constexpr std::uint16_t IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9;
constexpr std::uint16_t IMAGE_SUBSYSTEM_EFI_APPLICATION = 10;
constexpr std::uint16_t IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11;
constexpr std::uint16_t IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12;
constexpr std::uint16_t IMAGE_SUBSYSTEM_EFI_ROM = 13;
constexpr std::uint16_t IMAGE_SUBSYSTEM_XBOX = 14;
constexpr std::uint16_t IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16;
constexpr std::uint16_t IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG = 17;

// Symbol section number values
constexpr std::int16_t IMAGE_SYM_UNDEFINED = 0;
constexpr std::int16_t IMAGE_SYM_ABSOLUTE = -1;
constexpr std::int16_t IMAGE_SYM_DEBUG = -2;

// Symbol table types
constexpr std::uint16_t IMAGE_SYM_TYPE_NULL = 0;
constexpr std::uint16_t IMAGE_SYM_TYPE_VOID = 1;
constexpr std::uint16_t IMAGE_SYM_TYPE_CHAR = 2;
constexpr std::uint16_t IMAGE_SYM_TYPE_SHORT = 3;
constexpr std::uint16_t IMAGE_SYM_TYPE_INT = 4;
constexpr std::uint16_t IMAGE_SYM_TYPE_LONG = 5;
constexpr std::uint16_t IMAGE_SYM_TYPE_FLOAT = 6;
constexpr std::uint16_t IMAGE_SYM_TYPE_DOUBLE = 7;
constexpr std::uint16_t IMAGE_SYM_TYPE_STRUCT = 8;
constexpr std::uint16_t IMAGE_SYM_TYPE_UNION = 9;
constexpr std::uint16_t IMAGE_SYM_TYPE_ENUM = 10;
constexpr std::uint16_t IMAGE_SYM_TYPE_MOE = 11;
constexpr std::uint16_t IMAGE_SYM_TYPE_BYTE = 12;
constexpr std::uint16_t IMAGE_SYM_TYPE_WORD = 13;
constexpr std::uint16_t IMAGE_SYM_TYPE_UINT = 14;
constexpr std::uint16_t IMAGE_SYM_TYPE_DWORD = 15;
constexpr std::uint16_t IMAGE_SYM_DTYPE_NULL = 0;
constexpr std::uint16_t IMAGE_SYM_DTYPE_POINTER = 1;
constexpr std::uint16_t IMAGE_SYM_DTYPE_FUNCTION = 2;
constexpr std::uint16_t IMAGE_SYM_DTYPE_ARRAY = 3;

// Symbol table storage classes
constexpr std::uint8_t IMAGE_SYM_CLASS_END_OF_FUNCTION = static_cast<std::uint8_t>(-1);
constexpr std::uint8_t IMAGE_SYM_CLASS_NULL = 0;
constexpr std::uint8_t IMAGE_SYM_CLASS_AUTOMATIC = 1;
constexpr std::uint8_t IMAGE_SYM_CLASS_EXTERNAL = 2;
constexpr std::uint8_t IMAGE_SYM_CLASS_STATIC = 3;
constexpr std::uint8_t IMAGE_SYM_CLASS_REGISTER = 4;
constexpr std::uint8_t IMAGE_SYM_CLASS_EXTERNAL_DEF = 5;
constexpr std::uint8_t IMAGE_SYM_CLASS_LABEL = 6;
constexpr std::uint8_t IMAGE_SYM_CLASS_UNDEFINED_LABEL = 7;
constexpr std::uint8_t IMAGE_SYM_CLASS_MEMBER_OF_STRUCT = 8;
constexpr std::uint8_t IMAGE_SYM_CLASS_ARGUMENT = 9;
constexpr std::uint8_t IMAGE_SYM_CLASS_STRUCT_TAG = 10;
constexpr std::uint8_t IMAGE_SYM_CLASS_MEMBER_OF_UNION = 11;
constexpr std::uint8_t IMAGE_SYM_CLASS_UNION_TAG = 12;
constexpr std::uint8_t IMAGE_SYM_CLASS_TYPE_DEFINITION = 13;
constexpr std::uint8_t IMAGE_SYM_CLASS_UNDEFINED_STATIC = 14;
constexpr std::uint8_t IMAGE_SYM_CLASS_ENUM_TAG = 15;
constexpr std::uint8_t IMAGE_SYM_CLASS_MEMBER_OF_ENUM = 16;
constexpr std::uint8_t IMAGE_SYM_CLASS_REGISTER_PARAM = 17;
constexpr std::uint8_t IMAGE_SYM_CLASS_BIT_FIELD = 18;
constexpr std::uint8_t IMAGE_SYM_CLASS_BLOCK = 100;
constexpr std::uint8_t IMAGE_SYM_CLASS_FUNCTION = 101;
constexpr std::uint8_t IMAGE_SYM_CLASS_END_OF_STRUCT = 102;
constexpr std::uint8_t IMAGE_SYM_CLASS_FILE = 103;
constexpr std::uint8_t IMAGE_SYM_CLASS_SECTION = 104;
constexpr std::uint8_t IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105;
constexpr std::uint8_t IMAGE_SYM_CLASS_CLR_TOKEN = 107;

// Optional header DLL characteristics
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000;
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000;

// Extended DLL characteristics
constexpr std::uint16_t IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT = 0x0001;
#endif
// clang-format on

struct dos_header {
  std::uint16_t e_magic;
  std::uint16_t e_cblp;
  std::uint16_t e_cp;
  std::uint16_t e_crlc;
  std::uint16_t e_cparhdr;
  std::uint16_t e_minalloc;
  std::uint16_t e_maxalloc;
  std::uint16_t e_ss;
  std::uint16_t e_sp;
  std::uint16_t e_csum;
  std::uint16_t e_ip;
  std::uint16_t e_cs;
  std::uint16_t e_lfarlc;
  std::uint16_t e_ovno;
  std::uint16_t e_res[4];
  std::uint16_t e_oemid;
  std::uint16_t e_oeminfo;
  std::uint16_t e_res2[10];
  std::uint32_t e_lfanew;
};

struct file_header {
  std::uint16_t Machine;
  std::uint16_t NumberOfSections;
  std::uint32_t TimeDateStamp;
  std::uint32_t PointerToSymbolTable;
  std::uint32_t NumberOfSymbols;
  std::uint16_t SizeOfOptionalHeader;
  std::uint16_t Characteristics;
};

struct data_directory {
  std::uint32_t VirtualAddress;
  std::uint32_t Size;
};

enum data_directory_kind {
  DIR_EXPORT = 0,
  DIR_IMPORT = 1,
  DIR_RESOURCE = 2,
  DIR_EXCEPTION = 3,
  DIR_SECURITY = 4,
  DIR_BASERELOC = 5,
  DIR_DEBUG = 6,
  DIR_ARCHITECTURE = 7,
  DIR_GLOBALPTR = 8,
  DIR_TLS = 9,
  DIR_LOAD_CONFIG = 10,
  DIR_BOUND_IMPORT = 11,
  DIR_IAT = 12,
  DIR_DELAY_IMPORT = 13,
  DIR_COM_DESCRIPTOR = 14,
  DIR_RESERVED = 15,
};

struct optional_header_32 {
  std::uint16_t Magic;
  std::uint8_t MajorLinkerVersion;
  std::uint8_t MinorLinkerVersion;
  std::uint32_t SizeOfCode;
  std::uint32_t SizeOfInitializedData;
  std::uint32_t SizeOfUninitializedData;
  std::uint32_t AddressOfEntryPoint;
  std::uint32_t BaseOfCode;
  std::uint32_t BaseOfData;
  std::uint32_t ImageBase;
  std::uint32_t SectionAlignment;
  std::uint32_t FileAlignment;
  std::uint16_t MajorOperatingSystemVersion;
  std::uint16_t MinorOperatingSystemVersion;
  std::uint16_t MajorImageVersion;
  std::uint16_t MinorImageVersion;
  std::uint16_t MajorSubsystemVersion;
  std::uint16_t MinorSubsystemVersion;
  std::uint32_t Win32VersionValue;
  std::uint32_t SizeOfImage;
  std::uint32_t SizeOfHeaders;
  std::uint32_t CheckSum;
  std::uint16_t Subsystem;
  std::uint16_t DllCharacteristics;
  std::uint32_t SizeOfStackReserve;
  std::uint32_t SizeOfStackCommit;
  std::uint32_t SizeOfHeapReserve;
  std::uint32_t SizeOfHeapCommit;
  std::uint32_t LoaderFlags;
  std::uint32_t NumberOfRvaAndSizes;
  data_directory DataDirectory[NUM_DIR_ENTRIES];
};

/*
 * This is used for PE32+ binaries. It is similar to optional_header_32
 * except some fields don't exist here (BaseOfData), and others are bigger.
 */
struct optional_header_64 {
  std::uint16_t Magic;
  std::uint8_t MajorLinkerVersion;
  std::uint8_t MinorLinkerVersion;
  std::uint32_t SizeOfCode;
  std::uint32_t SizeOfInitializedData;
  std::uint32_t SizeOfUninitializedData;
  std::uint32_t AddressOfEntryPoint;
  std::uint32_t BaseOfCode;
  std::uint64_t ImageBase;
  std::uint32_t SectionAlignment;
  std::uint32_t FileAlignment;
  std::uint16_t MajorOperatingSystemVersion;
  std::uint16_t MinorOperatingSystemVersion;
  std::uint16_t MajorImageVersion;
  std::uint16_t MinorImageVersion;
  std::uint16_t MajorSubsystemVersion;
  std::uint16_t MinorSubsystemVersion;
  std::uint32_t Win32VersionValue;
  std::uint32_t SizeOfImage;
  std::uint32_t SizeOfHeaders;
  std::uint32_t CheckSum;
  std::uint16_t Subsystem;
  std::uint16_t DllCharacteristics;
  std::uint64_t SizeOfStackReserve;
  std::uint64_t SizeOfStackCommit;
  std::uint64_t SizeOfHeapReserve;
  std::uint64_t SizeOfHeapCommit;
  std::uint32_t LoaderFlags;
  std::uint32_t NumberOfRvaAndSizes;
  data_directory DataDirectory[NUM_DIR_ENTRIES];
};

struct nt_header_32 {
  std::uint32_t Signature;
  file_header FileHeader;
  optional_header_32 OptionalHeader;
  optional_header_64 OptionalHeader64;
  std::uint16_t OptionalMagic;
};

struct rich_entry {
  std::uint16_t ProductId;
  std::uint16_t BuildNumber;
  std::uint32_t Count;
};

struct rich_header {
  std::uint32_t StartSignature;
  std::vector<rich_entry> Entries;
  std::uint32_t EndSignature;
  std::uint32_t DecryptionKey;
  std::uint32_t Checksum;
  bool isPresent;
  bool isValid;
};

/*
 * This structure is only used to know how far to move the offset
 * when parsing resources. The data is stored in a resource_dir_entry
 * struct but that also has extra information used in the parsing which
 * causes the size to be inaccurate.
 */
struct resource_dir_entry_sz {
  std::uint32_t ID;
  std::uint32_t RVA;
};

struct resource_dir_entry {
  inline resource_dir_entry(void) : ID(0), RVA(0), type(0), name(0), lang(0) {
  }

  std::uint32_t ID;
  std::uint32_t RVA;
  std::uint32_t type;
  std::uint32_t name;
  std::uint32_t lang;
  std::string type_str;
  std::string name_str;
  std::string lang_str;
};

struct resource_dir_table {
  std::uint32_t Characteristics;
  std::uint32_t TimeDateStamp;
  std::uint16_t MajorVersion;
  std::uint16_t MinorVersion;
  std::uint16_t NameEntries;
  std::uint16_t IDEntries;
};

struct resource_dat_entry {
  std::uint32_t RVA;
  std::uint32_t size;
  std::uint32_t codepage;
  std::uint32_t reserved;
};

struct image_section_header {
  std::uint8_t Name[NT_SHORT_NAME_LEN];
  union {
    std::uint32_t PhysicalAddress;
    std::uint32_t VirtualSize;
  } Misc;
  std::uint32_t VirtualAddress;
  std::uint32_t SizeOfRawData;
  std::uint32_t PointerToRawData;
  std::uint32_t PointerToRelocations;
  std::uint32_t PointerToLinenumbers;
  std::uint16_t NumberOfRelocations;
  std::uint16_t NumberOfLinenumbers;
  std::uint32_t Characteristics;
};

struct import_dir_entry {
  std::uint32_t LookupTableRVA;
  std::uint32_t TimeStamp;
  std::uint32_t ForwarderChain;
  std::uint32_t NameRVA;
  std::uint32_t AddressRVA;
};

struct export_dir_table {
  std::uint32_t ExportFlags;
  std::uint32_t TimeDateStamp;
  std::uint16_t MajorVersion;
  std::uint16_t MinorVersion;
  std::uint32_t NameRVA;
  std::uint32_t OrdinalBase;
  std::uint32_t AddressTableEntries;
  std::uint32_t NumberOfNamePointers;
  std::uint32_t ExportAddressTableRVA;
  std::uint32_t NamePointerRVA;
  std::uint32_t OrdinalTableRVA;
};

struct debug_dir_entry {
  std::uint32_t Characteristics;
  std::uint32_t TimeStamp;
  std::uint16_t MajorVersion;
  std::uint16_t MinorVersion;
  std::uint32_t Type;
  std::uint32_t SizeOfData;
  std::uint32_t AddressOfRawData;
  std::uint32_t PointerToRawData;
};

enum reloc_type {
  RELOC_ABSOLUTE = 0,
  RELOC_HIGH = 1,
  RELOC_LOW = 2,
  RELOC_HIGHLOW = 3,
  RELOC_HIGHADJ = 4,
  RELOC_MIPS_JMPADDR = 5, // only valid on MIPS
  RELOC_ARM_MOV32 = 5,    // only valid on ARM/Thumb
  RELOC_RISCV_HIGH20 = 5, // only valid on RISC-V
  RELOC_RESERVED = 6,
  RELOC_THUMB_MOV32 = 7,         // only valid on Thumb
  RELOC_RISCV_LOW32I = 7,        // only valid on RISC-V
  RELOC_RISCV_LOW12S = 8,        // only valid on RISC-V
  RELOC_LOONGARCH32_MARK_LA = 8, // only valid on LoongArch 32
  RELOC_LOONGARCH64_MARK_LA = 8, // only valid on LoongArch 64
  RELOC_MIPS_JMPADDR16 = 9,      // only valid on MIPS
  RELOC_IA64_IMM64 = 9,
  RELOC_DIR64 = 10
};

struct reloc_block {
  std::uint32_t PageRVA;
  std::uint32_t BlockSize;
};

struct image_load_config_code_integrity {
  std::uint16_t Flags;
  std::uint16_t Catalog;
  std::uint32_t CatalogOffset;
  std::uint32_t Reserved;
};

struct image_load_config_32 {
  std::uint32_t Size;
  std::uint32_t TimeDateStamp;
  std::uint16_t MajorVersion;
  std::uint16_t MinorVersion;
  std::uint32_t GlobalFlagsClear;
  std::uint32_t GlobalFlagsSet;
  std::uint32_t CriticalSectionDefaultTimeout;
  std::uint32_t DeCommitFreeBlockThreshold;
  std::uint32_t DeCommitTotalFreeThreshold;
  std::uint32_t LockPrefixTable;
  std::uint32_t MaximumAllocationSize;
  std::uint32_t VirtualMemoryThreshold;
  std::uint32_t ProcessHeapFlags;
  std::uint32_t ProcessAffinityMask;
  std::uint16_t CSDVersion;
  std::uint16_t DependentLoadFlags;
  std::uint32_t EditList;
  std::uint32_t SecurityCookie;
  std::uint32_t SEHandlerTable;
  std::uint32_t SEHandlerCount;
  std::uint32_t GuardCFCheckFunctionPointer;
  std::uint32_t GuardCFDispatchFunctionPointer;
  std::uint32_t GuardCFFunctionTable;
  std::uint32_t GuardCFFunctionCount;
  std::uint32_t GuardFlags;
  image_load_config_code_integrity CodeIntegrity;
  std::uint32_t GuardAddressTakenIatEntryTable;
  std::uint32_t GuardAddressTakenIatEntryCount;
  std::uint32_t GuardLongJumpTargetTable;
  std::uint32_t GuardLongJumpTargetCount;
  std::uint32_t DynamicValueRelocTable;
  std::uint32_t CHPEMetadataPointer;
  std::uint32_t GuardRFFailureRoutine;
  std::uint32_t GuardRFFailureRoutineFunctionPointer;
  std::uint32_t DynamicValueRelocTableOffset;
  std::uint16_t DynamicValueRelocTableSection;
  std::uint16_t Reserved2;
  std::uint32_t GuardRFVerifyStackPointerFunctionPointer;
  std::uint32_t HotPatchTableOffset;
  std::uint32_t Reserved3;
  std::uint32_t EnclaveConfigurationPointer;
  std::uint32_t VolatileMetadataPointer;
};

struct image_load_config_64 {
  std::uint32_t Size;
  std::uint32_t TimeDateStamp;
  std::uint16_t MajorVersion;
  std::uint16_t MinorVersion;
  std::uint32_t GlobalFlagsClear;
  std::uint32_t GlobalFlagsSet;
  std::uint32_t CriticalSectionDefaultTimeout;
  std::uint64_t DeCommitFreeBlockThreshold;
  std::uint64_t DeCommitTotalFreeThreshold;
  std::uint64_t LockPrefixTable;
  std::uint64_t MaximumAllocationSize;
  std::uint64_t VirtualMemoryThreshold;
  std::uint64_t ProcessAffinityMask;
  std::uint32_t ProcessHeapFlags;
  std::uint16_t CSDVersion;
  std::uint16_t DependentLoadFlags;
  std::uint64_t EditList;
  std::uint64_t SecurityCookie;
  std::uint64_t SEHandlerTable;
  std::uint64_t SEHandlerCount;
  std::uint64_t GuardCFCheckFunctionPointer;
  std::uint64_t GuardCFDispatchFunctionPointer;
  std::uint64_t GuardCFFunctionTable;
  std::uint64_t GuardCFFunctionCount;
  std::uint32_t GuardFlags;
  image_load_config_code_integrity CodeIntegrity;
  std::uint64_t GuardAddressTakenIatEntryTable;
  std::uint64_t GuardAddressTakenIatEntryCount;
  std::uint64_t GuardLongJumpTargetTable;
  std::uint64_t GuardLongJumpTargetCount;
  std::uint64_t DynamicValueRelocTable;
  std::uint64_t CHPEMetadataPointer;
  std::uint64_t GuardRFFailureRoutine;
  std::uint64_t GuardRFFailureRoutineFunctionPointer;
  std::uint32_t DynamicValueRelocTableOffset;
  std::uint16_t DynamicValueRelocTableSection;
  std::uint16_t Reserved2;
  std::uint64_t GuardRFVerifyStackPointerFunctionPointer;
  std::uint32_t HotPatchTableOffset;
  std::uint32_t Reserved3;
  std::uint64_t EnclaveConfigurationPointer;
  std::uint64_t VolatileMetadataPointer;
};
} // namespace peparse

#pragma once

#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>

#include "util_bit.h"
#include "util_likely.h"

namespace dxvk {

  template<typename T, size_t N>
  class small_vector {
    using storage = std::aligned_storage_t<sizeof(T), alignof(T)>;
  public:

    constexpr static size_t MinCapacity = N;

    small_vector() { }

    small_vector(size_t size) {
      resize(size);
    }

    small_vector(const small_vector& other) {
      reserve(other.m_size);

      for (size_t i = 0; i < other.m_size; i++)
        *ptr(i) = *other.ptr(i);

      m_size = other.m_size;
    }

    small_vector& operator = (const small_vector& other) {
      for (size_t i = 0; i < m_size; i++)
        ptr(i)->~T();

      reserve(other.m_size);

      for (size_t i = 0; i < other.m_size; i++)
        *ptr(i) = *other.ptr(i);

      m_size = other.m_size;
      return *this;
    }

    small_vector(small_vector&& other) {
      if (other.m_size <= N) {
        for (size_t i = 0; i < other.m_size; i++)
          new (&u.m_data[i]) T(std::move(*other.ptr(i)));
      } else {
        u.m_ptr = other.u.m_ptr;
        m_capacity = other.m_capacity;

        other.u.m_ptr = nullptr;
        other.m_capacity = N;
      }

      m_size = other.m_size;
      other.m_size = 0;
    }

    small_vector& operator = (small_vector&& other) {
      for (size_t i = 0; i < m_size; i++)
        ptr(i)->~T();

      if (m_capacity > N)
        delete[] u.m_ptr;

      if (other.m_size <= N) {
        m_capacity = N;

        for (size_t i = 0; i < other.m_size; i++)
          new (&u.m_data[i]) T(std::move(*other.ptr(i)));
      } else {
        u.m_ptr = other.u.m_ptr;
        m_capacity = other.m_capacity;

        other.u.m_ptr = nullptr;
        other.m_capacity = N;
      }

      m_size = other.m_size;
      other.m_size = 0;
      return *this;
    }

    ~small_vector() {
      for (size_t i = 0; i < m_size; i++)
        ptr(i)->~T();

      if (m_capacity > N)
        delete[] u.m_ptr;
    }

    size_t size() const {
      return m_size;
    }

    void reserve(size_t n) {
      if (likely(n <= m_capacity))
        return;

      n = pick_capacity(n);

      storage* data = new storage[n];

      for (size_t i = 0; i < m_size; i++) {
        new (&data[i]) T(std::move(*ptr(i)));
        ptr(i)->~T();
      }

      if (m_capacity > N)
        delete[] u.m_ptr;

      m_capacity = n;
      u.m_ptr = data;
    }

    const T* data() const { return ptr(0); }
          T* data()       { return ptr(0); }

    void resize(size_t n) {
      reserve(n);

      for (size_t i = n; i < m_size; i++)
        ptr(i)->~T();

      for (size_t i = m_size; i < n; i++)
        new (ptr(i)) T();

      m_size = n;
    }

    void push_back(const T& object) {
      reserve(m_size + 1);
      new (ptr(m_size++)) T(object);
    }

    void push_back(T&& object) {
      reserve(m_size + 1);
      new (ptr(m_size++)) T(std::move(object));
    }

    template<typename... Args>
    T& emplace_back(Args... args) {
      reserve(m_size + 1);
      return *(new (ptr(m_size++)) T(std::forward<Args>(args)...));
    }

    void erase(size_t idx) {
      ptr(idx)->~T();

      for (size_t i = idx; i < m_size - 1; i++) {
        new (ptr(i)) T(std::move(*ptr(i + 1)));
        ptr(i + 1)->~T();
      }
    }

    void pop_back() {
      ptr(--m_size)->~T();
    }

    void clear() {
      for (size_t i = 0; i < m_size; i++)
        ptr(i)->~T();

      m_size = 0;
    }

    bool empty() const {
      return m_size == 0;
    }

          T& operator [] (size_t idx)       { return *ptr(idx); }
    const T& operator [] (size_t idx) const { return *ptr(idx); }

          T& front()       { return *ptr(0); }
    const T& front() const { return *ptr(0); }

          T& back()       { return *ptr(m_size - 1); }
    const T& back() const { return *ptr(m_size - 1); }

  private:

    size_t m_capacity = N;
    size_t m_size     = 0;

    union {
      storage* m_ptr;
      storage  m_data[N];
    } u;

    size_t pick_capacity(size_t n) {
      // Pick next largest power of two for the new capacity
      return size_t(1u) << ((sizeof(n) * 8u) - bit::lzcnt(n - 1));
    }

    T* ptr(size_t idx) {
      return m_capacity == N
        ? reinterpret_cast<T*>(&u.m_data[idx])
        : reinterpret_cast<T*>(&u.m_ptr[idx]);
    }

    const T* ptr(size_t idx) const {
      return m_capacity == N
        ? reinterpret_cast<const T*>(&u.m_data[idx])
        : reinterpret_cast<const T*>(&u.m_ptr[idx]);
    }

  };

}

#include "config/config.hpp"
#include "common/exception.hpp"

#include "config/default_conf.hpp"

#include <vulkan/vulkan_core.h>
#include <toml11/find.hpp>
#include <toml11/parser.hpp>
#include <toml.hpp>

#include <unordered_map>
#include <filesystem>
#include <algorithm>
#include <exception>
#include <stdexcept>
#include <iostream>
#include <optional>
#include <fstream>
#include <cstdlib>
#include <utility>
#include <string>

using namespace Config;

namespace {
    Configuration globalConf{};
    std::optional<std::unordered_map<std::string, Configuration>> gameConfs;
}

Configuration Config::activeConf{};

namespace {
    /// Turn a string into a VkPresentModeKHR enum value.
    VkPresentModeKHR into_present(const std::string& mode) {
        if (mode == "fifo" || mode == "vsync")
            return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR;
        if (mode == "mailbox")
            return VkPresentModeKHR::VK_PRESENT_MODE_MAILBOX_KHR;
        if (mode == "immediate")
            return VkPresentModeKHR::VK_PRESENT_MODE_IMMEDIATE_KHR;
        return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR;
    }
}

void Config::updateConfig(const std::string& file) {
    if (!std::filesystem::exists(file)) {
        std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n';
        const auto parent = std::filesystem::path(file).parent_path();
        if (!std::filesystem::exists(parent))
            if (!std::filesystem::create_directories(parent))
                throw std::runtime_error("Unable to create configuration directory at " + parent.string());

        std::ofstream out(file);
        if (!out.is_open())
            throw std::runtime_error("Unable to create configuration file at " + file);
        out << DEFAULT_CONFIG;
        out.close();
    }

    // parse config file
    std::optional<toml::value> parsed;
    try {
        parsed.emplace(toml::parse(file));
        if (!parsed->contains("version"))
            throw std::runtime_error("Configuration file is missing 'version' field");
        if (parsed->at("version").as_integer() != 1)
            throw std::runtime_error("Configuration file version is not supported, expected 1");
    } catch (const std::exception& e) {
        throw LSFG::rethrowable_error("Unable to parse configuration file", e);
    }
    auto& toml = *parsed;

    // parse global configuration
    const toml::value globalTable = toml::find_or_default<toml::table>(toml, "global");
    const Configuration global{
        .dll =   toml::find_or(globalTable, "dll", std::string()),
        .config_file = file,
        .timestamp = std::filesystem::last_write_time(file)
    };

    // validate global configuration
    if (global.multiplier < 2)
        throw std::runtime_error("Global Multiplier cannot be less than 2");
    if (global.flowScale < 0.25F || global.flowScale > 1.0F)
        throw std::runtime_error("Flow scale must be between 0.25 and 1.0");

    // parse game-specific configuration
    std::unordered_map<std::string, Configuration> games;
    const toml::value gamesList = toml::find_or_default<toml::array>(toml, "game");
    for (const auto& gameTable : gamesList.as_array()) {
        if (!gameTable.is_table())
            throw std::runtime_error("Invalid game configuration entry");
        if (!gameTable.contains("exe"))
            throw std::runtime_error("Game override missing 'exe' field");

        const std::string exe = toml::find<std::string>(gameTable, "exe");
        Configuration game{
            .enable = true,
            .dll = global.dll,
            .multiplier = toml::find_or(gameTable, "multiplier", 2U),
            .flowScale = toml::find_or(gameTable, "flow_scale", 1.0F),
            .performance = toml::find_or(gameTable, "performance_mode", false),
            .hdr = toml::find_or(gameTable, "hdr_mode", false),
            .e_present =   into_present(toml::find_or(gameTable, "experimental_present_mode", "")),
            .config_file = file,
            .timestamp = global.timestamp
        };

        // validate the configuration
        if (game.multiplier < 1)
            throw std::runtime_error("Multiplier cannot be less than 1");
        if (game.flowScale < 0.25F || game.flowScale > 1.0F)
            throw std::runtime_error("Flow scale must be between 0.25 and 1.0");
        games[exe] = std::move(game);
    }

    // store configurations
    globalConf = global;
    gameConfs = std::move(games);
}

Configuration Config::getConfig(const std::pair<std::string, std::string>& name) {
    // process legacy environment variables
    if (std::getenv("LSFG_LEGACY")) {
        Configuration conf{
            .enable = true,
            .multiplier = 2,
            .flowScale = 1.0F,
            .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR
        };

        const char* dll = std::getenv("LSFG_DLL_PATH");
        if (dll) conf.dll = std::string(dll);
        const char* multiplier = std::getenv("LSFG_MULTIPLIER");
        if (multiplier) conf.multiplier = std::stoul(multiplier);
        const char* flow_scale = std::getenv("LSFG_FLOW_SCALE");
        if (flow_scale) conf.flowScale = std::stof(flow_scale);
        const char* performance = std::getenv("LSFG_PERFORMANCE_MODE");
        if (performance) conf.performance = std::string(performance) == "1";
        const char* hdr = std::getenv("LSFG_HDR_MODE");
        if (hdr) conf.hdr = std::string(hdr) == "1";
        const char* e_present = std::getenv("LSFG_EXPERIMENTAL_PRESENT_MODE");
        if (e_present) conf.e_present = into_present(std::string(e_present));

        return conf;
    }

    // process new configuration system
    if (!gameConfs.has_value())
        return globalConf;

    const auto& games = *gameConfs;
    auto it = std::ranges::find_if(games, [&name](const auto& pair) {
        return name.first.ends_with(pair.first) || (name.second == pair.first);
    });
    if (it != games.end())
        return it->second;

    return globalConf;
}

#include "hooks.hpp"
#include "common/exception.hpp"
#include "config/config.hpp"
#include "utils/utils.hpp"
#include "context.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>

#include <unordered_map>
#include <filesystem>
#include <stdexcept>
#include <algorithm>
#include <exception>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <vector>

using namespace Hooks;

namespace {

    ///
    /// Add extensions to the instance create info.
    ///
    VkResult myvkCreateInstance(
            const VkInstanceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkInstance* pInstance) {
        auto extensions = Utils::addExtensions(
            pCreateInfo->ppEnabledExtensionNames,
            pCreateInfo->enabledExtensionCount,
            {
                "VK_KHR_get_physical_device_properties2",
                "VK_KHR_external_memory_capabilities",
                "VK_KHR_external_semaphore_capabilities"
            }
        );
        VkInstanceCreateInfo createInfo = *pCreateInfo;
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();
        auto res = Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance);
        if (res == VK_ERROR_EXTENSION_NOT_PRESENT)
            throw std::runtime_error(
                "Required Vulkan instance extensions are not present."
                "Your GPU driver is not supported.");
        return res;
    }

    /// Map of devices to related information.
    std::unordered_map<VkDevice, DeviceInfo> deviceToInfo;

    ///
    /// Add extensions to the device create info.
    /// (function pointers are not initialized yet)
    ///
    VkResult myvkCreateDevicePre(
            VkPhysicalDevice physicalDevice,
            const VkDeviceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkDevice* pDevice) {
        // add extensions
        auto extensions = Utils::addExtensions(
            pCreateInfo->ppEnabledExtensionNames,
            pCreateInfo->enabledExtensionCount,
            {
                "VK_KHR_external_memory",
                "VK_KHR_external_memory_fd",
                "VK_KHR_external_semaphore",
                "VK_KHR_external_semaphore_fd"
            }
        );
        VkDeviceCreateInfo createInfo = *pCreateInfo;
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();
        auto res = Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice);
        if (res == VK_ERROR_EXTENSION_NOT_PRESENT)
            throw std::runtime_error(
                "Required Vulkan device extensions are not present."
                "Your GPU driver is not supported.");
        return res;
    }

    ///
    /// Add related device information after the device is created.
    ///
    VkResult myvkCreateDevicePost(
            VkPhysicalDevice physicalDevice,
            VkDeviceCreateInfo* pCreateInfo,
            const VkAllocationCallbacks*,
            VkDevice* pDevice) {
        deviceToInfo.emplace(*pDevice, DeviceInfo {
            .device = *pDevice,
            .physicalDevice = physicalDevice,
            .queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, VK_QUEUE_GRAPHICS_BIT)
        });
        return VK_SUCCESS;
    }

    /// Erase the device information when the device is destroyed.
    void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) noexcept {
        deviceToInfo.erase(device);
        Layer::ovkDestroyDevice(device, pAllocator);
    }

    std::unordered_map<VkSwapchainKHR, LsContext> swapchains;
    std::unordered_map<VkSwapchainKHR, VkDevice> swapchainToDeviceTable;
    std::unordered_map<VkSwapchainKHR, VkPresentModeKHR> swapchainToPresent;

    ///
    /// Adjust swapchain creation parameters and create a swapchain context.
    ///
    VkResult myvkCreateSwapchainKHR(
            VkDevice device,
            const VkSwapchainCreateInfoKHR* pCreateInfo,
            const VkAllocationCallbacks* pAllocator,
            VkSwapchainKHR* pSwapchain) noexcept {
        // find device
        auto it = deviceToInfo.find(device);
        if (it == deviceToInfo.end()) {
            Utils::logLimitN("swapMap", 5, "Device not found in map");
            return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain);
        }
        Utils::resetLimitN("swapMap");
        auto& deviceInfo = it->second;

        // increase amount of images in swapchain
        VkSwapchainCreateInfoKHR createInfo = *pCreateInfo;
        const auto maxImages = Utils::getMaxImageCount(
            deviceInfo.physicalDevice, pCreateInfo->surface);
        createInfo.minImageCount = createInfo.minImageCount + 1
            + static_cast<uint32_t>(deviceInfo.queue.first);
        if (createInfo.minImageCount > maxImages) {
            createInfo.minImageCount = maxImages;
            Utils::logLimitN("swapCount", 10,
                "Requested image count (" +
                    std::to_string(pCreateInfo->minImageCount) + ") "
                "exceeds maximum allowed (" +
                    std::to_string(maxImages) + "). "
                "Continuing with maximum allowed image count. "
                "This might lead to performance degradation.");
        } else {
            Utils::resetLimitN("swapCount");
        }

        // allow copy operations on swapchain images
        createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
        createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;

        // enforce present mode
        createInfo.presentMode = Config::activeConf.e_present;

        // retire potential old swapchain
        if (pCreateInfo->oldSwapchain) {
            swapchains.erase(pCreateInfo->oldSwapchain);
            swapchainToDeviceTable.erase(pCreateInfo->oldSwapchain);
        }

        // create swapchain
        auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain);
        if (res != VK_SUCCESS)
            return res; // can't be caused by lsfg-vk (yet)

        try {
            swapchainToPresent.emplace(*pSwapchain, createInfo.presentMode);

            // get all swapchain images
            uint32_t imageCount{};
            res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr);
            if (res != VK_SUCCESS || imageCount == 0)
                throw LSFG::vulkan_error(res, "Failed to get swapchain image count");

            std::vector<VkImage> swapchainImages(imageCount);
            res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain,
                &imageCount, swapchainImages.data());
            if (res != VK_SUCCESS)
                throw LSFG::vulkan_error(res, "Failed to get swapchain images");

            // create swapchain context
            swapchainToDeviceTable.emplace(*pSwapchain, device);
            swapchains.emplace(*pSwapchain, LsContext(
                deviceInfo, *pSwapchain, pCreateInfo->imageExtent,
                swapchainImages
            ));

            std::cerr << "lsfg-vk: Swapchain context " <<
                    (createInfo.oldSwapchain ? "recreated" : "created")
                << " (using " << imageCount << " images).\n";

            Utils::resetLimitN("swapCtxCreate");
        } catch (const std::exception& e) {
            Utils::logLimitN("swapCtxCreate", 5,
                "An error occurred while creating the swapchain wrapper:\n"
                "- " + std::string(e.what()));
            return VK_SUCCESS; // swapchain is still valid
        }
        return VK_SUCCESS;
    }

    ///
    /// Update presentation parameters and present the next frame(s).
    ///
    VkResult myvkQueuePresentKHR(
            VkQueue queue,
            const VkPresentInfoKHR* pPresentInfo) noexcept {
        // find swapchain device
        auto it = swapchainToDeviceTable.find(*pPresentInfo->pSwapchains);
        if (it == swapchainToDeviceTable.end()) {
            Utils::logLimitN("swapMap", 5,
                "Swapchain not found in map");
            return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
        }

        // find device info
        auto it2 = deviceToInfo.find(it->second);
        if (it2 == deviceToInfo.end()) {
            Utils::logLimitN("swapMap", 5,
                "Device not found in map");
            return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
        }
        auto& deviceInfo = it2->second;

        // find swapchain context
        auto it3 = swapchains.find(*pPresentInfo->pSwapchains);
        if (it3 == swapchains.end()) {
            Utils::logLimitN("swapMap", 5,
                "Swapchain context not found in map");
            return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
        }
        auto& swapchain = it3->second;

        // find present mode
        auto it4 = swapchainToPresent.find(*pPresentInfo->pSwapchains);
        if (it4 == swapchainToPresent.end()) {
            Utils::logLimitN("swapMap", 5,
                "Swapchain present mode not found in map");
            return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
        }
        auto& present = it4->second;

        // enforce present mode | NOLINTBEGIN
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
        const VkSwapchainPresentModeInfoEXT* presentModeInfo =
            reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(pPresentInfo->pNext);
        while (presentModeInfo) {
            if (presentModeInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) {
                for (size_t i = 0; i < presentModeInfo->swapchainCount; i++)
                    const_cast<VkPresentModeKHR*>(presentModeInfo->pPresentModes)[i] =
                        present;
            }
            presentModeInfo =
                reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(presentModeInfo->pNext);
        }
        #pragma clang diagnostic pop

        // NOLINTEND | present the next frame
        VkResult res{}; // might return VK_SUBOPTIMAL_KHR
        try {
            // ensure config is valid
            auto& conf = Config::activeConf;
            if (!conf.config_file.empty()
                    && (
                            !std::filesystem::exists(conf.config_file)
                          || conf.timestamp != std::filesystem::last_write_time(conf.config_file)
                    )) {
                Layer::ovkQueuePresentKHR(queue, pPresentInfo);
                return VK_ERROR_OUT_OF_DATE_KHR;
            }

            // ensure present mode is still valid
            if (present != conf.e_present) {
                Layer::ovkQueuePresentKHR(queue, pPresentInfo);
                return VK_ERROR_OUT_OF_DATE_KHR;
            }

            // skip if disabled
            if (conf.multiplier <= 1)
                return Layer::ovkQueuePresentKHR(queue, pPresentInfo);

            // present the swapchain
            std::vector<VkSemaphore> semaphores(pPresentInfo->waitSemaphoreCount);
            std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data());

            res = swapchain.present(deviceInfo, pPresentInfo->pNext,
                queue, semaphores, *pPresentInfo->pImageIndices);

            Utils::resetLimitN("swapPresent");
        } catch (const std::exception& e) {
            Utils::logLimitN("swapPresent", 5,
                "An error occurred while presenting the swapchain:\n"
                "- " + std::string(e.what()));
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        return res;
    }

    /// Erase the swapchain context and mapping when the swapchain is destroyed.
    void myvkDestroySwapchainKHR(
            VkDevice device,
            VkSwapchainKHR swapchain,
            const VkAllocationCallbacks* pAllocator) noexcept {
        swapchains.erase(swapchain);
        swapchainToDeviceTable.erase(swapchain);
        swapchainToPresent.erase(swapchain);
        Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator);
    }
}

std::unordered_map<std::string, PFN_vkVoidFunction> Hooks::hooks = {
    // instance hooks
    {"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateInstance)},

    // device hooks
    {"vkCreateDevicePre", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateDevicePre)},
    {"vkCreateDevicePost", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateDevicePost)},
    {"vkDestroyDevice", reinterpret_cast<PFN_vkVoidFunction>(myvkDestroyDevice)},

    // swapchain hooks
    {"vkCreateSwapchainKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateSwapchainKHR)},
    {"vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkQueuePresentKHR)},
    {"vkDestroySwapchainKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkDestroySwapchainKHR)}
};

#pragma once

#include <cstddef>
#include <cstdint>
#include <functional>
#include <ostream>
#include <utility>

namespace dxvk {

  /**
   * \brief Pointer for reference-counted objects
   *
   * This only requires the given type to implement \c incRef
   * and \c decRef methods that adjust the reference count.
   * \tparam T Object type
   */
  template<typename T>
  class Rc {
    template<typename Tx>
    friend class Rc;
  public:

    Rc() = default;
    Rc(std::nullptr_t) { }

    Rc(T* object)
    : m_object(object) {
      this->incRef();
    }

    Rc(const Rc& other)
    : m_object(other.m_object) {
      this->incRef();
    }

    template<typename Tx>
    Rc(const Rc<Tx>& other)
    : m_object(other.m_object) {
      this->incRef();
    }

    Rc(Rc&& other)
    : m_object(other.m_object) {
      other.m_object = nullptr;
    }

    template<typename Tx>
    Rc(Rc<Tx>&& other)
    : m_object(other.m_object) {
      other.m_object = nullptr;
    }

    Rc& operator = (std::nullptr_t) {
      this->decRef();
      m_object = nullptr;
      return *this;
    }

    Rc& operator = (const Rc& other) {
      other.incRef();
      this->decRef();
      m_object = other.m_object;
      return *this;
    }

    template<typename Tx>
    Rc& operator = (const Rc<Tx>& other) {
      other.incRef();
      this->decRef();
      m_object = other.m_object;
      return *this;
    }

    Rc& operator = (Rc&& other) {
      this->decRef();
      this->m_object = other.m_object;
      other.m_object = nullptr;
      return *this;
    }

    template<typename Tx>
    Rc& operator = (Rc<Tx>&& other) {
      this->decRef();
      this->m_object = other.m_object;
      other.m_object = nullptr;
      return *this;
    }

    ~Rc() {
      this->decRef();
    }

    T& operator *  () const { return *m_object; }
    T* operator -> () const { return  m_object; }
    T* ptr() const { return m_object; }

    template<typename Tx> bool operator == (const Rc<Tx>& other) const { return m_object == other.m_object; }
    template<typename Tx> bool operator != (const Rc<Tx>& other) const { return m_object != other.m_object; }

    template<typename Tx> bool operator == (Tx* other) const { return m_object == other; }
    template<typename Tx> bool operator != (Tx* other) const { return m_object != other; }

    bool operator == (std::nullptr_t) const { return m_object == nullptr; }
    bool operator != (std::nullptr_t) const { return m_object != nullptr; }

    explicit operator bool () const {
      return m_object != nullptr;
    }

    /**
     * \brief Sets pointer without acquiring a reference
     *
     * Must only be use when a reference has been taken via
     * other means.
     * \param [in] object Object pointer
     */
    void unsafeInsert(T* object) {
      this->decRef();
      m_object = object;
    }

    /**
     * \brief Extracts raw pointer
     *
     * Sets the smart pointer to null without decrementing the
     * reference count. Must only be used when the reference
     * count is decremented in some other way.
     * \returns Pointer to owned object
     */
    T* unsafeExtract() {
      return std::exchange(m_object, nullptr);
    }

    /**
     * \brief Creates smart pointer without taking reference
     *
     * Must only be used when a refernece has been obtained via other means.
     * \param [in] object Pointer to object to take ownership of
     */
    static Rc<T> unsafeCreate(T* object) {
      return Rc<T>(object, false);
    }

  private:

    T* m_object = nullptr;

    explicit Rc(T* object, bool)
    : m_object(object) { }

    force_inline void incRef() const {
      if (m_object != nullptr)
        m_object->incRef();
    }

    force_inline void decRef() const {
      if (m_object != nullptr) {
        if constexpr (std::is_void_v<decltype(m_object->decRef())>) {
          m_object->decRef();
        } else {
          // Deprecated, objects should manage themselves now.
          if (!m_object->decRef())
            delete m_object;
        }
      }
    }

  };

  template<typename Tx, typename Ty>
  bool operator == (Tx* a, const Rc<Ty>& b) { return b == a; }

  template<typename Tx, typename Ty>
  bool operator != (Tx* a, const Rc<Ty>& b) { return b != a; }

  struct RcHash {
    template<typename T>
    size_t operator () (const Rc<T>& rc) const {
      return reinterpret_cast<uintptr_t>(rc.ptr()) / sizeof(T);
    }
  };

}

template<typename T>
std::ostream& operator << (std::ostream& os, const dxvk::Rc<T>& rc) {
  return os << rc.ptr();
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/image.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <cstdint>
#include <memory>
#include <optional>

using namespace LSFG::Core;

Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
        VkImageUsageFlags usage, VkImageAspectFlags aspectFlags)
        : extent(extent), format(format), aspectFlags(aspectFlags) {
    // create image
    const VkImageCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = format,
        .extent = {
            .width = extent.width,
            .height = extent.height,
            .depth = 1
        },
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .usage = usage,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VkImage imageHandle{};
    auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle);
    if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create Vulkan image");

    // find memory type
    VkPhysicalDeviceMemoryProperties memProps;
    vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps);

    VkMemoryRequirements memReqs;
    vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
    std::optional<uint32_t> memType{};
    for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
        if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
            (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
            memType.emplace(i);
            break;
        } // NOLINTEND
    }
    if (!memType.has_value())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image");
#pragma clang diagnostic pop

    // allocate and bind memory
    const VkMemoryAllocateInfo allocInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .allocationSize = memReqs.size,
        .memoryTypeIndex = memType.value()
    };
    VkDeviceMemory memoryHandle{};
    res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle);
    if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image");

    res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image");

    // create image view
    const VkImageViewCreateInfo viewDesc{
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .image = imageHandle,
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
        .format = format,
        .components = {
            .r = VK_COMPONENT_SWIZZLE_IDENTITY,
            .g = VK_COMPONENT_SWIZZLE_IDENTITY,
            .b = VK_COMPONENT_SWIZZLE_IDENTITY,
            .a = VK_COMPONENT_SWIZZLE_IDENTITY
        },
        .subresourceRange = {
            .aspectMask = aspectFlags,
            .levelCount = 1,
            .layerCount = 1
        }
    };

    VkImageView viewHandle{};
    res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle);
    if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create image view");

    // store objects in shared ptr
    this->layout = std::make_shared<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
    this->image = std::shared_ptr<VkImage>(
        new VkImage(imageHandle),
        [dev = device.handle()](VkImage* img) {
            vkDestroyImage(dev, *img, nullptr);
        }
    );
    this->memory = std::shared_ptr<VkDeviceMemory>(
        new VkDeviceMemory(memoryHandle),
        [dev = device.handle()](VkDeviceMemory* mem) {
            vkFreeMemory(dev, *mem, nullptr);
        }
    );
    this->view = std::shared_ptr<VkImageView>(
        new VkImageView(viewHandle),
        [dev = device.handle()](VkImageView* imgView) {
            vkDestroyImageView(dev, *imgView, nullptr);
        }
    );
}

// shared memory constructor

Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
        VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int fd)
        : extent(extent), format(format), aspectFlags(aspectFlags) {
    // create image
    const VkExternalMemoryImageCreateInfo externalInfo{
        .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
        .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
    };
    const VkImageCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pNext = &externalInfo,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = format,
        .extent = {
            .width = extent.width,
            .height = extent.height,
            .depth = 1
        },
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .usage = usage,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VkImage imageHandle{};
    auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle);
    if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create Vulkan image");

    // find memory type
    VkPhysicalDeviceMemoryProperties memProps;
    vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps);

    VkMemoryRequirements memReqs;
    vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
    std::optional<uint32_t> memType{};
    for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
        if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
            (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
            memType.emplace(i);
            break;
        } // NOLINTEND
    }
    if (!memType.has_value())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image");
#pragma clang diagnostic pop

    // ~~allocate~~ and bind memory
    const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo2{
        .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR,
        .image = imageHandle,
    };
    const VkImportMemoryFdInfoKHR importInfo{
        .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
        .pNext = &dedicatedInfo2,
        .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR,
        .fd = fd // closes the fd
    };
    const VkMemoryAllocateInfo allocInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = fd == -1 ? nullptr : &importInfo,
        .allocationSize = memReqs.size,
        .memoryTypeIndex = memType.value()
    };
    VkDeviceMemory memoryHandle{};
    res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle);
    if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image");

    res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image");

    // create image view
    const VkImageViewCreateInfo viewDesc{
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .image = imageHandle,
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
        .format = format,
        .components = {
            .r = VK_COMPONENT_SWIZZLE_IDENTITY,
            .g = VK_COMPONENT_SWIZZLE_IDENTITY,
            .b = VK_COMPONENT_SWIZZLE_IDENTITY,
            .a = VK_COMPONENT_SWIZZLE_IDENTITY
        },
        .subresourceRange = {
            .aspectMask = aspectFlags,
            .levelCount = 1,
            .layerCount = 1
        }
    };

    VkImageView viewHandle{};
    res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle);
    if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create image view");

    // store objects in shared ptr
    this->layout = std::make_shared<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
    this->image = std::shared_ptr<VkImage>(
        new VkImage(imageHandle),
        [dev = device.handle()](VkImage* img) {
            vkDestroyImage(dev, *img, nullptr);
        }
    );
    this->memory = std::shared_ptr<VkDeviceMemory>(
        new VkDeviceMemory(memoryHandle),
        [dev = device.handle()](VkDeviceMemory* mem) {
            vkFreeMemory(dev, *mem, nullptr);
        }
    );
    this->view = std::shared_ptr<VkImageView>(
        new VkImageView(viewHandle),
        [dev = device.handle()](VkImageView* imgView) {
            vkDestroyImageView(dev, *imgView, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/device.hpp"
#include "core/instance.hpp"
#include "common/exception.hpp"

#include <cstdint>
#include <memory>
#include <optional>
#include <vector>

using namespace LSFG::Core;

const std::vector<const char*> requiredExtensions = {
    "VK_KHR_external_memory_fd",
    "VK_KHR_external_semaphore_fd",
    "VK_EXT_robustness2",
};

Device::Device(const Instance& instance, uint64_t deviceUUID) {
    // get all physical devices
    uint32_t deviceCount{};
    auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr);
    if (res != VK_SUCCESS || deviceCount == 0)
        throw LSFG::vulkan_error(res, "Failed to enumerate physical devices");

    std::vector<VkPhysicalDevice> devices(deviceCount);
    res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, devices.data());
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to get physical devices");

    // get device by uuid
    std::optional<VkPhysicalDevice> physicalDevice;
    for (const auto& device : devices) {
        VkPhysicalDeviceProperties properties;
        vkGetPhysicalDeviceProperties(device, &properties);

        const uint64_t uuid =
            static_cast<uint64_t>(properties.vendorID) << 32 | properties.deviceID;
        if (deviceUUID == uuid || deviceUUID == 0x1463ABAC) {
            physicalDevice = device;
            break;
        }
    }
    if (!physicalDevice)
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED,
            "Could not find physical device with UUID");

    // find queue family indices
    uint32_t familyCount{};
    vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, nullptr);

    std::vector<VkQueueFamilyProperties> queueFamilies(familyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, queueFamilies.data());

    std::optional<uint32_t> computeFamilyIdx;
    for (uint32_t i = 0; i < familyCount; ++i) {
        if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
            computeFamilyIdx = i;
    }
    if (!computeFamilyIdx)
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found");

    // create logical device
    const float queuePriority{1.0F}; // highest priority
    VkPhysicalDeviceRobustness2FeaturesEXT robustness2{
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT,
        .nullDescriptor = VK_TRUE,
    };
    VkPhysicalDeviceVulkan13Features features13{
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
        .pNext = &robustness2,
        .synchronization2 = VK_TRUE
    };
    const VkPhysicalDeviceVulkan12Features features12{
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
        .pNext = &features13,
        .timelineSemaphore = VK_TRUE,
        .vulkanMemoryModel = VK_TRUE
    };
    const VkDeviceQueueCreateInfo computeQueueDesc{
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .queueFamilyIndex = *computeFamilyIdx,
        .queueCount = 1,
        .pQueuePriorities = &queuePriority
    };
    const VkDeviceCreateInfo deviceCreateInfo{
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = &features12,
        .queueCreateInfoCount = 1,
        .pQueueCreateInfos = &computeQueueDesc,
        .enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size()),
        .ppEnabledExtensionNames = requiredExtensions.data()
    };
    VkDevice deviceHandle{};
    res = vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle);
    if (res != VK_SUCCESS | deviceHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create logical device");

    volkLoadDevice(deviceHandle);

    // get compute queue
    VkQueue queueHandle{};
    vkGetDeviceQueue(deviceHandle, *computeFamilyIdx, 0, &queueHandle);

    // store in shared ptr
    this->computeQueue = queueHandle;
    this->computeFamilyIdx = *computeFamilyIdx;
    this->physicalDevice = *physicalDevice;
    this->device = std::shared_ptr<VkDevice>(
        new VkDevice(deviceHandle),
        [](VkDevice* device) {
            vkDestroyDevice(*device, nullptr);
        }
    );
}

/*
The MIT License (MIT)

Copyright (c) 2013 Andrew Ruef

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include <cstring>
#include <fstream>

// keep this header above "windows.h" because it contains many types
#include <pe-parse/parse.h>

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

#include <intrin.h>
#include <windows.h>
#else
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

namespace {

inline std::uint16_t byteSwapUint16(std::uint16_t val) {
#if defined(_MSC_VER) || defined(_MSC_FULL_VER)
  return _byteswap_ushort(val);
#else
  return __builtin_bswap16(val);
#endif
}

inline std::uint32_t byteSwapUint32(std::uint32_t val) {
#if defined(_MSC_VER) || defined(_MSC_FULL_VER)
  return _byteswap_ulong(val);
#else
  return __builtin_bswap32(val);
#endif
}

inline uint64_t byteSwapUint64(std::uint64_t val) {
#if defined(_MSC_VER) || defined(_MSC_FULL_VER)
  return _byteswap_uint64(val);
#else
  return __builtin_bswap64(val);
#endif
}

} // anonymous namespace

namespace peparse {

extern std::uint32_t err;
extern std::string err_loc;

struct buffer_detail {
#ifdef _WIN32
  HANDLE file;
  HANDLE sec;
#else
  int fd;
#endif
};

bool readByte(bounded_buffer *b, std::uint32_t offset, std::uint8_t &out) {
  if (b == nullptr) {
    PE_ERR(PEERR_BUFFER);
    return false;
  }

  if (offset >= b->bufLen) {
    PE_ERR(PEERR_ADDRESS);
    return false;
  }

  std::uint8_t *tmp = (b->buf + offset);
  out = *tmp;

  return true;
}

bool readWord(bounded_buffer *b, std::uint32_t offset, std::uint16_t &out) {
  if (b == nullptr) {
    PE_ERR(PEERR_BUFFER);
    return false;
  }

  if (static_cast<std::uint64_t>(offset) + 1 >= b->bufLen) {
    PE_ERR(PEERR_ADDRESS);
    return false;
  }

  std::uint16_t tmp;
  memcpy(&tmp, (b->buf + offset), sizeof(std::uint16_t));
  if (b->swapBytes) {
    out = byteSwapUint16(tmp);
  } else {
    out = tmp;
  }

  return true;
}

bool readDword(bounded_buffer *b, std::uint32_t offset, std::uint32_t &out) {
  if (b == nullptr) {
    PE_ERR(PEERR_BUFFER);
    return false;
  }

  if (static_cast<std::uint64_t>(offset) + 3 >= b->bufLen) {
    PE_ERR(PEERR_ADDRESS);
    return false;
  }

  std::uint32_t tmp;
  memcpy(&tmp, (b->buf + offset), sizeof(std::uint32_t));
  if (b->swapBytes) {
    out = byteSwapUint32(tmp);
  } else {
    out = tmp;
  }

  return true;
}

bool readQword(bounded_buffer *b, std::uint32_t offset, std::uint64_t &out) {
  if (b == nullptr) {
    PE_ERR(PEERR_BUFFER);
    return false;
  }

  if (static_cast<std::uint64_t>(offset) + 7 >= b->bufLen) {
    PE_ERR(PEERR_ADDRESS);
    return false;
  }

  std::uint64_t tmp;
  memcpy(&tmp, (b->buf + offset), sizeof(std::uint64_t));
  if (b->swapBytes) {
    out = byteSwapUint64(tmp);
  } else {
    out = tmp;
  }

  return true;
}

bool readChar16(bounded_buffer *b, std::uint32_t offset, char16_t &out) {
  if (b == nullptr) {
    PE_ERR(PEERR_BUFFER);
    return false;
  }

  if (static_cast<std::uint64_t>(offset) + 1 >= b->bufLen) {
    PE_ERR(PEERR_ADDRESS);
    return false;
  }

  char16_t tmp;
  if (b->swapBytes) {
    std::uint8_t tmpBuf[2];
    tmpBuf[0] = *(b->buf + offset + 1);
    tmpBuf[1] = *(b->buf + offset);
    memcpy(&tmp, tmpBuf, sizeof(std::uint16_t));
  } else {
    memcpy(&tmp, (b->buf + offset), sizeof(std::uint16_t));
  }
  out = tmp;

  return true;
}

bounded_buffer *readFileToFileBuffer(const char *filePath) {
#ifdef _WIN32
  HANDLE h = CreateFileA(filePath,
                         GENERIC_READ,
                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                         nullptr,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         nullptr);
  if (h == INVALID_HANDLE_VALUE) {
    PE_ERR(PEERR_OPEN);
    return nullptr;
  }

  DWORD fileSize = GetFileSize(h, nullptr);

  if (fileSize == INVALID_FILE_SIZE) {
    CloseHandle(h);
    PE_ERR(PEERR_OPEN);
    return nullptr;
  }

#else
  // only where we have mmap / open / etc
  int fd = open(filePath, O_RDONLY);

  if (fd == -1) {
    PE_ERR(PEERR_OPEN);
    return nullptr;
  }
#endif

  // make a buffer object
  bounded_buffer *p = new (std::nothrow) bounded_buffer();
  if (p == nullptr) {
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  memset(p, 0, sizeof(bounded_buffer));
  buffer_detail *d = new (std::nothrow) buffer_detail();

  if (d == nullptr) {
    delete p;
    PE_ERR(PEERR_MEM);
    return nullptr;
  }
  memset(d, 0, sizeof(buffer_detail));
  p->detail = d;

// only where we have mmap / open / etc
#ifdef _WIN32
  p->detail->file = h;

  HANDLE hMap = CreateFileMapping(h, nullptr, PAGE_READONLY, 0, 0, nullptr);

  if (hMap == nullptr) {
    CloseHandle(h);
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  p->detail->sec = hMap;

  LPVOID ptr = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);

  if (ptr == nullptr) {
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  p->buf = reinterpret_cast<std::uint8_t *>(ptr);
  p->bufLen = fileSize;
#else
  p->detail->fd = fd;

  struct stat s;
  memset(&s, 0, sizeof(struct stat));

  if (fstat(fd, &s) != 0) {
    close(fd);
    delete d;
    delete p;
    PE_ERR(PEERR_STAT);
    return nullptr;
  }

  void *maddr = mmap(nullptr,
                     static_cast<std::size_t>(s.st_size),
                     PROT_READ,
                     MAP_SHARED,
                     fd,
                     0);

  if (maddr == MAP_FAILED) {
    close(fd);
    delete d;
    delete p;
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  p->buf = reinterpret_cast<std::uint8_t *>(maddr);
  p->bufLen = static_cast<std::uint32_t>(s.st_size);
#endif
  p->copy = false;
  p->swapBytes = false;

  return p;
}

bounded_buffer *makeBufferFromPointer(std::uint8_t *data, std::uint32_t sz) {
  if (data == nullptr) {
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  bounded_buffer *p = new (std::nothrow) bounded_buffer();

  if (p == nullptr) {
    PE_ERR(PEERR_MEM);
    return nullptr;
  }

  p->copy = true;
  p->detail = nullptr;
  p->buf = data;
  p->bufLen = sz;
  p->swapBytes = false;

  return p;
}

// split buffer inclusively from from to to by offset
bounded_buffer *
splitBuffer(bounded_buffer *b, std::uint32_t from, std::uint32_t to) {
  if (b == nullptr) {
    return nullptr;
  }

  // safety checks
  if (to < from || to > b->bufLen) {
    return nullptr;
  }

  // make a new buffer
  auto newBuff = new (std::nothrow) bounded_buffer();
  if (newBuff == nullptr) {
    return nullptr;
  }

  newBuff->copy = true;
  newBuff->buf = b->buf + from;
  newBuff->bufLen = (to - from);

  return newBuff;
}

void deleteBuffer(bounded_buffer *b) {
  if (b == nullptr) {
    return;
  }

  if (!b->copy) {
#ifdef _WIN32
    UnmapViewOfFile(b->buf);
    CloseHandle(b->detail->sec);
    CloseHandle(b->detail->file);
#else
    munmap(b->buf, b->bufLen);
    close(b->detail->fd);
#endif
  }

  delete b->detail;
  delete b;
}

std::uint64_t bufLen(bounded_buffer *b) {
  return b->bufLen;
}
} // namespace peparse

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/commandbuffer.hpp"
#include "core/device.hpp"
#include "core/commandpool.hpp"
#include "core/fence.hpp"
#include "core/semaphore.hpp"
#include "common/exception.hpp"

#include <memory>
#include <stdexcept>
#include <cstdint>
#include <optional>
#include <vector>

using namespace LSFG::Core;

CommandBuffer::CommandBuffer(const Core::Device& device, const CommandPool& pool) {
    // create command buffer
    const VkCommandBufferAllocateInfo desc{
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = pool.handle(),
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1
    };
    VkCommandBuffer commandBufferHandle{};
    auto res = vkAllocateCommandBuffers(device.handle(), &desc, &commandBufferHandle);
    if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to allocate command buffer");

    // store command buffer in shared ptr
    this->state = std::make_shared<CommandBufferState>(CommandBufferState::Empty);
    this->commandBuffer = std::shared_ptr<VkCommandBuffer>(
        new VkCommandBuffer(commandBufferHandle),
        [dev = device.handle(), pool = pool.handle()](VkCommandBuffer* cmdBuffer) {
            vkFreeCommandBuffers(dev, pool, 1, cmdBuffer);
        }
    );
}

void CommandBuffer::begin() {
    if (*this->state != CommandBufferState::Empty)
        throw std::logic_error("Command buffer is not in Empty state");

    const VkCommandBufferBeginInfo beginInfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
    };
    auto res = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to begin command buffer");

    *this->state = CommandBufferState::Recording;
}

void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) const {
    if (*this->state != CommandBufferState::Recording)
        throw std::logic_error("Command buffer is not in Recording state");

    vkCmdDispatch(*this->commandBuffer, x, y, z);
}

void CommandBuffer::end() {
    if (*this->state != CommandBufferState::Recording)
        throw std::logic_error("Command buffer is not in Recording state");

    auto res = vkEndCommandBuffer(*this->commandBuffer);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to end command buffer");

    *this->state = CommandBufferState::Full;
}

void CommandBuffer::submit(VkQueue queue, std::optional<Fence> fence,
        const std::vector<Semaphore>& waitSemaphores,
        std::optional<std::vector<uint64_t>> waitSemaphoreValues,
        const std::vector<Semaphore>& signalSemaphores,
        std::optional<std::vector<uint64_t>> signalSemaphoreValues) {
    if (*this->state != CommandBufferState::Full)
        throw std::logic_error("Command buffer is not in Full state");

    const std::vector<VkPipelineStageFlags> waitStages(waitSemaphores.size(),
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
    VkTimelineSemaphoreSubmitInfo timelineInfo{
        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
    };
    if (waitSemaphoreValues.has_value()) {
        timelineInfo.waitSemaphoreValueCount =
            static_cast<uint32_t>(waitSemaphoreValues->size());
        timelineInfo.pWaitSemaphoreValues = waitSemaphoreValues->data();
    }
    if (signalSemaphoreValues.has_value()) {
        timelineInfo.signalSemaphoreValueCount =
            static_cast<uint32_t>(signalSemaphoreValues->size());
        timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data();
    }

    std::vector<VkSemaphore> waitSemaphoresHandles;
    waitSemaphoresHandles.reserve(waitSemaphores.size());
    for (const auto& semaphore : waitSemaphores)
        waitSemaphoresHandles.push_back(semaphore.handle());
    std::vector<VkSemaphore> signalSemaphoresHandles;
    signalSemaphoresHandles.reserve(signalSemaphores.size());
    for (const auto& semaphore : signalSemaphores)
        signalSemaphoresHandles.push_back(semaphore.handle());

    const VkSubmitInfo submitInfo{
        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .pNext = (waitSemaphoreValues.has_value() || signalSemaphoreValues.has_value())
            ? &timelineInfo : nullptr,
        .waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()),
        .pWaitSemaphores = waitSemaphoresHandles.data(),
        .pWaitDstStageMask = waitStages.data(),
        .commandBufferCount = 1,
        .pCommandBuffers = &(*this->commandBuffer),
        .signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size()),
        .pSignalSemaphores = signalSemaphoresHandles.data()
    };
    auto res = vkQueueSubmit(queue, 1, &submitInfo, fence ? fence->handle() : VK_NULL_HANDLE);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to submit command buffer");

    *this->state = CommandBufferState::Submitted;
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"

#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>

using namespace LSFG;
using namespace LSFG_3_1P;

Context::Context(Vulkan& vk,
        int in0, int in1, const std::vector<int>& outN,
        VkExtent2D extent, VkFormat format) {
    // import input images
    this->inImg_0 = Core::Image(vk.device, extent, format,
        VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
        VK_IMAGE_ASPECT_COLOR_BIT, in0);
    this->inImg_1 = Core::Image(vk.device, extent, format,
        VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
        VK_IMAGE_ASPECT_COLOR_BIT, in1);

    // prepare render data
    for (size_t i = 0; i < 8; i++) {
        auto& data = this->data.at(i);
        data.internalSemaphores.resize(vk.generationCount);
        data.outSemaphores.resize(vk.generationCount);
        data.completionFences.resize(vk.generationCount);
        data.cmdBuffers2.resize(vk.generationCount);
    }

    // create shader chains
    this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1);
    for (size_t i = 0; i < 7; i++)
        this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i));
    this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages());
    for (size_t i = 0; i < 7; i++) {
        this->gamma.at(i) = Shaders::Gamma(vk,
            this->alpha.at(6 - i).getOutImages(),
            this->beta.getOutImages().at(std::min<size_t>(6 - i, 5)),
            (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()));
        if (i < 4) continue;

        this->delta.at(i - 4) = Shaders::Delta(vk,
            this->alpha.at(6 - i).getOutImages(),
            this->beta.getOutImages().at(6 - i),
            (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()),
            (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()));
    }
    this->generate = Shaders::Generate(vk,
        this->inImg_0, this->inImg_1,
        this->gamma.at(6).getOutImage(),
        this->delta.at(2).getOutImage1(),
        this->delta.at(2).getOutImage2(),
        outN, format);
}

void Context::present(Vulkan& vk,
        int inSem, const std::vector<int>& outSem) {
    auto& data = this->data.at(this->frameIdx % 8);

    // 3. wait for completion of previous frame in this slot
    if (data.shouldWait)
        for (auto& fence : data.completionFences)
            if (!fence.wait(vk.device, UINT64_MAX))
                throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out");
    data.shouldWait = true;

    // 1. create mipmaps and process input image
    if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem);
    for (size_t i = 0; i < vk.generationCount; i++)
        data.internalSemaphores.at(i) = Core::Semaphore(vk.device);

    data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool);
    data.cmdBuffer1.begin();

    this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx);
    for (size_t i = 0; i < 7; i++)
        this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx);
    this->beta.Dispatch(data.cmdBuffer1, this->frameIdx);

    data.cmdBuffer1.end();
    std::vector<Core::Semaphore> waits = { data.inSemaphore };
    if (inSem < 0) waits.clear();
    data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt,
        waits, std::nullopt,
        data.internalSemaphores, std::nullopt);

    // 2. generate intermediary frames
    for (size_t pass = 0; pass < vk.generationCount; pass++) {
        auto& internalSemaphore = data.internalSemaphores.at(pass);
        auto& outSemaphore = data.outSemaphores.at(pass);
        if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass));
        auto& completionFence = data.completionFences.at(pass);
        completionFence = Core::Fence(vk.device);

        auto& buf2 = data.cmdBuffers2.at(pass);
        buf2 = Core::CommandBuffer(vk.device, vk.commandPool);
        buf2.begin();

        for (size_t i = 0; i < 7; i++) {
            this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass);
            if (i >= 4)
                this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass, i == 6);
        }
        this->generate.Dispatch(buf2, this->frameIdx, pass);

        buf2.end();
        std::vector<Core::Semaphore> signals = { outSemaphore };
        if (inSem < 0) signals.clear();
        buf2.submit(vk.device.getComputeQueue(), completionFence,
            { internalSemaphore }, std::nullopt,
            signals, std::nullopt);
    }

    this->frameIdx++;
}

class DxbcProgramType {
  public:
    VkShaderStageFlagBits shaderStage() const {
    switch (m_type) {
      case DxbcProgramType::PixelShader    : return VK_SHADER_STAGE_FRAGMENT_BIT;
      case DxbcProgramType::VertexShader   : return VK_SHADER_STAGE_VERTEX_BIT;
      case DxbcProgramType::GeometryShader : return VK_SHADER_STAGE_GEOMETRY_BIT;
      case DxbcProgramType::HullShader     : return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
      case DxbcProgramType::DomainShader   : return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
      case DxbcProgramType::ComputeShader  : return VK_SHADER_STAGE_COMPUTE_BIT;
      default: throw DxvkError("DxbcProgramInfo::shaderStage: Unsupported program type");
    }
  }
    spv::ExecutionModel executionModel() const {
    switch (m_type) {
      case DxbcProgramType::PixelShader    : return spv::ExecutionModelFragment;
      case DxbcProgramType::VertexShader   : return spv::ExecutionModelVertex;
      case DxbcProgramType::GeometryShader : return spv::ExecutionModelGeometry;
      case DxbcProgramType::HullShader     : return spv::ExecutionModelTessellationControl;
      case DxbcProgramType::DomainShader   : return spv::ExecutionModelTessellationEvaluation;
      case DxbcProgramType::ComputeShader  : return spv::ExecutionModelGLCompute;
      default: throw DxvkError("DxbcProgramInfo::executionModel: Unsupported program type");
    }
  }
  private:
    DxbcProgramType m_type  = DxbcProgramType::PixelShader;
};

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "common/utils.hpp"
#include "core/buffer.hpp"
#include "core/image.hpp"
#include "core/device.hpp"
#include "core/commandpool.hpp"
#include "core/fence.hpp"
#include "common/exception.hpp"

#include <cstdint>
#include <cerrno>
#include <cstdlib>
#include <fstream>
#include <string>
#include <ios>
#include <system_error>
#include <vector>

using namespace LSFG;
using namespace LSFG::Utils;

BarrierBuilder& BarrierBuilder::addR2W(Core::Image& image) {
    this->barriers.emplace_back(VkImageMemoryBarrier2 {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
        .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
        .srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
        .dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
        .oldLayout = image.getLayout(),
        .newLayout = VK_IMAGE_LAYOUT_GENERAL,
        .image = image.handle(),
        .subresourceRange = {
            .aspectMask = image.getAspectFlags(),
            .levelCount = 1,
            .layerCount = 1
        }
    });
    image.setLayout(VK_IMAGE_LAYOUT_GENERAL);

    return *this;
}

BarrierBuilder& BarrierBuilder::addW2R(Core::Image& image) {
    this->barriers.emplace_back(VkImageMemoryBarrier2 {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
        .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
        .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
        .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
        .oldLayout = image.getLayout(),
        .newLayout = VK_IMAGE_LAYOUT_GENERAL,
        .image = image.handle(),
        .subresourceRange = {
            .aspectMask = image.getAspectFlags(),
            .levelCount = 1,
            .layerCount = 1
        }
    });
    image.setLayout(VK_IMAGE_LAYOUT_GENERAL);

    return *this;
}

void BarrierBuilder::build() const {
    const VkDependencyInfo dependencyInfo = {
        .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
        .imageMemoryBarrierCount = static_cast<uint32_t>(this->barriers.size()),
        .pImageMemoryBarriers = this->barriers.data()
    };
    vkCmdPipelineBarrier2(this->commandBuffer->handle(), &dependencyInfo);
}

void Utils::uploadImage(const Core::Device& device, const Core::CommandPool& commandPool,
        Core::Image& image, const std::string& path) {
    // read image bytecode
    std::ifstream file(path.data(), std::ios::binary | std::ios::ate);
    if (!file.is_open())
        throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path);

    std::streamsize size = file.tellg();
    size -= 124 + 4; // dds header and magic bytes
    std::vector<char> code(static_cast<size_t>(size));

    file.seekg(124 + 4, std::ios::beg);
    if (!file.read(code.data(), size))
        throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path);

    file.close();

    // copy data to buffer
    const Core::Buffer stagingBuffer(
        device, code.data(), static_cast<uint32_t>(code.size()),
        VK_BUFFER_USAGE_TRANSFER_SRC_BIT
    );

    // perform the upload
    Core::CommandBuffer commandBuffer(device, commandPool);
    commandBuffer.begin();

    const VkImageMemoryBarrier barrier{
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .srcAccessMask = VK_ACCESS_NONE,
        .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
        .oldLayout = image.getLayout(),
        .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .image = image.handle(),
        .subresourceRange = {
            .aspectMask = image.getAspectFlags(),
            .levelCount = 1,
            .layerCount = 1
        }
    };
    image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    vkCmdPipelineBarrier(
        commandBuffer.handle(),
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
        0, 0, nullptr, 0, nullptr, 1, &barrier
    );

    auto extent = image.getExtent();
    const VkBufferImageCopy region{
        .bufferImageHeight = 0,
        .imageSubresource = {
            .aspectMask = image.getAspectFlags(),
            .layerCount = 1
        },
        .imageExtent = { extent.width, extent.height, 1 }
    };
    vkCmdCopyBufferToImage(
        commandBuffer.handle(),
        stagingBuffer.handle(), image.handle(),
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region
    );

    commandBuffer.end();

    Core::Fence fence(device);
    commandBuffer.submit(device.getComputeQueue(), fence);

    // wait for the upload to complete
    if (!fence.wait(device))
        throw LSFG::vulkan_error(VK_TIMEOUT, "Upload operation timed out");
}

void Utils::clearImage(const Core::Device& device, Core::Image& image, bool white) {
    Core::Fence fence(device);
    const Core::CommandPool cmdPool(device);
    Core::CommandBuffer cmdBuf(device, cmdPool);
    cmdBuf.begin();

    const VkImageMemoryBarrier2 barrier{
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
        .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
        .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
        .oldLayout = image.getLayout(),
        .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .image = image.handle(),
        .subresourceRange = {
            .aspectMask = image.getAspectFlags(),
            .levelCount = 1,
            .layerCount = 1
        }
    };
    const VkDependencyInfo dependencyInfo = {
        .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
        .imageMemoryBarrierCount = 1,
        .pImageMemoryBarriers = &barrier
    };
    image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    vkCmdPipelineBarrier2(cmdBuf.handle(), &dependencyInfo);

    const float clearValue = white ? 1.0F : 0.0F;
    const VkClearColorValue clearColor = {{ clearValue, clearValue, clearValue, clearValue }};
    const VkImageSubresourceRange subresourceRange = {
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
        .levelCount = 1,
        .layerCount = 1
    };
    vkCmdClearColorImage(cmdBuf.handle(),
        image.handle(), image.getLayout(),
        &clearColor,
        1, &subresourceRange);

    cmdBuf.end();

    cmdBuf.submit(device.getComputeQueue(), fence);
    if (!fence.wait(device))
        throw LSFG::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence.");
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"

#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>

using namespace LSFG_3_1;

Context::Context(Vulkan& vk,
        int in0, int in1, const std::vector<int>& outN,
        VkExtent2D extent, VkFormat format) {
    // import input images
    this->inImg_0 = Core::Image(vk.device, extent, format,
        VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
        VK_IMAGE_ASPECT_COLOR_BIT, in0);
    this->inImg_1 = Core::Image(vk.device, extent, format,
        VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
        VK_IMAGE_ASPECT_COLOR_BIT, in1);

    // prepare render data
    for (size_t i = 0; i < 8; i++) {
        auto& data = this->data.at(i);
        data.internalSemaphores.resize(vk.generationCount);
        data.outSemaphores.resize(vk.generationCount);
        data.completionFences.resize(vk.generationCount);
        data.cmdBuffers2.resize(vk.generationCount);
    }

    // create shader chains
    this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1);
    for (size_t i = 0; i < 7; i++)
        this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i));
    this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages());
    for (size_t i = 0; i < 7; i++) {
        this->gamma.at(i) = Shaders::Gamma(vk,
            this->alpha.at(6 - i).getOutImages(),
            this->beta.getOutImages().at(std::min<size_t>(6 - i, 5)),
            (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()));
        if (i < 4) continue;

        this->delta.at(i - 4) = Shaders::Delta(vk,
            this->alpha.at(6 - i).getOutImages(),
            this->beta.getOutImages().at(6 - i),
            (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()),
            (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()),
            (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2()));
    }
    this->generate = Shaders::Generate(vk,
        this->inImg_0, this->inImg_1,
        this->gamma.at(6).getOutImage(),
        this->delta.at(2).getOutImage1(),
        this->delta.at(2).getOutImage2(),
        outN, format);
}

void Context::present(Vulkan& vk,
        int inSem, const std::vector<int>& outSem) {
    auto& data = this->data.at(this->frameIdx % 8);

    // 3. wait for completion of previous frame in this slot
    if (data.shouldWait)
        for (auto& fence : data.completionFences)
            if (!fence.wait(vk.device, UINT64_MAX))
                throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out");
    data.shouldWait = true;

    // 1. create mipmaps and process input image
    if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem);
    for (size_t i = 0; i < vk.generationCount; i++)
        data.internalSemaphores.at(i) = Core::Semaphore(vk.device);

    data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool);
    data.cmdBuffer1.begin();

    this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx);
    for (size_t i = 0; i < 7; i++)
        this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx);
    this->beta.Dispatch(data.cmdBuffer1, this->frameIdx);

    data.cmdBuffer1.end();
    std::vector<Core::Semaphore> waits = { data.inSemaphore };
    if (inSem < 0) waits.clear();
    data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt,
        waits, std::nullopt,
        data.internalSemaphores, std::nullopt);

    // 2. generate intermediary frames
    for (size_t pass = 0; pass < vk.generationCount; pass++) {
        auto& internalSemaphore = data.internalSemaphores.at(pass);
        auto& outSemaphore = data.outSemaphores.at(pass);
        if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass));
        auto& completionFence = data.completionFences.at(pass);
        completionFence = Core::Fence(vk.device);

        auto& buf2 = data.cmdBuffers2.at(pass);
        buf2 = Core::CommandBuffer(vk.device, vk.commandPool);
        buf2.begin();

        for (size_t i = 0; i < 7; i++) {
            this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass);
            if (i >= 4)
                this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass);
        }
        this->generate.Dispatch(buf2, this->frameIdx, pass);

        buf2.end();
        std::vector<Core::Semaphore> signals = { outSemaphore };
        if (inSem < 0) signals.clear();
        buf2.submit(vk.device.getComputeQueue(), completionFence,
            { internalSemaphore }, std::nullopt,
            signals, std::nullopt);
    }

    this->frameIdx++;
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/buffer.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>

using namespace LSFG::Core;

void Buffer::construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage) {
    // create buffer
    const VkBufferCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = this->size,
        .usage = usage,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VkBuffer bufferHandle{};
    auto res = vkCreateBuffer(device.handle(), &desc, nullptr, &bufferHandle);
    if (res != VK_SUCCESS || bufferHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create Vulkan buffer");

    // find memory type
    VkPhysicalDeviceMemoryProperties memProps;
    vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps);

    VkMemoryRequirements memReqs;
    vkGetBufferMemoryRequirements(device.handle(), bufferHandle, &memReqs);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
    std::optional<uint32_t> memType{};
    for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
        if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
            (memProps.memoryTypes[i].propertyFlags &
                (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) {
            memType.emplace(i);
            break;
        } // NOLINTEND
    }
    if (!memType.has_value())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer");
#pragma clang diagnostic pop

    // allocate and bind memory
    const VkMemoryAllocateInfo allocInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .allocationSize = memReqs.size,
        .memoryTypeIndex = memType.value()
    };
    VkDeviceMemory memoryHandle{};
    res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle);
    if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan buffer");

    res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan buffer");

    // upload data to buffer
    uint8_t* buf{};
    res = vkMapMemory(device.handle(), memoryHandle, 0, this->size, 0, reinterpret_cast<void**>(&buf));
    if (res != VK_SUCCESS || buf == nullptr)
        throw LSFG::vulkan_error(res, "Failed to map memory for Vulkan buffer");
    std::copy_n(reinterpret_cast<const uint8_t*>(data), this->size, buf);
    vkUnmapMemory(device.handle(), memoryHandle);

    // store buffer and memory in shared ptr
    this->buffer = std::shared_ptr<VkBuffer>(
        new VkBuffer(bufferHandle),
        [dev = device.handle()](VkBuffer* img) {
            vkDestroyBuffer(dev, *img, nullptr);
        }
    );
    this->memory = std::shared_ptr<VkDeviceMemory>(
        new VkDeviceMemory(memoryHandle),
        [dev = device.handle()](VkDeviceMemory* mem) {
            vkFreeMemory(dev, *mem, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/descriptorset.hpp"
#include "core/device.hpp"
#include "core/descriptorpool.hpp"
#include "core/shadermodule.hpp"
#include "core/commandbuffer.hpp"
#include "core/pipeline.hpp"
#include "core/image.hpp"
#include "core/sampler.hpp"
#include "core/buffer.hpp"
#include "common/exception.hpp"

#include <memory>
#include <cstdint>

using namespace LSFG::Core;

DescriptorSet::DescriptorSet(const Core::Device& device,
        const DescriptorPool& pool, const ShaderModule& shaderModule) {
    // create descriptor set
    VkDescriptorSetLayout layout = shaderModule.getLayout();
    const VkDescriptorSetAllocateInfo desc{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = pool.handle(),
        .descriptorSetCount = 1,
        .pSetLayouts = &layout
    };
    VkDescriptorSet descriptorSetHandle{};
    auto res = vkAllocateDescriptorSets(device.handle(), &desc, &descriptorSetHandle);
    if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to allocate descriptor set");

    /// store set in shared ptr
    this->descriptorSet = std::shared_ptr<VkDescriptorSet>(
        new VkDescriptorSet(descriptorSetHandle),
        [dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) {
            vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle);
        }
    );
}

DescriptorSetUpdateBuilder DescriptorSet::update(const Core::Device& device) const {
    return { *this, device };
}

void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const {
    VkDescriptorSet descriptorSetHandle = this->handle();
    vkCmdBindDescriptorSets(commandBuffer.handle(),
        VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(),
        0, 1, &descriptorSetHandle, 0, nullptr);
}

// updater class

DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Image& image) {
    this->entries.push_back({
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = this->descriptorSet->handle(),
        .dstBinding = static_cast<uint32_t>(this->entries.size()),
        .descriptorCount = 1,
        .descriptorType = type,
        .pImageInfo = new VkDescriptorImageInfo {
            .imageView = image.getView(),
            .imageLayout = VK_IMAGE_LAYOUT_GENERAL
        },
        .pBufferInfo = nullptr
    });
    return *this;
}

DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Sampler& sampler) {
    this->entries.push_back({
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = this->descriptorSet->handle(),
        .dstBinding = static_cast<uint32_t>(this->entries.size()),
        .descriptorCount = 1,
        .descriptorType = type,
        .pImageInfo = new VkDescriptorImageInfo {
            .sampler = sampler.handle(),
        },
        .pBufferInfo = nullptr
    });
    return *this;
}

DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Buffer& buffer) {
    this->entries.push_back({
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = this->descriptorSet->handle(),
        .dstBinding = static_cast<uint32_t>(this->entries.size()),
        .descriptorCount = 1,
        .descriptorType = type,
        .pImageInfo = nullptr,
        .pBufferInfo = new VkDescriptorBufferInfo {
            .buffer = buffer.handle(),
            .range = buffer.getSize()
        }
    });
    return *this;
}

DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type) {
    this->entries.push_back({
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = this->descriptorSet->handle(),
        .dstBinding = static_cast<uint32_t>(this->entries.size()),
        .descriptorCount = 1,
        .descriptorType = type,
        .pImageInfo = new VkDescriptorImageInfo {
        },
        .pBufferInfo = nullptr
    });
    return *this;
}

void DescriptorSetUpdateBuilder::build() {
    vkUpdateDescriptorSets(this->device->handle(),
        static_cast<uint32_t>(this->entries.size()),
        this->entries.data(), 0, nullptr);

    // NOLINTBEGIN
    for (const auto& entry : this->entries) {
        delete entry.pImageInfo;
        delete entry.pBufferInfo;
    }
    // NOLINTEND
}

#pragma once

#include <type_traits>

#include "util_bit.h"

namespace dxvk {

  template<typename T>
  class Flags {

  public:

    using IntType = std::underlying_type_t<T>;

    Flags() { }

    Flags(IntType t)
    : m_bits(t) { }

    template<typename... Tx>
    Flags(T f, Tx... fx) {
      this->set(f, fx...);
    }

    template<typename... Tx>
    void set(Tx... fx) {
      m_bits |= bits(fx...);
    }

    void set(Flags flags) {
      m_bits |= flags.m_bits;
    }

    template<typename... Tx>
    void clr(Tx... fx) {
      m_bits &= ~bits(fx...);
    }

    void clr(Flags flags) {
      m_bits &= ~flags.m_bits;
    }

    template<typename... Tx>
    bool any(Tx... fx) const {
      return (m_bits & bits(fx...)) != 0;
    }

    template<typename... Tx>
    bool all(Tx... fx) const {
      const IntType mask = bits(fx...);
      return (m_bits & mask) == mask;
    }

    bool test(T f) const {
      return this->any(f);
    }

    bool isClear() const {
      return m_bits == 0;
    }

    void clrAll() {
      m_bits = 0;
    }

    IntType raw() const {
      return m_bits;
    }

    Flags operator & (const Flags& other) const {
      return Flags(m_bits & other.m_bits);
    }

    Flags operator | (const Flags& other) const {
      return Flags(m_bits | other.m_bits);
    }

    Flags operator ^ (const Flags& other) const {
      return Flags(m_bits ^ other.m_bits);
    }

    bool operator == (const Flags& other) const {
      return m_bits == other.m_bits;
    }

    bool operator != (const Flags& other) const {
      return m_bits != other.m_bits;
    }

  private:

    IntType m_bits = 0;

    static IntType bit(T f) {
      return IntType(1) << static_cast<IntType>(f);
    }

    template<typename... Tx>
    static IntType bits(T f, Tx... fx) {
      return bit(f) | bits(fx...);
    }

    static IntType bits() {
      return 0;
    }

  };

}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/delta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Delta::Delta(Vulkan& vk, std::array<std::array<Core::Image, 2>, 3> inImgs1,
        Core::Image inImg2,
        std::optional<Core::Image> optImg1,
        std::optional<Core::Image> optImg2)
        : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)),
          optImg1(std::move(optImg1)), optImg2(std::move(optImg2)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "p_delta[0]",
            { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[5]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[6]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[7]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[8]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_delta[9]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "p_delta[0]"),
        vk.shaders.getPipeline(vk.device, "p_delta[1]"),
        vk.shaders.getPipeline(vk.device, "p_delta[2]"),
        vk.shaders.getPipeline(vk.device, "p_delta[3]"),
        vk.shaders.getPipeline(vk.device, "p_delta[4]"),
        vk.shaders.getPipeline(vk.device, "p_delta[5]"),
        vk.shaders.getPipeline(vk.device, "p_delta[6]"),
        vk.shaders.getPipeline(vk.device, "p_delta[7]"),
        vk.shaders.getPipeline(vk.device, "p_delta[8]"),
        vk.shaders.getPipeline(vk.device, "p_delta[9]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    this->samplers.at(2) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent();
    for (size_t i = 0; i < 3; i++)
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
    for (size_t i = 0; i < 2; i++)
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);

    this->outImg1 = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);
    this->outImg2 = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);

    // hook up shaders
    for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(pass_idx + 1) / static_cast<float>(vk.generationCount + 1),
            false, !this->optImg1.has_value());
        for (size_t i = 0; i < 3; i++) {
            pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(0));
            pass.firstDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
                .build();
        }
        pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(1));
        pass.descriptorSets.at(0).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(2));
        pass.descriptorSets.at(1).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
            .build();
        pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(3));
        pass.descriptorSets.at(2).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(4));
        pass.descriptorSets.at(3).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1)
            .build();
        for (size_t i = 0; i < 3; i++) {
            pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(5));
            pass.sixthDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0))
                .build();
        }
        pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(6));
        pass.descriptorSets.at(4).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .build();
        pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(7));
        pass.descriptorSets.at(5).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0))
            .build();
        pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(8));
        pass.descriptorSets.at(6).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .build();
        pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(9));
        pass.descriptorSets.at(7).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2)
            .build();
    }
}

void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx,
        bool last) {
    auto& pass = this->passes.at(pass_idx);

    // first shader
    const auto extent = this->tempImgs1.at(0).getExtent();
    const uint32_t threadsX = (extent.width + 7) >> 3;
    const uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg1)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(0).bind(buf);
    pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(2).bind(buf);
    pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addW2R(this->optImg1)
        .addW2R(this->inImg2)
        .addR2W(this->outImg1)
        .build();

    this->pipelines.at(4).bind(buf);
    pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);

    // sixth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg1)
        .addW2R(this->optImg2)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(5).bind(buf);
    pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5));
    buf.dispatch(threadsX, threadsY, 1);

    if (!last)
        return;

    // seventh shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .build();

    this->pipelines.at(6).bind(buf);
    pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6));
    buf.dispatch(threadsX, threadsY, 1);

    // eighth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addR2W(this->tempImgs2)
        .build();
    this->pipelines.at(7).bind(buf);
    pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7));
    buf.dispatch(threadsX, threadsY, 1);

    // ninth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .build();

    this->pipelines.at(8).bind(buf);
    pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8));
    buf.dispatch(threadsX, threadsY, 1);

    // tenth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addR2W(this->outImg2)
        .build();

    this->pipelines.at(9).bind(buf);
    pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9));
    buf.dispatch(threadsX, threadsY, 1);
}

#include "pool/resourcepool.hpp"
#include "core/buffer.hpp"
#include "core/device.hpp"
#include "core/sampler.hpp"

#include <vulkan/vulkan_core.h>

#include <array>
#include <cstdint>

using namespace LSFG;
using namespace LSFG::Pool;

struct ConstantBuffer {
    std::array<uint32_t, 2> inputOffset;
    uint32_t firstIter;
    uint32_t firstIterS;
    uint32_t advancedColorKind;
    uint32_t hdrSupport;
    float resolutionInvScale;
    float timestamp;
    float uiThreshold;
    std::array<uint32_t, 3> pad;
};

Core::Buffer ResourcePool::getBuffer(
            const Core::Device& device,
            float timestamp, bool firstIter, bool firstIterS) {
    uint64_t hash = 0;
    const union { float f; uint32_t i; } u{
        .f = timestamp };
    hash |= u.i;
    hash |= static_cast<uint64_t>(firstIter) << 32;
    hash |= static_cast<uint64_t>(firstIterS) << 33;

    auto it = buffers.find(hash);
    if (it != buffers.end())
        return it->second;

    // create the buffer
    const ConstantBuffer data{
        .inputOffset = { 0, 0 },
        .advancedColorKind = this->isHdr ? 2U : 0U,
        .hdrSupport = this->isHdr,
        .resolutionInvScale = this->flowScale,
        .timestamp = timestamp,
        .uiThreshold = 0.5F,
    };
    Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
    buffers[hash] = buffer;
    return buffer;
}

Core::Sampler ResourcePool::getSampler(
            const Core::Device& device,
            VkSamplerAddressMode type,
            VkCompareOp compare,
            bool isWhite) {
    uint64_t hash = 0;
    hash |= static_cast<uint64_t>(type) << 0;
    hash |= static_cast<uint64_t>(compare) << 8;
    hash |= static_cast<uint64_t>(isWhite) << 16;

    auto it = samplers.find(hash);
    if (it != samplers.end())
        return it->second;

    // create the sampler
    Core::Sampler sampler(device, type, compare, isWhite);
    samplers[hash] = sampler;
    return sampler;
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/gamma.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Gamma::Gamma(Vulkan& vk, std::array<std::array<Core::Image, 2>, 3> inImgs1,
        Core::Image inImg2,
        std::optional<Core::Image> optImg)
        : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)),
          optImg(std::move(optImg)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "p_gamma[0]",
            { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_gamma[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_gamma[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_gamma[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_gamma[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "p_gamma[0]"),
        vk.shaders.getPipeline(vk.device, "p_gamma[1]"),
        vk.shaders.getPipeline(vk.device, "p_gamma[2]"),
        vk.shaders.getPipeline(vk.device, "p_gamma[3]"),
        vk.shaders.getPipeline(vk.device, "p_gamma[4]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    this->samplers.at(2) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent();
    for (size_t i = 0; i < 3; i++)
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
    for (size_t i = 0; i < 2; i++)
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);

    this->outImg = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);

    // hook up shaders
    for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(pass_idx + 1) / static_cast<float>(vk.generationCount + 1),
            !this->optImg.has_value());
        for (size_t i = 0; i < 3; i++) {
            pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(0));
            pass.firstDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
                .build();
        }
        pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(1));
        pass.descriptorSets.at(0).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(2));
        pass.descriptorSets.at(1).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
            .build();
        pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(3));
        pass.descriptorSets.at(2).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(4));
        pass.descriptorSets.at(3).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg)
            .build();
    }
}

void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) {
    auto& pass = this->passes.at(pass_idx);

    // first shader
    const auto extent = this->tempImgs1.at(0).getExtent();
    const uint32_t threadsX = (extent.width + 7) >> 3;
    const uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(0).bind(buf);
    pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .build();

    this->pipelines.at(2).bind(buf);
    pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addW2R(this->optImg)
        .addW2R(this->inImg2)
        .addR2W(this->outImg)
        .build();

    this->pipelines.at(4).bind(buf);
    pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/generate.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <vector>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Generate::Generate(Vulkan& vk,
    Core::Image inImg1, Core::Image inImg2,
    Core::Image inImg3, Core::Image inImg4, Core::Image inImg5,
    const std::vector<int>& fds, VkFormat format)
        : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)),
          inImg3(std::move(inImg3)), inImg4(std::move(inImg4)),
          inImg5(std::move(inImg5)) {
    // create resources
    this->shaderModule = vk.shaders.getShader(vk.device, "generate",
        { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
          { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
          { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
          { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
    this->pipeline = vk.shaders.getPipeline(vk.device, "generate");
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS);

    // create internal images/outputs
    const VkExtent2D extent = this->inImg1.getExtent();
    for (size_t i = 0; i < vk.generationCount; i++)
        this->outImgs.emplace_back(vk.device, extent, format,
            VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
            VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i));

    // hook up shaders
    for (size_t i = 0; i < vk.generationCount; i++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(i + 1) / static_cast<float>(vk.generationCount + 1));
        for (size_t j = 0; j < 2; j++) {
            pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModule);
            pass.descriptorSet.at(j).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i))
                .build();
        }
    }
}

void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) {
    auto& pass = this->passes.at(pass_idx);

    // first pass
    const auto extent = this->inImg1.getExtent();
    const uint32_t threadsX = (extent.width + 15) >> 4;
    const uint32_t threadsY = (extent.height + 15) >> 4;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImg1)
        .addW2R(this->inImg2)
        .addW2R(this->inImg3)
        .addW2R(this->inImg4)
        .addW2R(this->inImg5)
        .addR2W(this->outImgs.at(pass_idx))
        .build();

    this->pipeline.bind(buf);
    pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline);
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/generate.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <vector>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Generate::Generate(Vulkan& vk,
    Core::Image inImg1, Core::Image inImg2,
    Core::Image inImg3, Core::Image inImg4, Core::Image inImg5,
    const std::vector<int>& fds, VkFormat format)
        : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)),
          inImg3(std::move(inImg3)), inImg4(std::move(inImg4)),
          inImg5(std::move(inImg5)) {
    // create resources
    this->shaderModule = vk.shaders.getShader(vk.device, "p_generate",
        { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
          { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
          { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
          { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
    this->pipeline = vk.shaders.getPipeline(vk.device, "p_generate");
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS);

    // create internal images/outputs
    const VkExtent2D extent = this->inImg1.getExtent();
    for (size_t i = 0; i < vk.generationCount; i++)
        this->outImgs.emplace_back(vk.device, extent, format,
            VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
            VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i));

    // hook up shaders
    for (size_t i = 0; i < vk.generationCount; i++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(i + 1) / static_cast<float>(vk.generationCount + 1));
        for (size_t j = 0; j < 2; j++) {
            pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModule);
            pass.descriptorSet.at(j).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i))
                .build();
        }
    }
}

void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) {
    auto& pass = this->passes.at(pass_idx);

    // first pass
    const auto extent = this->inImg1.getExtent();
    const uint32_t threadsX = (extent.width + 15) >> 4;
    const uint32_t threadsY = (extent.height + 15) >> 4;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImg1)
        .addW2R(this->inImg2)
        .addW2R(this->inImg3)
        .addW2R(this->inImg4)
        .addW2R(this->inImg5)
        .addR2W(this->outImgs.at(pass_idx))
        .build();

    this->pipeline.bind(buf);
    pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline);
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/shadermodule.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <vector>
#include <cstdint>
#include <utility>
#include <cstddef>
#include <memory>

using namespace LSFG::Core;

ShaderModule::ShaderModule(const Core::Device& device, const std::vector<uint8_t>& code,
        const std::vector<std::pair<size_t, VkDescriptorType>>& descriptorTypes) {
    // create shader module
    const uint8_t* data_ptr = code.data();
    const VkShaderModuleCreateInfo createInfo{
        .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
        .codeSize = code.size(),
        .pCode = reinterpret_cast<const uint32_t*>(data_ptr)
    };
    VkShaderModule shaderModuleHandle{};
    auto res = vkCreateShaderModule(device.handle(), &createInfo, nullptr, &shaderModuleHandle);
    if (res != VK_SUCCESS || !shaderModuleHandle)
        throw LSFG::vulkan_error(res, "Failed to create shader module");

    // create descriptor set layout
    std::vector<VkDescriptorSetLayoutBinding> layoutBindings;
    size_t bindIdx = 0;
    for (const auto &[count, type] : descriptorTypes)
        for (size_t i = 0; i < count; i++, bindIdx++)
            layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
                .binding = static_cast<uint32_t>(bindIdx),
                .descriptorType = type,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
            });

    const VkDescriptorSetLayoutCreateInfo layoutDesc{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = static_cast<uint32_t>(layoutBindings.size()),
        .pBindings = layoutBindings.data()
    };
    VkDescriptorSetLayout descriptorSetLayout{};
    res = vkCreateDescriptorSetLayout(device.handle(), &layoutDesc, nullptr, &descriptorSetLayout);
    if (res != VK_SUCCESS || !descriptorSetLayout)
        throw LSFG::vulkan_error(res, "Failed to create descriptor set layout");

    // store module and layout in shared ptr
    this->shaderModule = std::shared_ptr<VkShaderModule>(
        new VkShaderModule(shaderModuleHandle),
        [dev = device.handle()](VkShaderModule* shaderModuleHandle) {
            vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr);
        }
    );
    this->descriptorSetLayout = std::shared_ptr<VkDescriptorSetLayout>(
        new VkDescriptorSetLayout(descriptorSetLayout),
        [dev = device.handle()](VkDescriptorSetLayout* layout) {
            vkDestroyDescriptorSetLayout(dev, *layout, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/gamma.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Gamma::Gamma(Vulkan& vk, std::array<std::array<Core::Image, 4>, 3> inImgs1,
        Core::Image inImg2,
        std::optional<Core::Image> optImg)
        : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)),
          optImg(std::move(optImg)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "gamma[0]",
            { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "gamma[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "gamma[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "gamma[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "gamma[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "gamma[0]"),
        vk.shaders.getPipeline(vk.device, "gamma[1]"),
        vk.shaders.getPipeline(vk.device, "gamma[2]"),
        vk.shaders.getPipeline(vk.device, "gamma[3]"),
        vk.shaders.getPipeline(vk.device, "gamma[4]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    this->samplers.at(2) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent();
    for (size_t i = 0; i < 4; i++) {
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);
    }

    this->outImg = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);

    // hook up shaders
    for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(pass_idx + 1) / static_cast<float>(vk.generationCount + 1),
            !this->optImg.has_value());
        for (size_t i = 0; i < 3; i++) {
            pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(0));
            pass.firstDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2))
                .build();
        }
        pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(1));
        pass.descriptorSets.at(0).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(2));
        pass.descriptorSets.at(1).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
            .build();
        pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(3));
        pass.descriptorSets.at(2).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(4));
        pass.descriptorSets.at(3).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg)
            .build();
    }
}

void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) {
    auto& pass = this->passes.at(pass_idx);

    // first shader
    const auto extent = this->tempImgs1.at(0).getExtent();
    const uint32_t threadsX = (extent.width + 7) >> 3;
    const uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .addR2W(this->tempImgs1.at(2))
        .build();

    this->pipelines.at(0).bind(buf);
    pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addW2R(this->tempImgs1.at(2))
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(2).bind(buf);
    pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addW2R(this->optImg)
        .addW2R(this->inImg2)
        .addR2W(this->outImg)
        .build();

    this->pipelines.at(4).bind(buf);
    pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);
}

#include "mini/image.hpp"
#include "common/exception.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>

#include <memory>
#include <cstdint>
#include <optional>

using namespace Mini;

Image::Image(VkDevice device, VkPhysicalDevice physicalDevice,
        VkExtent2D extent, VkFormat format,
        VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd)
        : extent(extent), format(format), aspectFlags(aspectFlags) {
    // create image
    const VkExternalMemoryImageCreateInfo externalInfo{
        .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
        .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
    };
    const VkImageCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pNext = &externalInfo,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = format,
        .extent = {
            .width = extent.width,
            .height = extent.height,
            .depth = 1
        },
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .usage = usage,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VkImage imageHandle{};
    auto res = Layer::ovkCreateImage(device, &desc, nullptr, &imageHandle);
    if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to create Vulkan image");

    // find memory type
    VkPhysicalDeviceMemoryProperties memProps;
    Layer::ovkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps);

    VkMemoryRequirements memReqs;
    Layer::ovkGetImageMemoryRequirements(device, imageHandle, &memReqs);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
    std::optional<uint32_t> memType{};
    for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
        if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
            (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
            memType.emplace(i);
            break;
        } // NOLINTEND
    }
    if (!memType.has_value())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image");
#pragma clang diagnostic pop

    // allocate and bind memory
    const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR,
        .image = imageHandle,
    };
    const VkExportMemoryAllocateInfo exportInfo{
        .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
        .pNext = &dedicatedInfo,
        .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
    };
    const VkMemoryAllocateInfo allocInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = &exportInfo,
        .allocationSize = memReqs.size,
        .memoryTypeIndex = memType.value()
    };
    VkDeviceMemory memoryHandle{};
    res = Layer::ovkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle);
    if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image");

    res = Layer::ovkBindImageMemory(device, imageHandle, memoryHandle, 0);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image");

    // obtain the sharing fd
    const VkMemoryGetFdInfoKHR fdInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
        .memory = memoryHandle,
        .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR,
    };
    res = Layer::ovkGetMemoryFdKHR(device, &fdInfo, fd);
    if (res != VK_SUCCESS || *fd < 0)
        throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image");

    // store objects in shared ptr
    this->image = std::shared_ptr<VkImage>(
        new VkImage(imageHandle),
        [dev = device](VkImage* img) {
            Layer::ovkDestroyImage(dev, *img, nullptr);
        }
    );
    this->memory = std::shared_ptr<VkDeviceMemory>(
        new VkDeviceMemory(memoryHandle),
        [dev = device](VkDeviceMemory* mem) {
            Layer::ovkFreeMemory(dev, *mem, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/delta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Delta::Delta(Vulkan& vk, std::array<std::array<Core::Image, 4>, 3> inImgs1,
        Core::Image inImg2,
        std::optional<Core::Image> optImg1,
        std::optional<Core::Image> optImg2,
        std::optional<Core::Image> optImg3)
        : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)),
          optImg1(std::move(optImg1)), optImg2(std::move(optImg2)),
          optImg3(std::move(optImg3)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "delta[0]",
            { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[5]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[6]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[7]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[8]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "delta[9]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "delta[0]"),
        vk.shaders.getPipeline(vk.device, "delta[1]"),
        vk.shaders.getPipeline(vk.device, "delta[2]"),
        vk.shaders.getPipeline(vk.device, "delta[3]"),
        vk.shaders.getPipeline(vk.device, "delta[4]"),
        vk.shaders.getPipeline(vk.device, "delta[5]"),
        vk.shaders.getPipeline(vk.device, "delta[6]"),
        vk.shaders.getPipeline(vk.device, "delta[7]"),
        vk.shaders.getPipeline(vk.device, "delta[8]"),
        vk.shaders.getPipeline(vk.device, "delta[9]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    this->samplers.at(2) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent();
    for (size_t i = 0; i < 4; i++) {
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);
    }

    this->outImg1 = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);
    this->outImg2 = Core::Image(vk.device,
        { extent.width, extent.height },
        VK_FORMAT_R16G16B16A16_SFLOAT);

    // hook up shaders
    for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) {
        auto& pass = this->passes.emplace_back();
        pass.buffer = vk.resources.getBuffer(vk.device,
            static_cast<float>(pass_idx + 1) / static_cast<float>(vk.generationCount + 1),
            false, !this->optImg1.has_value());
        for (size_t i = 0; i < 3; i++) {
            pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(0));
            pass.firstDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2))
                .build();
        }
        pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(1));
        pass.descriptorSets.at(0).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(2));
        pass.descriptorSets.at(1).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
            .build();
        pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(3));
        pass.descriptorSets.at(2).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
            .build();
        pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(4));
        pass.descriptorSets.at(3).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1)
            .build();
        for (size_t i = 0; i < 3; i++) {
            pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool,
                this->shaderModules.at(5));
            pass.sixthDescriptorSet.at(i).update(vk.device)
                .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
                .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3))
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1)
                .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2)
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0))
                .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1))
                .build();
        }
        pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(6));
        pass.descriptorSets.at(4).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
            .build();
        pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(7));
        pass.descriptorSets.at(5).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1))
            .build();
        pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(8));
        pass.descriptorSets.at(6).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1))
            .build();
        pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool,
            this->shaderModules.at(9));
        pass.descriptorSets.at(7).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg3)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2)
            .build();
    }
}

void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) {
    auto& pass = this->passes.at(pass_idx);

    // first shader
    const auto extent = this->tempImgs1.at(0).getExtent();
    const uint32_t threadsX = (extent.width + 7) >> 3;
    const uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg1)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .addR2W(this->tempImgs1.at(2))
        .build();

    this->pipelines.at(0).bind(buf);
    pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addW2R(this->tempImgs1.at(2))
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(2).bind(buf);
    pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addW2R(this->optImg1)
        .addW2R(this->inImg2)
        .addR2W(this->outImg1)
        .build();

    this->pipelines.at(4).bind(buf);
    pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);

    // sixth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs1.at((frameCount + 2) % 3))
        .addW2R(this->inImgs1.at(frameCount % 3))
        .addW2R(this->optImg1)
        .addW2R(this->optImg2)
        .addR2W(this->tempImgs2.at(0))
        .addR2W(this->tempImgs2.at(1))
        .build();

    this->pipelines.at(5).bind(buf);
    pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5));
    buf.dispatch(threadsX, threadsY, 1);

    // seventh shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2.at(0))
        .addW2R(this->tempImgs2.at(1))
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .build();

    this->pipelines.at(6).bind(buf);
    pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6));
    buf.dispatch(threadsX, threadsY, 1);

    // eighth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addR2W(this->tempImgs2.at(0))
        .addR2W(this->tempImgs2.at(1))
        .build();
    this->pipelines.at(7).bind(buf);
    pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7));
    buf.dispatch(threadsX, threadsY, 1);

    // ninth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2.at(0))
        .addW2R(this->tempImgs2.at(1))
        .addW2R(this->optImg3)
        .addR2W(this->tempImgs1.at(0))
        .addR2W(this->tempImgs1.at(1))
        .build();

    this->pipelines.at(8).bind(buf);
    pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8));
    buf.dispatch(threadsX, threadsY, 1);

    // tenth shader
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1.at(0))
        .addW2R(this->tempImgs1.at(1))
        .addW2R(this->optImg3)
        .addR2W(this->outImg2)
        .build();

    this->pipelines.at(9).bind(buf);
    pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9));
    buf.dispatch(threadsX, threadsY, 1);
}

#pragma once

#include <vulkan/vulkan.h>

#include "util_flags.h"

namespace dxvk {

  struct D3D11Options;

  enum class DxbcFloatControlFlag : uint32_t {
    DenormFlushToZero32,
    DenormPreserve64,
    PreserveNan32,
    PreserveNan64,
  };

  using DxbcFloatControlFlags = Flags<DxbcFloatControlFlag>;

  struct DxbcOptions {
    DxbcOptions() {}

    // Clamp oDepth in fragment shaders if the depth
    // clip device feature is not supported
    bool useDepthClipWorkaround = false;

    /// Determines whether format qualifiers
    /// on typed UAV loads are required
    bool supportsTypedUavLoadR32 = false;

    /// Determines whether raw access chains are supported
    bool supportsRawAccessChains = false;

    /// Clear thread-group shared memory to zero
    bool zeroInitWorkgroupMemory = false;

    /// Declare vertex positions as invariant
    bool invariantPosition = false;

    /// Insert memory barriers after TGSM stoes
    bool forceVolatileTgsmAccess = false;

    /// Try to detect hazards in UAV access and insert
    /// barriers when we know control flow is uniform.
    bool forceComputeUavBarriers = false;

    /// Replace ld_ms with ld
    bool disableMsaa = false;

    /// Force sample rate shading by using sample
    /// interpolation for fragment shader inputs
    bool forceSampleRateShading = false;

    // Enable per-sample interlock if supported
    bool enableSampleShadingInterlock = false;

    /// Use tightly packed arrays for immediate
    /// constant buffers if possible
    bool supportsTightIcbPacking = false;

    /// Whether exporting point size is required
    bool needsPointSizeExport = true;

    /// Whether to enable sincos emulation
    bool sincosEmulation = false;

    /// Float control flags
    DxbcFloatControlFlags floatControl;

    /// Minimum storage buffer alignment
    VkDeviceSize minSsboAlignment = 0;
  };

}

#include "mini/commandbuffer.hpp"
#include "mini/commandpool.hpp"
#include "common/exception.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>

#include <cstdint>
#include <memory>
#include <stdexcept>
#include <vector>

using namespace Mini;

CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) {
    // create command buffer
    const VkCommandBufferAllocateInfo desc{
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = pool.handle(),
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1
    };
    VkCommandBuffer commandBufferHandle{};
    auto res = Layer::ovkAllocateCommandBuffers(device, &desc, &commandBufferHandle);
    if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to allocate command buffer");
    res = Layer::ovkSetDeviceLoaderData(device, commandBufferHandle);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to set device loader data for command buffer");

    // store command buffer in shared ptr
    this->state = std::make_shared<CommandBufferState>(CommandBufferState::Empty);
    this->commandBuffer = std::shared_ptr<VkCommandBuffer>(
        new VkCommandBuffer(commandBufferHandle),
        [dev = device, pool = pool.handle()](VkCommandBuffer* cmdBuffer) {
            Layer::ovkFreeCommandBuffers(dev, pool, 1, cmdBuffer);
        }
    );
}

void CommandBuffer::begin() {
    if (*this->state != CommandBufferState::Empty)
        throw std::logic_error("Command buffer is not in Empty state");

    const VkCommandBufferBeginInfo beginInfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
    };
    auto res = Layer::ovkBeginCommandBuffer(*this->commandBuffer, &beginInfo);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to begin command buffer");

    *this->state = CommandBufferState::Recording;
}

void CommandBuffer::end() {
    if (*this->state != CommandBufferState::Recording)
        throw std::logic_error("Command buffer is not in Recording state");

    auto res = Layer::ovkEndCommandBuffer(*this->commandBuffer);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to end command buffer");

    *this->state = CommandBufferState::Full;
}

void CommandBuffer::submit(VkQueue queue,
        const std::vector<VkSemaphore>& waitSemaphores,
        const std::vector<VkSemaphore>& signalSemaphores) {
    if (*this->state != CommandBufferState::Full)
        throw std::logic_error("Command buffer is not in Full state");

    const std::vector<VkPipelineStageFlags> waitStages(waitSemaphores.size(),
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);

    const VkSubmitInfo submitInfo{
        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()),
        .pWaitSemaphores = waitSemaphores.data(),
        .pWaitDstStageMask = waitStages.data(),
        .commandBufferCount = 1,
        .pCommandBuffers = &(*this->commandBuffer),
        .signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size()),
        .pSignalSemaphores = signalSemaphores.data()
    };
    auto res = Layer::ovkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to submit command buffer");

    *this->state = CommandBufferState::Submitted;
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/semaphore.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <optional>
#include <cstdint>
#include <memory>
#include <stdexcept>

using namespace LSFG::Core;

Semaphore::Semaphore(const Core::Device& device, std::optional<uint32_t> initial) {
    // create semaphore
    const VkSemaphoreTypeCreateInfo typeInfo{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
        .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
        .initialValue = initial.value_or(0)
    };
    const VkSemaphoreCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = initial.has_value() ? &typeInfo : nullptr,
    };
    VkSemaphore semaphoreHandle{};
    auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle);
    if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create semaphore");

    // store semaphore in shared ptr
    this->isTimeline = initial.has_value();
    this->semaphore = std::shared_ptr<VkSemaphore>(
        new VkSemaphore(semaphoreHandle),
        [dev = device.handle()](VkSemaphore* semaphoreHandle) {
            vkDestroySemaphore(dev, *semaphoreHandle, nullptr);
        }
    );
}

Semaphore::Semaphore(const Core::Device& device, int fd) {
    // create semaphore
    const VkExportSemaphoreCreateInfo exportInfo{
        .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
        .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
    };
    const VkSemaphoreCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = &exportInfo
    };
    VkSemaphore semaphoreHandle{};
    auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle);
    if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create semaphore");

    // import semaphore from fd
    auto vkImportSemaphoreFdKHR = reinterpret_cast<PFN_vkImportSemaphoreFdKHR>(
        vkGetDeviceProcAddr(device.handle(), "vkImportSemaphoreFdKHR"));

    const VkImportSemaphoreFdInfoKHR importInfo{
        .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
        .semaphore = semaphoreHandle,
        .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
        .fd = fd // closes the fd
    };
    res = vkImportSemaphoreFdKHR(device.handle(), &importInfo);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to import semaphore from fd");

    // store semaphore in shared ptr
    this->isTimeline = false;
    this->semaphore = std::shared_ptr<VkSemaphore>(
        new VkSemaphore(semaphoreHandle),
        [dev = device.handle()](VkSemaphore* semaphoreHandle) {
            vkDestroySemaphore(dev, *semaphoreHandle, nullptr);
        }
    );
}

void Semaphore::signal(const Core::Device& device, uint64_t value) const {
    if (!this->isTimeline)
        throw std::logic_error("Invalid timeline semaphore");

    const VkSemaphoreSignalInfo signalInfo{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO,
        .semaphore = this->handle(),
        .value = value
    };
    auto res = vkSignalSemaphore(device.handle(), &signalInfo);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to signal semaphore");
}

bool Semaphore::wait(const Core::Device& device, uint64_t value, uint64_t timeout) const {
    if (!this->isTimeline)
        throw std::logic_error("Invalid timeline semaphore");

    VkSemaphore semaphore = this->handle();
    const VkSemaphoreWaitInfo waitInfo{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO,
        .semaphoreCount = 1,
        .pSemaphores = &semaphore,
        .pValues = &value
    };
    auto res = vkWaitSemaphores(device.handle(), &waitInfo, timeout);
    if (res != VK_SUCCESS && res != VK_TIMEOUT)
        throw LSFG::vulkan_error(res, "Unable to wait for semaphore");

    return res == VK_SUCCESS;
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "lsfg_3_1.hpp"
#include "v3_1/context.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/instance.hpp"
#include "pool/shaderpool.hpp"
#include "common/exception.hpp"
#include "common/utils.hpp"

#include <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

using namespace LSFG;
using namespace LSFG_3_1;

namespace {
    std::optional<Core::Instance> instance;
    std::optional<Vulkan> device;
    std::unordered_map<int32_t, Context> contexts;
}

void LSFG_3_1::initialize(uint64_t deviceUUID,
        bool isHdr, float flowScale, uint64_t generationCount,
        const std::function<std::vector<uint8_t>(const std::string&)>& loader) {
    if (instance.has_value() || device.has_value())
        return;

    instance.emplace();
    device.emplace(Vulkan {
        .device{*instance, deviceUUID},
        .generationCount = generationCount,
        .flowScale = flowScale,
        .isHdr = isHdr
    });
    contexts = std::unordered_map<int32_t, Context>();

    device->commandPool = Core::CommandPool(device->device);
    device->descriptorPool = Core::DescriptorPool(device->device);

    device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
    device->shaders = Pool::ShaderPool(loader);

    std::srand(static_cast<uint32_t>(std::time(nullptr)));
}

int32_t LSFG_3_1::createContext(
        int in0, int in1, const std::vector<int>& outN,
        VkExtent2D extent, VkFormat format) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    const int32_t id = std::rand();
    contexts.emplace(id, Context(*device, in0, in1, outN, extent, format));
    return id;
}

void LSFG_3_1::presentContext(int32_t id, int inSem, const std::vector<int>& outSem) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    auto it = contexts.find(id);
    if (it == contexts.end())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");

    it->second.present(*device, inSem, outSem);
}

void LSFG_3_1::deleteContext(int32_t id) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    auto it = contexts.find(id);
    if (it == contexts.end())
        throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");

    vkDeviceWaitIdle(device->device.handle());
    contexts.erase(it);
}

void LSFG_3_1::finalize() {
    if (!instance.has_value() || !device.has_value())
        return;

    vkDeviceWaitIdle(device->device.handle());
    contexts.clear();
    device.reset();
    instance.reset();
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "lsfg_3_1p.hpp"
#include "v3_1p/context.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/instance.hpp"
#include "pool/shaderpool.hpp"
#include "common/exception.hpp"
#include "common/utils.hpp"

#include <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

using namespace LSFG;
using namespace LSFG_3_1P;

namespace {
    std::optional<Core::Instance> instance;
    std::optional<Vulkan> device;
    std::unordered_map<int32_t, Context> contexts;
}

void LSFG_3_1P::initialize(uint64_t deviceUUID,
        bool isHdr, float flowScale, uint64_t generationCount,
        const std::function<std::vector<uint8_t>(const std::string&)>& loader) {
    if (instance.has_value() || device.has_value())
        return;

    instance.emplace();
    device.emplace(Vulkan {
        .device{*instance, deviceUUID},
        .generationCount = generationCount,
        .flowScale = flowScale,
        .isHdr = isHdr
    });
    contexts = std::unordered_map<int32_t, Context>();

    device->commandPool = Core::CommandPool(device->device);
    device->descriptorPool = Core::DescriptorPool(device->device);

    device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
    device->shaders = Pool::ShaderPool(loader);

    std::srand(static_cast<uint32_t>(std::time(nullptr)));
}

int32_t LSFG_3_1P::createContext(
        int in0, int in1, const std::vector<int>& outN,
        VkExtent2D extent, VkFormat format) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    const int32_t id = std::rand();
    contexts.emplace(id, Context(*device, in0, in1, outN, extent, format));
    return id;
}

void LSFG_3_1P::presentContext(int32_t id, int inSem, const std::vector<int>& outSem) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    auto it = contexts.find(id);
    if (it == contexts.end())
        throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");

    it->second.present(*device, inSem, outSem);
}

void LSFG_3_1P::deleteContext(int32_t id) {
    if (!instance.has_value() || !device.has_value())
        throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized");

    auto it = contexts.find(id);
    if (it == contexts.end())
        throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");

    vkDeviceWaitIdle(device->device.handle());
    contexts.erase(it);
}

void LSFG_3_1P::finalize() {
    if (!instance.has_value() || !device.has_value())
        return;

    vkDeviceWaitIdle(device->device.handle());
    contexts.clear();
    device.reset();
    instance.reset();
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include "core/image.hpp"
#include "core/commandbuffer.hpp"

#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Mipmaps::Mipmaps(Vulkan& vk,
        Core::Image inImg_0, Core::Image inImg_1)
        : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) {
    // create resources
    this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps",
        { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
          { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
          { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
          { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
    this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps");
    this->buffer = vk.resources.getBuffer(vk.device);
    this->sampler = vk.resources.getSampler(vk.device);
    for (size_t i = 0; i < 2; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule);

    // create outputs
    const VkExtent2D flowExtent{
        .width = static_cast<uint32_t>(
            static_cast<float>(this->inImg_0.getExtent().width) / vk.flowScale),
        .height = static_cast<uint32_t>(
            static_cast<float>(this->inImg_0.getExtent().height) / vk.flowScale)
    };
    for (size_t i = 0; i < 7; i++)
        this->outImgs.at(i) = Core::Image(vk.device,
            { flowExtent.width >> i, flowExtent.height >> i },
            VK_FORMAT_R8_UNORM);

    // hook up shaders
    for (size_t fc = 0; fc < 2; fc++)
        this->descriptorSets.at(fc).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs)
            .build();
}

void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto flowExtent = this->outImgs.at(0).getExtent();
    const uint32_t threadsX = (flowExtent.width + 63) >> 6;
    const uint32_t threadsY = (flowExtent.height + 63) >> 6;

    Utils::BarrierBuilder(buf)
        .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1)
        .addR2W(this->outImgs)
        .build();

    this->pipeline.bind(buf);
    this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline);
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include "core/image.hpp"
#include "core/commandbuffer.hpp"

#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Mipmaps::Mipmaps(Vulkan& vk,
        Core::Image inImg_0, Core::Image inImg_1)
        : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) {
    // create resources
    this->shaderModule = vk.shaders.getShader(vk.device, "p_mipmaps",
        { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
          { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
          { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
          { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
    this->pipeline = vk.shaders.getPipeline(vk.device, "p_mipmaps");
    this->buffer = vk.resources.getBuffer(vk.device);
    this->sampler = vk.resources.getSampler(vk.device);
    for (size_t i = 0; i < 2; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule);

    // create outputs
    const VkExtent2D flowExtent{
        .width = static_cast<uint32_t>(
            static_cast<float>(this->inImg_0.getExtent().width) / vk.flowScale),
        .height = static_cast<uint32_t>(
            static_cast<float>(this->inImg_0.getExtent().height) / vk.flowScale)
    };
    for (size_t i = 0; i < 7; i++)
        this->outImgs.at(i) = Core::Image(vk.device,
            { flowExtent.width >> i, flowExtent.height >> i },
            VK_FORMAT_R8_UNORM);

    // hook up shaders
    for (size_t fc = 0; fc < 2; fc++)
        this->descriptorSets.at(fc).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs)
            .build();
}

void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto flowExtent = this->outImgs.at(0).getExtent();
    const uint32_t threadsX = (flowExtent.width + 63) >> 6;
    const uint32_t threadsY = (flowExtent.height + 63) >> 6;

    Utils::BarrierBuilder(buf)
        .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1)
        .addR2W(this->outImgs)
        .build();

    this->pipeline.bind(buf);
    this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline);
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/beta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Beta::Beta(Vulkan& vk, std::array<std::array<Core::Image, 4>, 3> inImgs)
        : inImgs(std::move(inImgs)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "beta[0]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 12, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "beta[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "beta[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "beta[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "beta[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "beta[0]"),
        vk.shaders.getPipeline(vk.device, "beta[1]"),
        vk.shaders.getPipeline(vk.device, "beta[2]"),
        vk.shaders.getPipeline(vk.device, "beta[3]"),
        vk.shaders.getPipeline(vk.device, "beta[4]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    for (size_t i = 0; i < 3; i++)
        this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0));
    for (size_t i = 0; i < 4; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1));
    this->buffer = vk.resources.getBuffer(vk.device, 0.5F);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent();
    for (size_t i = 0; i < 2; i++) {
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);
    }

    for (size_t i = 0; i < 6; i++)
        this->outImgs.at(i) = Core::Image(vk.device,
            { extent.width >> i, extent.height >> i },
            VK_FORMAT_R8_UNORM);

    // hook up shaders
    for (size_t i = 0; i < 3; i++) {
        this->firstDescriptorSet.at(i).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
            .build();
    }
    this->descriptorSets.at(0).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
        .build();
    this->descriptorSets.at(1).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
        .build();
    this->descriptorSets.at(2).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
        .build();
    this->descriptorSets.at(3).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs)
        .build();
}

void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto extent = this->tempImgs1.at(0).getExtent();
    uint32_t threadsX = (extent.width + 7) >> 3;
    uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs.at(0))
        .addW2R(this->inImgs.at(1))
        .addW2R(this->inImgs.at(2))
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(0).bind(buf);
    this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    this->descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(2).bind(buf);
    this->descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    this->descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth pass
    threadsX = (extent.width + 31) >> 5;
    threadsY = (extent.height + 31) >> 5;

    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->outImgs)
        .build();

    this->pipelines.at(4).bind(buf);
    this->descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/beta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <array>
#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Beta::Beta(Vulkan& vk, std::array<std::array<Core::Image, 2>, 3> inImgs)
        : inImgs(std::move(inImgs)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "p_beta[0]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_beta[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_beta[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_beta[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_beta[4]",
            { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "p_beta[0]"),
        vk.shaders.getPipeline(vk.device, "p_beta[1]"),
        vk.shaders.getPipeline(vk.device, "p_beta[2]"),
        vk.shaders.getPipeline(vk.device, "p_beta[3]"),
        vk.shaders.getPipeline(vk.device, "p_beta[4]")
    }};
    this->samplers.at(0) = vk.resources.getSampler(vk.device);
    this->samplers.at(1) = vk.resources.getSampler(vk.device,
        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
    for (size_t i = 0; i < 3; i++)
        this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0));
    for (size_t i = 0; i < 4; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1));
    this->buffer = vk.resources.getBuffer(vk.device, 0.5F);

    // create internal images/outputs
    const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent();
    for (size_t i = 0; i < 2; i++) {
        this->tempImgs1.at(i) = Core::Image(vk.device, extent);
        this->tempImgs2.at(i) = Core::Image(vk.device, extent);
    }

    for (size_t i = 0; i < 6; i++)
        this->outImgs.at(i) = Core::Image(vk.device,
            { extent.width >> i, extent.height >> i },
            VK_FORMAT_R8_UNORM);

    // hook up shaders
    for (size_t i = 0; i < 3; i++) {
        this->firstDescriptorSet.at(i).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3))
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3))
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
            .build();
    }
    this->descriptorSets.at(0).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
        .build();
    this->descriptorSets.at(1).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
        .build();
    this->descriptorSets.at(2).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
        .build();
    this->descriptorSets.at(3).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0))
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs)
        .build();
}

void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto extent = this->tempImgs1.at(0).getExtent();
    uint32_t threadsX = (extent.width + 7) >> 3;
    uint32_t threadsY = (extent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImgs.at(0))
        .addW2R(this->inImgs.at(1))
        .addW2R(this->inImgs.at(2))
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(0).bind(buf);
    this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    this->descriptorSets.at(0).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(2).bind(buf);
    this->descriptorSets.at(1).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(3).bind(buf);
    this->descriptorSets.at(2).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);

    // fifth pass
    threadsX = (extent.width + 31) >> 5;
    threadsY = (extent.height + 31) >> 5;

    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->outImgs)
        .build();

    this->pipelines.at(4).bind(buf);
    this->descriptorSets.at(3).bind(buf, this->pipelines.at(4));
    buf.dispatch(threadsX, threadsY, 1);
}

#pragma once

#include <atomic>

#include "../util_likely.h"

namespace dxvk {

  /**
   * \brief Reference-counted object
   */
  class RcObject {

  public:

    /**
     * \brief Increments reference count
     * \returns New reference count
     */
    force_inline uint32_t incRef() {
      return ++m_refCount;
    }

    /**
     * \brief Decrements reference count
     * \returns New reference count
     */
    force_inline uint32_t decRef() {
      return --m_refCount;
    }

  private:

    std::atomic<uint32_t> m_refCount = { 0u };

  };

}

#pragma once

#include <cstdint>
#include <string>

namespace dxvk {

  enum class LogLevel : uint32_t {
    Trace = 0,
    Debug = 1,
    Info  = 2,
    Warn  = 3,
    Error = 4,
    None  = 5,
  };

  /**
   * \brief Logger
   *
   * Logger for one DLL. Creates a text file and
   * writes all log messages to that file.
   */
  class Logger {

  public:

    Logger() {}
    Logger(const std::string& file_name) {}
    ~Logger() {}

    static void trace(const std::string& message) {}
    static void debug(const std::string& message) {}
    static void info (const std::string& message) {}
    static void warn (const std::string& message) {}
    static void err  (const std::string& message) {}
    static void log  (LogLevel level, const std::string& message) {}

    static LogLevel logLevel() {
      return LogLevel::Warn;
    }

  };

}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1/shaders/alpha.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1::Shaders;

Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "alpha[0]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "alpha[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "alpha[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "alpha[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "alpha[0]"),
        vk.shaders.getPipeline(vk.device, "alpha[1]"),
        vk.shaders.getPipeline(vk.device, "alpha[2]"),
        vk.shaders.getPipeline(vk.device, "alpha[3]")
    }};
    this->sampler = vk.resources.getSampler(vk.device);
    for (size_t i = 0; i < 3; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i));
    for (size_t i = 0; i < 3; i++)
        this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3));

    // create internal images/outputs
    const VkExtent2D extent = this->inImg.getExtent();
    const VkExtent2D halfExtent = {
        .width = (extent.width + 1) >> 1,
        .height = (extent.height + 1) >> 1
    };
    for (size_t i = 0; i < 2; i++) {
        this->tempImgs1.at(i) = Core::Image(vk.device, halfExtent);
        this->tempImgs2.at(i) = Core::Image(vk.device, halfExtent);
    }

    const VkExtent2D quarterExtent = {
        .width = (halfExtent.width + 1) >> 1,
        .height = (halfExtent.height + 1) >> 1
    };
    for (size_t i = 0; i < 4; i++) {
        this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent);
        for (size_t j = 0; j < 3; j++)
            this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent);
    }

    // hook up shaders
    this->descriptorSets.at(0).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1)
        .build();
    this->descriptorSets.at(1).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2)
        .build();
    this->descriptorSets.at(2).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3)
        .build();
    for (size_t i = 0; i < 3; i++)
        this->lastDescriptorSet.at(i).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i))
            .build();
}

void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto halfExtent = this->tempImgs1.at(0).getExtent();
    uint32_t threadsX = (halfExtent.width + 7) >> 3;
    uint32_t threadsY = (halfExtent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImg)
        .addR2W(this->tempImgs1)
        .build();

    this->pipelines.at(0).bind(buf);
    this->descriptorSets.at(0).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs1)
        .addR2W(this->tempImgs2)
        .build();

    this->pipelines.at(1).bind(buf);
    this->descriptorSets.at(1).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third pass
    const auto quarterExtent = this->tempImgs3.at(0).getExtent();
    threadsX = (quarterExtent.width + 7) >> 3;
    threadsY = (quarterExtent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs2)
        .addR2W(this->tempImgs3)
        .build();

    this->pipelines.at(2).bind(buf);
    this->descriptorSets.at(2).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs3)
        .addR2W(this->outImgs.at(frameCount % 3))
        .build();

    this->pipelines.at(3).bind(buf);
    this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "v3_1p/shaders/alpha.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"

#include <utility>
#include <cstddef>
#include <cstdint>

using namespace LSFG_3_1P::Shaders;

Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) {
    // create resources
    this->shaderModules = {{
        vk.shaders.getShader(vk.device, "p_alpha[0]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_alpha[1]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_alpha[2]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
        vk.shaders.getShader(vk.device, "p_alpha[3]",
            { { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
              { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
              { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
    }};
    this->pipelines = {{
        vk.shaders.getPipeline(vk.device, "p_alpha[0]"),
        vk.shaders.getPipeline(vk.device, "p_alpha[1]"),
        vk.shaders.getPipeline(vk.device, "p_alpha[2]"),
        vk.shaders.getPipeline(vk.device, "p_alpha[3]")
    }};
    this->sampler = vk.resources.getSampler(vk.device);
    for (size_t i = 0; i < 3; i++)
        this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i));
    for (size_t i = 0; i < 3; i++)
        this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3));

    // create internal images/outputs
    const VkExtent2D extent = this->inImg.getExtent();
    const VkExtent2D halfExtent = {
        .width = (extent.width + 1) >> 1,
        .height = (extent.height + 1) >> 1
    };
    this->tempImg1 = Core::Image(vk.device, halfExtent);
    this->tempImg2 = Core::Image(vk.device, halfExtent);

    const VkExtent2D quarterExtent = {
        .width = (halfExtent.width + 1) >> 1,
        .height = (halfExtent.height + 1) >> 1
    };
    for (size_t i = 0; i < 2; i++) {
        this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent);
        for (size_t j = 0; j < 3; j++)
            this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent);
    }

    // hook up shaders
    this->descriptorSets.at(0).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImg1)
        .build();
    this->descriptorSets.at(1).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImg1)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImg2)
        .build();
    this->descriptorSets.at(2).update(vk.device)
        .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
        .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImg2)
        .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3)
        .build();
    for (size_t i = 0; i < 3; i++)
        this->lastDescriptorSet.at(i).update(vk.device)
            .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler)
            .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3)
            .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i))
            .build();
}

void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) {
    // first pass
    const auto halfExtent = this->tempImg1.getExtent();
    uint32_t threadsX = (halfExtent.width + 7) >> 3;
    uint32_t threadsY = (halfExtent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->inImg)
        .addR2W(this->tempImg1)
        .build();

    this->pipelines.at(0).bind(buf);
    this->descriptorSets.at(0).bind(buf, this->pipelines.at(0));
    buf.dispatch(threadsX, threadsY, 1);

    // second pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImg1)
        .addR2W(this->tempImg2)
        .build();

    this->pipelines.at(1).bind(buf);
    this->descriptorSets.at(1).bind(buf, this->pipelines.at(1));
    buf.dispatch(threadsX, threadsY, 1);

    // third pass
    const auto quarterExtent = this->tempImgs3.at(0).getExtent();
    threadsX = (quarterExtent.width + 7) >> 3;
    threadsY = (quarterExtent.height + 7) >> 3;

    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImg2)
        .addR2W(this->tempImgs3)
        .build();

    this->pipelines.at(2).bind(buf);
    this->descriptorSets.at(2).bind(buf, this->pipelines.at(2));
    buf.dispatch(threadsX, threadsY, 1);

    // fourth pass
    Utils::BarrierBuilder(buf)
        .addW2R(this->tempImgs3)
        .addR2W(this->outImgs.at(frameCount % 3))
        .build();

    this->pipelines.at(3).bind(buf);
    this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3));
    buf.dispatch(threadsX, threadsY, 1);
}

/*
** Copyright (c) 2014-2024 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and/or associated documentation files (the "Materials"),
** to deal in the Materials without restriction, including without limitation
** the rights to use, copy, modify, merge, publish, distribute, sublicense,
** and/or sell copies of the Materials, and to permit persons to whom the
** Materials are furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Materials.
**
** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
** IN THE MATERIALS.
*/

#ifndef GLSLstd450_H
#define GLSLstd450_H

static const int GLSLstd450Version = 100;
static const int GLSLstd450Revision = 3;

enum GLSLstd450 {
    GLSLstd450Bad = 0,              // Don't use

    GLSLstd450Round = 1,
    GLSLstd450RoundEven = 2,
    GLSLstd450Trunc = 3,
    GLSLstd450FAbs = 4,
    GLSLstd450SAbs = 5,
    GLSLstd450FSign = 6,
    GLSLstd450SSign = 7,
    GLSLstd450Floor = 8,
    GLSLstd450Ceil = 9,
    GLSLstd450Fract = 10,

    GLSLstd450Radians = 11,
    GLSLstd450Degrees = 12,
    GLSLstd450Sin = 13,
    GLSLstd450Cos = 14,
    GLSLstd450Tan = 15,
    GLSLstd450Asin = 16,
    GLSLstd450Acos = 17,
    GLSLstd450Atan = 18,
    GLSLstd450Sinh = 19,
    GLSLstd450Cosh = 20,
    GLSLstd450Tanh = 21,
    GLSLstd450Asinh = 22,
    GLSLstd450Acosh = 23,
    GLSLstd450Atanh = 24,
    GLSLstd450Atan2 = 25,

    GLSLstd450Pow = 26,
    GLSLstd450Exp = 27,
    GLSLstd450Log = 28,
    GLSLstd450Exp2 = 29,
    GLSLstd450Log2 = 30,
    GLSLstd450Sqrt = 31,
    GLSLstd450InverseSqrt = 32,

    GLSLstd450Determinant = 33,
    GLSLstd450MatrixInverse = 34,

    GLSLstd450Modf = 35,            // second operand needs an OpVariable to write to
    GLSLstd450ModfStruct = 36,      // no OpVariable operand
    GLSLstd450FMin = 37,
    GLSLstd450UMin = 38,
    GLSLstd450SMin = 39,
    GLSLstd450FMax = 40,
    GLSLstd450UMax = 41,
    GLSLstd450SMax = 42,
    GLSLstd450FClamp = 43,
    GLSLstd450UClamp = 44,
    GLSLstd450SClamp = 45,
    GLSLstd450FMix = 46,
    GLSLstd450IMix = 47,            // Reserved
    GLSLstd450Step = 48,
    GLSLstd450SmoothStep = 49,

    GLSLstd450Fma = 50,
    GLSLstd450Frexp = 51,            // second operand needs an OpVariable to write to
    GLSLstd450FrexpStruct = 52,      // no OpVariable operand
    GLSLstd450Ldexp = 53,

    GLSLstd450PackSnorm4x8 = 54,
    GLSLstd450PackUnorm4x8 = 55,
    GLSLstd450PackSnorm2x16 = 56,
    GLSLstd450PackUnorm2x16 = 57,
    GLSLstd450PackHalf2x16 = 58,
    GLSLstd450PackDouble2x32 = 59,
    GLSLstd450UnpackSnorm2x16 = 60,
    GLSLstd450UnpackUnorm2x16 = 61,
    GLSLstd450UnpackHalf2x16 = 62,
    GLSLstd450UnpackSnorm4x8 = 63,
    GLSLstd450UnpackUnorm4x8 = 64,
    GLSLstd450UnpackDouble2x32 = 65,

    GLSLstd450Length = 66,
    GLSLstd450Distance = 67,
    GLSLstd450Cross = 68,
    GLSLstd450Normalize = 69,
    GLSLstd450FaceForward = 70,
    GLSLstd450Reflect = 71,
    GLSLstd450Refract = 72,

    GLSLstd450FindILsb = 73,
    GLSLstd450FindSMsb = 74,
    GLSLstd450FindUMsb = 75,

    GLSLstd450InterpolateAtCentroid = 76,
    GLSLstd450InterpolateAtSample = 77,
    GLSLstd450InterpolateAtOffset = 78,

    GLSLstd450NMin = 79,
    GLSLstd450NMax = 80,
    GLSLstd450NClamp = 81,

    GLSLstd450Count
};

#endif  // #ifndef GLSLstd450_H

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/descriptorpool.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <array>
#include <cstdint>
#include <memory>

using namespace LSFG::Core;

DescriptorPool::DescriptorPool(const Core::Device& device) {
    // create descriptor pool
    const std::array<VkDescriptorPoolSize, 4> pools{{ // arbitrary limits
        { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 },
        { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 4096 },
        { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 4096 },
        { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 4096 }
    }};
    const VkDescriptorPoolCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
        .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
        .maxSets = 16384,
        .poolSizeCount = static_cast<uint32_t>(pools.size()),
        .pPoolSizes = pools.data()
    };
    VkDescriptorPool poolHandle{};
    auto res = vkCreateDescriptorPool(device.handle(), &desc, nullptr, &poolHandle);
    if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create descriptor pool");

    // store pool in shared ptr
    this->descriptorPool = std::shared_ptr<VkDescriptorPool>(
        new VkDescriptorPool(poolHandle),
        [dev = device.handle()](VkDescriptorPool* poolHandle) {
            vkDestroyDescriptorPool(dev, *poolHandle, nullptr);
        }
    );
}

#pragma once

#include <cstddef>

namespace dxvk {

  struct DxvkEq {
    template<typename T>
    size_t operator () (const T& a, const T& b) const {
      return a.eq(b);
    }
  };

  struct DxvkHash {
    template<typename T>
    size_t operator () (const T& object) const {
      return object.hash();
    }
  };

  class DxvkHashState {

  public:

    void add(size_t hash) {
      m_value ^= hash + 0x9e3779b9
               + (m_value << 6)
               + (m_value >> 2);
    }

    operator size_t () const {
      return m_value;
    }

  private:

    size_t m_value = 0;

  };

}

#pragma once

#include <cmath>

namespace dxvk {

  constexpr size_t CACHE_LINE_SIZE = 64;
  constexpr double pi = 3.14159265359;

  template<typename T>
  constexpr T clamp(T n, T lo, T hi) {
    if (n < lo) return lo;
    if (n > hi) return hi;
    return n;
  }

  template<typename T, typename U = T>
  constexpr T align(T what, U to) {
    return (what + to - 1) & ~(to - 1);
  }

  template<typename T, typename U = T>
  constexpr T alignDown(T what, U to) {
    return (what / to) * to;
  }

  // Equivalent of std::clamp for use with floating point numbers
  // Handles (-){INFINITY,NAN} cases.
  // Will return min in cases of NAN, etc.
  inline float fclamp(float value, float min, float max) {
    return std::fmin(
      std::fmax(value, min), max);
  }

  template<typename T>
  inline T divCeil(T dividend, T divisor) {
    return (dividend + divisor - 1) / divisor;
  }

}

/**
 * volk
 *
 * Copyright (C) 2018-2025, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
 * Report bugs and download new versions at https://github.com/zeux/volk
 *
 * This library is distributed under the MIT License. See notice at the end of this file.
 */
/* clang-format off */
#ifndef VOLK_H_
#define VOLK_H_

#if defined(VULKAN_H_) && !defined(VK_NO_PROTOTYPES)
#	error To use volk, you need to define VK_NO_PROTOTYPES before including vulkan.h
#endif

/* VOLK_GENERATE_VERSION_DEFINE */
#define VOLK_HEADER_VERSION 323
/* VOLK_GENERATE_VERSION_DEFINE */

#ifndef VK_NO_PROTOTYPES
#	define VK_NO_PROTOTYPES
#endif

#ifndef VULKAN_H_
#	ifdef VOLK_VULKAN_H_PATH
#		include VOLK_VULKAN_H_PATH
#	else /* Platform headers included below */
#		include <vulkan/vk_platform.h>
#		include <vulkan/vulkan_core.h>
#	endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct VolkDeviceTable;

/**
 * Initialize library by loading Vulkan loader; call this function before creating the Vulkan instance.
 *
 * Returns VK_SUCCESS on success and VK_ERROR_INITIALIZATION_FAILED otherwise.
 */
VkResult volkInitialize(void);

/**
 * Initialize library by providing a custom handler to load global symbols.
 *
 * This function can be used instead of volkInitialize.
 * The handler function pointer will be asked to load global Vulkan symbols which require no instance
 * (such as vkCreateInstance, vkEnumerateInstance* and vkEnumerateInstanceVersion if available).
 */
void volkInitializeCustom(PFN_vkGetInstanceProcAddr handler);

/**
 * Finalize library by unloading Vulkan loader and resetting global symbols to NULL.
 *
 * This function does not need to be called on process exit (as loader will be unloaded automatically) or if volkInitialize failed.
 * In general this function is optional to call but may be useful in rare cases eg if volk needs to be reinitialized multiple times.
 */
void volkFinalize(void);

/**
 * Get Vulkan instance version supported by the Vulkan loader, or 0 if Vulkan isn't supported
 *
 * Returns 0 if volkInitialize wasn't called or failed.
 */
uint32_t volkGetInstanceVersion(void);

/**
 * Load global function pointers using application-created VkInstance; call this function after creating the Vulkan instance.
 */
void volkLoadInstance(VkInstance instance);

/**
 * Load global function pointers using application-created VkInstance; call this function after creating the Vulkan instance.
 * Skips loading device-based function pointers, requires usage of volkLoadDevice afterwards.
 */
void volkLoadInstanceOnly(VkInstance instance);

/**
 * Load global function pointers using application-created VkDevice; call this function after creating the Vulkan device.
 *
 * Note: this is not suitable for applications that want to use multiple VkDevice objects concurrently.
 */
void volkLoadDevice(VkDevice device);

/**
 * Return last VkInstance for which global function pointers have been loaded via volkLoadInstance(),
 * or VK_NULL_HANDLE if volkLoadInstance() has not been called.
 */
VkInstance volkGetLoadedInstance(void);

/**
 * Return last VkDevice for which global function pointers have been loaded via volkLoadDevice(),
 * or VK_NULL_HANDLE if volkLoadDevice() has not been called.
 */
VkDevice volkGetLoadedDevice(void);

/**
 * Load function pointers using application-created VkDevice into a table.
 * Application should use function pointers from that table instead of using global function pointers.
 */
void volkLoadDeviceTable(struct VolkDeviceTable* table, VkDevice device);

#ifdef __cplusplus
}
#endif

/* Instead of directly including vulkan.h, we include platform-specific parts of the SDK manually
 * This is necessary to avoid including platform headers in some cases (which vulkan.h does unconditionally)
 * and replace them with forward declarations, which makes build times faster and avoids macro conflicts.
 *
 * Note that we only replace platform-specific headers when the headers are known to be problematic: very large
 * or slow to compile (Windows), or introducing unprefixed macros which can cause conflicts (Windows, Xlib).
 */
#if !defined(VULKAN_H_) && !defined(VOLK_VULKAN_H_PATH)

#ifdef VK_USE_PLATFORM_ANDROID_KHR
#include <vulkan/vulkan_android.h>
#endif

#ifdef VK_USE_PLATFORM_FUCHSIA
#include <zircon/types.h>
#include <vulkan/vulkan_fuchsia.h>
#endif

#ifdef VK_USE_PLATFORM_IOS_MVK
#include <vulkan/vulkan_ios.h>
#endif

#ifdef VK_USE_PLATFORM_MACOS_MVK
#include <vulkan/vulkan_macos.h>
#endif

#ifdef VK_USE_PLATFORM_METAL_EXT
#include <vulkan/vulkan_metal.h>
#endif

#ifdef VK_USE_PLATFORM_VI_NN
#include <vulkan/vulkan_vi.h>
#endif

#ifdef VK_USE_PLATFORM_WAYLAND_KHR
#include <vulkan/vulkan_wayland.h>
#endif

#ifdef VK_USE_PLATFORM_WIN32_KHR
typedef unsigned long DWORD;
typedef const wchar_t* LPCWSTR;
typedef void* HANDLE;
typedef struct HINSTANCE__* HINSTANCE;
typedef struct HWND__* HWND;
typedef struct HMONITOR__* HMONITOR;
typedef struct _SECURITY_ATTRIBUTES SECURITY_ATTRIBUTES;
#include <vulkan/vulkan_win32.h>
#endif

#ifdef VK_USE_PLATFORM_XCB_KHR
#include <xcb/xcb.h>
#include <vulkan/vulkan_xcb.h>
#endif

#ifdef VK_USE_PLATFORM_XLIB_KHR
typedef struct _XDisplay Display;
typedef unsigned long Window;
typedef unsigned long VisualID;
#include <vulkan/vulkan_xlib.h>
#endif

#ifdef VK_USE_PLATFORM_DIRECTFB_EXT
#include <directfb.h>
#include <vulkan/vulkan_directfb.h>
#endif

#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
typedef struct _XDisplay Display;
typedef unsigned long RROutput;
#include <vulkan/vulkan_xlib_xrandr.h>
#endif

#ifdef VK_USE_PLATFORM_GGP
#include <ggp_c/vulkan_types.h>
#include <vulkan/vulkan_ggp.h>
#endif

#ifdef VK_USE_PLATFORM_SCREEN_QNX
#include <screen/screen.h>
#include <vulkan/vulkan_screen.h>
#endif

#ifdef VK_USE_PLATFORM_SCI
#include <nvscisync.h>
#include <nvscibuf.h>
#include <vulkan/vulkan_sci.h>
#endif

#ifdef VK_ENABLE_BETA_EXTENSIONS
#include <vulkan/vulkan_beta.h>
#endif

#endif

/**
 * Device-specific function pointer table
 */
struct VolkDeviceTable
{
	/* VOLK_GENERATE_DEVICE_TABLE */
#if defined(VK_VERSION_1_0)
	PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
	PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
	PFN_vkAllocateMemory vkAllocateMemory;
	PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
	PFN_vkBindBufferMemory vkBindBufferMemory;
	PFN_vkBindImageMemory vkBindImageMemory;
	PFN_vkCmdBeginQuery vkCmdBeginQuery;
	PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
	PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
	PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
	PFN_vkCmdBindPipeline vkCmdBindPipeline;
	PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
	PFN_vkCmdBlitImage vkCmdBlitImage;
	PFN_vkCmdClearAttachments vkCmdClearAttachments;
	PFN_vkCmdClearColorImage vkCmdClearColorImage;
	PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
	PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
	PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
	PFN_vkCmdCopyImage vkCmdCopyImage;
	PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
	PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
	PFN_vkCmdDispatch vkCmdDispatch;
	PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
	PFN_vkCmdDraw vkCmdDraw;
	PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
	PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
	PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
	PFN_vkCmdEndQuery vkCmdEndQuery;
	PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
	PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
	PFN_vkCmdFillBuffer vkCmdFillBuffer;
	PFN_vkCmdNextSubpass vkCmdNextSubpass;
	PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
	PFN_vkCmdPushConstants vkCmdPushConstants;
	PFN_vkCmdResetEvent vkCmdResetEvent;
	PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
	PFN_vkCmdResolveImage vkCmdResolveImage;
	PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
	PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
	PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
	PFN_vkCmdSetEvent vkCmdSetEvent;
	PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
	PFN_vkCmdSetScissor vkCmdSetScissor;
	PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
	PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
	PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
	PFN_vkCmdSetViewport vkCmdSetViewport;
	PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
	PFN_vkCmdWaitEvents vkCmdWaitEvents;
	PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
	PFN_vkCreateBuffer vkCreateBuffer;
	PFN_vkCreateBufferView vkCreateBufferView;
	PFN_vkCreateCommandPool vkCreateCommandPool;
	PFN_vkCreateComputePipelines vkCreateComputePipelines;
	PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
	PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
	PFN_vkCreateEvent vkCreateEvent;
	PFN_vkCreateFence vkCreateFence;
	PFN_vkCreateFramebuffer vkCreateFramebuffer;
	PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
	PFN_vkCreateImage vkCreateImage;
	PFN_vkCreateImageView vkCreateImageView;
	PFN_vkCreatePipelineCache vkCreatePipelineCache;
	PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
	PFN_vkCreateQueryPool vkCreateQueryPool;
	PFN_vkCreateRenderPass vkCreateRenderPass;
	PFN_vkCreateSampler vkCreateSampler;
	PFN_vkCreateSemaphore vkCreateSemaphore;
	PFN_vkCreateShaderModule vkCreateShaderModule;
	PFN_vkDestroyBuffer vkDestroyBuffer;
	PFN_vkDestroyBufferView vkDestroyBufferView;
	PFN_vkDestroyCommandPool vkDestroyCommandPool;
	PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
	PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
	PFN_vkDestroyDevice vkDestroyDevice;
	PFN_vkDestroyEvent vkDestroyEvent;
	PFN_vkDestroyFence vkDestroyFence;
	PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
	PFN_vkDestroyImage vkDestroyImage;
	PFN_vkDestroyImageView vkDestroyImageView;
	PFN_vkDestroyPipeline vkDestroyPipeline;
	PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
	PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
	PFN_vkDestroyQueryPool vkDestroyQueryPool;
	PFN_vkDestroyRenderPass vkDestroyRenderPass;
	PFN_vkDestroySampler vkDestroySampler;
	PFN_vkDestroySemaphore vkDestroySemaphore;
	PFN_vkDestroyShaderModule vkDestroyShaderModule;
	PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
	PFN_vkEndCommandBuffer vkEndCommandBuffer;
	PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
	PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
	PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
	PFN_vkFreeMemory vkFreeMemory;
	PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
	PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
	PFN_vkGetDeviceQueue vkGetDeviceQueue;
	PFN_vkGetEventStatus vkGetEventStatus;
	PFN_vkGetFenceStatus vkGetFenceStatus;
	PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
	PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
	PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
	PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
	PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
	PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
	PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
	PFN_vkMapMemory vkMapMemory;
	PFN_vkMergePipelineCaches vkMergePipelineCaches;
	PFN_vkQueueBindSparse vkQueueBindSparse;
	PFN_vkQueueSubmit vkQueueSubmit;
	PFN_vkQueueWaitIdle vkQueueWaitIdle;
	PFN_vkResetCommandBuffer vkResetCommandBuffer;
	PFN_vkResetCommandPool vkResetCommandPool;
	PFN_vkResetDescriptorPool vkResetDescriptorPool;
	PFN_vkResetEvent vkResetEvent;
	PFN_vkResetFences vkResetFences;
	PFN_vkSetEvent vkSetEvent;
	PFN_vkUnmapMemory vkUnmapMemory;
	PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
	PFN_vkWaitForFences vkWaitForFences;
#else
	PFN_vkVoidFunction padding_6ce80d51[120];
#endif /* defined(VK_VERSION_1_0) */
#if defined(VK_VERSION_1_1)
	PFN_vkBindBufferMemory2 vkBindBufferMemory2;
	PFN_vkBindImageMemory2 vkBindImageMemory2;
	PFN_vkCmdDispatchBase vkCmdDispatchBase;
	PFN_vkCmdSetDeviceMask vkCmdSetDeviceMask;
	PFN_vkCreateDescriptorUpdateTemplate vkCreateDescriptorUpdateTemplate;
	PFN_vkCreateSamplerYcbcrConversion vkCreateSamplerYcbcrConversion;
	PFN_vkDestroyDescriptorUpdateTemplate vkDestroyDescriptorUpdateTemplate;
	PFN_vkDestroySamplerYcbcrConversion vkDestroySamplerYcbcrConversion;
	PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2;
	PFN_vkGetDescriptorSetLayoutSupport vkGetDescriptorSetLayoutSupport;
	PFN_vkGetDeviceGroupPeerMemoryFeatures vkGetDeviceGroupPeerMemoryFeatures;
	PFN_vkGetDeviceQueue2 vkGetDeviceQueue2;
	PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2;
	PFN_vkGetImageSparseMemoryRequirements2 vkGetImageSparseMemoryRequirements2;
	PFN_vkTrimCommandPool vkTrimCommandPool;
	PFN_vkUpdateDescriptorSetWithTemplate vkUpdateDescriptorSetWithTemplate;
#else
	PFN_vkVoidFunction padding_1ec56847[16];
#endif /* defined(VK_VERSION_1_1) */
#if defined(VK_VERSION_1_2)
	PFN_vkCmdBeginRenderPass2 vkCmdBeginRenderPass2;
	PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount;
	PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount;
	PFN_vkCmdEndRenderPass2 vkCmdEndRenderPass2;
	PFN_vkCmdNextSubpass2 vkCmdNextSubpass2;
	PFN_vkCreateRenderPass2 vkCreateRenderPass2;
	PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress;
	PFN_vkGetBufferOpaqueCaptureAddress vkGetBufferOpaqueCaptureAddress;
	PFN_vkGetDeviceMemoryOpaqueCaptureAddress vkGetDeviceMemoryOpaqueCaptureAddress;
	PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue;
	PFN_vkResetQueryPool vkResetQueryPool;
	PFN_vkSignalSemaphore vkSignalSemaphore;
	PFN_vkWaitSemaphores vkWaitSemaphores;
#else
	PFN_vkVoidFunction padding_a3e00662[13];
#endif /* defined(VK_VERSION_1_2) */
#if defined(VK_VERSION_1_3)
	PFN_vkCmdBeginRendering vkCmdBeginRendering;
	PFN_vkCmdBindVertexBuffers2 vkCmdBindVertexBuffers2;
	PFN_vkCmdBlitImage2 vkCmdBlitImage2;
	PFN_vkCmdCopyBuffer2 vkCmdCopyBuffer2;
	PFN_vkCmdCopyBufferToImage2 vkCmdCopyBufferToImage2;
	PFN_vkCmdCopyImage2 vkCmdCopyImage2;
	PFN_vkCmdCopyImageToBuffer2 vkCmdCopyImageToBuffer2;
	PFN_vkCmdEndRendering vkCmdEndRendering;
	PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2;
	PFN_vkCmdResetEvent2 vkCmdResetEvent2;
	PFN_vkCmdResolveImage2 vkCmdResolveImage2;
	PFN_vkCmdSetCullMode vkCmdSetCullMode;
	PFN_vkCmdSetDepthBiasEnable vkCmdSetDepthBiasEnable;
	PFN_vkCmdSetDepthBoundsTestEnable vkCmdSetDepthBoundsTestEnable;
	PFN_vkCmdSetDepthCompareOp vkCmdSetDepthCompareOp;
	PFN_vkCmdSetDepthTestEnable vkCmdSetDepthTestEnable;
	PFN_vkCmdSetDepthWriteEnable vkCmdSetDepthWriteEnable;
	PFN_vkCmdSetEvent2 vkCmdSetEvent2;
	PFN_vkCmdSetFrontFace vkCmdSetFrontFace;
	PFN_vkCmdSetPrimitiveRestartEnable vkCmdSetPrimitiveRestartEnable;
	PFN_vkCmdSetPrimitiveTopology vkCmdSetPrimitiveTopology;
	PFN_vkCmdSetRasterizerDiscardEnable vkCmdSetRasterizerDiscardEnable;
	PFN_vkCmdSetScissorWithCount vkCmdSetScissorWithCount;
	PFN_vkCmdSetStencilOp vkCmdSetStencilOp;
	PFN_vkCmdSetStencilTestEnable vkCmdSetStencilTestEnable;
	PFN_vkCmdSetViewportWithCount vkCmdSetViewportWithCount;
	PFN_vkCmdWaitEvents2 vkCmdWaitEvents2;
	PFN_vkCmdWriteTimestamp2 vkCmdWriteTimestamp2;
	PFN_vkCreatePrivateDataSlot vkCreatePrivateDataSlot;
	PFN_vkDestroyPrivateDataSlot vkDestroyPrivateDataSlot;
	PFN_vkGetDeviceBufferMemoryRequirements vkGetDeviceBufferMemoryRequirements;
	PFN_vkGetDeviceImageMemoryRequirements vkGetDeviceImageMemoryRequirements;
	PFN_vkGetDeviceImageSparseMemoryRequirements vkGetDeviceImageSparseMemoryRequirements;
	PFN_vkGetPrivateData vkGetPrivateData;
	PFN_vkQueueSubmit2 vkQueueSubmit2;
	PFN_vkSetPrivateData vkSetPrivateData;
#else
	PFN_vkVoidFunction padding_ee798a88[36];
#endif /* defined(VK_VERSION_1_3) */
#if defined(VK_VERSION_1_4)
	PFN_vkCmdBindDescriptorSets2 vkCmdBindDescriptorSets2;
	PFN_vkCmdBindIndexBuffer2 vkCmdBindIndexBuffer2;
	PFN_vkCmdPushConstants2 vkCmdPushConstants2;
	PFN_vkCmdPushDescriptorSet vkCmdPushDescriptorSet;
	PFN_vkCmdPushDescriptorSet2 vkCmdPushDescriptorSet2;
	PFN_vkCmdPushDescriptorSetWithTemplate vkCmdPushDescriptorSetWithTemplate;
	PFN_vkCmdPushDescriptorSetWithTemplate2 vkCmdPushDescriptorSetWithTemplate2;
	PFN_vkCmdSetLineStipple vkCmdSetLineStipple;
	PFN_vkCmdSetRenderingAttachmentLocations vkCmdSetRenderingAttachmentLocations;
	PFN_vkCmdSetRenderingInputAttachmentIndices vkCmdSetRenderingInputAttachmentIndices;
	PFN_vkCopyImageToImage vkCopyImageToImage;
	PFN_vkCopyImageToMemory vkCopyImageToMemory;
	PFN_vkCopyMemoryToImage vkCopyMemoryToImage;
	PFN_vkGetDeviceImageSubresourceLayout vkGetDeviceImageSubresourceLayout;
	PFN_vkGetImageSubresourceLayout2 vkGetImageSubresourceLayout2;
	PFN_vkGetRenderingAreaGranularity vkGetRenderingAreaGranularity;
	PFN_vkMapMemory2 vkMapMemory2;
	PFN_vkTransitionImageLayout vkTransitionImageLayout;
	PFN_vkUnmapMemory2 vkUnmapMemory2;
#else
	PFN_vkVoidFunction padding_82585fa3[19];
#endif /* defined(VK_VERSION_1_4) */
#if defined(VK_AMDX_shader_enqueue)
	PFN_vkCmdDispatchGraphAMDX vkCmdDispatchGraphAMDX;
	PFN_vkCmdDispatchGraphIndirectAMDX vkCmdDispatchGraphIndirectAMDX;
	PFN_vkCmdDispatchGraphIndirectCountAMDX vkCmdDispatchGraphIndirectCountAMDX;
	PFN_vkCmdInitializeGraphScratchMemoryAMDX vkCmdInitializeGraphScratchMemoryAMDX;
	PFN_vkCreateExecutionGraphPipelinesAMDX vkCreateExecutionGraphPipelinesAMDX;
	PFN_vkGetExecutionGraphPipelineNodeIndexAMDX vkGetExecutionGraphPipelineNodeIndexAMDX;
	PFN_vkGetExecutionGraphPipelineScratchSizeAMDX vkGetExecutionGraphPipelineScratchSizeAMDX;
#else
	PFN_vkVoidFunction padding_9d3e2bba[7];
#endif /* defined(VK_AMDX_shader_enqueue) */
#if defined(VK_AMD_anti_lag)
	PFN_vkAntiLagUpdateAMD vkAntiLagUpdateAMD;
#else
	PFN_vkVoidFunction padding_cf792fb4[1];
#endif /* defined(VK_AMD_anti_lag) */
#if defined(VK_AMD_buffer_marker)
	PFN_vkCmdWriteBufferMarkerAMD vkCmdWriteBufferMarkerAMD;
#else
	PFN_vkVoidFunction padding_7836e92f[1];
#endif /* defined(VK_AMD_buffer_marker) */
#if defined(VK_AMD_buffer_marker) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2))
	PFN_vkCmdWriteBufferMarker2AMD vkCmdWriteBufferMarker2AMD;
#else
	PFN_vkVoidFunction padding_bbf9b7bb[1];
#endif /* defined(VK_AMD_buffer_marker) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2)) */
#if defined(VK_AMD_display_native_hdr)
	PFN_vkSetLocalDimmingAMD vkSetLocalDimmingAMD;
#else
	PFN_vkVoidFunction padding_6b81b2fb[1];
#endif /* defined(VK_AMD_display_native_hdr) */
#if defined(VK_AMD_draw_indirect_count)
	PFN_vkCmdDrawIndexedIndirectCountAMD vkCmdDrawIndexedIndirectCountAMD;
	PFN_vkCmdDrawIndirectCountAMD vkCmdDrawIndirectCountAMD;
#else
	PFN_vkVoidFunction padding_fbfa9964[2];
#endif /* defined(VK_AMD_draw_indirect_count) */
#if defined(VK_AMD_shader_info)
	PFN_vkGetShaderInfoAMD vkGetShaderInfoAMD;
#else
	PFN_vkVoidFunction padding_bfb754b[1];
#endif /* defined(VK_AMD_shader_info) */
#if defined(VK_ANDROID_external_memory_android_hardware_buffer)
	PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID;
	PFN_vkGetMemoryAndroidHardwareBufferANDROID vkGetMemoryAndroidHardwareBufferANDROID;
#else
	PFN_vkVoidFunction padding_c67b1beb[2];
#endif /* defined(VK_ANDROID_external_memory_android_hardware_buffer) */
#if defined(VK_ARM_data_graph)
	PFN_vkBindDataGraphPipelineSessionMemoryARM vkBindDataGraphPipelineSessionMemoryARM;
	PFN_vkCmdDispatchDataGraphARM vkCmdDispatchDataGraphARM;
	PFN_vkCreateDataGraphPipelineSessionARM vkCreateDataGraphPipelineSessionARM;
	PFN_vkCreateDataGraphPipelinesARM vkCreateDataGraphPipelinesARM;
	PFN_vkDestroyDataGraphPipelineSessionARM vkDestroyDataGraphPipelineSessionARM;
	PFN_vkGetDataGraphPipelineAvailablePropertiesARM vkGetDataGraphPipelineAvailablePropertiesARM;
	PFN_vkGetDataGraphPipelinePropertiesARM vkGetDataGraphPipelinePropertiesARM;
	PFN_vkGetDataGraphPipelineSessionBindPointRequirementsARM vkGetDataGraphPipelineSessionBindPointRequirementsARM;
	PFN_vkGetDataGraphPipelineSessionMemoryRequirementsARM vkGetDataGraphPipelineSessionMemoryRequirementsARM;
#else
	PFN_vkVoidFunction padding_894d85d8[9];
#endif /* defined(VK_ARM_data_graph) */
#if defined(VK_ARM_tensors)
	PFN_vkBindTensorMemoryARM vkBindTensorMemoryARM;
	PFN_vkCmdCopyTensorARM vkCmdCopyTensorARM;
	PFN_vkCreateTensorARM vkCreateTensorARM;
	PFN_vkCreateTensorViewARM vkCreateTensorViewARM;
	PFN_vkDestroyTensorARM vkDestroyTensorARM;
	PFN_vkDestroyTensorViewARM vkDestroyTensorViewARM;
	PFN_vkGetDeviceTensorMemoryRequirementsARM vkGetDeviceTensorMemoryRequirementsARM;
	PFN_vkGetTensorMemoryRequirementsARM vkGetTensorMemoryRequirementsARM;
#else
	PFN_vkVoidFunction padding_df67a729[8];
#endif /* defined(VK_ARM_tensors) */
#if defined(VK_ARM_tensors) && defined(VK_EXT_descriptor_buffer)
	PFN_vkGetTensorOpaqueCaptureDescriptorDataARM vkGetTensorOpaqueCaptureDescriptorDataARM;
	PFN_vkGetTensorViewOpaqueCaptureDescriptorDataARM vkGetTensorViewOpaqueCaptureDescriptorDataARM;
#else
	PFN_vkVoidFunction padding_9483bf7e[2];
#endif /* defined(VK_ARM_tensors) && defined(VK_EXT_descriptor_buffer) */
#if defined(VK_EXT_attachment_feedback_loop_dynamic_state)
	PFN_vkCmdSetAttachmentFeedbackLoopEnableEXT vkCmdSetAttachmentFeedbackLoopEnableEXT;
#else
	PFN_vkVoidFunction padding_760a41f5[1];
#endif /* defined(VK_EXT_attachment_feedback_loop_dynamic_state) */
#if defined(VK_EXT_buffer_device_address)
	PFN_vkGetBufferDeviceAddressEXT vkGetBufferDeviceAddressEXT;
#else
	PFN_vkVoidFunction padding_3b69d885[1];
#endif /* defined(VK_EXT_buffer_device_address) */
#if defined(VK_EXT_calibrated_timestamps)
	PFN_vkGetCalibratedTimestampsEXT vkGetCalibratedTimestampsEXT;
#else
	PFN_vkVoidFunction padding_d0981c89[1];
#endif /* defined(VK_EXT_calibrated_timestamps) */
#if defined(VK_EXT_color_write_enable)
	PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT;
#else
	PFN_vkVoidFunction padding_d301ecc3[1];
#endif /* defined(VK_EXT_color_write_enable) */
#if defined(VK_EXT_conditional_rendering)
	PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT;
	PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT;
#else
	PFN_vkVoidFunction padding_ab532c18[2];
#endif /* defined(VK_EXT_conditional_rendering) */
#if defined(VK_EXT_debug_marker)
	PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBeginEXT;
	PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEndEXT;
	PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsertEXT;
	PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectNameEXT;
	PFN_vkDebugMarkerSetObjectTagEXT vkDebugMarkerSetObjectTagEXT;
#else
	PFN_vkVoidFunction padding_89986968[5];
#endif /* defined(VK_EXT_debug_marker) */
#if defined(VK_EXT_depth_bias_control)
	PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT;
#else
	PFN_vkVoidFunction padding_bcddab4d[1];
#endif /* defined(VK_EXT_depth_bias_control) */
#if defined(VK_EXT_descriptor_buffer)
	PFN_vkCmdBindDescriptorBufferEmbeddedSamplersEXT vkCmdBindDescriptorBufferEmbeddedSamplersEXT;
	PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT;
	PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT;
	PFN_vkGetBufferOpaqueCaptureDescriptorDataEXT vkGetBufferOpaqueCaptureDescriptorDataEXT;
	PFN_vkGetDescriptorEXT vkGetDescriptorEXT;
	PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT;
	PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT;
	PFN_vkGetImageOpaqueCaptureDescriptorDataEXT vkGetImageOpaqueCaptureDescriptorDataEXT;
	PFN_vkGetImageViewOpaqueCaptureDescriptorDataEXT vkGetImageViewOpaqueCaptureDescriptorDataEXT;
	PFN_vkGetSamplerOpaqueCaptureDescriptorDataEXT vkGetSamplerOpaqueCaptureDescriptorDataEXT;
#else
	PFN_vkVoidFunction padding_80aa973c[10];
#endif /* defined(VK_EXT_descriptor_buffer) */
#if defined(VK_EXT_descriptor_buffer) && (defined(VK_KHR_acceleration_structure) || defined(VK_NV_ray_tracing))
	PFN_vkGetAccelerationStructureOpaqueCaptureDescriptorDataEXT vkGetAccelerationStructureOpaqueCaptureDescriptorDataEXT;
#else
	PFN_vkVoidFunction padding_98d0fb33[1];
#endif /* defined(VK_EXT_descriptor_buffer) && (defined(VK_KHR_acceleration_structure) || defined(VK_NV_ray_tracing)) */
#if defined(VK_EXT_device_fault)
	PFN_vkGetDeviceFaultInfoEXT vkGetDeviceFaultInfoEXT;
#else
	PFN_vkVoidFunction padding_55095419[1];
#endif /* defined(VK_EXT_device_fault) */
#if defined(VK_EXT_device_generated_commands)
	PFN_vkCmdExecuteGeneratedCommandsEXT vkCmdExecuteGeneratedCommandsEXT;
	PFN_vkCmdPreprocessGeneratedCommandsEXT vkCmdPreprocessGeneratedCommandsEXT;
	PFN_vkCreateIndirectCommandsLayoutEXT vkCreateIndirectCommandsLayoutEXT;
	PFN_vkCreateIndirectExecutionSetEXT vkCreateIndirectExecutionSetEXT;
	PFN_vkDestroyIndirectCommandsLayoutEXT vkDestroyIndirectCommandsLayoutEXT;
	PFN_vkDestroyIndirectExecutionSetEXT vkDestroyIndirectExecutionSetEXT;
	PFN_vkGetGeneratedCommandsMemoryRequirementsEXT vkGetGeneratedCommandsMemoryRequirementsEXT;
	PFN_vkUpdateIndirectExecutionSetPipelineEXT vkUpdateIndirectExecutionSetPipelineEXT;
	PFN_vkUpdateIndirectExecutionSetShaderEXT vkUpdateIndirectExecutionSetShaderEXT;
#else
	PFN_vkVoidFunction padding_7ba7ebaa[9];
#endif /* defined(VK_EXT_device_generated_commands) */
#if defined(VK_EXT_discard_rectangles)
	PFN_vkCmdSetDiscardRectangleEXT vkCmdSetDiscardRectangleEXT;
#else
	PFN_vkVoidFunction padding_d6355c2[1];
#endif /* defined(VK_EXT_discard_rectangles) */
#if defined(VK_EXT_discard_rectangles) && VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION >= 2
	PFN_vkCmdSetDiscardRectangleEnableEXT vkCmdSetDiscardRectangleEnableEXT;
	PFN_vkCmdSetDiscardRectangleModeEXT vkCmdSetDiscardRectangleModeEXT;
#else
	PFN_vkVoidFunction padding_7bb44f77[2];
#endif /* defined(VK_EXT_discard_rectangles) && VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION >= 2 */
#if defined(VK_EXT_display_control)
	PFN_vkDisplayPowerControlEXT vkDisplayPowerControlEXT;
	PFN_vkGetSwapchainCounterEXT vkGetSwapchainCounterEXT;
	PFN_vkRegisterDeviceEventEXT vkRegisterDeviceEventEXT;
	PFN_vkRegisterDisplayEventEXT vkRegisterDisplayEventEXT;
#else
	PFN_vkVoidFunction padding_d30dfaaf[4];
#endif /* defined(VK_EXT_display_control) */
#if defined(VK_EXT_external_memory_host)
	PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT;
#else
	PFN_vkVoidFunction padding_357656e9[1];
#endif /* defined(VK_EXT_external_memory_host) */
#if defined(VK_EXT_external_memory_metal)
	PFN_vkGetMemoryMetalHandleEXT vkGetMemoryMetalHandleEXT;
	PFN_vkGetMemoryMetalHandlePropertiesEXT vkGetMemoryMetalHandlePropertiesEXT;
#else
	PFN_vkVoidFunction padding_37d43fb[2];
#endif /* defined(VK_EXT_external_memory_metal) */
#if defined(VK_EXT_fragment_density_map_offset)
	PFN_vkCmdEndRendering2EXT vkCmdEndRendering2EXT;
#else
	PFN_vkVoidFunction padding_9c90cf11[1];
#endif /* defined(VK_EXT_fragment_density_map_offset) */
#if defined(VK_EXT_full_screen_exclusive)
	PFN_vkAcquireFullScreenExclusiveModeEXT vkAcquireFullScreenExclusiveModeEXT;
	PFN_vkReleaseFullScreenExclusiveModeEXT vkReleaseFullScreenExclusiveModeEXT;
#else
	PFN_vkVoidFunction padding_3859df46[2];
#endif /* defined(VK_EXT_full_screen_exclusive) */
#if defined(VK_EXT_full_screen_exclusive) && (defined(VK_KHR_device_group) || defined(VK_VERSION_1_1))
	PFN_vkGetDeviceGroupSurfacePresentModes2EXT vkGetDeviceGroupSurfacePresentModes2EXT;
#else
	PFN_vkVoidFunction padding_e5b48b5b[1];
#endif /* defined(VK_EXT_full_screen_exclusive) && (defined(VK_KHR_device_group) || defined(VK_VERSION_1_1)) */
#if defined(VK_EXT_hdr_metadata)
	PFN_vkSetHdrMetadataEXT vkSetHdrMetadataEXT;
#else
	PFN_vkVoidFunction padding_ca6d733c[1];
#endif /* defined(VK_EXT_hdr_metadata) */
#if defined(VK_EXT_host_image_copy)
	PFN_vkCopyImageToImageEXT vkCopyImageToImageEXT;
	PFN_vkCopyImageToMemoryEXT vkCopyImageToMemoryEXT;
	PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImageEXT;
	PFN_vkTransitionImageLayoutEXT vkTransitionImageLayoutEXT;
#else
	PFN_vkVoidFunction padding_dd6d9b61[4];
#endif /* defined(VK_EXT_host_image_copy) */
#if defined(VK_EXT_host_query_reset)
	PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT;
#else
	PFN_vkVoidFunction padding_34e58bd3[1];
#endif /* defined(VK_EXT_host_query_reset) */
#if defined(VK_EXT_image_drm_format_modifier)
	PFN_vkGetImageDrmFormatModifierPropertiesEXT vkGetImageDrmFormatModifierPropertiesEXT;
#else
	PFN_vkVoidFunction padding_eb50dc14[1];
#endif /* defined(VK_EXT_image_drm_format_modifier) */
#if defined(VK_EXT_line_rasterization)
	PFN_vkCmdSetLineStippleEXT vkCmdSetLineStippleEXT;
#else
	PFN_vkVoidFunction padding_8a212c37[1];
#endif /* defined(VK_EXT_line_rasterization) */
#if defined(VK_EXT_mesh_shader)
	PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT;
	PFN_vkCmdDrawMeshTasksIndirectEXT vkCmdDrawMeshTasksIndirectEXT;
#else
	PFN_vkVoidFunction padding_f65e838[2];
#endif /* defined(VK_EXT_mesh_shader) */
#if defined(VK_EXT_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2))
	PFN_vkCmdDrawMeshTasksIndirectCountEXT vkCmdDrawMeshTasksIndirectCountEXT;
#else
	PFN_vkVoidFunction padding_dcbaac2f[1];
#endif /* defined(VK_EXT_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2)) */
#if defined(VK_EXT_metal_objects)
	PFN_vkExportMetalObjectsEXT vkExportMetalObjectsEXT;
#else
	PFN_vkVoidFunction padding_df21f735[1];
#endif /* defined(VK_EXT_metal_objects) */
#if defined(VK_EXT_multi_draw)
	PFN_vkCmdDrawMultiEXT vkCmdDrawMultiEXT;
	PFN_vkCmdDrawMultiIndexedEXT vkCmdDrawMultiIndexedEXT;
#else
	PFN_vkVoidFunction padding_ce8b93b6[2];
#endif /* defined(VK_EXT_multi_draw) */
#if defined(VK_EXT_opacity_micromap)
	PFN_vkBuildMicromapsEXT vkBuildMicromapsEXT;
	PFN_vkCmdBuildMicromapsEXT vkCmdBuildMicromapsEXT;
	PFN_vkCmdCopyMemoryToMicromapEXT vkCmdCopyMemoryToMicromapEXT;
	PFN_vkCmdCopyMicromapEXT vkCmdCopyMicromapEXT;
	PFN_vkCmdCopyMicromapToMemoryEXT vkCmdCopyMicromapToMemoryEXT;
	PFN_vkCmdWriteMicromapsPropertiesEXT vkCmdWriteMicromapsPropertiesEXT;
	PFN_vkCopyMemoryToMicromapEXT vkCopyMemoryToMicromapEXT;
	PFN_vkCopyMicromapEXT vkCopyMicromapEXT;
	PFN_vkCopyMicromapToMemoryEXT vkCopyMicromapToMemoryEXT;
	PFN_vkCreateMicromapEXT vkCreateMicromapEXT;
	PFN_vkDestroyMicromapEXT vkDestroyMicromapEXT;
	PFN_vkGetDeviceMicromapCompatibilityEXT vkGetDeviceMicromapCompatibilityEXT;
	PFN_vkGetMicromapBuildSizesEXT vkGetMicromapBuildSizesEXT;
	PFN_vkWriteMicromapsPropertiesEXT vkWriteMicromapsPropertiesEXT;
#else
	PFN_vkVoidFunction padding_fa41e53c[14];
#endif /* defined(VK_EXT_opacity_micromap) */
#if defined(VK_EXT_pageable_device_local_memory)
	PFN_vkSetDeviceMemoryPriorityEXT vkSetDeviceMemoryPriorityEXT;
#else
	PFN_vkVoidFunction padding_b2d2c2d7[1];
#endif /* defined(VK_EXT_pageable_device_local_memory) */
#if defined(VK_EXT_pipeline_properties)
	PFN_vkGetPipelinePropertiesEXT vkGetPipelinePropertiesEXT;
#else
	PFN_vkVoidFunction padding_11313020[1];
#endif /* defined(VK_EXT_pipeline_properties) */
#if defined(VK_EXT_private_data)
	PFN_vkCreatePrivateDataSlotEXT vkCreatePrivateDataSlotEXT;
	PFN_vkDestroyPrivateDataSlotEXT vkDestroyPrivateDataSlotEXT;
	PFN_vkGetPrivateDataEXT vkGetPrivateDataEXT;
	PFN_vkSetPrivateDataEXT vkSetPrivateDataEXT;
#else
	PFN_vkVoidFunction padding_108010f[4];
#endif /* defined(VK_EXT_private_data) */
#if defined(VK_EXT_sample_locations)
	PFN_vkCmdSetSampleLocationsEXT vkCmdSetSampleLocationsEXT;
#else
	PFN_vkVoidFunction padding_26f9079f[1];
#endif /* defined(VK_EXT_sample_locations) */
#if defined(VK_EXT_shader_module_identifier)
	PFN_vkGetShaderModuleCreateInfoIdentifierEXT vkGetShaderModuleCreateInfoIdentifierEXT;
	PFN_vkGetShaderModuleIdentifierEXT vkGetShaderModuleIdentifierEXT;
#else
	PFN_vkVoidFunction padding_e10c8f86[2];
#endif /* defined(VK_EXT_shader_module_identifier) */
#if defined(VK_EXT_shader_object)
	PFN_vkCmdBindShadersEXT vkCmdBindShadersEXT;
	PFN_vkCreateShadersEXT vkCreateShadersEXT;
	PFN_vkDestroyShaderEXT vkDestroyShaderEXT;
	PFN_vkGetShaderBinaryDataEXT vkGetShaderBinaryDataEXT;
#else
	PFN_vkVoidFunction padding_374f3e18[4];
#endif /* defined(VK_EXT_shader_object) */
#if defined(VK_EXT_swapchain_maintenance1)
	PFN_vkReleaseSwapchainImagesEXT vkReleaseSwapchainImagesEXT;
#else
	PFN_vkVoidFunction padding_ea55bf74[1];
#endif /* defined(VK_EXT_swapchain_maintenance1) */
#if defined(VK_EXT_transform_feedback)
	PFN_vkCmdBeginQueryIndexedEXT vkCmdBeginQueryIndexedEXT;
	PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT;
	PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT;
	PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT;
	PFN_vkCmdEndQueryIndexedEXT vkCmdEndQueryIndexedEXT;
	PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT;
#else
	PFN_vkVoidFunction padding_36980658[6];
#endif /* defined(VK_EXT_transform_feedback) */
#if defined(VK_EXT_validation_cache)
	PFN_vkCreateValidationCacheEXT vkCreateValidationCacheEXT;
	PFN_vkDestroyValidationCacheEXT vkDestroyValidationCacheEXT;
	PFN_vkGetValidationCacheDataEXT vkGetValidationCacheDataEXT;
	PFN_vkMergeValidationCachesEXT vkMergeValidationCachesEXT;
#else
	PFN_vkVoidFunction padding_b4f2df29[4];
#endif /* defined(VK_EXT_validation_cache) */
#if defined(VK_FUCHSIA_buffer_collection)
	PFN_vkCreateBufferCollectionFUCHSIA vkCreateBufferCollectionFUCHSIA;
	PFN_vkDestroyBufferCollectionFUCHSIA vkDestroyBufferCollectionFUCHSIA;
	PFN_vkGetBufferCollectionPropertiesFUCHSIA vkGetBufferCollectionPropertiesFUCHSIA;
	PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA vkSetBufferCollectionBufferConstraintsFUCHSIA;
	PFN_vkSetBufferCollectionImageConstraintsFUCHSIA vkSetBufferCollectionImageConstraintsFUCHSIA;
#else
	PFN_vkVoidFunction padding_8eaa27bc[5];
#endif /* defined(VK_FUCHSIA_buffer_collection) */
#if defined(VK_FUCHSIA_external_memory)
	PFN_vkGetMemoryZirconHandleFUCHSIA vkGetMemoryZirconHandleFUCHSIA;
	PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA vkGetMemoryZirconHandlePropertiesFUCHSIA;
#else
	PFN_vkVoidFunction padding_e3cb8a67[2];
#endif /* defined(VK_FUCHSIA_external_memory) */
#if defined(VK_FUCHSIA_external_semaphore)
	PFN_vkGetSemaphoreZirconHandleFUCHSIA vkGetSemaphoreZirconHandleFUCHSIA;
	PFN_vkImportSemaphoreZirconHandleFUCHSIA vkImportSemaphoreZirconHandleFUCHSIA;
#else
	PFN_vkVoidFunction padding_3df6f656[2];
#endif /* defined(VK_FUCHSIA_external_semaphore) */
#if defined(VK_GOOGLE_display_timing)
	PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
	PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#else
	PFN_vkVoidFunction padding_2a6f50cd[2];
#endif /* defined(VK_GOOGLE_display_timing) */
#if defined(VK_HUAWEI_cluster_culling_shader)
	PFN_vkCmdDrawClusterHUAWEI vkCmdDrawClusterHUAWEI;
	PFN_vkCmdDrawClusterIndirectHUAWEI vkCmdDrawClusterIndirectHUAWEI;
#else
	PFN_vkVoidFunction padding_75b97be6[2];
#endif /* defined(VK_HUAWEI_cluster_culling_shader) */
#if defined(VK_HUAWEI_invocation_mask)
	PFN_vkCmdBindInvocationMaskHUAWEI vkCmdBindInvocationMaskHUAWEI;
#else
	PFN_vkVoidFunction padding_c3a4569f[1];
#endif /* defined(VK_HUAWEI_invocation_mask) */
#if defined(VK_HUAWEI_subpass_shading) && VK_HUAWEI_SUBPASS_SHADING_SPEC_VERSION >= 2
	PFN_vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI;
#else
	PFN_vkVoidFunction padding_2e923f32[1];
#endif /* defined(VK_HUAWEI_subpass_shading) && VK_HUAWEI_SUBPASS_SHADING_SPEC_VERSION >= 2 */
#if defined(VK_HUAWEI_subpass_shading)
	PFN_vkCmdSubpassShadingHUAWEI vkCmdSubpassShadingHUAWEI;
#else
	PFN_vkVoidFunction padding_f766fdf5[1];
#endif /* defined(VK_HUAWEI_subpass_shading) */
#if defined(VK_INTEL_performance_query)
	PFN_vkAcquirePerformanceConfigurationINTEL vkAcquirePerformanceConfigurationINTEL;
	PFN_vkCmdSetPerformanceMarkerINTEL vkCmdSetPerformanceMarkerINTEL;
	PFN_vkCmdSetPerformanceOverrideINTEL vkCmdSetPerformanceOverrideINTEL;
	PFN_vkCmdSetPerformanceStreamMarkerINTEL vkCmdSetPerformanceStreamMarkerINTEL;
	PFN_vkGetPerformanceParameterINTEL vkGetPerformanceParameterINTEL;
	PFN_vkInitializePerformanceApiINTEL vkInitializePerformanceApiINTEL;
	PFN_vkQueueSetPerformanceConfigurationINTEL vkQueueSetPerformanceConfigurationINTEL;
	PFN_vkReleasePerformanceConfigurationINTEL vkReleasePerformanceConfigurationINTEL;
	PFN_vkUninitializePerformanceApiINTEL vkUninitializePerformanceApiINTEL;
#else
	PFN_vkVoidFunction padding_495a0a0b[9];
#endif /* defined(VK_INTEL_performance_query) */
#if defined(VK_KHR_acceleration_structure)
	PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR;
	PFN_vkCmdBuildAccelerationStructuresIndirectKHR vkCmdBuildAccelerationStructuresIndirectKHR;
	PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
	PFN_vkCmdCopyAccelerationStructureKHR vkCmdCopyAccelerationStructureKHR;
	PFN_vkCmdCopyAccelerationStructureToMemoryKHR vkCmdCopyAccelerationStructureToMemoryKHR;
	PFN_vkCmdCopyMemoryToAccelerationStructureKHR vkCmdCopyMemoryToAccelerationStructureKHR;
	PFN_vkCmdWriteAccelerationStructuresPropertiesKHR vkCmdWriteAccelerationStructuresPropertiesKHR;
	PFN_vkCopyAccelerationStructureKHR vkCopyAccelerationStructureKHR;
	PFN_vkCopyAccelerationStructureToMemoryKHR vkCopyAccelerationStructureToMemoryKHR;
	PFN_vkCopyMemoryToAccelerationStructureKHR vkCopyMemoryToAccelerationStructureKHR;
	PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
	PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
	PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
	PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
	PFN_vkGetDeviceAccelerationStructureCompatibilityKHR vkGetDeviceAccelerationStructureCompatibilityKHR;
	PFN_vkWriteAccelerationStructuresPropertiesKHR vkWriteAccelerationStructuresPropertiesKHR;
#else
	PFN_vkVoidFunction padding_5a999b78[16];
#endif /* defined(VK_KHR_acceleration_structure) */
#if defined(VK_KHR_bind_memory2)
	PFN_vkBindBufferMemory2KHR vkBindBufferMemory2KHR;
	PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR;
#else
	PFN_vkVoidFunction padding_ed8481f5[2];
#endif /* defined(VK_KHR_bind_memory2) */
#if defined(VK_KHR_buffer_device_address)
	PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR;
	PFN_vkGetBufferOpaqueCaptureAddressKHR vkGetBufferOpaqueCaptureAddressKHR;
	PFN_vkGetDeviceMemoryOpaqueCaptureAddressKHR vkGetDeviceMemoryOpaqueCaptureAddressKHR;
#else
	PFN_vkVoidFunction padding_178fdf81[3];
#endif /* defined(VK_KHR_buffer_device_address) */
#if defined(VK_KHR_calibrated_timestamps)
	PFN_vkGetCalibratedTimestampsKHR vkGetCalibratedTimestampsKHR;
#else
	PFN_vkVoidFunction padding_8fd6f40d[1];
#endif /* defined(VK_KHR_calibrated_timestamps) */
#if defined(VK_KHR_copy_commands2)
	PFN_vkCmdBlitImage2KHR vkCmdBlitImage2KHR;
	PFN_vkCmdCopyBuffer2KHR vkCmdCopyBuffer2KHR;
	PFN_vkCmdCopyBufferToImage2KHR vkCmdCopyBufferToImage2KHR;
	PFN_vkCmdCopyImage2KHR vkCmdCopyImage2KHR;
	PFN_vkCmdCopyImageToBuffer2KHR vkCmdCopyImageToBuffer2KHR;
	PFN_vkCmdResolveImage2KHR vkCmdResolveImage2KHR;
#else
	PFN_vkVoidFunction padding_4c841ff2[6];
#endif /* defined(VK_KHR_copy_commands2) */
#if defined(VK_KHR_create_renderpass2)
	PFN_vkCmdBeginRenderPass2KHR vkCmdBeginRenderPass2KHR;
	PFN_vkCmdEndRenderPass2KHR vkCmdEndRenderPass2KHR;
	PFN_vkCmdNextSubpass2KHR vkCmdNextSubpass2KHR;
	PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR;
#else
	PFN_vkVoidFunction padding_2a0a8727[4];
#endif /* defined(VK_KHR_create_renderpass2) */
#if defined(VK_KHR_deferred_host_operations)
	PFN_vkCreateDeferredOperationKHR vkCreateDeferredOperationKHR;
	PFN_vkDeferredOperationJoinKHR vkDeferredOperationJoinKHR;
	PFN_vkDestroyDeferredOperationKHR vkDestroyDeferredOperationKHR;
	PFN_vkGetDeferredOperationMaxConcurrencyKHR vkGetDeferredOperationMaxConcurrencyKHR;
	PFN_vkGetDeferredOperationResultKHR vkGetDeferredOperationResultKHR;
#else
	PFN_vkVoidFunction padding_346287bb[5];
#endif /* defined(VK_KHR_deferred_host_operations) */
#if defined(VK_KHR_descriptor_update_template)
	PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR;
	PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR;
	PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR;
#else
	PFN_vkVoidFunction padding_3d63aec0[3];
#endif /* defined(VK_KHR_descriptor_update_template) */
#if defined(VK_KHR_device_group)
	PFN_vkCmdDispatchBaseKHR vkCmdDispatchBaseKHR;
	PFN_vkCmdSetDeviceMaskKHR vkCmdSetDeviceMaskKHR;
	PFN_vkGetDeviceGroupPeerMemoryFeaturesKHR vkGetDeviceGroupPeerMemoryFeaturesKHR;
#else
	PFN_vkVoidFunction padding_5ebe16bd[3];
#endif /* defined(VK_KHR_device_group) */
#if defined(VK_KHR_display_swapchain)
	PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#else
	PFN_vkVoidFunction padding_12099367[1];
#endif /* defined(VK_KHR_display_swapchain) */
#if defined(VK_KHR_draw_indirect_count)
	PFN_vkCmdDrawIndexedIndirectCountKHR vkCmdDrawIndexedIndirectCountKHR;
	PFN_vkCmdDrawIndirectCountKHR vkCmdDrawIndirectCountKHR;
#else
	PFN_vkVoidFunction padding_7b5bc4c1[2];
#endif /* defined(VK_KHR_draw_indirect_count) */
#if defined(VK_KHR_dynamic_rendering)
	PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR;
	PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR;
#else
	PFN_vkVoidFunction padding_b80f75a5[2];
#endif /* defined(VK_KHR_dynamic_rendering) */
#if defined(VK_KHR_dynamic_rendering_local_read)
	PFN_vkCmdSetRenderingAttachmentLocationsKHR vkCmdSetRenderingAttachmentLocationsKHR;
	PFN_vkCmdSetRenderingInputAttachmentIndicesKHR vkCmdSetRenderingInputAttachmentIndicesKHR;
#else
	PFN_vkVoidFunction padding_b1510532[2];
#endif /* defined(VK_KHR_dynamic_rendering_local_read) */
#if defined(VK_KHR_external_fence_fd)
	PFN_vkGetFenceFdKHR vkGetFenceFdKHR;
	PFN_vkImportFenceFdKHR vkImportFenceFdKHR;
#else
	PFN_vkVoidFunction padding_a2c787d5[2];
#endif /* defined(VK_KHR_external_fence_fd) */
#if defined(VK_KHR_external_fence_win32)
	PFN_vkGetFenceWin32HandleKHR vkGetFenceWin32HandleKHR;
	PFN_vkImportFenceWin32HandleKHR vkImportFenceWin32HandleKHR;
#else
	PFN_vkVoidFunction padding_55d8e6a9[2];
#endif /* defined(VK_KHR_external_fence_win32) */
#if defined(VK_KHR_external_memory_fd)
	PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR;
	PFN_vkGetMemoryFdPropertiesKHR vkGetMemoryFdPropertiesKHR;
#else
	PFN_vkVoidFunction padding_982d9e19[2];
#endif /* defined(VK_KHR_external_memory_fd) */
#if defined(VK_KHR_external_memory_win32)
	PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR;
	PFN_vkGetMemoryWin32HandlePropertiesKHR vkGetMemoryWin32HandlePropertiesKHR;
#else
	PFN_vkVoidFunction padding_4af9e25a[2];
#endif /* defined(VK_KHR_external_memory_win32) */
#if defined(VK_KHR_external_semaphore_fd)
	PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR;
	PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR;
#else
	PFN_vkVoidFunction padding_2237b7cf[2];
#endif /* defined(VK_KHR_external_semaphore_fd) */
#if defined(VK_KHR_external_semaphore_win32)
	PFN_vkGetSemaphoreWin32HandleKHR vkGetSemaphoreWin32HandleKHR;
	PFN_vkImportSemaphoreWin32HandleKHR vkImportSemaphoreWin32HandleKHR;
#else
	PFN_vkVoidFunction padding_c18dea52[2];
#endif /* defined(VK_KHR_external_semaphore_win32) */
#if defined(VK_KHR_fragment_shading_rate)
	PFN_vkCmdSetFragmentShadingRateKHR vkCmdSetFragmentShadingRateKHR;
#else
	PFN_vkVoidFunction padding_f91b0a90[1];
#endif /* defined(VK_KHR_fragment_shading_rate) */
#if defined(VK_KHR_get_memory_requirements2)
	PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR;
	PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR;
	PFN_vkGetImageSparseMemoryRequirements2KHR vkGetImageSparseMemoryRequirements2KHR;
#else
	PFN_vkVoidFunction padding_79d9c5c4[3];
#endif /* defined(VK_KHR_get_memory_requirements2) */
#if defined(VK_KHR_line_rasterization)
	PFN_vkCmdSetLineStippleKHR vkCmdSetLineStippleKHR;
#else
	PFN_vkVoidFunction padding_83c2939[1];
#endif /* defined(VK_KHR_line_rasterization) */
#if defined(VK_KHR_maintenance1)
	PFN_vkTrimCommandPoolKHR vkTrimCommandPoolKHR;
#else
	PFN_vkVoidFunction padding_4b372c56[1];
#endif /* defined(VK_KHR_maintenance1) */
#if defined(VK_KHR_maintenance3)
	PFN_vkGetDescriptorSetLayoutSupportKHR vkGetDescriptorSetLayoutSupportKHR;
#else
	PFN_vkVoidFunction padding_5ea7858d[1];
#endif /* defined(VK_KHR_maintenance3) */
#if defined(VK_KHR_maintenance4)
	PFN_vkGetDeviceBufferMemoryRequirementsKHR vkGetDeviceBufferMemoryRequirementsKHR;
	PFN_vkGetDeviceImageMemoryRequirementsKHR vkGetDeviceImageMemoryRequirementsKHR;
	PFN_vkGetDeviceImageSparseMemoryRequirementsKHR vkGetDeviceImageSparseMemoryRequirementsKHR;
#else
	PFN_vkVoidFunction padding_8e2d4198[3];
#endif /* defined(VK_KHR_maintenance4) */
#if defined(VK_KHR_maintenance5)
	PFN_vkCmdBindIndexBuffer2KHR vkCmdBindIndexBuffer2KHR;
	PFN_vkGetDeviceImageSubresourceLayoutKHR vkGetDeviceImageSubresourceLayoutKHR;
	PFN_vkGetImageSubresourceLayout2KHR vkGetImageSubresourceLayout2KHR;
	PFN_vkGetRenderingAreaGranularityKHR vkGetRenderingAreaGranularityKHR;
#else
	PFN_vkVoidFunction padding_37040339[4];
#endif /* defined(VK_KHR_maintenance5) */
#if defined(VK_KHR_maintenance6)
	PFN_vkCmdBindDescriptorSets2KHR vkCmdBindDescriptorSets2KHR;
	PFN_vkCmdPushConstants2KHR vkCmdPushConstants2KHR;
#else
	PFN_vkVoidFunction padding_442955d8[2];
#endif /* defined(VK_KHR_maintenance6) */
#if defined(VK_KHR_maintenance6) && defined(VK_KHR_push_descriptor)
	PFN_vkCmdPushDescriptorSet2KHR vkCmdPushDescriptorSet2KHR;
	PFN_vkCmdPushDescriptorSetWithTemplate2KHR vkCmdPushDescriptorSetWithTemplate2KHR;
#else
	PFN_vkVoidFunction padding_80e8513f[2];
#endif /* defined(VK_KHR_maintenance6) && defined(VK_KHR_push_descriptor) */
#if defined(VK_KHR_maintenance6) && defined(VK_EXT_descriptor_buffer)
	PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT vkCmdBindDescriptorBufferEmbeddedSamplers2EXT;
	PFN_vkCmdSetDescriptorBufferOffsets2EXT vkCmdSetDescriptorBufferOffsets2EXT;
#else
	PFN_vkVoidFunction padding_2816b9cd[2];
#endif /* defined(VK_KHR_maintenance6) && defined(VK_EXT_descriptor_buffer) */
#if defined(VK_KHR_map_memory2)
	PFN_vkMapMemory2KHR vkMapMemory2KHR;
	PFN_vkUnmapMemory2KHR vkUnmapMemory2KHR;
#else
	PFN_vkVoidFunction padding_5a6d8986[2];
#endif /* defined(VK_KHR_map_memory2) */
#if defined(VK_KHR_performance_query)
	PFN_vkAcquireProfilingLockKHR vkAcquireProfilingLockKHR;
	PFN_vkReleaseProfilingLockKHR vkReleaseProfilingLockKHR;
#else
	PFN_vkVoidFunction padding_76f2673b[2];
#endif /* defined(VK_KHR_performance_query) */
#if defined(VK_KHR_pipeline_binary)
	PFN_vkCreatePipelineBinariesKHR vkCreatePipelineBinariesKHR;
	PFN_vkDestroyPipelineBinaryKHR vkDestroyPipelineBinaryKHR;
	PFN_vkGetPipelineBinaryDataKHR vkGetPipelineBinaryDataKHR;
	PFN_vkGetPipelineKeyKHR vkGetPipelineKeyKHR;
	PFN_vkReleaseCapturedPipelineDataKHR vkReleaseCapturedPipelineDataKHR;
#else
	PFN_vkVoidFunction padding_65232810[5];
#endif /* defined(VK_KHR_pipeline_binary) */
#if defined(VK_KHR_pipeline_executable_properties)
	PFN_vkGetPipelineExecutableInternalRepresentationsKHR vkGetPipelineExecutableInternalRepresentationsKHR;
	PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR;
	PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR;
#else
	PFN_vkVoidFunction padding_f7629b1e[3];
#endif /* defined(VK_KHR_pipeline_executable_properties) */
#if defined(VK_KHR_present_wait)
	PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
#else
	PFN_vkVoidFunction padding_b16cbe03[1];
#endif /* defined(VK_KHR_present_wait) */
#if defined(VK_KHR_present_wait2)
	PFN_vkWaitForPresent2KHR vkWaitForPresent2KHR;
#else
	PFN_vkVoidFunction padding_7401483a[1];
#endif /* defined(VK_KHR_present_wait2) */
#if defined(VK_KHR_push_descriptor)
	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSetKHR;
#else
	PFN_vkVoidFunction padding_8f7712ad[1];
#endif /* defined(VK_KHR_push_descriptor) */
#if defined(VK_KHR_ray_tracing_maintenance1) && defined(VK_KHR_ray_tracing_pipeline)
	PFN_vkCmdTraceRaysIndirect2KHR vkCmdTraceRaysIndirect2KHR;
#else
	PFN_vkVoidFunction padding_dd5f9b4a[1];
#endif /* defined(VK_KHR_ray_tracing_maintenance1) && defined(VK_KHR_ray_tracing_pipeline) */
#if defined(VK_KHR_ray_tracing_pipeline)
	PFN_vkCmdSetRayTracingPipelineStackSizeKHR vkCmdSetRayTracingPipelineStackSizeKHR;
	PFN_vkCmdTraceRaysIndirectKHR vkCmdTraceRaysIndirectKHR;
	PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR;
	PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;
	PFN_vkGetRayTracingCaptureReplayShaderGroupHandlesKHR vkGetRayTracingCaptureReplayShaderGroupHandlesKHR;
	PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR;
	PFN_vkGetRayTracingShaderGroupStackSizeKHR vkGetRayTracingShaderGroupStackSizeKHR;
#else
	PFN_vkVoidFunction padding_af99aedc[7];
#endif /* defined(VK_KHR_ray_tracing_pipeline) */
#if defined(VK_KHR_sampler_ycbcr_conversion)
	PFN_vkCreateSamplerYcbcrConversionKHR vkCreateSamplerYcbcrConversionKHR;
	PFN_vkDestroySamplerYcbcrConversionKHR vkDestroySamplerYcbcrConversionKHR;
#else
	PFN_vkVoidFunction padding_88e61b30[2];
#endif /* defined(VK_KHR_sampler_ycbcr_conversion) */
#if defined(VK_KHR_shared_presentable_image)
	PFN_vkGetSwapchainStatusKHR vkGetSwapchainStatusKHR;
#else
	PFN_vkVoidFunction padding_1ff3379[1];
#endif /* defined(VK_KHR_shared_presentable_image) */
#if defined(VK_KHR_swapchain)
	PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
	PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
	PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
	PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
	PFN_vkQueuePresentKHR vkQueuePresentKHR;
#else
	PFN_vkVoidFunction padding_a1de893b[5];
#endif /* defined(VK_KHR_swapchain) */
#if defined(VK_KHR_swapchain_maintenance1)
	PFN_vkReleaseSwapchainImagesKHR vkReleaseSwapchainImagesKHR;
#else
	PFN_vkVoidFunction padding_e032d5c4[1];
#endif /* defined(VK_KHR_swapchain_maintenance1) */
#if defined(VK_KHR_synchronization2)
	PFN_vkCmdPipelineBarrier2KHR vkCmdPipelineBarrier2KHR;
	PFN_vkCmdResetEvent2KHR vkCmdResetEvent2KHR;
	PFN_vkCmdSetEvent2KHR vkCmdSetEvent2KHR;
	PFN_vkCmdWaitEvents2KHR vkCmdWaitEvents2KHR;
	PFN_vkCmdWriteTimestamp2KHR vkCmdWriteTimestamp2KHR;
	PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR;
#else
	PFN_vkVoidFunction padding_e85bf128[6];
#endif /* defined(VK_KHR_synchronization2) */
#if defined(VK_KHR_timeline_semaphore)
	PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR;
	PFN_vkSignalSemaphoreKHR vkSignalSemaphoreKHR;
	PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR;
#else
	PFN_vkVoidFunction padding_c799d931[3];
#endif /* defined(VK_KHR_timeline_semaphore) */
#if defined(VK_KHR_video_decode_queue)
	PFN_vkCmdDecodeVideoKHR vkCmdDecodeVideoKHR;
#else
	PFN_vkVoidFunction padding_7a7cc7ad[1];
#endif /* defined(VK_KHR_video_decode_queue) */
#if defined(VK_KHR_video_encode_queue)
	PFN_vkCmdEncodeVideoKHR vkCmdEncodeVideoKHR;
	PFN_vkGetEncodedVideoSessionParametersKHR vkGetEncodedVideoSessionParametersKHR;
#else
	PFN_vkVoidFunction padding_f2997fb4[2];
#endif /* defined(VK_KHR_video_encode_queue) */
#if defined(VK_KHR_video_queue)
	PFN_vkBindVideoSessionMemoryKHR vkBindVideoSessionMemoryKHR;
	PFN_vkCmdBeginVideoCodingKHR vkCmdBeginVideoCodingKHR;
	PFN_vkCmdControlVideoCodingKHR vkCmdControlVideoCodingKHR;
	PFN_vkCmdEndVideoCodingKHR vkCmdEndVideoCodingKHR;
	PFN_vkCreateVideoSessionKHR vkCreateVideoSessionKHR;
	PFN_vkCreateVideoSessionParametersKHR vkCreateVideoSessionParametersKHR;
	PFN_vkDestroyVideoSessionKHR vkDestroyVideoSessionKHR;
	PFN_vkDestroyVideoSessionParametersKHR vkDestroyVideoSessionParametersKHR;
	PFN_vkGetVideoSessionMemoryRequirementsKHR vkGetVideoSessionMemoryRequirementsKHR;
	PFN_vkUpdateVideoSessionParametersKHR vkUpdateVideoSessionParametersKHR;
#else
	PFN_vkVoidFunction padding_98fb7016[10];
#endif /* defined(VK_KHR_video_queue) */
#if defined(VK_NVX_binary_import)
	PFN_vkCmdCuLaunchKernelNVX vkCmdCuLaunchKernelNVX;
	PFN_vkCreateCuFunctionNVX vkCreateCuFunctionNVX;
	PFN_vkCreateCuModuleNVX vkCreateCuModuleNVX;
	PFN_vkDestroyCuFunctionNVX vkDestroyCuFunctionNVX;
	PFN_vkDestroyCuModuleNVX vkDestroyCuModuleNVX;
#else
	PFN_vkVoidFunction padding_eb54309b[5];
#endif /* defined(VK_NVX_binary_import) */
#if defined(VK_NVX_image_view_handle)
	PFN_vkGetImageViewHandleNVX vkGetImageViewHandleNVX;
#else
	PFN_vkVoidFunction padding_887f6736[1];
#endif /* defined(VK_NVX_image_view_handle) */
#if defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 3
	PFN_vkGetImageViewHandle64NVX vkGetImageViewHandle64NVX;
#else
	PFN_vkVoidFunction padding_64ad40e2[1];
#endif /* defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 3 */
#if defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 2
	PFN_vkGetImageViewAddressNVX vkGetImageViewAddressNVX;
#else
	PFN_vkVoidFunction padding_d290479a[1];
#endif /* defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 2 */
#if defined(VK_NV_clip_space_w_scaling)
	PFN_vkCmdSetViewportWScalingNV vkCmdSetViewportWScalingNV;
#else
	PFN_vkVoidFunction padding_88d7eb2e[1];
#endif /* defined(VK_NV_clip_space_w_scaling) */
#if defined(VK_NV_cluster_acceleration_structure)
	PFN_vkCmdBuildClusterAccelerationStructureIndirectNV vkCmdBuildClusterAccelerationStructureIndirectNV;
	PFN_vkGetClusterAccelerationStructureBuildSizesNV vkGetClusterAccelerationStructureBuildSizesNV;
#else
	PFN_vkVoidFunction padding_60e35395[2];
#endif /* defined(VK_NV_cluster_acceleration_structure) */
#if defined(VK_NV_cooperative_vector)
	PFN_vkCmdConvertCooperativeVectorMatrixNV vkCmdConvertCooperativeVectorMatrixNV;
	PFN_vkConvertCooperativeVectorMatrixNV vkConvertCooperativeVectorMatrixNV;
#else
	PFN_vkVoidFunction padding_f4a887d0[2];
#endif /* defined(VK_NV_cooperative_vector) */
#if defined(VK_NV_copy_memory_indirect)
	PFN_vkCmdCopyMemoryIndirectNV vkCmdCopyMemoryIndirectNV;
	PFN_vkCmdCopyMemoryToImageIndirectNV vkCmdCopyMemoryToImageIndirectNV;
#else
	PFN_vkVoidFunction padding_9536230e[2];
#endif /* defined(VK_NV_copy_memory_indirect) */
#if defined(VK_NV_cuda_kernel_launch)
	PFN_vkCmdCudaLaunchKernelNV vkCmdCudaLaunchKernelNV;
	PFN_vkCreateCudaFunctionNV vkCreateCudaFunctionNV;
	PFN_vkCreateCudaModuleNV vkCreateCudaModuleNV;
	PFN_vkDestroyCudaFunctionNV vkDestroyCudaFunctionNV;
	PFN_vkDestroyCudaModuleNV vkDestroyCudaModuleNV;
	PFN_vkGetCudaModuleCacheNV vkGetCudaModuleCacheNV;
#else
	PFN_vkVoidFunction padding_2eabdf3b[6];
#endif /* defined(VK_NV_cuda_kernel_launch) */
#if defined(VK_NV_device_diagnostic_checkpoints)
	PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV;
	PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV;
#else
	PFN_vkVoidFunction padding_adaa5a21[2];
#endif /* defined(VK_NV_device_diagnostic_checkpoints) */
#if defined(VK_NV_device_diagnostic_checkpoints) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2))
	PFN_vkGetQueueCheckpointData2NV vkGetQueueCheckpointData2NV;
#else
	PFN_vkVoidFunction padding_c776633d[1];
#endif /* defined(VK_NV_device_diagnostic_checkpoints) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2)) */
#if defined(VK_NV_device_generated_commands)
	PFN_vkCmdBindPipelineShaderGroupNV vkCmdBindPipelineShaderGroupNV;
	PFN_vkCmdExecuteGeneratedCommandsNV vkCmdExecuteGeneratedCommandsNV;
	PFN_vkCmdPreprocessGeneratedCommandsNV vkCmdPreprocessGeneratedCommandsNV;
	PFN_vkCreateIndirectCommandsLayoutNV vkCreateIndirectCommandsLayoutNV;
	PFN_vkDestroyIndirectCommandsLayoutNV vkDestroyIndirectCommandsLayoutNV;
	PFN_vkGetGeneratedCommandsMemoryRequirementsNV vkGetGeneratedCommandsMemoryRequirementsNV;
#else
	PFN_vkVoidFunction padding_4c7e4395[6];
#endif /* defined(VK_NV_device_generated_commands) */
#if defined(VK_NV_device_generated_commands_compute)
	PFN_vkCmdUpdatePipelineIndirectBufferNV vkCmdUpdatePipelineIndirectBufferNV;
	PFN_vkGetPipelineIndirectDeviceAddressNV vkGetPipelineIndirectDeviceAddressNV;
	PFN_vkGetPipelineIndirectMemoryRequirementsNV vkGetPipelineIndirectMemoryRequirementsNV;
#else
	PFN_vkVoidFunction padding_5195094c[3];
#endif /* defined(VK_NV_device_generated_commands_compute) */
#if defined(VK_NV_external_compute_queue)
	PFN_vkCreateExternalComputeQueueNV vkCreateExternalComputeQueueNV;
	PFN_vkDestroyExternalComputeQueueNV vkDestroyExternalComputeQueueNV;
	PFN_vkGetExternalComputeQueueDataNV vkGetExternalComputeQueueDataNV;
#else
	PFN_vkVoidFunction padding_4f947e0b[3];
#endif /* defined(VK_NV_external_compute_queue) */
#if defined(VK_NV_external_memory_rdma)
	PFN_vkGetMemoryRemoteAddressNV vkGetMemoryRemoteAddressNV;
#else
	PFN_vkVoidFunction padding_920e405[1];
#endif /* defined(VK_NV_external_memory_rdma) */
#if defined(VK_NV_external_memory_win32)
	PFN_vkGetMemoryWin32HandleNV vkGetMemoryWin32HandleNV;
#else
	PFN_vkVoidFunction padding_c13d6f3a[1];
#endif /* defined(VK_NV_external_memory_win32) */
#if defined(VK_NV_fragment_shading_rate_enums)
	PFN_vkCmdSetFragmentShadingRateEnumNV vkCmdSetFragmentShadingRateEnumNV;
#else
	PFN_vkVoidFunction padding_4979ca14[1];
#endif /* defined(VK_NV_fragment_shading_rate_enums) */
#if defined(VK_NV_low_latency2)
	PFN_vkGetLatencyTimingsNV vkGetLatencyTimingsNV;
	PFN_vkLatencySleepNV vkLatencySleepNV;
	PFN_vkQueueNotifyOutOfBandNV vkQueueNotifyOutOfBandNV;
	PFN_vkSetLatencyMarkerNV vkSetLatencyMarkerNV;
	PFN_vkSetLatencySleepModeNV vkSetLatencySleepModeNV;
#else
	PFN_vkVoidFunction padding_fabf8b19[5];
#endif /* defined(VK_NV_low_latency2) */
#if defined(VK_NV_memory_decompression)
	PFN_vkCmdDecompressMemoryIndirectCountNV vkCmdDecompressMemoryIndirectCountNV;
	PFN_vkCmdDecompressMemoryNV vkCmdDecompressMemoryNV;
#else
	PFN_vkVoidFunction padding_706009[2];
#endif /* defined(VK_NV_memory_decompression) */
#if defined(VK_NV_mesh_shader)
	PFN_vkCmdDrawMeshTasksIndirectNV vkCmdDrawMeshTasksIndirectNV;
	PFN_vkCmdDrawMeshTasksNV vkCmdDrawMeshTasksNV;
#else
	PFN_vkVoidFunction padding_ac232758[2];
#endif /* defined(VK_NV_mesh_shader) */
#if defined(VK_NV_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2))
	PFN_vkCmdDrawMeshTasksIndirectCountNV vkCmdDrawMeshTasksIndirectCountNV;
#else
	PFN_vkVoidFunction padding_53495be7[1];
#endif /* defined(VK_NV_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2)) */
#if defined(VK_NV_optical_flow)
	PFN_vkBindOpticalFlowSessionImageNV vkBindOpticalFlowSessionImageNV;
	PFN_vkCmdOpticalFlowExecuteNV vkCmdOpticalFlowExecuteNV;
	PFN_vkCreateOpticalFlowSessionNV vkCreateOpticalFlowSessionNV;
	PFN_vkDestroyOpticalFlowSessionNV vkDestroyOpticalFlowSessionNV;
#else
	PFN_vkVoidFunction padding_f67571eb[4];
#endif /* defined(VK_NV_optical_flow) */
#if defined(VK_NV_partitioned_acceleration_structure)
	PFN_vkCmdBuildPartitionedAccelerationStructuresNV vkCmdBuildPartitionedAccelerationStructuresNV;
	PFN_vkGetPartitionedAccelerationStructuresBuildSizesNV vkGetPartitionedAccelerationStructuresBuildSizesNV;
#else
	PFN_vkVoidFunction padding_d27c8c6d[2];
#endif /* defined(VK_NV_partitioned_acceleration_structure) */
#if defined(VK_NV_ray_tracing)
	PFN_vkBindAccelerationStructureMemoryNV vkBindAccelerationStructureMemoryNV;
	PFN_vkCmdBuildAccelerationStructureNV vkCmdBuildAccelerationStructureNV;
	PFN_vkCmdCopyAccelerationStructureNV vkCmdCopyAccelerationStructureNV;
	PFN_vkCmdTraceRaysNV vkCmdTraceRaysNV;
	PFN_vkCmdWriteAccelerationStructuresPropertiesNV vkCmdWriteAccelerationStructuresPropertiesNV;
	PFN_vkCompileDeferredNV vkCompileDeferredNV;
	PFN_vkCreateAccelerationStructureNV vkCreateAccelerationStructureNV;
	PFN_vkCreateRayTracingPipelinesNV vkCreateRayTracingPipelinesNV;
	PFN_vkDestroyAccelerationStructureNV vkDestroyAccelerationStructureNV;
	PFN_vkGetAccelerationStructureHandleNV vkGetAccelerationStructureHandleNV;
	PFN_vkGetAccelerationStructureMemoryRequirementsNV vkGetAccelerationStructureMemoryRequirementsNV;
	PFN_vkGetRayTracingShaderGroupHandlesNV vkGetRayTracingShaderGroupHandlesNV;
#else
	PFN_vkVoidFunction padding_feefbeac[12];
#endif /* defined(VK_NV_ray_tracing) */
#if defined(VK_NV_scissor_exclusive) && VK_NV_SCISSOR_EXCLUSIVE_SPEC_VERSION >= 2
	PFN_vkCmdSetExclusiveScissorEnableNV vkCmdSetExclusiveScissorEnableNV;
#else
	PFN_vkVoidFunction padding_e3c24f80[1];
#endif /* defined(VK_NV_scissor_exclusive) && VK_NV_SCISSOR_EXCLUSIVE_SPEC_VERSION >= 2 */
#if defined(VK_NV_scissor_exclusive)
	PFN_vkCmdSetExclusiveScissorNV vkCmdSetExclusiveScissorNV;
#else
	PFN_vkVoidFunction padding_8e88d86c[1];
#endif /* defined(VK_NV_scissor_exclusive) */
#if defined(VK_NV_shading_rate_image)
	PFN_vkCmdBindShadingRateImageNV vkCmdBindShadingRateImageNV;
	PFN_vkCmdSetCoarseSampleOrderNV vkCmdSetCoarseSampleOrderNV;
	PFN_vkCmdSetViewportShadingRatePaletteNV vkCmdSetViewportShadingRatePaletteNV;
#else
	PFN_vkVoidFunction padding_92a0767f[3];
#endif /* defined(VK_NV_shading_rate_image) */
#if defined(VK_QCOM_tile_memory_heap)
	PFN_vkCmdBindTileMemoryQCOM vkCmdBindTileMemoryQCOM;
#else
	PFN_vkVoidFunction padding_e2d55d04[1];
#endif /* defined(VK_QCOM_tile_memory_heap) */
#if defined(VK_QCOM_tile_properties)
	PFN_vkGetDynamicRenderingTilePropertiesQCOM vkGetDynamicRenderingTilePropertiesQCOM;
	PFN_vkGetFramebufferTilePropertiesQCOM vkGetFramebufferTilePropertiesQCOM;
#else
	PFN_vkVoidFunction padding_be12e32[2];
#endif /* defined(VK_QCOM_tile_properties) */
#if defined(VK_QCOM_tile_shading)
	PFN_vkCmdBeginPerTileExecutionQCOM vkCmdBeginPerTileExecutionQCOM;
	PFN_vkCmdDispatchTileQCOM vkCmdDispatchTileQCOM;
	PFN_vkCmdEndPerTileExecutionQCOM vkCmdEndPerTileExecutionQCOM;
#else
	PFN_vkVoidFunction padding_fcd9e1df[3];
#endif /* defined(VK_QCOM_tile_shading) */
#if defined(VK_QNX_external_memory_screen_buffer)
	PFN_vkGetScreenBufferPropertiesQNX vkGetScreenBufferPropertiesQNX;
#else
	PFN_vkVoidFunction padding_1c27735d[1];
#endif /* defined(VK_QNX_external_memory_screen_buffer) */
#if defined(VK_VALVE_descriptor_set_host_mapping)
	PFN_vkGetDescriptorSetHostMappingVALVE vkGetDescriptorSetHostMappingVALVE;
	PFN_vkGetDescriptorSetLayoutHostMappingInfoVALVE vkGetDescriptorSetLayoutHostMappingInfoVALVE;
#else
	PFN_vkVoidFunction padding_fd71e4c6[2];
#endif /* defined(VK_VALVE_descriptor_set_host_mapping) */
#if (defined(VK_EXT_depth_clamp_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clamp_control))
	PFN_vkCmdSetDepthClampRangeEXT vkCmdSetDepthClampRangeEXT;
#else
	PFN_vkVoidFunction padding_faa18a61[1];
#endif /* (defined(VK_EXT_depth_clamp_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clamp_control)) */
#if (defined(VK_EXT_extended_dynamic_state)) || (defined(VK_EXT_shader_object))
	PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
	PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
	PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
	PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
	PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
	PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
	PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
	PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
	PFN_vkCmdSetScissorWithCountEXT vkCmdSetScissorWithCountEXT;
	PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
	PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
	PFN_vkCmdSetViewportWithCountEXT vkCmdSetViewportWithCountEXT;
#else
	PFN_vkVoidFunction padding_3e8c720f[12];
#endif /* (defined(VK_EXT_extended_dynamic_state)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state2)) || (defined(VK_EXT_shader_object))
	PFN_vkCmdSetDepthBiasEnableEXT vkCmdSetDepthBiasEnableEXT;
	PFN_vkCmdSetLogicOpEXT vkCmdSetLogicOpEXT;
	PFN_vkCmdSetPatchControlPointsEXT vkCmdSetPatchControlPointsEXT;
	PFN_vkCmdSetPrimitiveRestartEnableEXT vkCmdSetPrimitiveRestartEnableEXT;
	PFN_vkCmdSetRasterizerDiscardEnableEXT vkCmdSetRasterizerDiscardEnableEXT;
#else
	PFN_vkVoidFunction padding_b93e02a6[5];
#endif /* (defined(VK_EXT_extended_dynamic_state2)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3)) || (defined(VK_EXT_shader_object))
	PFN_vkCmdSetAlphaToCoverageEnableEXT vkCmdSetAlphaToCoverageEnableEXT;
	PFN_vkCmdSetAlphaToOneEnableEXT vkCmdSetAlphaToOneEnableEXT;
	PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT;
	PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT;
	PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT;
	PFN_vkCmdSetDepthClampEnableEXT vkCmdSetDepthClampEnableEXT;
	PFN_vkCmdSetLogicOpEnableEXT vkCmdSetLogicOpEnableEXT;
	PFN_vkCmdSetPolygonModeEXT vkCmdSetPolygonModeEXT;
	PFN_vkCmdSetRasterizationSamplesEXT vkCmdSetRasterizationSamplesEXT;
	PFN_vkCmdSetSampleMaskEXT vkCmdSetSampleMaskEXT;
#else
	PFN_vkVoidFunction padding_ab566e7e[10];
#endif /* (defined(VK_EXT_extended_dynamic_state3)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3) && (defined(VK_KHR_maintenance2) || defined(VK_VERSION_1_1))) || (defined(VK_EXT_shader_object))
	PFN_vkCmdSetTessellationDomainOriginEXT vkCmdSetTessellationDomainOriginEXT;
#else
	PFN_vkVoidFunction padding_6730ed0c[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && (defined(VK_KHR_maintenance2) || defined(VK_VERSION_1_1))) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_transform_feedback)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_transform_feedback))
	PFN_vkCmdSetRasterizationStreamEXT vkCmdSetRasterizationStreamEXT;
#else
	PFN_vkVoidFunction padding_d3ebb335[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_transform_feedback)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_transform_feedback)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_conservative_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_conservative_rasterization))
	PFN_vkCmdSetConservativeRasterizationModeEXT vkCmdSetConservativeRasterizationModeEXT;
	PFN_vkCmdSetExtraPrimitiveOverestimationSizeEXT vkCmdSetExtraPrimitiveOverestimationSizeEXT;
#else
	PFN_vkVoidFunction padding_a21758f4[2];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_conservative_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_conservative_rasterization)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_enable)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_enable))
	PFN_vkCmdSetDepthClipEnableEXT vkCmdSetDepthClipEnableEXT;
#else
	PFN_vkVoidFunction padding_a498a838[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_enable)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_enable)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_sample_locations)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_sample_locations))
	PFN_vkCmdSetSampleLocationsEnableEXT vkCmdSetSampleLocationsEnableEXT;
#else
	PFN_vkVoidFunction padding_67db38de[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_sample_locations)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_sample_locations)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_blend_operation_advanced)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_blend_operation_advanced))
	PFN_vkCmdSetColorBlendAdvancedEXT vkCmdSetColorBlendAdvancedEXT;
#else
	PFN_vkVoidFunction padding_fbea7481[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_blend_operation_advanced)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_blend_operation_advanced)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_provoking_vertex)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_provoking_vertex))
	PFN_vkCmdSetProvokingVertexModeEXT vkCmdSetProvokingVertexModeEXT;
#else
	PFN_vkVoidFunction padding_3a8ec90e[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_provoking_vertex)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_provoking_vertex)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_line_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_line_rasterization))
	PFN_vkCmdSetLineRasterizationModeEXT vkCmdSetLineRasterizationModeEXT;
	PFN_vkCmdSetLineStippleEnableEXT vkCmdSetLineStippleEnableEXT;
#else
	PFN_vkVoidFunction padding_29cdb756[2];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_line_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_line_rasterization)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_control))
	PFN_vkCmdSetDepthClipNegativeOneToOneEXT vkCmdSetDepthClipNegativeOneToOneEXT;
#else
	PFN_vkVoidFunction padding_815a7240[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_control)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_clip_space_w_scaling)) || (defined(VK_EXT_shader_object) && defined(VK_NV_clip_space_w_scaling))
	PFN_vkCmdSetViewportWScalingEnableNV vkCmdSetViewportWScalingEnableNV;
#else
	PFN_vkVoidFunction padding_d1f00511[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_clip_space_w_scaling)) || (defined(VK_EXT_shader_object) && defined(VK_NV_clip_space_w_scaling)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_viewport_swizzle)) || (defined(VK_EXT_shader_object) && defined(VK_NV_viewport_swizzle))
	PFN_vkCmdSetViewportSwizzleNV vkCmdSetViewportSwizzleNV;
#else
	PFN_vkVoidFunction padding_7a73d553[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_viewport_swizzle)) || (defined(VK_EXT_shader_object) && defined(VK_NV_viewport_swizzle)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_fragment_coverage_to_color)) || (defined(VK_EXT_shader_object) && defined(VK_NV_fragment_coverage_to_color))
	PFN_vkCmdSetCoverageToColorEnableNV vkCmdSetCoverageToColorEnableNV;
	PFN_vkCmdSetCoverageToColorLocationNV vkCmdSetCoverageToColorLocationNV;
#else
	PFN_vkVoidFunction padding_6045fb8c[2];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_fragment_coverage_to_color)) || (defined(VK_EXT_shader_object) && defined(VK_NV_fragment_coverage_to_color)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_framebuffer_mixed_samples)) || (defined(VK_EXT_shader_object) && defined(VK_NV_framebuffer_mixed_samples))
	PFN_vkCmdSetCoverageModulationModeNV vkCmdSetCoverageModulationModeNV;
	PFN_vkCmdSetCoverageModulationTableEnableNV vkCmdSetCoverageModulationTableEnableNV;
	PFN_vkCmdSetCoverageModulationTableNV vkCmdSetCoverageModulationTableNV;
#else
	PFN_vkVoidFunction padding_bdc35c80[3];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_framebuffer_mixed_samples)) || (defined(VK_EXT_shader_object) && defined(VK_NV_framebuffer_mixed_samples)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_shading_rate_image)) || (defined(VK_EXT_shader_object) && defined(VK_NV_shading_rate_image))
	PFN_vkCmdSetShadingRateImageEnableNV vkCmdSetShadingRateImageEnableNV;
#else
	PFN_vkVoidFunction padding_9a5cd6e8[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_shading_rate_image)) || (defined(VK_EXT_shader_object) && defined(VK_NV_shading_rate_image)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_representative_fragment_test)) || (defined(VK_EXT_shader_object) && defined(VK_NV_representative_fragment_test))
	PFN_vkCmdSetRepresentativeFragmentTestEnableNV vkCmdSetRepresentativeFragmentTestEnableNV;
#else
	PFN_vkVoidFunction padding_3ee17e96[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_representative_fragment_test)) || (defined(VK_EXT_shader_object) && defined(VK_NV_representative_fragment_test)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_coverage_reduction_mode)) || (defined(VK_EXT_shader_object) && defined(VK_NV_coverage_reduction_mode))
	PFN_vkCmdSetCoverageReductionModeNV vkCmdSetCoverageReductionModeNV;
#else
	PFN_vkVoidFunction padding_263d525a[1];
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_coverage_reduction_mode)) || (defined(VK_EXT_shader_object) && defined(VK_NV_coverage_reduction_mode)) */
#if (defined(VK_EXT_host_image_copy)) || (defined(VK_EXT_image_compression_control))
	PFN_vkGetImageSubresourceLayout2EXT vkGetImageSubresourceLayout2EXT;
#else
	PFN_vkVoidFunction padding_ecddace1[1];
#endif /* (defined(VK_EXT_host_image_copy)) || (defined(VK_EXT_image_compression_control)) */
#if (defined(VK_EXT_shader_object)) || (defined(VK_EXT_vertex_input_dynamic_state))
	PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT;
#else
	PFN_vkVoidFunction padding_d83e1de1[1];
#endif /* (defined(VK_EXT_shader_object)) || (defined(VK_EXT_vertex_input_dynamic_state)) */
#if (defined(VK_KHR_descriptor_update_template) && defined(VK_KHR_push_descriptor)) || (defined(VK_KHR_push_descriptor) && (defined(VK_VERSION_1_1) || defined(VK_KHR_descriptor_update_template)))
	PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR;
#else
	PFN_vkVoidFunction padding_60f8358a[1];
#endif /* (defined(VK_KHR_descriptor_update_template) && defined(VK_KHR_push_descriptor)) || (defined(VK_KHR_push_descriptor) && (defined(VK_VERSION_1_1) || defined(VK_KHR_descriptor_update_template))) */
#if (defined(VK_KHR_device_group) && defined(VK_KHR_surface)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1))
	PFN_vkGetDeviceGroupPresentCapabilitiesKHR vkGetDeviceGroupPresentCapabilitiesKHR;
	PFN_vkGetDeviceGroupSurfacePresentModesKHR vkGetDeviceGroupSurfacePresentModesKHR;
#else
	PFN_vkVoidFunction padding_460290c6[2];
#endif /* (defined(VK_KHR_device_group) && defined(VK_KHR_surface)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1)) */
#if (defined(VK_KHR_device_group) && defined(VK_KHR_swapchain)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1))
	PFN_vkAcquireNextImage2KHR vkAcquireNextImage2KHR;
#else
	PFN_vkVoidFunction padding_cffc198[1];
#endif /* (defined(VK_KHR_device_group) && defined(VK_KHR_swapchain)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1)) */
	/* VOLK_GENERATE_DEVICE_TABLE */
};

#ifdef __cplusplus
extern "C" {
#endif

/* VOLK_GENERATE_PROTOTYPES_H */
#if defined(VK_VERSION_1_0)
extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
extern PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
extern PFN_vkAllocateMemory vkAllocateMemory;
extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
extern PFN_vkBindBufferMemory vkBindBufferMemory;
extern PFN_vkBindImageMemory vkBindImageMemory;
extern PFN_vkCmdBeginQuery vkCmdBeginQuery;
extern PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
extern PFN_vkCmdBindPipeline vkCmdBindPipeline;
extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
extern PFN_vkCmdBlitImage vkCmdBlitImage;
extern PFN_vkCmdClearAttachments vkCmdClearAttachments;
extern PFN_vkCmdClearColorImage vkCmdClearColorImage;
extern PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
extern PFN_vkCmdCopyImage vkCmdCopyImage;
extern PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
extern PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
extern PFN_vkCmdDispatch vkCmdDispatch;
extern PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
extern PFN_vkCmdDraw vkCmdDraw;
extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
extern PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
extern PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
extern PFN_vkCmdEndQuery vkCmdEndQuery;
extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
extern PFN_vkCmdFillBuffer vkCmdFillBuffer;
extern PFN_vkCmdNextSubpass vkCmdNextSubpass;
extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
extern PFN_vkCmdPushConstants vkCmdPushConstants;
extern PFN_vkCmdResetEvent vkCmdResetEvent;
extern PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
extern PFN_vkCmdResolveImage vkCmdResolveImage;
extern PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
extern PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
extern PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
extern PFN_vkCmdSetEvent vkCmdSetEvent;
extern PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
extern PFN_vkCmdSetScissor vkCmdSetScissor;
extern PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
extern PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
extern PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
extern PFN_vkCmdSetViewport vkCmdSetViewport;
extern PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
extern PFN_vkCmdWaitEvents vkCmdWaitEvents;
extern PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
extern PFN_vkCreateBuffer vkCreateBuffer;
extern PFN_vkCreateBufferView vkCreateBufferView;
extern PFN_vkCreateCommandPool vkCreateCommandPool;
extern PFN_vkCreateComputePipelines vkCreateComputePipelines;
extern PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
extern PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
extern PFN_vkCreateDevice vkCreateDevice;
extern PFN_vkCreateEvent vkCreateEvent;
extern PFN_vkCreateFence vkCreateFence;
extern PFN_vkCreateFramebuffer vkCreateFramebuffer;
extern PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
extern PFN_vkCreateImage vkCreateImage;
extern PFN_vkCreateImageView vkCreateImageView;
extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkCreatePipelineCache vkCreatePipelineCache;
extern PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
extern PFN_vkCreateQueryPool vkCreateQueryPool;
extern PFN_vkCreateRenderPass vkCreateRenderPass;
extern PFN_vkCreateSampler vkCreateSampler;
extern PFN_vkCreateSemaphore vkCreateSemaphore;
extern PFN_vkCreateShaderModule vkCreateShaderModule;
extern PFN_vkDestroyBuffer vkDestroyBuffer;
extern PFN_vkDestroyBufferView vkDestroyBufferView;
extern PFN_vkDestroyCommandPool vkDestroyCommandPool;
extern PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
extern PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
extern PFN_vkDestroyDevice vkDestroyDevice;
extern PFN_vkDestroyEvent vkDestroyEvent;
extern PFN_vkDestroyFence vkDestroyFence;
extern PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
extern PFN_vkDestroyImage vkDestroyImage;
extern PFN_vkDestroyImageView vkDestroyImageView;
extern PFN_vkDestroyInstance vkDestroyInstance;
extern PFN_vkDestroyPipeline vkDestroyPipeline;
extern PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
extern PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
extern PFN_vkDestroyQueryPool vkDestroyQueryPool;
extern PFN_vkDestroyRenderPass vkDestroyRenderPass;
extern PFN_vkDestroySampler vkDestroySampler;
extern PFN_vkDestroySemaphore vkDestroySemaphore;
extern PFN_vkDestroyShaderModule vkDestroyShaderModule;
extern PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
extern PFN_vkEndCommandBuffer vkEndCommandBuffer;
extern PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
extern PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
extern PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
extern PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
extern PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
extern PFN_vkFreeMemory vkFreeMemory;
extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
extern PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
extern PFN_vkGetDeviceQueue vkGetDeviceQueue;
extern PFN_vkGetEventStatus vkGetEventStatus;
extern PFN_vkGetFenceStatus vkGetFenceStatus;
extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
extern PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
extern PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
extern PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
extern PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties;
extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties;
extern PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
extern PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
extern PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
extern PFN_vkMapMemory vkMapMemory;
extern PFN_vkMergePipelineCaches vkMergePipelineCaches;
extern PFN_vkQueueBindSparse vkQueueBindSparse;
extern PFN_vkQueueSubmit vkQueueSubmit;
extern PFN_vkQueueWaitIdle vkQueueWaitIdle;
extern PFN_vkResetCommandBuffer vkResetCommandBuffer;
extern PFN_vkResetCommandPool vkResetCommandPool;
extern PFN_vkResetDescriptorPool vkResetDescriptorPool;
extern PFN_vkResetEvent vkResetEvent;
extern PFN_vkResetFences vkResetFences;
extern PFN_vkSetEvent vkSetEvent;
extern PFN_vkUnmapMemory vkUnmapMemory;
extern PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
extern PFN_vkWaitForFences vkWaitForFences;
#endif /* defined(VK_VERSION_1_0) */
#if defined(VK_VERSION_1_1)
extern PFN_vkBindBufferMemory2 vkBindBufferMemory2;
extern PFN_vkBindImageMemory2 vkBindImageMemory2;
extern PFN_vkCmdDispatchBase vkCmdDispatchBase;
extern PFN_vkCmdSetDeviceMask vkCmdSetDeviceMask;
extern PFN_vkCreateDescriptorUpdateTemplate vkCreateDescriptorUpdateTemplate;
extern PFN_vkCreateSamplerYcbcrConversion vkCreateSamplerYcbcrConversion;
extern PFN_vkDestroyDescriptorUpdateTemplate vkDestroyDescriptorUpdateTemplate;
extern PFN_vkDestroySamplerYcbcrConversion vkDestroySamplerYcbcrConversion;
extern PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
extern PFN_vkEnumeratePhysicalDeviceGroups vkEnumeratePhysicalDeviceGroups;
extern PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2;
extern PFN_vkGetDescriptorSetLayoutSupport vkGetDescriptorSetLayoutSupport;
extern PFN_vkGetDeviceGroupPeerMemoryFeatures vkGetDeviceGroupPeerMemoryFeatures;
extern PFN_vkGetDeviceQueue2 vkGetDeviceQueue2;
extern PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2;
extern PFN_vkGetImageSparseMemoryRequirements2 vkGetImageSparseMemoryRequirements2;
extern PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties;
extern PFN_vkGetPhysicalDeviceExternalFenceProperties vkGetPhysicalDeviceExternalFenceProperties;
extern PFN_vkGetPhysicalDeviceExternalSemaphoreProperties vkGetPhysicalDeviceExternalSemaphoreProperties;
extern PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2;
extern PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2;
extern PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2;
extern PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2;
extern PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties2 vkGetPhysicalDeviceQueueFamilyProperties2;
extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties2 vkGetPhysicalDeviceSparseImageFormatProperties2;
extern PFN_vkTrimCommandPool vkTrimCommandPool;
extern PFN_vkUpdateDescriptorSetWithTemplate vkUpdateDescriptorSetWithTemplate;
#endif /* defined(VK_VERSION_1_1) */
#if defined(VK_VERSION_1_2)
extern PFN_vkCmdBeginRenderPass2 vkCmdBeginRenderPass2;
extern PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount;
extern PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount;
extern PFN_vkCmdEndRenderPass2 vkCmdEndRenderPass2;
extern PFN_vkCmdNextSubpass2 vkCmdNextSubpass2;
extern PFN_vkCreateRenderPass2 vkCreateRenderPass2;
extern PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress;
extern PFN_vkGetBufferOpaqueCaptureAddress vkGetBufferOpaqueCaptureAddress;
extern PFN_vkGetDeviceMemoryOpaqueCaptureAddress vkGetDeviceMemoryOpaqueCaptureAddress;
extern PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue;
extern PFN_vkResetQueryPool vkResetQueryPool;
extern PFN_vkSignalSemaphore vkSignalSemaphore;
extern PFN_vkWaitSemaphores vkWaitSemaphores;
#endif /* defined(VK_VERSION_1_2) */
#if defined(VK_VERSION_1_3)
extern PFN_vkCmdBeginRendering vkCmdBeginRendering;
extern PFN_vkCmdBindVertexBuffers2 vkCmdBindVertexBuffers2;
extern PFN_vkCmdBlitImage2 vkCmdBlitImage2;
extern PFN_vkCmdCopyBuffer2 vkCmdCopyBuffer2;
extern PFN_vkCmdCopyBufferToImage2 vkCmdCopyBufferToImage2;
extern PFN_vkCmdCopyImage2 vkCmdCopyImage2;
extern PFN_vkCmdCopyImageToBuffer2 vkCmdCopyImageToBuffer2;
extern PFN_vkCmdEndRendering vkCmdEndRendering;
extern PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2;
extern PFN_vkCmdResetEvent2 vkCmdResetEvent2;
extern PFN_vkCmdResolveImage2 vkCmdResolveImage2;
extern PFN_vkCmdSetCullMode vkCmdSetCullMode;
extern PFN_vkCmdSetDepthBiasEnable vkCmdSetDepthBiasEnable;
extern PFN_vkCmdSetDepthBoundsTestEnable vkCmdSetDepthBoundsTestEnable;
extern PFN_vkCmdSetDepthCompareOp vkCmdSetDepthCompareOp;
extern PFN_vkCmdSetDepthTestEnable vkCmdSetDepthTestEnable;
extern PFN_vkCmdSetDepthWriteEnable vkCmdSetDepthWriteEnable;
extern PFN_vkCmdSetEvent2 vkCmdSetEvent2;
extern PFN_vkCmdSetFrontFace vkCmdSetFrontFace;
extern PFN_vkCmdSetPrimitiveRestartEnable vkCmdSetPrimitiveRestartEnable;
extern PFN_vkCmdSetPrimitiveTopology vkCmdSetPrimitiveTopology;
extern PFN_vkCmdSetRasterizerDiscardEnable vkCmdSetRasterizerDiscardEnable;
extern PFN_vkCmdSetScissorWithCount vkCmdSetScissorWithCount;
extern PFN_vkCmdSetStencilOp vkCmdSetStencilOp;
extern PFN_vkCmdSetStencilTestEnable vkCmdSetStencilTestEnable;
extern PFN_vkCmdSetViewportWithCount vkCmdSetViewportWithCount;
extern PFN_vkCmdWaitEvents2 vkCmdWaitEvents2;
extern PFN_vkCmdWriteTimestamp2 vkCmdWriteTimestamp2;
extern PFN_vkCreatePrivateDataSlot vkCreatePrivateDataSlot;
extern PFN_vkDestroyPrivateDataSlot vkDestroyPrivateDataSlot;
extern PFN_vkGetDeviceBufferMemoryRequirements vkGetDeviceBufferMemoryRequirements;
extern PFN_vkGetDeviceImageMemoryRequirements vkGetDeviceImageMemoryRequirements;
extern PFN_vkGetDeviceImageSparseMemoryRequirements vkGetDeviceImageSparseMemoryRequirements;
extern PFN_vkGetPhysicalDeviceToolProperties vkGetPhysicalDeviceToolProperties;
extern PFN_vkGetPrivateData vkGetPrivateData;
extern PFN_vkQueueSubmit2 vkQueueSubmit2;
extern PFN_vkSetPrivateData vkSetPrivateData;
#endif /* defined(VK_VERSION_1_3) */
#if defined(VK_VERSION_1_4)
extern PFN_vkCmdBindDescriptorSets2 vkCmdBindDescriptorSets2;
extern PFN_vkCmdBindIndexBuffer2 vkCmdBindIndexBuffer2;
extern PFN_vkCmdPushConstants2 vkCmdPushConstants2;
extern PFN_vkCmdPushDescriptorSet vkCmdPushDescriptorSet;
extern PFN_vkCmdPushDescriptorSet2 vkCmdPushDescriptorSet2;
extern PFN_vkCmdPushDescriptorSetWithTemplate vkCmdPushDescriptorSetWithTemplate;
extern PFN_vkCmdPushDescriptorSetWithTemplate2 vkCmdPushDescriptorSetWithTemplate2;
extern PFN_vkCmdSetLineStipple vkCmdSetLineStipple;
extern PFN_vkCmdSetRenderingAttachmentLocations vkCmdSetRenderingAttachmentLocations;
extern PFN_vkCmdSetRenderingInputAttachmentIndices vkCmdSetRenderingInputAttachmentIndices;
extern PFN_vkCopyImageToImage vkCopyImageToImage;
extern PFN_vkCopyImageToMemory vkCopyImageToMemory;
extern PFN_vkCopyMemoryToImage vkCopyMemoryToImage;
extern PFN_vkGetDeviceImageSubresourceLayout vkGetDeviceImageSubresourceLayout;
extern PFN_vkGetImageSubresourceLayout2 vkGetImageSubresourceLayout2;
extern PFN_vkGetRenderingAreaGranularity vkGetRenderingAreaGranularity;
extern PFN_vkMapMemory2 vkMapMemory2;
extern PFN_vkTransitionImageLayout vkTransitionImageLayout;
extern PFN_vkUnmapMemory2 vkUnmapMemory2;
#endif /* defined(VK_VERSION_1_4) */
#if defined(VK_AMDX_shader_enqueue)
extern PFN_vkCmdDispatchGraphAMDX vkCmdDispatchGraphAMDX;
extern PFN_vkCmdDispatchGraphIndirectAMDX vkCmdDispatchGraphIndirectAMDX;
extern PFN_vkCmdDispatchGraphIndirectCountAMDX vkCmdDispatchGraphIndirectCountAMDX;
extern PFN_vkCmdInitializeGraphScratchMemoryAMDX vkCmdInitializeGraphScratchMemoryAMDX;
extern PFN_vkCreateExecutionGraphPipelinesAMDX vkCreateExecutionGraphPipelinesAMDX;
extern PFN_vkGetExecutionGraphPipelineNodeIndexAMDX vkGetExecutionGraphPipelineNodeIndexAMDX;
extern PFN_vkGetExecutionGraphPipelineScratchSizeAMDX vkGetExecutionGraphPipelineScratchSizeAMDX;
#endif /* defined(VK_AMDX_shader_enqueue) */
#if defined(VK_AMD_anti_lag)
extern PFN_vkAntiLagUpdateAMD vkAntiLagUpdateAMD;
#endif /* defined(VK_AMD_anti_lag) */
#if defined(VK_AMD_buffer_marker)
extern PFN_vkCmdWriteBufferMarkerAMD vkCmdWriteBufferMarkerAMD;
#endif /* defined(VK_AMD_buffer_marker) */
#if defined(VK_AMD_buffer_marker) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2))
extern PFN_vkCmdWriteBufferMarker2AMD vkCmdWriteBufferMarker2AMD;
#endif /* defined(VK_AMD_buffer_marker) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2)) */
#if defined(VK_AMD_display_native_hdr)
extern PFN_vkSetLocalDimmingAMD vkSetLocalDimmingAMD;
#endif /* defined(VK_AMD_display_native_hdr) */
#if defined(VK_AMD_draw_indirect_count)
extern PFN_vkCmdDrawIndexedIndirectCountAMD vkCmdDrawIndexedIndirectCountAMD;
extern PFN_vkCmdDrawIndirectCountAMD vkCmdDrawIndirectCountAMD;
#endif /* defined(VK_AMD_draw_indirect_count) */
#if defined(VK_AMD_shader_info)
extern PFN_vkGetShaderInfoAMD vkGetShaderInfoAMD;
#endif /* defined(VK_AMD_shader_info) */
#if defined(VK_ANDROID_external_memory_android_hardware_buffer)
extern PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID;
extern PFN_vkGetMemoryAndroidHardwareBufferANDROID vkGetMemoryAndroidHardwareBufferANDROID;
#endif /* defined(VK_ANDROID_external_memory_android_hardware_buffer) */
#if defined(VK_ARM_data_graph)
extern PFN_vkBindDataGraphPipelineSessionMemoryARM vkBindDataGraphPipelineSessionMemoryARM;
extern PFN_vkCmdDispatchDataGraphARM vkCmdDispatchDataGraphARM;
extern PFN_vkCreateDataGraphPipelineSessionARM vkCreateDataGraphPipelineSessionARM;
extern PFN_vkCreateDataGraphPipelinesARM vkCreateDataGraphPipelinesARM;
extern PFN_vkDestroyDataGraphPipelineSessionARM vkDestroyDataGraphPipelineSessionARM;
extern PFN_vkGetDataGraphPipelineAvailablePropertiesARM vkGetDataGraphPipelineAvailablePropertiesARM;
extern PFN_vkGetDataGraphPipelinePropertiesARM vkGetDataGraphPipelinePropertiesARM;
extern PFN_vkGetDataGraphPipelineSessionBindPointRequirementsARM vkGetDataGraphPipelineSessionBindPointRequirementsARM;
extern PFN_vkGetDataGraphPipelineSessionMemoryRequirementsARM vkGetDataGraphPipelineSessionMemoryRequirementsARM;
extern PFN_vkGetPhysicalDeviceQueueFamilyDataGraphProcessingEnginePropertiesARM vkGetPhysicalDeviceQueueFamilyDataGraphProcessingEnginePropertiesARM;
extern PFN_vkGetPhysicalDeviceQueueFamilyDataGraphPropertiesARM vkGetPhysicalDeviceQueueFamilyDataGraphPropertiesARM;
#endif /* defined(VK_ARM_data_graph) */
#if defined(VK_ARM_tensors)
extern PFN_vkBindTensorMemoryARM vkBindTensorMemoryARM;
extern PFN_vkCmdCopyTensorARM vkCmdCopyTensorARM;
extern PFN_vkCreateTensorARM vkCreateTensorARM;
extern PFN_vkCreateTensorViewARM vkCreateTensorViewARM;
extern PFN_vkDestroyTensorARM vkDestroyTensorARM;
extern PFN_vkDestroyTensorViewARM vkDestroyTensorViewARM;
extern PFN_vkGetDeviceTensorMemoryRequirementsARM vkGetDeviceTensorMemoryRequirementsARM;
extern PFN_vkGetPhysicalDeviceExternalTensorPropertiesARM vkGetPhysicalDeviceExternalTensorPropertiesARM;
extern PFN_vkGetTensorMemoryRequirementsARM vkGetTensorMemoryRequirementsARM;
#endif /* defined(VK_ARM_tensors) */
#if defined(VK_ARM_tensors) && defined(VK_EXT_descriptor_buffer)
extern PFN_vkGetTensorOpaqueCaptureDescriptorDataARM vkGetTensorOpaqueCaptureDescriptorDataARM;
extern PFN_vkGetTensorViewOpaqueCaptureDescriptorDataARM vkGetTensorViewOpaqueCaptureDescriptorDataARM;
#endif /* defined(VK_ARM_tensors) && defined(VK_EXT_descriptor_buffer) */
#if defined(VK_EXT_acquire_drm_display)
extern PFN_vkAcquireDrmDisplayEXT vkAcquireDrmDisplayEXT;
extern PFN_vkGetDrmDisplayEXT vkGetDrmDisplayEXT;
#endif /* defined(VK_EXT_acquire_drm_display) */
#if defined(VK_EXT_acquire_xlib_display)
extern PFN_vkAcquireXlibDisplayEXT vkAcquireXlibDisplayEXT;
extern PFN_vkGetRandROutputDisplayEXT vkGetRandROutputDisplayEXT;
#endif /* defined(VK_EXT_acquire_xlib_display) */
#if defined(VK_EXT_attachment_feedback_loop_dynamic_state)
extern PFN_vkCmdSetAttachmentFeedbackLoopEnableEXT vkCmdSetAttachmentFeedbackLoopEnableEXT;
#endif /* defined(VK_EXT_attachment_feedback_loop_dynamic_state) */
#if defined(VK_EXT_buffer_device_address)
extern PFN_vkGetBufferDeviceAddressEXT vkGetBufferDeviceAddressEXT;
#endif /* defined(VK_EXT_buffer_device_address) */
#if defined(VK_EXT_calibrated_timestamps)
extern PFN_vkGetCalibratedTimestampsEXT vkGetCalibratedTimestampsEXT;
extern PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT vkGetPhysicalDeviceCalibrateableTimeDomainsEXT;
#endif /* defined(VK_EXT_calibrated_timestamps) */
#if defined(VK_EXT_color_write_enable)
extern PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT;
#endif /* defined(VK_EXT_color_write_enable) */
#if defined(VK_EXT_conditional_rendering)
extern PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT;
extern PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT;
#endif /* defined(VK_EXT_conditional_rendering) */
#if defined(VK_EXT_debug_marker)
extern PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBeginEXT;
extern PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEndEXT;
extern PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsertEXT;
extern PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectNameEXT;
extern PFN_vkDebugMarkerSetObjectTagEXT vkDebugMarkerSetObjectTagEXT;
#endif /* defined(VK_EXT_debug_marker) */
#if defined(VK_EXT_debug_report)
extern PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
extern PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;
extern PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
#endif /* defined(VK_EXT_debug_report) */
#if defined(VK_EXT_debug_utils)
extern PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT;
extern PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT;
extern PFN_vkCmdInsertDebugUtilsLabelEXT vkCmdInsertDebugUtilsLabelEXT;
extern PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
extern PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
extern PFN_vkQueueBeginDebugUtilsLabelEXT vkQueueBeginDebugUtilsLabelEXT;
extern PFN_vkQueueEndDebugUtilsLabelEXT vkQueueEndDebugUtilsLabelEXT;
extern PFN_vkQueueInsertDebugUtilsLabelEXT vkQueueInsertDebugUtilsLabelEXT;
extern PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT;
extern PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT;
extern PFN_vkSubmitDebugUtilsMessageEXT vkSubmitDebugUtilsMessageEXT;
#endif /* defined(VK_EXT_debug_utils) */
#if defined(VK_EXT_depth_bias_control)
extern PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT;
#endif /* defined(VK_EXT_depth_bias_control) */
#if defined(VK_EXT_descriptor_buffer)
extern PFN_vkCmdBindDescriptorBufferEmbeddedSamplersEXT vkCmdBindDescriptorBufferEmbeddedSamplersEXT;
extern PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT;
extern PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT;
extern PFN_vkGetBufferOpaqueCaptureDescriptorDataEXT vkGetBufferOpaqueCaptureDescriptorDataEXT;
extern PFN_vkGetDescriptorEXT vkGetDescriptorEXT;
extern PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT;
extern PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT;
extern PFN_vkGetImageOpaqueCaptureDescriptorDataEXT vkGetImageOpaqueCaptureDescriptorDataEXT;
extern PFN_vkGetImageViewOpaqueCaptureDescriptorDataEXT vkGetImageViewOpaqueCaptureDescriptorDataEXT;
extern PFN_vkGetSamplerOpaqueCaptureDescriptorDataEXT vkGetSamplerOpaqueCaptureDescriptorDataEXT;
#endif /* defined(VK_EXT_descriptor_buffer) */
#if defined(VK_EXT_descriptor_buffer) && (defined(VK_KHR_acceleration_structure) || defined(VK_NV_ray_tracing))
extern PFN_vkGetAccelerationStructureOpaqueCaptureDescriptorDataEXT vkGetAccelerationStructureOpaqueCaptureDescriptorDataEXT;
#endif /* defined(VK_EXT_descriptor_buffer) && (defined(VK_KHR_acceleration_structure) || defined(VK_NV_ray_tracing)) */
#if defined(VK_EXT_device_fault)
extern PFN_vkGetDeviceFaultInfoEXT vkGetDeviceFaultInfoEXT;
#endif /* defined(VK_EXT_device_fault) */
#if defined(VK_EXT_device_generated_commands)
extern PFN_vkCmdExecuteGeneratedCommandsEXT vkCmdExecuteGeneratedCommandsEXT;
extern PFN_vkCmdPreprocessGeneratedCommandsEXT vkCmdPreprocessGeneratedCommandsEXT;
extern PFN_vkCreateIndirectCommandsLayoutEXT vkCreateIndirectCommandsLayoutEXT;
extern PFN_vkCreateIndirectExecutionSetEXT vkCreateIndirectExecutionSetEXT;
extern PFN_vkDestroyIndirectCommandsLayoutEXT vkDestroyIndirectCommandsLayoutEXT;
extern PFN_vkDestroyIndirectExecutionSetEXT vkDestroyIndirectExecutionSetEXT;
extern PFN_vkGetGeneratedCommandsMemoryRequirementsEXT vkGetGeneratedCommandsMemoryRequirementsEXT;
extern PFN_vkUpdateIndirectExecutionSetPipelineEXT vkUpdateIndirectExecutionSetPipelineEXT;
extern PFN_vkUpdateIndirectExecutionSetShaderEXT vkUpdateIndirectExecutionSetShaderEXT;
#endif /* defined(VK_EXT_device_generated_commands) */
#if defined(VK_EXT_direct_mode_display)
extern PFN_vkReleaseDisplayEXT vkReleaseDisplayEXT;
#endif /* defined(VK_EXT_direct_mode_display) */
#if defined(VK_EXT_directfb_surface)
extern PFN_vkCreateDirectFBSurfaceEXT vkCreateDirectFBSurfaceEXT;
extern PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT vkGetPhysicalDeviceDirectFBPresentationSupportEXT;
#endif /* defined(VK_EXT_directfb_surface) */
#if defined(VK_EXT_discard_rectangles)
extern PFN_vkCmdSetDiscardRectangleEXT vkCmdSetDiscardRectangleEXT;
#endif /* defined(VK_EXT_discard_rectangles) */
#if defined(VK_EXT_discard_rectangles) && VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION >= 2
extern PFN_vkCmdSetDiscardRectangleEnableEXT vkCmdSetDiscardRectangleEnableEXT;
extern PFN_vkCmdSetDiscardRectangleModeEXT vkCmdSetDiscardRectangleModeEXT;
#endif /* defined(VK_EXT_discard_rectangles) && VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION >= 2 */
#if defined(VK_EXT_display_control)
extern PFN_vkDisplayPowerControlEXT vkDisplayPowerControlEXT;
extern PFN_vkGetSwapchainCounterEXT vkGetSwapchainCounterEXT;
extern PFN_vkRegisterDeviceEventEXT vkRegisterDeviceEventEXT;
extern PFN_vkRegisterDisplayEventEXT vkRegisterDisplayEventEXT;
#endif /* defined(VK_EXT_display_control) */
#if defined(VK_EXT_display_surface_counter)
extern PFN_vkGetPhysicalDeviceSurfaceCapabilities2EXT vkGetPhysicalDeviceSurfaceCapabilities2EXT;
#endif /* defined(VK_EXT_display_surface_counter) */
#if defined(VK_EXT_external_memory_host)
extern PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT;
#endif /* defined(VK_EXT_external_memory_host) */
#if defined(VK_EXT_external_memory_metal)
extern PFN_vkGetMemoryMetalHandleEXT vkGetMemoryMetalHandleEXT;
extern PFN_vkGetMemoryMetalHandlePropertiesEXT vkGetMemoryMetalHandlePropertiesEXT;
#endif /* defined(VK_EXT_external_memory_metal) */
#if defined(VK_EXT_fragment_density_map_offset)
extern PFN_vkCmdEndRendering2EXT vkCmdEndRendering2EXT;
#endif /* defined(VK_EXT_fragment_density_map_offset) */
#if defined(VK_EXT_full_screen_exclusive)
extern PFN_vkAcquireFullScreenExclusiveModeEXT vkAcquireFullScreenExclusiveModeEXT;
extern PFN_vkGetPhysicalDeviceSurfacePresentModes2EXT vkGetPhysicalDeviceSurfacePresentModes2EXT;
extern PFN_vkReleaseFullScreenExclusiveModeEXT vkReleaseFullScreenExclusiveModeEXT;
#endif /* defined(VK_EXT_full_screen_exclusive) */
#if defined(VK_EXT_full_screen_exclusive) && (defined(VK_KHR_device_group) || defined(VK_VERSION_1_1))
extern PFN_vkGetDeviceGroupSurfacePresentModes2EXT vkGetDeviceGroupSurfacePresentModes2EXT;
#endif /* defined(VK_EXT_full_screen_exclusive) && (defined(VK_KHR_device_group) || defined(VK_VERSION_1_1)) */
#if defined(VK_EXT_hdr_metadata)
extern PFN_vkSetHdrMetadataEXT vkSetHdrMetadataEXT;
#endif /* defined(VK_EXT_hdr_metadata) */
#if defined(VK_EXT_headless_surface)
extern PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT;
#endif /* defined(VK_EXT_headless_surface) */
#if defined(VK_EXT_host_image_copy)
extern PFN_vkCopyImageToImageEXT vkCopyImageToImageEXT;
extern PFN_vkCopyImageToMemoryEXT vkCopyImageToMemoryEXT;
extern PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImageEXT;
extern PFN_vkTransitionImageLayoutEXT vkTransitionImageLayoutEXT;
#endif /* defined(VK_EXT_host_image_copy) */
#if defined(VK_EXT_host_query_reset)
extern PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT;
#endif /* defined(VK_EXT_host_query_reset) */
#if defined(VK_EXT_image_drm_format_modifier)
extern PFN_vkGetImageDrmFormatModifierPropertiesEXT vkGetImageDrmFormatModifierPropertiesEXT;
#endif /* defined(VK_EXT_image_drm_format_modifier) */
#if defined(VK_EXT_line_rasterization)
extern PFN_vkCmdSetLineStippleEXT vkCmdSetLineStippleEXT;
#endif /* defined(VK_EXT_line_rasterization) */
#if defined(VK_EXT_mesh_shader)
extern PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT;
extern PFN_vkCmdDrawMeshTasksIndirectEXT vkCmdDrawMeshTasksIndirectEXT;
#endif /* defined(VK_EXT_mesh_shader) */
#if defined(VK_EXT_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2))
extern PFN_vkCmdDrawMeshTasksIndirectCountEXT vkCmdDrawMeshTasksIndirectCountEXT;
#endif /* defined(VK_EXT_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2)) */
#if defined(VK_EXT_metal_objects)
extern PFN_vkExportMetalObjectsEXT vkExportMetalObjectsEXT;
#endif /* defined(VK_EXT_metal_objects) */
#if defined(VK_EXT_metal_surface)
extern PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
#endif /* defined(VK_EXT_metal_surface) */
#if defined(VK_EXT_multi_draw)
extern PFN_vkCmdDrawMultiEXT vkCmdDrawMultiEXT;
extern PFN_vkCmdDrawMultiIndexedEXT vkCmdDrawMultiIndexedEXT;
#endif /* defined(VK_EXT_multi_draw) */
#if defined(VK_EXT_opacity_micromap)
extern PFN_vkBuildMicromapsEXT vkBuildMicromapsEXT;
extern PFN_vkCmdBuildMicromapsEXT vkCmdBuildMicromapsEXT;
extern PFN_vkCmdCopyMemoryToMicromapEXT vkCmdCopyMemoryToMicromapEXT;
extern PFN_vkCmdCopyMicromapEXT vkCmdCopyMicromapEXT;
extern PFN_vkCmdCopyMicromapToMemoryEXT vkCmdCopyMicromapToMemoryEXT;
extern PFN_vkCmdWriteMicromapsPropertiesEXT vkCmdWriteMicromapsPropertiesEXT;
extern PFN_vkCopyMemoryToMicromapEXT vkCopyMemoryToMicromapEXT;
extern PFN_vkCopyMicromapEXT vkCopyMicromapEXT;
extern PFN_vkCopyMicromapToMemoryEXT vkCopyMicromapToMemoryEXT;
extern PFN_vkCreateMicromapEXT vkCreateMicromapEXT;
extern PFN_vkDestroyMicromapEXT vkDestroyMicromapEXT;
extern PFN_vkGetDeviceMicromapCompatibilityEXT vkGetDeviceMicromapCompatibilityEXT;
extern PFN_vkGetMicromapBuildSizesEXT vkGetMicromapBuildSizesEXT;
extern PFN_vkWriteMicromapsPropertiesEXT vkWriteMicromapsPropertiesEXT;
#endif /* defined(VK_EXT_opacity_micromap) */
#if defined(VK_EXT_pageable_device_local_memory)
extern PFN_vkSetDeviceMemoryPriorityEXT vkSetDeviceMemoryPriorityEXT;
#endif /* defined(VK_EXT_pageable_device_local_memory) */
#if defined(VK_EXT_pipeline_properties)
extern PFN_vkGetPipelinePropertiesEXT vkGetPipelinePropertiesEXT;
#endif /* defined(VK_EXT_pipeline_properties) */
#if defined(VK_EXT_private_data)
extern PFN_vkCreatePrivateDataSlotEXT vkCreatePrivateDataSlotEXT;
extern PFN_vkDestroyPrivateDataSlotEXT vkDestroyPrivateDataSlotEXT;
extern PFN_vkGetPrivateDataEXT vkGetPrivateDataEXT;
extern PFN_vkSetPrivateDataEXT vkSetPrivateDataEXT;
#endif /* defined(VK_EXT_private_data) */
#if defined(VK_EXT_sample_locations)
extern PFN_vkCmdSetSampleLocationsEXT vkCmdSetSampleLocationsEXT;
extern PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT vkGetPhysicalDeviceMultisamplePropertiesEXT;
#endif /* defined(VK_EXT_sample_locations) */
#if defined(VK_EXT_shader_module_identifier)
extern PFN_vkGetShaderModuleCreateInfoIdentifierEXT vkGetShaderModuleCreateInfoIdentifierEXT;
extern PFN_vkGetShaderModuleIdentifierEXT vkGetShaderModuleIdentifierEXT;
#endif /* defined(VK_EXT_shader_module_identifier) */
#if defined(VK_EXT_shader_object)
extern PFN_vkCmdBindShadersEXT vkCmdBindShadersEXT;
extern PFN_vkCreateShadersEXT vkCreateShadersEXT;
extern PFN_vkDestroyShaderEXT vkDestroyShaderEXT;
extern PFN_vkGetShaderBinaryDataEXT vkGetShaderBinaryDataEXT;
#endif /* defined(VK_EXT_shader_object) */
#if defined(VK_EXT_swapchain_maintenance1)
extern PFN_vkReleaseSwapchainImagesEXT vkReleaseSwapchainImagesEXT;
#endif /* defined(VK_EXT_swapchain_maintenance1) */
#if defined(VK_EXT_tooling_info)
extern PFN_vkGetPhysicalDeviceToolPropertiesEXT vkGetPhysicalDeviceToolPropertiesEXT;
#endif /* defined(VK_EXT_tooling_info) */
#if defined(VK_EXT_transform_feedback)
extern PFN_vkCmdBeginQueryIndexedEXT vkCmdBeginQueryIndexedEXT;
extern PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT;
extern PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT;
extern PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT;
extern PFN_vkCmdEndQueryIndexedEXT vkCmdEndQueryIndexedEXT;
extern PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT;
#endif /* defined(VK_EXT_transform_feedback) */
#if defined(VK_EXT_validation_cache)
extern PFN_vkCreateValidationCacheEXT vkCreateValidationCacheEXT;
extern PFN_vkDestroyValidationCacheEXT vkDestroyValidationCacheEXT;
extern PFN_vkGetValidationCacheDataEXT vkGetValidationCacheDataEXT;
extern PFN_vkMergeValidationCachesEXT vkMergeValidationCachesEXT;
#endif /* defined(VK_EXT_validation_cache) */
#if defined(VK_FUCHSIA_buffer_collection)
extern PFN_vkCreateBufferCollectionFUCHSIA vkCreateBufferCollectionFUCHSIA;
extern PFN_vkDestroyBufferCollectionFUCHSIA vkDestroyBufferCollectionFUCHSIA;
extern PFN_vkGetBufferCollectionPropertiesFUCHSIA vkGetBufferCollectionPropertiesFUCHSIA;
extern PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA vkSetBufferCollectionBufferConstraintsFUCHSIA;
extern PFN_vkSetBufferCollectionImageConstraintsFUCHSIA vkSetBufferCollectionImageConstraintsFUCHSIA;
#endif /* defined(VK_FUCHSIA_buffer_collection) */
#if defined(VK_FUCHSIA_external_memory)
extern PFN_vkGetMemoryZirconHandleFUCHSIA vkGetMemoryZirconHandleFUCHSIA;
extern PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA vkGetMemoryZirconHandlePropertiesFUCHSIA;
#endif /* defined(VK_FUCHSIA_external_memory) */
#if defined(VK_FUCHSIA_external_semaphore)
extern PFN_vkGetSemaphoreZirconHandleFUCHSIA vkGetSemaphoreZirconHandleFUCHSIA;
extern PFN_vkImportSemaphoreZirconHandleFUCHSIA vkImportSemaphoreZirconHandleFUCHSIA;
#endif /* defined(VK_FUCHSIA_external_semaphore) */
#if defined(VK_FUCHSIA_imagepipe_surface)
extern PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA;
#endif /* defined(VK_FUCHSIA_imagepipe_surface) */
#if defined(VK_GGP_stream_descriptor_surface)
extern PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP;
#endif /* defined(VK_GGP_stream_descriptor_surface) */
#if defined(VK_GOOGLE_display_timing)
extern PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
extern PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#endif /* defined(VK_GOOGLE_display_timing) */
#if defined(VK_HUAWEI_cluster_culling_shader)
extern PFN_vkCmdDrawClusterHUAWEI vkCmdDrawClusterHUAWEI;
extern PFN_vkCmdDrawClusterIndirectHUAWEI vkCmdDrawClusterIndirectHUAWEI;
#endif /* defined(VK_HUAWEI_cluster_culling_shader) */
#if defined(VK_HUAWEI_invocation_mask)
extern PFN_vkCmdBindInvocationMaskHUAWEI vkCmdBindInvocationMaskHUAWEI;
#endif /* defined(VK_HUAWEI_invocation_mask) */
#if defined(VK_HUAWEI_subpass_shading) && VK_HUAWEI_SUBPASS_SHADING_SPEC_VERSION >= 2
extern PFN_vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI;
#endif /* defined(VK_HUAWEI_subpass_shading) && VK_HUAWEI_SUBPASS_SHADING_SPEC_VERSION >= 2 */
#if defined(VK_HUAWEI_subpass_shading)
extern PFN_vkCmdSubpassShadingHUAWEI vkCmdSubpassShadingHUAWEI;
#endif /* defined(VK_HUAWEI_subpass_shading) */
#if defined(VK_INTEL_performance_query)
extern PFN_vkAcquirePerformanceConfigurationINTEL vkAcquirePerformanceConfigurationINTEL;
extern PFN_vkCmdSetPerformanceMarkerINTEL vkCmdSetPerformanceMarkerINTEL;
extern PFN_vkCmdSetPerformanceOverrideINTEL vkCmdSetPerformanceOverrideINTEL;
extern PFN_vkCmdSetPerformanceStreamMarkerINTEL vkCmdSetPerformanceStreamMarkerINTEL;
extern PFN_vkGetPerformanceParameterINTEL vkGetPerformanceParameterINTEL;
extern PFN_vkInitializePerformanceApiINTEL vkInitializePerformanceApiINTEL;
extern PFN_vkQueueSetPerformanceConfigurationINTEL vkQueueSetPerformanceConfigurationINTEL;
extern PFN_vkReleasePerformanceConfigurationINTEL vkReleasePerformanceConfigurationINTEL;
extern PFN_vkUninitializePerformanceApiINTEL vkUninitializePerformanceApiINTEL;
#endif /* defined(VK_INTEL_performance_query) */
#if defined(VK_KHR_acceleration_structure)
extern PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR;
extern PFN_vkCmdBuildAccelerationStructuresIndirectKHR vkCmdBuildAccelerationStructuresIndirectKHR;
extern PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
extern PFN_vkCmdCopyAccelerationStructureKHR vkCmdCopyAccelerationStructureKHR;
extern PFN_vkCmdCopyAccelerationStructureToMemoryKHR vkCmdCopyAccelerationStructureToMemoryKHR;
extern PFN_vkCmdCopyMemoryToAccelerationStructureKHR vkCmdCopyMemoryToAccelerationStructureKHR;
extern PFN_vkCmdWriteAccelerationStructuresPropertiesKHR vkCmdWriteAccelerationStructuresPropertiesKHR;
extern PFN_vkCopyAccelerationStructureKHR vkCopyAccelerationStructureKHR;
extern PFN_vkCopyAccelerationStructureToMemoryKHR vkCopyAccelerationStructureToMemoryKHR;
extern PFN_vkCopyMemoryToAccelerationStructureKHR vkCopyMemoryToAccelerationStructureKHR;
extern PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
extern PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
extern PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
extern PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
extern PFN_vkGetDeviceAccelerationStructureCompatibilityKHR vkGetDeviceAccelerationStructureCompatibilityKHR;
extern PFN_vkWriteAccelerationStructuresPropertiesKHR vkWriteAccelerationStructuresPropertiesKHR;
#endif /* defined(VK_KHR_acceleration_structure) */
#if defined(VK_KHR_android_surface)
extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
#endif /* defined(VK_KHR_android_surface) */
#if defined(VK_KHR_bind_memory2)
extern PFN_vkBindBufferMemory2KHR vkBindBufferMemory2KHR;
extern PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR;
#endif /* defined(VK_KHR_bind_memory2) */
#if defined(VK_KHR_buffer_device_address)
extern PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR;
extern PFN_vkGetBufferOpaqueCaptureAddressKHR vkGetBufferOpaqueCaptureAddressKHR;
extern PFN_vkGetDeviceMemoryOpaqueCaptureAddressKHR vkGetDeviceMemoryOpaqueCaptureAddressKHR;
#endif /* defined(VK_KHR_buffer_device_address) */
#if defined(VK_KHR_calibrated_timestamps)
extern PFN_vkGetCalibratedTimestampsKHR vkGetCalibratedTimestampsKHR;
extern PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR vkGetPhysicalDeviceCalibrateableTimeDomainsKHR;
#endif /* defined(VK_KHR_calibrated_timestamps) */
#if defined(VK_KHR_cooperative_matrix)
extern PFN_vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR;
#endif /* defined(VK_KHR_cooperative_matrix) */
#if defined(VK_KHR_copy_commands2)
extern PFN_vkCmdBlitImage2KHR vkCmdBlitImage2KHR;
extern PFN_vkCmdCopyBuffer2KHR vkCmdCopyBuffer2KHR;
extern PFN_vkCmdCopyBufferToImage2KHR vkCmdCopyBufferToImage2KHR;
extern PFN_vkCmdCopyImage2KHR vkCmdCopyImage2KHR;
extern PFN_vkCmdCopyImageToBuffer2KHR vkCmdCopyImageToBuffer2KHR;
extern PFN_vkCmdResolveImage2KHR vkCmdResolveImage2KHR;
#endif /* defined(VK_KHR_copy_commands2) */
#if defined(VK_KHR_create_renderpass2)
extern PFN_vkCmdBeginRenderPass2KHR vkCmdBeginRenderPass2KHR;
extern PFN_vkCmdEndRenderPass2KHR vkCmdEndRenderPass2KHR;
extern PFN_vkCmdNextSubpass2KHR vkCmdNextSubpass2KHR;
extern PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR;
#endif /* defined(VK_KHR_create_renderpass2) */
#if defined(VK_KHR_deferred_host_operations)
extern PFN_vkCreateDeferredOperationKHR vkCreateDeferredOperationKHR;
extern PFN_vkDeferredOperationJoinKHR vkDeferredOperationJoinKHR;
extern PFN_vkDestroyDeferredOperationKHR vkDestroyDeferredOperationKHR;
extern PFN_vkGetDeferredOperationMaxConcurrencyKHR vkGetDeferredOperationMaxConcurrencyKHR;
extern PFN_vkGetDeferredOperationResultKHR vkGetDeferredOperationResultKHR;
#endif /* defined(VK_KHR_deferred_host_operations) */
#if defined(VK_KHR_descriptor_update_template)
extern PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR;
extern PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR;
extern PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR;
#endif /* defined(VK_KHR_descriptor_update_template) */
#if defined(VK_KHR_device_group)
extern PFN_vkCmdDispatchBaseKHR vkCmdDispatchBaseKHR;
extern PFN_vkCmdSetDeviceMaskKHR vkCmdSetDeviceMaskKHR;
extern PFN_vkGetDeviceGroupPeerMemoryFeaturesKHR vkGetDeviceGroupPeerMemoryFeaturesKHR;
#endif /* defined(VK_KHR_device_group) */
#if defined(VK_KHR_device_group_creation)
extern PFN_vkEnumeratePhysicalDeviceGroupsKHR vkEnumeratePhysicalDeviceGroupsKHR;
#endif /* defined(VK_KHR_device_group_creation) */
#if defined(VK_KHR_display)
extern PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR;
extern PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR;
extern PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR;
extern PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR;
extern PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR;
extern PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR;
extern PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR;
#endif /* defined(VK_KHR_display) */
#if defined(VK_KHR_display_swapchain)
extern PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#endif /* defined(VK_KHR_display_swapchain) */
#if defined(VK_KHR_draw_indirect_count)
extern PFN_vkCmdDrawIndexedIndirectCountKHR vkCmdDrawIndexedIndirectCountKHR;
extern PFN_vkCmdDrawIndirectCountKHR vkCmdDrawIndirectCountKHR;
#endif /* defined(VK_KHR_draw_indirect_count) */
#if defined(VK_KHR_dynamic_rendering)
extern PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR;
extern PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR;
#endif /* defined(VK_KHR_dynamic_rendering) */
#if defined(VK_KHR_dynamic_rendering_local_read)
extern PFN_vkCmdSetRenderingAttachmentLocationsKHR vkCmdSetRenderingAttachmentLocationsKHR;
extern PFN_vkCmdSetRenderingInputAttachmentIndicesKHR vkCmdSetRenderingInputAttachmentIndicesKHR;
#endif /* defined(VK_KHR_dynamic_rendering_local_read) */
#if defined(VK_KHR_external_fence_capabilities)
extern PFN_vkGetPhysicalDeviceExternalFencePropertiesKHR vkGetPhysicalDeviceExternalFencePropertiesKHR;
#endif /* defined(VK_KHR_external_fence_capabilities) */
#if defined(VK_KHR_external_fence_fd)
extern PFN_vkGetFenceFdKHR vkGetFenceFdKHR;
extern PFN_vkImportFenceFdKHR vkImportFenceFdKHR;
#endif /* defined(VK_KHR_external_fence_fd) */
#if defined(VK_KHR_external_fence_win32)
extern PFN_vkGetFenceWin32HandleKHR vkGetFenceWin32HandleKHR;
extern PFN_vkImportFenceWin32HandleKHR vkImportFenceWin32HandleKHR;
#endif /* defined(VK_KHR_external_fence_win32) */
#if defined(VK_KHR_external_memory_capabilities)
extern PFN_vkGetPhysicalDeviceExternalBufferPropertiesKHR vkGetPhysicalDeviceExternalBufferPropertiesKHR;
#endif /* defined(VK_KHR_external_memory_capabilities) */
#if defined(VK_KHR_external_memory_fd)
extern PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR;
extern PFN_vkGetMemoryFdPropertiesKHR vkGetMemoryFdPropertiesKHR;
#endif /* defined(VK_KHR_external_memory_fd) */
#if defined(VK_KHR_external_memory_win32)
extern PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR;
extern PFN_vkGetMemoryWin32HandlePropertiesKHR vkGetMemoryWin32HandlePropertiesKHR;
#endif /* defined(VK_KHR_external_memory_win32) */
#if defined(VK_KHR_external_semaphore_capabilities)
extern PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR vkGetPhysicalDeviceExternalSemaphorePropertiesKHR;
#endif /* defined(VK_KHR_external_semaphore_capabilities) */
#if defined(VK_KHR_external_semaphore_fd)
extern PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR;
extern PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR;
#endif /* defined(VK_KHR_external_semaphore_fd) */
#if defined(VK_KHR_external_semaphore_win32)
extern PFN_vkGetSemaphoreWin32HandleKHR vkGetSemaphoreWin32HandleKHR;
extern PFN_vkImportSemaphoreWin32HandleKHR vkImportSemaphoreWin32HandleKHR;
#endif /* defined(VK_KHR_external_semaphore_win32) */
#if defined(VK_KHR_fragment_shading_rate)
extern PFN_vkCmdSetFragmentShadingRateKHR vkCmdSetFragmentShadingRateKHR;
extern PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR vkGetPhysicalDeviceFragmentShadingRatesKHR;
#endif /* defined(VK_KHR_fragment_shading_rate) */
#if defined(VK_KHR_get_display_properties2)
extern PFN_vkGetDisplayModeProperties2KHR vkGetDisplayModeProperties2KHR;
extern PFN_vkGetDisplayPlaneCapabilities2KHR vkGetDisplayPlaneCapabilities2KHR;
extern PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR vkGetPhysicalDeviceDisplayPlaneProperties2KHR;
extern PFN_vkGetPhysicalDeviceDisplayProperties2KHR vkGetPhysicalDeviceDisplayProperties2KHR;
#endif /* defined(VK_KHR_get_display_properties2) */
#if defined(VK_KHR_get_memory_requirements2)
extern PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR;
extern PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR;
extern PFN_vkGetImageSparseMemoryRequirements2KHR vkGetImageSparseMemoryRequirements2KHR;
#endif /* defined(VK_KHR_get_memory_requirements2) */
#if defined(VK_KHR_get_physical_device_properties2)
extern PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR;
extern PFN_vkGetPhysicalDeviceFormatProperties2KHR vkGetPhysicalDeviceFormatProperties2KHR;
extern PFN_vkGetPhysicalDeviceImageFormatProperties2KHR vkGetPhysicalDeviceImageFormatProperties2KHR;
extern PFN_vkGetPhysicalDeviceMemoryProperties2KHR vkGetPhysicalDeviceMemoryProperties2KHR;
extern PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR vkGetPhysicalDeviceQueueFamilyProperties2KHR;
extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR vkGetPhysicalDeviceSparseImageFormatProperties2KHR;
#endif /* defined(VK_KHR_get_physical_device_properties2) */
#if defined(VK_KHR_get_surface_capabilities2)
extern PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR vkGetPhysicalDeviceSurfaceCapabilities2KHR;
extern PFN_vkGetPhysicalDeviceSurfaceFormats2KHR vkGetPhysicalDeviceSurfaceFormats2KHR;
#endif /* defined(VK_KHR_get_surface_capabilities2) */
#if defined(VK_KHR_line_rasterization)
extern PFN_vkCmdSetLineStippleKHR vkCmdSetLineStippleKHR;
#endif /* defined(VK_KHR_line_rasterization) */
#if defined(VK_KHR_maintenance1)
extern PFN_vkTrimCommandPoolKHR vkTrimCommandPoolKHR;
#endif /* defined(VK_KHR_maintenance1) */
#if defined(VK_KHR_maintenance3)
extern PFN_vkGetDescriptorSetLayoutSupportKHR vkGetDescriptorSetLayoutSupportKHR;
#endif /* defined(VK_KHR_maintenance3) */
#if defined(VK_KHR_maintenance4)
extern PFN_vkGetDeviceBufferMemoryRequirementsKHR vkGetDeviceBufferMemoryRequirementsKHR;
extern PFN_vkGetDeviceImageMemoryRequirementsKHR vkGetDeviceImageMemoryRequirementsKHR;
extern PFN_vkGetDeviceImageSparseMemoryRequirementsKHR vkGetDeviceImageSparseMemoryRequirementsKHR;
#endif /* defined(VK_KHR_maintenance4) */
#if defined(VK_KHR_maintenance5)
extern PFN_vkCmdBindIndexBuffer2KHR vkCmdBindIndexBuffer2KHR;
extern PFN_vkGetDeviceImageSubresourceLayoutKHR vkGetDeviceImageSubresourceLayoutKHR;
extern PFN_vkGetImageSubresourceLayout2KHR vkGetImageSubresourceLayout2KHR;
extern PFN_vkGetRenderingAreaGranularityKHR vkGetRenderingAreaGranularityKHR;
#endif /* defined(VK_KHR_maintenance5) */
#if defined(VK_KHR_maintenance6)
extern PFN_vkCmdBindDescriptorSets2KHR vkCmdBindDescriptorSets2KHR;
extern PFN_vkCmdPushConstants2KHR vkCmdPushConstants2KHR;
#endif /* defined(VK_KHR_maintenance6) */
#if defined(VK_KHR_maintenance6) && defined(VK_KHR_push_descriptor)
extern PFN_vkCmdPushDescriptorSet2KHR vkCmdPushDescriptorSet2KHR;
extern PFN_vkCmdPushDescriptorSetWithTemplate2KHR vkCmdPushDescriptorSetWithTemplate2KHR;
#endif /* defined(VK_KHR_maintenance6) && defined(VK_KHR_push_descriptor) */
#if defined(VK_KHR_maintenance6) && defined(VK_EXT_descriptor_buffer)
extern PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT vkCmdBindDescriptorBufferEmbeddedSamplers2EXT;
extern PFN_vkCmdSetDescriptorBufferOffsets2EXT vkCmdSetDescriptorBufferOffsets2EXT;
#endif /* defined(VK_KHR_maintenance6) && defined(VK_EXT_descriptor_buffer) */
#if defined(VK_KHR_map_memory2)
extern PFN_vkMapMemory2KHR vkMapMemory2KHR;
extern PFN_vkUnmapMemory2KHR vkUnmapMemory2KHR;
#endif /* defined(VK_KHR_map_memory2) */
#if defined(VK_KHR_performance_query)
extern PFN_vkAcquireProfilingLockKHR vkAcquireProfilingLockKHR;
extern PFN_vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR;
extern PFN_vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR;
extern PFN_vkReleaseProfilingLockKHR vkReleaseProfilingLockKHR;
#endif /* defined(VK_KHR_performance_query) */
#if defined(VK_KHR_pipeline_binary)
extern PFN_vkCreatePipelineBinariesKHR vkCreatePipelineBinariesKHR;
extern PFN_vkDestroyPipelineBinaryKHR vkDestroyPipelineBinaryKHR;
extern PFN_vkGetPipelineBinaryDataKHR vkGetPipelineBinaryDataKHR;
extern PFN_vkGetPipelineKeyKHR vkGetPipelineKeyKHR;
extern PFN_vkReleaseCapturedPipelineDataKHR vkReleaseCapturedPipelineDataKHR;
#endif /* defined(VK_KHR_pipeline_binary) */
#if defined(VK_KHR_pipeline_executable_properties)
extern PFN_vkGetPipelineExecutableInternalRepresentationsKHR vkGetPipelineExecutableInternalRepresentationsKHR;
extern PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR;
extern PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR;
#endif /* defined(VK_KHR_pipeline_executable_properties) */
#if defined(VK_KHR_present_wait)
extern PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
#endif /* defined(VK_KHR_present_wait) */
#if defined(VK_KHR_present_wait2)
extern PFN_vkWaitForPresent2KHR vkWaitForPresent2KHR;
#endif /* defined(VK_KHR_present_wait2) */
#if defined(VK_KHR_push_descriptor)
extern PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSetKHR;
#endif /* defined(VK_KHR_push_descriptor) */
#if defined(VK_KHR_ray_tracing_maintenance1) && defined(VK_KHR_ray_tracing_pipeline)
extern PFN_vkCmdTraceRaysIndirect2KHR vkCmdTraceRaysIndirect2KHR;
#endif /* defined(VK_KHR_ray_tracing_maintenance1) && defined(VK_KHR_ray_tracing_pipeline) */
#if defined(VK_KHR_ray_tracing_pipeline)
extern PFN_vkCmdSetRayTracingPipelineStackSizeKHR vkCmdSetRayTracingPipelineStackSizeKHR;
extern PFN_vkCmdTraceRaysIndirectKHR vkCmdTraceRaysIndirectKHR;
extern PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR;
extern PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;
extern PFN_vkGetRayTracingCaptureReplayShaderGroupHandlesKHR vkGetRayTracingCaptureReplayShaderGroupHandlesKHR;
extern PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR;
extern PFN_vkGetRayTracingShaderGroupStackSizeKHR vkGetRayTracingShaderGroupStackSizeKHR;
#endif /* defined(VK_KHR_ray_tracing_pipeline) */
#if defined(VK_KHR_sampler_ycbcr_conversion)
extern PFN_vkCreateSamplerYcbcrConversionKHR vkCreateSamplerYcbcrConversionKHR;
extern PFN_vkDestroySamplerYcbcrConversionKHR vkDestroySamplerYcbcrConversionKHR;
#endif /* defined(VK_KHR_sampler_ycbcr_conversion) */
#if defined(VK_KHR_shared_presentable_image)
extern PFN_vkGetSwapchainStatusKHR vkGetSwapchainStatusKHR;
#endif /* defined(VK_KHR_shared_presentable_image) */
#if defined(VK_KHR_surface)
extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
#endif /* defined(VK_KHR_surface) */
#if defined(VK_KHR_swapchain)
extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
extern PFN_vkQueuePresentKHR vkQueuePresentKHR;
#endif /* defined(VK_KHR_swapchain) */
#if defined(VK_KHR_swapchain_maintenance1)
extern PFN_vkReleaseSwapchainImagesKHR vkReleaseSwapchainImagesKHR;
#endif /* defined(VK_KHR_swapchain_maintenance1) */
#if defined(VK_KHR_synchronization2)
extern PFN_vkCmdPipelineBarrier2KHR vkCmdPipelineBarrier2KHR;
extern PFN_vkCmdResetEvent2KHR vkCmdResetEvent2KHR;
extern PFN_vkCmdSetEvent2KHR vkCmdSetEvent2KHR;
extern PFN_vkCmdWaitEvents2KHR vkCmdWaitEvents2KHR;
extern PFN_vkCmdWriteTimestamp2KHR vkCmdWriteTimestamp2KHR;
extern PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR;
#endif /* defined(VK_KHR_synchronization2) */
#if defined(VK_KHR_timeline_semaphore)
extern PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR;
extern PFN_vkSignalSemaphoreKHR vkSignalSemaphoreKHR;
extern PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR;
#endif /* defined(VK_KHR_timeline_semaphore) */
#if defined(VK_KHR_video_decode_queue)
extern PFN_vkCmdDecodeVideoKHR vkCmdDecodeVideoKHR;
#endif /* defined(VK_KHR_video_decode_queue) */
#if defined(VK_KHR_video_encode_queue)
extern PFN_vkCmdEncodeVideoKHR vkCmdEncodeVideoKHR;
extern PFN_vkGetEncodedVideoSessionParametersKHR vkGetEncodedVideoSessionParametersKHR;
extern PFN_vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR;
#endif /* defined(VK_KHR_video_encode_queue) */
#if defined(VK_KHR_video_queue)
extern PFN_vkBindVideoSessionMemoryKHR vkBindVideoSessionMemoryKHR;
extern PFN_vkCmdBeginVideoCodingKHR vkCmdBeginVideoCodingKHR;
extern PFN_vkCmdControlVideoCodingKHR vkCmdControlVideoCodingKHR;
extern PFN_vkCmdEndVideoCodingKHR vkCmdEndVideoCodingKHR;
extern PFN_vkCreateVideoSessionKHR vkCreateVideoSessionKHR;
extern PFN_vkCreateVideoSessionParametersKHR vkCreateVideoSessionParametersKHR;
extern PFN_vkDestroyVideoSessionKHR vkDestroyVideoSessionKHR;
extern PFN_vkDestroyVideoSessionParametersKHR vkDestroyVideoSessionParametersKHR;
extern PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR vkGetPhysicalDeviceVideoCapabilitiesKHR;
extern PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR vkGetPhysicalDeviceVideoFormatPropertiesKHR;
extern PFN_vkGetVideoSessionMemoryRequirementsKHR vkGetVideoSessionMemoryRequirementsKHR;
extern PFN_vkUpdateVideoSessionParametersKHR vkUpdateVideoSessionParametersKHR;
#endif /* defined(VK_KHR_video_queue) */
#if defined(VK_KHR_wayland_surface)
extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
#endif /* defined(VK_KHR_wayland_surface) */
#if defined(VK_KHR_win32_surface)
extern PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
extern PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR;
#endif /* defined(VK_KHR_win32_surface) */
#if defined(VK_KHR_xcb_surface)
extern PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR;
#endif /* defined(VK_KHR_xcb_surface) */
#if defined(VK_KHR_xlib_surface)
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
#endif /* defined(VK_KHR_xlib_surface) */
#if defined(VK_MVK_ios_surface)
extern PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK;
#endif /* defined(VK_MVK_ios_surface) */
#if defined(VK_MVK_macos_surface)
extern PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
#endif /* defined(VK_MVK_macos_surface) */
#if defined(VK_NN_vi_surface)
extern PFN_vkCreateViSurfaceNN vkCreateViSurfaceNN;
#endif /* defined(VK_NN_vi_surface) */
#if defined(VK_NVX_binary_import)
extern PFN_vkCmdCuLaunchKernelNVX vkCmdCuLaunchKernelNVX;
extern PFN_vkCreateCuFunctionNVX vkCreateCuFunctionNVX;
extern PFN_vkCreateCuModuleNVX vkCreateCuModuleNVX;
extern PFN_vkDestroyCuFunctionNVX vkDestroyCuFunctionNVX;
extern PFN_vkDestroyCuModuleNVX vkDestroyCuModuleNVX;
#endif /* defined(VK_NVX_binary_import) */
#if defined(VK_NVX_image_view_handle)
extern PFN_vkGetImageViewHandleNVX vkGetImageViewHandleNVX;
#endif /* defined(VK_NVX_image_view_handle) */
#if defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 3
extern PFN_vkGetImageViewHandle64NVX vkGetImageViewHandle64NVX;
#endif /* defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 3 */
#if defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 2
extern PFN_vkGetImageViewAddressNVX vkGetImageViewAddressNVX;
#endif /* defined(VK_NVX_image_view_handle) && VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION >= 2 */
#if defined(VK_NV_acquire_winrt_display)
extern PFN_vkAcquireWinrtDisplayNV vkAcquireWinrtDisplayNV;
extern PFN_vkGetWinrtDisplayNV vkGetWinrtDisplayNV;
#endif /* defined(VK_NV_acquire_winrt_display) */
#if defined(VK_NV_clip_space_w_scaling)
extern PFN_vkCmdSetViewportWScalingNV vkCmdSetViewportWScalingNV;
#endif /* defined(VK_NV_clip_space_w_scaling) */
#if defined(VK_NV_cluster_acceleration_structure)
extern PFN_vkCmdBuildClusterAccelerationStructureIndirectNV vkCmdBuildClusterAccelerationStructureIndirectNV;
extern PFN_vkGetClusterAccelerationStructureBuildSizesNV vkGetClusterAccelerationStructureBuildSizesNV;
#endif /* defined(VK_NV_cluster_acceleration_structure) */
#if defined(VK_NV_cooperative_matrix)
extern PFN_vkGetPhysicalDeviceCooperativeMatrixPropertiesNV vkGetPhysicalDeviceCooperativeMatrixPropertiesNV;
#endif /* defined(VK_NV_cooperative_matrix) */
#if defined(VK_NV_cooperative_matrix2)
extern PFN_vkGetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV vkGetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV;
#endif /* defined(VK_NV_cooperative_matrix2) */
#if defined(VK_NV_cooperative_vector)
extern PFN_vkCmdConvertCooperativeVectorMatrixNV vkCmdConvertCooperativeVectorMatrixNV;
extern PFN_vkConvertCooperativeVectorMatrixNV vkConvertCooperativeVectorMatrixNV;
extern PFN_vkGetPhysicalDeviceCooperativeVectorPropertiesNV vkGetPhysicalDeviceCooperativeVectorPropertiesNV;
#endif /* defined(VK_NV_cooperative_vector) */
#if defined(VK_NV_copy_memory_indirect)
extern PFN_vkCmdCopyMemoryIndirectNV vkCmdCopyMemoryIndirectNV;
extern PFN_vkCmdCopyMemoryToImageIndirectNV vkCmdCopyMemoryToImageIndirectNV;
#endif /* defined(VK_NV_copy_memory_indirect) */
#if defined(VK_NV_coverage_reduction_mode)
extern PFN_vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV;
#endif /* defined(VK_NV_coverage_reduction_mode) */
#if defined(VK_NV_cuda_kernel_launch)
extern PFN_vkCmdCudaLaunchKernelNV vkCmdCudaLaunchKernelNV;
extern PFN_vkCreateCudaFunctionNV vkCreateCudaFunctionNV;
extern PFN_vkCreateCudaModuleNV vkCreateCudaModuleNV;
extern PFN_vkDestroyCudaFunctionNV vkDestroyCudaFunctionNV;
extern PFN_vkDestroyCudaModuleNV vkDestroyCudaModuleNV;
extern PFN_vkGetCudaModuleCacheNV vkGetCudaModuleCacheNV;
#endif /* defined(VK_NV_cuda_kernel_launch) */
#if defined(VK_NV_device_diagnostic_checkpoints)
extern PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV;
extern PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV;
#endif /* defined(VK_NV_device_diagnostic_checkpoints) */
#if defined(VK_NV_device_diagnostic_checkpoints) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2))
extern PFN_vkGetQueueCheckpointData2NV vkGetQueueCheckpointData2NV;
#endif /* defined(VK_NV_device_diagnostic_checkpoints) && (defined(VK_VERSION_1_3) || defined(VK_KHR_synchronization2)) */
#if defined(VK_NV_device_generated_commands)
extern PFN_vkCmdBindPipelineShaderGroupNV vkCmdBindPipelineShaderGroupNV;
extern PFN_vkCmdExecuteGeneratedCommandsNV vkCmdExecuteGeneratedCommandsNV;
extern PFN_vkCmdPreprocessGeneratedCommandsNV vkCmdPreprocessGeneratedCommandsNV;
extern PFN_vkCreateIndirectCommandsLayoutNV vkCreateIndirectCommandsLayoutNV;
extern PFN_vkDestroyIndirectCommandsLayoutNV vkDestroyIndirectCommandsLayoutNV;
extern PFN_vkGetGeneratedCommandsMemoryRequirementsNV vkGetGeneratedCommandsMemoryRequirementsNV;
#endif /* defined(VK_NV_device_generated_commands) */
#if defined(VK_NV_device_generated_commands_compute)
extern PFN_vkCmdUpdatePipelineIndirectBufferNV vkCmdUpdatePipelineIndirectBufferNV;
extern PFN_vkGetPipelineIndirectDeviceAddressNV vkGetPipelineIndirectDeviceAddressNV;
extern PFN_vkGetPipelineIndirectMemoryRequirementsNV vkGetPipelineIndirectMemoryRequirementsNV;
#endif /* defined(VK_NV_device_generated_commands_compute) */
#if defined(VK_NV_external_compute_queue)
extern PFN_vkCreateExternalComputeQueueNV vkCreateExternalComputeQueueNV;
extern PFN_vkDestroyExternalComputeQueueNV vkDestroyExternalComputeQueueNV;
extern PFN_vkGetExternalComputeQueueDataNV vkGetExternalComputeQueueDataNV;
#endif /* defined(VK_NV_external_compute_queue) */
#if defined(VK_NV_external_memory_capabilities)
extern PFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV vkGetPhysicalDeviceExternalImageFormatPropertiesNV;
#endif /* defined(VK_NV_external_memory_capabilities) */
#if defined(VK_NV_external_memory_rdma)
extern PFN_vkGetMemoryRemoteAddressNV vkGetMemoryRemoteAddressNV;
#endif /* defined(VK_NV_external_memory_rdma) */
#if defined(VK_NV_external_memory_win32)
extern PFN_vkGetMemoryWin32HandleNV vkGetMemoryWin32HandleNV;
#endif /* defined(VK_NV_external_memory_win32) */
#if defined(VK_NV_fragment_shading_rate_enums)
extern PFN_vkCmdSetFragmentShadingRateEnumNV vkCmdSetFragmentShadingRateEnumNV;
#endif /* defined(VK_NV_fragment_shading_rate_enums) */
#if defined(VK_NV_low_latency2)
extern PFN_vkGetLatencyTimingsNV vkGetLatencyTimingsNV;
extern PFN_vkLatencySleepNV vkLatencySleepNV;
extern PFN_vkQueueNotifyOutOfBandNV vkQueueNotifyOutOfBandNV;
extern PFN_vkSetLatencyMarkerNV vkSetLatencyMarkerNV;
extern PFN_vkSetLatencySleepModeNV vkSetLatencySleepModeNV;
#endif /* defined(VK_NV_low_latency2) */
#if defined(VK_NV_memory_decompression)
extern PFN_vkCmdDecompressMemoryIndirectCountNV vkCmdDecompressMemoryIndirectCountNV;
extern PFN_vkCmdDecompressMemoryNV vkCmdDecompressMemoryNV;
#endif /* defined(VK_NV_memory_decompression) */
#if defined(VK_NV_mesh_shader)
extern PFN_vkCmdDrawMeshTasksIndirectNV vkCmdDrawMeshTasksIndirectNV;
extern PFN_vkCmdDrawMeshTasksNV vkCmdDrawMeshTasksNV;
#endif /* defined(VK_NV_mesh_shader) */
#if defined(VK_NV_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2))
extern PFN_vkCmdDrawMeshTasksIndirectCountNV vkCmdDrawMeshTasksIndirectCountNV;
#endif /* defined(VK_NV_mesh_shader) && (defined(VK_KHR_draw_indirect_count) || defined(VK_VERSION_1_2)) */
#if defined(VK_NV_optical_flow)
extern PFN_vkBindOpticalFlowSessionImageNV vkBindOpticalFlowSessionImageNV;
extern PFN_vkCmdOpticalFlowExecuteNV vkCmdOpticalFlowExecuteNV;
extern PFN_vkCreateOpticalFlowSessionNV vkCreateOpticalFlowSessionNV;
extern PFN_vkDestroyOpticalFlowSessionNV vkDestroyOpticalFlowSessionNV;
extern PFN_vkGetPhysicalDeviceOpticalFlowImageFormatsNV vkGetPhysicalDeviceOpticalFlowImageFormatsNV;
#endif /* defined(VK_NV_optical_flow) */
#if defined(VK_NV_partitioned_acceleration_structure)
extern PFN_vkCmdBuildPartitionedAccelerationStructuresNV vkCmdBuildPartitionedAccelerationStructuresNV;
extern PFN_vkGetPartitionedAccelerationStructuresBuildSizesNV vkGetPartitionedAccelerationStructuresBuildSizesNV;
#endif /* defined(VK_NV_partitioned_acceleration_structure) */
#if defined(VK_NV_ray_tracing)
extern PFN_vkBindAccelerationStructureMemoryNV vkBindAccelerationStructureMemoryNV;
extern PFN_vkCmdBuildAccelerationStructureNV vkCmdBuildAccelerationStructureNV;
extern PFN_vkCmdCopyAccelerationStructureNV vkCmdCopyAccelerationStructureNV;
extern PFN_vkCmdTraceRaysNV vkCmdTraceRaysNV;
extern PFN_vkCmdWriteAccelerationStructuresPropertiesNV vkCmdWriteAccelerationStructuresPropertiesNV;
extern PFN_vkCompileDeferredNV vkCompileDeferredNV;
extern PFN_vkCreateAccelerationStructureNV vkCreateAccelerationStructureNV;
extern PFN_vkCreateRayTracingPipelinesNV vkCreateRayTracingPipelinesNV;
extern PFN_vkDestroyAccelerationStructureNV vkDestroyAccelerationStructureNV;
extern PFN_vkGetAccelerationStructureHandleNV vkGetAccelerationStructureHandleNV;
extern PFN_vkGetAccelerationStructureMemoryRequirementsNV vkGetAccelerationStructureMemoryRequirementsNV;
extern PFN_vkGetRayTracingShaderGroupHandlesNV vkGetRayTracingShaderGroupHandlesNV;
#endif /* defined(VK_NV_ray_tracing) */
#if defined(VK_NV_scissor_exclusive) && VK_NV_SCISSOR_EXCLUSIVE_SPEC_VERSION >= 2
extern PFN_vkCmdSetExclusiveScissorEnableNV vkCmdSetExclusiveScissorEnableNV;
#endif /* defined(VK_NV_scissor_exclusive) && VK_NV_SCISSOR_EXCLUSIVE_SPEC_VERSION >= 2 */
#if defined(VK_NV_scissor_exclusive)
extern PFN_vkCmdSetExclusiveScissorNV vkCmdSetExclusiveScissorNV;
#endif /* defined(VK_NV_scissor_exclusive) */
#if defined(VK_NV_shading_rate_image)
extern PFN_vkCmdBindShadingRateImageNV vkCmdBindShadingRateImageNV;
extern PFN_vkCmdSetCoarseSampleOrderNV vkCmdSetCoarseSampleOrderNV;
extern PFN_vkCmdSetViewportShadingRatePaletteNV vkCmdSetViewportShadingRatePaletteNV;
#endif /* defined(VK_NV_shading_rate_image) */
#if defined(VK_OHOS_surface)
extern PFN_vkCreateSurfaceOHOS vkCreateSurfaceOHOS;
#endif /* defined(VK_OHOS_surface) */
#if defined(VK_QCOM_tile_memory_heap)
extern PFN_vkCmdBindTileMemoryQCOM vkCmdBindTileMemoryQCOM;
#endif /* defined(VK_QCOM_tile_memory_heap) */
#if defined(VK_QCOM_tile_properties)
extern PFN_vkGetDynamicRenderingTilePropertiesQCOM vkGetDynamicRenderingTilePropertiesQCOM;
extern PFN_vkGetFramebufferTilePropertiesQCOM vkGetFramebufferTilePropertiesQCOM;
#endif /* defined(VK_QCOM_tile_properties) */
#if defined(VK_QCOM_tile_shading)
extern PFN_vkCmdBeginPerTileExecutionQCOM vkCmdBeginPerTileExecutionQCOM;
extern PFN_vkCmdDispatchTileQCOM vkCmdDispatchTileQCOM;
extern PFN_vkCmdEndPerTileExecutionQCOM vkCmdEndPerTileExecutionQCOM;
#endif /* defined(VK_QCOM_tile_shading) */
#if defined(VK_QNX_external_memory_screen_buffer)
extern PFN_vkGetScreenBufferPropertiesQNX vkGetScreenBufferPropertiesQNX;
#endif /* defined(VK_QNX_external_memory_screen_buffer) */
#if defined(VK_QNX_screen_surface)
extern PFN_vkCreateScreenSurfaceQNX vkCreateScreenSurfaceQNX;
extern PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX vkGetPhysicalDeviceScreenPresentationSupportQNX;
#endif /* defined(VK_QNX_screen_surface) */
#if defined(VK_VALVE_descriptor_set_host_mapping)
extern PFN_vkGetDescriptorSetHostMappingVALVE vkGetDescriptorSetHostMappingVALVE;
extern PFN_vkGetDescriptorSetLayoutHostMappingInfoVALVE vkGetDescriptorSetLayoutHostMappingInfoVALVE;
#endif /* defined(VK_VALVE_descriptor_set_host_mapping) */
#if (defined(VK_EXT_depth_clamp_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clamp_control))
extern PFN_vkCmdSetDepthClampRangeEXT vkCmdSetDepthClampRangeEXT;
#endif /* (defined(VK_EXT_depth_clamp_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clamp_control)) */
#if (defined(VK_EXT_extended_dynamic_state)) || (defined(VK_EXT_shader_object))
extern PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
extern PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
extern PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
extern PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
extern PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
extern PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
extern PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
extern PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
extern PFN_vkCmdSetScissorWithCountEXT vkCmdSetScissorWithCountEXT;
extern PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
extern PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
extern PFN_vkCmdSetViewportWithCountEXT vkCmdSetViewportWithCountEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state2)) || (defined(VK_EXT_shader_object))
extern PFN_vkCmdSetDepthBiasEnableEXT vkCmdSetDepthBiasEnableEXT;
extern PFN_vkCmdSetLogicOpEXT vkCmdSetLogicOpEXT;
extern PFN_vkCmdSetPatchControlPointsEXT vkCmdSetPatchControlPointsEXT;
extern PFN_vkCmdSetPrimitiveRestartEnableEXT vkCmdSetPrimitiveRestartEnableEXT;
extern PFN_vkCmdSetRasterizerDiscardEnableEXT vkCmdSetRasterizerDiscardEnableEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state2)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3)) || (defined(VK_EXT_shader_object))
extern PFN_vkCmdSetAlphaToCoverageEnableEXT vkCmdSetAlphaToCoverageEnableEXT;
extern PFN_vkCmdSetAlphaToOneEnableEXT vkCmdSetAlphaToOneEnableEXT;
extern PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT;
extern PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT;
extern PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT;
extern PFN_vkCmdSetDepthClampEnableEXT vkCmdSetDepthClampEnableEXT;
extern PFN_vkCmdSetLogicOpEnableEXT vkCmdSetLogicOpEnableEXT;
extern PFN_vkCmdSetPolygonModeEXT vkCmdSetPolygonModeEXT;
extern PFN_vkCmdSetRasterizationSamplesEXT vkCmdSetRasterizationSamplesEXT;
extern PFN_vkCmdSetSampleMaskEXT vkCmdSetSampleMaskEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3)) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3) && (defined(VK_KHR_maintenance2) || defined(VK_VERSION_1_1))) || (defined(VK_EXT_shader_object))
extern PFN_vkCmdSetTessellationDomainOriginEXT vkCmdSetTessellationDomainOriginEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && (defined(VK_KHR_maintenance2) || defined(VK_VERSION_1_1))) || (defined(VK_EXT_shader_object)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_transform_feedback)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_transform_feedback))
extern PFN_vkCmdSetRasterizationStreamEXT vkCmdSetRasterizationStreamEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_transform_feedback)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_transform_feedback)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_conservative_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_conservative_rasterization))
extern PFN_vkCmdSetConservativeRasterizationModeEXT vkCmdSetConservativeRasterizationModeEXT;
extern PFN_vkCmdSetExtraPrimitiveOverestimationSizeEXT vkCmdSetExtraPrimitiveOverestimationSizeEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_conservative_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_conservative_rasterization)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_enable)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_enable))
extern PFN_vkCmdSetDepthClipEnableEXT vkCmdSetDepthClipEnableEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_enable)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_enable)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_sample_locations)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_sample_locations))
extern PFN_vkCmdSetSampleLocationsEnableEXT vkCmdSetSampleLocationsEnableEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_sample_locations)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_sample_locations)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_blend_operation_advanced)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_blend_operation_advanced))
extern PFN_vkCmdSetColorBlendAdvancedEXT vkCmdSetColorBlendAdvancedEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_blend_operation_advanced)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_blend_operation_advanced)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_provoking_vertex)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_provoking_vertex))
extern PFN_vkCmdSetProvokingVertexModeEXT vkCmdSetProvokingVertexModeEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_provoking_vertex)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_provoking_vertex)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_line_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_line_rasterization))
extern PFN_vkCmdSetLineRasterizationModeEXT vkCmdSetLineRasterizationModeEXT;
extern PFN_vkCmdSetLineStippleEnableEXT vkCmdSetLineStippleEnableEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_line_rasterization)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_line_rasterization)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_control))
extern PFN_vkCmdSetDepthClipNegativeOneToOneEXT vkCmdSetDepthClipNegativeOneToOneEXT;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_EXT_depth_clip_control)) || (defined(VK_EXT_shader_object) && defined(VK_EXT_depth_clip_control)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_clip_space_w_scaling)) || (defined(VK_EXT_shader_object) && defined(VK_NV_clip_space_w_scaling))
extern PFN_vkCmdSetViewportWScalingEnableNV vkCmdSetViewportWScalingEnableNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_clip_space_w_scaling)) || (defined(VK_EXT_shader_object) && defined(VK_NV_clip_space_w_scaling)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_viewport_swizzle)) || (defined(VK_EXT_shader_object) && defined(VK_NV_viewport_swizzle))
extern PFN_vkCmdSetViewportSwizzleNV vkCmdSetViewportSwizzleNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_viewport_swizzle)) || (defined(VK_EXT_shader_object) && defined(VK_NV_viewport_swizzle)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_fragment_coverage_to_color)) || (defined(VK_EXT_shader_object) && defined(VK_NV_fragment_coverage_to_color))
extern PFN_vkCmdSetCoverageToColorEnableNV vkCmdSetCoverageToColorEnableNV;
extern PFN_vkCmdSetCoverageToColorLocationNV vkCmdSetCoverageToColorLocationNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_fragment_coverage_to_color)) || (defined(VK_EXT_shader_object) && defined(VK_NV_fragment_coverage_to_color)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_framebuffer_mixed_samples)) || (defined(VK_EXT_shader_object) && defined(VK_NV_framebuffer_mixed_samples))
extern PFN_vkCmdSetCoverageModulationModeNV vkCmdSetCoverageModulationModeNV;
extern PFN_vkCmdSetCoverageModulationTableEnableNV vkCmdSetCoverageModulationTableEnableNV;
extern PFN_vkCmdSetCoverageModulationTableNV vkCmdSetCoverageModulationTableNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_framebuffer_mixed_samples)) || (defined(VK_EXT_shader_object) && defined(VK_NV_framebuffer_mixed_samples)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_shading_rate_image)) || (defined(VK_EXT_shader_object) && defined(VK_NV_shading_rate_image))
extern PFN_vkCmdSetShadingRateImageEnableNV vkCmdSetShadingRateImageEnableNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_shading_rate_image)) || (defined(VK_EXT_shader_object) && defined(VK_NV_shading_rate_image)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_representative_fragment_test)) || (defined(VK_EXT_shader_object) && defined(VK_NV_representative_fragment_test))
extern PFN_vkCmdSetRepresentativeFragmentTestEnableNV vkCmdSetRepresentativeFragmentTestEnableNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_representative_fragment_test)) || (defined(VK_EXT_shader_object) && defined(VK_NV_representative_fragment_test)) */
#if (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_coverage_reduction_mode)) || (defined(VK_EXT_shader_object) && defined(VK_NV_coverage_reduction_mode))
extern PFN_vkCmdSetCoverageReductionModeNV vkCmdSetCoverageReductionModeNV;
#endif /* (defined(VK_EXT_extended_dynamic_state3) && defined(VK_NV_coverage_reduction_mode)) || (defined(VK_EXT_shader_object) && defined(VK_NV_coverage_reduction_mode)) */
#if (defined(VK_EXT_host_image_copy)) || (defined(VK_EXT_image_compression_control))
extern PFN_vkGetImageSubresourceLayout2EXT vkGetImageSubresourceLayout2EXT;
#endif /* (defined(VK_EXT_host_image_copy)) || (defined(VK_EXT_image_compression_control)) */
#if (defined(VK_EXT_shader_object)) || (defined(VK_EXT_vertex_input_dynamic_state))
extern PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT;
#endif /* (defined(VK_EXT_shader_object)) || (defined(VK_EXT_vertex_input_dynamic_state)) */
#if (defined(VK_KHR_descriptor_update_template) && defined(VK_KHR_push_descriptor)) || (defined(VK_KHR_push_descriptor) && (defined(VK_VERSION_1_1) || defined(VK_KHR_descriptor_update_template)))
extern PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR;
#endif /* (defined(VK_KHR_descriptor_update_template) && defined(VK_KHR_push_descriptor)) || (defined(VK_KHR_push_descriptor) && (defined(VK_VERSION_1_1) || defined(VK_KHR_descriptor_update_template))) */
#if (defined(VK_KHR_device_group) && defined(VK_KHR_surface)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1))
extern PFN_vkGetDeviceGroupPresentCapabilitiesKHR vkGetDeviceGroupPresentCapabilitiesKHR;
extern PFN_vkGetDeviceGroupSurfacePresentModesKHR vkGetDeviceGroupSurfacePresentModesKHR;
extern PFN_vkGetPhysicalDevicePresentRectanglesKHR vkGetPhysicalDevicePresentRectanglesKHR;
#endif /* (defined(VK_KHR_device_group) && defined(VK_KHR_surface)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1)) */
#if (defined(VK_KHR_device_group) && defined(VK_KHR_swapchain)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1))
extern PFN_vkAcquireNextImage2KHR vkAcquireNextImage2KHR;
#endif /* (defined(VK_KHR_device_group) && defined(VK_KHR_swapchain)) || (defined(VK_KHR_swapchain) && defined(VK_VERSION_1_1)) */
/* VOLK_GENERATE_PROTOTYPES_H */

#ifdef __cplusplus
}
#endif

#endif

#ifdef VOLK_IMPLEMENTATION
#undef VOLK_IMPLEMENTATION
/* Prevent tools like dependency checkers from detecting a cyclic dependency */
#define VOLK_SOURCE "volk.c"
#include VOLK_SOURCE
#endif

/**
 * Copyright (c) 2018-2025 Arseny Kapoulkine
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*/
/* clang-format on */

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/instance.hpp"
#include "common/exception.hpp"

#include <cstdint>
#include <memory>
#include <vector>

using namespace LSFG::Core;

const std::vector<const char*> requiredExtensions = {

};

Instance::Instance() {
    volkInitialize();

    // create Vulkan instance
    const VkApplicationInfo appInfo{
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = "lsfg-vk-base",
        .applicationVersion = VK_MAKE_VERSION(0, 0, 1),
        .pEngineName = "lsfg-vk-base",
        .engineVersion = VK_MAKE_VERSION(0, 0, 1),
        .apiVersion = VK_API_VERSION_1_3
    };
    const VkInstanceCreateInfo createInfo{
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pApplicationInfo = &appInfo,
        .enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size()),
        .ppEnabledExtensionNames = requiredExtensions.data()
    };
    VkInstance instanceHandle{};
    auto res = vkCreateInstance(&createInfo, nullptr, &instanceHandle);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Failed to create Vulkan instance");

    volkLoadInstance(instanceHandle);

    // store in shared ptr
    this->instance = std::shared_ptr<VkInstance>(
        new VkInstance(instanceHandle),
        [](VkInstance* instance) {
            vkDestroyInstance(*instance, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/fence.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <memory>
#include <cstdint>

using namespace LSFG::Core;

Fence::Fence(const Core::Device& device) {
     // create fence
    const VkFenceCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
    };
    VkFence fenceHandle{};
    auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle);
    if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create fence");

    // store fence in shared ptr
    this->fence = std::shared_ptr<VkFence>(
        new VkFence(fenceHandle),
        [dev = device.handle()](VkFence* fenceHandle) {
            vkDestroyFence(dev, *fenceHandle, nullptr);
        }
    );
}

void Fence::reset(const Core::Device& device) const {
    VkFence fenceHandle = this->handle();
    auto res = vkResetFences(device.handle(), 1, &fenceHandle);
    if (res != VK_SUCCESS)
        throw LSFG::vulkan_error(res, "Unable to reset fence");
}

bool Fence::wait(const Core::Device& device, uint64_t timeout) const {
    VkFence fenceHandle = this->handle();
    auto res = vkWaitForFences(device.handle(), 1, &fenceHandle, VK_TRUE, timeout);
    if (res != VK_SUCCESS && res != VK_TIMEOUT)
        throw LSFG::vulkan_error(res, "Unable to wait for fence");

    return res == VK_SUCCESS;
}

#include "common/exception.hpp"

#include <vulkan/vulkan_core.h>

#include <exception>
#include <stdexcept>
#include <cstdint>
#include <format>
#include <string>

using namespace LSFG;

vulkan_error::vulkan_error(VkResult result, const std::string& message)
    : std::runtime_error(std::format("{} (error {})", message, static_cast<int32_t>(result))),
      result(result) {}

vulkan_error::~vulkan_error() noexcept = default;

rethrowable_error::rethrowable_error(const std::string& message, const std::exception& exe)
        : std::runtime_error(message) {
    this->message = std::format("{}\n- {}", message, exe.what());
}

rethrowable_error::~rethrowable_error() noexcept = default;

/*
The MIT License (MIT)

Copyright (c) 2020 Trail of Bits, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include <Windows.h>
#include <pe-parse/to_string.h>

namespace peparse {
std::string from_utf16(const UCharString &u) {
  std::string result;
  std::size_t size = WideCharToMultiByte(CP_UTF8,
                                         0,
                                         u.data(),
                                         static_cast<int>(u.size()),
                                         nullptr,
                                         0,
                                         nullptr,
                                         nullptr);

  if (size <= 0) {
    return result;
  }

  result.reserve(size);
  WideCharToMultiByte(CP_UTF8,
                      0,
                      u.data(),
                      static_cast<int>(u.size()),
                      &result[0],
                      static_cast<int>(result.capacity()),
                      nullptr,
                      nullptr);

  return result;
}
} // namespace peparse

#pragma once

#include <cstddef>

namespace dxvk {

  enum DxvkLimits : size_t {
    MaxNumRenderTargets         =     8,
    MaxNumVertexAttributes      =    32,
    MaxNumVertexBindings        =    32,
    MaxNumXfbBuffers            =     4,
    MaxNumXfbStreams            =     4,
    MaxNumViewports             =    16,
    MaxNumResourceSlots         =  1216,
    MaxNumQueuedCommandBuffers  =    32,
    MaxNumQueryCountPerPool     =   128,
    MaxNumSpecConstants         =    12,
    MaxUniformBufferSize        = 65536,
    MaxVertexBindingStride      =  2048,
    MaxPushConstantSize         =   128,
  };

}

#include "mini/commandpool.hpp"
#include "common/exception.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>

#include <cstdint>
#include <memory>

using namespace Mini;

CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) {
    // create command pool
    const VkCommandPoolCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .queueFamilyIndex = graphicsFamilyIdx
    };
    VkCommandPool commandPoolHandle{};
    auto res = Layer::ovkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle);
    if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create command pool");

    // store command pool in shared ptr
    this->commandPool = std::shared_ptr<VkCommandPool>(
        new VkCommandPool(commandPoolHandle),
        [dev = device](VkCommandPool* commandPoolHandle) {
            Layer::ovkDestroyCommandPool(dev, *commandPoolHandle, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/pipeline.hpp"
#include "core/device.hpp"
#include "core/shadermodule.hpp"
#include "core/commandbuffer.hpp"
#include "common/exception.hpp"

#include <memory>

using namespace LSFG::Core;

Pipeline::Pipeline(const Core::Device& device, const ShaderModule& shader) {
    // create pipeline layout
    VkDescriptorSetLayout shaderLayout = shader.getLayout();
    const VkPipelineLayoutCreateInfo layoutDesc{
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .setLayoutCount = 1,
        .pSetLayouts = &shaderLayout,
    };
    VkPipelineLayout layoutHandle{};
    auto res = vkCreatePipelineLayout(device.handle(), &layoutDesc, nullptr, &layoutHandle);
    if (res != VK_SUCCESS || !layoutHandle)
        throw LSFG::vulkan_error(res, "Failed to create pipeline layout");

    // create pipeline
    const VkPipelineShaderStageCreateInfo shaderStageInfo{
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_COMPUTE_BIT,
        .module = shader.handle(),
        .pName = "main",
    };
    const VkComputePipelineCreateInfo pipelineDesc{
        .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
        .stage = shaderStageInfo,
        .layout = layoutHandle,
    };
    VkPipeline pipelineHandle{};
    res = vkCreateComputePipelines(device.handle(),
        VK_NULL_HANDLE, 1, &pipelineDesc, nullptr, &pipelineHandle);
    if (res != VK_SUCCESS || !pipelineHandle)
        throw LSFG::vulkan_error(res, "Failed to create compute pipeline");

    // store layout and pipeline in shared ptr
    this->layout = std::shared_ptr<VkPipelineLayout>(
        new VkPipelineLayout(layoutHandle),
        [dev = device.handle()](VkPipelineLayout* layout) {
            vkDestroyPipelineLayout(dev, *layout, nullptr);
        }
    );
    this->pipeline = std::shared_ptr<VkPipeline>(
        new VkPipeline(pipelineHandle),
        [dev = device.handle()](VkPipeline* pipeline) {
            vkDestroyPipeline(dev, *pipeline, nullptr);
        }
    );
}

void Pipeline::bind(const CommandBuffer& commandBuffer) const {
     vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline);
}

#include "mini/semaphore.hpp"
#include "common/exception.hpp"
#include "layer.hpp"

#include <vulkan/vulkan_core.h>

#include <memory>

using namespace Mini;

Semaphore::Semaphore(VkDevice device) {
    // create semaphore
    const VkSemaphoreCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
    };
    VkSemaphore semaphoreHandle{};
    auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle);
    if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create semaphore");

    // store semaphore in shared ptr
    this->semaphore = std::shared_ptr<VkSemaphore>(
        new VkSemaphore(semaphoreHandle),
        [dev = device](VkSemaphore* semaphoreHandle) {
            Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr);
        }
    );
}

Semaphore::Semaphore(VkDevice device, int* fd) {
    // create semaphore
    const VkExportSemaphoreCreateInfo exportInfo{
        .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
        .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
    };
    const VkSemaphoreCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = &exportInfo
    };
    VkSemaphore semaphoreHandle{};
    auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle);
    if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create semaphore");

    // export semaphore to fd
    const VkSemaphoreGetFdInfoKHR fdInfo{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
        .semaphore = semaphoreHandle,
        .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
    };
    res = Layer::ovkGetSemaphoreFdKHR(device, &fdInfo, fd);
    if (res != VK_SUCCESS || *fd < 0)
        throw LSFG::vulkan_error(res, "Unable to export semaphore to fd");

    // store semaphore in shared ptr
    this->semaphore = std::shared_ptr<VkSemaphore>(
        new VkSemaphore(semaphoreHandle),
        [dev = device](VkSemaphore* semaphoreHandle) {
            Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr);
        }
    );
}

#include "pool/shaderpool.hpp"
#include "core/shadermodule.hpp"
#include "core/device.hpp"
#include "core/pipeline.hpp"

#include <vulkan/vulkan_core.h>

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <utility>

using namespace LSFG;
using namespace LSFG::Pool;

Core::ShaderModule ShaderPool::getShader(
        const Core::Device& device, const std::string& name,
        const std::vector<std::pair<size_t, VkDescriptorType>>& types) {
    auto it = shaders.find(name);
    if (it != shaders.end())
        return it->second;

    // grab the shader
    auto bytecode = this->source(name);
    if (bytecode.empty())
        throw std::runtime_error("Shader code is empty: " + name);

    // create the shader module
    Core::ShaderModule shader(device, bytecode, types);
    shaders[name] = shader;
    return shader;
}

Core::Pipeline ShaderPool::getPipeline(
        const Core::Device& device, const std::string& name) {
    auto it = pipelines.find(name);
    if (it != pipelines.end())
        return it->second;

    // grab the shader module
    auto shader = this->getShader(device, name, {});

    // create the pipeline
    Core::Pipeline pipeline(device, shader);
    pipelines[name] = pipeline;
    return pipeline;
}

#pragma once

#include <string>

namespace dxvk {

  /**
   * \brief DXVK error
   *
   * A generic exception class that stores a
   * message. Exceptions should be logged.
   */
  class DxvkError {

  public:

    DxvkError() { }
    DxvkError(std::string&& message)
    : m_message(std::move(message)) { }

    const std::string& message() const {
      return m_message;
    }

  private:

    std::string m_message;

  };

}

/*
The MIT License (MIT)

Copyright (c) 2019 Trail of Bits, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include <codecvt>
#include <locale>
#include <pe-parse/to_string.h>

namespace peparse {
// See
// https://stackoverflow.com/questions/38688417/utf-conversion-functions-in-c11
std::string from_utf16(const UCharString &u) {
  std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
  return convert.to_bytes(u);
}
} // namespace peparse

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/sampler.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <memory>

using namespace LSFG::Core;

Sampler::Sampler(const Core::Device& device,
        VkSamplerAddressMode mode,
        VkCompareOp compare,
        bool isWhite) {
    // create sampler
    const VkSamplerCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
        .magFilter = VK_FILTER_LINEAR,
        .minFilter = VK_FILTER_LINEAR,
        .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
        .addressModeU = mode,
        .addressModeV = mode,
        .addressModeW = mode,
        .compareOp = compare,
        .maxLod = VK_LOD_CLAMP_NONE,
        .borderColor =
            isWhite ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
                    : VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK
    };
    VkSampler samplerHandle{};
    auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle);
    if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create sampler");

    // store sampler in shared ptr
    this->sampler = std::shared_ptr<VkSampler>(
        new VkSampler(samplerHandle),
        [dev = device.handle()](VkSampler* samplerHandle) {
            vkDestroySampler(dev, *samplerHandle, nullptr);
        }
    );
}

#include <volk.h>
#include <vulkan/vulkan_core.h>

#include "core/commandpool.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"

#include <memory>

using namespace LSFG::Core;

CommandPool::CommandPool(const Core::Device& device) {
    // create command pool
    const VkCommandPoolCreateInfo desc{
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .queueFamilyIndex = device.getComputeFamilyIdx()
    };
    VkCommandPool commandPoolHandle{};
    auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle);
    if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE)
        throw LSFG::vulkan_error(res, "Unable to create command pool");

    // store command pool in shared ptr
    this->commandPool = std::shared_ptr<VkCommandPool>(
        new VkCommandPool(commandPoolHandle),
        [dev = device.handle()](VkCommandPool* commandPoolHandle) {
            vkDestroyCommandPool(dev, *commandPoolHandle, nullptr);
        }
    );
}

#pragma once

#include <sstream>

#include "log/log.h"

#ifdef _MSC_VER
#define METHOD_NAME __FUNCSIG__
#else
#define METHOD_NAME __PRETTY_FUNCTION__
#endif

#define TRACE_ENABLED

#ifdef TRACE_ENABLED
#define TRACE(...) \
  do { dxvk::debug::trace(METHOD_NAME, ##__VA_ARGS__); } while (0)
#else
#define TRACE(...) \
  do { } while (0)
#endif

namespace dxvk::debug {

  std::string methodName(const std::string& prettyName);

  inline void traceArgs(std::stringstream& stream) { }

  template<typename Arg1>
  void traceArgs(std::stringstream& stream, const Arg1& arg1) {
    stream << arg1;
  }

  template<typename Arg1, typename Arg2, typename... Args>
  void traceArgs(std::stringstream& stream, const Arg1& arg1, const Arg2& arg2, const Args&... args) {
    stream << arg1 << ",";
    traceArgs(stream, arg2, args...);
  }

  template<typename... Args>
  void trace(const std::string& funcName, const Args&... args) {
    std::stringstream stream;
    stream << methodName(funcName) << "(";
    traceArgs(stream, args...);
    stream << ")";
    Logger::trace(stream.str());
  }

}

#include "log/log_debug.h"

namespace dxvk::debug {

  std::string methodName(const std::string& prettyName) {
    size_t end = prettyName.find("(");
    size_t begin = prettyName.substr(0, end).rfind(" ") + 1;
    return prettyName.substr(begin,end - begin);
  }

}

#include <toml11/parser.hpp>
#include <toml11/skip.hpp>
#include <toml11/types.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

namespace toml
{

namespace detail
{
template bool skip_whitespace            <type_config>(location& loc, const context<type_config>&);
template bool skip_empty_lines           <type_config>(location& loc, const context<type_config>&);
template void skip_comment_block         <type_config>(location& loc, const context<type_config>&);
template void skip_empty_or_comment_lines<type_config>(location& loc, const context<type_config>&);
template void skip_string_like           <type_config>(location& loc, const context<type_config>&);
template void skip_array_like            <type_config>(location& loc, const context<type_config>&);
template void skip_inline_table_like     <type_config>(location& loc, const context<type_config>&);
template void skip_value                 <type_config>(location& loc, const context<type_config>&);
template void skip_key_value_pair        <type_config>(location& loc, const context<type_config>&);
template void skip_until_next_table      <type_config>(location& loc, const context<type_config>&);

template bool skip_whitespace            <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template bool skip_empty_lines           <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_comment_block         <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_empty_or_comment_lines<ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_string_like           <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_array_like            <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_inline_table_like     <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_value                 <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_key_value_pair        <ordered_type_config>(location& loc, const context<ordered_type_config>&);
template void skip_until_next_table      <ordered_type_config>(location& loc, const context<ordered_type_config>&);

} // detail
} // toml

#pragma once

#include <sstream>
#include <string>

#if defined(_MSC_VER)
typedef std::basic_string<wchar_t> UCharString;
#else
typedef std::u16string UCharString;
#endif

namespace peparse {
template <class T>
static std::string to_string(T t, std::ios_base &(*f)(std::ios_base &) ) {
  std::ostringstream oss;
  oss << f << t;
  return oss.str();
}

std::string from_utf16(const UCharString &u);
} // namespace peparse

#include <toml11/parser.hpp>
#include <toml11/types.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

namespace toml
{

template result<basic_value<type_config>, std::vector<error_info>> try_parse<type_config>(std::vector<unsigned char>, std::string, spec);
template result<basic_value<type_config>, std::vector<error_info>> try_parse<type_config>(std::istream&, std::string, spec);
template result<basic_value<type_config>, std::vector<error_info>> try_parse<type_config>(std::string, spec);
template result<basic_value<type_config>, std::vector<error_info>> try_parse<type_config>(FILE*, std::string, spec);
template result<basic_value<type_config>, std::vector<error_info>> try_parse_str<type_config>(std::string, spec, cxx::source_location);

template basic_value<type_config> parse<type_config>(std::vector<unsigned char>, std::string, spec);
template basic_value<type_config> parse<type_config>(std::istream&, std::string, spec);
template basic_value<type_config> parse<type_config>(std::string, spec);
template basic_value<type_config> parse<type_config>(FILE*, std::string, spec);
template basic_value<type_config> parse_str<type_config>(std::string, spec, cxx::source_location);

template result<basic_value<ordered_type_config>, std::vector<error_info>> try_parse<ordered_type_config>(std::vector<unsigned char>, std::string, spec);
template result<basic_value<ordered_type_config>, std::vector<error_info>> try_parse<ordered_type_config>(std::istream&, std::string, spec);
template result<basic_value<ordered_type_config>, std::vector<error_info>> try_parse<ordered_type_config>(std::string, spec);
template result<basic_value<ordered_type_config>, std::vector<error_info>> try_parse<ordered_type_config>(FILE*, std::string, spec);
template result<basic_value<ordered_type_config>, std::vector<error_info>> try_parse_str<ordered_type_config>(std::string, spec, cxx::source_location);

template basic_value<ordered_type_config> parse<ordered_type_config>(std::vector<unsigned char>, std::string, spec);
template basic_value<ordered_type_config> parse<ordered_type_config>(std::istream&, std::string, spec);
template basic_value<ordered_type_config> parse<ordered_type_config>(std::string, spec);
template basic_value<ordered_type_config> parse<ordered_type_config>(FILE*, std::string, spec);
template basic_value<ordered_type_config> parse_str<ordered_type_config>(std::string, spec, cxx::source_location);

#if defined(TOML11_HAS_FILESYSTEM)
template cxx::enable_if_t<std::is_same<std::filesystem::path, std::filesystem::path>::value, result<basic_value<type_config>,         std::vector<error_info>>> try_parse<type_config,         std::filesystem::path>(const std::filesystem::path&, spec);
template cxx::enable_if_t<std::is_same<std::filesystem::path, std::filesystem::path>::value, result<basic_value<ordered_type_config>, std::vector<error_info>>> try_parse<ordered_type_config, std::filesystem::path>(const std::filesystem::path&, spec);
template cxx::enable_if_t<std::is_same<std::filesystem::path, std::filesystem::path>::value, basic_value<type_config>                                         > parse    <type_config,         std::filesystem::path>(const std::filesystem::path&, spec);
template cxx::enable_if_t<std::is_same<std::filesystem::path, std::filesystem::path>::value, basic_value<ordered_type_config>                                 > parse    <ordered_type_config, std::filesystem::path>(const std::filesystem::path&, spec);
#endif // filesystem

} // toml

#pragma once

#define ENUM_NAME(name) \
  case name: return os << #name

#define ENUM_DEFAULT(name) \
  default: return os << static_cast<int32_t>(e)

#include <toml11/serializer.hpp>
#include <toml11/types.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

namespace toml
{
struct type_config;
struct ordered_type_config;

template typename basic_value<type_config>::string_type
format<type_config>(const basic_value<type_config>&, const spec);

template typename basic_value<type_config>::string_type
format<type_config>(const typename basic_value<type_config>::key_type& k,
                    const basic_value<type_config>& v, const spec);

template typename basic_value<type_config>::string_type
format<type_config>(const std::vector<typename basic_value<type_config>::key_type>& ks,
                    const basic_value<type_config>& v, const spec s);

template typename basic_value<type_config>::string_type
format<ordered_type_config>(const basic_value<ordered_type_config>&, const spec);

template typename basic_value<type_config>::string_type
format<ordered_type_config>(const typename basic_value<ordered_type_config>::key_type& k,
                            const basic_value<ordered_type_config>& v, const spec);

template typename basic_value<type_config>::string_type
format<ordered_type_config>(const std::vector<typename basic_value<ordered_type_config>::key_type>& ks,
                            const basic_value<ordered_type_config>& v, const spec s);

namespace detail
{
template class serializer<::toml::type_config>;
template class serializer<::toml::ordered_type_config>;
} // detail
} // toml

#pragma once

#ifdef __GNUC__
#define likely(x) __builtin_expect(bool(x),1)
#define unlikely(x) __builtin_expect(bool(x),0)
#define force_inline inline __attribute__((always_inline))
#else
#define likely(x)   (x)
#define unlikely(x) (x)
#define force_inline inline
#endif

#include <toml11/context.hpp>
#include <toml11/types.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

namespace toml
{
namespace detail
{
template class context<::toml::type_config>;
template class context<::toml::ordered_type_config>;
} // detail
} // toml

#include <toml11/impl/value_t_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/datetime_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/region_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/format_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/color_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/comments_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/location_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/syntax_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/error_info_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/literal_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/source_location_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/impl/scanner_impl.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

#include <toml11/value.hpp>
#include <toml11/types.hpp>

#if ! defined(TOML11_COMPILE_SOURCES)
#error "Define `TOML11_COMPILE_SOURCES` before compiling source code!"
#endif

namespace toml
{
template class basic_value<type_config>;
template class basic_value<ordered_type_config>;
} // toml

#pragma once

#include <thirdparty/spirv.hpp>
#include <thirdparty/GLSL.std.450.h>

#include "log/log.h"
#include "log/log_debug.h"

#include "util_error.h"
#include "util_flags.h"
#include "util_likely.h"
#include "util_string.h"

#include "rc/util_rc.h"
#include "rc/util_rc_ptr.h"

#pragma once

#include <vulkan/vulkan.h>
#include <thirdparty/spirv.hpp>

#include "dxvk_limits.h"
#include "dxvk_pipelayout.h"

#include "log/log.h"
#include "log/log_debug.h"

#include "rc/util_rc.h"
#include "rc/util_rc_ptr.h"

#include "util_bit.h"
#include "util_enum.h"
#include "util_error.h"
#include "util_string.h"
#include "util_flags.h"
#include "util_small_vector.h"

static bool compareSemanticNames(
      const std::string& a,
      const std::string& b) {}

This code segment functions to: Compare two input strings in a case-insensitive manner and verify their character sequence equivalence regardless of letter casing differences.

```cpp
[TASK_BEGIN]
static bool compareSemanticNames(
  const std::string& a,
  const std::string& b) {
  if (a.size() != b.size())
    return false;

  for (size_t i = 0; i < a.size(); ++i) {
    if (std::tolower(static_cast<unsigned char>(a[i])) !=
        std::tolower(static_cast<unsigned char>(b[i])))
      return false;
  }

  return true;
}
[TASK_END]
```

static bool compareSemanticNames(
      const std::string& a,
      const std::string& b) {
    if (a.size() != b.size())
      return false;
    for (size_t i = 0; i < a.size(); i++) {
      char ac = a[i];
      char bc = b[i];
      if (ac != bc) {
        if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A';
        if (bc >= 'A' && bc <= 'Z') bc += 'a' - 'A';
        if (ac != bc)
          return false;
      }
    }
    return true;
  }

static bool compareSemanticNames(
  const std::string& a,
  const std::string& b) {
  if (a.size() != b.size())
    return false;
  for (size_t i = 0; i < a.size(); ++i) {
    if (std::tolower(static_cast<unsigned char>(a[i])) !=
        std::tolower(static_cast<unsigned char>(b[i])))
      return false;
  }
  return true;
}