void D3D11PipelineStateViewer::setState()

in qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp [1190:2116]


void D3D11PipelineStateViewer::setState()
{
  if(!m_Ctx.IsCaptureLoaded())
  {
    clearState();
    return;
  }

  const D3D11Pipe::State &state = *m_Ctx.CurD3D11PipelineState();
  const ActionDescription *action = m_Ctx.CurAction();

  const QPixmap &tick = Pixmaps::tick(this);
  const QPixmap &cross = Pixmaps::cross(this);

  ////////////////////////////////////////////////
  // Vertex Input

  if(state.inputAssembly.bytecode)
  {
    QString layout = ToQStr(state.inputAssembly.resourceId);

    if(state.inputAssembly.bytecode && !state.inputAssembly.bytecode->debugInfo.files.empty())
    {
      const ShaderDebugInfo &dbg = state.inputAssembly.bytecode->debugInfo;
      int entryFile = qMax(0, dbg.entryLocation.fileIndex);

      layout += QFormatStr(": %1() - %2")
                    .arg(state.inputAssembly.bytecode->entryPoint)
                    .arg(QFileInfo(dbg.files[entryFile].filename).fileName());
    }

    ui->iaBytecode->setText(layout);
  }
  else
  {
    ui->iaBytecode->setText(ToQStr(state.inputAssembly.resourceId));
  }

  ui->iaBytecodeMismatch->setVisible(false);

  // check for IA-VS mismatches here.
  // This should be moved to a "Render Doctor" window reporting problems
  if(state.inputAssembly.bytecode && state.vertexShader.reflection)
  {
    QString mismatchDetails;

    // VS wants more elements
    if(state.inputAssembly.bytecode->inputSignature.count() <
       state.vertexShader.reflection->inputSignature.count())
    {
      int excess = state.vertexShader.reflection->inputSignature.count() -
                   state.inputAssembly.bytecode->inputSignature.count();

      bool allSystem = true;

      // The VS signature can consume more elements as long as they are all system value types
      // (ie. SV_VertexID or SV_InstanceID)
      for(int e = 0; e < excess; e++)
      {
        if(state.vertexShader.reflection
               ->inputSignature[state.vertexShader.reflection->inputSignature.count() - 1 - e]
               .systemValue == ShaderBuiltin::Undefined)
        {
          allSystem = false;
          break;
        }
      }

      if(!allSystem)
        mismatchDetails += tr("IA bytecode provides fewer elements than VS wants.\n");
    }

    {
      const rdcarray<SigParameter> &IA = state.inputAssembly.bytecode->inputSignature;
      const rdcarray<SigParameter> &VS = state.vertexShader.reflection->inputSignature;

      int count = qMin(IA.count(), VS.count());

      for(int i = 0; i < count; i++)
      {
        QString IAname = IA[i].semanticIdxName;
        QString VSname = VS[i].semanticIdxName;

        // misorder or misnamed semantics
        if(IAname.compare(VSname, Qt::CaseInsensitive))
          mismatchDetails += tr("IA bytecode semantic %1: %2 != VS bytecode semantic %1: %3\n")
                                 .arg(i)
                                 .arg(IAname)
                                 .arg(VSname);

        // VS wants more components
        if(IA[i].compCount < VS[i].compCount)
          mismatchDetails += tr("IA bytecode semantic %1 (%2) is %4-wide).arg(VS bytecode semantic "
                                "%1 (%2) %3 is %5-wide\n")
                                 .arg(i)
                                 .arg(IAname)
                                 .arg(VSname)
                                 .arg(IA[i].compCount)
                                 .arg(VS[i].compCount);

        // VS wants different types
        if(IA[i].varType != VS[i].varType)
          mismatchDetails +=
              tr("IA bytecode semantic %1 (%2) is %4).arg(VS bytecode semantic %1 (%3) is %5\n")
                  .arg(i)
                  .arg(IAname)
                  .arg(VSname)
                  .arg(ToQStr(IA[i].varType))
                  .arg(ToQStr(VS[i].varType));
      }
    }

    if(!mismatchDetails.isEmpty())
    {
      ui->iaBytecodeMismatch->setText(
          tr("Warning: Mismatch detected between bytecode used to create IA and currently bound VS "
             "inputs"));
      ui->iaBytecodeMismatch->setToolTip(mismatchDetails.trimmed());
      ui->iaBytecodeMismatch->setVisible(true);
    }
  }

  int vs = 0;

  bool usedVBuffers[128] = {};
  uint32_t layoutOffs[128] = {};

  vs = ui->iaLayouts->verticalScrollBar()->value();
  ui->iaLayouts->beginUpdate();
  ui->iaLayouts->clear();
  {
    int i = 0;
    for(const D3D11Pipe::Layout &l : state.inputAssembly.layouts)
    {
      QString byteOffs = Formatter::HumanFormat(l.byteOffset, Formatter::OffsetSize);

      // D3D11 specific value
      if(l.byteOffset == ~0U)
      {
        byteOffs = lit("APPEND_ALIGNED (%1)").arg(layoutOffs[l.inputSlot]);
      }
      else
      {
        layoutOffs[l.inputSlot] = l.byteOffset;
      }

      layoutOffs[l.inputSlot] += l.format.compByteWidth * l.format.compCount;

      bool filledSlot = true;
      bool usedSlot = false;

      for(int ia = 0;
          state.inputAssembly.bytecode && ia < state.inputAssembly.bytecode->inputSignature.count();
          ia++)
      {
        if(!QString(state.inputAssembly.bytecode->inputSignature[ia].semanticName)
                .compare(l.semanticName, Qt::CaseInsensitive) &&
           state.inputAssembly.bytecode->inputSignature[ia].semanticIndex == l.semanticIndex)
        {
          usedSlot = true;
          break;
        }
      }

      if(showNode(usedSlot, filledSlot))
      {
        RDTreeWidgetItem *node =
            new RDTreeWidgetItem({i, l.semanticName, l.semanticIndex, l.format.Name(), l.inputSlot,
                                  byteOffs, l.perInstance ? lit("PER_INSTANCE") : lit("PER_VERTEX"),
                                  l.instanceDataStepRate, QString()});

        node->setTag(i);

        if(usedSlot)
          usedVBuffers[l.inputSlot] = true;

        if(!usedSlot)
          setInactiveRow(node);

        ui->iaLayouts->addTopLevelItem(node);
      }

      i++;
    }
  }
  ui->iaLayouts->clearSelection();
  ui->iaLayouts->endUpdate();
  ui->iaLayouts->verticalScrollBar()->setValue(vs);

  int numCPs = PatchList_Count(state.inputAssembly.topology);
  if(numCPs > 0)
  {
    ui->topology->setText(tr("PatchList (%1 Control Points)").arg(numCPs));
  }
  else
  {
    ui->topology->setText(ToQStr(state.inputAssembly.topology));
  }

  m_Common.setTopologyDiagram(ui->topologyDiagram, state.inputAssembly.topology);

  bool ibufferUsed = action && (action->flags & ActionFlags::Indexed);

  m_VBNodes.clear();
  m_EmptyNodes.clear();

  vs = ui->iaBuffers->verticalScrollBar()->value();
  ui->iaBuffers->beginUpdate();
  ui->iaBuffers->clear();

  if(state.inputAssembly.indexBuffer.resourceId != ResourceId())
  {
    if(ibufferUsed || ui->showUnused->isChecked())
    {
      uint64_t length = 0;

      BufferDescription *buf = m_Ctx.GetBuffer(state.inputAssembly.indexBuffer.resourceId);

      if(buf)
        length = buf->length;

      RDTreeWidgetItem *node = new RDTreeWidgetItem({
          tr("Index"),
          state.inputAssembly.indexBuffer.resourceId,
          Formatter::HumanFormat(state.inputAssembly.indexBuffer.byteStride, Formatter::OffsetSize),
          Formatter::HumanFormat(state.inputAssembly.indexBuffer.byteOffset, Formatter::OffsetSize),
          Formatter::HumanFormat(length, Formatter::OffsetSize),
          QString(),
      });

      QString iformat;

      if(state.inputAssembly.indexBuffer.byteStride == 1)
        iformat = lit("ubyte");
      else if(state.inputAssembly.indexBuffer.byteStride == 2)
        iformat = lit("ushort");
      else if(state.inputAssembly.indexBuffer.byteStride == 4)
        iformat = lit("uint");

      iformat +=
          lit(" indices[%1]").arg(RENDERDOC_NumVerticesPerPrimitive(state.inputAssembly.topology));

      node->setTag(QVariant::fromValue(D3D11VBIBTag(
          state.inputAssembly.indexBuffer.resourceId,
          state.inputAssembly.indexBuffer.byteOffset +
              (action ? action->indexOffset * state.inputAssembly.indexBuffer.byteStride : 0),
          iformat)));

      if(!ibufferUsed)
        setInactiveRow(node);

      if(state.inputAssembly.indexBuffer.resourceId == ResourceId())
      {
        setEmptyRow(node);
        m_EmptyNodes.push_back(node);
      }

      ui->iaBuffers->addTopLevelItem(node);
    }
  }
  else
  {
    if(ibufferUsed || ui->showEmpty->isChecked())
    {
      RDTreeWidgetItem *node = new RDTreeWidgetItem(
          {tr("Index"), tr("No Buffer Set"), lit("-"), lit("-"), lit("-"), QString()});

      QString iformat;

      if(state.inputAssembly.indexBuffer.byteStride == 1)
        iformat = lit("ubyte");
      else if(state.inputAssembly.indexBuffer.byteStride == 2)
        iformat = lit("ushort");
      else if(state.inputAssembly.indexBuffer.byteStride == 4)
        iformat = lit("uint");

      iformat +=
          lit(" indices[%1]").arg(RENDERDOC_NumVerticesPerPrimitive(state.inputAssembly.topology));

      node->setTag(QVariant::fromValue(D3D11VBIBTag(
          state.inputAssembly.indexBuffer.resourceId,
          state.inputAssembly.indexBuffer.byteOffset +
              (action ? action->indexOffset * state.inputAssembly.indexBuffer.byteStride : 0),
          iformat)));

      setEmptyRow(node);
      m_EmptyNodes.push_back(node);

      if(!ibufferUsed)
        setInactiveRow(node);

      ui->iaBuffers->addTopLevelItem(node);
    }
  }

  for(int i = 0; i < state.inputAssembly.vertexBuffers.count(); i++)
  {
    const D3D11Pipe::VertexBuffer &v = state.inputAssembly.vertexBuffers[i];

    bool filledSlot = (v.resourceId != ResourceId());
    bool usedSlot = (usedVBuffers[i]);

    if(showNode(usedSlot, filledSlot))
    {
      qulonglong length = 0;

      BufferDescription *buf = m_Ctx.GetBuffer(v.resourceId);
      if(buf)
        length = buf->length;

      RDTreeWidgetItem *node = NULL;

      if(filledSlot)
        node = new RDTreeWidgetItem({
            i,
            v.resourceId,
            Formatter::HumanFormat(v.byteStride, Formatter::OffsetSize),
            Formatter::HumanFormat(v.byteOffset, Formatter::OffsetSize),
            Formatter::HumanFormat(length, Formatter::OffsetSize),
            QString(),
        });
      else
        node =
            new RDTreeWidgetItem({i, tr("No Buffer Set"), lit("-"), lit("-"), lit("-"), QString()});

      node->setTag(QVariant::fromValue(
          D3D11VBIBTag(v.resourceId, v.byteOffset, m_Common.GetVBufferFormatString(i))));

      if(!filledSlot)
      {
        setEmptyRow(node);
        m_EmptyNodes.push_back(node);
      }

      if(!usedSlot)
        setInactiveRow(node);

      m_VBNodes.push_back(node);

      ui->iaBuffers->addTopLevelItem(node);
    }
    else
    {
      m_VBNodes.push_back(NULL);
    }
  }
  ui->iaBuffers->clearSelection();
  ui->iaBuffers->endUpdate();
  ui->iaBuffers->verticalScrollBar()->setValue(vs);

  QToolButton *shaderButtons[] = {
      ui->vsShaderViewButton, ui->hsShaderViewButton, ui->dsShaderViewButton,
      ui->gsShaderViewButton, ui->psShaderViewButton, ui->csShaderViewButton,
      ui->vsShaderEditButton, ui->hsShaderEditButton, ui->dsShaderEditButton,
      ui->gsShaderEditButton, ui->psShaderEditButton, ui->csShaderEditButton,
      ui->vsShaderSaveButton, ui->hsShaderSaveButton, ui->dsShaderSaveButton,
      ui->gsShaderSaveButton, ui->psShaderSaveButton, ui->csShaderSaveButton,
  };

  for(QToolButton *b : shaderButtons)
  {
    const D3D11Pipe::Shader *stage = stageForSender(b);

    if(stage == NULL || stage->resourceId == ResourceId())
      continue;

    b->setEnabled(stage->reflection != NULL);

    m_Common.SetupShaderEditButton(b, ResourceId(), stage->resourceId, stage->reflection);
  }

  ui->iaBytecodeViewButton->setEnabled(true);

  ////////////////////////////////////////////////
  // Main iteration over descriptor storage

  bool targets[32] = {};

  {
    ScopedTreeUpdater restorers[] = {
        // VS
        ui->vsResources,
        ui->vsSamplers,
        ui->vsCBuffers,
        ui->vsClasses,
        // GS
        ui->gsResources,
        ui->gsSamplers,
        ui->gsCBuffers,
        ui->gsClasses,
        // HS
        ui->hsResources,
        ui->hsSamplers,
        ui->hsCBuffers,
        ui->hsClasses,
        // DS
        ui->dsResources,
        ui->dsSamplers,
        ui->dsCBuffers,
        ui->dsClasses,
        // PS
        ui->psResources,
        ui->psSamplers,
        ui->psCBuffers,
        ui->psClasses,
        // CS
        ui->csResources,
        ui->csSamplers,
        ui->csCBuffers,
        ui->csClasses,
        ui->csUAVs,
        // OM - we handle this here since it overlaps with the shader-based UAVs
        ui->targetOutputs,
    };

    rdcarray<Descriptor> outputs = m_Ctx.CurPipelineState().GetOutputTargets();
    for(uint32_t i = 0; i < outputs.size(); i++)
    {
      addResourceRow(D3D11ViewTag(D3D11ViewTag::OMTarget, i, outputs[i]), NULL, true,
                     ui->targetOutputs);

      if(outputs[i].resource != ResourceId())
        targets[i] = true;
    }

    setShaderState(state.vertexShader, ui->vsShader, ui->vsResources, ui->vsSamplers,
                   ui->vsCBuffers, ui->vsClasses);
    setShaderState(state.geometryShader, ui->gsShader, ui->gsResources, ui->gsSamplers,
                   ui->gsCBuffers, ui->gsClasses);
    setShaderState(state.hullShader, ui->hsShader, ui->hsResources, ui->hsSamplers, ui->hsCBuffers,
                   ui->hsClasses);
    setShaderState(state.domainShader, ui->dsShader, ui->dsResources, ui->dsSamplers,
                   ui->dsCBuffers, ui->dsClasses);
    setShaderState(state.pixelShader, ui->psShader, ui->psResources, ui->psSamplers, ui->psCBuffers,
                   ui->psClasses);
    setShaderState(state.computeShader, ui->csShader, ui->csResources, ui->csSamplers,
                   ui->csCBuffers, ui->csClasses);

    const ShaderReflection *shaderRefls[NumShaderStages];
    RDTreeWidget *resources[] = {
        ui->vsResources, ui->hsResources, ui->dsResources,
        ui->gsResources, ui->psResources, ui->csResources,
    };
    RDTreeWidget *samplers[] = {
        ui->vsSamplers, ui->hsSamplers, ui->dsSamplers,
        ui->gsSamplers, ui->psSamplers, ui->csSamplers,
    };
    RDTreeWidget *cbuffers[] = {
        ui->vsCBuffers, ui->hsCBuffers, ui->dsCBuffers,
        ui->gsCBuffers, ui->psCBuffers, ui->csCBuffers,
    };

    for(ShaderStage stage : values<ShaderStage>())
      shaderRefls[(uint32_t)stage] = m_Ctx.CurPipelineState().GetShaderReflection(stage);

    for(uint32_t i = 0; i < m_Locations.size(); i++)
    {
      // expect only one stage per location
      ShaderStage stage = FirstStageForMask(m_Locations[i].stageMask);
      uint32_t reg = m_Locations[i].fixedBindNumber;

      bool usedSlot = false;

      if(m_Locations[i].category == DescriptorCategory::ConstantBlock)
      {
        const ConstantBlock *shaderBind = NULL;

        if(shaderRefls[(uint32_t)stage])
        {
          for(int b = 0; b < shaderRefls[(uint32_t)stage]->constantBlocks.count(); b++)
          {
            const ConstantBlock &res = shaderRefls[(uint32_t)stage]->constantBlocks[b];

            if(res.fixedBindNumber == reg)
            {
              shaderBind = &res;
              usedSlot = HasAccess(stage, m_Locations[i].category, b);
              break;
            }
          }
        }

        Descriptor b = m_Descriptors[i];

        addCBufferRow(b, reg, shaderBind, usedSlot, cbuffers[(uint32_t)stage]);
      }
      else if(m_Locations[i].category == DescriptorCategory::Sampler)
      {
        const ShaderSampler *shaderBind = NULL;

        if(shaderRefls[(uint32_t)stage])
        {
          for(int b = 0; b < shaderRefls[(uint32_t)stage]->samplers.count(); b++)
          {
            const ShaderSampler &res = shaderRefls[(uint32_t)stage]->samplers[b];

            if(res.fixedBindNumber == reg)
            {
              shaderBind = &res;
              usedSlot = HasAccess(stage, m_Locations[i].category, b);
              break;
            }
          }
        }

        addSamplerRow(m_SamplerDescriptors[i], reg, shaderBind, usedSlot, samplers[(uint32_t)stage]);
      }
      else if(m_Locations[i].category == DescriptorCategory::ReadOnlyResource)
      {
        const ShaderResource *shaderBind = NULL;

        if(shaderRefls[(uint32_t)stage])
        {
          for(int b = 0; b < shaderRefls[(uint32_t)stage]->readOnlyResources.count(); b++)
          {
            const ShaderResource &res = shaderRefls[(uint32_t)stage]->readOnlyResources[b];

            if(res.fixedBindNumber == reg)
            {
              shaderBind = &res;
              usedSlot = HasAccess(stage, m_Locations[i].category, b);
              break;
            }
          }
        }

        addResourceRow(D3D11ViewTag(D3D11ViewTag::SRV, reg, m_Descriptors[i]), shaderBind, usedSlot,
                       resources[(uint32_t)stage]);
      }
      else if(m_Locations[i].category == DescriptorCategory::ReadWriteResource)
      {
        const ShaderResource *shaderBind = NULL;

        if(stage == ShaderStage::Compute)
        {
          if(shaderRefls[(uint32_t)stage])
          {
            for(int b = 0; b < shaderRefls[(uint32_t)stage]->readWriteResources.count(); b++)
            {
              const ShaderResource &res = shaderRefls[(uint32_t)stage]->readWriteResources[b];

              if(res.fixedBindNumber == reg)
              {
                shaderBind = &res;
                usedSlot = HasAccess(stage, m_Locations[i].category, b);
                break;
              }
            }
          }

          addResourceRow(D3D11ViewTag(D3D11ViewTag::SRV, reg, m_Descriptors[i]), shaderBind,
                         usedSlot, ui->csUAVs);
        }
        else
        {
          // skip any descriptors from before the first valid OM UAV
          if(reg < state.outputMerger.uavStartSlot)
            continue;

          // only iterate UAV descriptors from the pixel shader stage - they will be duplicated
          // per-stage and below we iterate over every stage
          if(stage != ShaderStage::Pixel)
            continue;

          // any non-CS shader can use these. When that's not supported (Before feature level 11.1)
          // this search will just boil down to only PS.
          // When multiple stages use the UAV, we allow the last stage to 'win' and define its type,
          // although it would be very surprising if the types were actually different anyway.
          for(const ShaderReflection *refl : shaderRefls)
          {
            if(refl && refl->stage != ShaderStage::Compute)
            {
              for(int b = 0; b < refl->readWriteResources.count(); b++)
              {
                const ShaderResource &res = refl->readWriteResources[b];

                if(res.fixedBindNumber == reg)
                {
                  shaderBind = &res;
                  usedSlot = HasAccess(stage, m_Locations[i].category, b);
                  break;
                }
              }
            }
          }

          addResourceRow(D3D11ViewTag(D3D11ViewTag::UAV, reg, m_Descriptors[i]), shaderBind,
                         usedSlot, ui->targetOutputs);
        }
      }
    }

    addResourceRow(D3D11ViewTag(D3D11ViewTag::OMDepth, 0, m_Ctx.CurPipelineState().GetDepthTarget()),
                   NULL, true, ui->targetOutputs);

    ui->vsClasses->parentWidget()->setVisible(ui->vsClasses->topLevelItemCount() > 0);
    ui->hsClasses->parentWidget()->setVisible(ui->hsClasses->topLevelItemCount() > 0);
    ui->dsClasses->parentWidget()->setVisible(ui->dsClasses->topLevelItemCount() > 0);
    ui->gsClasses->parentWidget()->setVisible(ui->gsClasses->topLevelItemCount() > 0);
    ui->psClasses->parentWidget()->setVisible(ui->psClasses->topLevelItemCount() > 0);
    ui->csClasses->parentWidget()->setVisible(ui->csClasses->topLevelItemCount() > 0);
  }

  bool streamoutSet = false;
  vs = ui->gsStreamOut->verticalScrollBar()->value();
  ui->gsStreamOut->beginUpdate();
  ui->gsStreamOut->clear();
  for(int i = 0; i < state.streamOut.outputs.count(); i++)
  {
    const D3D11Pipe::StreamOutBind &s = state.streamOut.outputs[i];

    bool filledSlot = (s.resourceId != ResourceId());
    bool usedSlot = (filledSlot);

    if(showNode(usedSlot, filledSlot))
    {
      qulonglong length = 0;
      BufferDescription *buf = m_Ctx.GetBuffer(s.resourceId);

      if(buf)
        length = buf->length;

      RDTreeWidgetItem *node = new RDTreeWidgetItem({
          i,
          s.resourceId,
          Formatter::HumanFormat(length, Formatter::OffsetSize),
          Formatter::HumanFormat(s.byteOffset, Formatter::OffsetSize),
          QString(),
      });

      node->setTag(QVariant::fromValue(s.resourceId));

      if(!filledSlot)
        setEmptyRow(node);

      if(!usedSlot)
        setInactiveRow(node);

      streamoutSet = true;

      ui->gsStreamOut->addTopLevelItem(node);
    }
  }
  ui->gsStreamOut->verticalScrollBar()->setValue(vs);
  ui->gsStreamOut->clearSelection();
  ui->gsStreamOut->endUpdate();

  ui->gsStreamOut->setVisible(streamoutSet);
  ui->soGroup->setVisible(streamoutSet);

  ////////////////////////////////////////////////
  // Rasterizer

  vs = ui->viewports->verticalScrollBar()->value();
  ui->viewports->beginUpdate();
  ui->viewports->clear();
  for(int i = 0; i < state.rasterizer.viewports.count(); i++)
  {
    const Viewport &v = state.rasterizer.viewports[i];

    if(v.enabled || ui->showEmpty->isChecked())
    {
      RDTreeWidgetItem *node =
          new RDTreeWidgetItem({i, v.x, v.y, v.width, v.height, v.minDepth, v.maxDepth});

      if(v.width == 0 || v.height == 0 || v.minDepth == v.maxDepth)
        setEmptyRow(node);

      if(!v.enabled)
        setInactiveRow(node);

      ui->viewports->addTopLevelItem(node);
    }
  }
  ui->viewports->verticalScrollBar()->setValue(vs);
  ui->viewports->clearSelection();
  ui->viewports->endUpdate();

  vs = ui->scissors->verticalScrollBar()->value();
  ui->scissors->beginUpdate();
  ui->scissors->clear();
  for(int i = 0; i < state.rasterizer.scissors.count(); i++)
  {
    const Scissor &s = state.rasterizer.scissors[i];

    if((s.enabled && state.rasterizer.state.scissorEnable) || ui->showEmpty->isChecked())
    {
      RDTreeWidgetItem *node = new RDTreeWidgetItem({i, s.x, s.y, s.width, s.height});

      if(s.width == 0 || s.height == 0)
        setEmptyRow(node);

      if(!s.enabled)
        setInactiveRow(node);

      ui->scissors->addTopLevelItem(node);
    }
  }
  ui->scissors->clearSelection();
  ui->scissors->verticalScrollBar()->setValue(vs);
  ui->scissors->endUpdate();

  ui->rastState->setText(ToQStr(state.rasterizer.state.resourceId));

  ui->fillMode->setText(ToQStr(state.rasterizer.state.fillMode));
  ui->cullMode->setText(ToQStr(state.rasterizer.state.cullMode));
  ui->frontCCW->setPixmap(state.rasterizer.state.frontCCW ? tick : cross);

  ui->scissorEnabled->setPixmap(state.rasterizer.state.scissorEnable ? tick : cross);
  ui->lineAA->setPixmap(state.rasterizer.state.antialiasedLines ? tick : cross);
  ui->multisample->setPixmap(state.rasterizer.state.multisampleEnable ? tick : cross);

  ui->depthClip->setPixmap(state.rasterizer.state.depthClip ? tick : cross);
  ui->depthBias->setText(Formatter::Format(state.rasterizer.state.depthBias));
  ui->depthBiasClamp->setText(Formatter::Format(state.rasterizer.state.depthBiasClamp));
  ui->slopeScaledBias->setText(Formatter::Format(state.rasterizer.state.slopeScaledDepthBias));
  ui->forcedSampleCount->setText(QString::number(state.rasterizer.state.forcedSampleCount));
  ui->conservativeRaster->setPixmap(
      state.rasterizer.state.conservativeRasterization != ConservativeRaster::Disabled ? tick
                                                                                       : cross);

  ////////////////////////////////////////////////
  // Predication

  if(state.predication.resourceId == ResourceId())
  {
    ui->predicateGroup->setVisible(false);
  }
  else
  {
    ui->predicateGroup->setVisible(true);
    ui->predicate->setText(ToQStr(state.predication.resourceId));
    ui->predicateValue->setText(state.predication.value ? lit("TRUE") : lit("FALSE"));
    ui->predicatePassing->setPixmap(state.predication.isPassing ? tick : cross);
  }

  ////////////////////////////////////////////////
  // Output Merger

  vs = ui->blends->verticalScrollBar()->value();
  ui->blends->beginUpdate();
  ui->blends->clear();
  {
    int i = 0;
    for(const ColorBlend &blend : state.outputMerger.blendState.blends)
    {
      bool filledSlot = (blend.enabled || targets[i]);
      bool usedSlot = (targets[i]);

      if(showNode(usedSlot, filledSlot))
      {
        RDTreeWidgetItem *node = NULL;

        node = new RDTreeWidgetItem(
            {i, blend.enabled ? tr("True") : tr("False"),

             ToQStr(blend.colorBlend.source), ToQStr(blend.colorBlend.destination),
             ToQStr(blend.colorBlend.operation),

             ToQStr(blend.alphaBlend.source), ToQStr(blend.alphaBlend.destination),
             ToQStr(blend.alphaBlend.operation),

             blend.logicOperationEnabled ? ToQStr(blend.logicOperation) : tr("Disabled"),

             QFormatStr("%1%2%3%4")
                 .arg((blend.writeMask & 0x1) == 0 ? lit("_") : lit("R"))
                 .arg((blend.writeMask & 0x2) == 0 ? lit("_") : lit("G"))
                 .arg((blend.writeMask & 0x4) == 0 ? lit("_") : lit("B"))
                 .arg((blend.writeMask & 0x8) == 0 ? lit("_") : lit("A"))});

        if(!filledSlot)
          setEmptyRow(node);

        if(!usedSlot)
          setInactiveRow(node);

        ui->blends->addTopLevelItem(node);
      }

      i++;
    }
  }
  ui->blends->clearSelection();
  ui->blends->endUpdate();
  ui->blends->verticalScrollBar()->setValue(vs);

  ui->blendState->setText(ToQStr(state.outputMerger.blendState.resourceId));

  ui->alphaToCoverage->setPixmap(state.outputMerger.blendState.alphaToCoverage ? tick : cross);
  ui->independentBlend->setPixmap(state.outputMerger.blendState.independentBlend ? tick : cross);
  ui->sampleMask->setText(Formatter::Format(state.outputMerger.blendState.sampleMask, true));

  ui->blendFactor->setText(QFormatStr("%1, %2, %3, %4")
                               .arg(state.outputMerger.blendState.blendFactor[0], 0, 'f', 2)
                               .arg(state.outputMerger.blendState.blendFactor[1], 0, 'f', 2)
                               .arg(state.outputMerger.blendState.blendFactor[2], 0, 'f', 2)
                               .arg(state.outputMerger.blendState.blendFactor[3], 0, 'f', 2));

  ui->depthState->setText(ToQStr(state.outputMerger.depthStencilState.resourceId));

  if(state.outputMerger.depthStencilState.depthEnable)
  {
    ui->depthEnabled->setPixmap(tick);
    ui->depthFunc->setText(ToQStr(state.outputMerger.depthStencilState.depthFunction));
    ui->depthWrite->setPixmap(state.outputMerger.depthStencilState.depthWrites ? tick : cross);
    ui->depthWrite->setText(QString());
  }
  else
  {
    ui->depthEnabled->setPixmap(cross);
    ui->depthFunc->setText(tr("Disabled"));
    ui->depthWrite->setPixmap(QPixmap());
    ui->depthWrite->setText(tr("Disabled"));
  }

  ui->stencilEnabled->setPixmap(state.outputMerger.depthStencilState.stencilEnable ? tick : cross);
  m_Common.SetStencilLabelValue(
      ui->stencilReadMask, (uint8_t)state.outputMerger.depthStencilState.frontFace.compareMask);
  m_Common.SetStencilLabelValue(ui->stencilWriteMask,
                                (uint8_t)state.outputMerger.depthStencilState.frontFace.writeMask);
  m_Common.SetStencilLabelValue(ui->stencilRef,
                                (uint8_t)state.outputMerger.depthStencilState.frontFace.reference);

  ui->stencils->beginUpdate();
  ui->stencils->clear();
  ui->stencils->addTopLevelItem(new RDTreeWidgetItem(
      {tr("Front"), ToQStr(state.outputMerger.depthStencilState.frontFace.function),
       ToQStr(state.outputMerger.depthStencilState.frontFace.failOperation),
       ToQStr(state.outputMerger.depthStencilState.frontFace.depthFailOperation),
       ToQStr(state.outputMerger.depthStencilState.frontFace.passOperation)}));
  ui->stencils->addTopLevelItem(new RDTreeWidgetItem(
      {tr("Back"), ToQStr(state.outputMerger.depthStencilState.backFace.function),
       ToQStr(state.outputMerger.depthStencilState.backFace.failOperation),
       ToQStr(state.outputMerger.depthStencilState.backFace.depthFailOperation),
       ToQStr(state.outputMerger.depthStencilState.backFace.passOperation)}));
  ui->stencils->clearSelection();
  ui->stencils->endUpdate();

  // set up thread debugging inputs
  bool enableDebug = m_Ctx.APIProps().shaderDebugging && state.computeShader.reflection &&
                     state.computeShader.reflection->debugInfo.debuggable && action &&
                     (action->flags & ActionFlags::Dispatch);
  if(enableDebug)
  {
    // Validate dispatch/threadgroup dimensions
    enableDebug &= action->dispatchDimension[0] > 0;
    enableDebug &= action->dispatchDimension[1] > 0;
    enableDebug &= action->dispatchDimension[2] > 0;

    const rdcfixedarray<uint32_t, 3> &threadDims =
        (action->dispatchThreadsDimension[0] == 0)
            ? state.computeShader.reflection->dispatchThreadsDimension
            : action->dispatchThreadsDimension;
    enableDebug &= threadDims[0] > 0;
    enableDebug &= threadDims[1] > 0;
    enableDebug &= threadDims[2] > 0;
  }

  if(enableDebug)
  {
    ui->computeDebugSelector->setEnabled(true);

    // set maximums for CS debugging
    m_ComputeDebugSelector->SetThreadBounds(
        action->dispatchDimension, (action->dispatchThreadsDimension[0] == 0)
                                       ? state.computeShader.reflection->dispatchThreadsDimension
                                       : action->dispatchThreadsDimension);

    ui->computeDebugSelector->setToolTip(
        tr("Debug this compute shader by specifying group/thread ID or dispatch ID"));
  }
  else
  {
    ui->computeDebugSelector->setEnabled(false);

    if(!m_Ctx.APIProps().shaderDebugging)
      ui->computeDebugSelector->setToolTip(tr("This API does not support shader debugging"));
    else if(!action || !(action->flags & ActionFlags::Dispatch))
      ui->computeDebugSelector->setToolTip(tr("No dispatch selected"));
    else if(!state.computeShader.reflection)
      ui->computeDebugSelector->setToolTip(tr("No compute shader bound"));
    else if(!state.computeShader.reflection->debugInfo.debuggable)
      ui->computeDebugSelector->setToolTip(
          tr("This shader doesn't support debugging: %1")
              .arg(state.computeShader.reflection->debugInfo.debugStatus));
    else
      ui->computeDebugSelector->setToolTip(tr("Invalid dispatch/threadgroup dimensions."));
  }

  // highlight the appropriate stages in the flowchart
  if(action == NULL)
  {
    ui->pipeFlow->setStagesEnabled({true, true, true, true, true, true, true, true, true});
  }
  else if(action->flags & ActionFlags::Dispatch)
  {
    ui->pipeFlow->setStagesEnabled({false, false, false, false, false, false, false, false, true});
  }
  else
  {
    bool streamOutActive = false;

    for(const D3D11Pipe::StreamOutBind &o : state.streamOut.outputs)
    {
      if(o.resourceId != ResourceId())
      {
        streamOutActive = true;
        break;
      }
    }

    if(state.geometryShader.resourceId == ResourceId() && streamOutActive)
    {
      ui->pipeFlow->setStageName(4, lit("SO"), tr("Stream Out"));
    }
    else
    {
      ui->pipeFlow->setStageName(4, lit("GS"), tr("Geometry Shader"));
    }

    ui->pipeFlow->setStagesEnabled(
        {true, true, state.hullShader.resourceId != ResourceId(),
         state.domainShader.resourceId != ResourceId(),
         state.geometryShader.resourceId != ResourceId() || streamOutActive, true,
         state.pixelShader.resourceId != ResourceId(), true, false});
  }
}