void VulkanPipelineStateViewer::setState()

in qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp [1736:2986]


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

  // cache latest state of these checkboxes
  m_ShowUnused = ui->showUnused->isChecked();
  m_ShowEmpty = ui->showEmpty->isChecked();

  m_CombinedImageSamplers.clear();

  const VKPipe::State &state = *m_Ctx.CurVulkanPipelineState();
  const ActionDescription *action = m_Ctx.CurAction();

  bool showUnused = ui->showUnused->isChecked();
  bool showEmpty = ui->showEmpty->isChecked();

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

  bool usedBindings[128] = {};

  // highlight the appropriate stages in the flowchart
  if(action == NULL)
  {
    QList<bool> allOn;
    for(int i = 0; i < ui->pipeFlow->stageNames().count(); i++)
      allOn.append(true);
    ui->pipeFlow->setStagesEnabled(allOn);
  }
  else if(action->flags & ActionFlags::Dispatch)
  {
    QList<bool> computeOnly;
    for(int i = 0; i < ui->pipeFlow->stageNames().count(); i++)
      computeOnly.append(false);
    computeOnly.back() = true;
    ui->pipeFlow->setStagesEnabled(computeOnly);
  }
  else if(action->flags & ActionFlags::MeshDispatch)
  {
    setNewMeshPipeFlow();
    ui->pipeFlow->setStagesEnabled(
        {state.taskShader.resourceId != ResourceId(), true, true, true, true, false});
  }
  else
  {
    bool xfbActive = !state.transformFeedback.buffers.isEmpty();

    bool raster = true;

    if(state.rasterizer.rasterizerDiscardEnable)
    {
      raster = false;
    }

    if(state.geometryShader.resourceId == ResourceId() && xfbActive)
    {
      ui->pipeFlow->setStageName(4, lit("XFB"), tr("Transform Feedback"));
    }
    else
    {
      ui->pipeFlow->setStageName(4, lit("GS"), tr("Geometry Shader"));
    }

    setOldMeshPipeFlow();
    ui->pipeFlow->setStagesEnabled(
        {true, true, state.tessControlShader.resourceId != ResourceId(),
         state.tessEvalShader.resourceId != ResourceId(),
         state.geometryShader.resourceId != ResourceId() || xfbActive, raster,
         raster && state.fragmentShader.resourceId != ResourceId(), raster, false});
  }

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

  int vs = 0;

  if(m_MeshPipe)
  {
    setShaderState(state.graphics, state.taskShader, ui->tsShader, ui->tsPipeLayout, ui->tsDescSets);
    setShaderState(state.graphics, state.meshShader, ui->msShader, ui->msPipeLayout, ui->msDescSets);

    if(state.meshShader.reflection)
      ui->msTopology->setText(ToQStr(state.meshShader.reflection->outputTopology));
    else
      ui->msTopology->setText(QString());
  }
  else
  {
    vs = ui->viAttrs->verticalScrollBar()->value();
    ui->viAttrs->beginUpdate();
    ui->viAttrs->clear();
    {
      int i = 0;
      for(const VKPipe::VertexAttribute &a : state.vertexInput.attributes)
      {
        bool usedSlot = false;

        QString name = tr("Attribute %1").arg(i);

        if(state.vertexShader.resourceId != ResourceId())
        {
          uint32_t attrib = a.location;

          if(attrib < state.vertexShader.reflection->inputSignature.size())
          {
            name = state.vertexShader.reflection->inputSignature[attrib].varName;
            usedSlot = true;
          }
        }

        if(showNode(usedSlot, /*filledSlot*/ true))
        {
          RDTreeWidgetItem *node = new RDTreeWidgetItem(
              {i, name, a.location, a.binding, a.format.Name(), a.byteOffset, QString()});

          node->setTag(i);

          usedBindings[a.binding] = true;

          if(!usedSlot)
            setInactiveRow(node);

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

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

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

    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);

    ui->primRestart->setVisible(state.inputAssembly.primitiveRestartEnable);

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

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

    if(state.inputAssembly.indexBuffer.resourceId != ResourceId())
    {
      if(ibufferUsed || showUnused)
      {
        uint64_t length = 1;

        if(!ibufferUsed)
          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, tr("Index"), lit("-"),
             (qulonglong)state.inputAssembly.indexBuffer.byteOffset,
             (qulonglong)state.inputAssembly.indexBuffer.byteStride, (qulonglong)length, 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(VulkanVBIBTag(
            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->viBuffers->addTopLevelItem(node);
      }
    }
    else
    {
      if(ibufferUsed || showEmpty)
      {
        RDTreeWidgetItem *node =
            new RDTreeWidgetItem({tr("Index"), ResourceId(), tr("Index"), lit("-"), 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(VulkanVBIBTag(
            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->viBuffers->addTopLevelItem(node);
      }
    }

    {
      int i = 0;
      for(; i < qMax(state.vertexInput.vertexBuffers.count(), state.vertexInput.bindings.count()); i++)
      {
        const VKPipe::VertexBuffer *vbuff =
            (i < state.vertexInput.vertexBuffers.count() ? &state.vertexInput.vertexBuffers[i]
                                                         : NULL);
        const VKPipe::VertexBinding *bind = NULL;

        for(int b = 0; b < state.vertexInput.bindings.count(); b++)
        {
          if(state.vertexInput.bindings[b].vertexBufferBinding == (uint32_t)i)
            bind = &state.vertexInput.bindings[b];
        }

        bool filledSlot = ((vbuff != NULL && vbuff->resourceId != ResourceId()) || bind != NULL);
        bool usedSlot = (usedBindings[i]);

        if(showNode(usedSlot, filledSlot))
        {
          QString rate = lit("-");
          uint64_t length = 1;
          uint64_t offset = 0;
          uint32_t stride = 0;
          uint32_t divisor = 1;

          if(vbuff != NULL)
          {
            offset = vbuff->byteOffset;
            stride = vbuff->byteStride;
            length = vbuff->byteSize;

            BufferDescription *buf = m_Ctx.GetBuffer(vbuff->resourceId);
            if(buf && length >= ULONG_MAX)
              length = buf->length;
          }

          if(bind != NULL)
          {
            rate = bind->perInstance ? tr("Instance") : tr("Vertex");
            if(bind->perInstance)
              divisor = bind->instanceDivisor;
          }
          else
          {
            rate += tr("No Binding");
          }

          RDTreeWidgetItem *node = NULL;

          if(filledSlot)
            node = new RDTreeWidgetItem({i, vbuff->resourceId, rate, divisor, (qulonglong)offset,
                                         stride, (qulonglong)length, QString()});
          else
            node = new RDTreeWidgetItem(
                {i, tr("No Binding"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), QString()});

          node->setTag(QVariant::fromValue(VulkanVBIBTag(
              vbuff != NULL ? vbuff->resourceId : ResourceId(),
              vbuff != NULL ? vbuff->byteOffset : 0, m_Common.GetVBufferFormatString(i))));

          if(!filledSlot || bind == NULL || vbuff == NULL || vbuff->resourceId == ResourceId())
          {
            setEmptyRow(node);
            m_EmptyNodes.push_back(node);
          }

          if(!usedSlot)
            setInactiveRow(node);

          m_VBNodes.push_back(node);

          ui->viBuffers->addTopLevelItem(node);
        }
        else
        {
          m_VBNodes.push_back(NULL);
        }
      }

      for(; i < (int)ARRAY_COUNT(usedBindings); i++)
      {
        if(usedBindings[i])
        {
          RDTreeWidgetItem *node = new RDTreeWidgetItem(
              {i, tr("No Binding"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), QString()});

          node->setTag(QVariant::fromValue(VulkanVBIBTag(ResourceId(), 0)));

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

          setInactiveRow(node);

          ui->viBuffers->addTopLevelItem(node);

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

    setShaderState(state.graphics, state.vertexShader, ui->vsShader, ui->vsPipeLayout,
                   ui->vsDescSets);
    setShaderState(state.graphics, state.geometryShader, ui->gsShader, ui->gsPipeLayout,
                   ui->gsDescSets);
    setShaderState(state.graphics, state.tessControlShader, ui->tcsShader, ui->tcsPipeLayout,
                   ui->tcsDescSets);
    setShaderState(state.graphics, state.tessEvalShader, ui->tesShader, ui->tesPipeLayout,
                   ui->tesDescSets);
  }

  setShaderState(state.graphics, state.fragmentShader, ui->fsShader, ui->fsPipeLayout,
                 ui->fsDescSets);
  setShaderState(state.compute, state.computeShader, ui->csShader, ui->csPipeLayout, ui->csDescSets);

  // fill in descriptor access
  {
    RDTreeWidget *resources[] = {
        ui->vsResources, ui->tcsResources, ui->tesResources, ui->gsResources,
        ui->fsResources, ui->csResources,  ui->tsResources,  ui->msResources,
    };

    RDTreeWidget *ubos[] = {
        ui->vsUBOs, ui->tcsUBOs, ui->tesUBOs, ui->gsUBOs,
        ui->fsUBOs, ui->csUBOs,  ui->tsUBOs,  ui->msUBOs,
    };

    ScopedTreeUpdater restorers[] = {
        ui->vsResources, ui->tcsResources, ui->tesResources, ui->gsResources,
        ui->fsResources, ui->csResources,  ui->tsResources,  ui->msResources,
        ui->vsUBOs,      ui->tcsUBOs,      ui->tesUBOs,      ui->gsUBOs,
        ui->fsUBOs,      ui->csUBOs,       ui->tsUBOs,       ui->msUBOs,
    };

    // samplers we only deduplicate within a stage
    QMap<ResourceId, RDTreeWidgetItem *> samplers[NumShaderStages];

    const ShaderReflection *shaderRefls[NumShaderStages];

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

    rdcarray<UsedDescriptor> descriptors = m_Ctx.CurPipelineState().GetAllUsedDescriptors();
    rdcarray<ResourceId> descSets;

    const VKPipe::Pipeline &pipeline =
        (action && (action->flags & ActionFlags::Dispatch)) ? state.compute : state.graphics;

    QMap<QPair<ResourceId, uint64_t>, uint32_t> dynamicOffsets;

    for(const VKPipe::DescriptorSet &set : pipeline.descriptorSets)
    {
      descSets.push_back(set.descriptorSetResourceId);

      for(const VKPipe::DynamicOffset &offs : set.dynamicOffsets)
      {
        dynamicOffsets[{set.descriptorSetResourceId, offs.descriptorByteOffset}] =
            offs.dynamicBufferByteOffset;
      }
    }

    std::sort(descriptors.begin(), descriptors.end(),
              [descSets](const UsedDescriptor &a, const UsedDescriptor &b) {
                int32_t a_set = descSets.indexOf(a.access.descriptorStore);
                int32_t b_set = descSets.indexOf(b.access.descriptorStore);

                // non-set associated things (specialisation constants, push constants, etc) to the end
                if(a_set == -1)
                  a_set = descSets.count() + 1;
                if(b_set == -1)
                  b_set = descSets.count() + 1;

                if(a_set != b_set)
                  return a_set < b_set;

                // for non-sets, sort by interface index
                if(a_set == b_set && a_set > descSets.count())
                {
                  return a.access.index < b.access.index;
                }

                // otherwise for normal sets, sort by byte offset
                return a.access.byteOffset < b.access.byteOffset;
              });

    for(const UsedDescriptor &used : descriptors)
    {
      const ShaderReflection *refl = shaderRefls[(uint32_t)used.access.stage];

      uint32_t dynamicOffset = 0;
      auto dynIt =
          dynamicOffsets.find({used.access.descriptorStore, (uint64_t)used.access.byteOffset});
      if(dynIt != dynamicOffsets.end())
        dynamicOffset = *dynIt;

      if(IsConstantBlockDescriptor(used.access.type))
      {
        const ConstantBlock *shaderBind = NULL;

        if(refl && used.access.index < refl->constantBlocks.size())
          shaderBind = &refl->constantBlocks[used.access.index];

        addConstantBlockRow(shaderBind, used, dynamicOffset, ubos[(uint32_t)used.access.stage]);
      }
      else
      {
        const bool ro = IsReadOnlyDescriptor(used.access.type);

        const ShaderResource *shaderRes = NULL;
        const ShaderSampler *shaderSamp = NULL;

        if(IsSamplerDescriptor(used.access.type))
        {
          if(refl && used.access.index < refl->samplers.size())
            shaderSamp = &refl->samplers[used.access.index];
        }
        else if(IsReadOnlyDescriptor(used.access.type))
        {
          if(refl && used.access.index < refl->readOnlyResources.size())
            shaderRes = &refl->readOnlyResources[used.access.index];
        }
        else
        {
          if(refl && used.access.index < refl->readWriteResources.size())
            shaderRes = &refl->readWriteResources[used.access.index];
        }

        addResourceRow(shaderRes, shaderSamp, used, dynamicOffset,
                       resources[(uint32_t)used.access.stage], samplers[(uint32_t)used.access.stage]);
      }
    }
  }

  QToolButton *shaderButtons[] = {
      // view buttons
      ui->tsShaderViewButton,
      ui->msShaderViewButton,
      ui->vsShaderViewButton,
      ui->tcsShaderViewButton,
      ui->tesShaderViewButton,
      ui->gsShaderViewButton,
      ui->fsShaderViewButton,
      ui->csShaderViewButton,
      // edit buttons
      ui->tsShaderEditButton,
      ui->msShaderEditButton,
      ui->vsShaderEditButton,
      ui->tcsShaderEditButton,
      ui->tesShaderEditButton,
      ui->gsShaderEditButton,
      ui->fsShaderEditButton,
      ui->csShaderEditButton,
      // save buttons
      ui->tsShaderSaveButton,
      ui->msShaderSaveButton,
      ui->vsShaderSaveButton,
      ui->tcsShaderSaveButton,
      ui->tesShaderSaveButton,
      ui->gsShaderSaveButton,
      ui->fsShaderSaveButton,
      ui->csShaderSaveButton,
  };

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

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

    ResourceId pipe = stage->stage == ShaderStage::Compute ? state.compute.pipelineResourceId
                                                           : state.graphics.pipelineResourceId;

    b->setEnabled(stage->reflection && pipe != ResourceId());

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

  QToolButton *messageButtons[] = {
      ui->vsShaderMessagesButton, ui->tcsShaderMessagesButton, ui->tesShaderMessagesButton,
      ui->gsShaderMessagesButton, ui->fsShaderMessagesButton,  ui->csShaderMessagesButton,
      ui->tsShaderMessagesButton, ui->msShaderMessagesButton,
  };

  int numMessages[NumShaderStages] = {};

  for(const ShaderMessage &msg : state.shaderMessages)
    numMessages[(uint32_t)msg.stage]++;

  static_assert(ARRAY_COUNT(messageButtons) <= ARRAY_COUNT(numMessages),
                "More buttons than shader stages");

  for(uint32_t i = 0; i < ARRAY_COUNT(messageButtons); i++)
  {
    messageButtons[i]->setVisible(numMessages[i] > 0);
    messageButtons[i]->setText(tr("%n Message(s)", "", numMessages[i]));
  }

  bool xfbSet = false;
  vs = ui->xfbBuffers->verticalScrollBar()->value();
  ui->xfbBuffers->beginUpdate();
  ui->xfbBuffers->clear();
  for(int i = 0; i < state.transformFeedback.buffers.count(); i++)
  {
    const VKPipe::XFBBuffer &s = state.transformFeedback.buffers[i];

    bool filledSlot = (s.bufferResourceId != ResourceId());
    bool usedSlot = (s.active);

    if(showNode(usedSlot, filledSlot))
    {
      qulonglong length = s.byteSize;

      BufferDescription *buf = m_Ctx.GetBuffer(s.bufferResourceId);

      if(buf && length == UINT64_MAX)
        length = buf->length - s.byteOffset;

      RDTreeWidgetItem *node = new RDTreeWidgetItem({
          i,
          s.active ? tr("Active") : tr("Inactive"),
          s.bufferResourceId,
          Formatter::HumanFormat(s.byteOffset, Formatter::OffsetSize),
          Formatter::HumanFormat(length, Formatter::OffsetSize),
          s.counterBufferResourceId,
          Formatter::HumanFormat(s.counterBufferOffset, Formatter::OffsetSize),
          QString(),
      });

      node->setTag(QVariant::fromValue(VulkanBufferTag(s.bufferResourceId, s.byteOffset, length)));

      if(!filledSlot)
        setEmptyRow(node);

      if(!usedSlot)
        setInactiveRow(node);

      xfbSet = true;

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

  ui->xfbBuffers->setVisible(xfbSet);
  ui->xfbGroup->setVisible(xfbSet);

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

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

  {
    int i = 0;
    for(const VKPipe::RenderArea &v : state.viewportScissor.discardRectangles)
    {
      RDTreeWidgetItem *node = new RDTreeWidgetItem({i, v.x, v.y, v.width, v.height});
      ui->discards->addTopLevelItem(node);

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

      i++;
    }
  }

  ui->discards->verticalScrollBar()->setValue(vs);
  ui->discards->clearSelection();
  ui->discards->endUpdate();

  ui->discardMode->setText(state.viewportScissor.discardRectanglesExclusive ? tr("Exclusive")
                                                                            : tr("Inclusive"));

  ui->discardGroup->setVisible(!state.viewportScissor.discardRectanglesExclusive ||
                               !state.viewportScissor.discardRectangles.isEmpty());

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

  int vs2 = ui->scissors->verticalScrollBar()->value();
  ui->scissors->beginUpdate();
  ui->scissors->clear();

  if(state.currentPass.renderpass.resourceId != ResourceId() || state.currentPass.renderpass.dynamic)
  {
    ui->scissors->addTopLevelItem(new RDTreeWidgetItem(
        {tr("Render Area"), state.currentPass.renderArea.x, state.currentPass.renderArea.y,
         state.currentPass.renderArea.width, state.currentPass.renderArea.height}));
  }

  {
    const QString ndcDepthRange =
        state.viewportScissor.depthNegativeOneToOne ? lit("[-1, 1]") : lit("[0, 1]");

    int i = 0;
    for(const VKPipe::ViewportScissor &v : state.viewportScissor.viewportScissors)
    {
      RDTreeWidgetItem *node = new RDTreeWidgetItem({i, v.vp.x, v.vp.y, v.vp.width, v.vp.height,
                                                     v.vp.minDepth, v.vp.maxDepth, ndcDepthRange});
      ui->viewports->addTopLevelItem(node);

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

      node = new RDTreeWidgetItem({i, v.scissor.x, v.scissor.y, v.scissor.width, v.scissor.height});
      ui->scissors->addTopLevelItem(node);

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

      i++;
    }
  }

  ui->viewports->verticalScrollBar()->setValue(vs);
  ui->viewports->clearSelection();
  ui->scissors->clearSelection();
  ui->scissors->verticalScrollBar()->setValue(vs2);

  ui->viewports->endUpdate();
  ui->scissors->endUpdate();

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

  if(state.rasterizer.depthBiasEnable)
  {
    ui->depthBias->setPixmap(QPixmap());
    ui->depthBiasClamp->setPixmap(QPixmap());
    ui->slopeScaledBias->setPixmap(QPixmap());
    ui->depthBias->setText(Formatter::Format(state.rasterizer.depthBias));
    ui->depthBiasClamp->setText(Formatter::Format(state.rasterizer.depthBiasClamp));
    ui->slopeScaledBias->setText(Formatter::Format(state.rasterizer.slopeScaledDepthBias));
  }
  else
  {
    ui->depthBias->setText(QString());
    ui->depthBiasClamp->setText(QString());
    ui->slopeScaledBias->setText(QString());
    ui->depthBias->setPixmap(cross);
    ui->depthBiasClamp->setPixmap(cross);
    ui->slopeScaledBias->setPixmap(cross);
  }

  ui->depthClamp->setPixmap(state.rasterizer.depthClampEnable ? tick : cross);
  ui->depthClip->setPixmap(state.rasterizer.depthClipEnable ? tick : cross);
  ui->rasterizerDiscard->setPixmap(state.rasterizer.rasterizerDiscardEnable ? tick : cross);
  ui->lineWidth->setText(Formatter::Format(state.rasterizer.lineWidth));

  QString conservRaster = ToQStr(state.rasterizer.conservativeRasterization);
  if(state.rasterizer.conservativeRasterization == ConservativeRaster::Overestimate &&
     state.rasterizer.extraPrimitiveOverestimationSize > 0.0f)
    conservRaster += QFormatStr(" (+%1)").arg(state.rasterizer.extraPrimitiveOverestimationSize);

  ui->conservativeRaster->setText(conservRaster);

  if(state.rasterizer.lineStippleFactor == 0)
  {
    ui->stippleFactor->setText(QString());
    ui->stippleFactor->setPixmap(cross);
    ui->stipplePattern->setText(QString());
    ui->stipplePattern->setPixmap(cross);
  }
  else
  {
    ui->stippleFactor->setPixmap(QPixmap());
    ui->stippleFactor->setText(ToQStr(state.rasterizer.lineStippleFactor));
    ui->stipplePattern->setPixmap(QPixmap());
    ui->stipplePattern->setText(QString::number(state.rasterizer.lineStipplePattern, 2));
  }

  ui->pipelineShadingRate->setText(QFormatStr("%1x%2")
                                       .arg(state.rasterizer.pipelineShadingRate.first)
                                       .arg(state.rasterizer.pipelineShadingRate.second));
  ui->shadingRateCombiners->setText(
      QFormatStr("%1, %2")
          .arg(ToQStr(state.rasterizer.shadingRateCombiners.first, GraphicsAPI::Vulkan))
          .arg(ToQStr(state.rasterizer.shadingRateCombiners.second, GraphicsAPI::Vulkan)));

  ui->provokingVertex->setText(state.rasterizer.provokingVertexFirst ? tr("First") : tr("Last"));

  if(state.currentPass.renderpass.multiviews.isEmpty())
  {
    ui->multiview->setText(tr("Disabled"));
  }
  else
  {
    QString views = tr("Views: ");
    for(int i = 0; i < state.currentPass.renderpass.multiviews.count(); i++)
    {
      if(i > 0)
        views += lit(", ");
      views += QString::number(state.currentPass.renderpass.multiviews[i]);
    }
    ui->multiview->setText(views);
  }

  ui->sampleCount->setText(QString::number(state.multisample.rasterSamples));
  ui->sampleShading->setPixmap(state.multisample.sampleShadingEnable ? tick : cross);
  ui->minSampleShading->setText(Formatter::Format(state.multisample.minSampleShading));
  ui->sampleMask->setText(Formatter::Format(state.multisample.sampleMask, true));
  ui->alphaToOne->setPixmap(state.colorBlend.alphaToOneEnable ? tick : cross);
  ui->alphaToCoverage->setPixmap(state.colorBlend.alphaToCoverageEnable ? tick : cross);

  ////////////////////////////////////////////////
  // Conditional Rendering

  if(state.conditionalRendering.bufferId == ResourceId())
  {
    ui->conditionalRenderingGroup->setVisible(false);
    ui->csConditionalRenderingGroup->setVisible(false);
  }
  else
  {
    ui->conditionalRenderingGroup->setVisible(true);
    ui->predicateBuffer->setText(QFormatStr("%1 (Byte Offset %2)")
                                     .arg(ToQStr(state.conditionalRendering.bufferId))
                                     .arg(state.conditionalRendering.byteOffset));
    ui->predicatePassing->setPixmap(state.conditionalRendering.isPassing ? tick : cross);
    ui->predicateInverted->setPixmap(state.conditionalRendering.isInverted ? tick : cross);

    ui->csConditionalRenderingGroup->setVisible(true);
    ui->csPredicateBuffer->setText(QFormatStr("%1 (Byte Offset %2)")
                                       .arg(ToQStr(state.conditionalRendering.bufferId))
                                       .arg(state.conditionalRendering.byteOffset));
    ui->csPredicatePassing->setPixmap(state.conditionalRendering.isPassing ? tick : cross);
    ui->csPredicateInverted->setPixmap(state.conditionalRendering.isInverted ? tick : cross);
  }

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

  if(state.currentPass.renderpass.dynamic)
  {
    QString dynamic = tr("Dynamic", "Dynamic rendering renderpass name");
    QString text = QFormatStr("Render Pass: %1").arg(dynamic);
    if(state.currentPass.renderpass.suspended)
      text += tr(" (Suspended)", "Dynamic rendering renderpass name");
    ui->renderpass->setText(text);
    ui->framebuffer->setText(tr("Framebuffer: %1").arg(dynamic));
  }
  else
  {
    QString text = QFormatStr("Render Pass: %1 (Subpass %2)")
                       .arg(ToQStr(state.currentPass.renderpass.resourceId))
                       .arg(state.currentPass.renderpass.subpass);
    if(state.currentPass.renderpass.feedbackLoop)
      text += tr(" (Feedback Loop)");
    ui->renderpass->setText(text);
    ui->framebuffer->setText(
        QFormatStr("Framebuffer: %1").arg(ToQStr(state.currentPass.framebuffer.resourceId)));
  }

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

  vs2 = ui->blends->verticalScrollBar()->value();
  ui->blends->beginUpdate();
  ui->blends->clear();
  {
    const VKPipe::Framebuffer &fb = state.currentPass.framebuffer;
    const VKPipe::RenderPass &rp = state.currentPass.renderpass;

    enum class AttType
    {
      Color,
      Resolve,
      Depth,
      DepthResolve,
      Density,
      ShadingRate
    };

    struct AttachRef
    {
      int32_t fbIdx;
      int32_t localIdx;
      AttType type;
    };

    rdcarray<AttachRef> attachs;

    // iterate the attachments in logical order, checking each index into the framebuffer

    for(int c = 0; c < rp.colorAttachments.count(); c++)
      attachs.push_back({int32_t(rp.colorAttachments[c]), c, AttType::Color});

    for(int c = 0; c < rp.resolveAttachments.count(); c++)
      attachs.push_back({int32_t(rp.resolveAttachments[c]), c, AttType::Resolve});

    attachs.push_back({rp.depthstencilAttachment, 0, AttType::Depth});
    attachs.push_back({rp.depthstencilResolveAttachment, 0, AttType::DepthResolve});
    attachs.push_back({rp.fragmentDensityAttachment, 0, AttType::Density});
    attachs.push_back({rp.shadingRateAttachment, 0, AttType::ShadingRate});

    for(const AttachRef &a : attachs)
    {
      int32_t attIdx = a.fbIdx;

      // negative index means unused
      bool usedSlot = (attIdx >= 0);

      bool filledSlot = false;
      if(usedSlot && attIdx < fb.attachments.count())
        filledSlot = fb.attachments[attIdx].resource != ResourceId();

      if(showNode(usedSlot, filledSlot))
      {
        QString slotname;

        if(a.type == AttType::Color)
        {
          slotname = QFormatStr("Color %1").arg(a.localIdx);

          if(state.fragmentShader.reflection != NULL)
          {
            const rdcarray<SigParameter> &outSig = state.fragmentShader.reflection->outputSignature;
            for(int s = 0; s < outSig.count(); s++)
            {
              if(outSig[s].regIndex == (uint32_t)a.localIdx &&
                 (outSig[s].systemValue == ShaderBuiltin::Undefined ||
                  outSig[s].systemValue == ShaderBuiltin::ColorOutput))
              {
                slotname += QFormatStr(": %1").arg(outSig[s].varName);
              }
            }
          }
        }
        else if(a.type == AttType::Resolve)
        {
          slotname = QFormatStr("Resolve %1").arg(a.localIdx);
        }
        else if(a.type == AttType::Depth)
        {
          slotname = lit("Depth/Stencil");

          if(filledSlot)
          {
            const Descriptor &p = fb.attachments[attIdx];

            slotname = lit("Depth");

            if(p.format.type == ResourceFormatType::D16S8 ||
               p.format.type == ResourceFormatType::D24S8 ||
               p.format.type == ResourceFormatType::D32S8)
              slotname = lit("Depth/Stencil");
            else if(p.format.type == ResourceFormatType::S8)
              slotname = lit("Stencil");
          }
        }
        else if(a.type == AttType::DepthResolve)
        {
          slotname = lit("Depth/Stencil Resolve");
        }
        else if(a.type == AttType::Density)
        {
          slotname = lit("Fragment Density Map");
        }
        else if(a.type == AttType::ShadingRate)
        {
          slotname = lit("Fragment Shading Rate Map");
        }

        RDTreeWidgetItem *node;

        if(filledSlot)
        {
          const Descriptor &p = fb.attachments[attIdx];

          QString format;
          QString typeName;
          QString dimensions;
          QString samples;
          bool tooltipOffsets = false;

          if(p.resource != ResourceId())
          {
            format = p.format.Name();
            typeName = tr("Unknown");
          }
          else
          {
            format = lit("-");
            typeName = lit("-");
            dimensions = lit("-");
            samples = lit("-");
          }

          TextureDescription *tex = m_Ctx.GetTexture(p.resource);
          if(tex)
          {
            dimensions += tr("%1x%2").arg(tex->width).arg(tex->height);
            if(tex->depth > 1)
              dimensions += tr("x%1").arg(tex->depth);
            if(tex->arraysize > 1)
              dimensions += tr("[%1]").arg(tex->arraysize);

            typeName = ToQStr(tex->type);
          }
          samples = getTextureRenderSamples(tex, state.currentPass.renderpass);

          if(p.swizzle.red != TextureSwizzle::Red || p.swizzle.green != TextureSwizzle::Green ||
             p.swizzle.blue != TextureSwizzle::Blue || p.swizzle.alpha != TextureSwizzle::Alpha)
          {
            format += tr(" swizzle[%1%2%3%4]")
                          .arg(ToQStr(p.swizzle.red))
                          .arg(ToQStr(p.swizzle.green))
                          .arg(ToQStr(p.swizzle.blue))
                          .arg(ToQStr(p.swizzle.alpha));
          }

          rdcpair<uint32_t, uint32_t> shadingRateTexelSize = {0, 0};

          if(a.type == AttType::Density)
          {
            if(state.currentPass.renderpass.fragmentDensityOffsets.size() > 2)
            {
              tooltipOffsets = true;
            }
            else if(state.currentPass.renderpass.fragmentDensityOffsets.size() > 0)
            {
              dimensions += tr(" : offsets");
              for(uint32_t j = 0; j < state.currentPass.renderpass.fragmentDensityOffsets.size(); j++)
              {
                const Offset &o = state.currentPass.renderpass.fragmentDensityOffsets[j];
                if(j > 0)
                  dimensions += tr(", ");

                dimensions += tr(" %1x%2").arg(o.x).arg(o.y);
              }
            }
          }
          else if(a.type == AttType::ShadingRate)
          {
            shadingRateTexelSize = state.currentPass.renderpass.shadingRateTexelSize;
          }

          QString resName = ToQStr(p.resource);

          if(shadingRateTexelSize.first > 0)
            resName +=
                tr(" (%1x%2 texels)").arg(shadingRateTexelSize.first).arg(shadingRateTexelSize.second);

          // append if colour or depth/stencil feedback is allowed
          if(a.type == AttType::Color && state.currentPass.colorFeedbackAllowed)
          {
            resName += tr(" (Feedback)");
          }
          else if(a.type == AttType::Depth && state.currentPass.depthFeedbackAllowed &&
                  state.currentPass.stencilFeedbackAllowed)
          {
            resName += tr(" (Feedback)");
          }
          else if(a.type == AttType::Depth && (state.currentPass.depthFeedbackAllowed ||
                                               state.currentPass.stencilFeedbackAllowed))
          {
            // if only one of depth or stencil is allowed, display that specifically
            if(tex->format.type == ResourceFormatType::D16S8 ||
               tex->format.type == ResourceFormatType::D24S8 ||
               tex->format.type == ResourceFormatType::D32S8)
            {
              if(state.currentPass.depthFeedbackAllowed)
                resName += tr(" (Depth Feedback)");
              else if(state.currentPass.stencilFeedbackAllowed)
                resName += tr(" (Depth Feedback)");
            }
            else if(tex->format.type == ResourceFormatType::S8 &&
                    state.currentPass.stencilFeedbackAllowed)
            {
              resName += tr(" (Feedback)");
            }
            // this case must be depth-only, since depth/stencil and stencil-only are covered above.
            else if(state.currentPass.depthFeedbackAllowed)
            {
              resName += tr(" (Feedback)");
            }
          }

          node = new RDTreeWidgetItem(
              {slotname, resName, typeName, dimensions, format, samples, QString()});

          if(tex)
            node->setTag(QVariant::fromValue(VulkanTextureTag(p.resource, p.format.compType)));

          if(p.resource == ResourceId())
            setEmptyRow(node);
          else if(!usedSlot)
            setInactiveRow(node);

          bool hasViewDetails = setViewDetails(
              node, p, tex, QString(),
              a.type == AttType::Resolve || a.type == AttType::DepthResolve, tooltipOffsets);

          if(hasViewDetails)
            node->setText(1, tr("%1 viewed by %2").arg(ToQStr(p.resource)).arg(ToQStr(p.view)));
        }
        else
        {
          // special simple case for an attachment that's not used. No framebuffer to look up so
          // just display the name and empty contents.

          node = new RDTreeWidgetItem({slotname, usedSlot ? ToQStr(ResourceId()) : tr("Unused"),
                                       QString(), QString(), QString(), QString(), QString()});

          setEmptyRow(node);
        }

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

    int i = 0;
    for(const ColorBlend &blend : state.colorBlend.blends)
    {
      bool usedSlot =
          (i < rp.colorAttachments.count() && rp.colorAttachments[i] < fb.attachments.size());

      if(showNode(usedSlot, /*filledSlot*/ true))
      {
        RDTreeWidgetItem *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),

             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(!usedSlot)
          setInactiveRow(node);

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

      i++;
    }
  }

  ui->fbAttach->clearSelection();
  ui->fbAttach->endUpdate();
  ui->fbAttach->verticalScrollBar()->setValue(vs);

  ui->blends->clearSelection();
  ui->blends->endUpdate();
  ui->blends->verticalScrollBar()->setValue(vs2);

  ui->blendFactor->setText(QFormatStr("%1, %2, %3, %4")
                               .arg(state.colorBlend.blendFactor[0], 0, 'f', 2)
                               .arg(state.colorBlend.blendFactor[1], 0, 'f', 2)
                               .arg(state.colorBlend.blendFactor[2], 0, 'f', 2)
                               .arg(state.colorBlend.blendFactor[3], 0, 'f', 2));
  if(state.colorBlend.blends.count() > 0)
    ui->logicOp->setText(state.colorBlend.blends[0].logicOperationEnabled
                             ? ToQStr(state.colorBlend.blends[0].logicOperation)
                             : lit("-"));
  else
    ui->logicOp->setText(lit("-"));

  if(state.depthStencil.depthTestEnable)
  {
    ui->depthEnabled->setPixmap(tick);
    ui->depthFunc->setText(ToQStr(state.depthStencil.depthFunction));
    ui->depthWrite->setPixmap(state.depthStencil.depthWriteEnable ? 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"));
  }

  if(state.depthStencil.depthBoundsEnable)
  {
    ui->depthBounds->setPixmap(QPixmap());
    ui->depthBounds->setText(Formatter::Format(state.depthStencil.minDepthBounds) + lit("-") +
                             Formatter::Format(state.depthStencil.maxDepthBounds));
  }
  else
  {
    ui->depthBounds->setText(QString());
    ui->depthBounds->setPixmap(cross);
  }

  ui->stencils->beginUpdate();
  ui->stencils->clear();
  if(state.depthStencil.stencilTestEnable)
  {
    ui->stencils->addTopLevelItem(new RDTreeWidgetItem({
        tr("Front"),
        ToQStr(state.depthStencil.frontFace.function),
        ToQStr(state.depthStencil.frontFace.failOperation),
        ToQStr(state.depthStencil.frontFace.depthFailOperation),
        ToQStr(state.depthStencil.frontFace.passOperation),
        QVariant(),
        QVariant(),
        QVariant(),
    }));

    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(0), 5,
                                     state.depthStencil.frontFace.writeMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(0), 6,
                                     state.depthStencil.frontFace.compareMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(0), 7,
                                     state.depthStencil.frontFace.reference);

    ui->stencils->addTopLevelItem(new RDTreeWidgetItem({
        tr("Back"),
        ToQStr(state.depthStencil.backFace.function),
        ToQStr(state.depthStencil.backFace.failOperation),
        ToQStr(state.depthStencil.backFace.depthFailOperation),
        ToQStr(state.depthStencil.backFace.passOperation),
        QVariant(),
        QVariant(),
        QVariant(),
    }));

    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 5,
                                     state.depthStencil.backFace.writeMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 6,
                                     state.depthStencil.backFace.compareMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 7,
                                     state.depthStencil.backFace.reference);
  }
  else
  {
    ui->stencils->addTopLevelItem(new RDTreeWidgetItem(
        {tr("Front"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-")}));
    ui->stencils->addTopLevelItem(new RDTreeWidgetItem(
        {tr("Back"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-"), lit("-")}));
  }
  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."));
  }
}