diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index 31acffb2e..a7c491b14 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -515,6 +515,7 @@ struct BufferConfiguration rdcarray taskSizes; rdcarray meshletVertexPrefixCounts; + uint32_t taskOrMeshletOffset = 0; uint64_t perPrimitiveOffset = 0; uint32_t perPrimitiveStride = 0; Topology topology = Topology::TriangleList; @@ -562,6 +563,7 @@ struct BufferConfiguration baseVertex = o.baseVertex; meshletVertexPrefixCounts = o.meshletVertexPrefixCounts; taskSizes = o.taskSizes; + taskOrMeshletOffset = o.taskOrMeshletOffset; perPrimitiveOffset = o.perPrimitiveOffset; perPrimitiveStride = o.perPrimitiveStride; topology = o.topology; @@ -1185,7 +1187,9 @@ public: size_t meshletIdx = it - config.meshletVertexPrefixCounts.begin(); - return QFormatStr("%1[%2]").arg(meshletIdx).arg(row + config.pagingOffset - *it); + return QFormatStr("%1[%2]") + .arg(meshletIdx + config.taskOrMeshletOffset) + .arg(row + config.pagingOffset - *it); } else { @@ -1950,9 +1954,6 @@ static void RT_FetchMeshPipeData(IReplayController *r, ICaptureContext &ctx, Pop data->out1Config.statusString = data->postOut1.status; - data->out2Config.numRows = data->postOut2.numIndices; - data->out2Config.unclampedNumRows = 0; - if(data->out2Config.indices) data->out2Config.indices->deref(); if(data->out2Config.displayIndices) @@ -1966,6 +1967,9 @@ static void RT_FetchMeshPipeData(IReplayController *r, ICaptureContext &ctx, Pop count += meshletSize.numIndices; } + data->out2Config.numRows = numIndices; + data->out2Config.unclampedNumRows = 0; + data->out2Config.topology = data->postOut2.topology; data->out2Config.perPrimitiveOffset = data->postOut2.perPrimitiveOffset; data->out2Config.perPrimitiveStride = data->postOut2.perPrimitiveStride; @@ -2431,6 +2435,15 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) m_DebugVert = new QAction(tr("&Debug this Vertex"), this); m_DebugVert->setIcon(Icons::wrench()); + m_FilterMesh = new QAction(tr("&Filter to this Meshlet"), this); + m_FilterMesh->setIcon(Icons::filter()); + + m_RemoveFilter = new QAction(tr("&Remove Filter"), this); + m_RemoveFilter->setIcon(Icons::arrow_undo()); + + m_GotoTask = new QAction(tr("&Go to task"), this); + m_GotoTask->setIcon(Icons::arrow_join()); + ui->exportDrop->setMenu(m_ExportMenu); QObject::connect(m_ExportMenu, &QMenu::aboutToShow, this, &BufferViewer::updateExportActionNames); @@ -2440,6 +2453,39 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) QObject::connect(m_ExportBytes, &QAction::triggered, [this] { exportData(BufferExport(BufferExport::RawBytes)); }); QObject::connect(m_DebugVert, &QAction::triggered, this, &BufferViewer::debugVertex); + QObject::connect(m_RemoveFilter, &QAction::triggered, + [this]() { SetMeshFilter(MeshFilter::None); }); + QObject::connect(m_FilterMesh, &QAction::triggered, [this]() { + QModelIndex idx = m_CurView->selectionModel()->currentIndex(); + + if(!idx.isValid()) + return; + + uint32_t taskIndex = 0, meshletIndex = 0; + GetIndicesForMeshRow((uint32_t)idx.row(), taskIndex, meshletIndex); + + SetMeshFilter(MeshFilter::Mesh, taskIndex, meshletIndex); + }); + QObject::connect(m_GotoTask, &QAction::triggered, [this]() { + // if there's a filter then by definition only one task is visible, just scroll to it + if(m_CurMeshFilter != MeshFilter::None) + { + ShowMeshData(MeshDataStage::TaskOut); + ScrollToRow(0, MeshDataStage::TaskOut); + return; + } + + QModelIndex idx = m_CurView->selectionModel()->currentIndex(); + + if(!idx.isValid()) + return; + + uint32_t taskIndex = 0, meshletIndex = 0; + GetIndicesForMeshRow((uint32_t)idx.row(), taskIndex, meshletIndex); + + ShowMeshData(MeshDataStage::TaskOut); + ScrollToRow((int)taskIndex, MeshDataStage::TaskOut); + }); QObject::connect(ui->exportDrop, &QToolButton::clicked, [this] { exportData(BufferExport(BufferExport::CSV)); }); @@ -2577,6 +2623,36 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) m_Ctx.AddCaptureViewer(this); } +void BufferViewer::GetIndicesForMeshRow(uint32_t row, uint32_t &taskIndex, uint32_t &meshletIdx) +{ + const BufferConfiguration &config2 = m_ModelOut2->getConfig(); + + auto it = std::upper_bound(config2.meshletVertexPrefixCounts.begin(), + config2.meshletVertexPrefixCounts.end(), row); + + if(it != config2.meshletVertexPrefixCounts.begin()) + it--; + + meshletIdx = uint32_t(it - config2.meshletVertexPrefixCounts.begin()); + + const BufferConfiguration &config1 = m_ModelOut1->getConfig(); + + taskIndex = 0; + + uint32_t meshletCounter = 0; + for(taskIndex = 0; taskIndex < meshletIdx && taskIndex < config1.taskSizes.size(); taskIndex++) + { + meshletCounter += config1.taskSizes[taskIndex].x * config1.taskSizes[taskIndex].y * + config1.taskSizes[taskIndex].z; + + if(meshletIdx < meshletCounter) + break; + } + + taskIndex += config1.taskOrMeshletOffset; + meshletIdx += config2.taskOrMeshletOffset; +} + void BufferViewer::SetupRawView() { ui->formatSpecifier->setVisible(true); @@ -2592,6 +2668,9 @@ void BufferViewer::SetupRawView() ui->viewIndex->setVisible(false); ui->dockarea->setVisible(false); + ui->meshFilterLabel->setVisible(false); + ui->resetMeshFilterButton->setVisible(false); + ui->inTable->setFrameShape(QFrame::NoFrame); ui->inTable->setPinnedColumns(1); @@ -2692,6 +2771,9 @@ void BufferViewer::SetupMeshView() ui->byteRangeLengthLabel->setVisible(false); byteRangeLength->setVisible(false); + ui->meshFilterLabel->setVisible(false); + ui->resetMeshFilterButton->setVisible(false); + ui->fixedVars->setVisible(false); ui->showPadding->setVisible(false); @@ -2870,15 +2952,23 @@ void BufferViewer::fixedVars_contextMenu(const QPoint &pos) QAction collapseAll(tr("C&ollapse All"), this); QAction copy(tr("&Copy"), this); QAction showPadding(tr("&Show Padding"), this); + QAction removeFilter(tr("&Remove Filter"), this); + QAction filterTask(tr("&Filter to this Task"), this); + QAction gotoMesh(tr("&Go to meshes"), this); expandAll.setIcon(Icons::arrow_out()); collapseAll.setIcon(Icons::arrow_in()); copy.setIcon(Icons::copy()); - showPadding.setIcon(Icons::align()); + removeFilter.setIcon(Icons::arrow_undo()); + filterTask.setIcon(Icons::filter()); + gotoMesh.setIcon(Icons::arrow_join()); showPadding.setCheckable(true); showPadding.setChecked(ui->showPadding->isChecked()); expandAll.setEnabled(item && item->childCount() > 0); + removeFilter.setEnabled(item && m_CurMeshFilter != MeshFilter::None); + filterTask.setEnabled(item); + gotoMesh.setEnabled(item); collapseAll.setEnabled(expandAll.isEnabled()); contextMenu.addAction(&expandAll); @@ -2887,14 +2977,63 @@ void BufferViewer::fixedVars_contextMenu(const QPoint &pos) contextMenu.addSeparator(); - if(!m_MeshView) + int idx = ui->fixedVars->indexOfTopLevelItem(item); + + if(m_MeshView) + { + contextMenu.addAction(&removeFilter); + contextMenu.addAction(&filterTask); + contextMenu.addAction(&gotoMesh); + + // if we're already filtering to a task, don't offer to filter any more. However if we're + // filtered to a mesh allow 'broadening' the filter back to the task + filterTask.setEnabled(m_CurMeshFilter != MeshFilter::TaskGroup); + + const BufferConfiguration &config1 = m_ModelOut1->getConfig(); + if(config1.taskSizes[idx].x * config1.taskSizes[idx].y * config1.taskSizes[idx].z == 0) + gotoMesh.setEnabled(false); + + // if there's a filter don't enable goto mesh as normally we just scroll to the first mesh - it + // would be redundant and potentially annoying to be able to and doesn't do anything useful + if(m_CurMeshFilter != MeshFilter::None) + gotoMesh.setEnabled(false); + } + else + { contextMenu.addAction(&showPadding); + } contextMenu.addSeparator(); contextMenu.addAction(m_ExportCSV); contextMenu.addAction(m_ExportBytes); + QObject::connect(&removeFilter, &QAction::triggered, + [this, idx]() { SetMeshFilter(MeshFilter::None); }); + QObject::connect(&filterTask, &QAction::triggered, [this, idx]() { + // if there's no filter, select this task. If we were mesh filtering, filter back to all meshes + // under the current task (don't use idx there, since it will just be 0) + if(m_CurMeshFilter == MeshFilter::None) + SetMeshFilter(MeshFilter::TaskGroup, idx); + else + SetMeshFilter(MeshFilter::TaskGroup, m_FilteredTaskGroup); + }); + QObject::connect(&gotoMesh, &QAction::triggered, [this, idx]() { + const BufferConfiguration &config1 = m_ModelOut1->getConfig(); + + uint32_t meshletIndex = 0; + for(int i = 0; i < idx && i < config1.taskSizes.count(); i++) + { + meshletIndex += config1.taskSizes[i].x * config1.taskSizes[i].y * config1.taskSizes[i].z; + } + + const BufferConfiguration &config2 = m_ModelOut2->getConfig(); + uint32_t vertexOffset = config2.meshletVertexPrefixCounts[meshletIndex]; + + ShowMeshData(MeshDataStage::MeshOut); + ScrollToRow((int)vertexOffset, MeshDataStage::MeshOut); + }); + QObject::connect(&expandAll, &QAction::triggered, [this, item]() { ui->fixedVars->expandAllItems(item); }); @@ -2919,6 +3058,35 @@ void BufferViewer::stageRowMenu(MeshDataStage stage, QMenu *menu, const QPoint & menu->setToolTipsVisible(true); + QModelIndex idx = m_CurView->selectionModel()->currentIndex(); + + const ActionDescription *action = m_Ctx.CurAction(); + + if(action && (action->flags & ActionFlags::MeshDispatch)) + { + if(stage == MeshDataStage::GSOut) + { + const BufferConfiguration &config = m_ModelOut2->getConfig(); + + auto it = std::upper_bound(config.meshletVertexPrefixCounts.begin(), + config.meshletVertexPrefixCounts.end(), (uint32_t)idx.row()); + + if(it != config.meshletVertexPrefixCounts.begin()) + it--; + + size_t meshletIdx = it - config.meshletVertexPrefixCounts.begin(); + + m_RemoveFilter->setEnabled(m_CurMeshFilter != MeshFilter::None); + + menu->addAction(m_RemoveFilter); + menu->addAction(m_FilterMesh); + menu->addAction(m_GotoTask); + menu->addSeparator(); + + m_GotoTask->setEnabled(m_Ctx.CurPipelineState().GetShaderReflection(ShaderStage::Task)); + } + } + if(m_MeshView && stage != MeshDataStage::GSOut) { const ShaderReflection *shaderDetails = @@ -2970,8 +3138,6 @@ void BufferViewer::stageRowMenu(MeshDataStage stage, QMenu *menu, const QPoint & else if(stage == MeshDataStage::MeshOut) contextMenu = ContextMenu::MeshPreview_MeshOutVertex; - QModelIndex idx = m_CurView->selectionModel()->currentIndex(); - ExtensionCallbackData callbackdata = {make_pyarg("stage", (uint32_t)stage)}; if(idx.isValid()) @@ -3042,6 +3208,12 @@ void BufferViewer::FillScrolls(PopulateBufferData *bufdata) bufdata->inVert = ui->inTable->indexAt(QPoint(0, 0)).row(); bufdata->out1Vert = ui->out1Table->indexAt(QPoint(0, 0)).row(); bufdata->out2Vert = ui->out2Table->indexAt(QPoint(0, 0)).row(); + + if(bufdata->meshDispatch) + { + bufdata->out1Horiz = ui->fixedVars->horizontalScrollBar()->value(); + bufdata->out1Vert = ui->fixedVars->indexOfTopLevelItem(ui->fixedVars->itemAt(QPoint(0, 0))); + } } void BufferViewer::OnEventChanged(uint32_t eventId) @@ -3260,6 +3432,86 @@ void BufferViewer::OnEventChanged(uint32_t eventId) bufdata->postOut1 = r->GetPostVSData(0, bufdata->inConfig.curView, MeshDataStage::TaskOut); bufdata->postOut2 = r->GetPostVSData(0, bufdata->inConfig.curView, MeshDataStage::MeshOut); + // apply mesh/task filtering to mesh data here, which will also propagate to preview + if(m_FilteredMeshGroup != ~0U) + { + bufdata->out1Config.taskOrMeshletOffset = m_FilteredTaskGroup; + + // find this meshlet's offset in the index buffer and filter to only it + uint32_t indexCount = 0, vertexCount = 0; + for(uint32_t i = 0; i <= m_FilteredMeshGroup && i < bufdata->postOut2.meshletSizes.size(); + i++) + { + MeshletSize meshletSize = bufdata->postOut2.meshletSizes[i]; + uint32_t numIndices = meshletSize.numIndices; + if(i == m_FilteredMeshGroup) + { + bufdata->postOut2.meshletIndexOffset = vertexCount; + bufdata->postOut2.meshletOffset = m_FilteredMeshGroup; + bufdata->out2Config.taskOrMeshletOffset = m_FilteredMeshGroup; + bufdata->postOut2.numIndices = numIndices; + bufdata->postOut2.meshletSizes = {meshletSize}; + bufdata->postOut2.indexByteOffset += indexCount * bufdata->postOut2.indexByteStride; + } + indexCount += numIndices; + vertexCount += meshletSize.numVertices; + } + } + else if(m_FilteredTaskGroup != ~0U) + { + bufdata->out1Config.taskOrMeshletOffset = m_FilteredTaskGroup; + + // find the relevant task and which mesh indices it corresponds to + uint32_t meshletCounter = 0; + for(uint32_t taskIndex = 0; + taskIndex <= m_FilteredTaskGroup && taskIndex < bufdata->postOut1.taskSizes.size(); + taskIndex++) + { + uint32_t numMeshesInTask = bufdata->postOut1.taskSizes[taskIndex].x * + bufdata->postOut1.taskSizes[taskIndex].y * + bufdata->postOut1.taskSizes[taskIndex].z; + + // once we've found the desired task, filter our view to only its meshes + if(taskIndex == m_FilteredTaskGroup) + { + bufdata->postOut2.numIndices = 0; + + rdcarray meshletSizes; + meshletSizes.reserve(numMeshesInTask); + + uint32_t indexCount = 0, vertexCount = 0; + for(uint32_t i = 0; + i < meshletCounter + numMeshesInTask && i < bufdata->postOut2.meshletSizes.size(); + i++) + { + uint32_t indicesInMeshlet = bufdata->postOut2.meshletSizes[i].numIndices; + + if(i >= meshletCounter) + { + bufdata->postOut2.numIndices += indicesInMeshlet; + meshletSizes.push_back(bufdata->postOut2.meshletSizes[i]); + } + + if(i == meshletCounter) + { + bufdata->postOut2.meshletIndexOffset = vertexCount; + bufdata->postOut2.meshletOffset = meshletCounter; + bufdata->out2Config.taskOrMeshletOffset = meshletCounter; + bufdata->postOut2.indexByteOffset += indexCount * bufdata->postOut2.indexByteStride; + } + indexCount += indicesInMeshlet; + vertexCount += bufdata->postOut2.meshletSizes[i].numVertices; + } + + bufdata->postOut2.meshletSizes = meshletSizes; + + break; + } + + meshletCounter += numMeshesInTask; + } + } + RT_FetchMeshPipeData(r, m_Ctx, bufdata); } else @@ -3499,20 +3751,25 @@ void BufferViewer::OnEventChanged(uint32_t eventId) { TaskGroupSize size = bufdata->out1Config.taskSizes[i]; - RDTreeWidgetItem *n = new RDTreeWidgetItem( - {QFormatStr("%1, %2, %3").arg(x).arg(y).arg(z), - QFormatStr("Dispatched [%1, %2, %3]").arg(size.x).arg(size.y).arg(size.z), - lit("Task Group")}); + RDTreeWidgetItem *n = NULL; - if(!bufdata->out1Config.columns.empty()) + if(m_CurMeshFilter == MeshFilter::None || m_FilteredTaskGroup == i) + { + n = new RDTreeWidgetItem( + {QFormatStr("%1, %2, %3").arg(x).arg(y).arg(z), + QFormatStr("Dispatched [%1, %2, %3]").arg(size.x).arg(size.y).arg(size.z), + lit("Task Group")}); + + ui->fixedVars->addTopLevelItem(n); + } + + if(n && !bufdata->out1Config.columns.empty()) { UI_AddTaskPayloads(n, i * bufdata->out1Config.buffers[0]->stride, bufdata->out1Config.columns, bufdata->out1Config.buffers[0]); } i++; - - ui->fixedVars->addTopLevelItem(n); } } } @@ -3530,6 +3787,12 @@ void BufferViewer::OnEventChanged(uint32_t eventId) 0); else ui->fixedVars->applyExpansion(state, 0); + + if(bufdata->out1Vert >= 0 && bufdata->out1Vert < ui->fixedVars->topLevelItemCount()) + { + ScrollToRow(bufdata->out1Vert, MeshDataStage::TaskOut); + ui->fixedVars->horizontalScrollBar()->setValue(bufdata->out1Horiz); + } } if(!m_MeshView) @@ -4795,6 +5058,13 @@ void BufferViewer::ViewTexture(ResourceId id, const Subresource &sub, const rdcs void BufferViewer::ScrollToRow(int32_t row, MeshDataStage stage) { + if(m_MeshView && stage == MeshDataStage::TaskOut) + { + ui->fixedVars->scrollToItem(ui->fixedVars->topLevelItem(row)); + ui->fixedVars->setSelectedItem(ui->fixedVars->topLevelItem(row)); + return; + } + ScrollToRow(tableForStage(stage), row); if(m_MeshView) @@ -4972,6 +5242,11 @@ void BufferViewer::updateLabelsAndLayout() m_Containers[1]->layout()->addWidget(ui->out2Table); m_Containers[2]->layout()->addWidget(ui->inTable); + ui->instanceLabel->setVisible(false); + ui->instance->setVisible(false); + ui->meshFilterLabel->setVisible(true); + ui->resetMeshFilterButton->setVisible(true); + ui->fixedVars->setVisible(true); ui->out1Table->setVisible(false); m_Containers[2]->setWindowTitle(tr("Mesh Input")); @@ -4994,6 +5269,11 @@ void BufferViewer::updateLabelsAndLayout() m_Containers[1]->layout()->addWidget(ui->out1Table); m_Containers[2]->layout()->addWidget(ui->out2Table); + ui->instanceLabel->setVisible(true); + ui->instance->setVisible(true); + ui->meshFilterLabel->setVisible(false); + ui->resetMeshFilterButton->setVisible(false); + ui->fixedVars->setVisible(false); ui->out1Table->setVisible(true); m_Containers[0]->setWindowTitle(tr("VS Input")); @@ -5018,6 +5298,11 @@ void BufferViewer::updateLabelsAndLayout() m_Containers[1]->layout()->addWidget(ui->out1Table); m_Containers[2]->layout()->addWidget(ui->out2Table); + ui->instanceLabel->setVisible(true); + ui->instance->setVisible(true); + ui->meshFilterLabel->setVisible(false); + ui->resetMeshFilterButton->setVisible(false); + ui->fixedVars->setVisible(false); ui->out1Table->setVisible(true); m_Containers[0]->setWindowTitle(tr("VS Input")); @@ -5267,6 +5552,7 @@ void BufferViewer::Reset() ClearModels(); updateLabelsAndLayout(); + SetMeshFilter(MeshFilter::None); ui->fixedVars->clear(); @@ -5623,6 +5909,11 @@ void BufferViewer::on_setFormat_toggled(bool checked) reflection->constantBlocks[m_CBufferSlot.slot].variables, 0)); } +void BufferViewer::on_resetMeshFilterButton_clicked() +{ + SetMeshFilter(MeshFilter::None); +} + void BufferViewer::processFormat(const QString &format) { // save scroll values now before we reset all the models @@ -6490,6 +6781,115 @@ void BufferViewer::on_viewIndex_valueChanged(int value) OnEventChanged(m_Ctx.CurEvent()); } +void BufferViewer::SetMeshFilter(MeshFilter filter, uint32_t taskGroup, uint32_t meshGroup) +{ + // calculate new scrolls manually to keep the same logical item selected + m_Scrolls = new PopulateBufferData; + FillScrolls(m_Scrolls); + + { + const BufferConfiguration &config1 = m_ModelOut1->getConfig(); + const BufferConfiguration &config2 = m_ModelOut2->getConfig(); + + // baseTaskRow is the first row in the mesh view for the start of the task with no mesh filter, + // and baseMeshRow is the offset to the filtered mesh (if relevant). They could be identical + const uint32_t prevBaseTaskRow = m_TaskFilterRowOffset; + const uint32_t prevBaseMeshRow = m_MeshFilterRowOffset; + + // if we're filtering directly to a task from none, we also have the prefix count we just have + // to determine the base mesh + uint32_t taskBaseMesh = 0; + for(uint32_t i = 0; i < taskGroup && i < config1.taskSizes.size(); i++) + taskBaseMesh += config1.taskSizes[i].x * config1.taskSizes[i].y * config1.taskSizes[i].z; + + uint32_t newBaseTaskRow = 0, newBaseMeshRow = 0; + if(filter == MeshFilter::None || config2.meshletVertexPrefixCounts.empty()) + { + // if the new filter is none, then our new base row for both is 0 + newBaseTaskRow = newBaseMeshRow = 0; + } + else if(m_CurMeshFilter == MeshFilter::None && filter == MeshFilter::Mesh) + { + newBaseTaskRow = config2.meshletVertexPrefixCounts[taskBaseMesh]; + newBaseMeshRow = config2.meshletVertexPrefixCounts[meshGroup]; + } + else if(m_CurMeshFilter == MeshFilter::None && filter == MeshFilter::TaskGroup) + { + newBaseTaskRow = newBaseMeshRow = config2.meshletVertexPrefixCounts[taskBaseMesh]; + } + else if(m_CurMeshFilter == MeshFilter::TaskGroup && filter == MeshFilter::Mesh) + { + // the first complex case - if we're already filtered to a task and now we're filtering to a + // mesh, we only have prefix counts relatively so look it up + newBaseTaskRow = prevBaseTaskRow; + newBaseMeshRow = prevBaseTaskRow + config2.meshletVertexPrefixCounts[meshGroup - taskBaseMesh]; + } + else if(m_CurMeshFilter == MeshFilter::Mesh && filter == MeshFilter::TaskGroup) + { + // the second complex case - if we're already filtered to a *mesh* and now we're filtering + // back to the task, we undo the previous per-mesh filter + newBaseTaskRow = newBaseMeshRow = prevBaseTaskRow; + + // only support filtering within the same group, not arbitrarily from one mesh in one task + // group to a different task group + Q_ASSERT(m_FilteredTaskGroup == taskGroup); + } + + const uint32_t prevBaseRow = prevBaseMeshRow; + const uint32_t newBaseRow = newBaseMeshRow; + + // when going to/from no filter, we just rebase by the base row and set the task row that we know directly + if(m_CurMeshFilter == MeshFilter::None) + { + m_Scrolls->out1Vert = 0; + m_Scrolls->out2Vert -= newBaseRow; + } + else if(filter == MeshFilter::None) + { + m_Scrolls->out1Vert = config1.taskOrMeshletOffset; + m_Scrolls->out2Vert += prevBaseRow; + } + + // otherwise changing between task and mesh filter, we rebase based on the difference between + // the number of meshes shown. The task filter doesn't have to change + else if(m_CurMeshFilter == MeshFilter::TaskGroup && filter == MeshFilter::Mesh) + { + m_Scrolls->out1Vert = 0; + m_Scrolls->out2Vert -= (newBaseRow - prevBaseRow); + } + else if(m_CurMeshFilter == MeshFilter::Mesh && filter == MeshFilter::TaskGroup) + { + m_Scrolls->out1Vert = 0; + m_Scrolls->out2Vert += (prevBaseRow - newBaseRow); + } + + m_TaskFilterRowOffset = newBaseTaskRow; + m_MeshFilterRowOffset = newBaseMeshRow; + } + + m_CurMeshFilter = filter; + m_FilteredTaskGroup = taskGroup; + m_FilteredMeshGroup = meshGroup; + + switch(m_CurMeshFilter) + { + case MeshFilter::None: + ui->meshFilterLabel->setText(tr("Current Range filter: None")); + ui->resetMeshFilterButton->setEnabled(false); + break; + case MeshFilter::TaskGroup: + ui->meshFilterLabel->setText(tr("Current Range filter: Single Task Group")); + ui->resetMeshFilterButton->setEnabled(true); + break; + case MeshFilter::Mesh: + ui->meshFilterLabel->setText(tr("Current Range filter: Single Meshlet")); + ui->resetMeshFilterButton->setEnabled(true); + break; + } + + OnEventChanged(m_Ctx.CurEvent()); +} + void BufferViewer::on_rowOffset_valueChanged(int value) { if(!m_MeshView && m_ModelIn->getConfig().unclampedNumRows > 0) diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 3f02c2d3b..35069eb10 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -141,6 +141,7 @@ private slots: void on_axisMappingCombo_currentIndexChanged(int index); void on_axisMappingButton_clicked(); void on_setFormat_toggled(bool checked); + void on_resetMeshFilterButton_clicked(); // manual slots void render_mouseMove(QMouseEvent *e); @@ -210,6 +211,8 @@ private: void FillScrolls(PopulateBufferData *bufdata); + void GetIndicesForMeshRow(uint32_t row, uint32_t &taskIndex, uint32_t &meshletIndex); + void UI_ResetArcball(); // data from raw buffer view @@ -263,6 +266,17 @@ private: // (mesh out) and [2] being in QWidget *m_Containers[3] = {}; + enum class MeshFilter + { + None, + TaskGroup, + Mesh, + }; + + MeshFilter m_CurMeshFilter = MeshFilter::None; + uint32_t m_FilteredTaskGroup = ~0U, m_FilteredMeshGroup = ~0U; + uint32_t m_TaskFilterRowOffset = 0, m_MeshFilterRowOffset = 0; + PopulateBufferData *m_Scrolls = NULL; QPoint m_Scroll[4]; @@ -306,6 +320,9 @@ private: QAction *m_ExportCSV = NULL; QAction *m_ExportBytes = NULL; QAction *m_DebugVert = NULL; + QAction *m_FilterMesh = NULL; + QAction *m_RemoveFilter = NULL; + QAction *m_GotoTask = NULL; RDSpinBox64 *byteRangeStart = NULL; RDSpinBox64 *byteRangeLength = NULL; @@ -322,6 +339,8 @@ private: void SetupMeshView(); void SetupRawView(); + void SetMeshFilter(MeshFilter filter, uint32_t taskGroup = ~0U, uint32_t meshGroup = ~0U); + void stageRowMenu(MeshDataStage stage, QMenu *menu, const QPoint &pos); void meshHeaderMenu(MeshDataStage stage, const QPoint &pos); diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 29a9f7741..77113b0d1 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -988,6 +988,24 @@ Enter 0.0 to use automatic/guessed value derived from data. + + + + Current Range filter: None + + + + + + + + :/arrow_undo.png:/arrow_undo.png + + + true + + +