text: /******************************************************************************
   * The MIT License (MIT)
   *
   * Copyright (c) 2015-2018 Baldur Karlsson
   * Copyright (c) 2014 Crytek
   *
   * 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.
   ******************************************************************************/
  
  // TODO remove me
  #include "dxbc_debug.h"
  #include <math.h>
  #include <algorithm>
  #include "api/replay/renderdoc_replay.h"
  #include "common/common.h"
  #include "driver/d3d11/d3d11_device.h"
  #include "driver/d3d11/d3d11_shader_cache.h"
  #include "maths/formatpacking.h"
  #include "dxbc_inspect.h"
  
  using namespace DXBC;
  
  namespace ShaderDebug
  {
  static float round_ne(float x)
  {
    if(!_finite(x) || _isnan(x))
      return x;
  
    float rem = remainderf(x, 1.0f);
  
    return x - rem;
  }
  
  VarType State::OperationType(const OpcodeType &op) const
  {
    switch(op)
    {
      // non typed operations, just return float
      case OPCODE_LOOP:
      case OPCODE_CONTINUE:
      case OPCODE_CONTINUEC:
      case OPCODE_ENDLOOP:
      case OPCODE_SWITCH:
      case OPCODE_CASE:
      case OPCODE_DEFAULT:
      case OPCODE_ENDSWITCH:
      case OPCODE_ELSE:
      case OPCODE_ENDIF:
      case OPCODE_RET:
      case OPCODE_RETC:
      case OPCODE_DISCARD:
      case OPCODE_NOP:
      case OPCODE_CUSTOMDATA:
      case OPCODE_SYNC:
      case OPCODE_STORE_UAV_TYPED:
      case OPCODE_STORE_RAW:
      case OPCODE_STORE_STRUCTURED:
        return VarType::Float;
  
      // operations that can be either type, also just return float (fixed up later)
      case OPCODE_SAMPLE:
      case OPCODE_SAMPLE_L:
      case OPCODE_SAMPLE_B:
      case OPCODE_SAMPLE_C:
      case OPCODE_SAMPLE_C_LZ:
      case OPCODE_GATHER4:
      case OPCODE_GATHER4_C:
      case OPCODE_GATHER4_PO:
      case OPCODE_GATHER4_PO_C:
      case OPCODE_SAMPLE_D:
      case OPCODE_RESINFO:
      case OPCODE_BUFINFO:
      case OPCODE_SAMPLE_INFO:
      case OPCODE_SAMPLE_POS:
      case OPCODE_EVAL_CENTROID:
      case OPCODE_EVAL_SAMPLE_INDEX:
      case OPCODE_EVAL_SNAPPED:
      case OPCODE_LOD:
      case OPCODE_LD:
      case OPCODE_LD_MS: return VarType::Float;
  
      case OPCODE_ADD:
      case OPCODE_MUL:
      case OPCODE_DIV:
      case OPCODE_MOV:
      case OPCODE_MOVC:
      case OPCODE_MAX:
      case OPCODE_MIN:
      case OPCODE_MAD:
      case OPCODE_DP2:
      case OPCODE_DP3:
      case OPCODE_DP4:
      case OPCODE_SINCOS:
      case OPCODE_F16TOF32:
      case OPCODE_F32TOF16:
      case OPCODE_FRC:
      case OPCODE_FTOI:
      case OPCODE_FTOU:
      case OPCODE_FTOD:
      case OPCODE_ROUND_PI:
      case OPCODE_ROUND_Z:
      case OPCODE_ROUND_NE:
      case OPCODE_ROUND_NI:
      case OPCODE_RCP:
      case OPCODE_RSQ:
      case OPCODE_SQRT:
      case OPCODE_LOG:
      case OPCODE_EXP:
      case OPCODE_LT:
      case OPCODE_GE:
      case OPCODE_EQ:
      case OPCODE_NE:
      case OPCODE_DERIV_RTX:
      case OPCODE_DERIV_RTX_COARSE:
      case OPCODE_DERIV_RTX_FINE:
      case OPCODE_DERIV_RTY:
      case OPCODE_DERIV_RTY_COARSE:
      case OPCODE_DERIV_RTY_FINE: return VarType::Float;
  
      case OPCODE_AND:
      case OPCODE_OR:
      case OPCODE_IADD:
      case OPCODE_IMUL:
      case OPCODE_IMAD:
      case OPCODE_ISHL:
      case OPCODE_IGE:
      case OPCODE_IEQ:
      case OPCODE_ILT:
      case OPCODE_ISHR:
      case OPCODE_IBFE:
      case OPCODE_INE:
      case OPCODE_INEG:
      case OPCODE_IMAX:
      case OPCODE_IMIN:
      case OPCODE_SWAPC:
      case OPCODE_BREAK:
      case OPCODE_BREAKC:
      case OPCODE_IF:
      case OPCODE_ITOF:
      case OPCODE_DTOI: return VarType::Int;
  
      case OPCODE_ATOMIC_IADD:
      case OPCODE_ATOMIC_IMAX:
      case OPCODE_ATOMIC_IMIN:
      case OPCODE_IMM_ATOMIC_IADD:
      case OPCODE_IMM_ATOMIC_IMAX:
      case OPCODE_IMM_ATOMIC_IMIN: return VarType::Int;
      case OPCODE_ATOMIC_AND:
      case OPCODE_ATOMIC_OR:
      case OPCODE_ATOMIC_XOR:
      case OPCODE_ATOMIC_CMP_STORE:
      case OPCODE_ATOMIC_UMAX:
      case OPCODE_ATOMIC_UMIN:
      case OPCODE_IMM_ATOMIC_AND:
      case OPCODE_IMM_ATOMIC_OR:
      case OPCODE_IMM_ATOMIC_XOR:
      case OPCODE_IMM_ATOMIC_EXCH:
      case OPCODE_IMM_ATOMIC_CMP_EXCH:
      case OPCODE_IMM_ATOMIC_UMAX:
      case OPCODE_IMM_ATOMIC_UMIN: return VarType::UInt;
  
      case OPCODE_BFREV:
      case OPCODE_COUNTBITS:
      case OPCODE_FIRSTBIT_HI:
      case OPCODE_FIRSTBIT_LO:
      case OPCODE_FIRSTBIT_SHI:
      case OPCODE_UADDC:
      case OPCODE_USUBB:
      case OPCODE_UMAD:
      case OPCODE_UMUL:
      case OPCODE_UMIN:
      case OPCODE_IMM_ATOMIC_ALLOC:
      case OPCODE_IMM_ATOMIC_CONSUME:
      case OPCODE_UMAX:
      case OPCODE_UDIV:
      case OPCODE_UTOF:
      case OPCODE_USHR:
      case OPCODE_ULT:
      case OPCODE_UGE:
      case OPCODE_BFI:
      case OPCODE_UBFE:
      case OPCODE_NOT:
      case OPCODE_XOR:
      case OPCODE_LD_RAW:
      case OPCODE_LD_UAV_TYPED:
      case OPCODE_LD_STRUCTURED:
      case OPCODE_DTOU: return VarType::UInt;
  
      case OPCODE_DADD:
      case OPCODE_DMAX:
      case OPCODE_DMIN:
      case OPCODE_DMUL:
      case OPCODE_DEQ:
      case OPCODE_DNE:
      case OPCODE_DGE:
      case OPCODE_DLT:
      case OPCODE_DMOV:
      case OPCODE_DMOVC:
      case OPCODE_DTOF:
      case OPCODE_DDIV:
      case OPCODE_DFMA:
      case OPCODE_DRCP:
      case OPCODE_ITOD:
      case OPCODE_UTOD: return VarType::Double;
  
      default: RDCERR("Unhandled operation %d in shader debugging", op); return VarType::Float;
    }
  }
  
  void DoubleSet(ShaderVariable &var, const double in[2])
  {
    var.value.d.x = in[0];
    var.value.d.y = in[1];
    var.type = VarType::Double;
  }
  
  void DoubleGet(const ShaderVariable &var, double out[2])
  {
    out[0] = var.value.d.x;
    out[1] = var.value.d.y;
  }
  
  ShaderVariable sat(const ShaderVariable &v, const VarType type)
  {
    ShaderVariable r = v;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.iv[i] = v.value.iv[i] < 0 ? 0 : (v.value.iv[i] > 1 ? 1 : v.value.iv[i]);
        break;
      }
      case VarType::UInt:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.uv[i] = v.value.uv[i] ? 1 : 0;
        break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.fv[i] = v.value.fv[i] < 0 ? 0 : (v.value.fv[i] > 1 ? 1 : v.value.fv[i]);
        break;
      }
      case VarType::Double:
      {
        double src[2];
        DoubleGet(v, src);
  
        double dst[2];
        dst[0] = src[0] < 0 ? 0 : (src[0] > 1 ? 1 : src[0]);
        dst[1] = src[1] < 0 ? 0 : (src[1] > 1 ? 1 : src[1]);
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable abs(const ShaderVariable &v, const VarType type)
  {
    ShaderVariable r = v;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.iv[i] = v.value.iv[i] > 0 ? v.value.iv[i] : -v.value.iv[i];
        break;
      }
      case VarType::UInt: { break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.fv[i] = v.value.fv[i] > 0 ? v.value.fv[i] : -v.value.fv[i];
        break;
      }
      case VarType::Double:
      {
        double src[2];
        DoubleGet(v, src);
  
        double dst[2];
        dst[0] = src[0] > 0 ? src[0] : -src[0];
        dst[1] = src[1] > 0 ? src[1] : -src[1];
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable neg(const ShaderVariable &v, const VarType type)
  {
    ShaderVariable r = v;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.iv[i] = -v.value.iv[i];
        break;
      }
      case VarType::UInt: { break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < v.columns; i++)
          r.value.fv[i] = -v.value.fv[i];
        break;
      }
      case VarType::Double:
      {
        double src[2];
        DoubleGet(v, src);
  
        double dst[2];
        dst[0] = -src[0];
        dst[1] = -src[1];
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable mul(const ShaderVariable &a, const ShaderVariable &b, const VarType type)
  {
    ShaderVariable r = a;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.iv[i] = a.value.iv[i] * b.value.iv[i];
        break;
      }
      case VarType::UInt:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.uv[i] = a.value.uv[i] * b.value.uv[i];
        break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.fv[i] = a.value.fv[i] * b.value.fv[i];
        break;
      }
      case VarType::Double:
      {
        double src0[2], src1[2];
        DoubleGet(a, src0);
        DoubleGet(b, src1);
  
        double dst[2];
        dst[0] = src0[0] * src1[0];
        dst[1] = src0[1] * src1[1];
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable div(const ShaderVariable &a, const ShaderVariable &b, const VarType type)
  {
    ShaderVariable r = a;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.iv[i] = a.value.iv[i] / b.value.iv[i];
        break;
      }
      case VarType::UInt:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.uv[i] = a.value.uv[i] / b.value.uv[i];
        break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.fv[i] = a.value.fv[i] / b.value.fv[i];
        break;
      }
      case VarType::Double:
      {
        double src0[2], src1[2];
        DoubleGet(a, src0);
        DoubleGet(b, src1);
  
        double dst[2];
        dst[0] = src0[0] / src1[0];
        dst[1] = src0[1] / src1[1];
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable add(const ShaderVariable &a, const ShaderVariable &b, const VarType type)
  {
    ShaderVariable r = a;
  
    switch(type)
    {
      case VarType::Int:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.iv[i] = a.value.iv[i] + b.value.iv[i];
        break;
      }
      case VarType::UInt:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.uv[i] = a.value.uv[i] + b.value.uv[i];
        break;
      }
      case VarType::Float:
      {
        for(size_t i = 0; i < a.columns; i++)
          r.value.fv[i] = a.value.fv[i] + b.value.fv[i];
        break;
      }
      case VarType::Double:
      {
        double src0[2], src1[2];
        DoubleGet(a, src0);
        DoubleGet(b, src1);
  
        double dst[2];
        dst[0] = src0[0] + src1[0];
        dst[1] = src0[1] + src1[1];
  
        DoubleSet(r, dst);
        break;
      }
      default:
        RDCFATAL(
            "Unsupported type of variable %d in math operation.\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.",
            type);
    }
  
    r.type = type;
  
    return r;
  }
  
  ShaderVariable sub(const ShaderVariable &a, const ShaderVariable &b, const VarType type)
  {
    return add(a, neg(b, type), type);
  }
  
  void State::Init()
  {
    vector<uint32_t> indexTempSizes;
  
    for(size_t i = 0; i < dxbc->GetNumDeclarations(); i++)
    {
      const DXBC::ASMDecl &decl = dxbc->GetDeclaration(i);
  
      if(decl.declaration == OPCODE_DCL_TEMPS)
      {
        registers.reserve(decl.numTemps);
  
        for(uint32_t t = 0; t < decl.numTemps; t++)
        {
          char buf[64] = {0};
  
          StringFormat::snprintf(buf, 63, "r%d", t);
  
          registers.push_back(ShaderVariable(buf, 0l, 0l, 0l, 0l));
        }
      }
      if(decl.declaration == OPCODE_DCL_INDEXABLE_TEMP)
      {
        uint32_t reg = decl.tempReg;
        uint32_t size = decl.numTemps;
        if(reg >= indexTempSizes.size())
          indexTempSizes.resize(reg + 1);
  
        indexTempSizes[reg] = size;
      }
    }
  
    if(indexTempSizes.size())
    {
      indexableTemps.resize(indexTempSizes.size());
  
      for(int32_t i = 0; i < (int32_t)indexTempSizes.size(); i++)
      {
        if(indexTempSizes[i] > 0)
        {
          indexableTemps[i].members.resize(indexTempSizes[i]);
          for(uint32_t t = 0; t < indexTempSizes[i]; t++)
          {
            char buf[64] = {0};
  
            StringFormat::snprintf(buf, 63, "x%u[%u]", i, t);
  
            indexableTemps[i].members[t] = ShaderVariable(buf, 0l, 0l, 0l, 0l);
          }
        }
      }
    }
  }
  
  bool State::Finished() const
  {
    return dxbc && (done || nextInstruction >= (int)dxbc->GetNumInstructions());
  }
  
  bool State::AssignValue(ShaderVariable &dst, uint32_t dstIndex, const ShaderVariable &src,
                          uint32_t srcIndex)
  {
    if(src.type == VarType::Float)
    {
      float ft = src.value.fv[srcIndex];
      if(!_finite(ft) || _isnan(ft))
        flags |= ShaderEvents::GeneratedNanOrInf;
    }
    else if(src.type == VarType::Double)
    {
      double dt = src.value.dv[srcIndex];
      if(!_finite(dt) || _isnan(dt))
        flags |= ShaderEvents::GeneratedNanOrInf;
    }
  
    bool ret = (dst.value.uv[dstIndex] != src.value.uv[srcIndex]);
  
    dst.value.uv[dstIndex] = src.value.uv[srcIndex];
  
    return ret;
  }
  
  void State::SetDst(const ASMOperand &dstoper, const ASMOperation &op, const ShaderVariable &val)
  {
    ShaderVariable *v = NULL;
  
    uint32_t indices[4] = {0};
  
    RDCASSERT(dstoper.indices.size() <= 4);
  
    for(size_t i = 0; i < dstoper.indices.size(); i++)
    {
      if(dstoper.indices[i].absolute)
        indices[i] = (uint32_t)dstoper.indices[i].index;
      else
        indices[i] = 0;
  
      if(dstoper.indices[i].relative)
      {
        ShaderVariable idx = GetSrc(dstoper.indices[i].operand, op);
  
        indices[i] += idx.value.i.x;
      }
    }
  
    RegisterRange range;
    range.index = uint16_t(indices[0]);
  
    switch(dstoper.type)
    {
      case TYPE_TEMP:
      {
        range.type = RegisterType::Temporary;
        RDCASSERT(indices[0] < (uint32_t)registers.size());
        if(indices[0] < (uint32_t)registers.size())
          v = &registers[(size_t)indices[0]];
        break;
      }
      case TYPE_INDEXABLE_TEMP:
      {
        range.type = RegisterType::IndexedTemporary;
        RDCASSERT(dstoper.indices.size() == 2);
  
        if(dstoper.indices.size() == 2)
        {
          RDCASSERT(indices[0] < (uint32_t)indexableTemps.size());
          if(indices[0] < (uint32_t)indexableTemps.size())
          {
            RDCASSERT(indices[1] < (uint32_t)indexableTemps[indices[0]].members.size());
            if(indices[1] < (uint32_t)indexableTemps[indices[0]].members.size())
            {
              v = &indexableTemps[indices[0]].members[indices[1]];
            }
          }
        }
        break;
      }
      case TYPE_OUTPUT:
      {
        range.type = RegisterType::Output;
        RDCASSERT(indices[0] < (uint32_t)outputs.size());
        if(indices[0] < (uint32_t)outputs.size())
          v = &outputs[(size_t)indices[0]];
        break;
      }
      case TYPE_INPUT:
      case TYPE_CONSTANT_BUFFER:
      {
        RDCERR(
            "Attempt to write to read-only operand (input, cbuffer, etc).\n"
            "This is likely a bug in the asm extraction as such code isn't likely to be produced by "
            "fxc.");
        break;
      }
      case TYPE_NULL:
      {
        // nothing to do!
        return;
      }
      case TYPE_OUTPUT_DEPTH:
      case TYPE_OUTPUT_DEPTH_LESS_EQUAL:
      case TYPE_OUTPUT_DEPTH_GREATER_EQUAL:
      case TYPE_OUTPUT_STENCIL_REF:
      case TYPE_OUTPUT_COVERAGE_MASK:
      {
        // handle all semantic outputs together
        ShaderBuiltin builtin = ShaderBuiltin::Count;
        switch(dstoper.type)
        {
          case TYPE_OUTPUT_DEPTH: builtin = ShaderBuiltin::DepthOutput; break;
          case TYPE_OUTPUT_DEPTH_LESS_EQUAL: builtin = ShaderBuiltin::DepthOutputLessEqual; break;
          case TYPE_OUTPUT_DEPTH_GREATER_EQUAL:
            builtin = ShaderBuiltin::DepthOutputGreaterEqual;
            break;
          case TYPE_OUTPUT_STENCIL_REF: builtin = ShaderBuiltin::StencilReference; break;
          case TYPE_OUTPUT_COVERAGE_MASK: builtin = ShaderBuiltin::MSAACoverage; break;
          default: RDCERR("Invalid dest operand!"); break;
        }
  
        for(size_t i = 0; i < dxbc->m_OutputSig.size(); i++)
        {
          if(dxbc->m_OutputSig[i].systemValue == builtin)
          {
            v = &outputs[i];
            break;
          }
        }
  
        if(!v)
        {
          RDCERR("Couldn't find type %d by semantic matching, falling back to string match",
                 dstoper.type);
  
          std::string name = dstoper.toString(dxbc, ToString::ShowSwizzle);
          for(size_t i = 0; i < outputs.size(); i++)
          {
            if(outputs[i].name == name)
            {
              v = &outputs[i];
              break;
            }
          }
  
          if(v)
            break;
        }
  
        break;
      }
      default:
      {
        RDCERR("Currently unsupported destination operand type %d!", dstoper.type);
  
        std::string name = dstoper.toString(dxbc, ToString::ShowSwizzle);
        for(size_t i = 0; i < outputs.size(); i++)
        {
          if(outputs[i].name == name)
          {
            v = &outputs[i];
            break;
          }
        }
  
        if(v)
          break;
  
        break;
      }
    }
  
    RDCASSERT(v);
  
    if(v)
    {
      ShaderVariable right = val;
  
      RDCASSERT(v->rows == 1 && right.rows == 1);
      RDCASSERT(right.columns <= 4);
  
      // behaviour for scalar and vector masks are slightly different.
      // in a scalar operation like r0.z = r4.x + r6.y
      // then when doing the set to dest we must write into the .z
      // from the only component - x - since the result is scalar.
      // in a vector operation like r0.zw = r4.xxxy + r6.yyyz
      // then we must write from matching component to matching component
  
      if(op.saturate)
        right = sat(right, OperationType(op.operation));
  
      if(dstoper.comps[0] != 0xff && dstoper.comps[1] == 0xff && dstoper.comps[2] == 0xff &&
         dstoper.comps[3] == 0xff)
      {
        RDCASSERT(dstoper.comps[0] != 0xff);
  
        bool changed = AssignValue(*v, dstoper.comps[0], right, 0);
  
        if(changed && range.type != RegisterType::Undefined)
        {
          range.component = dstoper.comps[0];
          modified.push_back(range);
        }
      }
      else
      {
        int compsWritten = 0;
        for(size_t i = 0; i < 4; i++)
        {
          // if comps value is 0xff, we should not write to this component
          if(dstoper.comps[i] != 0xff)
          {
            RDCASSERT(dstoper.comps[i] < v->columns);
            bool changed = AssignValue(*v, dstoper.comps[i], right, dstoper.comps[i]);
            compsWritten++;
  
            if(changed && range.type != RegisterType::Undefined)
            {
              range.component = dstoper.comps[i];
              modified.push_back(range);
            }
          }
        }
  
        if(compsWritten == 0)
        {
          bool changed = AssignValue(*v, 0, right, 0);
  
          if(changed && range.type != RegisterType::Undefined)
          {
            range.component = 0;
            modified.push_back(range);
          }
        }
      }
    }
  }
  
  ShaderVariable State::DDX(bool fine, State quad[4], const DXBC::ASMOperand &oper,
                            const DXBC::ASMOperation &op) const
  {
    ShaderVariable ret;
  
    VarType optype = OperationType(op.operation);
  
    if(!fine)
    {
      // use top-left pixel's neighbours
      ret = sub(quad[1].GetSrc(oper, op), quad[0].GetSrc(oper, op), optype);
    }
    // find direct neighbours - left pixel in the quad
    else if(quadIndex % 2 == 0)
    {
      ret = sub(quad[quadIndex + 1].GetSrc(oper, op), quad[quadIndex].GetSrc(oper, op), optype);
    }
    else
    {
      ret = sub(quad[quadIndex].GetSrc(oper, op), quad[quadIndex - 1].GetSrc(oper, op), optype);
    }
  
    return ret;
  }
  
  ShaderVariable State::DDY(bool fine, State quad[4], const DXBC::ASMOperand &oper,
                            const DXBC::ASMOperation &op) const
  {
    ShaderVariable ret;
  
    VarType optype = OperationType(op.operation);
  
    if(!fine)
    {
      // use top-left pixel's neighbours
      ret = sub(quad[2].GetSrc(oper, op), quad[0].GetSrc(oper, op), optype);
    }
    // find direct neighbours - top pixel in the quad
    else if(quadIndex / 2 == 0)
    {
      ret = sub(quad[quadIndex + 2].GetSrc(oper, op), quad[quadIndex].GetSrc(oper, op), optype);
    }
    else
    {
      ret = sub(quad[quadIndex].GetSrc(oper, op), quad[quadIndex - 2].GetSrc(oper, op), optype);
    }
  
    return ret;
  }
  
  ShaderVariable State::GetSrc(const ASMOperand &oper, const ASMOperation &op) const
  {
    ShaderVariable v, s;
  
    uint32_t indices[4] = {0};
  
    RDCASSERT(oper.indices.size() <= 4);
  
    for(size_t i = 0; i < oper.indices.size(); i++)
    {
      if(oper.indices[i].absolute)
        indices[i] = (uint32_t)oper.indices[i].index;
      else
        indices[i] = 0;
  
      if(oper.indices[i].relative)
      {
        ShaderVariable idx = GetSrc(oper.indices[i].operand, op);
  
        indices[i] += idx.value.i.x;
      }
    }
  
    switch(oper.type)
    {
      case TYPE_TEMP:
      {
        // we assume we never write to an uninitialised register
        RDCASSERT(indices[0] < (uint32_t)registers.size());
  
        if(indices[0] < (uint32_t)registers.size())
          v = s = registers[indices[0]];
        else
          v = s = ShaderVariable("", indices[0], indices[0], indices[0], indices[0]);
  
        break;
      }
      case TYPE_INDEXABLE_TEMP:
      {
        RDCASSERT(oper.indices.size() == 2);
  
        if(oper.indices.size() == 2)
        {
          RDCASSERT(indices[0] < (uint32_t)indexableTemps.size());
          if(indices[0] < (uint32_t)indexableTemps.size())
          {
            RDCASSERT(indices[1] < (uint32_t)indexableTemps[indices[0]].members.size());
            if(indices[1] < (uint32_t)indexableTemps[indices[0]].members.size())
            {
              v = s = indexableTemps[indices[0]].members[indices[1]];
            }
          }
        }
        break;
      }
      case TYPE_INPUT:
      {
        RDCASSERT(indices[0] < (uint32_t)trace->inputs.size());
  
        if(indices[0] < (uint32_t)trace->inputs.size())
          v = s = trace->inputs[indices[0]];
        else
          v = s = ShaderVariable("", indices[0], indices[0], indices[0], indices[0]);
  
        break;
      }
      case TYPE_OUTPUT:
      {
        RDCASSERT(indices[0] < (uint32_t)outputs.size());
  
        if(indices[0] < (uint32_t)outputs.size())
          v = s = outputs[indices[0]];
        else
          v = s = ShaderVariable("", indices[0], indices[0], indices[0], indices[0]);
  
        break;
      }
  
      // instructions referencing group shared memory handle it specially (the operand
      // itself just names the groupshared memory region, there's a separate dst address
      // operand).
      case TYPE_THREAD_GROUP_SHARED_MEMORY:
  
      case TYPE_RESOURCE:
      case TYPE_SAMPLER:
      case TYPE_UNORDERED_ACCESS_VIEW:
      case TYPE_NULL:
      case TYPE_RASTERIZER:
      {
        // should be handled specially by instructions that expect these types of
        // argument but let's be sane and include the index
        v = s = ShaderVariable("", indices[0], indices[0], indices[0], indices[0]);
        break;
      }
      case TYPE_IMMEDIATE32:
      case TYPE_IMMEDIATE64:
      {
        s.name = "Immediate";
  
        if(oper.numComponents == NUMCOMPS_1)
        {
          s.rows = 1;
          s.columns = 1;
        }
        else if(oper.numComponents == NUMCOMPS_4)
        {
          s.rows = 1;
          s.columns = 4;
        }
        else
        {
          RDCFATAL("N-wide vectors not supported (per hlsl spec)");
        }
  
        if(oper.type == TYPE_IMMEDIATE32)
        {
          for(size_t i = 0; i < s.columns; i++)
          {
            s.value.iv[i] = (int32_t)oper.values[i];
          }
        }
        else
        {
          RDCUNIMPLEMENTED(
              "Encountered immediate 64bit value!");    // need to figure out what to do here.
        }
  
        v = s;
  
        break;
      }
      case TYPE_CONSTANT_BUFFER:
      {
        int cb = -1;
  
        for(size_t i = 0; i < dxbc->m_CBuffers.size(); i++)
        {
          if(dxbc->m_CBuffers[i].reg == indices[0])
          {
            cb = (int)i;
            break;
          }
        }
  
        RDCASSERTMSG("Invalid cbuffer lookup", cb != -1 && cb < trace->constantBlocks.count(), cb,
                     trace->constantBlocks.count());
  
        if(cb >= 0 && cb < trace->constantBlocks.count())
        {
          RDCASSERTMSG("Out of bounds cbuffer lookup",
                       indices[1] < (uint32_t)trace->constantBlocks[cb].members.count(), indices[1],
                       trace->constantBlocks[cb].members.count());
  
          if(indices[1] < (uint32_t)trace->constantBlocks[cb].members.count())
            v = s = trace->constantBlocks[cb].members[indices[1]];
          else
            v = s = ShaderVariable("", 0U, 0U, 0U, 0U);
        }
        else
        {
          v = s = ShaderVariable("", 0U, 0U, 0U, 0U);
        }
  
        break;
      }
      case TYPE_IMMEDIATE_CONSTANT_BUFFER:
      {
        v = s = ShaderVariable("", 0, 0, 0, 0);
  
        // if this Vec4f is entirely in the ICB
        if(indices[0] <= dxbc->m_Immediate.size() / 4 - 1)
        {
          memcpy(s.value.uv, &dxbc->m_Immediate[indices[0] * 4], sizeof(Vec4f));
        }
        else
        {
          // ICBs are always a multiple of Vec4fs, so no need to do a partial read (like in a normal
          // CB)
          RDCWARN(
              "Shader read off the end of an immediate constant buffer. Bug in shader or simulation? "
              "Clamping to 0s");
        }
  
        break;
      }
      case TYPE_INPUT_THREAD_GROUP_ID:
      {
        v = s = ShaderVariable("vThreadGroupID", semantics.GroupID[0], semantics.GroupID[1],
                               semantics.GroupID[2], (uint32_t)0);
  
        break;
      }
      case TYPE_INPUT_THREAD_ID:
      {
        uint32_t numthreads[3] = {0, 0, 0};
  
        for(size_t i = 0; i < dxbc->GetNumDeclarations(); i++)
        {
          const ASMDecl &decl = dxbc->GetDeclaration(i);
  
          if(decl.declaration == OPCODE_DCL_THREAD_GROUP)
          {
            numthreads[0] = decl.groupSize[0];
            numthreads[1] = decl.groupSize[1];
            numthreads[2] = decl.groupSize[2];
          }
        }
  
        RDCASSERT(numthreads[0] >= 1 && numthreads[0] <= 1024);
        RDCASSERT(numthreads[1] >= 1 && numthreads[1] <= 1024);
        RDCASSERT(numthreads[2] >= 1 && numthreads[2] <= 64);
        RDCASSERT(numthreads[0] * numthreads[1] * numthreads[2] <= 1024);
  
        v = s =
            ShaderVariable("vThreadID", semantics.GroupID[0] * numthreads[0] + semantics.ThreadID[0],
                           semantics.GroupID[1] * numthreads[1] + semantics.ThreadID[1],
                           semantics.GroupID[2] * numthreads[2] + semantics.ThreadID[2], (uint32_t)0);
  
        break;
      }
      case TYPE_INPUT_THREAD_ID_IN_GROUP:
      {
        v = s = ShaderVariable("vThreadIDInGroup", semantics.ThreadID[0], semantics.ThreadID[1],
                               semantics.ThreadID[2], (uint32_t)0);
  
        break;
      }
      case TYPE_INPUT_THREAD_ID_IN_GROUP_FLATTENED:
      {
        uint32_t numthreads[3] = {0, 0, 0};
  
        for(size_t i = 0; i < dxbc->GetNumDeclarations(); i++)
        {
          const ASMDecl &decl = dxbc->GetDeclaration(i);
  
          if(decl.declaration == OPCODE_DCL_THREAD_GROUP)
          {
            numthreads[0] = decl.groupSize[0];
            numthreads[1] = decl.groupSize[1];
            numthreads[2] = decl.groupSize[2];
          }
        }
  
        RDCASSERT(numthreads[0] >= 1 && numthreads[0] <= 1024);
        RDCASSERT(numthreads[1] >= 1 && numthreads[1] <= 1024);
        RDCASSERT(numthreads[2] >= 1 && numthreads[2] <= 64);
        RDCASSERT(numthreads[0] * numthreads[1] * numthreads[2] <= 1024);
  
        uint32_t flattened = semantics.ThreadID[2] * numthreads[0] * numthreads[1] +
                             semantics.ThreadID[1] * numthreads[0] + semantics.ThreadID[0];
  
        v = s = ShaderVariable("vThreadIDInGroupFlattened", flattened, flattened, flattened, flattened);
        break;
      }
      case TYPE_INPUT_COVERAGE_MASK:
      {
        v = s = ShaderVariable("vCoverage", semantics.coverage, semantics.coverage,
                               semantics.coverage, semantics.coverage);
        break;
      }
      case TYPE_INPUT_PRIMITIVEID:
      {
        v = s = ShaderVariable("vPrimitiveID", semantics.primID, semantics.primID, semantics.primID,
                               semantics.primID);
        break;
      }
      default:
      {
        RDCERR("Currently unsupported operand type %d!", oper.type);
  
        v = s = ShaderVariable("vUnsupported", (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
  
        break;
      }
    }
  
    // perform swizzling
    v.value.uv[0] = s.value.uv[oper.comps[0] == 0xff ? 0 : oper.comps[0]];
    v.value.uv[1] = s.value.uv[oper.comps[1] == 0xff ? 1 : oper.comps[1]];
    v.value.uv[2] = s.value.uv[oper.comps[2] == 0xff ? 2 : oper.comps[2]];
    v.value.uv[3] = s.value.uv[oper.comps[3] == 0xff ? 3 : oper.comps[3]];
  
    if(oper.comps[0] != 0xff && oper.comps[1] == 0xff && oper.comps[2] == 0xff && oper.comps[3] == 0xff)
      v.columns = 1;
    else
      v.columns = 4;
  
    if(oper.modifier == OPERAND_MODIFIER_ABS || oper.modifier == OPERAND_MODIFIER_ABSNEG)
    {
      v = abs(v, OperationType(op.operation));
    }
  
    if(oper.modifier == OPERAND_MODIFIER_NEG || oper.modifier == OPERAND_MODIFIER_ABSNEG)
    {
      v = neg(v, OperationType(op.operation));
    }
  
    return v;
  }
  
  static uint32_t BitwiseReverseLSB16(uint32_t x)
  {
    // Reverse the bits in x, then discard the lower half
    // https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
    x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
    x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
    x = ((x >> 4) & 0x0F0F0F0F) | ((x & 0x0F0F0F0F) << 4);
    x = ((x >> 8) & 0x00FF00FF) | ((x & 0x00FF00FF) << 8);
    return x << 16;
  }
  
  static uint32_t PopCount(uint32_t x)
  {
    // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    return (((x + (x >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
  }
  
  State State::GetNext(GlobalState &global, State quad[4]) const
  {
    State s = *this;
  
    s.modified.clear();
  
    if(s.nextInstruction >= s.dxbc->GetNumInstructions())
      return s;
  
    const ASMOperation &op = s.dxbc->GetInstruction((size_t)s.nextInstruction);
  
    s.nextInstruction++;
    s.flags = ShaderEvents::NoEvent;
  
    vector<ShaderVariable> srcOpers;
  
    size_t numOperands = s.dxbc->NumOperands(op.operation);
  
    VarType optype = OperationType(op.operation);
  
    RDCASSERT(op.operands.size() == numOperands);
  
    for(size_t i = 1; i < numOperands; i++)
      srcOpers.push_back(GetSrc(op.operands[i], op));
  
    switch(op.operation)
    {
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Math operations
  
      case OPCODE_DADD:
      case OPCODE_IADD:
      case OPCODE_ADD: s.SetDst(op.operands[0], op, add(srcOpers[0], srcOpers[1], optype)); break;
      case OPCODE_DDIV:
      case OPCODE_DIV: s.SetDst(op.operands[0], op, div(srcOpers[0], srcOpers[1], optype)); break;
      case OPCODE_UDIV:
      {
        ShaderVariable quot("", (uint32_t)0xffffffff, (uint32_t)0xffffffff, (uint32_t)0xffffffff,
                            (uint32_t)0xffffffff);
        ShaderVariable rem("", (uint32_t)0xffffffff, (uint32_t)0xffffffff, (uint32_t)0xffffffff,
                           (uint32_t)0xffffffff);
  
        for(size_t i = 0; i < 4; i++)
        {
          if(srcOpers[2].value.uv[i] != 0)
          {
            quot.value.uv[i] = srcOpers[1].value.uv[i] / srcOpers[2].value.uv[i];
            rem.value.uv[i] = srcOpers[1].value.uv[i] - (quot.value.uv[i] * srcOpers[2].value.uv[i]);
          }
        }
  
        if(op.operands[0].type != TYPE_NULL)
        {
          s.SetDst(op.operands[0], op, quot);
        }
        if(op.operands[1].type != TYPE_NULL)
        {
          s.SetDst(op.operands[1], op, rem);
        }
        break;
      }
      case OPCODE_BFREV:
      {
        ShaderVariable ret("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          ret.value.uv[i] = BitwiseReverseLSB16(srcOpers[0].value.uv[i]);
        }
  
        s.SetDst(op.operands[0], op, ret);
  
        break;
      }
      case OPCODE_COUNTBITS:
      {
        ShaderVariable ret("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          ret.value.uv[i] = PopCount(srcOpers[0].value.uv[i]);
        }
  
        s.SetDst(op.operands[0], op, ret);
        break;
      }
      case OPCODE_FIRSTBIT_HI:
      {
        ShaderVariable ret("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          unsigned char found = BitScanReverse((DWORD *)&ret.value.uv[i], srcOpers[0].value.uv[i]);
          if(found == 0)
          {
            ret.value.uv[i] = ~0U;
          }
          else
          {
            // firstbit_hi counts index 0 as the MSB, BitScanReverse counts index 0 as the LSB. So we
            // need to invert
            ret.value.uv[i] = 31 - ret.value.uv[i];
          }
        }
  
        s.SetDst(op.operands[0], op, ret);
        break;
      }
      case OPCODE_FIRSTBIT_LO:
      {
        ShaderVariable ret("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          unsigned char found = BitScanForward((DWORD *)&ret.value.uv[i], srcOpers[0].value.uv[i]);
          if(found == 0)
            ret.value.uv[i] = ~0U;
        }
  
        s.SetDst(op.operands[0], op, ret);
        break;
      }
      case OPCODE_FIRSTBIT_SHI:
      {
        ShaderVariable ret("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          uint32_t u = srcOpers[0].value.uv[i];
          if(srcOpers[0].value.iv[i] < 0)
            u = ~u;
  
          unsigned char found = BitScanReverse((DWORD *)&ret.value.uv[i], u);
  
          if(found == 0)
          {
            ret.value.uv[i] = ~0U;
          }
          else
          {
            // firstbit_shi counts index 0 as the MSB, BitScanReverse counts index 0 as the LSB. So we
            // need to invert
            ret.value.uv[i] = 31 - ret.value.uv[i];
          }
        }
  
        s.SetDst(op.operands[0], op, ret);
        break;
      }
      case OPCODE_IMUL:
      case OPCODE_UMUL:
      {
        ShaderVariable hi("", 0U, 0U, 0U, 0U);
        ShaderVariable lo("", 0U, 0U, 0U, 0U);
  
        for(size_t i = 0; i < 4; i++)
        {
          if(op.operation == OPCODE_UMUL)
          {
            uint64_t res = uint64_t(srcOpers[1].value.uv[i]) * uint64_t(srcOpers[2].value.uv[i]);
  
            hi.value.uv[i] = uint32_t((res >> 32) & 0xffffffff);
            lo.value.uv[i] = uint32_t(res & 0xffffffff);
          }
          else if(op.operation == OPCODE_IMUL)
          {
            int64_t res = int64_t(srcOpers[1].value.iv[i]) * int64_t(srcOpers[2].value.iv[i]);
  
            hi.value.uv[i] = uint32_t((res >> 32) & 0xffffffff);
            lo.value.uv[i] = uint32_t(res & 0xffffffff);
          }
        }
  
        if(op.operands[0].type != TYPE_NULL)
        {
          s.SetDst(op.operands[0], op, hi);
        }
        if(op.operands[1].type != TYPE_NULL)
        {
          s.SetDst(op.operands[1], op, lo);
        }
        break;
      }
      case OPCODE_DMUL:
      case OPCODE_MUL: s.SetDst(op.operands[0], op, mul(srcOpers[0], srcOpers[1], optype)); break;
      case OPCODE_UADDC:
      {
        uint64_t src[4];
        for(int i = 0; i < 4; i++)
          src[i] = (uint64_t)srcOpers[1].value.uv[i];
        for(int i = 0; i < 4; i++)
          src[i] = (uint64_t)srcOpers[2].value.uv[i];
  
        // set the rounded result
        uint32_t dst[4];
  
        for(int i = 0; i < 4; i++)
          dst[i] = (uint32_t)(src[i] & 0xffffffff);
  
        s.SetDst(op.operands[0], op, ShaderVariable("", dst[0], dst[1], dst[2], dst[3]));
  
        // if not null, set the carry bits
        if(op.operands[1].type != TYPE_NULL)
          s.SetDst(op.operands[1], op,
                   ShaderVariable("", src[0] > 0xffffffff ? 1U : 0U, src[1] > 0xffffffff ? 1U : 0U,
                                  src[2] > 0xffffffff ? 1U : 0U, src[3] > 0xffffffff ? 1U : 0U));
  
        break;
      }
      case OPCODE_USUBB:
      {
        uint64_t src0[4];
        uint64_t src1[4];
  
        // add on a 'borrow' bit
        for(int i = 0; i < 4; i++)
          src0[i] = 0x100000000 | (uint64_t)srcOpers[1].value.uv[i];
        for(int i = 0; i < 4; i++)
          src1[i] = (uint64_t)srcOpers[2].value.uv[i];
  
        // do the subtract
        uint64_t result[4];
        for(int i = 0; i < 4; i++)
          result[i] = src0[i] - src1[i];
  
        uint32_t dst[4];
        for(int i = 0; i < 4; i++)
          dst[i] = (uint32_t)(result[0] & 0xffffffff);
  
        s.SetDst(op.operands[0], op, ShaderVariable("", dst[0], dst[1], dst[2], dst[3]));
  
        // if not null, mark where the borrow bits were used
        if(op.operands[1].type != TYPE_NULL)
          s.SetDst(
              op.operands[1], op,
              ShaderVariable("", result[0] <= 0xffffffff ? 1U : 0U, result[1] <= 0xffffffff ? 1U : 0U,
                             result[2] <= 0xffffffff ? 1U : 0U, result[3] <= 0xffffffff ? 1U : 0U));
  
        break;
      }
      case OPCODE_IMAD:
      case OPCODE_UMAD:
      case OPCODE_MAD:
      case OPCODE_DFMA:
        s.SetDst(op.operands[0], op, add(mul(srcOpers[0], srcOpers[1], optype), srcOpers[2], optype));
        break;
      case OPCODE_DP2:
      case OPCODE_DP3:
      case OPCODE_DP4:
      {
        ShaderVariable dot = mul(srcOpers[0], srcOpers[1], optype);
  
        float sum = dot.value.f.x;
        if(op.operation >= OPCODE_DP2)
          sum += dot.value.f.y;
        if(op.operation >= OPCODE_DP3)
          sum += dot.value.f.z;
        if(op.operation >= OPCODE_DP4)
          sum += dot.value.f.w;
  
        s.SetDst(op.operands[0], op, ShaderVariable("", sum, sum, sum, sum));
        break;
      }
      case OPCODE_F16TOF32:
      {
        s.SetDst(op.operands[0], op, ShaderVariable("", ConvertFromHalf(srcOpers[0].value.u.x & 0xffff),
                                                    ConvertFromHalf(srcOpers[0].value.u.y & 0xffff),
                                                    ConvertFromHalf(srcOpers[0].value.u.z & 0xffff),
                                                    ConvertFromHalf(srcOpers[0].value.u.w & 0xffff)));
        break;
      }
      case OPCODE_F32TOF16:
      {
        s.SetDst(op.operands[0], op, ShaderVariable("", (uint32_t)ConvertToHalf(srcOpers[0].value.f.x),
                                                    (uint32_t)ConvertToHalf(srcOpers[0].value.f.y),
                                                    (uint32_t)ConvertToHalf(srcOpers[0].value.f.z),
                                                    (uint32_t)ConvertToHalf(srcOpers[0].value.f.w)));
        break;
      }
      case OPCODE_FRC:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", srcOpers[0].value.f.x - floor(srcOpers[0].value.f.x),
                                srcOpers[0].value.f.y - floor(srcOpers[0].value.f.y),
                                srcOpers[0].value.f.z - floor(srcOpers[0].value.f.z),
                                srcOpers[0].value.f.w - floor(srcOpers[0].value.f.w)));
        break;
      // positive infinity
      case OPCODE_ROUND_PI:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", ceil(srcOpers[0].value.f.x), ceil(srcOpers[0].value.f.y),
                                ceil(srcOpers[0].value.f.z), ceil(srcOpers[0].value.f.w)));
        break;
      // negative infinity
      case OPCODE_ROUND_NI:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", floor(srcOpers[0].value.f.x), floor(srcOpers[0].value.f.y),
                                floor(srcOpers[0].value.f.z), floor(srcOpers[0].value.f.w)));
        break;
      // towards zero
      case OPCODE_ROUND_Z:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable(
                "",
                srcOpers[0].value.f.x < 0 ? ceil(srcOpers[0].value.f.x) : floor(srcOpers[0].value.f.x),
                srcOpers[0].value.f.y < 0 ? ceil(srcOpers[0].value.f.y) : floor(srcOpers[0].value.f.y),
                srcOpers[0].value.f.z < 0 ? ceil(srcOpers[0].value.f.z) : floor(srcOpers[0].value.f.z),
                srcOpers[0].value.f.w < 0 ? ceil(srcOpers[0].value.f.w) : floor(srcOpers[0].value.f.w)));
        break;
      // to nearest even int (banker's rounding)
      case OPCODE_ROUND_NE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", round_ne(srcOpers[0].value.f.x), round_ne(srcOpers[0].value.f.y),
                                round_ne(srcOpers[0].value.f.z), round_ne(srcOpers[0].value.f.w)));
        break;
      case OPCODE_INEG: s.SetDst(op.operands[0], op, neg(srcOpers[0], optype)); break;
      case OPCODE_IMIN:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.i.x < srcOpers[1].value.i.x ? srcOpers[0].value.i.x
                                                                             : srcOpers[1].value.i.x,
                           srcOpers[0].value.i.y < srcOpers[1].value.i.y ? srcOpers[0].value.i.y
                                                                         : srcOpers[1].value.i.y,
                           srcOpers[0].value.i.z < srcOpers[1].value.i.z ? srcOpers[0].value.i.z
                                                                         : srcOpers[1].value.i.z,
                           srcOpers[0].value.i.w < srcOpers[1].value.i.w ? srcOpers[0].value.i.w
                                                                         : srcOpers[1].value.i.w));
        break;
      case OPCODE_UMIN:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.u.x < srcOpers[1].value.u.x ? srcOpers[0].value.u.x
                                                                             : srcOpers[1].value.u.x,
                           srcOpers[0].value.u.y < srcOpers[1].value.u.y ? srcOpers[0].value.u.y
                                                                         : srcOpers[1].value.u.y,
                           srcOpers[0].value.u.z < srcOpers[1].value.u.z ? srcOpers[0].value.u.z
                                                                         : srcOpers[1].value.u.z,
                           srcOpers[0].value.u.w < srcOpers[1].value.u.w ? srcOpers[0].value.u.w
                                                                         : srcOpers[1].value.u.w));
        break;
      case OPCODE_DMIN:
      {
        double src0[2], src1[2];
        DoubleGet(srcOpers[0], src0);
        DoubleGet(srcOpers[1], src1);
  
        double dst[2];
        dst[0] = src0[0] < src1[0] ? src0[0] : src1[0];
        dst[1] = src0[1] < src1[1] ? src0[1] : src1[1];
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
        DoubleSet(r, dst);
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
      case OPCODE_MIN:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.f.x < srcOpers[1].value.f.x ? srcOpers[0].value.f.x
                                                                             : srcOpers[1].value.f.x,
                           srcOpers[0].value.f.y < srcOpers[1].value.f.y ? srcOpers[0].value.f.y
                                                                         : srcOpers[1].value.f.y,
                           srcOpers[0].value.f.z < srcOpers[1].value.f.z ? srcOpers[0].value.f.z
                                                                         : srcOpers[1].value.f.z,
                           srcOpers[0].value.f.w < srcOpers[1].value.f.w ? srcOpers[0].value.f.w
                                                                         : srcOpers[1].value.f.w));
        break;
      case OPCODE_UMAX:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.u.x >= srcOpers[1].value.u.x ? srcOpers[0].value.u.x
                                                                              : srcOpers[1].value.u.x,
                           srcOpers[0].value.u.y >= srcOpers[1].value.u.y ? srcOpers[0].value.u.y
                                                                          : srcOpers[1].value.u.y,
                           srcOpers[0].value.u.z >= srcOpers[1].value.u.z ? srcOpers[0].value.u.z
                                                                          : srcOpers[1].value.u.z,
                           srcOpers[0].value.u.w >= srcOpers[1].value.u.w ? srcOpers[0].value.u.w
                                                                          : srcOpers[1].value.u.w));
        break;
      case OPCODE_IMAX:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.i.x >= srcOpers[1].value.i.x ? srcOpers[0].value.i.x
                                                                              : srcOpers[1].value.i.x,
                           srcOpers[0].value.i.y >= srcOpers[1].value.i.y ? srcOpers[0].value.i.y
                                                                          : srcOpers[1].value.i.y,
                           srcOpers[0].value.i.z >= srcOpers[1].value.i.z ? srcOpers[0].value.i.z
                                                                          : srcOpers[1].value.i.z,
                           srcOpers[0].value.i.w >= srcOpers[1].value.i.w ? srcOpers[0].value.i.w
                                                                          : srcOpers[1].value.i.w));
        break;
      case OPCODE_DMAX:
      {
        double src0[2], src1[2];
        DoubleGet(srcOpers[0], src0);
        DoubleGet(srcOpers[1], src1);
  
        double dst[2];
        dst[0] = src0[0] >= src1[0] ? src0[0] : src1[0];
        dst[1] = src0[1] >= src1[1] ? src0[1] : src1[1];
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
        DoubleSet(r, dst);
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
      case OPCODE_MAX:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.f.x >= srcOpers[1].value.f.x ? srcOpers[0].value.f.x
                                                                              : srcOpers[1].value.f.x,
                           srcOpers[0].value.f.y >= srcOpers[1].value.f.y ? srcOpers[0].value.f.y
                                                                          : srcOpers[1].value.f.y,
                           srcOpers[0].value.f.z >= srcOpers[1].value.f.z ? srcOpers[0].value.f.z
                                                                          : srcOpers[1].value.f.z,
                           srcOpers[0].value.f.w >= srcOpers[1].value.f.w ? srcOpers[0].value.f.w
                                                                          : srcOpers[1].value.f.w));
        break;
      case OPCODE_SQRT:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", sqrtf(srcOpers[0].value.f.x), sqrtf(srcOpers[0].value.f.y),
                                sqrtf(srcOpers[0].value.f.z), sqrtf(srcOpers[0].value.f.w)));
        break;
      case OPCODE_DRCP:
      {
        double ds[2] = {0.0, 0.0};
        DoubleGet(srcOpers[0], ds);
        ds[0] = 1.0f / ds[0];
        ds[1] = 1.0f / ds[1];
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
        DoubleSet(r, ds);
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
  
      case OPCODE_IBFE:
      {
        // bottom 5 bits
        ShaderVariable width(
            "", (int32_t)(srcOpers[0].value.i.x & 0x1f), (int32_t)(srcOpers[0].value.i.y & 0x1f),
            (int32_t)(srcOpers[0].value.i.z & 0x1f), (int32_t)(srcOpers[0].value.i.w & 0x1f));
        ShaderVariable offset(
            "", (int32_t)(srcOpers[1].value.i.x & 0x1f), (int32_t)(srcOpers[1].value.i.y & 0x1f),
            (int32_t)(srcOpers[1].value.i.z & 0x1f), (int32_t)(srcOpers[1].value.i.w & 0x1f));
  
        ShaderVariable dest("", (int32_t)0, (int32_t)0, (int32_t)0, (int32_t)0);
  
        for(int comp = 0; comp < 4; comp++)
        {
          if(width.value.iv[comp] == 0)
          {
            dest.value.iv[comp] = 0;
          }
          else if(width.value.iv[comp] + offset.value.iv[comp] < 32)
          {
            dest.value.iv[comp] = srcOpers[2].value.iv[comp]
                                  << (32 - (width.value.iv[comp] + offset.value.iv[comp]));
            dest.value.iv[comp] = dest.value.iv[comp] >> (32 - width.value.iv[comp]);
          }
          else
          {
            dest.value.iv[comp] = srcOpers[2].value.iv[comp] >> offset.value.iv[comp];
          }
        }
  
        s.SetDst(op.operands[0], op, dest);
        break;
      }
      case OPCODE_UBFE:
      {
        // bottom 5 bits
        ShaderVariable width(
            "", (uint32_t)(srcOpers[0].value.u.x & 0x1f), (uint32_t)(srcOpers[0].value.u.y & 0x1f),
            (uint32_t)(srcOpers[0].value.u.z & 0x1f), (uint32_t)(srcOpers[0].value.u.w & 0x1f));
        ShaderVariable offset(
            "", (uint32_t)(srcOpers[1].value.u.x & 0x1f), (uint32_t)(srcOpers[1].value.u.y & 0x1f),
            (uint32_t)(srcOpers[1].value.u.z & 0x1f), (uint32_t)(srcOpers[1].value.u.w & 0x1f));
  
        ShaderVariable dest("", (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
  
        for(int comp = 0; comp < 4; comp++)
        {
          if(width.value.uv[comp] == 0)
          {
            dest.value.uv[comp] = 0;
          }
          else if(width.value.uv[comp] + offset.value.uv[comp] < 32)
          {
            dest.value.uv[comp] = srcOpers[2].value.uv[comp]
                                  << (32 - (width.value.uv[comp] + offset.value.uv[comp]));
            dest.value.uv[comp] = dest.value.uv[comp] >> (32 - width.value.uv[comp]);
          }
          else
          {
            dest.value.uv[comp] = srcOpers[2].value.uv[comp] >> offset.value.uv[comp];
          }
        }
  
        s.SetDst(op.operands[0], op, dest);
        break;
      }
      case OPCODE_BFI:
      {
        // bottom 5 bits
        ShaderVariable width(
            "", (uint32_t)(srcOpers[0].value.u.x & 0x1f), (uint32_t)(srcOpers[0].value.u.y & 0x1f),
            (uint32_t)(srcOpers[0].value.u.z & 0x1f), (uint32_t)(srcOpers[0].value.u.w & 0x1f));
        ShaderVariable offset(
            "", (uint32_t)(srcOpers[1].value.u.x & 0x1f), (uint32_t)(srcOpers[1].value.u.y & 0x1f),
            (uint32_t)(srcOpers[1].value.u.z & 0x1f), (uint32_t)(srcOpers[1].value.u.w & 0x1f));
  
        ShaderVariable dest("", (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
  
        for(int comp = 0; comp < 4; comp++)
        {
          uint32_t bitmask = (((1 << width.value.uv[comp]) - 1) << offset.value.uv[comp]) & 0xffffffff;
          dest.value.uv[comp] =
              (uint32_t)(((srcOpers[2].value.uv[comp] << offset.value.uv[comp]) & bitmask) |
                         (srcOpers[3].value.uv[comp] & ~bitmask));
        }
  
        s.SetDst(op.operands[0], op, dest);
        break;
      }
      case OPCODE_ISHL:
      {
        uint32_t shifts[] = {
            srcOpers[1].value.u.x & 0x1f, srcOpers[1].value.u.y & 0x1f, srcOpers[1].value.u.z & 0x1f,
            srcOpers[1].value.u.w & 0x1f,
        };
  
        // if we were only given a single component, it's the form that shifts all components
        // by the same amount
        if(op.operands[2].numComponents == NUMCOMPS_1 ||
           (op.operands[2].comps[2] < 4 && op.operands[2].comps[2] == 0xff))
          shifts[3] = shifts[2] = shifts[1] = shifts[0];
  
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.i.x << shifts[0], srcOpers[0].value.i.y << shifts[1],
                           srcOpers[0].value.i.z << shifts[2], srcOpers[0].value.i.w << shifts[3]));
        break;
      }
      case OPCODE_USHR:
      {
        uint32_t shifts[] = {
            srcOpers[1].value.u.x & 0x1f, srcOpers[1].value.u.y & 0x1f, srcOpers[1].value.u.z & 0x1f,
            srcOpers[1].value.u.w & 0x1f,
        };
  
        // if we were only given a single component, it's the form that shifts all components
        // by the same amount
        if(op.operands[2].numComponents == NUMCOMPS_1 ||
           (op.operands[2].comps[2] < 4 && op.operands[2].comps[2] == 0xff))
          shifts[3] = shifts[2] = shifts[1] = shifts[0];
  
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.u.x >> shifts[0], srcOpers[0].value.u.y >> shifts[1],
                           srcOpers[0].value.u.z >> shifts[2], srcOpers[0].value.u.w >> shifts[3]));
        break;
      }
      case OPCODE_ISHR:
      {
        uint32_t shifts[] = {
            srcOpers[1].value.u.x & 0x1f, srcOpers[1].value.u.y & 0x1f, srcOpers[1].value.u.z & 0x1f,
            srcOpers[1].value.u.w & 0x1f,
        };
  
        // if we were only given a single component, it's the form that shifts all components
        // by the same amount
        if(op.operands[2].numComponents == NUMCOMPS_1 ||
           (op.operands[2].comps[2] < 4 && op.operands[2].comps[2] == 0xff))
          shifts[3] = shifts[2] = shifts[1] = shifts[0];
  
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.i.x >> shifts[0], srcOpers[0].value.i.y >> shifts[1],
                           srcOpers[0].value.i.z >> shifts[2], srcOpers[0].value.i.w >> shifts[3]));
        break;
      }
      case OPCODE_AND:
        s.SetDst(op.operands[0], op, ShaderVariable("", srcOpers[0].value.i.x & srcOpers[1].value.i.x,
                                                    srcOpers[0].value.i.y & srcOpers[1].value.i.y,
                                                    srcOpers[0].value.i.z & srcOpers[1].value.i.z,
                                                    srcOpers[0].value.i.w & srcOpers[1].value.i.w));
        break;
      case OPCODE_OR:
        s.SetDst(op.operands[0], op, ShaderVariable("", srcOpers[0].value.i.x | srcOpers[1].value.i.x,
                                                    srcOpers[0].value.i.y | srcOpers[1].value.i.y,
                                                    srcOpers[0].value.i.z | srcOpers[1].value.i.z,
                                                    srcOpers[0].value.i.w | srcOpers[1].value.i.w));
        break;
      case OPCODE_XOR:
        s.SetDst(op.operands[0], op, ShaderVariable("", srcOpers[0].value.u.x ^ srcOpers[1].value.u.x,
                                                    srcOpers[0].value.u.y ^ srcOpers[1].value.u.y,
                                                    srcOpers[0].value.u.z ^ srcOpers[1].value.u.z,
                                                    srcOpers[0].value.u.w ^ srcOpers[1].value.u.w));
        break;
      case OPCODE_NOT:
        s.SetDst(op.operands[0], op, ShaderVariable("", ~srcOpers[0].value.u.x, ~srcOpers[0].value.u.y,
                                                    ~srcOpers[0].value.u.z, ~srcOpers[0].value.u.w));
        break;
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // transcendental functions with loose ULP requirements, so we pass them to the GPU to get
      // more accurate (well, LESS accurate but more representative) answers.
  
      case OPCODE_RCP:
      case OPCODE_RSQ:
      case OPCODE_EXP:
      case OPCODE_LOG:
      case OPCODE_SINCOS:
      {
        string csProgram =
            "RWBuffer<float4> outval : register(u0);\n"
            "cbuffer srcOper : register(b0) { float4 inval; };\n"
            "[numthreads(1, 1, 1)]\n"
            "void main() {\n";
  
        switch(op.operation)
        {
          case OPCODE_RCP: csProgram += "outval[0] = rcp(inval);\n"; break;
          case OPCODE_RSQ: csProgram += "outval[0] = rsqrt(inval);\n"; break;
          case OPCODE_EXP: csProgram += "outval[0] = exp2(inval);\n"; break;
          case OPCODE_LOG: csProgram += "outval[0] = log2(inval);\n"; break;
          case OPCODE_SINCOS: csProgram += "sincos(inval, outval[0], outval[1]);\n"; break;
        }
  
        csProgram += "}\n";
  
        ID3D11ComputeShader *cs =
            device->GetShaderCache()->MakeCShader(csProgram.c_str(), "main", "cs_5_0");
  
        ID3D11DeviceContext *context = NULL;
  
        device->GetImmediateContext(&context);
  
        // back up CB/UAV on CS slot 0
  
        ID3D11Buffer *prevCB = NULL;
        ID3D11UnorderedAccessView *prevUAV = NULL;
  
        context->CSGetConstantBuffers(0, 1, &prevCB);
        context->CSGetUnorderedAccessViews(0, 1, &prevUAV);
  
        ID3D11Buffer *constBuf = NULL;
  
        D3D11_BUFFER_DESC cdesc;
  
        cdesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        cdesc.CPUAccessFlags = 0;
        cdesc.MiscFlags = 0;
        cdesc.StructureByteStride = sizeof(Vec4f);
        cdesc.ByteWidth = sizeof(Vec4f);
        cdesc.Usage = D3D11_USAGE_DEFAULT;
  
        D3D11_SUBRESOURCE_DATA operData = {};
        operData.pSysMem = &srcOpers[0].value.uv[0];
        operData.SysMemPitch = sizeof(Vec4f);
        operData.SysMemSlicePitch = sizeof(Vec4f);
  
        if(op.operation == OPCODE_SINCOS)
          operData.pSysMem = &srcOpers[1].value.uv[0];
  
        HRESULT hr = S_OK;
  
        hr = device->CreateBuffer(&cdesc, &operData, &constBuf);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create constant buf HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        context->CSSetConstantBuffers(0, 1, &constBuf);
  
        context->CSSetShader(cs, NULL, 0);
  
        ID3D11UnorderedAccessView *uav = NULL;
  
        ID3D11Buffer *uavBuf = NULL;
        ID3D11Buffer *copyBuf = NULL;
  
        D3D11_BUFFER_DESC bdesc;
  
        bdesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
        bdesc.CPUAccessFlags = 0;
        bdesc.MiscFlags = 0;
        bdesc.StructureByteStride = sizeof(Vec4f);
        bdesc.ByteWidth = sizeof(Vec4f) * 2;
        bdesc.Usage = D3D11_USAGE_DEFAULT;
  
        hr = device->CreateBuffer(&bdesc, NULL, &uavBuf);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create UAV buf HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        bdesc.BindFlags = 0;
        bdesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
        bdesc.Usage = D3D11_USAGE_STAGING;
  
        hr = device->CreateBuffer(&bdesc, NULL, &copyBuf);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create copy buf HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
  
        uavDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
        uavDesc.Buffer.FirstElement = 0;
        uavDesc.Buffer.NumElements = 2;
        uavDesc.Buffer.Flags = 0;
  
        hr = device->CreateUnorderedAccessView(uavBuf, &uavDesc, &uav);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create uav HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        context->CSSetUnorderedAccessViews(0, 1, &uav, NULL);
        context->Dispatch(1, 1, 1);
  
        context->CopyResource(copyBuf, uavBuf);
  
        D3D11_MAPPED_SUBRESOURCE mapped;
        hr = context->Map(copyBuf, 0, D3D11_MAP_READ, 0, &mapped);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to map results HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        ShaderVariable calcResultA("calcA", 0.0f, 0.0f, 0.0f, 0.0f);
        ShaderVariable calcResultB("calcB", 0.0f, 0.0f, 0.0f, 0.0f);
  
        uint32_t *resA = (uint32_t *)mapped.pData;
        uint32_t *resB = resA + 4;
  
        memcpy(calcResultA.value.uv, resA, sizeof(uint32_t) * 4);
        memcpy(calcResultB.value.uv, resB, sizeof(uint32_t) * 4);
  
        context->Unmap(copyBuf, 0);
  
        SAFE_RELEASE(constBuf);
        SAFE_RELEASE(uavBuf);
        SAFE_RELEASE(copyBuf);
        SAFE_RELEASE(uav);
        SAFE_RELEASE(cs);
  
        // restore whatever was on CS slot 0 before we messed with it
  
        UINT append[] = {~0U};
        context->CSSetConstantBuffers(0, 1, &prevCB);
        context->CSSetUnorderedAccessViews(0, 1, &prevUAV, append);
  
        SAFE_RELEASE(context);
  
        SAFE_RELEASE(prevCB);
        SAFE_RELEASE(prevUAV);
  
        if(op.operation == OPCODE_SINCOS)
        {
          if(op.operands[0].type != TYPE_NULL)
            s.SetDst(op.operands[0], op, calcResultA);
          if(op.operands[1].type != TYPE_NULL)
            s.SetDst(op.operands[1], op, calcResultB);
        }
        else
        {
          s.SetDst(op.operands[0], op, calcResultA);
        }
  
        break;
      }
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Misc
  
      case OPCODE_NOP:
      case OPCODE_CUSTOMDATA:
      case OPCODE_SYNC:    // might never need to implement this. Who knows!
        break;
      case OPCODE_DMOV:
      case OPCODE_MOV: s.SetDst(op.operands[0], op, srcOpers[0]); break;
      case OPCODE_DMOVC:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.u.x ? srcOpers[1].value.u.x : srcOpers[2].value.u.x,
                           srcOpers[0].value.u.x ? srcOpers[1].value.u.y : srcOpers[2].value.u.y,
                           srcOpers[0].value.u.y ? srcOpers[1].value.u.z : srcOpers[2].value.u.z,
                           srcOpers[0].value.u.y ? srcOpers[1].value.u.w : srcOpers[2].value.u.w));
        break;
      case OPCODE_MOVC:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[0].value.i.x ? srcOpers[1].value.i.x : srcOpers[2].value.i.x,
                           srcOpers[0].value.i.y ? srcOpers[1].value.i.y : srcOpers[2].value.i.y,
                           srcOpers[0].value.i.z ? srcOpers[1].value.i.z : srcOpers[2].value.i.z,
                           srcOpers[0].value.i.w ? srcOpers[1].value.i.w : srcOpers[2].value.i.w));
        break;
      case OPCODE_SWAPC:
        s.SetDst(
            op.operands[0], op,
            ShaderVariable("", srcOpers[1].value.i.x ? srcOpers[3].value.i.x : srcOpers[2].value.i.x,
                           srcOpers[1].value.i.y ? srcOpers[3].value.i.y : srcOpers[2].value.i.y,
                           srcOpers[1].value.i.z ? srcOpers[3].value.i.z : srcOpers[2].value.i.z,
                           srcOpers[1].value.i.w ? srcOpers[3].value.i.w : srcOpers[2].value.i.w));
  
        s.SetDst(
            op.operands[1], op,
            ShaderVariable("", srcOpers[1].value.i.x ? srcOpers[2].value.i.x : srcOpers[3].value.i.x,
                           srcOpers[1].value.i.y ? srcOpers[2].value.i.y : srcOpers[3].value.i.y,
                           srcOpers[1].value.i.z ? srcOpers[2].value.i.z : srcOpers[3].value.i.z,
                           srcOpers[1].value.i.w ? srcOpers[2].value.i.w : srcOpers[3].value.i.w));
        break;
      case OPCODE_ITOF:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (float)srcOpers[0].value.i.x, (float)srcOpers[0].value.i.y,
                                (float)srcOpers[0].value.i.z, (float)srcOpers[0].value.i.w));
        break;
      case OPCODE_UTOF:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (float)srcOpers[0].value.u.x, (float)srcOpers[0].value.u.y,
                                (float)srcOpers[0].value.u.z, (float)srcOpers[0].value.u.w));
        break;
      case OPCODE_FTOI:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (int)srcOpers[0].value.f.x, (int)srcOpers[0].value.f.y,
                                (int)srcOpers[0].value.f.z, (int)srcOpers[0].value.f.w));
        break;
      case OPCODE_FTOU:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (uint32_t)srcOpers[0].value.f.x, (uint32_t)srcOpers[0].value.f.y,
                                (uint32_t)srcOpers[0].value.f.z, (uint32_t)srcOpers[0].value.f.w));
        break;
      case OPCODE_ITOD:
      case OPCODE_UTOD:
      case OPCODE_FTOD:
      {
        double res[2];
  
        if(op.operation == OPCODE_ITOD)
        {
          res[0] = (double)srcOpers[0].value.i.x;
          res[1] = (double)srcOpers[0].value.i.y;
        }
        else if(op.operation == OPCODE_UTOD)
        {
          res[0] = (double)srcOpers[0].value.u.x;
          res[1] = (double)srcOpers[0].value.u.y;
        }
        else if(op.operation == OPCODE_FTOD)
        {
          res[0] = (double)srcOpers[0].value.f.x;
          res[1] = (double)srcOpers[0].value.f.y;
        }
  
        // if we only did a 1-wide double op, copy .xy into .zw so we can then
        // swizzle into .xy or .zw freely on the destination operand.
        // e.g. ftod r0.zw, r0.z - if we didn't do this, there'd be nothing valid in .zw
        if(op.operands[1].comps[2] == 0xff)
          res[1] = res[0];
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
        DoubleSet(r, res);
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
      case OPCODE_DTOI:
      case OPCODE_DTOU:
      case OPCODE_DTOF:
      {
        double src[2];
        DoubleGet(srcOpers[0], src);
  
        // special behaviour for dest mask. if it's .xz then first goes into .x, second into .z.
        // if the mask is .y then the first goes into .y and second goes nowhere.
        // so we need to check the dest mask and put the results into the right place
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
  
        if(op.operation == OPCODE_DTOU)
        {
          if(op.operands[0].comps[1] == 0xff)    // only one mask
          {
            r.value.uv[op.operands[0].comps[0]] = uint32_t(src[0]);
          }
          else
          {
            r.value.uv[op.operands[0].comps[0]] = uint32_t(src[0]);
            r.value.uv[op.operands[0].comps[1]] = uint32_t(src[1]);
          }
        }
        else if(op.operation == OPCODE_DTOI)
        {
          if(op.operands[0].comps[1] == 0xff)    // only one mask
          {
            r.value.iv[op.operands[0].comps[0]] = int32_t(src[0]);
          }
          else
          {
            r.value.iv[op.operands[0].comps[0]] = int32_t(src[0]);
            r.value.iv[op.operands[0].comps[1]] = int32_t(src[1]);
          }
        }
        else if(op.operation == OPCODE_DTOF)
        {
          if(op.operands[0].comps[1] == 0xff)    // only one mask
          {
            r.value.fv[op.operands[0].comps[0]] = float(src[0]);
          }
          else
          {
            r.value.fv[op.operands[0].comps[0]] = float(src[0]);
            r.value.fv[op.operands[0].comps[1]] = float(src[1]);
          }
        }
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Comparison
  
      case OPCODE_EQ:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.f.x == srcOpers[1].value.f.x ? ~0l : 0l),
                                (srcOpers[0].value.f.y == srcOpers[1].value.f.y ? ~0l : 0l),
                                (srcOpers[0].value.f.z == srcOpers[1].value.f.z ? ~0l : 0l),
                                (srcOpers[0].value.f.w == srcOpers[1].value.f.w ? ~0l : 0l)));
        break;
      case OPCODE_NE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.f.x != srcOpers[1].value.f.x ? ~0l : 0l),
                                (srcOpers[0].value.f.y != srcOpers[1].value.f.y ? ~0l : 0l),
                                (srcOpers[0].value.f.z != srcOpers[1].value.f.z ? ~0l : 0l),
                                (srcOpers[0].value.f.w != srcOpers[1].value.f.w ? ~0l : 0l)));
        break;
      case OPCODE_LT:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.f.x < srcOpers[1].value.f.x ? ~0l : 0l),
                                (srcOpers[0].value.f.y < srcOpers[1].value.f.y ? ~0l : 0l),
                                (srcOpers[0].value.f.z < srcOpers[1].value.f.z ? ~0l : 0l),
                                (srcOpers[0].value.f.w < srcOpers[1].value.f.w ? ~0l : 0l)));
        break;
      case OPCODE_GE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.f.x >= srcOpers[1].value.f.x ? ~0l : 0l),
                                (srcOpers[0].value.f.y >= srcOpers[1].value.f.y ? ~0l : 0l),
                                (srcOpers[0].value.f.z >= srcOpers[1].value.f.z ? ~0l : 0l),
                                (srcOpers[0].value.f.w >= srcOpers[1].value.f.w ? ~0l : 0l)));
        break;
      case OPCODE_DEQ:
      case OPCODE_DNE:
      case OPCODE_DGE:
      case OPCODE_DLT:
      {
        double src0[2], src1[2];
        DoubleGet(srcOpers[0], src0);
        DoubleGet(srcOpers[1], src1);
  
        uint32_t cmp1 = 0;
        uint32_t cmp2 = 0;
  
        switch(op.operation)
        {
          case OPCODE_DEQ:
            cmp1 = (src0[0] == src1[0] ? ~0l : 0l);
            cmp2 = (src0[1] == src1[1] ? ~0l : 0l);
            break;
          case OPCODE_DNE:
            cmp1 = (src0[0] != src1[0] ? ~0l : 0l);
            cmp2 = (src0[1] != src1[1] ? ~0l : 0l);
            break;
          case OPCODE_DGE:
            cmp1 = (src0[0] >= src1[0] ? ~0l : 0l);
            cmp2 = (src0[1] >= src1[1] ? ~0l : 0l);
            break;
          case OPCODE_DLT:
            cmp1 = (src0[0] < src1[0] ? ~0l : 0l);
            cmp2 = (src0[1] < src1[1] ? ~0l : 0l);
            break;
        }
  
        // special behaviour for dest mask. if it's .xz then first comparison goes into .x, second
        // into .z.
        // if the mask is .y then the first comparison goes into .y and second goes nowhere.
        // so we need to check the dest mask and put the comparison results into the right place
  
        ShaderVariable r("", 0U, 0U, 0U, 0U);
  
        if(op.operands[0].comps[1] == 0xff)    // only one mask
        {
          r.value.uv[op.operands[0].comps[0]] = cmp1;
        }
        else
        {
          r.value.uv[op.operands[0].comps[0]] = cmp1;
          r.value.uv[op.operands[0].comps[1]] = cmp2;
        }
  
        s.SetDst(op.operands[0], op, r);
        break;
      }
      case OPCODE_IEQ:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.i.x == srcOpers[1].value.i.x ? ~0l : 0l),
                                (srcOpers[0].value.i.y == srcOpers[1].value.i.y ? ~0l : 0l),
                                (srcOpers[0].value.i.z == srcOpers[1].value.i.z ? ~0l : 0l),
                                (srcOpers[0].value.i.w == srcOpers[1].value.i.w ? ~0l : 0l)));
        break;
      case OPCODE_INE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.i.x != srcOpers[1].value.i.x ? ~0l : 0l),
                                (srcOpers[0].value.i.y != srcOpers[1].value.i.y ? ~0l : 0l),
                                (srcOpers[0].value.i.z != srcOpers[1].value.i.z ? ~0l : 0l),
                                (srcOpers[0].value.i.w != srcOpers[1].value.i.w ? ~0l : 0l)));
        break;
      case OPCODE_IGE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.i.x >= srcOpers[1].value.i.x ? ~0l : 0l),
                                (srcOpers[0].value.i.y >= srcOpers[1].value.i.y ? ~0l : 0l),
                                (srcOpers[0].value.i.z >= srcOpers[1].value.i.z ? ~0l : 0l),
                                (srcOpers[0].value.i.w >= srcOpers[1].value.i.w ? ~0l : 0l)));
        break;
      case OPCODE_ILT:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.i.x < srcOpers[1].value.i.x ? ~0l : 0l),
                                (srcOpers[0].value.i.y < srcOpers[1].value.i.y ? ~0l : 0l),
                                (srcOpers[0].value.i.z < srcOpers[1].value.i.z ? ~0l : 0l),
                                (srcOpers[0].value.i.w < srcOpers[1].value.i.w ? ~0l : 0l)));
        break;
      case OPCODE_ULT:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.u.x < srcOpers[1].value.u.x ? ~0l : 0l),
                                (srcOpers[0].value.u.y < srcOpers[1].value.u.y ? ~0l : 0l),
                                (srcOpers[0].value.u.z < srcOpers[1].value.u.z ? ~0l : 0l),
                                (srcOpers[0].value.u.w < srcOpers[1].value.u.w ? ~0l : 0l)));
        break;
      case OPCODE_UGE:
        s.SetDst(op.operands[0], op,
                 ShaderVariable("", (srcOpers[0].value.u.x >= srcOpers[1].value.u.x ? ~0l : 0l),
                                (srcOpers[0].value.u.y >= srcOpers[1].value.u.y ? ~0l : 0l),
                                (srcOpers[0].value.u.z >= srcOpers[1].value.u.z ? ~0l : 0l),
                                (srcOpers[0].value.u.w >= srcOpers[1].value.u.w ? ~0l : 0l)));
        break;
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Atomic instructions
  
      case OPCODE_IMM_ATOMIC_ALLOC:
      {
        uint32_t count = global.uavs[srcOpers[0].value.u.x].hiddenCounter++;
        s.SetDst(op.operands[0], op, ShaderVariable("", count, count, count, count));
        break;
      }
  
      case OPCODE_IMM_ATOMIC_CONSUME:
      {
        uint32_t count = --global.uavs[srcOpers[0].value.u.x].hiddenCounter;
        s.SetDst(op.operands[0], op, ShaderVariable("", count, count, count, count));
        break;
      }
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Derivative instructions
  
      // don't differentiate, coarse, fine, whatever. The spec lets us implement it all as fine.
      case OPCODE_DERIV_RTX:
      case OPCODE_DERIV_RTX_COARSE:
      case OPCODE_DERIV_RTX_FINE:
        if(quad == NULL)
          RDCERR(
              "Attempt to use derivative instruction not in pixel shader. Undefined results will "
              "occur!");
        else
          s.SetDst(op.operands[0], op,
                   s.DDX(op.operation == OPCODE_DERIV_RTX_FINE, quad, op.operands[1], op));
        break;
      case OPCODE_DERIV_RTY:
      case OPCODE_DERIV_RTY_COARSE:
      case OPCODE_DERIV_RTY_FINE:
        if(quad == NULL)
          RDCERR(
              "Attempt to use derivative instruction not in pixel shader. Undefined results will "
              "occur!");
        else
          s.SetDst(op.operands[0], op,
                   s.DDY(op.operation == OPCODE_DERIV_RTY_FINE, quad, op.operands[1], op));
        break;
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Buffer/Texture load and store
  
      // handle atomic operations all together
      case OPCODE_ATOMIC_IADD:
      case OPCODE_ATOMIC_IMAX:
      case OPCODE_ATOMIC_IMIN:
      case OPCODE_ATOMIC_AND:
      case OPCODE_ATOMIC_OR:
      case OPCODE_ATOMIC_XOR:
      case OPCODE_ATOMIC_CMP_STORE:
      case OPCODE_ATOMIC_UMAX:
      case OPCODE_ATOMIC_UMIN:
      case OPCODE_IMM_ATOMIC_IADD:
      case OPCODE_IMM_ATOMIC_IMAX:
      case OPCODE_IMM_ATOMIC_IMIN:
      case OPCODE_IMM_ATOMIC_AND:
      case OPCODE_IMM_ATOMIC_OR:
      case OPCODE_IMM_ATOMIC_XOR:
      case OPCODE_IMM_ATOMIC_EXCH:
      case OPCODE_IMM_ATOMIC_CMP_EXCH:
      case OPCODE_IMM_ATOMIC_UMAX:
      case OPCODE_IMM_ATOMIC_UMIN:
      {
        ASMOperand beforeResult;
        uint32_t resIndex = 0;
        ShaderVariable *dstAddress = NULL;
        ShaderVariable *src0 = NULL;
        ShaderVariable *src1 = NULL;
        bool gsm = false;
  
        if(op.operation == OPCODE_IMM_ATOMIC_IADD || op.operation == OPCODE_IMM_ATOMIC_IMAX ||
           op.operation == OPCODE_IMM_ATOMIC_IMIN || op.operation == OPCODE_IMM_ATOMIC_AND ||
           op.operation == OPCODE_IMM_ATOMIC_OR || op.operation == OPCODE_IMM_ATOMIC_XOR ||
           op.operation == OPCODE_IMM_ATOMIC_EXCH || op.operation == OPCODE_IMM_ATOMIC_CMP_EXCH ||
           op.operation == OPCODE_IMM_ATOMIC_UMAX || op.operation == OPCODE_IMM_ATOMIC_UMIN)
        {
          beforeResult = op.operands[0];
          resIndex = (uint32_t)op.operands[1].indices[0].index;
          gsm = (op.operands[1].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          dstAddress = &srcOpers[1];
          src0 = &srcOpers[2];
          if(srcOpers.size() > 3)
            src1 = &srcOpers[3];
        }
        else
        {
          beforeResult.type = TYPE_NULL;
          resIndex = (uint32_t)op.operands[0].indices[0].index;
          gsm = (op.operands[0].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          dstAddress = &srcOpers[0];
          src0 = &srcOpers[1];
          if(srcOpers.size() > 2)
            src1 = &srcOpers[2];
        }
  
        uint32_t stride = 4;
        uint32_t offset = 0;
        uint32_t numElems = 0;
        bool structured = false;
  
        byte *data = NULL;
  
        if(gsm)
        {
          offset = 0;
          if(resIndex > global.groupshared.size())
          {
            numElems = 0;
            stride = 4;
            data = NULL;
          }
          else
          {
            numElems = global.groupshared[resIndex].count;
            stride = global.groupshared[resIndex].bytestride;
            data = &global.groupshared[resIndex].data[0];
            structured = global.groupshared[resIndex].structured;
          }
        }
        else
        {
          offset = global.uavs[resIndex].firstElement;
          numElems = global.uavs[resIndex].numElements;
          data = &global.uavs[resIndex].data[0];
  
          for(size_t i = 0; i < s.dxbc->GetNumDeclarations(); i++)
          {
            const DXBC::ASMDecl &decl = dxbc->GetDeclaration(i);
  
            if(decl.operand.type == TYPE_UNORDERED_ACCESS_VIEW &&
               decl.operand.indices[0].index == resIndex)
            {
              if(decl.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW)
              {
                stride = 4;
                structured = false;
                break;
              }
              else if(decl.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED)
              {
                stride = decl.stride;
                structured = true;
                break;
              }
            }
          }
        }
  
        RDCASSERT(data);
  
        // seems like .x is element index, and .y is byte address, in the dstAddress operand
        //
        // "Out of bounds addressing on u# causes nothing to be written to memory, except if the
        //  u# is structured, and byte offset into the struct (second component of the address) is
        //  causing the out of bounds access, then the entire contents of the UAV become undefined."
        //
        // "The number of components taken from the address is determined by the dimensionality of dst
        // u# or g#."
  
        if(data)
        {
          data += (offset + dstAddress->value.u.x) * stride;
          if(structured)
            data += dstAddress->value.u.y;
        }
  
        // if out of bounds, undefined result is returned to dst0 for immediate operands,
        // so we only need to care about the in-bounds case.
        // Also helper/inactive pixels are not allowed to modify UAVs
        if(data && offset + dstAddress->value.u.x < numElems && !Finished())
        {
          uint32_t *udst = (uint32_t *)data;
          int32_t *idst = (int32_t *)data;
  
          if(beforeResult.type != TYPE_NULL)
          {
            s.SetDst(beforeResult, op, ShaderVariable("", *udst, *udst, *udst, *udst));
          }
  
          // not verified below since by definition the operations that expect usrc1 will have it
          uint32_t *usrc0 = src0 ? src0->value.uv : NULL;
          uint32_t *usrc1 = src1 ? src1->value.uv : NULL;
  
          int32_t *isrc0 = src0 ? src0->value.iv : NULL;
  
          switch(op.operation)
          {
            case OPCODE_IMM_ATOMIC_IADD:
            case OPCODE_ATOMIC_IADD: *udst = *udst + *usrc0; break;
            case OPCODE_IMM_ATOMIC_IMAX:
            case OPCODE_ATOMIC_IMAX: *idst = std::max(*idst, *isrc0); break;
            case OPCODE_IMM_ATOMIC_IMIN:
            case OPCODE_ATOMIC_IMIN: *idst = std::min(*idst, *isrc0); break;
            case OPCODE_IMM_ATOMIC_AND:
            case OPCODE_ATOMIC_AND: *udst = *udst & *usrc0; break;
            case OPCODE_IMM_ATOMIC_OR:
            case OPCODE_ATOMIC_OR: *udst = *udst | *usrc0; break;
            case OPCODE_IMM_ATOMIC_XOR:
            case OPCODE_ATOMIC_XOR: *udst = *udst ^ *usrc0; break;
            case OPCODE_IMM_ATOMIC_EXCH: *udst = *usrc0; break;
            case OPCODE_IMM_ATOMIC_CMP_EXCH:
            case OPCODE_ATOMIC_CMP_STORE:
              if(*udst == *usrc1)
                *udst = *usrc0;
              break;
            case OPCODE_IMM_ATOMIC_UMAX:
            case OPCODE_ATOMIC_UMAX: *udst = std::max(*udst, *usrc0); break;
            case OPCODE_IMM_ATOMIC_UMIN:
            case OPCODE_ATOMIC_UMIN: *udst = std::min(*udst, *usrc0); break;
          }
        }
  
        break;
      }
  
      // store and load paths are mostly identical
      case OPCODE_STORE_UAV_TYPED:
      case OPCODE_STORE_RAW:
      case OPCODE_STORE_STRUCTURED:
  
      case OPCODE_LD_RAW:
      case OPCODE_LD_UAV_TYPED:
      case OPCODE_LD_STRUCTURED:
      {
        uint32_t resIndex = 0;
        uint32_t elemOffset = 0;
        uint32_t elemIdx = 0;
  
        uint32_t texCoords[3] = {0, 0, 0};
  
        uint32_t stride = 0;
  
        bool srv = true;
        bool gsm = false;
  
        bool load = true;
  
        if(op.operation == OPCODE_STORE_UAV_TYPED || op.operation == OPCODE_STORE_RAW ||
           op.operation == OPCODE_STORE_STRUCTURED)
        {
          load = false;
        }
  
        if(load)
          s.flags = ShaderEvents::SampleLoadGather;
  
        if(op.operation == OPCODE_LD_STRUCTURED || op.operation == OPCODE_STORE_STRUCTURED)
        {
          if(load)
          {
            resIndex = (uint32_t)op.operands[3].indices[0].index;
            srv = (op.operands[3].type == TYPE_RESOURCE);
            gsm = (op.operands[3].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
  
            stride = op.stride;
          }
          else
          {
            resIndex = (uint32_t)op.operands[0].indices[0].index;
            srv = false;
            gsm = (op.operands[0].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          }
  
          if(stride == 0)
          {
            if(gsm && resIndex < global.groupshared.size())
            {
              stride = global.groupshared[resIndex].bytestride;
            }
            else if(!gsm)
            {
              for(size_t i = 0; i < s.dxbc->GetNumDeclarations(); i++)
              {
                const DXBC::ASMDecl &decl = dxbc->GetDeclaration(i);
  
                if(decl.operand.type == TYPE_UNORDERED_ACCESS_VIEW && !srv &&
                   decl.operand.indices[0].index == resIndex &&
                   decl.declaration == OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED)
                {
                  stride = decl.stride;
                  break;
                }
                if(decl.operand.type == TYPE_RESOURCE && srv &&
                   decl.operand.indices[0].index == resIndex &&
                   decl.declaration == OPCODE_DCL_RESOURCE_STRUCTURED)
                {
                  stride = decl.stride;
                  break;
                }
              }
            }
          }
  
          elemOffset = srcOpers[1].value.u.x;
          elemIdx = srcOpers[0].value.u.x;
        }
        else if(op.operation == OPCODE_LD_UAV_TYPED || op.operation == OPCODE_STORE_UAV_TYPED)
        {
          if(load)
          {
            resIndex = (uint32_t)op.operands[2].indices[0].index;
            gsm = (op.operands[2].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          }
          else
          {
            resIndex = (uint32_t)op.operands[0].indices[0].index;
            gsm = (op.operands[0].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          }
  
          elemIdx = srcOpers[0].value.u.x;
  
          // could be a tex load
          texCoords[0] = srcOpers[0].value.u.x;
          texCoords[1] = srcOpers[0].value.u.y;
          texCoords[2] = srcOpers[0].value.u.z;
  
          stride = 4;
          srv = false;
        }
        else if(op.operation == OPCODE_LD_RAW || op.operation == OPCODE_STORE_RAW)
        {
          if(load)
          {
            resIndex = (uint32_t)op.operands[2].indices[0].index;
            srv = (op.operands[2].type == TYPE_RESOURCE);
            gsm = (op.operands[2].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          }
          else
          {
            resIndex = (uint32_t)op.operands[0].indices[0].index;
            srv = false;
            gsm = (op.operands[0].type == TYPE_THREAD_GROUP_SHARED_MEMORY);
          }
  
          elemIdx = srcOpers[0].value.u.x;
          stride = 1;
        }
  
        RDCASSERT(stride != 0);
  
        uint32_t offset = srv ? global.srvs[resIndex].firstElement : global.uavs[resIndex].firstElement;
        uint32_t numElems = srv ? global.srvs[resIndex].numElements : global.uavs[resIndex].numElements;
        GlobalState::ViewFmt fmt = srv ? global.srvs[resIndex].format : global.uavs[resIndex].format;
  
        // indexing for raw views is in bytes, but firstElement/numElements is in format-sized
        // units. Multiply up by bytesize
        if(op.operation == OPCODE_LD_RAW || op.operation == OPCODE_STORE_RAW)
        {
          offset *= fmt.byteWidth;
          numElems *= fmt.byteWidth;
        }
  
        byte *data = srv ? &global.srvs[resIndex].data[0] : &global.uavs[resIndex].data[0];
        bool texData = srv ? false : global.uavs[resIndex].tex;
        uint32_t rowPitch = srv ? 0 : global.uavs[resIndex].rowPitch;
        uint32_t depthPitch = srv ? 0 : global.uavs[resIndex].depthPitch;
  
        if(load && !srv && !gsm && (fmt.numComps != 1 || fmt.byteWidth != 4))
        {
          device->AddDebugMessage(
              MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
              StringFormat::Fmt(
                  "Shader debugging %d: %s\n"
                  "UAV loads aren't supported from anything but 32-bit single channel resources",
                  s.nextInstruction - 1, op.str.c_str()));
        }
  
        if(gsm)
        {
          offset = 0;
          if(resIndex > global.groupshared.size())
          {
            numElems = 0;
            stride = 4;
            data = NULL;
          }
          else
          {
            numElems = global.groupshared[resIndex].count;
            stride = global.groupshared[resIndex].bytestride;
            data = &global.groupshared[resIndex].data[0];
            fmt.fmt = CompType::UInt;
            fmt.byteWidth = 4;
            fmt.numComps = global.groupshared[resIndex].bytestride / 4;
            fmt.reversed = false;
            fmt.stride = 0;
          }
          texData = false;
        }
  
        RDCASSERT(data);
  
        size_t texOffset = 0;
  
        if(texData)
        {
          texOffset += texCoords[0] * fmt.Stride();
          texOffset += texCoords[1] * rowPitch;
          texOffset += texCoords[2] * depthPitch;
        }
  
        if(!data || (!texData && elemIdx >= numElems) ||
           (texData && texOffset >= global.uavs[resIndex].data.size()))
        {
          if(load)
            s.SetDst(op.operands[0], op, ShaderVariable("", 0U, 0U, 0U, 0U));
        }
        else
        {
          if(gsm || !texData)
          {
            data += (offset + elemIdx) * stride;
            data += elemOffset;
          }
          else
          {
            data += texOffset;
          }
  
          uint32_t *datau32 = (uint32_t *)data;
  
          int maxIndex = fmt.numComps;
  
          uint32_t srcIdx = 1;
          if(op.operation == OPCODE_STORE_STRUCTURED || op.operation == OPCODE_LD_STRUCTURED)
          {
            srcIdx = 2;
            maxIndex = (stride - elemOffset) / sizeof(uint32_t);
          }
          // raw loads/stores can come from any component (as long as it's within range of the data!)
          if(op.operation == OPCODE_LD_RAW || op.operation == OPCODE_STORE_RAW)
          {
            maxIndex = 4;
          }
  
          if(load)
          {
            ShaderVariable fetch("", 0U, 0U, 0U, 0U);
  
            for(int i = 0; i < 4; i++)
            {
              uint8_t comp = op.operands[srcIdx + 1].comps[i];
              if(op.operands[srcIdx + 1].comps[i] == 0xff || comp >= maxIndex)
                comp = 0;
  
              fetch.value.uv[i] = datau32[comp];
            }
  
            // if we are assigning into a scalar, SetDst expects the result to be in .x (as normally
            // we are assigning FROM a scalar also).
            // to match this expectation, propogate the component across.
            if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff &&
               op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff)
              fetch.value.uv[0] = fetch.value.uv[op.operands[0].comps[0]];
  
            s.SetDst(op.operands[0], op, fetch);
          }
          else if(!Finished())    // helper/inactive pixels can't modify UAVs
          {
            for(int i = 0; i < 4; i++)
            {
              uint8_t comp = op.operands[0].comps[i];
              // masks must be contiguous from x, if we reach the 'end' we're done
              if(comp == 0xff || comp >= maxIndex)
                break;
  
              datau32[i] = srcOpers[srcIdx].value.uv[i];
            }
          }
        }
  
        break;
      }
  
      case OPCODE_EVAL_CENTROID:
      case OPCODE_EVAL_SAMPLE_INDEX:
      case OPCODE_EVAL_SNAPPED:
      {
        // opcodes only seem to be supported for regular inputs
        RDCASSERT(op.operands[1].type == TYPE_INPUT);
  
        GlobalState::SampleEvalCacheKey key;
  
        key.quadIndex = quadIndex;
  
        // if this is TYPE_INPUT we can look up the index directly
        key.inputRegisterIndex = (int32_t)op.operands[1].indices[0].index;
  
        for(int c = 0; c < 4; c++)
        {
          if(op.operands[0].comps[c] == 0xff)
            break;
  
          key.numComponents = c + 1;
        }
  
        key.firstComponent = op.operands[1].comps[op.operands[0].comps[0]];
  
        if(op.operation == OPCODE_EVAL_SAMPLE_INDEX)
        {
          key.sample = srcOpers[1].value.i.x;
        }
        else if(op.operation == OPCODE_EVAL_SNAPPED)
        {
          key.offsetx = RDCCLAMP(srcOpers[1].value.i.x, -8, 7);
          key.offsety = RDCCLAMP(srcOpers[1].value.i.y, -8, 7);
        }
        else if(op.operation == OPCODE_EVAL_CENTROID)
        {
          // OPCODE_EVAL_CENTROID is the default, -1 sample and 0,0 offset
        }
  
        // look up this combination in the cache, if we get a hit then return that value.
        auto it = global.sampleEvalCache.find(key);
        if(it != global.sampleEvalCache.end())
        {
          // perform source operand swizzling
          ShaderVariable var = it->second;
  
          for(int i = 0; i < 4; i++)
            if(op.operands[1].comps[i] < 4)
              var.value.uv[i] = it->second.value.uv[op.operands[1].comps[i]];
  
          s.SetDst(op.operands[0], op, var);
        }
        else
        {
          // if we got here, either the cache is empty (we're not rendering MSAA at all) so we should
          // just return the interpolant, or something went wrong and the item we want isn't cached so
          // the best we can do is return the interpolant.
  
          if(!global.sampleEvalCache.empty())
          {
            device->AddDebugMessage(
                MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                StringFormat::Fmt(
                    "Shader debugging %d: %s\n"
                    "No sample evaluate found in cache. Possible out-of-bounds sample index",
                    s.nextInstruction - 1, op.str.c_str()));
          }
  
          s.SetDst(op.operands[0], op, srcOpers[0]);
        }
  
        break;
      }
  
      case OPCODE_SAMPLE_INFO:
      case OPCODE_SAMPLE_POS:
      {
        ID3D11DeviceContext *context = NULL;
        device->GetImmediateContext(&context);
  
        ShaderVariable result("", 0U, 0U, 0U, 0U);
  
        ID3D11Resource *res = NULL;
  
        if(op.operands[1].type == TYPE_RASTERIZER)
        {
          ID3D11RenderTargetView *rtv[8] = {};
          ID3D11DepthStencilView *dsv = NULL;
  
          context->OMGetRenderTargets(8, rtv, &dsv);
  
          // try depth first - both should match sample count though to be valid
          if(dsv)
          {
            dsv->GetResource(&res);
          }
          else
          {
            for(size_t i = 0; i < ARRAY_COUNT(rtv); i++)
            {
              if(rtv[i])
              {
                rtv[i]->GetResource(&res);
                break;
              }
            }
          }
  
          if(!res)
          {
            RDCWARN("No targets bound for output when calling sampleinfo on rasterizer");
  
            device->AddDebugMessage(
                MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                StringFormat::Fmt("Shader debugging %d: %s\n"
                                  "No targets bound for output when calling sampleinfo on rasterizer",
                                  s.nextInstruction - 1, op.str.c_str()));
          }
  
          for(size_t i = 0; i < ARRAY_COUNT(rtv); i++)
            SAFE_RELEASE(rtv[i]);
          SAFE_RELEASE(dsv);
        }
        else if(op.operands[1].type == TYPE_RESOURCE && op.operands[1].indices.size() == 1 &&
                op.operands[1].indices[0].absolute && !op.operands[1].indices[0].relative)
        {
          UINT slot = (UINT)(op.operands[1].indices[0].index & 0xffffffff);
  
          ID3D11ShaderResourceView *srv = NULL;
          if(s.dxbc->m_Type == D3D11_ShaderType_Vertex)
            context->VSGetShaderResources(slot, 1, &srv);
          else if(s.dxbc->m_Type == D3D11_ShaderType_Hull)
            context->HSGetShaderResources(slot, 1, &srv);
          else if(s.dxbc->m_Type == D3D11_ShaderType_Domain)
            context->DSGetShaderResources(slot, 1, &srv);
          else if(s.dxbc->m_Type == D3D11_ShaderType_Geometry)
            context->GSGetShaderResources(slot, 1, &srv);
          else if(s.dxbc->m_Type == D3D11_ShaderType_Pixel)
            context->PSGetShaderResources(slot, 1, &srv);
          else if(s.dxbc->m_Type == D3D11_ShaderType_Compute)
            context->CSGetShaderResources(slot, 1, &srv);
  
          if(srv)
          {
            srv->GetResource(&res);
          }
          else
          {
            RDCWARN("SRV is NULL being queried by sampleinfo");
  
            device->AddDebugMessage(
                MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                StringFormat::Fmt("Shader debugging %d: %s\nSRV is NULL being queried by sampleinfo",
                                  s.nextInstruction - 1, op.str.c_str()));
          }
  
          SAFE_RELEASE(srv);
        }
        else
        {
          RDCWARN("unexpected operand type to sample_info");
        }
  
        if(res)
        {
          D3D11_RESOURCE_DIMENSION dim = D3D11_RESOURCE_DIMENSION_UNKNOWN;
          res->GetType(&dim);
  
          if(dim == D3D11_RESOURCE_DIMENSION_TEXTURE2D)
          {
            D3D11_TEXTURE2D_DESC desc;
            ((ID3D11Texture2D *)res)->GetDesc(&desc);
  
            // returns 1 for non-multisampled resources
            result.value.u.x = RDCMAX(1U, desc.SampleDesc.Count);
          }
          else
          {
            if(op.operands[1].type == TYPE_RASTERIZER)
            {
              // special behaviour for non-2D (i.e. by definition non-multisampled) textures when
              // querying the rasterizer, just return 1.
              result.value.u.x = 1;
            }
            else
            {
              device->AddDebugMessage(
                  MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                  StringFormat::Fmt("Shader debugging %d: %s\nResource specified is not a 2D texture",
                                    s.nextInstruction - 1, op.str.c_str()));
  
              result.value.u.x = 0;
            }
          }
  
          SAFE_RELEASE(res);
        }
  
        // "If there is no resource bound to the specified slot, 0 is returned."
  
        // lookup sample pos if we got a count from above
        if(op.operation == OPCODE_SAMPLE_POS && result.value.u.x > 0 &&
           op.operands[2].type == TYPE_IMMEDIATE32)
        {
          // assume standard sample pattern - this might not hold in all cases
          // http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
  
          uint32_t sampleIndex = op.operands[2].values[0];
          uint32_t sampleCount = result.value.u.x;
  
          if(sampleIndex >= sampleCount)
          {
            RDCWARN("sample index %u is out of bounds on resource bound to sample_pos (%u samples)",
                    sampleIndex, sampleCount);
          }
          else
          {
            const float *sample_pattern = NULL;
  
  // co-ordinates are given as (i,j) in 16ths of a pixel
  #define _SMP(c) ((c) / 16.0f)
  
            if(sampleCount == 1)
            {
              RDCWARN("Non-multisampled texture being passed to sample_pos");
  
              device->AddDebugMessage(
                  MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                  StringFormat::Fmt(
                      "Shader debugging %d: %s\nNon-multisampled texture being passed to sample_pos",
                      s.nextInstruction - 1, op.str.c_str()));
  
              sample_pattern = NULL;
            }
            else if(sampleCount == 2)
            {
              static const float pattern_2x[] = {
                  _SMP(4.0f), _SMP(4.0f), _SMP(-4.0f), _SMP(-4.0f),
              };
  
              sample_pattern = &pattern_2x[0];
            }
            else if(sampleCount == 4)
            {
              static const float pattern_4x[] = {
                  _SMP(-2.0f), _SMP(-6.0f), _SMP(6.0f), _SMP(-2.0f),
                  _SMP(-6.0f), _SMP(2.0f),  _SMP(2.0f), _SMP(6.0f),
              };
  
              sample_pattern = &pattern_4x[0];
            }
            else if(sampleCount == 8)
            {
              static const float pattern_8x[] = {
                  _SMP(1.0f),  _SMP(-3.0f), _SMP(-1.0f), _SMP(3.0f),  _SMP(5.0f),  _SMP(1.0f),
                  _SMP(-3.0f), _SMP(-5.0f), _SMP(-5.0f), _SMP(5.0f),  _SMP(-7.0f), _SMP(-1.0f),
                  _SMP(3.0f),  _SMP(7.0f),  _SMP(7.0f),  _SMP(-7.0f),
              };
  
              sample_pattern = &pattern_8x[0];
            }
            else if(sampleCount == 16)
            {
              static const float pattern_16x[] = {
                  _SMP(1.0f),  _SMP(1.0f),  _SMP(-1.0f), _SMP(-3.0f), _SMP(-3.0f), _SMP(2.0f),
                  _SMP(4.0f),  _SMP(-1.0f), _SMP(-5.0f), _SMP(-2.0f), _SMP(2.0f),  _SMP(5.0f),
                  _SMP(5.0f),  _SMP(3.0f),  _SMP(3.0f),  _SMP(-5.0f), _SMP(-2.0f), _SMP(6.0f),
                  _SMP(0.0f),  _SMP(-7.0f), _SMP(-4.0f), _SMP(-6.0f), _SMP(-6.0f), _SMP(4.0f),
                  _SMP(-8.0f), _SMP(0.0f),  _SMP(7.0f),  _SMP(-4.0f), _SMP(6.0f),  _SMP(7.0f),
                  _SMP(-7.0f), _SMP(-8.0f),
              };
  
              sample_pattern = &pattern_16x[0];
            }
            else    // unsupported sample count
            {
              RDCERR("Unsupported sample count on resource for sample_pos: %u", result.value.u.x);
  
              sample_pattern = NULL;
            }
  
            if(sample_pattern == NULL)
            {
              result.value.f.x = 0.0f;
              result.value.f.y = 0.0f;
            }
            else
            {
              result.value.f.x = sample_pattern[sampleIndex * 2 + 0];
              result.value.f.y = sample_pattern[sampleIndex * 2 + 1];
            }
          }
  
  #undef _SMP
        }
  
        // apply swizzle
        ShaderVariable swizzled("", 0.0f, 0.0f, 0.0f, 0.0f);
  
        for(int i = 0; i < 4; i++)
        {
          if(op.operands[1].comps[i] == 0xff)
            swizzled.value.uv[i] = result.value.uv[0];
          else
            swizzled.value.uv[i] = result.value.uv[op.operands[1].comps[i]];
        }
  
        // apply ret type
        if(op.operation == OPCODE_SAMPLE_POS)
        {
          result = swizzled;
          result.type = VarType::Float;
        }
        else if(op.resinfoRetType == RETTYPE_FLOAT)
        {
          result.value.f.x = (float)swizzled.value.u.x;
          result.value.f.y = (float)swizzled.value.u.y;
          result.value.f.z = (float)swizzled.value.u.z;
          result.value.f.w = (float)swizzled.value.u.w;
          result.type = VarType::Float;
        }
        else
        {
          result = swizzled;
          result.type = VarType::UInt;
        }
  
        // if we are assigning into a scalar, SetDst expects the result to be in .x (as normally we
        // are assigning FROM a scalar also).
        // to match this expectation, propogate the component across.
        if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff &&
           op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff)
          result.value.uv[0] = result.value.uv[op.operands[0].comps[0]];
  
        s.SetDst(op.operands[0], op, result);
  
        SAFE_RELEASE(context);
  
        break;
      }
  
      case OPCODE_BUFINFO:
      {
        ID3D11DeviceContext *context = NULL;
        device->GetImmediateContext(&context);
  
        if(op.operands[1].indices.size() == 1 && op.operands[1].indices[0].absolute &&
           !op.operands[1].indices[0].relative)
        {
          UINT slot = (UINT)(op.operands[1].indices[0].index & 0xffffffff);
  
          ShaderVariable result("", 0U, 0U, 0U, 0U);
  
          if(op.operands[1].type == TYPE_UNORDERED_ACCESS_VIEW)
          {
            ID3D11UnorderedAccessView *uav = NULL;
            if(s.dxbc->m_Type == D3D11_ShaderType_Compute)
              context->CSGetUnorderedAccessViews(slot, 1, &uav);
            else
              context->OMGetRenderTargetsAndUnorderedAccessViews(0, NULL, NULL, slot, 1, &uav);
  
            if(uav)
            {
              D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
              uav->GetDesc(&uavDesc);
  
              if(uavDesc.ViewDimension == D3D11_UAV_DIMENSION_BUFFER)
              {
                result.value.u.x = result.value.u.y = result.value.u.z = result.value.u.w =
                    uavDesc.Buffer.NumElements;
              }
              else
              {
                RDCWARN("Unexpected UAV dimension %d passed to bufinfo", uavDesc.ViewDimension);
  
                device->AddDebugMessage(
                    MessageCategory::Shaders, MessageSeverity::High, MessageSource::RuntimeWarning,
                    StringFormat::Fmt(
                        "Shader debugging %d: %s\nUAV being queried by bufinfo is not a buffer",
                        s.nextInstruction - 1, op.str.c_str()));
              }
            }
            else
            {
              RDCWARN("UAV is NULL being queried by bufinfo");
  
              device->AddDebugMessage(
                  MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                  StringFormat::Fmt("Shader debugging %d: %s\nUAV being queried by bufinfo is NULL",
                                    s.nextInstruction - 1, op.str.c_str()));
            }
  
            SAFE_RELEASE(uav);
          }
          else
          {
            ID3D11ShaderResourceView *srv = NULL;
            if(s.dxbc->m_Type == D3D11_ShaderType_Vertex)
              context->VSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Hull)
              context->HSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Domain)
              context->DSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Geometry)
              context->GSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Pixel)
              context->PSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Compute)
              context->CSGetShaderResources(slot, 1, &srv);
  
            if(srv)
            {
              D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
              srv->GetDesc(&srvDesc);
  
              if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_BUFFER)
              {
                result.value.u.x = result.value.u.y = result.value.u.z = result.value.u.w =
                    srvDesc.Buffer.NumElements;
              }
              else if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_BUFFEREX)
              {
                result.value.u.x = result.value.u.y = result.value.u.z = result.value.u.w =
                    srvDesc.BufferEx.NumElements;
              }
              else
              {
                RDCWARN("Unexpected SRV dimension %d passed to bufinfo", srvDesc.ViewDimension);
  
                device->AddDebugMessage(
                    MessageCategory::Shaders, MessageSeverity::High, MessageSource::RuntimeWarning,
                    StringFormat::Fmt(
                        "Shader debugging %d: %s\nSRV being queried by bufinfo is not a buffer",
                        s.nextInstruction - 1, op.str.c_str()));
              }
            }
            else
            {
              RDCWARN("SRV is NULL being queried by bufinfo");
  
              device->AddDebugMessage(
                  MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning,
                  StringFormat::Fmt("Shader debugging %d: %s\nSRV being queried by bufinfo is NULL",
                                    s.nextInstruction - 1, op.str.c_str()));
            }
  
            SAFE_RELEASE(srv);
          }
  
          // apply swizzle
          ShaderVariable swizzled("", 0.0f, 0.0f, 0.0f, 0.0f);
  
          for(int i = 0; i < 4; i++)
          {
            if(op.operands[1].comps[i] == 0xff)
              swizzled.value.uv[i] = result.value.uv[0];
            else
              swizzled.value.uv[i] = result.value.uv[op.operands[1].comps[i]];
          }
  
          result = swizzled;
          result.type = VarType::UInt;
  
          // if we are assigning into a scalar, SetDst expects the result to be in .x (as normally we
          // are assigning FROM a scalar also).
          // to match this expectation, propogate the component across.
          if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff &&
             op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff)
            result.value.uv[0] = result.value.uv[op.operands[0].comps[0]];
  
          s.SetDst(op.operands[0], op, result);
        }
        else
        {
          RDCERR("Unexpected relative addressing");
          s.SetDst(op.operands[0], op, ShaderVariable("", 0.0f, 0.0f, 0.0f, 0.0f));
        }
  
        SAFE_RELEASE(context);
  
        break;
      }
  
      case OPCODE_RESINFO:
      {
        // spec says "srcMipLevel is read as an unsigned integer scalar"
        uint32_t mipLevel = srcOpers[0].value.u.x;
  
        ID3D11DeviceContext *context = NULL;
        device->GetImmediateContext(&context);
  
        if(op.operands[2].indices.size() == 1 && op.operands[2].indices[0].absolute &&
           !op.operands[2].indices[0].relative)
        {
          UINT slot = (UINT)(op.operands[2].indices[0].index & 0xffffffff);
  
          ShaderVariable result("", 0.0f, 0.0f, 0.0f, 0.0f);
  
          int dim = 0;
  
          if(op.operands[2].type != TYPE_UNORDERED_ACCESS_VIEW)
          {
            ID3D11ShaderResourceView *srv = NULL;
            if(s.dxbc->m_Type == D3D11_ShaderType_Vertex)
              context->VSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Hull)
              context->HSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Domain)
              context->DSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Geometry)
              context->GSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Pixel)
              context->PSGetShaderResources(slot, 1, &srv);
            else if(s.dxbc->m_Type == D3D11_ShaderType_Compute)
              context->CSGetShaderResources(slot, 1, &srv);
  
            if(srv)
            {
              D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
              srv->GetDesc(&srvDesc);
  
              switch(srvDesc.ViewDimension)
              {
                case D3D11_SRV_DIMENSION_BUFFER:
                {
                  dim = 1;
  
                  result.value.u.x = srvDesc.Buffer.NumElements;
                  result.value.u.y = 0;
                  result.value.u.z = 0;
                  result.value.u.w = 0;
                  break;
                }
                case D3D11_SRV_DIMENSION_BUFFEREX:
                {
                  dim = 1;
  
                  result.value.u.x = srvDesc.BufferEx.NumElements;
                  result.value.u.y = 0;
                  result.value.u.z = 0;
                  result.value.u.w = 0;
                  break;
                }
                case D3D11_SRV_DIMENSION_TEXTURE1D:
                case D3D11_SRV_DIMENSION_TEXTURE1DARRAY:
                {
                  ID3D11Texture1D *tex = NULL;
                  srv->GetResource((ID3D11Resource **)&tex);
  
                  dim = 1;
  
                  if(tex)
                  {
                    bool isarray = srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
  
                    D3D11_TEXTURE1D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = isarray ? srvDesc.Texture1DArray.ArraySize : 0;
                    result.value.u.z = 0;
                    result.value.u.w =
                        isarray ? srvDesc.Texture1DArray.MipLevels : srvDesc.Texture1D.MipLevels;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
                case D3D11_SRV_DIMENSION_TEXTURE2D:
                case D3D11_SRV_DIMENSION_TEXTURE2DARRAY:
                case D3D11_SRV_DIMENSION_TEXTURE2DMS:
                case D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY:
                {
                  ID3D11Texture2D *tex = NULL;
                  srv->GetResource((ID3D11Resource **)&tex);
  
                  dim = 2;
  
                  if(tex)
                  {
                    D3D11_TEXTURE2D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = RDCMAX(1U, desc.Height >> mipLevel);
  
                    if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE2D)
                    {
                      result.value.u.z = 0;
                      result.value.u.w = srvDesc.Texture2D.MipLevels;
                    }
                    else if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE2DARRAY)
                    {
                      result.value.u.z = srvDesc.Texture2DArray.ArraySize;
                      result.value.u.w = srvDesc.Texture2DArray.MipLevels;
                    }
                    else if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE2DMS)
                    {
                      result.value.u.z = 0;
                      result.value.u.w = 1;
                    }
                    else if(srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY)
                    {
                      result.value.u.z = srvDesc.Texture2DMSArray.ArraySize;
                      result.value.u.w = 1;
                    }
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = result.value.u.z = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
                case D3D11_SRV_DIMENSION_TEXTURE3D:
                {
                  ID3D11Texture3D *tex = NULL;
                  srv->GetResource((ID3D11Resource **)&tex);
  
                  dim = 3;
  
                  if(tex)
                  {
                    D3D11_TEXTURE3D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = RDCMAX(1U, desc.Height >> mipLevel);
                    result.value.u.z = RDCMAX(1U, desc.Depth >> mipLevel);
                    result.value.u.w = srvDesc.Texture3D.MipLevels;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = result.value.u.z = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
                case D3D11_SRV_DIMENSION_TEXTURECUBE:
                case D3D11_SRV_DIMENSION_TEXTURECUBEARRAY:
                {
                  ID3D11Texture2D *tex = NULL;
                  srv->GetResource((ID3D11Resource **)&tex);
  
                  dim = 2;
  
                  if(tex)
                  {
                    bool isarray = srvDesc.ViewDimension == D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
  
                    D3D11_TEXTURE2D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = RDCMAX(1U, desc.Height >> mipLevel);
  
                    // the spec says "If srcResource is a TextureCubeArray, [...]. dest.z is set to an
                    // undefined value."
                    // but that's stupid, and implementations seem to return the number of cubes
                    result.value.u.z = isarray ? srvDesc.TextureCubeArray.NumCubes : 0;
                    result.value.u.w =
                        isarray ? srvDesc.TextureCubeArray.MipLevels : srvDesc.TextureCube.MipLevels;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = result.value.u.z = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
              }
  
              SAFE_RELEASE(srv);
            }
          }
          else
          {
            ID3D11UnorderedAccessView *uav = NULL;
            if(s.dxbc->m_Type == D3D11_ShaderType_Compute)
            {
              context->CSGetUnorderedAccessViews(slot, 1, &uav);
            }
            else
            {
              ID3D11RenderTargetView *rtvs[8] = {0};
              ID3D11DepthStencilView *dsv = NULL;
              context->OMGetRenderTargetsAndUnorderedAccessViews(0, rtvs, &dsv, slot, 1, &uav);
  
              for(int i = 0; i < 8; i++)
                SAFE_RELEASE(rtvs[i]);
              SAFE_RELEASE(dsv);
            }
  
            if(uav)
            {
              D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
              uav->GetDesc(&uavDesc);
  
              switch(uavDesc.ViewDimension)
              {
                case D3D11_UAV_DIMENSION_BUFFER:
                {
                  ID3D11Buffer *buf = NULL;
                  uav->GetResource((ID3D11Resource **)&buf);
  
                  dim = 1;
  
                  if(buf)
                  {
                    D3D11_BUFFER_DESC desc;
                    buf->GetDesc(&desc);
                    result.value.u.x = desc.ByteWidth;
                    result.value.u.y = 0;
                    result.value.u.z = 0;
                    result.value.u.w = 0;
  
                    SAFE_RELEASE(buf);
                  }
                  break;
                }
                case D3D11_UAV_DIMENSION_TEXTURE1D:
                case D3D11_UAV_DIMENSION_TEXTURE1DARRAY:
                {
                  ID3D11Texture1D *tex = NULL;
                  uav->GetResource((ID3D11Resource **)&tex);
  
                  dim = 1;
  
                  if(tex)
                  {
                    bool isarray = uavDesc.ViewDimension == D3D11_UAV_DIMENSION_TEXTURE1DARRAY;
  
                    D3D11_TEXTURE1D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = isarray ? uavDesc.Texture1DArray.ArraySize : 0;
                    result.value.u.z = 0;
  
                    // spec says "For UAVs (u#), the number of mip levels is always 1."
                    result.value.u.w = 1;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
                case D3D11_UAV_DIMENSION_TEXTURE2D:
                case D3D11_UAV_DIMENSION_TEXTURE2DARRAY:
                {
                  ID3D11Texture2D *tex = NULL;
                  uav->GetResource((ID3D11Resource **)&tex);
  
                  dim = 2;
  
                  if(tex)
                  {
                    D3D11_TEXTURE2D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = RDCMAX(1U, desc.Height >> mipLevel);
  
                    if(uavDesc.ViewDimension == D3D11_UAV_DIMENSION_TEXTURE2D)
                      result.value.u.z = 0;
                    else if(uavDesc.ViewDimension == D3D11_UAV_DIMENSION_TEXTURE2DARRAY)
                      result.value.u.z = uavDesc.Texture2DArray.ArraySize;
  
                    // spec says "For UAVs (u#), the number of mip levels is always 1."
                    result.value.u.w = 1;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = result.value.u.z = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
                case D3D11_UAV_DIMENSION_TEXTURE3D:
                {
                  ID3D11Texture3D *tex = NULL;
                  uav->GetResource((ID3D11Resource **)&tex);
  
                  dim = 3;
  
                  if(tex)
                  {
                    D3D11_TEXTURE3D_DESC desc;
                    tex->GetDesc(&desc);
                    result.value.u.x = RDCMAX(1U, desc.Width >> mipLevel);
                    result.value.u.y = RDCMAX(1U, desc.Height >> mipLevel);
                    result.value.u.z = RDCMAX(1U, desc.Depth >> mipLevel);
  
                    // spec says "For UAVs (u#), the number of mip levels is always 1."
                    result.value.u.w = 1;
  
                    if(mipLevel >= result.value.u.w)
                      result.value.u.x = result.value.u.y = result.value.u.z = 0;
  
                    SAFE_RELEASE(tex);
                  }
                  break;
                }
              }
  
              SAFE_RELEASE(uav);
            }
          }
  
          // need a valid dimension even if the resource was unbound, so
          // search for the declaration
          if(dim == 0)
          {
            for(size_t i = 0; i < s.dxbc->GetNumDeclarations(); i++)
            {
              const DXBC::ASMDecl &decl = dxbc->GetDeclaration(i);
  
              if(decl.declaration == OPCODE_DCL_RESOURCE && decl.operand.type == TYPE_RESOURCE &&
                 decl.operand.indices.size() == 1 &&
                 decl.operand.indices[0] == op.operands[2].indices[0])
              {
                switch(decl.dim)
                {
                  case RESOURCE_DIMENSION_BUFFER:
                  case RESOURCE_DIMENSION_RAW_BUFFER:
                  case RESOURCE_DIMENSION_STRUCTURED_BUFFER:
                  case RESOURCE_DIMENSION_TEXTURE1D:
                  case RESOURCE_DIMENSION_TEXTURE1DARRAY: dim = 1; break;
                  case RESOURCE_DIMENSION_TEXTURE2D:
                  case RESOURCE_DIMENSION_TEXTURE2DMS:
                  case RESOURCE_DIMENSION_TEXTURE2DARRAY:
                  case RESOURCE_DIMENSION_TEXTURE2DMSARRAY:
                  case RESOURCE_DIMENSION_TEXTURECUBE:
                  case RESOURCE_DIMENSION_TEXTURECUBEARRAY: dim = 2; break;
                  case RESOURCE_DIMENSION_TEXTURE3D: dim = 3; break;
                }
                break;
              }
            }
          }
  
          // apply swizzle
          ShaderVariable swizzled("", 0.0f, 0.0f, 0.0f, 0.0f);
  
          for(int i = 0; i < 4; i++)
          {
            if(op.operands[2].comps[i] == 0xff)
              swizzled.value.uv[i] = result.value.uv[0];
            else
              swizzled.value.uv[i] = result.value.uv[op.operands[2].comps[i]];
          }
  
          // apply ret type
          if(op.resinfoRetType == RETTYPE_FLOAT)
          {
            result.value.f.x = (float)swizzled.value.u.x;
            result.value.f.y = (float)swizzled.value.u.y;
            result.value.f.z = (float)swizzled.value.u.z;
            result.value.f.w = (float)swizzled.value.u.w;
            result.type = VarType::Float;
          }
          else if(op.resinfoRetType == RETTYPE_RCPFLOAT)
          {
            // only width/height/depth values we set are reciprocated, other values
            // are just left as is
            if(dim <= 1)
              result.value.f.x = 1.0f / (float)swizzled.value.u.x;
            else
              result.value.f.x = (float)swizzled.value.u.x;
  
            if(dim <= 2)
              result.value.f.y = 1.0f / (float)swizzled.value.u.y;
            else
              result.value.f.y = (float)swizzled.value.u.y;
  
            if(dim <= 3)
              result.value.f.z = 1.0f / (float)swizzled.value.u.z;
            else
              result.value.f.z = (float)swizzled.value.u.z;
  
            result.value.f.w = (float)swizzled.value.u.w;
            result.type = VarType::Float;
          }
          else if(op.resinfoRetType == RETTYPE_UINT)
          {
            result = swizzled;
            result.type = VarType::UInt;
          }
  
          // if we are assigning into a scalar, SetDst expects the result to be in .x (as normally we
          // are assigning FROM a scalar also).
          // to match this expectation, propogate the component across.
          if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff &&
             op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff)
            result.value.uv[0] = result.value.uv[op.operands[0].comps[0]];
  
          s.SetDst(op.operands[0], op, result);
        }
        else
        {
          RDCERR("Unexpected relative addressing");
          s.SetDst(op.operands[0], op, ShaderVariable("", 0.0f, 0.0f, 0.0f, 0.0f));
        }
  
        SAFE_RELEASE(context);
  
        break;
      }
      case OPCODE_SAMPLE:
      case OPCODE_SAMPLE_L:
      case OPCODE_SAMPLE_B:
      case OPCODE_SAMPLE_D:
      case OPCODE_SAMPLE_C:
      case OPCODE_SAMPLE_C_LZ:
      case OPCODE_LD:
      case OPCODE_LD_MS:
      case OPCODE_GATHER4:
      case OPCODE_GATHER4_C:
      case OPCODE_GATHER4_PO:
      case OPCODE_GATHER4_PO_C:
      case OPCODE_LOD:
      {
        string sampler = "";
        string texture = "";
        string funcRet = "";
        DXGI_FORMAT retFmt = DXGI_FORMAT_UNKNOWN;
  
        if(op.operation != OPCODE_LOD)
        {
          s.flags = ShaderEvents::SampleLoadGather;
        }
  
        if(op.operation == OPCODE_SAMPLE_C || op.operation == OPCODE_SAMPLE_C_LZ ||
           op.operation == OPCODE_GATHER4_C || op.operation == OPCODE_GATHER4_PO_C ||
           op.operation == OPCODE_LOD)
        {
          retFmt = DXGI_FORMAT_R32G32B32A32_FLOAT;
          funcRet = "float4";
        }
  
        bool useOffsets = true;
        int texdim = 2;
        int offsdim = 2;    // ddN and offset dimension
  
        DXBC::ResourceDimension resourceDim = DXBC::RESOURCE_DIMENSION_UNKNOWN;
  
        for(size_t i = 0; i < s.dxbc->GetNumDeclarations(); i++)
        {
          const DXBC::ASMDecl &decl = dxbc->GetDeclaration(i);
  
          if(decl.declaration == OPCODE_DCL_SAMPLER && op.operands.size() > 3 &&
             decl.operand.indices == op.operands[3].indices)
          {
            if(decl.samplerMode == SAMPLER_MODE_DEFAULT)
              sampler = "SamplerState s";
            else if(decl.samplerMode == SAMPLER_MODE_COMPARISON)
              sampler = "SamplerComparisonState s";
            else
              RDCERR("Unsupported sampler type %d in sample operation", decl.samplerMode);
          }
          if(decl.dim == RESOURCE_DIMENSION_BUFFER && op.operation == OPCODE_LD &&
             decl.declaration == OPCODE_DCL_RESOURCE && decl.operand.type == TYPE_RESOURCE &&
             decl.operand.indices.size() == 1 && decl.operand.indices[0] == op.operands[2].indices[0])
          {
            resourceDim = decl.dim;
  
            uint32_t resIndex = (uint32_t)decl.operand.indices[0].index;
  
            byte *data = &global.srvs[resIndex].data[0];
            uint32_t offset = global.srvs[resIndex].firstElement;
            uint32_t numElems = global.srvs[resIndex].numElements;
  
            GlobalState::ViewFmt fmt = global.srvs[resIndex].format;
  
            data += fmt.Stride() * offset;
  
            ShaderVariable result;
  
            {
              result = ShaderVariable("", 0.0f, 0.0f, 0.0f, 0.0f);
  
              if(srcOpers[0].value.uv[0] < numElems)
              {
                byte *d = data + srcOpers[0].value.uv[0] * fmt.Stride();
  
                if(fmt.byteWidth == 10)
                {
                  uint32_t u = *((uint32_t *)d);
  
                  if(fmt.fmt == CompType::UInt)
                  {
                    result.value.u.x = (u >> 0) & 0x3ff;
                    result.value.u.y = (u >> 10) & 0x3ff;
                    result.value.u.z = (u >> 20) & 0x3ff;
                    result.value.u.w = (u >> 30) & 0x003;
                  }
                  else if(fmt.fmt == CompType::UNorm)
                  {
                    Vec4f res = ConvertFromR10G10B10A2(u);
                    result.value.f.x = res.x;
                    result.value.f.y = res.y;
                    result.value.f.z = res.z;
                    result.value.f.w = res.w;
                  }
                  else
                  {
                    RDCERR("Unexpected format type on buffer resource");
                  }
                }
                else if(fmt.byteWidth == 11)
                {
                  uint32_t *u = (uint32_t *)d;
  
                  Vec3f res = ConvertFromR11G11B10(*u);
                  result.value.f.x = res.x;
                  result.value.f.y = res.y;
                  result.value.f.z = res.z;
                  result.value.f.w = 1.0f;
                }
                else if(fmt.byteWidth == 4)
                {
                  uint32_t *u = (uint32_t *)d;
  
                  for(int c = 0; c < fmt.numComps; c++)
                    result.value.uv[c] = u[c];
                }
                else if(fmt.byteWidth == 2)
                {
                  if(fmt.fmt == CompType::Float)
                  {
                    uint16_t *u = (uint16_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.fv[c] = ConvertFromHalf(u[c]);
                  }
                  else if(fmt.fmt == CompType::UInt)
                  {
                    uint16_t *u = (uint16_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.uv[c] = u[c];
                  }
                  else if(fmt.fmt == CompType::SInt)
                  {
                    int16_t *in = (int16_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.iv[c] = in[c];
                  }
                  else if(fmt.fmt == CompType::UNorm)
                  {
                    uint16_t *u = (uint16_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.fv[c] = float(u[c]) / float(0xffff);
                  }
                  else if(fmt.fmt == CompType::SNorm)
                  {
                    int16_t *in = (int16_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                    {
                      // -32768 is mapped to -1, then -32767 to -32767 are mapped to -1 to 1
                      if(in[c] == -32768)
                        result.value.fv[c] = -1.0f;
                      else
                        result.value.fv[c] = float(in[c]) / 32767.0f;
                    }
                  }
                  else
                  {
                    RDCERR("Unexpected format type on buffer resource");
                  }
                }
                else if(fmt.byteWidth == 1)
                {
                  if(fmt.fmt == CompType::UInt)
                  {
                    uint8_t *u = (uint8_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.uv[c] = u[c];
                  }
                  else if(fmt.fmt == CompType::SInt)
                  {
                    int8_t *in = (int8_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.iv[c] = in[c];
                  }
                  else if(fmt.fmt == CompType::UNorm)
                  {
                    uint8_t *u = (uint8_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                      result.value.fv[c] = float(u[c]) / float(0xff);
                  }
                  else if(fmt.fmt == CompType::SNorm)
                  {
                    int8_t *in = (int8_t *)d;
  
                    for(int c = 0; c < fmt.numComps; c++)
                    {
                      // -128 is mapped to -1, then -127 to -127 are mapped to -1 to 1
                      if(in[c] == -128)
                        result.value.fv[c] = -1.0f;
                      else
                        result.value.fv[c] = float(in[c]) / 127.0f;
                    }
                  }
                  else
                  {
                    RDCERR("Unexpected format type on buffer resource");
                  }
                }
  
                if(fmt.reversed)
                  result = ShaderVariable("", result.value.uv[0], result.value.uv[1],
                                          result.value.uv[2], result.value.uv[3]);
              }
            }
  
            ShaderVariable fetch("", 0U, 0U, 0U, 0U);
  
            for(int c = 0; c < 4; c++)
            {
              uint8_t comp = op.operands[2].comps[c];
              if(op.operands[2].comps[c] == 0xff)
                comp = 0;
  
              fetch.value.uv[c] = result.value.uv[comp];
            }
  
            // if we are assigning into a scalar, SetDst expects the result to be in .x (as normally
            // we are assigning FROM a scalar also).
            // to match this expectation, propogate the component across.
            if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff &&
               op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff)
              fetch.value.uv[0] = fetch.value.uv[op.operands[0].comps[0]];
  
            s.SetDst(op.operands[0], op, fetch);
  
            return s;
          }
          if(decl.declaration == OPCODE_DCL_RESOURCE && decl.operand.type == TYPE_RESOURCE &&
             decl.operand.indices.size() == 1 && decl.operand.indices[0] == op.operands[2].indices[0])
          {
            resourceDim = decl.dim;
  
            if(decl.dim == RESOURCE_DIMENSION_TEXTURE1D)
            {
              texture = "Texture1D";
              texdim = 1;
              offsdim = 1;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE2D)
            {
              texture = "Texture2D";
              texdim = 2;
              offsdim = 2;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE2DMS)
            {
              texture = "Texture2DMS";
              texdim = 2;
              offsdim = 2;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE3D)
            {
              texture = "Texture3D";
              texdim = 3;
              offsdim = 3;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURECUBE)
            {
              texture = "TextureCube";
              texdim = 3;
              offsdim = 3;
              useOffsets = false;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE1DARRAY)
            {
              texture = "Texture1DArray";
              texdim = 2;
              offsdim = 1;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE2DARRAY)
            {
              texture = "Texture2DArray";
              texdim = 3;
              offsdim = 2;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURE2DMSARRAY)
            {
              texture = "Texture2DMSArray";
              texdim = 3;
              offsdim = 2;
            }
            else if(decl.dim == RESOURCE_DIMENSION_TEXTURECUBEARRAY)
            {
              texture = "TextureCubeArray";
              texdim = 4;
              offsdim = 3;
              useOffsets = false;
            }
            else
            {
              RDCERR("Unsupported resource type %d in sample operation", decl.dim);
            }
  
            // doesn't seem like these are ever less than four components, even if the texture is
            // declared <float3> for example.
            // shouldn't matter though is it just comes out in the wash.
            RDCASSERT(decl.resType[0] == decl.resType[1] && decl.resType[1] == decl.resType[2] &&
                      decl.resType[2] == decl.resType[3]);
            RDCASSERT(decl.resType[0] != RETURN_TYPE_CONTINUED &&
                      decl.resType[0] != RETURN_TYPE_UNUSED && decl.resType[0] != RETURN_TYPE_MIXED &&
                      decl.resType[0] >= 0 && decl.resType[0] < NUM_RETURN_TYPES);
  
            char *typeStr[NUM_RETURN_TYPES] = {
                "",    // enum starts at ==1
                "unorm float",
                "snorm float",
                "int",
                "uint",
                "float",
                "__",    // RETURN_TYPE_MIXED
                "double",
                "__",    // RETURN_TYPE_CONTINUED
                "__",    // RETURN_TYPE_UNUSED
            };
  
            // obviously these may be overly optimistic in some cases
            // but since we don't know at debug time what the source texture format is
            // we just use the fattest one necessary. There's no harm in retrieving at
            // higher precision
            DXGI_FORMAT fmts[NUM_RETURN_TYPES] = {
                DXGI_FORMAT_UNKNOWN,    // enum starts at ==1
                DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT,
                DXGI_FORMAT_R32G32B32A32_SINT, DXGI_FORMAT_R32G32B32A32_UINT,
                DXGI_FORMAT_R32G32B32A32_FLOAT,
                DXGI_FORMAT_UNKNOWN,    // RETURN_TYPE_MIXED
  
                // should maybe be double, but there is no double texture format anyway!
                // spec is unclear but I presume reads are done at most at float
                // precision anyway since that's the source, and converted to doubles.
                DXGI_FORMAT_R32G32B32A32_FLOAT,
  
                DXGI_FORMAT_UNKNOWN,    // RETURN_TYPE_CONTINUED
                DXGI_FORMAT_UNKNOWN,    // RETURN_TYPE_UNUSED
            };
  
            char buf[64] = {0};
            StringFormat::snprintf(buf, 63, "%s4", typeStr[decl.resType[0]]);
  
            if(retFmt == DXGI_FORMAT_UNKNOWN)
            {
              funcRet = buf;
  
              retFmt = fmts[decl.resType[0]];
            }
  
            if(decl.dim == RESOURCE_DIMENSION_TEXTURE2DMS ||
               decl.dim == RESOURCE_DIMENSION_TEXTURE2DMSARRAY)
            {
              if(decl.sampleCount > 0)
                StringFormat::snprintf(buf, 63, "%s4, %d", typeStr[decl.resType[0]], decl.sampleCount);
            }
  
            texture += "<";
            texture += buf;
            texture += "> t";
          }
        }
  
        // for lod operation, it's only defined for certain resources - otherwise just returns 0
        if(op.operation == OPCODE_LOD && resourceDim != RESOURCE_DIMENSION_TEXTURE1D &&
           resourceDim != RESOURCE_DIMENSION_TEXTURE1DARRAY &&
           resourceDim != RESOURCE_DIMENSION_TEXTURE2D &&
           resourceDim != RESOURCE_DIMENSION_TEXTURE2DARRAY &&
           resourceDim != RESOURCE_DIMENSION_TEXTURE3D && resourceDim != RESOURCE_DIMENSION_TEXTURECUBE)
        {
          ShaderVariable invalidResult("tex", 0.0f, 0.0f, 0.0f, 0.0f);
  
          s.SetDst(op.operands[0], op, invalidResult);
          break;
        }
  
        string sampleProgram;
  
        char buf[256] = {0};
        char buf2[256] = {0};
        char buf3[256] = {0};
  
        ShaderVariable ddxCalc;
        ShaderVariable ddyCalc;
  
        // these ops need DDX/DDY
        if(op.operation == OPCODE_SAMPLE || op.operation == OPCODE_SAMPLE_B ||
           op.operation == OPCODE_SAMPLE_C || op.operation == OPCODE_LOD)
        {
          if(quad == NULL)
          {
            RDCERR(
                "Attempt to use derivative instruction not in pixel shader. Undefined results will "
                "occur!");
          }
          else
          {
            // texture samples use coarse derivatives
            ddxCalc = s.DDX(false, quad, op.operands[1], op);
            ddyCalc = s.DDY(false, quad, op.operands[1], op);
          }
        }
        else if(op.operation == OPCODE_SAMPLE_D)
        {
          ddxCalc = srcOpers[3];
          ddyCalc = srcOpers[4];
        }
  
        // serious printf abuse below!
  
        char *formats[4][2] = {
            {"float(%.10f)", "int(%d)"},
            {"float2(%.10f, %.10f)", "int2(%d, %d)"},
            {"float3(%.10f, %.10f, %.10f)", "int3(%d, %d, %d)"},
            {"float4(%.10f, %.10f, %.10f, %.10f)", "int4(%d, %d, %d, %d)"},
        };
  
        int texcoordType = 0;
        int ddxType = 0;
        int ddyType = 0;
  
        int texdimOffs = 0;
  
        if(op.operation == OPCODE_SAMPLE || op.operation == OPCODE_SAMPLE_L ||
           op.operation == OPCODE_SAMPLE_B || op.operation == OPCODE_SAMPLE_D ||
           op.operation == OPCODE_SAMPLE_C || op.operation == OPCODE_SAMPLE_C_LZ ||
           op.operation == OPCODE_GATHER4 || op.operation == OPCODE_GATHER4_C ||
           op.operation == OPCODE_GATHER4_PO || op.operation == OPCODE_GATHER4_PO_C ||
           op.operation == OPCODE_LOD)
        {
          // all floats
          texcoordType = ddxType = ddyType = 0;
        }
        else if(op.operation == OPCODE_LD)
        {
          // int address, one larger than texdim (to account for mip/slice parameter)
          texdimOffs = 1;
          texcoordType = 1;
  
          if(texdim == 4)
          {
            RDCERR("Unexpectedly large texture in load operation");
          }
        }
        else if(op.operation == OPCODE_LD_MS)
        {
          texcoordType = 1;
  
          if(texdim == 4)
          {
            RDCERR("Unexpectedly large texture in load operation");
          }
        }
  
        ShaderVariable uv = srcOpers[0];
  
        for(uint32_t i = 0; i < ddxCalc.columns; i++)
        {
          if(ddxType == 0 && (_isnan(ddxCalc.value.fv[i]) || !_finite(ddxCalc.value.fv[i])))
          {
            RDCWARN("NaN or Inf in texlookup");
            ddxCalc.value.fv[i] = 0.0f;
  
            device->AddDebugMessage(MessageCategory::Shaders, MessageSeverity::High,
                                    MessageSource::RuntimeWarning,
                                    StringFormat::Fmt("Shader debugging %d: %s\nNaN or Inf found in "
                                                      "texture lookup ddx - using 0.0 instead",
                                                      s.nextInstruction - 1, op.str.c_str()));
          }
          if(ddyType == 0 && (_isnan(ddyCalc.value.fv[i]) || !_finite(ddyCalc.value.fv[i])))
          {
            RDCWARN("NaN or Inf in texlookup");
            ddyCalc.value.fv[i] = 0.0f;
  
            device->AddDebugMessage(MessageCategory::Shaders, MessageSeverity::High,
                                    MessageSource::RuntimeWarning,
                                    StringFormat::Fmt("Shader debugging %d: %s\nNaN or Inf found in "
                                                      "texture lookup ddy - using 0.0 instead",
                                                      s.nextInstruction - 1, op.str.c_str()));
          }
        }
  
        for(uint32_t i = 0; i < uv.columns; i++)
        {
          if(texcoordType == 0 && (_isnan(uv.value.fv[i]) || !_finite(uv.value.fv[i])))
          {
            RDCWARN("NaN or Inf in texlookup");
            uv.value.fv[i] = 0.0f;
  
            device->AddDebugMessage(MessageCategory::Shaders, MessageSeverity::High,
                                    MessageSource::RuntimeWarning,
                                    StringFormat::Fmt("Shader debugging %d: %s\nNaN or Inf found in "
                                                      "texture lookup uv - using 0.0 instead",
                                                      s.nextInstruction - 1, op.str.c_str()));
          }
        }
  
        // because of unions in .value we can pass the float versions and printf will interpret it as
        // the right type according to formats
        if(texcoordType == 0)
          StringFormat::snprintf(buf, 255, formats[texdim + texdimOffs - 1][texcoordType],
                                 uv.value.f.x, uv.value.f.y, uv.value.f.z, uv.value.f.w);
        else
          StringFormat::snprintf(buf, 255, formats[texdim + texdimOffs - 1][texcoordType],
                                 uv.value.i.x, uv.value.i.y, uv.value.i.z, uv.value.i.w);
  
        if(ddxType == 0)
          StringFormat::snprintf(buf2, 255, formats[offsdim + texdimOffs - 1][ddxType],
                                 ddxCalc.value.f.x, ddxCalc.value.f.y, ddxCalc.value.f.z,
                                 ddxCalc.value.f.w);
        else
          StringFormat::snprintf(buf2, 255, formats[offsdim + texdimOffs - 1][ddxType],
                                 ddxCalc.value.i.x, ddxCalc.value.i.y, ddxCalc.value.i.z,
                                 ddxCalc.value.i.w);
  
        if(ddyType == 0)
          StringFormat::snprintf(buf3, 255, formats[offsdim + texdimOffs - 1][ddyType],
                                 ddyCalc.value.f.x, ddyCalc.value.f.y, ddyCalc.value.f.z,
                                 ddyCalc.value.f.w);
        else
          StringFormat::snprintf(buf3, 255, formats[offsdim + texdimOffs - 1][ddyType],
                                 ddyCalc.value.i.x, ddyCalc.value.i.y, ddyCalc.value.i.z,
                                 ddyCalc.value.i.w);
  
        string texcoords = buf;
        string ddx = buf2;
        string ddy = buf3;
  
        if(op.operation == OPCODE_LD_MS)
        {
          StringFormat::snprintf(buf, 255, formats[0][1], srcOpers[2].value.i.x);
        }
  
        string sampleIdx = buf;
  
        string offsets = "";
  
        if(useOffsets)
        {
          if(offsdim == 1)
            StringFormat::snprintf(buf, 255, ", int(%d)", op.texelOffset[0]);
          if(offsdim == 2)
            StringFormat::snprintf(buf, 255, ", int2(%d, %d)", op.texelOffset[0], op.texelOffset[1]);
          if(offsdim == 3)
            StringFormat::snprintf(buf, 255, ", int3(%d, %d, %d)", op.texelOffset[0],
                                   op.texelOffset[1], op.texelOffset[2]);
          // texdim == 4 is cube arrays, no offset supported
  
          offsets = buf;
        }
  
        string swizzle = ".";
  
        char elems[] = "xyzw";
  
        for(int i = 0; i < 4; i++)
        {
          if(op.operands[2].comps[i] == 0xff)
            swizzle += "x";
          else
            swizzle += elems[op.operands[2].comps[i]];
        }
  
        const char *channel = "";
        if(op.operation == OPCODE_GATHER4 || op.operation == OPCODE_GATHER4_C ||
           op.operation == OPCODE_GATHER4_PO || op.operation == OPCODE_GATHER4_PO_C)
        {
          switch(op.operands[3].comps[0])
          {
            case 0: channel = "Red"; break;
            case 1: channel = "Green"; break;
            case 2: channel = "Blue"; break;
            case 3: channel = "Alpha"; break;
          }
        }
  
        string vsProgram = "float4 main(uint id : SV_VertexID) : SV_Position {\n";
        vsProgram += "return float4((id == 2) ? 3.0f : -1.0f, (id == 0) ? -3.0f : 1.0f, 0.5, 1.0);\n";
        vsProgram += "}";
  
        UINT texSlot = (UINT)op.operands[2].indices[0].index;
        UINT sampSlot = 0;
  
        for(size_t i = 0; i < op.operands.size(); i++)
        {
          const ASMOperand &operand = op.operands[i];
          if(operand.type == OperandType::TYPE_SAMPLER)
            sampSlot = (UINT)operand.indices[0].index;
        }
  
        if(op.operation == OPCODE_SAMPLE || op.operation == OPCODE_SAMPLE_B ||
           op.operation == OPCODE_SAMPLE_D)
        {
          sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram +=
              "t.SampleGrad(s, " + texcoords + ", " + ddx + ", " + ddy + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_SAMPLE_L)
        {
          // lod selection
          StringFormat::snprintf(buf, 255, "%.10f", srcOpers[3].value.f.x);
  
          sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram +=
              "t.SampleLevel(s, " + texcoords + ", " + buf + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_SAMPLE_C || op.operation == OPCODE_LOD)
        {
          // these operations need derivatives but have no hlsl function to call to provide them, so
          // we fake it in the vertex shader
  
          string uvDim = "1";
          uvDim[0] += char(texdim + texdimOffs - 1);
  
          vsProgram = "void main(uint id : SV_VertexID, out float4 pos : SV_Position, out float" +
                      uvDim + " uv : uvs) {\n";
  
          StringFormat::snprintf(
              buf, 255, formats[texdim + texdimOffs - 1][texcoordType],
              uv.value.f.x + ddyCalc.value.f.x * 2.0f, uv.value.f.y + ddyCalc.value.f.y * 2.0f,
              uv.value.f.z + ddyCalc.value.f.z * 2.0f, uv.value.f.w + ddyCalc.value.f.w * 2.0f);
  
          vsProgram += "if(id == 0) uv = " + string(buf) + ";\n";
  
          StringFormat::snprintf(buf, 255, formats[texdim + texdimOffs - 1][texcoordType],
                                 uv.value.f.x, uv.value.f.y, uv.value.f.z, uv.value.f.w);
  
          vsProgram += "if(id == 1) uv = " + string(buf) + ";\n";
  
          StringFormat::snprintf(
              buf, 255, formats[texdim + texdimOffs - 1][texcoordType],
              uv.value.f.x + ddxCalc.value.f.x * 2.0f, uv.value.f.y + ddxCalc.value.f.y * 2.0f,
              uv.value.f.z + ddxCalc.value.f.z * 2.0f, uv.value.f.w + ddxCalc.value.f.w * 2.0f);
  
          vsProgram += "if(id == 2) uv = " + string(buf) + ";\n";
  
          vsProgram +=
              "pos = float4((id == 2) ? 3.0f : -1.0f, (id == 0) ? -3.0f : 1.0f, 0.5, 1.0);\n";
          vsProgram += "}";
  
          if(op.operation == OPCODE_SAMPLE_C)
          {
            // comparison value
            StringFormat::snprintf(buf, 255, "%.10f", srcOpers[3].value.f.x);
  
            sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
            sampleProgram += funcRet + " main(float4 pos : SV_Position, float" + uvDim +
                             " uv : uvs) : SV_Target0\n{\n";
            sampleProgram +=
                "return t.SampleCmpLevelZero(s, uv, " + string(buf) + offsets + ").xxxx;";
            sampleProgram += "\n}\n";
          }
          else if(op.operation == OPCODE_LOD)
          {
            sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
            sampleProgram += funcRet + " main(float4 pos : SV_Position, float" + uvDim +
                             " uv : uvs) : SV_Target0\n{\n";
            sampleProgram +=
                "return float4(t.CalculateLevelOfDetail(s, uv), t.CalculateLevelOfDetailUnclamped(s, "
                "uv), 0.0f, 0.0f);";
            sampleProgram += "\n}\n";
          }
        }
        else if(op.operation == OPCODE_SAMPLE_C_LZ)
        {
          // comparison value
          StringFormat::snprintf(buf, 255, "%.10f", srcOpers[3].value.f.x);
  
          sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram +=
              "t.SampleCmpLevelZero(s, " + texcoords + ", " + buf + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_LD)
        {
          sampleProgram = texture + " : register(t0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram += "t.Load(" + texcoords + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_LD_MS)
        {
          sampleProgram = texture + " : register(t0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram += "t.Load(" + texcoords + ", " + sampleIdx + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_GATHER4 || op.operation == OPCODE_GATHER4_PO)
        {
          sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram +=
              "t.Gather" + string(channel) + "(s, " + texcoords + offsets + ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
        else if(op.operation == OPCODE_GATHER4_C || op.operation == OPCODE_GATHER4_PO_C)
        {
          // comparison value
          if(op.operation == OPCODE_GATHER4_C)
            StringFormat::snprintf(buf, 255, ", %.10f", srcOpers[3].value.f.x);
          else if(op.operation == OPCODE_GATHER4_PO_C)
            StringFormat::snprintf(buf, 255, ", %.10f", srcOpers[4].value.f.x);
  
          sampleProgram = texture + " : register(t0);\n" + sampler + " : register(s0);\n\n";
          sampleProgram += funcRet + " main() : SV_Target0\n{\nreturn ";
          sampleProgram += "t.GatherCmp" + string(channel) + "(s, " + texcoords + buf + offsets +
                           ")" + swizzle + ";";
          sampleProgram += "\n}\n";
        }
  
        ID3D11VertexShader *vs =
            device->GetShaderCache()->MakeVShader(vsProgram.c_str(), "main", "vs_5_0");
        ID3D11PixelShader *ps =
            device->GetShaderCache()->MakePShader(sampleProgram.c_str(), "main", "ps_5_0");
  
        ID3D11DeviceContext *context = NULL;
  
        device->GetImmediateContext(&context);
  
        // back up SRV/sampler on PS slot 0
  
        ID3D11ShaderResourceView *prevSRV = NULL;
        ID3D11SamplerState *prevSamp = NULL;
  
        context->PSGetShaderResources(0, 1, &prevSRV);
        context->PSGetSamplers(0, 1, &prevSamp);
  
        ID3D11ShaderResourceView *usedSRV = NULL;
        ID3D11SamplerState *usedSamp = NULL;
  
        // fetch SRV and sampler from the shader stage we're debugging that this opcode wants to
        // load from
  
        if(dxbc->m_Type == D3D11_ShaderType_Vertex)
          context->VSGetShaderResources(texSlot, 1, &usedSRV);
        else if(dxbc->m_Type == D3D11_ShaderType_Hull)
          context->HSGetShaderResources(texSlot, 1, &usedSRV);
        else if(dxbc->m_Type == D3D11_ShaderType_Domain)
          context->DSGetShaderResources(texSlot, 1, &usedSRV);
        else if(dxbc->m_Type == D3D11_ShaderType_Geometry)
          context->GSGetShaderResources(texSlot, 1, &usedSRV);
        else if(dxbc->m_Type == D3D11_ShaderType_Pixel)
          context->PSGetShaderResources(texSlot, 1, &usedSRV);
        else if(dxbc->m_Type == D3D11_ShaderType_Compute)
          context->CSGetShaderResources(texSlot, 1, &usedSRV);
  
        if(dxbc->m_Type == D3D11_ShaderType_Vertex)
          context->VSGetSamplers(sampSlot, 1, &usedSamp);
        else if(dxbc->m_Type == D3D11_ShaderType_Hull)
          context->HSGetSamplers(sampSlot, 1, &usedSamp);
        else if(dxbc->m_Type == D3D11_ShaderType_Domain)
          context->DSGetSamplers(sampSlot, 1, &usedSamp);
        else if(dxbc->m_Type == D3D11_ShaderType_Geometry)
          context->GSGetSamplers(sampSlot, 1, &usedSamp);
        else if(dxbc->m_Type == D3D11_ShaderType_Pixel)
          context->PSGetSamplers(sampSlot, 1, &usedSamp);
        else if(dxbc->m_Type == D3D11_ShaderType_Compute)
          context->CSGetSamplers(sampSlot, 1, &usedSamp);
  
        // set onto PS while we perform the sample
        context->PSSetShaderResources(0, 1, &usedSRV);
        context->PSSetSamplers(0, 1, &usedSamp);
  
        context->IASetInputLayout(NULL);
        context->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
  
        context->VSSetShader(vs, NULL, 0);
        context->PSSetShader(ps, NULL, 0);
  
        // for bias instruction we can't do a SampleGradBias, so add the bias into the sampler state.
        if(op.operation == OPCODE_SAMPLE_B)
        {
          ID3D11SamplerState *samp = NULL;
  
          context->PSGetSamplers((UINT)srcOpers[2].value.u.x, 1, &samp);
  
          RDCASSERT(samp);
  
          D3D11_SAMPLER_DESC desc;
  
          samp->GetDesc(&desc);
  
          SAFE_RELEASE(samp);
  
          desc.MipLODBias = RDCCLAMP(desc.MipLODBias + srcOpers[3].value.f.x, -15.99f, 15.99f);
  
          ID3D11SamplerState *replacementSamp = NULL;
  
          HRESULT hr = device->CreateSamplerState(&desc, &replacementSamp);
  
          if(FAILED(hr))
          {
            RDCERR("Failed to create new sampler state in debugging HRESULT: %s", ToStr(hr).c_str());
          }
          else
          {
            context->PSSetSamplers(0, 1, &replacementSamp);
  
            SAFE_RELEASE(replacementSamp);
          }
        }
  
        D3D11_VIEWPORT view = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
        context->RSSetViewports(1, &view);
  
        context->GSSetShader(NULL, NULL, 0);
        context->DSSetShader(NULL, NULL, 0);
        context->HSSetShader(NULL, NULL, 0);
        context->CSSetShader(NULL, NULL, 0);
  
        context->SOSetTargets(0, NULL, NULL);
  
        context->RSSetState(NULL);
        context->OMSetBlendState(NULL, NULL, (UINT)~0);
        context->OMSetDepthStencilState(NULL, 0);
  
        ID3D11RenderTargetView *rtv = NULL;
  
        ID3D11Texture2D *rtTex = NULL;
        ID3D11Texture2D *copyTex = NULL;
  
        D3D11_TEXTURE2D_DESC tdesc;
  
        RDCASSERT(retFmt != DXGI_FORMAT_UNKNOWN);
  
        tdesc.ArraySize = 1;
        tdesc.BindFlags = D3D11_BIND_RENDER_TARGET;
        tdesc.CPUAccessFlags = 0;
        tdesc.Format = retFmt;
        tdesc.Width = 1;
        tdesc.Height = 1;
        tdesc.MipLevels = 0;
        tdesc.MiscFlags = 0;
        tdesc.SampleDesc.Count = 1;
        tdesc.SampleDesc.Quality = 0;
        tdesc.Usage = D3D11_USAGE_DEFAULT;
  
        HRESULT hr = S_OK;
  
        hr = device->CreateTexture2D(&tdesc, NULL, &rtTex);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create RT tex HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        tdesc.BindFlags = 0;
        tdesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
        tdesc.Usage = D3D11_USAGE_STAGING;
  
        hr = device->CreateTexture2D(&tdesc, NULL, &copyTex);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create copy tex HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        D3D11_RENDER_TARGET_VIEW_DESC rtDesc;
  
        rtDesc.Format = retFmt;
        rtDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
        rtDesc.Texture2D.MipSlice = 0;
  
        hr = device->CreateRenderTargetView(rtTex, &rtDesc, &rtv);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to create rt rtv HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        context->OMSetRenderTargetsAndUnorderedAccessViews(1, &rtv, NULL, 0, 0, NULL, NULL);
        context->Draw(3, 0);
  
        context->CopyResource(copyTex, rtTex);
  
        D3D11_MAPPED_SUBRESOURCE mapped;
        hr = context->Map(copyTex, 0, D3D11_MAP_READ, 0, &mapped);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to map results HRESULT: %s", ToStr(hr).c_str());
          return s;
        }
  
        ShaderVariable lookupResult("tex", 0.0f, 0.0f, 0.0f, 0.0f);
  
        memcpy(lookupResult.value.iv, mapped.pData, sizeof(uint32_t) * 4);
  
        context->Unmap(copyTex, 0);
  
        SAFE_RELEASE(rtTex);
        SAFE_RELEASE(copyTex);
        SAFE_RELEASE(rtv);
        SAFE_RELEASE(vs);
        SAFE_RELEASE(ps);
  
        // restore whatever was on PS slot 0 before we messed with it
  
        context->PSSetShaderResources(0, 1, &prevSRV);
        context->PSSetSamplers(0, 1, &prevSamp);
  
        SAFE_RELEASE(context);
  
        SAFE_RELEASE(prevSRV);
        SAFE_RELEASE(prevSamp);
  
        SAFE_RELEASE(usedSRV);
        SAFE_RELEASE(usedSamp);
  
        // should be a better way of doing this
        if(op.operands[0].comps[1] == 0xff)
          lookupResult.value.iv[0] = lookupResult.value.iv[op.operands[0].comps[0]];
  
        s.SetDst(op.operands[0], op, lookupResult);
        break;
      }
  
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      // Flow control
  
      case OPCODE_SWITCH:
      {
        uint32_t switchValue = GetSrc(op.operands[0], op).value.u.x;
  
        int depth = 0;
  
        uint32_t jumpLocation = 0;
  
        uint32_t search = s.nextInstruction;
  
        for(; search < (uint32_t)dxbc->GetNumInstructions(); search++)
        {
          const ASMOperation &nextOp = s.dxbc->GetInstruction((size_t)search);
  
          // track nested switch statements to ensure we don't accidentally pick the case from a
          // different switch
          if(nextOp.operation == OPCODE_SWITCH)
          {
            depth++;
          }
          else if(nextOp.operation == OPCODE_ENDSWITCH)
          {
            depth--;
          }
          else if(depth == 0)
          {
            // note the default: location as jumpLocation if we haven't found a matching
            // case yet. If we find one later, this will be overridden
            if(nextOp.operation == OPCODE_DEFAULT)
              jumpLocation = search;
  
            // reached end of our switch statement
            if(nextOp.operation == OPCODE_ENDSWITCH)
              break;
  
            if(nextOp.operation == OPCODE_CASE)
            {
              uint32_t caseValue = GetSrc(nextOp.operands[0], nextOp).value.u.x;
  
              // comparison is defined to be bitwise
              if(caseValue == switchValue)
              {
                // we've found our case, break out
                jumpLocation = search;
                break;
              }
            }
          }
        }
  
        // jumpLocation points to the case we're taking, either a matching case or default
  
        if(jumpLocation == 0)
        {
          RDCERR("Didn't find matching case or default: for switch(%u)!", switchValue);
        }
        else
        {
          // skip straight past any case or default labels as we don't want to step to them, we want
          // next instruction to point
          // at the next excutable instruction (which might be a break if we're doing nothing)
          for(; jumpLocation < (uint32_t)dxbc->GetNumInstructions(); jumpLocation++)
          {
            const ASMOperation &nextOp = s.dxbc->GetInstruction(jumpLocation);
  
            if(nextOp.operation != OPCODE_CASE && nextOp.operation != OPCODE_DEFAULT)
              break;
          }
  
          s.nextInstruction = jumpLocation;
        }
  
        break;
      }
      case OPCODE_CASE:
      case OPCODE_DEFAULT:
      case OPCODE_LOOP:
      case OPCODE_ENDSWITCH:
      case OPCODE_ENDIF:
        // do nothing. Basically just an anonymous label that is used elsewhere
        // (IF/ELSE/SWITCH/ENDLOOP/BREAK)
        break;
      case OPCODE_CONTINUE:
      case OPCODE_CONTINUEC:
      case OPCODE_ENDLOOP:
      {
        int depth = 0;
  
        int32_t test = op.operation == OPCODE_CONTINUEC ? GetSrc(op.operands[0], op).value.i.x : 0;
  
        if(op.operation == OPCODE_CONTINUE || op.operation == OPCODE_CONTINUEC)
          depth = 1;
  
        if((test == 0 && !op.nonzero) || (test != 0 && op.nonzero) ||
           op.operation == OPCODE_CONTINUE || op.operation == OPCODE_ENDLOOP)
        {
          // skip back one to the endloop that we're processing
          s.nextInstruction--;
  
          for(; s.nextInstruction >= 0; s.nextInstruction--)
          {
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDLOOP)
              depth++;
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_LOOP)
              depth--;
  
            if(depth == 0)
            {
              break;
            }
          }
  
          RDCASSERT(s.nextInstruction >= 0);
        }
  
        break;
      }
      case OPCODE_BREAK:
      case OPCODE_BREAKC:
      {
        int32_t test = op.operation == OPCODE_BREAKC ? GetSrc(op.operands[0], op).value.i.x : 0;
  
        if((test == 0 && !op.nonzero) || (test != 0 && op.nonzero) || op.operation == OPCODE_BREAK)
        {
          // break out (jump to next endloop/endswitch)
          int depth = 1;
  
          for(; s.nextInstruction < (int)dxbc->GetNumInstructions(); s.nextInstruction++)
          {
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_LOOP ||
               s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_SWITCH)
              depth++;
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDLOOP ||
               s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDSWITCH)
              depth--;
  
            if(depth == 0)
            {
              break;
            }
          }
  
          RDCASSERT(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDLOOP ||
                    s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDSWITCH);
  
          // don't want to process the endloop and jump again!
          s.nextInstruction++;
        }
  
        break;
      }
      case OPCODE_IF:
      {
        int32_t test = GetSrc(op.operands[0], op).value.i.x;
  
        if((test == 0 && !op.nonzero) || (test != 0 && op.nonzero))
        {
          // nothing, we go into the if.
        }
        else
        {
          // jump to after the next matching else/endif
          int depth = 0;
  
          // skip back one to the if that we're processing
          s.nextInstruction--;
  
          for(; s.nextInstruction < (int)dxbc->GetNumInstructions(); s.nextInstruction++)
          {
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_IF)
              depth++;
            // only step out on an else if it's the matching depth to our starting if (depth == 1)
            if(depth == 1 && s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ELSE)
              depth--;
            if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDIF)
              depth--;
  
            if(depth == 0)
            {
              break;
            }
          }
  
          RDCASSERT(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ELSE ||
                    s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDIF);
  
          // step to next instruction after the else/endif (processing an else would skip that block)
          s.nextInstruction++;
        }
  
        break;
      }
      case OPCODE_ELSE:
      {
        // if we hit an else then we've just processed the if() bracket and need to break out (jump to
        // next endif)
        int depth = 1;
  
        for(; s.nextInstruction < (int)dxbc->GetNumInstructions(); s.nextInstruction++)
        {
          if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_IF)
            depth++;
          if(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDIF)
            depth--;
  
          if(depth == 0)
          {
            break;
          }
        }
  
        RDCASSERT(s.dxbc->GetInstruction(s.nextInstruction).operation == OPCODE_ENDIF);
  
        // step to next instruction after the else/endif (for consistency with handling in the if
        // block)
        s.nextInstruction++;
  
        break;
      }
      case OPCODE_DISCARD:
      {
        int32_t test = GetSrc(op.operands[0], op).value.i.x;
  
        if((test != 0 && !op.nonzero) || (test == 0 && op.nonzero))
        {
          // don't discard
          break;
        }
  
        // discarding.
        s.done = true;
        break;
      }
      case OPCODE_RET:
      case OPCODE_RETC:
      {
        int32_t test = op.operation == OPCODE_RETC ? GetSrc(op.operands[0], op).value.i.x : 0;
  
        if((test == 0 && !op.nonzero) || (test != 0 && op.nonzero) || op.operation == OPCODE_RET)
        {
          // assumes not in a function call
          s.done = true;
        }
        break;
      }
      default:
      {
        RDCERR("Unsupported operation %d in assembly debugging", op.operation);
        break;
      }
    }
  
    return s;
  }
  
  };    // namespace ShaderDebug
  