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."));
}
}