text: /******************************************************************************
   * The MIT License (MIT)
   *
   * Copyright (c) 2020-2021 Baldur Karlsson
   *
   * 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 "spirv_debug.h"
  #include "common/formatting.h"
  #include "spirv_op_helpers.h"
  #include "spirv_reflect.h"
  #include "var_dispatch_helpers.h"
  
  // this could be cleaner if ShaderVariable wasn't a very public struct, but it's not worth it so
  // we just reserve value slots that we know won't be used in opaque variables
  static const uint32_t PointerVariableSlot = 0;
  static const uint32_t Scalar0VariableSlot = 1;
  static const uint32_t Scalar1VariableSlot = 2;
  static const uint32_t BaseIdVariableSlot = 3;
  static const uint32_t MajorStrideVariableSlot = 4;
  static const uint32_t ArrayVariableSlot = 8;
  static const uint32_t TextureTypeVariableSlot = 9;
  static const uint32_t BufferPointerByteOffsetVariableSlot = 9;
  static const uint32_t BufferPointerTypeIdVariableSlot = 10;
  static const uint32_t SSBOVariableSlot = 11;
  
  static ShaderVariable *pointerIfMutable(const ShaderVariable &var)
  {
    return NULL;
  }
  static ShaderVariable *pointerIfMutable(ShaderVariable &var)
  {
    return &var;
  }
  
  static void ClampScalars(rdcspv::DebugAPIWrapper *apiWrapper, const ShaderVariable &var,
                           uint32_t &scalar0)
  {
    if(scalar0 > var.columns && scalar0 != ~0U)
    {
      apiWrapper->AddDebugMessage(
          MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
          StringFormat::Fmt("Invalid scalar index %u at %u-vector %s. Clamping to %u", scalar0,
                            var.columns, var.name.c_str(), var.columns - 1));
      scalar0 = RDCMIN(0U, uint32_t(var.columns - 1));
    }
  }
  
  static void ClampScalars(rdcspv::DebugAPIWrapper *apiWrapper, const ShaderVariable &var,
                           uint32_t &scalar0, uint32_t &scalar1)
  {
    if(scalar0 > var.columns && scalar0 != ~0U)
    {
      apiWrapper->AddDebugMessage(
          MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
          StringFormat::Fmt("Invalid scalar index %u at matrix %s with %u columns. Clamping to %u",
                            scalar0, var.columns, var.name.c_str(), var.columns - 1));
      scalar0 = RDCMIN(0U, uint32_t(var.columns - 1));
    }
    if(scalar1 > var.rows && scalar1 != ~0U)
    {
      apiWrapper->AddDebugMessage(
          MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
          StringFormat::Fmt("Invalid scalar index %u at matrix %s with %u rows. Clamping to %u",
                            scalar1, var.rows, var.name.c_str(), var.rows - 1));
      scalar1 = RDCMIN(0U, uint32_t(var.rows - 1));
    }
  }
  
  static uint32_t VarByteSize(const ShaderVariable &var)
  {
    return VarTypeByteSize(var.type) * RDCMAX(1U, (uint32_t)var.rows) *
           RDCMAX(1U, (uint32_t)var.columns);
  }
  
  static void *VarElemPointer(ShaderVariable &var, uint32_t comp)
  {
    RDCASSERTNOTEQUAL(var.type, VarType::Unknown);
    byte *ret = (byte *)var.value.u8v.data();
    return ret + comp * VarTypeByteSize(var.type);
  }
  
  static const void *VarElemPointer(const ShaderVariable &var, uint32_t comp)
  {
    RDCASSERTNOTEQUAL(var.type, VarType::Unknown);
    const byte *ret = (const byte *)var.value.u8v.data();
    return ret + comp * VarTypeByteSize(var.type);
  }
  
  namespace rdcspv
  {
  void AssignValue(ShaderVariable &dst, const ShaderVariable &src)
  {
    dst.value = src.value;
  
    RDCASSERTEQUAL(dst.members.size(), src.members.size());
  
    for(size_t i = 0; i < src.members.size(); i++)
      AssignValue(dst.members[i], src.members[i]);
  }
  
  Debugger::Debugger()
  {
  }
  
  Debugger::~Debugger()
  {
    SAFE_DELETE(apiWrapper);
  }
  
  void Debugger::Parse(const rdcarray<uint32_t> &spirvWords)
  {
    Processor::Parse(spirvWords);
  }
  
  Iter Debugger::GetIterForInstruction(uint32_t inst)
  {
    return Iter(m_SPIRV, instructionOffsets[inst]);
  }
  
  uint32_t Debugger::GetInstructionForIter(Iter it)
  {
    return instructionOffsets.indexOf(it.offs());
  }
  
  uint32_t Debugger::GetInstructionForFunction(Id id)
  {
    return instructionOffsets.indexOf(functions[id].begin);
  }
  
  uint32_t Debugger::GetInstructionForLabel(Id id)
  {
    uint32_t ret = labelInstruction[id];
    RDCASSERT(ret);
    return ret;
  }
  
  const rdcspv::DataType &Debugger::GetType(Id typeId)
  {
    return dataTypes[typeId];
  }
  
  const rdcspv::DataType &Debugger::GetTypeForId(Id ssaId)
  {
    return dataTypes[idTypes[ssaId]];
  }
  
  const Decorations &Debugger::GetDecorations(Id typeId)
  {
    return decorations[typeId];
  }
  
  void Debugger::MakeSignatureNames(const rdcarray<SPIRVInterfaceAccess> &sigList,
                                    rdcarray<rdcstr> &sigNames)
  {
    for(const SPIRVInterfaceAccess &sig : sigList)
    {
      rdcstr name = GetRawName(sig.ID);
  
      const DataType *type = &dataTypes[idTypes[sig.ID]];
  
      RDCASSERT(type->type == DataType::PointerType);
      type = &dataTypes[type->InnerType()];
  
      for(uint32_t chain : sig.accessChain)
      {
        if(type->type == DataType::ArrayType)
        {
          name += StringFormat::Fmt("[%u]", chain);
          type = &dataTypes[type->InnerType()];
        }
        else if(type->type == DataType::StructType)
        {
          if(!type->children[chain].name.empty())
            name += "." + type->children[chain].name;
          else
            name += StringFormat::Fmt("._child%u", chain);
          type = &dataTypes[type->children[chain].type];
        }
        else if(type->type == DataType::MatrixType)
        {
          name += StringFormat::Fmt(".col%u", chain);
          type = &dataTypes[type->InnerType()];
        }
        else
        {
          RDCERR("Got access chain with non-aggregate type in interface.");
          break;
        }
      }
  
      sigNames.push_back(name);
    }
  }
  
  // this function is implemented here to keep it next to the code we might need to update, even
  // though it's checked at reflection time.
  void Reflector::CheckDebuggable(bool &debuggable, rdcstr &debugStatus) const
  {
    debuggable = true;
    debugStatus.clear();
  
    if(m_MajorVersion > 1 || m_MinorVersion > 6)
    {
      debugStatus +=
          StringFormat::Fmt("Unsupported SPIR-V version %u.%u\n", m_MajorVersion, m_MinorVersion);
      debuggable = false;
    }
  
    // this list is sorted in order of the SPIR-V registry.
    const rdcstr whitelist[] = {
        "SPV_KHR_shader_draw_parameters",
        "SPV_KHR_16bit_storage",
        "SPV_KHR_device_group",
        "SPV_KHR_multiview",
        "SPV_KHR_storage_buffer_storage_class",
        "SPV_KHR_post_depth_coverage",
        "SPV_KHR_shader_atomic_counter_ops",
        "SPV_EXT_shader_stencil_export",
        "SPV_EXT_shader_viewport_index_layer",
        "SPV_EXT_fragment_fully_covered",
        "SPV_GOOGLE_decorate_string",
        "SPV_GOOGLE_hlsl_functionality1",
        "SPV_EXT_descriptor_indexing",
        "SPV_KHR_8bit_storage",
        "SPV_KHR_vulkan_memory_model",
        "SPV_EXT_fragment_invocation_density",
        "SPV_KHR_no_integer_wrap_decoration",
        "SPV_KHR_float_controls",
        "SPV_KHR_shader_clock",
        "SPV_EXT_demote_to_helper_invocation",
        "SPV_KHR_non_semantic_info",
        "SPV_EXT_shader_atomic_float_add",
        "SPV_KHR_terminate_invocation",
        "SPV_EXT_shader_image_int64",
        "SPV_GOOGLE_user_type",
    };
  
    // whitelist supported extensions
    for(const rdcstr &ext : extensions)
    {
      bool supported = false;
      for(const rdcstr &check : whitelist)
      {
        if(ext == check)
        {
          supported = true;
          break;
        }
      }
  
      if(supported)
        continue;
  
      debuggable = false;
      debugStatus += StringFormat::Fmt("Unsupported SPIR-V extension %s\n", ext.c_str());
    }
  
    for(Capability c : capabilities)
    {
      bool supported = false;
      switch(c)
      {
        case Capability::Matrix:
        case Capability::Shader:
        // we "support" geometry/tessellation in case the module contains other entry points, but
        // these can't be debugged right now.
        case Capability::Geometry:
        case Capability::Tessellation:
        case Capability::AtomicStorage:
        case Capability::TessellationPointSize:
        case Capability::GeometryPointSize:
        case Capability::ImageGatherExtended:
        case Capability::StorageImageMultisample:
        case Capability::UniformBufferArrayDynamicIndexing:
        case Capability::SampledImageArrayDynamicIndexing:
        case Capability::StorageBufferArrayDynamicIndexing:
        case Capability::StorageImageArrayDynamicIndexing:
        case Capability::ClipDistance:
        case Capability::CullDistance:
        case Capability::ImageCubeArray:
        case Capability::SampleRateShading:
        case Capability::ImageRect:
        case Capability::SampledRect:
        case Capability::InputAttachment:
        case Capability::MinLod:
        case Capability::Sampled1D:
        case Capability::Image1D:
        case Capability::SampledCubeArray:
        case Capability::SampledBuffer:
        case Capability::ImageBuffer:
        case Capability::ImageMSArray:
        case Capability::StorageImageExtendedFormats:
        case Capability::ImageQuery:
        case Capability::DerivativeControl:
        case Capability::TransformFeedback:
        case Capability::GeometryStreams:
        case Capability::StorageImageReadWithoutFormat:
        case Capability::StorageImageWriteWithoutFormat:
        case Capability::MultiViewport:
        case Capability::ShaderLayer:
        case Capability::ShaderViewportIndex:
        case Capability::DrawParameters:
        case Capability::DeviceGroup:
        case Capability::MultiView:
        case Capability::AtomicStorageOps:
        case Capability::SampleMaskPostDepthCoverage:
        case Capability::StencilExportEXT:
        case Capability::ShaderClockKHR:
        case Capability::ShaderViewportIndexLayerEXT:
        case Capability::FragmentFullyCoveredEXT:
        case Capability::FragmentDensityEXT:
        case Capability::ShaderNonUniform:
        case Capability::RuntimeDescriptorArray:
        case Capability::InputAttachmentArrayDynamicIndexing:
        case Capability::UniformTexelBufferArrayDynamicIndexing:
        case Capability::StorageTexelBufferArrayDynamicIndexing:
        case Capability::UniformBufferArrayNonUniformIndexing:
        case Capability::SampledImageArrayNonUniformIndexing:
        case Capability::StorageBufferArrayNonUniformIndexing:
        case Capability::StorageImageArrayNonUniformIndexing:
        case Capability::InputAttachmentArrayNonUniformIndexing:
        case Capability::UniformTexelBufferArrayNonUniformIndexing:
        case Capability::StorageTexelBufferArrayNonUniformIndexing:
        case Capability::VulkanMemoryModel:
        case Capability::VulkanMemoryModelDeviceScope:
        case Capability::DemoteToHelperInvocationEXT:
        case Capability::AtomicFloat32AddEXT:
        case Capability::AtomicFloat32MinMaxEXT:
        case Capability::AtomicFloat16AddEXT:
        case Capability::AtomicFloat16MinMaxEXT:
        case Capability::AtomicFloat64AddEXT:
        case Capability::AtomicFloat64MinMaxEXT:
        case Capability::Float16Buffer:
        case Capability::Float16:
        case Capability::Int64:
        case Capability::Int16:
        case Capability::Int8:
        case Capability::StorageBuffer16BitAccess:
        case Capability::UniformAndStorageBuffer16BitAccess:
        case Capability::StoragePushConstant16:
        case Capability::StorageInputOutput16:
        case Capability::StorageBuffer8BitAccess:
        case Capability::UniformAndStorageBuffer8BitAccess:
        case Capability::StoragePushConstant8:
        case Capability::Float64:
        case Capability::Int64Atomics:
        case Capability::Int64ImageEXT:
        case Capability::ExpectAssumeKHR:
        case Capability::BitInstructions:
        {
          supported = true;
          break;
        }
  
        // we plan to support these but needs additional testing/proving
  
        // physical pointers
        case Capability::PhysicalStorageBufferAddresses:
  
        // MSAA custom interpolation
        case Capability::InterpolationFunction:
  
        // variable pointers
        case Capability::VariablePointersStorageBuffer:
        case Capability::VariablePointers:
  
        // float controls
        case Capability::DenormPreserve:
        case Capability::DenormFlushToZero:
        case Capability::SignedZeroInfNanPreserve:
        case Capability::RoundingModeRTE:
        case Capability::RoundingModeRTZ:
  
        // group instructions
        case Capability::Groups:
        case Capability::GroupNonUniform:
        case Capability::GroupNonUniformVote:
        case Capability::GroupNonUniformArithmetic:
        case Capability::GroupNonUniformBallot:
        case Capability::GroupNonUniformShuffle:
        case Capability::GroupNonUniformShuffleRelative:
        case Capability::GroupNonUniformClustered:
        case Capability::GroupNonUniformQuad:
        case Capability::SubgroupBallotKHR:
        case Capability::SubgroupVoteKHR:
  
        // workgroup layout:
        case Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR:
        case Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR:
        case Capability::WorkgroupMemoryExplicitLayoutKHR:
  
        // sparse operations
        case Capability::SparseResidency:
  
        // fragment interlock
        case Capability::FragmentShaderSampleInterlockEXT:
        case Capability::FragmentShaderShadingRateInterlockEXT:
        case Capability::FragmentShaderPixelInterlockEXT:
        {
          supported = false;
          break;
        }
  
        // fragment shading rate
        case Capability::FragmentShadingRateKHR:
        {
          supported = false;
          break;
        }
  
        // integer dot product
        case Capability::DotProductKHR:
        case Capability::DotProductInput4x8BitKHR:
        case Capability::DotProductInput4x8BitPackedKHR:
        case Capability::DotProductInputAllKHR:
        {
          supported = false;
          break;
        }
  
        // raytracing
        case Capability::RayQueryKHR:
        case Capability::RayTraversalPrimitiveCullingKHR:
        case Capability::RayTracingKHR:
        {
          supported = false;
          break;
        }
  
        // no plans to support these - mostly Kernel/OpenCL related or vendor extensions
        case Capability::Addresses:
        case Capability::Linkage:
        case Capability::Kernel:
        case Capability::Vector16:
        case Capability::ImageBasic:
        case Capability::ImageReadWrite:
        case Capability::ImageMipmap:
        case Capability::Pipes:
        case Capability::DeviceEnqueue:
        case Capability::LiteralSampler:
        case Capability::GenericPointer:
        case Capability::SubgroupDispatch:
        case Capability::NamedBarrier:
        case Capability::PipeStorage:
        case Capability::Float16ImageAMD:
        case Capability::ImageGatherBiasLodAMD:
        case Capability::FragmentMaskAMD:
        case Capability::ImageReadWriteLodAMD:
        case Capability::SampleMaskOverrideCoverageNV:
        case Capability::GeometryShaderPassthroughNV:
        case Capability::ShaderViewportMaskNV:
        case Capability::ShaderStereoViewNV:
        case Capability::PerViewAttributesNV:
        case Capability::MeshShadingNV:
        case Capability::FragmentBarycentricNV:
        case Capability::ImageFootprintNV:
        case Capability::ComputeDerivativeGroupQuadsNV:
        case Capability::GroupNonUniformPartitionedNV:
        case Capability::RayTracingNV:
        case Capability::ComputeDerivativeGroupLinearNV:
        case Capability::CooperativeMatrixNV:
        case Capability::ShaderSMBuiltinsNV:
        case Capability::SubgroupShuffleINTEL:
        case Capability::SubgroupBufferBlockIOINTEL:
        case Capability::SubgroupImageBlockIOINTEL:
        case Capability::SubgroupImageMediaBlockIOINTEL:
        case Capability::IntegerFunctions2INTEL:
        case Capability::SubgroupAvcMotionEstimationINTEL:
        case Capability::SubgroupAvcMotionEstimationIntraINTEL:
        case Capability::SubgroupAvcMotionEstimationChromaINTEL:
        case Capability::FunctionPointersINTEL:
        case Capability::IndirectReferencesINTEL:
        case Capability::FPGAKernelAttributesINTEL:
        case Capability::FPGALoopControlsINTEL:
        case Capability::FPGAMemoryAttributesINTEL:
        case Capability::FPGARegINTEL:
        case Capability::UnstructuredLoopControlsINTEL:
        case Capability::KernelAttributesINTEL:
        case Capability::BlockingPipesINTEL:
        case Capability::OptNoneINTEL:
        case Capability::RayTracingMotionBlurNV:
        case Capability::RoundToInfinityINTEL:
        case Capability::FloatingPointModeINTEL:
        case Capability::AsmINTEL:
        case Capability::VectorAnyINTEL:
        case Capability::VectorComputeINTEL:
        case Capability::VariableLengthArrayINTEL:
        case Capability::FunctionFloatControlINTEL:
        case Capability::FPFastMathModeINTEL:
        case Capability::ArbitraryPrecisionFixedPointINTEL:
        case Capability::ArbitraryPrecisionFloatingPointINTEL:
        case Capability::ArbitraryPrecisionIntegersINTEL:
        case Capability::FPGAMemoryAccessesINTEL:
        case Capability::FPGAClusterAttributesINTEL:
        case Capability::LoopFuseINTEL:
        case Capability::FPGABufferLocationINTEL:
        case Capability::USMStorageClassesINTEL:
        case Capability::IOPipesINTEL:
        case Capability::LongConstantCompositeINTEL:
        case Capability::DebugInfoModuleINTEL:
        case Capability::Max:
        case Capability::Invalid:
        {
          supported = false;
          break;
        }
  
        // deprecated provisional raytracing
        case Capability::RayQueryProvisionalKHR:
        case Capability::RayTracingProvisionalKHR:
        {
          supported = false;
          break;
        }
      }
  
      if(!supported)
      {
        debuggable = false;
        debugStatus += StringFormat::Fmt("Unsupported capability '%s'\n", ToStr(c).c_str());
      }
    }
  
    for(auto it = extSets.begin(); it != extSets.end(); it++)
    {
      Id id = it->first;
      const rdcstr &setname = it->second;
  
      if(setname == "GLSL.std.450" || setname.beginsWith("NonSemantic."))
        continue;
  
      debuggable = false;
      debugStatus += StringFormat::Fmt("Unsupported extended instruction set: '%s'\n", setname.c_str());
    }
  
    debugStatus.trim();
  }
  
  ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage shaderStage,
                                         const rdcstr &entryPoint,
                                         const rdcarray<SpecConstant> &specInfo,
                                         const std::map<size_t, uint32_t> &instructionLines,
                                         const SPIRVPatchData &patchData, uint32_t activeIndex)
  {
    Id entryId = entryLookup[ShaderEntryPoint(entryPoint, shaderStage)];
  
    if(entryId == Id())
    {
      RDCERR("Invalid entry point '%s'", entryPoint.c_str());
      return new ShaderDebugTrace;
    }
  
    rdcarray<Id> entryInterface;
  
    for(const EntryPoint &e : entries)
    {
      if(e.id == entryId)
      {
        entryInterface = e.usedIds;
        break;
      }
    }
  
    global.clock = uint64_t(time(NULL)) << 32;
  
    for(auto it = extSets.begin(); it != extSets.end(); it++)
    {
      Id id = it->first;
      const rdcstr &setname = it->second;
  
      if(setname == "GLSL.std.450")
      {
        ExtInstDispatcher extinst;
  
        extinst.name = setname;
  
        ConfigureGLSLStd450(extinst);
  
        global.extInsts[id] = extinst;
      }
      else if(setname.beginsWith("NonSemantic."))
      {
        ExtInstDispatcher extinst;
  
        extinst.name = setname;
  
        extinst.nonsemantic = true;
  
        global.extInsts[id] = extinst;
      }
    }
  
    ShaderDebugTrace *ret = new ShaderDebugTrace;
    ret->debugger = this;
    ret->stage = shaderStage;
    activeLaneIndex = activeIndex;
    stage = shaderStage;
    apiWrapper = api;
  
    uint32_t workgroupSize = shaderStage == ShaderStage::Pixel ? 4 : 1;
    for(uint32_t i = 0; i < workgroupSize; i++)
      workgroup.push_back(ThreadState(i, *this, global));
  
    ThreadState &active = GetActiveLane();
  
    active.nextInstruction = instructionOffsets.indexOf(functions[entryId].begin);
  
    active.ids.resize(idOffsets.size());
  
    // evaluate all constants
    for(auto it = constants.begin(); it != constants.end(); it++)
      active.ids[it->first] = EvaluateConstant(it->first, specInfo);
  
    rdcarray<rdcstr> inputSigNames, outputSigNames;
  
    MakeSignatureNames(patchData.inputs, inputSigNames);
    MakeSignatureNames(patchData.outputs, outputSigNames);
  
    struct PointerId
    {
      PointerId(Id i, rdcarray<ShaderVariable> GlobalState::*th, rdcarray<ShaderVariable> &storage)
          : id(i), globalStorage(th), index(storage.size() - 1)
      {
      }
      PointerId(Id i, rdcarray<ShaderVariable> ThreadState::*th, rdcarray<ShaderVariable> &storage)
          : id(i), threadStorage(th), index(storage.size() - 1)
      {
      }
  
      void Set(Debugger &d, const GlobalState &global, ThreadState &lane) const
      {
        if(globalStorage)
          lane.ids[id] = d.MakePointerVariable(id, &(global.*globalStorage)[index]);
        else
          lane.ids[id] = d.MakePointerVariable(id, &(lane.*threadStorage)[index]);
      }
  
      Id id;
      rdcarray<ShaderVariable> GlobalState::*globalStorage = NULL;
      rdcarray<ShaderVariable> ThreadState::*threadStorage = NULL;
      size_t index;
    };
  
  #define GLOBAL_POINTER(id, list) PointerId(id, &GlobalState::list, global.list)
  #define THREAD_POINTER(id, list) PointerId(id, &ThreadState::list, active.list)
  
    rdcarray<Id> inputIDs, outputIDs;
    rdcarray<PointerId> pointerIDs;
  
    // allocate storage for globals with opaque storage classes, and prepare to set up pointers to
    // them for the global variables themselves
    for(const Variable &v : globals)
    {
      if(v.storage == StorageClass::Input || v.storage == StorageClass::Output)
      {
        if(!entryInterface.contains(v.id))
          continue;
  
        const bool isInput = (v.storage == StorageClass::Input);
  
        ShaderVariable var;
        var.name = GetRawName(v.id);
  
        rdcstr rawName = var.name;
        rdcstr sourceName = GetHumanName(v.id);
  
        // if we don't have a good human name, generate a better one using the interface information
        // we have
        if(sourceName == var.name)
        {
          if(decorations[v.id].flags & Decorations::HasBuiltIn)
            sourceName = StringFormat::Fmt("_%s", ToStr(decorations[v.id].builtIn).c_str());
          else if(decorations[v.id].flags & Decorations::HasLocation)
            sourceName =
                StringFormat::Fmt("_%s%u", isInput ? "input" : "output", decorations[v.id].location);
          else
            sourceName = StringFormat::Fmt("_sig%u", v.id.value());
        }
  
        const DataType &type = dataTypes[v.type];
  
        // global variables should all be pointers into opaque storage
        RDCASSERT(type.type == DataType::PointerType);
  
        const rdcarray<rdcstr> &sigNames = isInput ? inputSigNames : outputSigNames;
  
        // fill the interface variable
        auto fillInputCallback = [this, isInput, ret, &sigNames, &rawName, &sourceName](
            ShaderVariable &var, const Decorations &curDecorations, const DataType &type,
            uint64_t location, const rdcstr &accessSuffix) {
  
          if(!var.members.empty())
            return;
  
          if(isInput)
          {
            uint32_t component = 0;
            for(const DecorationAndParamData &dec : curDecorations.others)
            {
              if(dec.value == Decoration::Component)
              {
                component = dec.component;
                break;
              }
            }
  
            ShaderBuiltin builtin = ShaderBuiltin::Undefined;
            if(curDecorations.flags & Decorations::HasBuiltIn)
              builtin = MakeShaderBuiltin(stage, curDecorations.builtIn);
  
            this->apiWrapper->FillInputValue(var, builtin, (uint32_t)location, component);
          }
          else
          {
            // make it obvious when uninitialised outputs are written
            memset(&var.value, 0xcc, sizeof(var.value));
          }
  
          if(sourceName != rawName)
          {
            rdcstr debugVarName = rawName + accessSuffix;
  
            SourceVariableMapping sourceVar;
            sourceVar.name = sourceName + accessSuffix;
            sourceVar.offset = (uint32_t)location;
            sourceVar.type = var.type;
            sourceVar.rows = var.rows;
            sourceVar.columns = var.columns;
            sourceVar.signatureIndex = sigNames.indexOf(debugVarName);
  
            for(uint32_t x = 0; x < uint32_t(var.rows) * var.columns; x++)
              sourceVar.variables.push_back(DebugVariableReference(
                  isInput ? DebugVariableType::Input : DebugVariableType::Variable, debugVarName, x));
  
            ret->sourceVars.push_back(sourceVar);
            if(!isInput)
              globalSourceVars.push_back(sourceVar);
          }
        };
  
        WalkVariable<ShaderVariable, true>(decorations[v.id], dataTypes[type.InnerType()], ~0U, var,
                                           rdcstr(), fillInputCallback);
  
        if(isInput)
        {
          // create the opaque storage
          active.inputs.push_back(var);
  
          // then make sure we know which ID to set up for the pointer
          inputIDs.push_back(v.id);
          pointerIDs.push_back(THREAD_POINTER(v.id, inputs));
        }
        else
        {
          active.outputs.push_back(var);
          outputIDs.push_back(v.id);
          liveGlobals.push_back(v.id);
          pointerIDs.push_back(THREAD_POINTER(v.id, outputs));
        }
      }
  
      // pick up uniform globals, which could be cbuffers, and push constants
      else if(v.storage == StorageClass::Uniform || v.storage == StorageClass::StorageBuffer ||
              v.storage == StorageClass::PushConstant)
      {
        ShaderVariable var;
        var.name = GetRawName(v.id);
  
        rdcstr sourceName = GetHumanName(v.id);
  
        const DataType &type = dataTypes[v.type];
  
        // global variables should all be pointers into opaque storage
        RDCASSERT(type.type == DataType::PointerType);
  
        const DataType *innertype = &dataTypes[type.InnerType()];
  
        if(sourceName == var.name)
          sourceName = GetHumanName(innertype->id);
  
        bool isArray = false;
        if(innertype->type == DataType::ArrayType)
        {
          isArray = true;
          innertype = &dataTypes[innertype->InnerType()];
        }
  
        const bool ssbo = (v.storage == StorageClass::StorageBuffer) ||
                          (decorations[innertype->id].flags & Decorations::BufferBlock);
  
        if(innertype->type == DataType::StructType)
        {
          // if we don't have a good human name, generate a better one using the interface information
          // we have
          if(sourceName == var.name)
          {
            if(v.storage == StorageClass::PushConstant)
              sourceName = "_pushconsts";
            else if(ssbo)
              sourceName = StringFormat::Fmt("_buffer_set%u_bind%u", decorations[v.id].set,
                                             decorations[v.id].binding);
            else
              sourceName = StringFormat::Fmt("_cbuffer_set%u_bind%u", decorations[v.id].set,
                                             decorations[v.id].binding);
          }
  
          uint32_t bindset = 0, bind = 0;
          if(v.storage == StorageClass::PushConstant)
          {
            bindset = PushConstantBindSet;
          }
          else
          {
            if(decorations[v.id].flags & Decorations::HasDescriptorSet)
              bindset = decorations[v.id].set;
            if(decorations[v.id].flags & Decorations::HasBinding)
              bind = decorations[v.id].binding;
          }
  
          SourceVariableMapping sourceVar;
          sourceVar.name = sourceName;
          sourceVar.offset = 0;
  
          if(ssbo)
          {
            var.rows = 1;
            var.columns = 1;
            var.type = VarType::ReadWriteResource;
  
            var.SetBinding((int32_t)bindset, (int32_t)bind, 0U);
  
            var.value.u64v[SSBOVariableSlot] = 1;
  
            if(isArray)
              var.value.u64v[ArrayVariableSlot] = 1;
  
            sourceVar.type = VarType::ReadWriteResource;
            sourceVar.rows = 1;
            sourceVar.columns = 1;
            sourceVar.variables.push_back(
                DebugVariableReference(DebugVariableType::ReadWriteResource, var.name));
  
            global.readWriteResources.push_back(var);
            pointerIDs.push_back(GLOBAL_POINTER(v.id, readWriteResources));
          }
          else
          {
            BindpointIndex bindpoint;
  
            // TODO handle arrays
            bindpoint.bindset = (int32_t)bindset;
            bindpoint.bind = (int32_t)bind;
  
            auto cbufferCallback = [this, bindpoint](
                ShaderVariable &var, const Decorations &curDecorations, const DataType &type,
                uint64_t offset, const rdcstr &) {
  
              if(!var.members.empty())
                return;
  
              // non-matrix case is simple, just read the size of the variable
              if(var.rows == 1)
              {
                this->apiWrapper->ReadBufferValue(bindpoint, offset, VarByteSize(var),
                                                  var.value.u8v.data());
              }
              else
              {
                // matrix case is more complicated. Either read column by column or row by row
                // depending on majorness
                uint32_t matrixStride = curDecorations.matrixStride;
  
                if(!(curDecorations.flags & Decorations::HasMatrixStride))
                {
                  RDCWARN("Matrix without matrix stride - assuming legacy vec4 packed");
                  matrixStride = 16;
                }
  
                if(curDecorations.flags & Decorations::ColMajor)
                {
                  ShaderVariable tmp;
                  tmp.type = var.type;
  
                  uint32_t colSize = VarTypeByteSize(var.type) * var.rows;
                  for(uint32_t c = 0; c < var.columns; c++)
                  {
                    // read the column
                    this->apiWrapper->ReadBufferValue(bindpoint, offset + c * matrixStride, colSize,
                                                      VarElemPointer(tmp, 0));
  
                    // now write it into the appropiate elements in the destination ShaderValue
                    for(uint32_t r = 0; r < var.rows; r++)
                      copyComp(var, r * var.columns + c, tmp, r);
                  }
                }
                else
                {
                  // row major is easier, read row-by-row directly into the output variable
                  uint32_t rowSize = VarTypeByteSize(var.type) * var.columns;
                  for(uint32_t r = 0; r < var.rows; r++)
                  {
                    // read the column into the destination ShaderValue, which is tightly packed with
                    // rows
                    this->apiWrapper->ReadBufferValue(bindpoint, offset + r * matrixStride, rowSize,
                                                      VarElemPointer(var, r * var.columns));
                  }
                }
              }
            };
  
            WalkVariable<ShaderVariable, true>(decorations[v.id], *innertype, 0U, var, rdcstr(),
                                               cbufferCallback);
  
            if(isArray)
              RDCERR("Uniform buffer arrays not supported yet");
  
            sourceVar.type = VarType::ConstantBlock;
            sourceVar.rows = 1;
            sourceVar.columns = 1;
            sourceVar.variables.push_back(DebugVariableReference(DebugVariableType::Constant, var.name));
  
            global.constantBlocks.push_back(var);
            pointerIDs.push_back(GLOBAL_POINTER(v.id, constantBlocks));
          }
  
          ret->sourceVars.push_back(sourceVar);
        }
        else
        {
          RDCERR("Unhandled type of uniform: %u", innertype->type);
        }
      }
      else if(v.storage == StorageClass::UniformConstant)
      {
        // only images/samplers are allowed to be in UniformConstant
        ShaderVariable var;
        var.rows = 1;
        var.columns = 1;
        var.name = GetRawName(v.id);
  
        rdcstr sourceName = GetHumanName(v.id);
  
        const DataType &type = dataTypes[v.type];
  
        // global variables should all be pointers into opaque storage
        RDCASSERT(type.type == DataType::PointerType);
  
        const DataType *innertype = &dataTypes[type.InnerType()];
  
        // if we don't have a good human name, generate a better one using the interface information
        // we have
        if(sourceName == var.name)
        {
          rdcstr innerName;
          if(innertype->type == DataType::SamplerType)
            innerName = "sampler";
          else if(innertype->type == DataType::SampledImageType)
            innerName = "sampledImage";
          else if(innertype->type == DataType::ImageType)
            innerName = "image";
          sourceName = StringFormat::Fmt("_%s_set%u_bind%u", innerName.c_str(), decorations[v.id].set,
                                         decorations[v.id].binding);
        }
  
        DebugVariableType debugType = DebugVariableType::ReadOnlyResource;
  
        uint32_t set = 0, bind = 0;
        if(decorations[v.id].flags & Decorations::HasDescriptorSet)
          set = decorations[v.id].set;
        if(decorations[v.id].flags & Decorations::HasBinding)
          bind = decorations[v.id].binding;
  
        var.SetBinding((int32_t)set, (int32_t)bind, 0U);
  
        if(innertype->type == DataType::ArrayType)
        {
          var.value.u64v[ArrayVariableSlot] = 1;
          innertype = &dataTypes[innertype->InnerType()];
        }
  
        if(innertype->type == DataType::SamplerType)
        {
          var.type = VarType::Sampler;
          debugType = DebugVariableType::Sampler;
  
          global.samplers.push_back(var);
          pointerIDs.push_back(GLOBAL_POINTER(v.id, samplers));
        }
        else if(innertype->type == DataType::SampledImageType || innertype->type == DataType::ImageType)
        {
          var.type = VarType::ReadOnlyResource;
          debugType = DebugVariableType::ReadOnlyResource;
  
          // store the texture type here, since the image may be copied around and combined with a
          // sampler, so accessing the original type might be non-trivial at point of access
          uint32_t texType = DebugAPIWrapper::Float_Texture;
  
          Id imgid = type.InnerType();
  
          if(innertype->type == DataType::SampledImageType)
            imgid = sampledImageTypes[imgid].baseId;
  
          if(imageTypes[imgid].dim == Dim::Buffer)
            texType |= DebugAPIWrapper::Buffer_Texture;
  
          if(imageTypes[imgid].dim == Dim::SubpassData)
            texType |= DebugAPIWrapper::Subpass_Texture;
  
          if(imageTypes[imgid].retType.type == Op::TypeInt)
          {
            if(imageTypes[imgid].retType.signedness)
              texType |= DebugAPIWrapper::SInt_Texture;
            else
              texType |= DebugAPIWrapper::UInt_Texture;
          }
  
          var.value.u64v[TextureTypeVariableSlot] = texType;
  
          if(imageTypes[imgid].sampled == 2 && imageTypes[imgid].dim != Dim::SubpassData)
          {
            var.type = VarType::ReadWriteResource;
            debugType = DebugVariableType::ReadWriteResource;
  
            global.readWriteResources.push_back(var);
            pointerIDs.push_back(GLOBAL_POINTER(v.id, readWriteResources));
          }
          else
          {
            global.readOnlyResources.push_back(var);
            pointerIDs.push_back(GLOBAL_POINTER(v.id, readOnlyResources));
          }
        }
        else
        {
          RDCERR("Unhandled type of uniform: %u", innertype->type);
        }
  
        SourceVariableMapping sourceVar;
        sourceVar.name = sourceName;
        sourceVar.type = var.type;
        sourceVar.rows = 1;
        sourceVar.columns = 1;
        sourceVar.offset = 0;
        sourceVar.variables.push_back(DebugVariableReference(debugType, var.name));
  
        ret->sourceVars.push_back(sourceVar);
      }
      else if(v.storage == StorageClass::Private || v.storage == StorageClass::Workgroup)
      {
        // private variables are allocated as globals. Similar to outputs
        ShaderVariable var;
        var.name = GetRawName(v.id);
  
        rdcstr sourceName = GetHumanName(v.id);
  
        const DataType &type = dataTypes[v.type];
  
        // global variables should all be pointers into opaque storage
        RDCASSERT(type.type == DataType::PointerType);
  
        auto uninitialisedCallback = [](ShaderVariable &var, const Decorations &, const DataType &,
                                        uint64_t, const rdcstr &) {
          if(!var.members.empty())
            return;
  
          memset(&var.value, 0xcc, sizeof(var.value));
        };
  
        WalkVariable<ShaderVariable, true>(decorations[v.id], dataTypes[type.InnerType()], ~0U, var,
                                           rdcstr(), uninitialisedCallback);
  
        if(v.initializer != Id())
          AssignValue(var, active.ids[v.initializer]);
  
        if(v.storage == StorageClass::Private)
        {
          active.privates.push_back(var);
          pointerIDs.push_back(THREAD_POINTER(v.id, privates));
        }
        else if(v.storage == StorageClass::Workgroup)
        {
          global.workgroups.push_back(var);
          pointerIDs.push_back(GLOBAL_POINTER(v.id, workgroups));
        }
  
        liveGlobals.push_back(v.id);
  
        if(sourceName != var.name)
        {
          SourceVariableMapping sourceVar;
          sourceVar.name = sourceName;
          sourceVar.type = var.type;
          sourceVar.rows = RDCMAX(1U, (uint32_t)var.rows);
          sourceVar.columns = RDCMAX(1U, (uint32_t)var.columns);
          sourceVar.offset = 0;
          for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
            sourceVar.variables.push_back(
                DebugVariableReference(DebugVariableType::Variable, var.name, x));
  
          globalSourceVars.push_back(sourceVar);
        }
      }
      else
      {
        RDCERR("Unhandled type of global variable: %s", ToStr(v.storage).c_str());
      }
    }
  
    std::sort(liveGlobals.begin(), liveGlobals.end());
  
    for(uint32_t i = 0; i < workgroupSize; i++)
    {
      ThreadState &lane = workgroup[i];
      if(i != activeLaneIndex)
      {
        lane.nextInstruction = active.nextInstruction;
        lane.inputs = active.inputs;
        lane.outputs = active.outputs;
        lane.privates = active.privates;
        lane.ids = active.ids;
        // mark as inactive/helper lane
        lane.helperInvocation = true;
      }
  
      // now that the globals are allocated and their storage won't move, we can take pointers to them
      for(const PointerId &p : pointerIDs)
        p.Set(*this, global, lane);
    }
  
    ret->lineInfo.resize(instructionOffsets.size());
    for(size_t i = 0; i < instructionOffsets.size(); i++)
    {
      ret->lineInfo[i] = m_LineColInfo[instructionOffsets[i]];
  
      {
        auto it = instructionLines.find(instructionOffsets[i]);
        if(it != instructionLines.end())
          ret->lineInfo[i].disassemblyLine = it->second;
        else
          ret->lineInfo[i].disassemblyLine = 0;
      }
    }
  
    ret->constantBlocks = global.constantBlocks;
    ret->readOnlyResources = global.readOnlyResources;
    ret->readWriteResources = global.readWriteResources;
    ret->samplers = global.samplers;
    ret->inputs = active.inputs;
  
    if(stage == ShaderStage::Pixel)
    {
      // apply derivatives to generate the correct inputs for the quad neighbours
      for(uint32_t q = 0; q < workgroupSize; q++)
      {
        if(q == activeLaneIndex)
          continue;
  
        for(size_t i = 0; i < inputIDs.size(); i++)
        {
          Id id = inputIDs[i];
  
          const DataType &type = dataTypes[idTypes[id]];
  
          // global variables should all be pointers into opaque storage
          RDCASSERT(type.type == DataType::PointerType);
  
          const DataType &innertype = dataTypes[type.InnerType()];
  
          auto derivCallback = [this, q](ShaderVariable &var, const Decorations &dec,
                                         const DataType &type, uint64_t location, const rdcstr &) {
            if(!var.members.empty())
              return;
  
            ApplyDerivatives(q, dec, (uint32_t)location, type, var);
          };
  
          WalkVariable<ShaderVariable, false>(decorations[id], innertype, ~0U, workgroup[q].inputs[i],
                                              rdcstr(), derivCallback);
        }
      }
    }
  
    return ret;
  }
  
  rdcarray<ShaderDebugState> Debugger::ContinueDebug()
  {
    ThreadState &active = GetActiveLane();
  
    rdcarray<ShaderDebugState> ret;
  
    // initialise the first ShaderDebugState if we haven't stepped yet
    if(steps == 0)
    {
      ShaderDebugState initial;
  
      // we should be sitting at the entry point function prologue, step forward into the first block
      // and past any function-local variable declarations
      for(size_t lane = 0; lane < workgroup.size(); lane++)
      {
        ThreadState &thread = workgroup[lane];
  
        if(lane == activeLaneIndex)
        {
          thread.EnterEntryPoint(&initial);
          thread.FillCallstack(initial);
          initial.nextInstruction = thread.nextInstruction;
          initial.sourceVars = thread.sourceVars;
        }
        else
        {
          thread.EnterEntryPoint(NULL);
        }
      }
  
      // globals won't be filled out by entering the entry point, ensure their change is registered.
      for(const Id &v : liveGlobals)
        initial.changes.push_back({ShaderVariable(), GetPointerValue(active.ids[v])});
  
      ret.push_back(initial);
  
      steps++;
    }
  
    // if we've finished, return an empty set to signify that
    if(active.Finished())
      return ret;
  
    rdcarray<bool> activeMask;
  
    // continue stepping until we have 100 target steps completed in a chunk. This may involve doing
    // more steps if our target thread is inactive
    for(int stepEnd = steps + 100; steps < stepEnd;)
    {
      global.clock++;
  
      if(active.Finished())
        break;
  
      // calculate the current mask of which threads are active
      CalcActiveMask(activeMask);
  
      // step all active members of the workgroup
      for(size_t lane = 0; lane < workgroup.size(); lane++)
      {
        ThreadState &thread = workgroup[lane];
  
        if(activeMask[lane])
        {
          if(thread.nextInstruction >= instructionOffsets.size())
          {
            if(lane == activeLaneIndex)
              ret.push_back(ShaderDebugState());
  
            continue;
          }
  
          if(lane == activeLaneIndex)
          {
            ShaderDebugState state;
  
            // see if we're retiring any IDs at this state
            for(size_t l = 0; l < thread.live.size();)
            {
              Id id = thread.live[l];
              if(idDeathOffset[id] < instructionOffsets[thread.nextInstruction])
              {
                thread.live.erase(l);
                ShaderVariableChange change;
                change.before = GetPointerValue(thread.ids[id]);
                state.changes.push_back(change);
  
                rdcstr name = GetRawName(id);
  
                thread.sourceVars.removeIf([name](const SourceVariableMapping &var) {
                  return var.variables[0].name.beginsWith(name);
                });
  
                continue;
              }
  
              l++;
            }
  
            thread.StepNext(&state, workgroup);
            state.stepIndex = steps;
            state.sourceVars = thread.sourceVars;
            thread.FillCallstack(state);
            ret.push_back(state);
  
            steps++;
          }
          else
          {
            thread.StepNext(NULL, workgroup);
          }
        }
      }
    }
  
    return ret;
  }
  
  ShaderVariable Debugger::MakePointerVariable(Id id, const ShaderVariable *v, uint32_t scalar0,
                                               uint32_t scalar1) const
  {
    ShaderVariable var;
    var.rows = var.columns = 1;
    var.type = VarType::GPUPointer;
    var.name = GetRawName(id);
    var.value.u64v[PointerVariableSlot] = (uint64_t)(uintptr_t)v;
    var.value.u64v[Scalar0VariableSlot] = scalar0;
    var.value.u64v[Scalar1VariableSlot] = scalar1;
    var.value.u64v[BaseIdVariableSlot] = id.value();
    return var;
  }
  
  ShaderVariable Debugger::MakeCompositePointer(const ShaderVariable &base, Id id,
                                                rdcarray<uint32_t> &indices)
  {
    const ShaderVariable *leaf = &base;
  
    // if the base is a plain value, we just start walking down the chain. If the base is a pointer
    // though, we want to step down the chain in the underlying storage, so dereference first.
    if(base.type == VarType::GPUPointer)
      leaf = (const ShaderVariable *)(uintptr_t)base.value.u64v[PointerVariableSlot];
  
    bool isArray = false;
  
    if((leaf->type == VarType::ReadWriteResource || leaf->type == VarType::ReadOnlyResource ||
        leaf->type == VarType::Sampler) &&
       leaf->value.u64v[ArrayVariableSlot])
    {
      isArray = true;
    }
  
    if(leaf->type == VarType::ReadWriteResource && leaf->value.u64v[SSBOVariableSlot])
    {
      ShaderVariable ret = MakePointerVariable(id, leaf);
  
      uint64_t byteOffset = base.value.u64v[BufferPointerByteOffsetVariableSlot];
      ret.value.u64v[MajorStrideVariableSlot] = base.value.u64v[MajorStrideVariableSlot];
  
      const DataType *type = &dataTypes[idTypes[id]];
  
      RDCASSERT(type->type == DataType::PointerType);
      type = &dataTypes[type->InnerType()];
  
      rdcspv::Id typeId =
          rdcspv::Id::fromWord(uint32_t(base.value.u64v[BufferPointerTypeIdVariableSlot]));
  
      if(typeId != rdcspv::Id())
        type = &dataTypes[typeId];
  
      // first walk any aggregate types
      size_t i = 0;
  
      // if it's an array, consume the array index first
      if(isArray)
      {
        ret.value.u64v[ArrayVariableSlot] = indices[i++];
        type = &dataTypes[type->InnerType()];
      }
  
      Decorations curDecorations = decorations[type->id];
  
      while(i < indices.size() &&
            (type->type == DataType::ArrayType || type->type == DataType::StructType))
      {
        if(type->type == DataType::ArrayType)
        {
          // look up the array stride
          const Decorations &dec = decorations[type->id];
          RDCASSERT(dec.flags & Decorations::HasArrayStride);
  
          // offset increases by index * arrayStride
          byteOffset += indices[i] * dec.arrayStride;
  
          // new type is the inner type
          type = &dataTypes[type->InnerType()];
        }
        else
        {
          // otherwise it's a struct member
          const DataType::Child &child = type->children[indices[i]];
  
          // offset increases by member offset
          RDCASSERT(child.decorations.flags & Decorations::HasOffset);
          byteOffset += child.decorations.offset;
  
          // new type is the child type
          type = &dataTypes[child.type];
          curDecorations = child.decorations;
        }
        i++;
      }
  
      if(curDecorations.flags & Decorations::HasMatrixStride)
        ret.value.u64v[MajorStrideVariableSlot] = curDecorations.matrixStride;
  
      if(curDecorations.flags & Decorations::RowMajor)
        ret.value.u64v[MajorStrideVariableSlot] |= 0x80000000U;
  
      size_t remaining = indices.size() - i;
      if(remaining == 2)
      {
        // pointer to a scalar in a matrix. indices[i] is column, indices[i + 1] is row
        RDCASSERT(curDecorations.flags & Decorations::HasMatrixStride);
  
        // type is the resulting scalar (first inner does matrix->colun type, second does column
        // type->scalar type)
        type = &dataTypes[dataTypes[type->InnerType()].InnerType()];
  
        if(curDecorations.flags & Decorations::RowMajor)
        {
          byteOffset +=
              curDecorations.matrixStride * indices[i + 1] + indices[i] * (type->scalar().width / 8);
        }
        else
        {
          byteOffset +=
              curDecorations.matrixStride * indices[i] + indices[i + 1] * (type->scalar().width / 8);
        }
      }
      else if(remaining == 1)
      {
        if(type->type == DataType::VectorType)
        {
          // pointer to a scalar in a vector.
  
          // type is the resulting scalar (first inner does matrix->colun type, second does column
          // type->scalar type)
          type = &dataTypes[type->InnerType()];
  
          byteOffset += indices[i] * (type->scalar().width / 8);
        }
        else
        {
          // pointer to a column in a matrix
          RDCASSERT(curDecorations.flags & Decorations::HasMatrixStride);
  
          // type is the resulting vector
          type = &dataTypes[type->InnerType()];
  
          if(curDecorations.flags & Decorations::RowMajor)
          {
            byteOffset += indices[i] * (type->scalar().width / 8);
          }
          else
          {
            byteOffset += curDecorations.matrixStride * indices[i];
          }
        }
      }
  
      ret.value.u64v[BufferPointerTypeIdVariableSlot] = type->id.value();
      ret.value.u64v[BufferPointerByteOffsetVariableSlot] = byteOffset;
  
      return ret;
    }
  
    // first walk any struct member/array indices
    size_t i = 0;
    if(isArray)
      i++;
    while(i < indices.size() && !leaf->members.empty())
    {
      uint32_t idx = indices[i++];
      if(idx >= leaf->members.size())
      {
        apiWrapper->AddDebugMessage(
            MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
            StringFormat::Fmt("Index %u invalid at leaf %s. Clamping to %zu", idx, leaf->name.c_str(),
                              leaf->members.size() - 1));
        idx = uint32_t(leaf->members.size() - 1);
      }
      leaf = &leaf->members[idx];
    }
  
    // apply any remaining scalar selectors
    uint32_t scalar0 = ~0U, scalar1 = ~0U;
  
    size_t remaining = indices.size() - i;
  
    if(remaining > 2)
    {
      apiWrapper->AddDebugMessage(
          MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
          StringFormat::Fmt("Too many indices left (%zu) at leaf %s. Ignoring all but last two",
                            remaining, leaf->name.c_str()));
      i = indices.size() - 2;
    }
  
    if(remaining == 2)
    {
      scalar0 = indices[i];
      scalar1 = indices[i + 1];
    }
    else if(remaining == 1)
    {
      scalar0 = indices[i];
    }
  
    ShaderVariable ret = MakePointerVariable(id, leaf, scalar0, scalar1);
  
    if(isArray)
      ret.value.u64v[ArrayVariableSlot] = indices[0];
  
    return ret;
  }
  
  uint64_t Debugger::GetPointerByteOffset(const ShaderVariable &ptr) const
  {
    return ptr.value.u64v[BufferPointerByteOffsetVariableSlot];
  }
  
  DebugAPIWrapper::TextureType Debugger::GetTextureType(const ShaderVariable &img) const
  {
    return (DebugAPIWrapper::TextureType)img.value.u64v[TextureTypeVariableSlot];
  }
  
  ShaderVariable Debugger::GetPointerValue(const ShaderVariable &ptr) const
  {
    // opaque pointers display as their inner value
    if(IsOpaquePointer(ptr))
    {
      const ShaderVariable *inner =
          (const ShaderVariable *)(uintptr_t)ptr.value.u64v[PointerVariableSlot];
      ShaderVariable ret = *inner;
      ret.name = ptr.name;
      // inherit any array index from the pointer
      BindpointIndex bind = ret.GetBinding();
      ret.SetBinding(bind.bindset, bind.bind, (uint32_t)ptr.value.u64v[ArrayVariableSlot]);
      return ret;
    }
  
    // every other kind of pointer displays as its contents
    return ReadFromPointer(ptr);
  }
  
  ShaderVariable Debugger::ReadFromPointer(const ShaderVariable &ptr) const
  {
    if(ptr.type != VarType::GPUPointer)
      return ptr;
  
    const ShaderVariable *inner =
        (const ShaderVariable *)(uintptr_t)ptr.value.u64v[PointerVariableSlot];
  
    ShaderVariable ret;
  
    if(inner->type == VarType::ReadWriteResource && inner->value.u64v[SSBOVariableSlot])
    {
      rdcspv::Id typeId =
          rdcspv::Id::fromWord(uint32_t(ptr.value.u64v[BufferPointerTypeIdVariableSlot]));
      uint64_t byteOffset = ptr.value.u64v[BufferPointerByteOffsetVariableSlot];
  
      BindpointIndex bind = inner->GetBinding();
  
      bind.arrayIndex = (uint32_t)ptr.value.u64v[ArrayVariableSlot];
  
      uint32_t varMatrixStride = (uint32_t)ptr.value.u64v[MajorStrideVariableSlot];
  
      Decorations parentDecorations;
      if((varMatrixStride & 0x80000000U) != 0)
        parentDecorations.flags = Decorations::RowMajor;
      else
        parentDecorations.flags = Decorations::ColMajor;
  
      varMatrixStride &= 0xff;
  
      if(varMatrixStride != 0)
      {
        parentDecorations.flags =
            Decorations::Flags(parentDecorations.flags | Decorations::HasMatrixStride);
        parentDecorations.matrixStride = varMatrixStride;
      }
  
      auto readCallback = [this, bind](ShaderVariable &var, const Decorations &dec,
                                       const DataType &type, uint64_t offset, const rdcstr &) {
  
        // ignore any callbacks we get on the way up for structs/arrays, we don't need it we only read
        // or write at primitive level
        if(!var.members.empty())
          return;
  
        bool rowMajor = (dec.flags & Decorations::RowMajor) != 0;
        uint32_t matrixStride = dec.matrixStride;
  
        if(type.type == DataType::MatrixType)
        {
          RDCASSERT(matrixStride != 0);
  
          if(rowMajor)
          {
            for(uint8_t r = 0; r < var.rows; r++)
            {
              apiWrapper->ReadBufferValue(bind, offset + r * matrixStride,
                                          VarTypeByteSize(var.type) * var.columns,
                                          VarElemPointer(var, r * var.columns));
            }
          }
          else
          {
            ShaderVariable tmp;
            tmp.type = var.type;
  
            // read column-wise
            for(uint8_t c = 0; c < var.columns; c++)
            {
              apiWrapper->ReadBufferValue(bind, offset + c * matrixStride,
                                          VarTypeByteSize(var.type) * var.rows,
                                          VarElemPointer(tmp, c * var.rows));
            }
  
            // transpose into our row major storage
            for(uint8_t r = 0; r < var.rows; r++)
              for(uint8_t c = 0; c < var.columns; c++)
                copyComp(var, r * var.columns + c, tmp, c * var.rows + r);
          }
        }
        else if(type.type == DataType::VectorType)
        {
          if(!rowMajor)
          {
            // we can read a vector at a time if the matrix is column major
            apiWrapper->ReadBufferValue(bind, offset, VarTypeByteSize(var.type) * var.columns,
                                        VarElemPointer(var, 0));
          }
          else
          {
            for(uint8_t c = 0; c < var.columns; c++)
            {
              apiWrapper->ReadBufferValue(bind, offset + c * matrixStride, VarTypeByteSize(var.type),
                                          VarElemPointer(var, VarTypeByteSize(var.type) * c));
            }
          }
        }
        else if(type.type == DataType::ScalarType)
        {
          apiWrapper->ReadBufferValue(bind, offset, VarTypeByteSize(var.type), VarElemPointer(var, 0));
        }
      };
  
      WalkVariable<ShaderVariable, true>(parentDecorations, dataTypes[typeId], byteOffset, ret,
                                         rdcstr(), readCallback);
  
      ret.name = ptr.name;
      return ret;
    }
  
    ret = *inner;
    ret.name = ptr.name;
  
    if(inner->type == VarType::ReadOnlyResource || inner->type == VarType::ReadWriteResource ||
       inner->type == VarType::Sampler)
    {
      BindpointIndex bind = ret.GetBinding();
  
      ret.SetBinding(bind.bindset, bind.bind, (uint32_t)ptr.value.u64v[ArrayVariableSlot]);
    }
  
    // we don't support pointers to scalars since our 'unit' of pointer is a ShaderVariable, so check
    // if we have scalar indices to apply:
    uint32_t scalar0 = (uint32_t)ptr.value.u64v[Scalar0VariableSlot];
    uint32_t scalar1 = (uint32_t)ptr.value.u64v[Scalar1VariableSlot];
  
    ShaderVariable tmp = ret;
  
    if(ret.rows > 1)
    {
      // matrix case
      ClampScalars(apiWrapper, ret, scalar0, scalar1);
  
      if(scalar0 != ~0U && scalar1 != ~0U)
      {
        // two indices - selecting a scalar. scalar0 is the first index in the chain so it chooses
        // column
        copyComp(ret, 0, tmp, scalar1 * ret.columns + scalar0);
  
        // it's a scalar now, even if it was a matrix before
        ret.rows = ret.columns = 1;
      }
      else if(scalar0 != ~0U)
      {
        // one index, selecting a column
        for(uint32_t row = 0; row < ret.rows; row++)
          copyComp(ret, row, tmp, row * ret.columns + scalar0);
  
        // it's a vector now, even if it was a matrix before
        ret.rows = 1;
      }
    }
    else
    {
      ClampScalars(apiWrapper, ret, scalar0);
  
      // vector case, selecting a scalar (if anything)
      if(scalar0 != ~0U)
      {
        copyComp(ret, 0, tmp, scalar0);
  
        // it's a scalar now, even if it was a matrix before
        ret.columns = 1;
      }
    }
  
    return ret;
  }
  
  Id Debugger::GetPointerBaseId(const ShaderVariable &ptr) const
  {
    RDCASSERT(ptr.type == VarType::GPUPointer);
  
    // we stored the base ID so that it's always available regardless of access chains
    return Id::fromWord((uint32_t)ptr.value.u64v[BaseIdVariableSlot]);
  }
  
  bool Debugger::IsOpaquePointer(const ShaderVariable &ptr) const
  {
    if(ptr.type != VarType::GPUPointer)
      return false;
  
    ShaderVariable *inner = (ShaderVariable *)(uintptr_t)ptr.value.u64v[PointerVariableSlot];
    return inner->type == VarType::ReadOnlyResource || inner->type == VarType::Sampler ||
           inner->type == VarType::ReadWriteResource;
  }
  
  bool Debugger::ArePointersAndEqual(const ShaderVariable &a, const ShaderVariable &b) const
  {
    // we can do a pointer comparison by checking the values, since we store all pointer-related
    // data in there
    if(a.type == VarType::GPUPointer && b.type == VarType::GPUPointer)
      return memcmp(&a.value, &b.value, sizeof(ShaderValue)) == 0;
  
    return false;
  }
  
  void Debugger::WriteThroughPointer(const ShaderVariable &ptr, const ShaderVariable &val)
  {
    ShaderVariable *storage = (ShaderVariable *)(uintptr_t)ptr.value.u64v[PointerVariableSlot];
  
    if(storage->type == VarType::ReadWriteResource)
    {
      rdcspv::Id typeId =
          rdcspv::Id::fromWord(uint32_t(ptr.value.u64v[BufferPointerTypeIdVariableSlot]));
      uint64_t byteOffset = ptr.value.u64v[BufferPointerByteOffsetVariableSlot];
  
      BindpointIndex bind = storage->GetBinding();
  
      bind.arrayIndex = (uint32_t)ptr.value.u64v[ArrayVariableSlot];
      uint32_t matrixStride = (uint32_t)ptr.value.u64v[MajorStrideVariableSlot];
      bool rowMajor = (matrixStride & 0x80000000U) != 0;
      matrixStride &= 0xff;
  
      auto writeCallback = [this, bind, matrixStride, rowMajor](
          const ShaderVariable &var, const Decorations &, const DataType &type, uint64_t offset,
          const rdcstr &) {
        if(!var.members.empty())
          return;
  
        if(type.type == DataType::MatrixType)
        {
          RDCASSERT(matrixStride != 0);
  
          if(rowMajor)
          {
            for(uint8_t r = 0; r < var.rows; r++)
            {
              apiWrapper->WriteBufferValue(bind, offset + r * matrixStride,
                                           VarTypeByteSize(var.type) * var.columns,
                                           VarElemPointer(var, r * var.columns));
            }
          }
          else
          {
            ShaderVariable tmp;
            tmp.type = var.type;
  
            // transpose from our row major storage
            for(uint8_t r = 0; r < var.rows; r++)
              for(uint8_t c = 0; c < var.columns; c++)
                copyComp(tmp, c * var.rows + r, var, r * var.columns + c);
  
            // read column-wise
            for(uint8_t c = 0; c < var.columns; c++)
            {
              apiWrapper->WriteBufferValue(bind, offset + c * matrixStride,
                                           VarTypeByteSize(var.type) * var.rows,
                                           VarElemPointer(tmp, c * var.rows));
            }
          }
        }
        else if(type.type == DataType::VectorType)
        {
          if(!rowMajor)
          {
            // we can write a vector at a time if the matrix is column major
            apiWrapper->WriteBufferValue(bind, offset, VarTypeByteSize(var.type) * var.columns,
                                         VarElemPointer(var, 0));
          }
          else
          {
            for(uint8_t c = 0; c < var.columns; c++)
              apiWrapper->WriteBufferValue(bind, offset + c * matrixStride, VarTypeByteSize(var.type),
                                           VarElemPointer(var, c));
          }
        }
        else if(type.type == DataType::ScalarType)
        {
          apiWrapper->WriteBufferValue(bind, offset, VarTypeByteSize(var.type), VarElemPointer(var, 0));
        }
      };
  
      WalkVariable<const ShaderVariable, false>(Decorations(), dataTypes[typeId], byteOffset, val,
                                                rdcstr(), writeCallback);
  
      return;
    }
  
    // we don't support pointers to scalars since our 'unit' of pointer is a ShaderVariable, so check
    // if we have scalar indices to apply:
    uint32_t scalar0 = (uint32_t)ptr.value.u64v[Scalar0VariableSlot];
    uint32_t scalar1 = (uint32_t)ptr.value.u64v[Scalar1VariableSlot];
  
    // in the common case we don't have scalar selectors. In this case just assign the value
    if(scalar0 == ~0U && scalar1 == ~0U)
    {
      AssignValue(*storage, val);
    }
    else
    {
      // otherwise we need to store only the selected part of this pointer. We assume by SPIR-V
      // validity rules that the incoming value matches the pointed value
      if(storage->rows > 1)
      {
        // matrix case
        ClampScalars(apiWrapper, *storage, scalar0, scalar1);
  
        if(scalar0 != ~0U && scalar1 != ~0U)
        {
          // two indices - selecting a scalar. scalar0 is the first index in the chain so it chooses
          // column
          copyComp(*storage, scalar1 * storage->columns + scalar0, val, 0);
        }
        else if(scalar0 != ~0U)
        {
          // one index, selecting a column
          for(uint32_t row = 0; row < storage->rows; row++)
            copyComp(*storage, row * storage->columns + scalar0, val, row);
        }
      }
      else
      {
        ClampScalars(apiWrapper, *storage, scalar0);
  
        // vector case, selecting a scalar
        copyComp(*storage, scalar0, val, 0);
      }
    }
  }
  
  rdcstr Debugger::GetRawName(Id id) const
  {
    return StringFormat::Fmt("_%u", id.value());
  }
  
  rdcstr Debugger::GetHumanName(Id id)
  {
    // see if we have a dynamic name assigned (to disambiguate), if so use that
    auto it = dynamicNames.find(id);
    if(it != dynamicNames.end())
      return it->second;
  
    // otherwise try the string first
    rdcstr name = strings[id];
  
    // if we don't have a string name, we can be sure the id is unambiguous
    if(name.empty())
      return GetRawName(id);
  
    rdcstr basename = name;
  
    // otherwise check to see if it's been used before. If so give it a new name
    int alias = 2;
    while(usedNames.find(name) != usedNames.end())
    {
      name = basename + "@" + ToStr(alias);
      alias++;
    }
  
    usedNames.insert(name);
    dynamicNames[id] = name;
  
    return name;
  }
  
  void Debugger::AddSourceVars(rdcarray<SourceVariableMapping> &sourceVars, const ShaderVariable &var,
                               Id id)
  {
    rdcstr name;
  
    auto it = dynamicNames.find(id);
    if(it != dynamicNames.end())
      name = it->second;
    else
      name = strings[id];
  
    if(!name.empty())
    {
      Id type = idTypes[id];
  
      SourceVariableMapping sourceVar;
  
      sourceVar.name = name;
      sourceVar.offset = 0;
      sourceVar.type = var.type;
      sourceVar.rows = RDCMAX(1U, (uint32_t)var.rows);
      sourceVar.columns = RDCMAX(1U, (uint32_t)var.columns);
      for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
        sourceVar.variables.push_back(DebugVariableReference(DebugVariableType::Variable, var.name, x));
  
      sourceVars.push_back(sourceVar);
    }
  }
  
  void Debugger::CalcActiveMask(rdcarray<bool> &activeMask)
  {
    // one bool per workgroup thread
    activeMask.resize(workgroup.size());
  
    // mark any threads that have finished as inactive, otherwise they're active
    for(size_t i = 0; i < workgroup.size(); i++)
      activeMask[i] = !workgroup[i].Finished();
  
    // only pixel shaders automatically converge workgroups, compute shaders need explicit sync
    if(stage != ShaderStage::Pixel)
      return;
  
    // otherwise we need to make sure that control flow which converges stays in lockstep so that
    // derivatives etc are still valid. While diverged, we don't have to keep threads in lockstep
    // since using derivatives is invalid.
    //
    // We take advantage of SPIR-V's structured control flow. We only ever diverge at a branch
    // instruction, and the preceeding OpLoopMerge/OpSelectionMerge.
    //
    // So the scheme is as follows:
    // * If we haven't diverged and all threads have the same nextInstruction, we're still uniform so
    //   continue in lockstep.
    // * As soon as they differ, we've diverged. Check the last mergeBlock that was specified - we
    //   won't be uniform again until all threads reach that block.
    // * Once we've diverged, any threads which are NOT in the merge block are active, and any threads
    //   which are in it are inactive. This causes them to pause and wait for others to catch up
    //   until the point where all threads are in the merge block at which point we've converged and
    //   can go back to uniformity.
  
    // if we're waiting on a converge block to be reached, we've diverged previously.
    bool wasDiverged = convergeBlock != Id();
  
    // see if we've diverged by starting procesing different next instructions
    bool diverged = false;
    for(size_t i = 1; !diverged && i < workgroup.size(); i++)
      diverged |= (workgroup[0].nextInstruction != workgroup[i].nextInstruction);
  
    if(!wasDiverged && diverged)
    {
      // if we've newly diverged, all workgroups should have the same merge block - the point where we
      // become uniform again.
      convergeBlock = workgroup[0].mergeBlock;
      for(size_t i = 1; i < workgroup.size(); i++)
        RDCASSERT(!activeMask[i] || convergeBlock == workgroup[i].mergeBlock);
    }
  
    if(wasDiverged || diverged)
    {
      // for every thread, turn it off if it's in the converge block
      rdcarray<bool> inConverge;
      inConverge.resize(activeMask.size());
      for(size_t i = 0; i < workgroup.size(); i++)
        inConverge[i] = (!workgroup[i].callstack.empty() &&
                         workgroup[i].callstack.back()->curBlock == convergeBlock);
  
      // is any thread active, but not converged?
      bool anyActiveNotConverged = false;
      for(size_t i = 0; i < workgroup.size(); i++)
        anyActiveNotConverged |= activeMask[i] && !inConverge[i];
  
      if(anyActiveNotConverged)
      {
        // if so, then only non-converged threads are active right now
        for(size_t i = 0; i < workgroup.size(); i++)
          activeMask[i] &= !inConverge[i];
      }
      else
      {
        // otherwise we can leave the active mask as is, forget the convergence point, and allow
        // everything to run as normal
        convergeBlock = Id();
      }
    }
  }
  
  void Debugger::AllocateVariable(Id id, Id typeId, ShaderVariable &outVar)
  {
    // allocs should always be pointers
    RDCASSERT(dataTypes[typeId].type == DataType::PointerType);
  
    auto initCallback = [](ShaderVariable &var, const Decorations &, const DataType &, uint64_t,
                           const rdcstr &) {
      // ignore any callbacks we get on the way up for structs/arrays, we don't need it we only read
      // or write at primitive level
      if(!var.members.empty())
        return;
  
      // make it obvious when uninitialised values are used
      memset(&var.value, 0xcc, sizeof(var.value));
    };
  
    WalkVariable<ShaderVariable, true>(Decorations(), dataTypes[dataTypes[typeId].InnerType()], ~0U,
                                       outVar, rdcstr(), initCallback);
  }
  
  template <typename ShaderVarType, bool allocate>
  uint32_t Debugger::WalkVariable(
      const Decorations &curDecorations, const DataType &type, uint64_t offsetOrLocation,
      ShaderVarType &var, const rdcstr &accessSuffix,
      std::function<void(ShaderVarType &, const Decorations &, const DataType &, uint64_t, const rdcstr &)>
          callback) const
  {
    // if we're walking a const variable we just want to walk it without modification. So outVar
    // is NULL. Otherwise outVar points to the variable itself so we modify it before iterating
    ShaderVariable *outVar = allocate ? pointerIfMutable(var) : NULL;
  
    // the Location decoration should either be on the variable itself (in which case we hit this
    // first thing), or on the first member of a struct. i.e. once we have a location already and
    // we're auto-assigning from there we shouldn't encounter another location decoration somewhere
    // further down the struct chain. This also prevents us from using the same location for every
    // element in an array, since we have the same set of decorations on the array as on the members
    if((curDecorations.flags & Decorations::HasLocation) && offsetOrLocation == ~0U)
      offsetOrLocation = curDecorations.location;
  
    uint32_t numLocations = 0;
  
    switch(type.type)
    {
      case DataType::ScalarType:
      {
        if(outVar)
        {
          outVar->type = type.scalar().Type();
          outVar->rows = 1;
          outVar->columns = 1;
        }
        numLocations = 1;
        break;
      }
      case DataType::VectorType:
      {
        if(outVar)
        {
          outVar->type = type.scalar().Type();
          outVar->rows = 1U;
          outVar->columns = RDCMAX(1U, type.vector().count) & 0xff;
        }
        numLocations = 1U;
        break;
      }
      case DataType::MatrixType:
      {
        if(outVar)
        {
          outVar->type = type.scalar().Type();
          outVar->columns = RDCMAX(1U, type.matrix().count) & 0xff;
          outVar->rows = RDCMAX(1U, type.vector().count) & 0xff;
        }
        numLocations = var.rows;
        break;
      }
      case DataType::StructType:
      {
        for(int32_t i = 0; i < type.children.count(); i++)
        {
          if(outVar)
          {
            outVar->members.push_back(ShaderVariable());
            if(!type.children[i].name.empty())
              outVar->members.back().name = type.children[i].name;
            else
              outVar->members.back().name = StringFormat::Fmt("_child%d", i);
          }
  
          rdcstr childAccess = accessSuffix + "." + var.members.back().name;
  
          const Decorations &childDecorations = type.children[i].decorations;
  
          uint64_t childOffsetOrLocation = offsetOrLocation;
  
          // if the struct is concrete, it must have an offset. Otherwise it's opaque and we're using
          // locations
          if(childDecorations.flags & Decorations::HasOffset)
            childOffsetOrLocation += childDecorations.offset;
          else if(offsetOrLocation != ~0U)
            childOffsetOrLocation += numLocations;
  
          uint32_t childLocations = WalkVariable<ShaderVarType, allocate>(
              childDecorations, dataTypes[type.children[i].type], childOffsetOrLocation,
              var.members[i], childAccess, callback);
  
          numLocations += childLocations;
        }
        break;
      }
      case DataType::ArrayType:
      {
        // array stride is decorated on the type, not the member itself
        const Decorations &typeDecorations = decorations[type.id];
  
        uint32_t childOffset = 0;
  
        uint32_t len = uintComp(GetActiveLane().ids[type.length], 0);
        for(uint32_t i = 0; i < len; i++)
        {
          if(outVar)
          {
            outVar->members.push_back(ShaderVariable());
            outVar->members.back().name = StringFormat::Fmt("[%u]", i);
          }
  
          rdcstr childAccess = accessSuffix + var.members.back().name;
  
          uint32_t childLocations = WalkVariable<ShaderVarType, allocate>(
              curDecorations, dataTypes[type.InnerType()], offsetOrLocation + childOffset,
              var.members[i], childAccess, callback);
  
          numLocations += childLocations;
  
          // as above - either the type is concrete and has an array stride, or else we're using
          // locations
          if(typeDecorations.flags & Decorations::HasArrayStride)
            childOffset += decorations[type.id].arrayStride;
          else if(offsetOrLocation != ~0U)
            childOffset = numLocations;
        }
        break;
      }
      case DataType::PointerType:
      case DataType::ImageType:
      case DataType::SamplerType:
      case DataType::SampledImageType:
      case DataType::UnknownType:
      {
        RDCERR("Unexpected variable type %d", type.type);
        return numLocations;
      }
    }
  
    if(callback)
      callback(var, curDecorations, type, offsetOrLocation, accessSuffix);
  
    // for auto-assigning locations, we return the number of locations
    return numLocations;
  }
  
  template <typename FloatType>
  static void ApplyDerivative(uint32_t activeLaneIndex, uint32_t quadIndex, FloatType *dst,
                              DebugAPIWrapper::DerivativeDeltas &derivs)
  {
    // We make the assumption that the coarse derivatives are generated from (0,0) in the quad, and
    // fine derivatives are generated from the destination index and its neighbours in X and Y.
    // This isn't spec'd but we must assume something and this will hopefully get us closest to
    // reproducing actual results.
    //
    // For debugging, we need members of the quad to be able to generate coarse and fine
    // derivatives.
    //
    // For (0,0) we only need the coarse derivatives to get our neighbours (1,0) and (0,1) which
    // will give us coarse and fine derivatives being identical.
    //
    // For the others we will need to use a combination of coarse and fine derivatives to get the
    // diagonal element in the quad. In the examples below, remember that the quad indices are:
    //
    // +---+---+
    // | 0 | 1 |
    // +---+---+
    // | 2 | 3 |
    // +---+---+
    //
    // And that we have definitions of the derivatives:
    //
    // ddx_coarse = (1,0) - (0,0)
    // ddy_coarse = (0,1) - (0,0)
    //
    // i.e. the same for all members of the quad
    //
    // ddx_fine   = (x,y) - (1-x,y)
    // ddy_fine   = (x,y) - (x,1-y)
    //
    // i.e. the difference to the neighbour of our desired invocation (the one we have the actual
    // inputs for, from gathering above).
    //
    // So e.g. if our thread is at (1,1) destIdx = 3
    //
    // (1,0) = (1,1) - ddx_fine
    // (0,1) = (1,1) - ddy_fine
    // (0,0) = (1,1) - ddy_fine - ddx_coarse
    //
    // and ddy_coarse is unused. For (1,0) destIdx = 1:
    //
    // (1,1) = (1,0) + ddy_fine
    // (0,1) = (1,0) - ddx_coarse + ddy_coarse
    // (0,0) = (1,0) - ddx_coarse
    //
    // and ddx_fine is unused (it's identical to ddx_coarse anyway)
  
    // in the diagrams below * marks the active lane index.
    //
    //   V and ^ == coarse ddy
    //   , and ` == fine ddy
    //   < and > == coarse ddx
    //   { and } == fine ddx
    //
    // We are basically making one or two cardinal direction moves from the starting point
    // (activeLaneIndex) to the end point (quadIndex).
    RDCASSERTNOTEQUAL(activeLaneIndex, quadIndex);
  
  #define ADD_DERIV(src)       \
    for(int i = 0; i < 4; i++) \
      dst[i] += comp<FloatType>(src, i);
  #define SUB_DERIV(src)       \
    for(int i = 0; i < 4; i++) \
      dst[i] -= comp<FloatType>(src, i);
  
    switch(activeLaneIndex)
    {
      case 0:
      {
        // +---+---+
        // |*0 > 1 |
        // +-V-+-V-+
        // | 2 | 3 |
        // +---+---+
        switch(quadIndex)
        {
          case 0: break;
          case 1: ADD_DERIV(derivs.ddxcoarse); break;
          case 2: ADD_DERIV(derivs.ddycoarse); break;
          case 3:
            ADD_DERIV(derivs.ddxcoarse);
            ADD_DERIV(derivs.ddycoarse);
            break;
          default: break;
        }
        break;
      }
      case 1:
      {
        // we need to use fine to get from 1 to 3 as coarse only ever involves 0->1 and 0->2
        // +---+---+
        // | 0 < 1*|
        // +-V-+-,-+
        // | 2 | 3 |
        // +---+---+
        switch(quadIndex)
        {
          case 0: SUB_DERIV(derivs.ddxcoarse); break;
          case 1: break;
          case 2:
            SUB_DERIV(derivs.ddxcoarse);
            ADD_DERIV(derivs.ddycoarse);
            break;
          case 3: ADD_DERIV(derivs.ddyfine); break;
          default: break;
        }
        break;
      }
      case 2:
      {
        // +---+---+
        // | 0 > 1 |
        // +-^-+---+
        // |*2 } 3 |
        // +---+---+
        switch(quadIndex)
        {
          case 0: SUB_DERIV(derivs.ddycoarse); break;
          case 1:
            SUB_DERIV(derivs.ddycoarse);
            ADD_DERIV(derivs.ddxcoarse);
            break;
          case 2: break;
          case 3: ADD_DERIV(derivs.ddxfine); break;
          default: break;
        }
        break;
      }
      case 3:
      {
        // +---+---+
        // | 0 < 1 |
        // +---+-`-+
        // | 2 { 3*|
        // +---+---+
        switch(quadIndex)
        {
          case 0:
            SUB_DERIV(derivs.ddyfine);
            SUB_DERIV(derivs.ddxcoarse);
            break;
          case 1: SUB_DERIV(derivs.ddyfine); break;
          case 2: SUB_DERIV(derivs.ddxfine); break;
          case 3: break;
          default: break;
        }
        break;
      }
      default: break;
    }
  }
  
  uint32_t Debugger::ApplyDerivatives(uint32_t quadIndex, const Decorations &curDecorations,
                                      uint32_t location, const DataType &inType, ShaderVariable &outVar)
  {
    // only floats have derivatives
    if(outVar.type == VarType::Float || outVar.type == VarType::Half || outVar.type == VarType::Double)
    {
      ShaderBuiltin builtin = ShaderBuiltin::Undefined;
      if(curDecorations.flags & Decorations::HasBuiltIn)
        builtin = MakeShaderBuiltin(stage, curDecorations.builtIn);
  
      uint32_t component = 0;
      for(const DecorationAndParamData &dec : curDecorations.others)
      {
        if(dec.value == Decoration::Component)
        {
          component = dec.component;
          break;
        }
      }
  
      if(curDecorations.flags & Decorations::HasLocation)
        location = curDecorations.location;
  
      DebugAPIWrapper::DerivativeDeltas derivs =
          apiWrapper->GetDerivative(builtin, location, component, outVar.type);
  
      if(outVar.type == VarType::Float)
        ApplyDerivative<float>(activeLaneIndex, quadIndex, outVar.value.f32v.data(), derivs);
      else if(outVar.type == VarType::Half)
        ApplyDerivative<half_float::half>(activeLaneIndex, quadIndex,
                                          (half_float::half *)outVar.value.u16v.data(), derivs);
      else if(outVar.type == VarType::Double)
        ApplyDerivative<double>(activeLaneIndex, quadIndex, outVar.value.f64v.data(), derivs);
    }
  
    // each row consumes a new location
    return outVar.rows;
  }
  
  void Debugger::PreParse(uint32_t maxId)
  {
    Processor::PreParse(maxId);
  
    strings.resize(idTypes.size());
  }
  
  void Debugger::PostParse()
  {
    Processor::PostParse();
  
    for(const MemberName &mem : memberNames)
      dataTypes[mem.id].children[mem.member].name = mem.name;
  
    // global IDs never hit a death point
    for(const Variable &v : globals)
      idDeathOffset[v.id] = ~0U;
  
    memberNames.clear();
  }
  
  void Debugger::RegisterOp(Iter it)
  {
    Processor::RegisterOp(it);
  
    OpDecoder opdata(it);
  
    // we add +1 so that we don't remove the ID on its last use, but the next subsequent instruction
    // since blocks always end with a terminator that doesn't consume IDs we're interested in
    // (variables) we'll always have one extra instruction to step to
    OpDecoder::ForEachID(it, [this, &it](Id id, bool result) {
      idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
    });
  
    if(opdata.op == Op::ExtInst)
    {
      OpExtInst extinst(it);
  
      if(extSets[extinst.set] == "GLSL.std.450")
      {
        // all parameters to GLSL.std.450 are Ids, extend idDeathOffset appropriately
        for(const uint32_t param : extinst.params)
        {
          Id id = Id::fromWord(param);
          idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
        }
      }
      else if(extSets[extinst.set] == "NonSemantic.DebugPrintf")
      {
        // all parameters to NonSemantic.DebugPrintf are Ids, extend idDeathOffset appropriately
        for(const uint32_t param : extinst.params)
        {
          Id id = Id::fromWord(param);
          idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
        }
      }
    }
  
    if(opdata.op == Op::Source)
    {
      OpSource source(it);
  
      if(!source.source.empty())
      {
        m_Files[source.file] = m_Files.size();
      }
    }
    else if(opdata.op == Op::Line)
    {
      OpLine line(it);
  
      m_CurLineCol.lineStart = line.line;
      m_CurLineCol.lineEnd = line.line;
      m_CurLineCol.colStart = line.column;
      m_CurLineCol.fileIndex = (int32_t)m_Files[line.file];
    }
    else if(opdata.op == Op::NoLine)
    {
      m_CurLineCol = LineColumnInfo();
    }
    else
    {
      m_LineColInfo[it.offs()] = m_CurLineCol;
    }
  
    if(opdata.op == Op::String)
    {
      OpString string(it);
  
      strings[string.result] = string.string;
    }
    else if(opdata.op == Op::Name)
    {
      OpName name(it);
  
      // technically you could name a string - in that case we ignore the name
      if(strings[name.target].empty())
        strings[name.target] = name.name;
    }
    else if(opdata.op == Op::MemberName)
    {
      OpMemberName memberName(it);
  
      memberNames.push_back({memberName.type, memberName.member, memberName.name});
    }
    else if(opdata.op == Op::EntryPoint)
    {
      OpEntryPoint entryPoint(it);
  
      entryLookup[ShaderEntryPoint(entryPoint.name, MakeShaderStage(entryPoint.executionModel))] =
          entryPoint.entryPoint;
    }
    else if(opdata.op == Op::Function)
    {
      OpFunction func(it);
  
      curFunction = &functions[func.result];
  
      curFunction->begin = it.offs();
    }
    else if(opdata.op == Op::FunctionParameter)
    {
      OpFunctionParameter param(it);
  
      curFunction->parameters.push_back(param.result);
    }
    else if(opdata.op == Op::Variable)
    {
      OpVariable var(it);
  
      if(var.storageClass == StorageClass::Function && curFunction)
        curFunction->variables.push_back(var.result);
  
      // variables are always pointers
      Id varType = dataTypes[var.resultType].InnerType();
  
      // if we don't have a name for this variable but it's a pointer to a struct that is named then
      // give the variable a name based on the type. This is a common pattern in GLSL for global
      // blocks, and since the variable is how we access commonly we should give it a recognisable
      // name.
      if(strings[var.result].empty() && dataTypes[varType].type == DataType::StructType &&
         !strings[varType].empty())
      {
        strings[var.result] = strings[varType] + "_var";
      }
    }
    else if(opdata.op == Op::Label)
    {
      OpLabel lab(it);
  
      labelInstruction[lab.result] = instructionOffsets.count();
    }
  
    // everything else inside a function becomes an instruction, including the OpFunction and
    // OpFunctionEnd. We won't actually execute these instructions
  
    instructionOffsets.push_back(it.offs());
  
    if(opdata.op == Op::FunctionEnd)
    {
      // don't automatically kill function parameters and variables. They will be manually killed when
      // returning from a function's scope
      for(const Id &id : curFunction->parameters)
        idDeathOffset[id] = ~0U;
      for(const Id &id : curFunction->variables)
        idDeathOffset[id] = ~0U;
  
      curFunction = NULL;
    }
  }
  
  };    // namespace rdcspv
  