void GLPipelineStateViewer::setState()

in qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp [1335:2584]


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

  const GLPipe::State &state = *m_Ctx.CurGLPipelineState();
  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] = {};

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

  int vs = 0;

  vs = ui->viAttrs->verticalScrollBar()->value();
  ui->viAttrs->beginUpdate();
  ui->viAttrs->clear();
  {
    int i = 0;
    for(const GLPipe::VertexAttribute &a : state.vertexInput.attributes)
    {
      bool filledSlot = true;
      bool usedSlot = false;

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

      uint32_t compCount = 4;
      CompType compType = CompType::Float;

      if(state.vertexShader.shaderResourceId != ResourceId())
      {
        int attrib = a.boundShaderInput;

        if(attrib >= 0 && attrib < state.vertexShader.reflection->inputSignature.count())
        {
          name = state.vertexShader.reflection->inputSignature[attrib].varName;
          compCount = state.vertexShader.reflection->inputSignature[attrib].compCount;
          compType = VarTypeCompType(state.vertexShader.reflection->inputSignature[attrib].varType);
          usedSlot = true;
        }
      }

      if(showNode(usedSlot, filledSlot))
      {
        QString format = QString(a.format.Name());

        if(!a.enabled)
          format = tr("Generic=") + MakeGenericValueString(compCount, compType, a);
        else if(a.floatCast)
          format += tr(" Cast to float");

        RDTreeWidgetItem *node = new RDTreeWidgetItem({
            i,
            a.enabled ? tr("Enabled") : tr("Disabled"),
            name,
            format,
            a.vertexBufferSlot,
            Formatter::HumanFormat(a.byteOffset, Formatter::OffsetSize),
            QString(),
        });

        node->setTag(i);

        if(a.enabled)
          usedBindings[a.vertexBufferSlot] = true;

        if(!usedSlot)
          setInactiveRow(node);

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

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

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

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

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

  if(ibufferUsed)
  {
    ui->primRestart->setVisible(true);
    if(state.vertexInput.primitiveRestart)
      ui->primRestart->setText(
          tr("Restart Idx: 0x%1").arg(Formatter::Format(state.vertexInput.restartIndex, true)));
    else
      ui->primRestart->setText(tr("Restart Idx: Disabled"));
  }
  else
  {
    ui->primRestart->setVisible(false);
  }

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

  ui->vaoLabel->setText(ToQStr(state.vertexInput.vertexArrayObject));

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

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

      if(!ibufferUsed)
        length = 0;

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

      if(buf)
        length = buf->length;

      RDTreeWidgetItem *node = new RDTreeWidgetItem({
          tr("Element"),
          state.vertexInput.indexBuffer,
          Formatter::HumanFormat(state.vertexInput.indexByteStride, Formatter::OffsetSize),
          0,
          0,
          Formatter::HumanFormat(length, Formatter::OffsetSize),
          QString(),
      });

      QString iformat;
      if(action)
      {
        if(state.vertexInput.indexByteStride == 1)
          iformat = lit("ubyte");
        else if(state.vertexInput.indexByteStride == 2)
          iformat = lit("ushort");
        else if(state.vertexInput.indexByteStride == 4)
          iformat = lit("uint");

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

      node->setTag(QVariant::fromValue(GLVBIBTag(
          state.vertexInput.indexBuffer,
          action ? action->indexOffset * state.vertexInput.indexByteStride : 0, iformat)));

      if(!ibufferUsed)
        setInactiveRow(node);

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

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

      QString iformat;
      if(action)
      {
        if(state.vertexInput.indexByteStride == 1)
          iformat = lit("ubyte");
        else if(state.vertexInput.indexByteStride == 2)
          iformat = lit("ushort");
        else if(state.vertexInput.indexByteStride == 4)
          iformat = lit("uint");

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

      node->setTag(QVariant::fromValue(GLVBIBTag(
          state.vertexInput.indexBuffer,
          action ? action->indexOffset * state.vertexInput.indexByteStride : 0, iformat)));

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

      if(!ibufferUsed)
        setInactiveRow(node);

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

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

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

    if(showNode(usedSlot, filledSlot))
    {
      uint64_t length = 0;
      uint64_t offset = v.byteOffset;

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

      RDTreeWidgetItem *node = new RDTreeWidgetItem({
          i,
          v.resourceId,
          Formatter::HumanFormat(v.byteStride, Formatter::OffsetSize),
          Formatter::HumanFormat(offset, Formatter::OffsetSize),
          v.instanceDivisor,
          Formatter::HumanFormat(length, Formatter::OffsetSize),
          QString(),
      });

      node->setTag(QVariant::fromValue(
          GLVBIBTag(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->viBuffers->addTopLevelItem(node);
    }
    else
    {
      m_VBNodes.push_back(NULL);
    }
  }
  ui->viBuffers->clearSelection();
  ui->viBuffers->endUpdate();
  ui->viBuffers->verticalScrollBar()->setValue(vs);

  {
    ScopedTreeUpdater restorers[] = {
        // VS
        ui->vsTextures,
        ui->vsSamplers,
        ui->vsUBOs,
        ui->vsSubroutines,
        ui->vsReadWrite,
        // GS
        ui->gsTextures,
        ui->gsSamplers,
        ui->gsUBOs,
        ui->gsSubroutines,
        ui->gsReadWrite,
        // tcs
        ui->tcsTextures,
        ui->tcsSamplers,
        ui->tcsUBOs,
        ui->tcsSubroutines,
        ui->tcsReadWrite,
        // tes
        ui->tesTextures,
        ui->tesSamplers,
        ui->tesUBOs,
        ui->tesSubroutines,
        ui->tesReadWrite,
        // fs
        ui->fsTextures,
        ui->fsSamplers,
        ui->fsUBOs,
        ui->fsSubroutines,
        ui->fsReadWrite,
        // CS
        ui->csTextures,
        ui->csSamplers,
        ui->csUBOs,
        ui->csSubroutines,
        ui->csReadWrite,
    };

    const ShaderReflection *shaderRefls[NumShaderStages];

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

    RDTreeWidgetItem textures[6];
    RDTreeWidgetItem samplers[6];
    RDTreeWidgetItem readwrites[6];

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

    for(uint32_t i = 0; i < m_Locations.size(); i++)
    {
      // locations are not stage specific
      uint32_t reg = m_Locations[i].fixedBindNumber;

      bool usedSlot = false;

      // look for any accesses that use this descriptor, we generally expect only one per stage
      // so if multiple exist then we'll pick the first one (somewhat arbitrarily). We could add
      // duplicates here if we wanted now that we have the information
      DescriptorAccess stageAccesses[NumShaderStages];
      for(const DescriptorAccess &access : m_Ctx.CurPipelineState().GetDescriptorAccess())
      {
        if(access.byteOffset == i * state.descriptorByteSize)
        {
          if(stageAccesses[(uint32_t)access.stage].type == DescriptorType::Unknown ||
             stageAccesses[(uint32_t)access.stage].staticallyUnused)
            stageAccesses[(uint32_t)access.stage] = access;
        }
      }

      const GLPipe::TextureCompleteness *texCompleteness = NULL;
      for(const GLPipe::TextureCompleteness &comp : state.textureCompleteness)
      {
        // GL descriptors are laid out linearly so we can identify the offset of the current
        // descriptor without having to store it
        if(comp.descriptorByteOffset == i * state.descriptorByteSize)
        {
          texCompleteness = &comp;
          break;
        }
      }

      if(m_Locations[i].category == DescriptorCategory::ConstantBlock)
      {
        for(ShaderStage stage : values<ShaderStage>())
        {
          if((uint32_t)stage >= ARRAY_COUNT(ubos))
            continue;

          const ShaderReflection *refl = shaderRefls[(uint32_t)stage];
          const ConstantBlock *shaderBind = NULL;

          const DescriptorAccess &access = stageAccesses[(uint32_t)stage];
          usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown;

          if(refl && access.type != DescriptorType::Unknown)
            shaderBind = &refl->constantBlocks[access.index];

          addUBORow(m_Descriptors[i], reg, access.index, shaderBind, usedSlot, ubos[(uint32_t)stage]);
        }
      }
      else if(m_Locations[i].category == DescriptorCategory::ReadOnlyResource)
      {
        // look for any shaders that use this binding
        for(ShaderStage stage : values<ShaderStage>())
        {
          if((uint32_t)stage >= ARRAY_COUNT(textures))
            continue;

          const ShaderReflection *refl = shaderRefls[(uint32_t)stage];

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

          const DescriptorAccess &access = stageAccesses[(uint32_t)stage];
          usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown;

          if(refl && access.type != DescriptorType::Unknown)
          {
            shaderTex = &refl->readOnlyResources[access.index];
            shaderSamp = &refl->samplers[access.index];
          }

          addImageSamplerRow(m_Descriptors[i], m_SamplerDescriptors[i], reg, shaderTex, shaderSamp,
                             usedSlot, texCompleteness, &textures[(uint32_t)stage],
                             &samplers[(uint32_t)stage]);
        }
      }
      else if(m_Locations[i].category == DescriptorCategory::ReadWriteResource)
      {
        // look for any shaders that use this binding
        for(ShaderStage stage : values<ShaderStage>())
        {
          if((uint32_t)stage >= ARRAY_COUNT(readwrites))
            continue;

          const ShaderReflection *refl = shaderRefls[(uint32_t)stage];

          const ShaderResource *shaderBind = NULL;

          const DescriptorAccess &access = stageAccesses[(uint32_t)stage];
          usedSlot = !access.staticallyUnused && access.type != DescriptorType::Unknown;

          if(refl && access.type != DescriptorType::Unknown)
            shaderBind = &refl->readWriteResources[access.index];

          addReadWriteRow(m_Descriptors[i], reg, access.index, shaderBind, usedSlot,
                          texCompleteness, &readwrites[(uint32_t)stage]);
        }
      }
    }

    RDTreeWidget *textureWidgets[] = {
        ui->vsTextures, ui->tcsTextures, ui->tesTextures,
        ui->gsTextures, ui->fsTextures,  ui->csTextures,
    };

    RDTreeWidget *samplerWidgets[] = {
        ui->vsSamplers, ui->tcsSamplers, ui->tesSamplers,
        ui->gsSamplers, ui->fsSamplers,  ui->csSamplers,
    };

    RDTreeWidget *readwriteWidgets[] = {
        ui->vsReadWrite, ui->tcsReadWrite, ui->tesReadWrite,
        ui->gsReadWrite, ui->fsReadWrite,  ui->csReadWrite,
    };

    // sort all entries by register, so that e.g. we don't display 2D textures before 3D textures
    // even if their locations all come together.
    for(size_t i = 0; i < ARRAY_COUNT(textures); i++)
    {
      rdcarray<RDTreeWidgetItem *> items;
      while(textures[i].childCount())
        items.push_back(textures[i].takeChild(textures[i].childCount() - 1));

      std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) {
        GLReadOnlyTag a_tag = a->tag().value<GLReadOnlyTag>();
        GLReadOnlyTag b_tag = b->tag().value<GLReadOnlyTag>();

        return a_tag.reg < b_tag.reg;
      });

      for(RDTreeWidgetItem *item : items)
        textureWidgets[i]->addTopLevelItem(item);
    }

    for(size_t i = 0; i < ARRAY_COUNT(samplers); i++)
    {
      rdcarray<RDTreeWidgetItem *> items;
      while(samplers[i].childCount())
        items.push_back(samplers[i].takeChild(samplers[i].childCount() - 1));

      std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) {
        GLReadOnlyTag a_tag = a->tag().value<GLReadOnlyTag>();
        GLReadOnlyTag b_tag = b->tag().value<GLReadOnlyTag>();

        return a_tag.reg < b_tag.reg;
      });

      for(RDTreeWidgetItem *item : items)
        samplerWidgets[i]->addTopLevelItem(item);
    }

    for(size_t i = 0; i < ARRAY_COUNT(readwrites); i++)
    {
      rdcarray<RDTreeWidgetItem *> items;
      while(readwrites[i].childCount())
        items.push_back(readwrites[i].takeChild(readwrites[i].childCount() - 1));

      std::sort(items.begin(), items.end(), [](RDTreeWidgetItem *a, RDTreeWidgetItem *b) {
        GLReadWriteTag a_tag = a->tag().value<GLReadWriteTag>();
        GLReadWriteTag b_tag = b->tag().value<GLReadWriteTag>();

        // sort by read-write type first (atomics, then SSBOs, then images)
        if(a_tag.readWriteType != b_tag.readWriteType)
          return a_tag.readWriteType < b_tag.readWriteType;

        // then by register
        return a_tag.reg < b_tag.reg;
      });

      for(RDTreeWidgetItem *item : items)
        readwriteWidgets[i]->addTopLevelItem(item);
    }

    // UBOs don't have to be sorted because there's only one type there, the locations are already
    // in order

    setShaderState(state.vertexShader, ui->vsShader, ui->vsSubroutines);
    setShaderState(state.geometryShader, ui->gsShader, ui->gsSubroutines);
    setShaderState(state.tessControlShader, ui->tcsShader, ui->tcsSubroutines);
    setShaderState(state.tessEvalShader, ui->tesShader, ui->tesSubroutines);
    setShaderState(state.fragmentShader, ui->fsShader, ui->fsSubroutines);
    setShaderState(state.computeShader, ui->csShader, ui->csSubroutines);

    ui->vsReadWrite->parentWidget()->setVisible(ui->vsReadWrite->topLevelItemCount() > 0 &&
                                                shaderRefls[0] &&
                                                shaderRefls[0]->readWriteResources.count() > 0);
    ui->tcsReadWrite->parentWidget()->setVisible(ui->tcsReadWrite->topLevelItemCount() > 0 &&
                                                 shaderRefls[1] &&
                                                 shaderRefls[1]->readWriteResources.count() > 0);
    ui->tesReadWrite->parentWidget()->setVisible(ui->tesReadWrite->topLevelItemCount() > 0 &&
                                                 shaderRefls[2] &&
                                                 shaderRefls[2]->readWriteResources.count() > 0);
    ui->gsReadWrite->parentWidget()->setVisible(ui->gsReadWrite->topLevelItemCount() > 0 &&
                                                shaderRefls[3] &&
                                                shaderRefls[3]->readWriteResources.count() > 0);
    ui->fsReadWrite->parentWidget()->setVisible(ui->fsReadWrite->topLevelItemCount() > 0 &&
                                                shaderRefls[4] &&
                                                shaderRefls[4]->readWriteResources.count() > 0);
    ui->csReadWrite->parentWidget()->setVisible(ui->csReadWrite->topLevelItemCount() > 0 &&
                                                shaderRefls[5] &&
                                                shaderRefls[5]->readWriteResources.count() > 0);
  }

  QToolButton *shaderButtons[] = {
      ui->vsShaderViewButton, ui->tcsShaderViewButton, ui->tesShaderViewButton,
      ui->gsShaderViewButton, ui->fsShaderViewButton,  ui->csShaderViewButton,
      ui->vsShaderEditButton, ui->tcsShaderEditButton, ui->tesShaderEditButton,
      ui->gsShaderEditButton, ui->fsShaderEditButton,  ui->csShaderEditButton,
      ui->vsShaderSaveButton, ui->tcsShaderSaveButton, ui->tesShaderSaveButton,
      ui->gsShaderSaveButton, ui->fsShaderSaveButton,  ui->csShaderSaveButton,
  };

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

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

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

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

  vs = ui->xfbBuffers->verticalScrollBar()->value();
  ui->xfbBuffers->beginUpdate();
  ui->xfbBuffers->clear();
  ui->xfbObj->setText(ToQStr(state.transformFeedback.feedbackResourceId));
  if(state.transformFeedback.active)
  {
    ui->xfbPaused->setPixmap(state.transformFeedback.paused ? tick : cross);
    for(int i = 0; i < (int)ARRAY_COUNT(state.transformFeedback.bufferResourceId); i++)
    {
      bool filledSlot = (state.transformFeedback.bufferResourceId[i] != ResourceId());
      bool usedSlot = (filledSlot);

      if(showNode(usedSlot, filledSlot))
      {
        qulonglong length = state.transformFeedback.byteSize[i];

        BufferDescription *buf = m_Ctx.GetBuffer(state.transformFeedback.bufferResourceId[i]);

        if(buf)
          length = buf->length;

        RDTreeWidgetItem *node = new RDTreeWidgetItem({
            i,
            state.transformFeedback.bufferResourceId[i],
            Formatter::HumanFormat(length, Formatter::OffsetSize),
            Formatter::HumanFormat(state.transformFeedback.byteOffset[i], Formatter::OffsetSize),
            QString(),
        });

        node->setTag(QVariant::fromValue(state.transformFeedback.bufferResourceId[i]));

        if(!filledSlot)
          setEmptyRow(node);

        if(!usedSlot)
          setInactiveRow(node);

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

  ui->xfbGroup->setVisible(state.transformFeedback.active);

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

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

  {
    // accumulate identical viewports to save on visual repetition
    int prev = 0;
    for(int i = 0; i < state.rasterizer.viewports.count(); i++)
    {
      const Viewport &v1 = state.rasterizer.viewports[prev];
      const Viewport &v2 = state.rasterizer.viewports[i];

      if(v1.width != v2.width || v1.height != v2.height || v1.x != v2.x || v1.y != v2.y ||
         v1.minDepth != v2.minDepth || v1.maxDepth != v2.maxDepth)
      {
        if(v1.width != v1.height || v1.width != 0 || v1.height != 0 || v1.minDepth != v1.maxDepth ||
           ui->showEmpty->isChecked())
        {
          QString indexstring;
          if(prev < i - 1)
            indexstring = QFormatStr("%1-%2").arg(prev).arg(i - 1);
          else
            indexstring = QString::number(prev);

          RDTreeWidgetItem *node = new RDTreeWidgetItem(
              {indexstring, v1.x, v1.y, v1.width, v1.height, v1.minDepth, v1.maxDepth});

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

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

        prev = i;
      }
    }

    // handle the last batch (the loop above leaves the last batch un-added)
    if(prev < state.rasterizer.viewports.count())
    {
      const Viewport &v1 = state.rasterizer.viewports[prev];

      // must display at least one viewport - otherwise if they are
      // all empty we get an empty list - we want a nice obvious
      // 'invalid viewport' entry. So check if last is 0

      if(v1.width != v1.height || v1.width != 0 || v1.height != 0 || v1.minDepth != v1.maxDepth ||
         ui->showEmpty->isChecked() || prev == 0)
      {
        QString indexstring;
        if(prev < state.rasterizer.viewports.count() - 1)
          indexstring = QFormatStr("%1-%2").arg(prev).arg(state.rasterizer.viewports.count() - 1);
        else
          indexstring = QString::number(prev);

        RDTreeWidgetItem *node = new RDTreeWidgetItem(
            {indexstring, v1.x, v1.y, v1.width, v1.height, v1.minDepth, v1.maxDepth});

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

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

  bool anyScissorEnable = false;

  vs = ui->scissors->verticalScrollBar()->value();
  ui->scissors->beginUpdate();
  ui->scissors->clear();
  {
    // accumulate identical scissors to save on visual repetition
    int prev = 0;
    for(int i = 0; i < state.rasterizer.scissors.count(); i++)
    {
      const Scissor &s1 = state.rasterizer.scissors[prev];
      const Scissor &s2 = state.rasterizer.scissors[i];

      if(s1.width != s2.width || s1.height != s2.height || s1.x != s2.x || s1.y != s2.y ||
         s1.enabled != s2.enabled)
      {
        if(s1.enabled || ui->showEmpty->isChecked())
        {
          QString indexstring;
          if(prev < i - 1)
            indexstring = QFormatStr("%1-%2").arg(prev).arg(i - 1);
          else
            indexstring = QString::number(prev);

          RDTreeWidgetItem *node = new RDTreeWidgetItem(
              {indexstring, s1.x, s1.y, s1.width, s1.height, s1.enabled ? tr("True") : tr("False")});

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

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

          anyScissorEnable = anyScissorEnable || s1.enabled;

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

        prev = i;
      }
    }

    // handle the last batch (the loop above leaves the last batch un-added)
    if(prev < state.rasterizer.scissors.count())
    {
      const Scissor &s1 = state.rasterizer.scissors[prev];

      if(s1.enabled || ui->showEmpty->isChecked())
      {
        QString indexstring;
        if(prev < state.rasterizer.scissors.count() - 1)
          indexstring = QFormatStr("%1-%2").arg(prev).arg(state.rasterizer.scissors.count() - 1);
        else
          indexstring = QString::number(prev);

        RDTreeWidgetItem *node = new RDTreeWidgetItem(
            {indexstring, s1.x, s1.y, s1.width, s1.height, s1.enabled ? tr("True") : tr("False")});

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

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

        anyScissorEnable = anyScissorEnable || s1.enabled;

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

  ui->fillMode->setText(ToQStr(state.rasterizer.state.fillMode));
  ui->cullMode->setText(ToQStr(state.rasterizer.state.cullMode));

  if(state.rasterizer.state.frontCCW)
  {
    if(state.vertexProcessing.clipOriginLowerLeft)
    {
      ui->frontFace->setText(tr("CCW"));
      ui->frontFace->setToolTip(QString());
    }
    else
    {
      ui->frontFace->setText(tr("CW (clip origin flipped)"));
      ui->frontFace->setToolTip(
          tr("The GL state specifies that front faces have CCW winding,\n"
             "but this is inverted by the upper-left clip origin."));
    }
  }
  else
  {
    if(state.vertexProcessing.clipOriginLowerLeft)
    {
      ui->frontFace->setText(tr("CW"));
      ui->frontFace->setToolTip(QString());
    }
    else
    {
      ui->frontFace->setText(tr("CCW (clip origin flipped)"));
      ui->frontFace->setToolTip(
          tr("The GL state specifies that front faces have CW winding,\n"
             "but this is inverted by the upper-left clip origin."));
    }
  }

  ui->scissorEnabled->setPixmap(anyScissorEnable ? tick : cross);
  ui->provoking->setText(state.vertexInput.provokingVertexLast ? tr("Last") : tr("First"));

  ui->rasterizerDiscard->setPixmap(state.vertexProcessing.discard ? tick : cross);

  if(state.rasterizer.state.programmablePointSize)
    ui->pointSize->setText(tr("Program", "ProgrammablePointSize"));
  else
    ui->pointSize->setText(Formatter::Format(state.rasterizer.state.pointSize));
  ui->lineWidth->setText(Formatter::Format(state.rasterizer.state.lineWidth));

  QString clipSetup;
  if(state.vertexProcessing.clipOriginLowerLeft)
    clipSetup += tr("0,0 Lower Left");
  else
    clipSetup += tr("0,0 Upper Left");
  clipSetup += lit(", ");
  if(state.vertexProcessing.clipNegativeOneToOne)
    clipSetup += lit("Z= -1 to 1");
  else
    clipSetup += lit("Z= 0 to 1");

  ui->clipSetup->setText(clipSetup);

  QString clipDistances;

  int numDist = 0;
  for(int i = 0; i < (int)ARRAY_COUNT(state.vertexProcessing.clipPlanes); i++)
  {
    if(state.vertexProcessing.clipPlanes[i])
    {
      if(numDist > 0)
        clipDistances += lit(", ");
      clipDistances += QString::number(i);

      numDist++;
    }
  }

  if(numDist == 0)
    clipDistances = lit("-");
  else
    clipDistances += tr(" enabled");

  ui->clipDistance->setText(clipDistances);

  ui->depthClamp->setPixmap(state.rasterizer.state.depthClamp ? tick : cross);
  ui->depthBias->setText(Formatter::Format(state.rasterizer.state.depthBias));
  ui->slopeScaledBias->setText(Formatter::Format(state.rasterizer.state.slopeScaledDepthBias));

  if(state.rasterizer.state.offsetClamp == 0.0f || qIsNaN(state.rasterizer.state.offsetClamp))
  {
    ui->offsetClamp->setText(QString());
    ui->offsetClamp->setPixmap(cross);
  }
  else
  {
    ui->offsetClamp->setPixmap(QPixmap());
    ui->offsetClamp->setText(Formatter::Format(state.rasterizer.state.offsetClamp));
  }

  ui->multisample->setPixmap(state.rasterizer.state.multisampleEnable ? tick : cross);
  ui->sampleShading->setPixmap(state.rasterizer.state.sampleShading ? tick : cross);
  ui->minSampleShading->setText(Formatter::Format(state.rasterizer.state.minSampleShadingRate));
  ui->alphaToCoverage->setPixmap(state.rasterizer.state.alphaToCoverage ? tick : cross);
  ui->alphaToOne->setPixmap(state.rasterizer.state.alphaToOne ? tick : cross);
  if(state.rasterizer.state.sampleCoverage)
  {
    QString sampleCoverage = Formatter::Format(state.rasterizer.state.sampleCoverageValue);
    if(state.rasterizer.state.sampleCoverageInvert)
      sampleCoverage += tr(" inverted");
    ui->sampleCoverage->setPixmap(QPixmap());
    ui->sampleCoverage->setText(sampleCoverage);
  }
  else
  {
    ui->sampleCoverage->setText(QString());
    ui->sampleCoverage->setPixmap(cross);
  }

  if(state.rasterizer.state.sampleMask)
  {
    ui->sampleMask->setPixmap(QPixmap());
    ui->sampleMask->setText(Formatter::Format(state.rasterizer.state.sampleMaskValue, true));
  }
  else
  {
    ui->sampleMask->setText(QString());
    ui->sampleMask->setPixmap(cross);
  }

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

  bool targets[32] = {};

  ui->drawFBO->setText(QFormatStr("Draw FBO: %1").arg(ToQStr(state.framebuffer.drawFBO.resourceId)));
  ui->readFBO->setText(QFormatStr("Read FBO: %1").arg(ToQStr(state.framebuffer.readFBO.resourceId)));

  vs = ui->framebuffer->verticalScrollBar()->value();
  ui->framebuffer->beginUpdate();
  ui->framebuffer->clear();
  {
    int i = 0;
    for(int db : state.framebuffer.drawFBO.drawBuffers)
    {
      ResourceId p;
      const Descriptor *r = NULL;

      if(db >= 0 && db < state.framebuffer.drawFBO.colorAttachments.count())
      {
        p = state.framebuffer.drawFBO.colorAttachments[db].resource;
        r = &state.framebuffer.drawFBO.colorAttachments[db];
      }

      bool filledSlot = (p != ResourceId());
      bool usedSlot = db >= 0;

      if(showNode(usedSlot, filledSlot))
      {
        uint32_t w = 1, h = 1, d = 1;
        uint32_t a = 1;
        QString format = tr("Unknown");
        QString typeName = tr("Unknown");

        if(p == ResourceId())
        {
          format = lit("-");
          typeName = lit("-");
          w = h = d = a = 0;
        }

        TextureDescription *tex = m_Ctx.GetTexture(p);
        if(tex)
        {
          w = tex->width;
          h = tex->height;
          d = tex->depth;
          a = tex->arraysize;
          format = tex->format.Name();
          typeName = ToQStr(tex->type);

          if(tex->format.SRGBCorrected() && !state.framebuffer.framebufferSRGB)
            format += lit(" (GL_FRAMEBUFFER_SRGB = 0)");
        }

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

        QString slotname = QString::number(i);

        if(state.fragmentShader.reflection)
        {
          for(int s = 0; s < state.fragmentShader.reflection->outputSignature.count(); s++)
          {
            if(state.fragmentShader.reflection->outputSignature[s].regIndex == (uint32_t)db &&
               (state.fragmentShader.reflection->outputSignature[s].systemValue ==
                    ShaderBuiltin::Undefined ||
                state.fragmentShader.reflection->outputSignature[s].systemValue ==
                    ShaderBuiltin::ColorOutput))
            {
              slotname +=
                  QFormatStr(": %1").arg(state.fragmentShader.reflection->outputSignature[s].varName);
            }
          }
        }

        RDTreeWidgetItem *node =
            new RDTreeWidgetItem({i, p, typeName, w, h, d, a, format, QString()});

        if(tex)
        {
          if(r)
            setViewDetails(node, tex, r->firstMip, 1, r->firstSlice, r->numSlices);
          node->setTag(QVariant::fromValue(p));
        }

        if(p == ResourceId())
        {
          setEmptyRow(node);
        }
        else
        {
          targets[i] = true;
        }

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

      i++;
    }

    ResourceId dsObjects[] = {
        state.framebuffer.drawFBO.depthAttachment.resource,
        state.framebuffer.drawFBO.stencilAttachment.resource,
    };

    uint32_t dsMips[] = {
        state.framebuffer.drawFBO.depthAttachment.firstMip,
        state.framebuffer.drawFBO.stencilAttachment.firstMip,
    };

    uint32_t dsSlice[] = {
        state.framebuffer.drawFBO.depthAttachment.firstSlice,
        state.framebuffer.drawFBO.stencilAttachment.firstSlice,
    };

    uint32_t dsNumSlices[] = {
        state.framebuffer.drawFBO.depthAttachment.numSlices,
        state.framebuffer.drawFBO.stencilAttachment.numSlices,
    };

    for(int dsIdx = 0; dsIdx < 2; dsIdx++)
    {
      ResourceId ds = dsObjects[dsIdx];
      uint32_t mip = dsMips[dsIdx];
      uint32_t slice = dsSlice[dsIdx];
      uint32_t numSlices = dsNumSlices[dsIdx];

      bool filledSlot = (ds != ResourceId());
      bool usedSlot = filledSlot;
      if(showNode(usedSlot, filledSlot))
      {
        uint32_t w = 1, h = 1, d = 1;
        uint32_t a = 1;
        QString format = tr("Unknown");
        QString typeName = tr("Unknown");

        if(ds == ResourceId())
        {
          format = lit("-");
          typeName = lit("-");
          w = h = d = a = 0;
        }

        TextureDescription *tex = m_Ctx.GetTexture(ds);
        if(tex)
        {
          w = tex->width;
          h = tex->height;
          d = tex->depth;
          a = tex->arraysize;
          format = tex->format.Name();
          typeName = ToQStr(tex->type);
        }

        QString slot = tr("Depth Only");
        if(dsIdx == 1)
          slot = tr("Stencil Only");

        bool depthstencil = false;

        if(state.framebuffer.drawFBO.depthAttachment.resource ==
               state.framebuffer.drawFBO.stencilAttachment.resource &&
           state.framebuffer.drawFBO.depthAttachment.resource != ResourceId())
        {
          depthstencil = true;
          slot = tr("Depth-Stencil");
        }

        RDTreeWidgetItem *node =
            new RDTreeWidgetItem({slot, ds, typeName, w, h, d, a, format, QString()});

        if(tex)
        {
          setViewDetails(node, tex, mip, 1, slice, numSlices);
          node->setTag(QVariant::fromValue(ds));
        }

        if(ds == ResourceId())
          setEmptyRow(node);

        ui->framebuffer->addTopLevelItem(node);

        // if we added a combined depth-stencil row, break now
        if(depthstencil)
          break;
      }
    }
  }

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

  vs = ui->blends->verticalScrollBar()->value();
  ui->blends->beginUpdate();
  ui->blends->clear();
  {
    bool logic = state.framebuffer.blendState.blends[0].logicOperationEnabled &&
                 state.framebuffer.blendState.blends[0].logicOperation != LogicOperation::NoOp;

    int i = 0;
    for(const ColorBlend &blend : state.framebuffer.blendState.blends)
    {
      bool filledSlot = (blend.enabled || targets[i]);
      bool usedSlot = (targets[i]);

      // if logic operation is enabled, blending is disabled
      if(logic)
        filledSlot = (i == 0);

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

        if(i == 0 && logic)
        {
          node = new RDTreeWidgetItem({i, tr("True"),

                                       lit("-"), lit("-"), ToQStr(blend.logicOperation),

                                       lit("-"), lit("-"), lit("-"),

                                       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"))});
        }
        else
        {
          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(!filledSlot)
          setEmptyRow(node);

        if(!usedSlot)
          setInactiveRow(node);

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

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

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

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

  if(state.depthState.depthBounds)
  {
    ui->depthBounds->setPixmap(QPixmap());
    ui->depthBounds->setText(Formatter::Format(state.depthState.nearBound) + lit("-") +
                             Formatter::Format(state.depthState.farBound));
  }
  else
  {
    ui->depthBounds->setText(QString());
    ui->depthBounds->setPixmap(cross);
  }

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

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

    ui->stencils->addTopLevelItem(new RDTreeWidgetItem(
        {tr("Back"), ToQStr(state.stencilState.backFace.function),
         ToQStr(state.stencilState.backFace.failOperation),
         ToQStr(state.stencilState.backFace.depthFailOperation),
         ToQStr(state.stencilState.backFace.passOperation),
         Formatter::Format((uint8_t)state.stencilState.backFace.writeMask, true),
         Formatter::Format((uint8_t)state.stencilState.backFace.compareMask, true),
         Formatter::Format((uint8_t)state.stencilState.backFace.reference, true)}));

    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 5,
                                     state.stencilState.backFace.writeMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 6,
                                     state.stencilState.backFace.compareMask);
    m_Common.SetStencilTreeItemValue(ui->stencils->topLevelItem(1), 7,
                                     state.stencilState.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();

  // 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 raster = true;

    if(state.vertexProcessing.discard)
    {
      raster = false;
    }

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

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