diff --git a/docs/getting_started/quick_start.rst b/docs/getting_started/quick_start.rst index ecbf9941bd..714ce6a95a 100644 --- a/docs/getting_started/quick_start.rst +++ b/docs/getting_started/quick_start.rst @@ -184,7 +184,7 @@ More details can be found on the :doc:`../window/mesh_viewer` page. Mesh Viewer allows you to inspect the geometry data as it passes through the pipeline. Both raw data in a grid view and 3D inspection is possible. The tabs in the preview window allow you to choose at which part of the pipeline you'd like to visualise the data. -By default the preview shows a wireframe rendering of the mesh, but you can choose solid shading options. This can either be simple shading or use a secondary attribute as color. Right clicking on any column allows you to choose the secondary attribute for rendering. +By default the preview shows a wireframe rendering of the mesh, but you can choose different visualisations. This can either be simple shading, a vertex exploder utility, or using a secondary attribute as color. Right clicking on any column allows you to choose the secondary attribute for rendering. You can also select which attribute is the position, in case either the auto-detection failed or you want to visualise another attribute like texture co-ordinates in 3D space. diff --git a/docs/imgs/Screenshots/ExploderBadShoes.png b/docs/imgs/Screenshots/ExploderBadShoes.png new file mode 100644 index 0000000000..92b18e1dd0 Binary files /dev/null and b/docs/imgs/Screenshots/ExploderBadShoes.png differ diff --git a/docs/imgs/Screenshots/ExploderGoodShoes.png b/docs/imgs/Screenshots/ExploderGoodShoes.png new file mode 100644 index 0000000000..364375cbc6 Binary files /dev/null and b/docs/imgs/Screenshots/ExploderGoodShoes.png differ diff --git a/docs/python_api/renderdoc/outputs.rst b/docs/python_api/renderdoc/outputs.rst index 07b209c1c2..d10d17ada0 100644 --- a/docs/python_api/renderdoc/outputs.rst +++ b/docs/python_api/renderdoc/outputs.rst @@ -61,7 +61,7 @@ Mesh View .. autoclass:: MeshFormat :members: -.. autoclass:: SolidShade +.. autoclass:: Visualisation :members: .. autoclass:: Camera diff --git a/docs/window/mesh_viewer.rst b/docs/window/mesh_viewer.rst index 35057d7c3e..9ea6bb1449 100644 --- a/docs/window/mesh_viewer.rst +++ b/docs/window/mesh_viewer.rst @@ -47,12 +47,21 @@ Page up and Page down will move vertically 'up' and 'down' relative to the camer Mesh Preview ------------ -In the 3D Mesh preview, you have the option to display the mesh with some solid shading modes, not just as a wireframe mesh. When solid shading you can toggle the wireframe on and off. +In the 3D Mesh preview, you have the option to display the mesh with some colored visualisations, not just as a wireframe mesh. When using one of the visualisations, you can toggle the wireframe on and off. * Solid Color simply displays a solid color for each triangle. * Flat Shaded will perform basic flat lighting calculations based on triangle normals to give a better idea of the topology of the mesh. * Secondary will display the selected secondary mesh element. * Meshlet is only relevant when using mesh shaders, and will colourise each meshlet with a different colour. +* Exploded will display a shaded mesh where vertex colors are a function of the vertex index (SV_VertexID or gl_VertexID). This means adjacent primitives that do not share the exact same vertices can be more easily seen by a hard break in coloring. Additionally, a slider allows displacing vertices along an explosion direction by an amount based on the vertex index, which also helps better visualise shared or disjoint vertices and can also help in visualising other mesh problems (e.g. hidden or duplicate vertices/primitives). + +.. figure:: ../imgs/Screenshots/ExploderBadShoes.png + + Exploded: Visualising a mesh with no vertex sharing. + +.. figure:: ../imgs/Screenshots/ExploderGoodShoes.png + + Exploded: Visualising a mesh with widely shared vertices. To select which element will be displayed as secondary, simply right click on the column you would like to use. This can be done on the input and output separately, and 4-component columns have the option to either show RGB as color, or alpha as grayscale. diff --git a/qrenderdoc/Styles/RDStyle/RDStyle.cpp b/qrenderdoc/Styles/RDStyle/RDStyle.cpp index 15a652721e..5d0b262260 100644 --- a/qrenderdoc/Styles/RDStyle/RDStyle.cpp +++ b/qrenderdoc/Styles/RDStyle/RDStyle.cpp @@ -37,6 +37,9 @@ namespace Constants { +static const int SliderHandleHalfWidth = 4; +static const int SliderGrooveHeight = 4; + static const int ButtonMargin = 6; static const int ButtonBorder = 1; @@ -470,6 +473,32 @@ QRect RDStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, return opt->rect; } + else if(cc == QStyle::CC_Slider) + { + QRect ret = opt->rect; + ret.adjust(1, 1, -1, -1); + if(sc == QStyle::SC_SliderGroove) + { + int toGrooveHeightHalfReduction = (ret.height() - Constants::SliderGrooveHeight) / 2; + ret.adjust(Constants::SliderHandleHalfWidth, toGrooveHeightHalfReduction, + -Constants::SliderHandleHalfWidth, -toGrooveHeightHalfReduction); + } + else if(sc == QStyle::SC_SliderHandle) + { + const QAbstractSlider *slider = qobject_cast(widget); + int sliderMin = slider->minimum(); + int sliderMax = slider->maximum(); + int sliderPos = slider->sliderPosition(); + qreal posUNorm = (qreal)(sliderPos - sliderMin) / (qreal)(sliderMax - sliderMin); + int grooveLeft = ret.left() + Constants::SliderHandleHalfWidth; + int grooveRight = ret.right() - Constants::SliderHandleHalfWidth; + int grooveWidth = grooveRight - grooveLeft; + int sliderX = (int)(posUNorm * (qreal)grooveWidth) + grooveLeft; + ret.setLeft(sliderX - Constants::SliderHandleHalfWidth); + ret.setRight(sliderX + Constants::SliderHandleHalfWidth); + } + return ret; + } else if(cc == QStyle::CC_ComboBox) { QRect rect = opt->rect; @@ -1118,6 +1147,19 @@ void RDStyle::drawComplexControl(ComplexControl control, const QStyleOptionCompl return; } + else if(control == QStyle::CC_Slider) + { + QRect grooveRect = subControlRect(control, opt, QStyle::SC_SliderGroove, widget); + p->drawLine(QLine(grooveRect.x(), grooveRect.y() + grooveRect.height() / 2, grooveRect.right(), + grooveRect.y() + grooveRect.height() / 2)); + + QRect handleRect = subControlRect(control, opt, QStyle::SC_SliderHandle, widget); + QBrush handleBrush = (m_Scheme == Light) ? opt->palette.brush(QPalette::Dark) + : opt->palette.brush(QPalette::Text); + p->fillRect(handleRect, handleBrush); + + return; + } else if(control == QStyle::CC_ComboBox) { const QStyleOptionComboBox *combo = qstyleoption_cast(opt); diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index 01f636b063..59b1a5fc7c 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -2402,6 +2402,7 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) memset(&m_Config, 0, sizeof(m_Config)); m_Config.type = MeshDataStage::VSIn; m_Config.wireframeDraw = true; + m_Config.exploderScale = 1.0f; ui->outputTabs->setCurrentIndex(0); m_CurStage = MeshDataStage::VSIn; @@ -2537,10 +2538,11 @@ BufferViewer::BufferViewer(ICaptureContext &ctx, bool meshview, QWidget *parent) configureDrawRange(); - ui->solidShading->clear(); - ui->solidShading->addItems({tr("None"), tr("Solid Colour"), tr("Flat Shaded"), tr("Secondary")}); - ui->solidShading->adjustSize(); - ui->solidShading->setCurrentIndex(0); + ui->visualisation->clear(); + ui->visualisation->addItems( + {tr("None"), tr("Solid Colour"), tr("Flat Shaded"), tr("Secondary"), tr("Exploded")}); + ui->visualisation->adjustSize(); + ui->visualisation->setCurrentIndex(0); ui->matrixType->addItems({tr("Perspective"), tr("Orthographic")}); @@ -2843,7 +2845,7 @@ void BufferViewer::SetupMeshView() BufferItemModel *model = (BufferItemModel *)m_CurView->model(); model->setPosColumn(-1); - model->setSecondaryColumn(-1, m_Config.solidShadeMode == SolidShade::Secondary, false); + model->setSecondaryColumn(-1, m_Config.visualisationMode == Visualisation::Secondary, false); UI_ConfigureFormats(); on_resetCamera_clicked(); @@ -2863,8 +2865,8 @@ void BufferViewer::SetupMeshView() QObject::connect(m_SelectSecondColumn, &QAction::triggered, [this]() { BufferItemModel *model = (BufferItemModel *)m_CurView->model(); - model->setSecondaryColumn(m_ContextColumn, m_Config.solidShadeMode == SolidShade::Secondary, - false); + model->setSecondaryColumn(m_ContextColumn, + m_Config.visualisationMode == Visualisation::Secondary, false); UI_ConfigureFormats(); UpdateCurrentMeshConfig(); @@ -2873,8 +2875,8 @@ void BufferViewer::SetupMeshView() QObject::connect(m_SelectSecondAlphaColumn, &QAction::triggered, [this]() { BufferItemModel *model = (BufferItemModel *)m_CurView->model(); - model->setSecondaryColumn(m_ContextColumn, m_Config.solidShadeMode == SolidShade::Secondary, - true); + model->setSecondaryColumn(m_ContextColumn, + m_Config.visualisationMode == Visualisation::Secondary, true); UI_ConfigureFormats(); UpdateCurrentMeshConfig(); INVOKE_MEMFN(RT_UpdateAndDisplay); @@ -3674,7 +3676,8 @@ void BufferViewer::OnEventChanged(uint32_t eventId) // similarly for secondary columns if(m_ModelIn->secondaryColumn() == -1 || bufdata->highlightNames[1] != bufdata->inConfig.columnName(m_ModelIn->secondaryColumn())) - m_ModelIn->setSecondaryColumn(-1, m_Config.solidShadeMode == SolidShade::Secondary, false); + m_ModelIn->setSecondaryColumn(-1, m_Config.visualisationMode == Visualisation::Secondary, + false); // and as above for VS Out / GS Out if(m_ModelOut1->posColumn() == -1 || @@ -3682,14 +3685,16 @@ void BufferViewer::OnEventChanged(uint32_t eventId) m_ModelOut1->setPosColumn(-1); if(m_ModelOut1->secondaryColumn() == -1 || bufdata->highlightNames[3] != bufdata->out1Config.columnName(m_ModelOut1->secondaryColumn())) - m_ModelOut1->setSecondaryColumn(-1, m_Config.solidShadeMode == SolidShade::Secondary, false); + m_ModelOut1->setSecondaryColumn(-1, m_Config.visualisationMode == Visualisation::Secondary, + false); if(m_ModelOut2->posColumn() == -1 || bufdata->highlightNames[4] != bufdata->out2Config.columnName(m_ModelOut2->posColumn())) m_ModelOut2->setPosColumn(-1); if(m_ModelOut2->secondaryColumn() == -1 || bufdata->highlightNames[5] != bufdata->out2Config.columnName(m_ModelOut2->secondaryColumn())) - m_ModelOut2->setSecondaryColumn(-1, m_Config.solidShadeMode == SolidShade::Secondary, false); + m_ModelOut2->setSecondaryColumn(-1, m_Config.visualisationMode == Visualisation::Secondary, + false); EnableCameraGuessControls(); @@ -5306,9 +5311,9 @@ void BufferViewer::updateLabelsAndLayout() ui->outputTabs->setTabText(0, tr("Mesh Input")); ui->outputTabs->setTabText(1, tr("Mesh Out")); - if(ui->solidShading->itemText(ui->solidShading->count() - 1) != tr("Meshlet")) - ui->solidShading->addItem(tr("Meshlet")); - ui->solidShading->adjustSize(); + if(ui->visualisation->itemText(ui->visualisation->count() - 1) != tr("Meshlet")) + ui->visualisation->addItem(tr("Meshlet")); + ui->visualisation->adjustSize(); } else { @@ -5334,9 +5339,9 @@ void BufferViewer::updateLabelsAndLayout() ui->outputTabs->setTabText(1, tr("VS Out")); ui->outputTabs->setTabText(2, tr("GS/DS Out")); - if(ui->solidShading->itemText(ui->solidShading->count() - 1) == tr("Meshlet")) - ui->solidShading->removeItem(ui->solidShading->count() - 1); - ui->solidShading->adjustSize(); + if(ui->visualisation->itemText(ui->visualisation->count() - 1) == tr("Meshlet")) + ui->visualisation->removeItem(ui->visualisation->count() - 1); + ui->visualisation->adjustSize(); } } else @@ -5363,9 +5368,9 @@ void BufferViewer::updateLabelsAndLayout() ui->outputTabs->setTabText(1, tr("VS Out")); ui->outputTabs->setTabText(2, tr("GS/DS Out")); - if(ui->solidShading->itemText(ui->solidShading->count() - 1) == tr("Meshlet")) - ui->solidShading->removeItem(ui->solidShading->count() - 1); - ui->solidShading->adjustSize(); + if(ui->visualisation->itemText(ui->visualisation->count() - 1) == tr("Meshlet")) + ui->visualisation->removeItem(ui->visualisation->count() - 1); + ui->visualisation->adjustSize(); } } else @@ -6678,7 +6683,7 @@ void BufferViewer::UpdateHighlightVerts() { m_Config.highlightVert = ~0U; - if(!ui->highlightVerts->isChecked()) + if(ui->highlightVerts->isHidden() || !ui->highlightVerts->isChecked()) return; RDTableView *table = currentTable(); @@ -6777,6 +6782,25 @@ void BufferViewer::on_highlightVerts_toggled(bool checked) INVOKE_MEMFN(RT_UpdateAndDisplay); } +void BufferViewer::on_vtxExploderSlider_valueChanged(int value) +{ + m_Config.vtxExploderSliderSNorm = (float)value / 100.0f; + + INVOKE_MEMFN(RT_UpdateAndDisplay); +} + +void BufferViewer::on_exploderReset_clicked() +{ + ui->vtxExploderSlider->setSliderPosition(0); +} + +void BufferViewer::on_exploderScale_valueChanged(double value) +{ + m_Config.exploderScale = (float)value; + + INVOKE_MEMFN(RT_UpdateAndDisplay); +} + void BufferViewer::on_wireframeRender_toggled(bool checked) { m_Config.wireframeDraw = checked; @@ -6784,7 +6808,7 @@ void BufferViewer::on_wireframeRender_toggled(bool checked) INVOKE_MEMFN(RT_UpdateAndDisplay); } -void BufferViewer::on_solidShading_currentIndexChanged(int index) +void BufferViewer::on_visualisation_currentIndexChanged(int index) { ui->wireframeRender->setEnabled(index > 0); @@ -6794,16 +6818,31 @@ void BufferViewer::on_solidShading_currentIndexChanged(int index) m_Config.wireframeDraw = true; } - m_Config.solidShadeMode = (SolidShade)qMax(0, index); + bool explodeHidden = (index != (int)Visualisation::Explode); + ui->vtxExploderLabel->setHidden(explodeHidden); + ui->vtxExploderSlider->setHidden(explodeHidden); + ui->exploderReset->setHidden(explodeHidden); + ui->exploderScaleLabel->setHidden(explodeHidden); + ui->exploderScale->setHidden(explodeHidden); + // Because the vertex/prim highlights draw from a new, temporary vertex buffer, + // those vertex IDs (which determine the explode displacement) won't necessarily + // match the original mesh's IDs and exploded vertices. Because of this, it seems + // cleanest to just avoid drawing the highlighted vert/prim with the explode + // visualisation (while also getting back a little room on the toolbar used by + // the extra exploder controls). + ui->highlightVerts->setHidden(!explodeHidden); + UpdateHighlightVerts(); + + m_Config.visualisationMode = (Visualisation)qMax(0, index); m_ModelIn->setSecondaryColumn(m_ModelIn->secondaryColumn(), - m_Config.solidShadeMode == SolidShade::Secondary, + m_Config.visualisationMode == Visualisation::Secondary, m_ModelIn->secondaryAlpha()); m_ModelOut1->setSecondaryColumn(m_ModelOut1->secondaryColumn(), - m_Config.solidShadeMode == SolidShade::Secondary, + m_Config.visualisationMode == Visualisation::Secondary, m_ModelOut1->secondaryAlpha()); m_ModelOut2->setSecondaryColumn(m_ModelOut2->secondaryColumn(), - m_Config.solidShadeMode == SolidShade::Secondary, + m_Config.visualisationMode == Visualisation::Secondary, m_ModelOut2->secondaryAlpha()); INVOKE_MEMFN(RT_UpdateAndDisplay); diff --git a/qrenderdoc/Windows/BufferViewer.h b/qrenderdoc/Windows/BufferViewer.h index 35069eb10d..b9c58e2594 100644 --- a/qrenderdoc/Windows/BufferViewer.h +++ b/qrenderdoc/Windows/BufferViewer.h @@ -128,8 +128,11 @@ private slots: void on_showPadding_toggled(bool checked); void on_resourceDetails_clicked(); void on_highlightVerts_toggled(bool checked); + void on_vtxExploderSlider_valueChanged(int value); + void on_exploderReset_clicked(); + void on_exploderScale_valueChanged(double value); void on_wireframeRender_toggled(bool checked); - void on_solidShading_currentIndexChanged(int index); + void on_visualisation_currentIndexChanged(int index); void on_drawRange_currentIndexChanged(int index); void on_controlType_currentIndexChanged(int index); void on_camSpeed_valueChanged(double value); diff --git a/qrenderdoc/Windows/BufferViewer.ui b/qrenderdoc/Windows/BufferViewer.ui index 0e2e3b48d2..cab4612acb 100644 --- a/qrenderdoc/Windows/BufferViewer.ui +++ b/qrenderdoc/Windows/BufferViewer.ui @@ -6,8 +6,8 @@ 0 0 - 788 - 537 + 1300 + 837 @@ -307,14 +307,14 @@ - + - Solid Shading + Visualisation - + @@ -382,6 +382,131 @@ + + + + true + + + + 0 + 0 + + + + Exploder + + + + + + + true + + + + 0 + 0 + + + + + 90 + 30 + + + + true + + + Qt::StrongFocus + + + Qt::NoContextMenu + + + Explode vertices + + + slider name + + + slider description + + + true + + + -100 + + + 100 + + + 0 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 20 + + + + + + + + 0 + 0 + + + + Reset/zero exploder slider + + + Reset + + + + + + + Scale + + + + + + + Exploder scaling + + + 1 + + + 0.100000000000000 + + + 99.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index 5a01c89a09..0b665c9332 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -309,10 +309,15 @@ struct MeshDisplay DOCUMENT("``True`` if the bounding box around the mesh should be rendered."); bool showBBox = false; - DOCUMENT("The :class:`solid shading mode ` to use when rendering the current mesh."); - SolidShade solidShadeMode = SolidShade::NoSolid; + DOCUMENT( + "The :class:`visualisation mode ` to use when rendering the current mesh."); + Visualisation visualisationMode = Visualisation::NoSolid; DOCUMENT("``True`` if the wireframe of the mesh should be rendered as well as solid shading."); bool wireframeDraw = true; + DOCUMENT("Displace/explode vertices to help visualise vertex reuse vs disjointedness."); + float vtxExploderSliderSNorm = 0.0f; + DOCUMENT("Scales the exploded vertex displacement."); + float exploderScale = 1.0f; static const uint32_t NoHighlight = ~0U; }; diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index edc604d49a..7316ff91b0 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -2895,7 +2895,7 @@ constexpr inline ResourceUsage RWResUsage(ShaderStage stage) return RWResUsage(uint32_t(stage)); } -DOCUMENT(R"(What kind of solid shading to use when rendering a mesh. +DOCUMENT(R"(What kind of visualisation to use when rendering a mesh. .. data:: NoSolid @@ -2913,22 +2913,27 @@ DOCUMENT(R"(What kind of solid shading to use when rendering a mesh. The mesh should be rendered using the secondary element as color. +.. data:: Explode + + The mesh should be rendered with vertices displaced and coloured by vertex ID. + .. data:: Meshlet The mesh should be rendered colorising each meshlet differently. )"); -enum class SolidShade : uint32_t +enum class Visualisation : uint32_t { NoSolid = 0, Solid, Lit, Secondary, + Explode, Meshlet, Count, }; -DECLARE_REFLECTION_ENUM(SolidShade); +DECLARE_REFLECTION_ENUM(Visualisation); DOCUMENT(R"(The fill mode for polygons. diff --git a/renderdoc/data/glsl/glsl_globals.h b/renderdoc/data/glsl/glsl_globals.h index 7ef8610196..b4a9c0af21 100644 --- a/renderdoc/data/glsl/glsl_globals.h +++ b/renderdoc/data/glsl/glsl_globals.h @@ -172,14 +172,15 @@ vec3 CalcCubeCoord(vec2 uv, int face) #endif -// first few match SolidShade enum +// first few match Visualisation enum #define MESHDISPLAY_SOLID 0x1 #define MESHDISPLAY_FACELIT 0x2 #define MESHDISPLAY_SECONDARY 0x3 -#define MESHDISPLAY_MESHLET 0x4 +#define MESHDISPLAY_EXPLODE 0x4 +#define MESHDISPLAY_MESHLET 0x5 // extra values below -#define MESHDISPLAY_SECONDARY_ALPHA 0x5 +#define MESHDISPLAY_SECONDARY_ALPHA 0x6 #define MAX_NUM_MESHLETS (512 * 1024) diff --git a/renderdoc/data/glsl/glsl_ubos.h b/renderdoc/data/glsl/glsl_ubos.h index ddee1b380d..c8399f2720 100644 --- a/renderdoc/data/glsl/glsl_ubos.h +++ b/renderdoc/data/glsl/glsl_ubos.h @@ -35,12 +35,19 @@ BINDING(0) uniform MeshUBOData mat4 mvp; mat4 invProj; vec4 color; + int displayFormat; uint homogenousInput; vec2 pointSpriteSize; + uint rawoutput; uint flipY; - vec2 padding; + float vtxExploderSNorm; + float exploderScale; + + vec3 exploderCentre; + float padding; + uvec4 meshletColours[12]; } INST_NAME(Mesh); diff --git a/renderdoc/data/glsl/mesh.frag b/renderdoc/data/glsl/mesh.frag index 4afd34702a..12050d353c 100644 --- a/renderdoc/data/glsl/mesh.frag +++ b/renderdoc/data/glsl/mesh.frag @@ -60,6 +60,12 @@ void main(void) color_out = vec4(Mesh.color.xyz * abs(dot(lightDir, NORM_NAME.xyz)), 1); } + else if(type == MESHDISPLAY_EXPLODE) + { + vec3 lightDir = normalize(vec3(0, -0.3f, -1)); + + color_out = vec4(SECONDARY_NAME.xyz * abs(dot(lightDir, NORM_NAME.xyz)), 1); + } else // if(type == MESHDISPLAY_SOLID) { color_out = vec4(Mesh.color.xyz, 1); diff --git a/renderdoc/data/glsl/mesh.vert b/renderdoc/data/glsl/mesh.vert index 5d29c30f19..aec9432cfd 100644 --- a/renderdoc/data/glsl/mesh.vert +++ b/renderdoc/data/glsl/mesh.vert @@ -36,6 +36,70 @@ #define SECONDARY_TYPE vec4 #endif +// This function is mostly duplicated between 'mesh.hlsl' and 'mesh.vert'. +// Without a convenient shared common source, changes to one should be +// reflected in the other. +void vtxExploder(in int vtxID, inout vec3 pos, inout vec3 secondary) +{ + if(Mesh.exploderScale > 0.0f) + { + float nonLinearVtxExplodeScale = 4.0f * Mesh.exploderScale * Mesh.vtxExploderSNorm * + Mesh.vtxExploderSNorm * Mesh.vtxExploderSNorm; + + // A vertex might be coincident with our 'exploderCentre' so that, when normalized, + // can give us INFs/NaNs that, even if multiplied by a zero 'exploderScale', can + // leave us with bad numbers (as seems to be the case with glsl/vulkan, but not hlsl). + // Still, we should make this case safe for when we have a non-zero 'exploderScale' - + vec3 offset = pos - Mesh.exploderCentre; + float offsetDistSquared = dot(offset, offset); + vec3 safeExplodeDir = offset * inversesqrt(max(offsetDistSquared, FLT_EPSILON)); + + float displacement = + nonLinearVtxExplodeScale * ((float((vtxID >> 1) & 0xf) / 15.0f) * 1.5f - 0.5f); + pos += (safeExplodeDir * displacement); + + // For the exploder visualisation, colour verts based on vertex ID, which we + // store in secondary. + // + // Interpolate a colour gradient from 0.0 to 1.0 and back to 0.0 for vertex IDs + // 0 to 16 to 32 respectively - + // 1 - | .`. + // | .` `. + // | .` | `. + // 0.5-| .` `. + // | .` | `. . + // |.` `. .` + // 0.0-+-----------+-----------+--- vtx IDs + // 0 16 32 + + float vtxIDMod32Div16 = float(vtxID % 32) / 16.0f; // 0: 0.0 16: 1.0 31: 1.94 + float descending = floor(vtxIDMod32Div16); // 0..15: 0.0 16..31: 1.0 + float gradientVal = abs(vtxIDMod32Div16 - (2.0f * descending)); // 0.0..1.0 + + // Use a hopefully fairly intuitive temperature gradient scheme to help visualise + // contiguous/nearby sequences of vertices, which should also show up breaks in + // colour where verts aren't shared between adjacent primitives. + const vec3 gradientColours[5] = + vec3[](vec3(0.004f, 0.002f, 0.025f), // 0.0..0.25: Dark blue + vec3(0.305f, 0.001f, 0.337f), // 0.25..0.5: Purple + vec3(0.665f, 0.033f, 0.133f), // 0.5..0.75: Purple orange + vec3(1.000f, 0.468f, 0.000f), // 0.75..1.0: Orange + vec3(1.000f, 1.000f, 1.000f) // 1.0: White + ); + + uint gradientSectionStartIdx = uint(gradientVal * 4.0f); + uint gradientSectionEndIdx = min(gradientSectionStartIdx + 1u, 4u); + vec3 gradSectionStartCol = gradientColours[gradientSectionStartIdx]; + vec3 gradSectionEndCol = gradientColours[gradientSectionEndIdx]; + + float sectionLerp = gradientVal - float(gradientSectionStartIdx) * 0.25f; + vec3 gradCol = mix(gradientColours[gradientSectionStartIdx], + gradientColours[gradientSectionEndIdx], sectionLerp); + + secondary = gradCol; + } +} + IO_LOCATION(0) in POSITION_TYPE vsin_position; IO_LOCATION(1) in SECONDARY_TYPE vsin_secondary; @@ -111,6 +175,9 @@ void main(void) vec2[](vec2(-1.0f, -1.0f), vec2(-1.0f, 1.0f), vec2(1.0f, -1.0f), vec2(1.0f, 1.0f)); vec4 pos = vec4(vsin_position); + vec4 secondary = vec4(vsin_secondary); + vtxExploder(VERTEX_ID, pos.xyz, secondary.xyz); + if(Mesh.homogenousInput == 0u) { pos = vec4(pos.xyz, 1); @@ -124,7 +191,7 @@ void main(void) gl_Position = Mesh.mvp * pos; gl_Position.xy += Mesh.pointSpriteSize.xy * 0.01f * psprite[VERTEX_ID % 4] * gl_Position.w; - vsout_secondary = vec4(vsin_secondary); + vsout_secondary = vec4(secondary); vsout_norm = vec4(0, 0, 1, 1); #ifdef VULKAN diff --git a/renderdoc/data/hlsl/hlsl_cbuffers.h b/renderdoc/data/hlsl/hlsl_cbuffers.h index 0e7d32d03b..90cbda75dd 100644 --- a/renderdoc/data/hlsl/hlsl_cbuffers.h +++ b/renderdoc/data/hlsl/hlsl_cbuffers.h @@ -107,12 +107,15 @@ cbuffer MeshVertexCBuffer REG(b0) float2 SpriteSize; uint homogenousInput; - uint vertMeshDisplayFormat; + float vtxExploderSNorm; + + float3 exploderCentre; + float exploderScale; // Non-zero values imply use of the exploder visualisation. + uint vertMeshDisplayFormat; uint meshletOffset; uint meshletCount; uint padding1; - uint padding2; uint4 meshletColours[12]; }; @@ -250,14 +253,15 @@ cbuffer DebugSampleOperation REG(b0) #define RESTYPE_DEPTH_STENCIL_MS 0x7 #define RESTYPE_TEX2D_MS 0x9 -// first few match SolidShade enum +// first few match Visualisation enum #define MESHDISPLAY_SOLID 0x1 #define MESHDISPLAY_FACELIT 0x2 #define MESHDISPLAY_SECONDARY 0x3 -#define MESHDISPLAY_MESHLET 0x4 +#define MESHDISPLAY_EXPLODE 0x4 +#define MESHDISPLAY_MESHLET 0x5 // extra values below -#define MESHDISPLAY_SECONDARY_ALPHA 0x5 +#define MESHDISPLAY_SECONDARY_ALPHA 0x6 #define MAX_NUM_MESHLETS (512 * 1024) diff --git a/renderdoc/data/hlsl/mesh.hlsl b/renderdoc/data/hlsl/mesh.hlsl index 9da7b5def2..19eba41af7 100644 --- a/renderdoc/data/hlsl/mesh.hlsl +++ b/renderdoc/data/hlsl/mesh.hlsl @@ -37,6 +37,70 @@ struct meshA2V float4 secondary : sec; }; +// This function is mostly duplicated between 'mesh.hlsl' and 'mesh.vert'. +// Without a convenient shared common source, changes to one should be +// reflected in the other. +void vtxExploder(in uint vtxID, inout float3 pos, inout float3 secondary) +{ + if(exploderScale > 0.0f) + { + float nonLinearVtxExplodeScale = + 4.0f * exploderScale * vtxExploderSNorm * vtxExploderSNorm * vtxExploderSNorm; + + // A vertex might be coincident with our 'exploderCentre' so that, when normalized, + // can give us INFs/NaNs that, even if multiplied by a zero 'exploderScale', can + // leave us with bad numbers (as seems to be the case with glsl/vulkan, but not hlsl). + // Still, we should make this case safe for when we have a non-zero 'exploderScale' - + float3 offset = pos - exploderCentre; + float offsetDistSquared = dot(offset, offset); + float3 safeExplodeDir = offset * rsqrt(max(offsetDistSquared, FLT_EPSILON)); + + float displacement = + nonLinearVtxExplodeScale * ((float((vtxID >> 1u) & 0xfu) / 15.0f) * 1.5f - 0.5f); + pos += (safeExplodeDir * displacement); + + // For the exploder visualisation, colour verts based on vertex ID, which we + // store in secondary. + // + // Interpolate a colour gradient from 0.0 to 1.0 and back to 0.0 for vertex IDs + // 0 to 16 to 32 respectively - + // 1 - | .`. + // | .` `. + // | .` | `. + // 0.5-| .` `. + // | .` | `. . + // |.` `. .` + // 0.0-+-----------+-----------+--- vtx IDs + // 0 16 32 + + float vtxIDMod32Div16 = (float)(vtxID % 32u) / 16.0f; // 0: 0.0 16: 1.0 31: 1.94 + float descending = floor(vtxIDMod32Div16); // 0..15: 0.0 16..31: 1.0 + float gradientVal = abs(vtxIDMod32Div16 - (2.0f * descending)); // 0.0..1.0 + + // Use a hopefully fairly intuitive temperature gradient scheme to help visualise + // contiguous/nearby sequences of vertices, which should also show up breaks in + // colour where verts aren't shared between adjacent primitives. + const float3 gradientColours[5] = { + float3(0.004f, 0.002f, 0.025f), // 0.0..0.25: Dark blue + float3(0.305f, 0.001f, 0.337f), // 0.25..0.5: Purple + float3(0.665f, 0.033f, 0.133f), // 0.5..0.75: Purple orange + float3(1.000f, 0.468f, 0.000f), // 0.75..1.0: Orange + float3(1.000f, 1.000f, 1.000f) // 1.0: White + }; + + uint gradientSectionStartIdx = (uint)(gradientVal * 4.0f); + uint gradientSectionEndIdx = min(gradientSectionStartIdx + 1u, 4u); + float3 gradSectionStartCol = gradientColours[gradientSectionStartIdx]; + float3 gradSectionEndCol = gradientColours[gradientSectionEndIdx]; + + float sectionLerp = gradientVal - (float)gradientSectionStartIdx * 0.25f; + float3 gradCol = lerp(gradientColours[gradientSectionStartIdx], + gradientColours[gradientSectionEndIdx], sectionLerp); + + secondary = gradCol; + } +} + StructuredBuffer meshletSizesBuf : register(t0); float4 unpackUnorm4x8(uint value) @@ -118,6 +182,7 @@ meshV2F RENDERDOC_MeshVS(meshA2V IN, uint vid : SV_VertexID) float2(1.0f, 1.0f)}; float4 pos = IN.pos; + vtxExploder(vid, pos.xyz, IN.secondary.xyz); if(homogenousInput == 0u) { @@ -214,6 +279,12 @@ float4 RENDERDOC_MeshPS(meshV2F IN) return float4(MeshColour.xyz * abs(dot(lightDir, IN.norm)), 1); } + else if(type == MESHDISPLAY_EXPLODE) + { + float3 lightDir = normalize(float3(0, -0.3f, -1)); + + return float4(IN.secondary.xyz * abs(dot(lightDir, IN.norm)), 1); + } else // if(type == MESHDISPLAY_SOLID) return float4(MeshColour.xyz, 1); } diff --git a/renderdoc/driver/d3d11/d3d11_rendermesh.cpp b/renderdoc/driver/d3d11/d3d11_rendermesh.cpp index 1e6085f4aa..9e0c63562d 100644 --- a/renderdoc/driver/d3d11/d3d11_rendermesh.cpp +++ b/renderdoc/driver/d3d11/d3d11_rendermesh.cpp @@ -33,6 +33,19 @@ #include "data/hlsl/hlsl_cbuffers.h" +static uint32_t VisModeToMeshDisplayFormat(const MeshDisplay &cfg) +{ + switch(cfg.visualisationMode) + { + default: return (uint32_t)cfg.visualisationMode; + case Visualisation::Secondary: + return cfg.second.showAlpha ? MESHDISPLAY_SECONDARY_ALPHA : MESHDISPLAY_SECONDARY; + case Visualisation::Meshlet: + RDCERR("D3D11 does not support meshlet rendering"); + return MESHDISPLAY_SOLID; + } +} + void D3D11Replay::RenderMesh(uint32_t eventId, const rdcarray &secondaryDraws, const MeshDisplay &cfg) { @@ -58,6 +71,12 @@ void D3D11Replay::RenderMesh(uint32_t eventId, const rdcarray &secon vertexData.ModelViewProj = projMat.Mul(camMat.Mul(axisMapMat)); vertexData.SpriteSize = Vec2f(); vertexData.homogenousInput = cfg.position.unproject; + vertexData.vtxExploderSNorm = cfg.vtxExploderSliderSNorm; + vertexData.exploderCentre = + Vec3f((cfg.minBounds.x + cfg.maxBounds.x) * 0.5f, (cfg.minBounds.y + cfg.maxBounds.y) * 0.5f, + (cfg.minBounds.z + cfg.maxBounds.z) * 0.5f); + vertexData.exploderScale = + (cfg.visualisationMode == Visualisation::Explode) ? cfg.exploderScale : 0.0f; Vec4f col(0.0f, 0.0f, 0.0f, 1.0f); ID3D11Buffer *psCBuf = GetDebugManager()->MakeCBuffer(&col, sizeof(col)); @@ -260,27 +279,21 @@ void D3D11Replay::RenderMesh(uint32_t eventId, const rdcarray &secon m_pImmediateContext->IASetIndexBuffer(NULL, DXGI_FORMAT_UNKNOWN, NULL); // draw solid shaded mode - if(cfg.solidShadeMode != SolidShade::NoSolid && cfg.position.topology < Topology::PatchList_1CPs) + if(cfg.visualisationMode != Visualisation::NoSolid && + cfg.position.topology < Topology::PatchList_1CPs) { m_pImmediateContext->RSSetState(m_General.RasterState); m_pImmediateContext->IASetPrimitiveTopology(topo); - pixelData.MeshDisplayFormat = (int)cfg.solidShadeMode; - if(cfg.solidShadeMode == SolidShade::Meshlet) - { - RDCERR("D3D11 does not support mesh rendering"); - pixelData.MeshDisplayFormat = (int)SolidShade::Solid; - } - - if(cfg.solidShadeMode == SolidShade::Secondary && cfg.second.showAlpha) - pixelData.MeshDisplayFormat = MESHDISPLAY_SECONDARY_ALPHA; + pixelData.MeshDisplayFormat = VisModeToMeshDisplayFormat(cfg); pixelData.MeshColour = Vec3f(0.8f, 0.8f, 0.0f); GetDebugManager()->FillCBuffer(psCBuf, &pixelData, sizeof(pixelData)); m_pImmediateContext->PSSetConstantBuffers(0, 1, &psCBuf); - if(cfg.solidShadeMode == SolidShade::Lit) + if(cfg.visualisationMode == Visualisation::Lit || + cfg.visualisationMode == Visualisation::Explode) { MeshGeometryCBuffer geomData; @@ -298,12 +311,13 @@ void D3D11Replay::RenderMesh(uint32_t eventId, const rdcarray &secon else m_pImmediateContext->Draw(cfg.position.numIndices, 0); - if(cfg.solidShadeMode == SolidShade::Lit) + if(cfg.visualisationMode == Visualisation::Lit || + cfg.visualisationMode == Visualisation::Explode) m_pImmediateContext->GSSetShader(NULL, NULL, 0); } // draw wireframe mode - if(cfg.solidShadeMode == SolidShade::NoSolid || cfg.wireframeDraw || + if(cfg.visualisationMode == Visualisation::NoSolid || cfg.wireframeDraw || cfg.position.topology >= Topology::PatchList_1CPs) { m_pImmediateContext->RSSetState(m_MeshRender.WireframeRasterState); @@ -335,6 +349,8 @@ void D3D11Replay::RenderMesh(uint32_t eventId, const rdcarray &secon // set up state for drawing helpers { vertexData.ModelViewProj = projMat.Mul(camMat.Mul(axisMapMat)); + vertexData.vtxExploderSNorm = 0.0f; + vertexData.exploderScale = 0.0f; GetDebugManager()->FillCBuffer(vsCBuf, &vertexData, sizeof(vertexData)); m_pImmediateContext->RSSetState(m_MeshRender.SolidRasterState); diff --git a/renderdoc/driver/d3d12/d3d12_rendermesh.cpp b/renderdoc/driver/d3d12/d3d12_rendermesh.cpp index fb27983dd8..ee0bd99029 100644 --- a/renderdoc/driver/d3d12/d3d12_rendermesh.cpp +++ b/renderdoc/driver/d3d12/d3d12_rendermesh.cpp @@ -38,6 +38,16 @@ RDOC_EXTERN_CONFIG(bool, D3D12_Debug_SingleSubmitFlushing); +static uint32_t VisModeToMeshDisplayFormat(const MeshDisplay &cfg) +{ + switch(cfg.visualisationMode) + { + default: return (uint32_t)cfg.visualisationMode; + case Visualisation::Secondary: + return cfg.second.showAlpha ? MESHDISPLAY_SECONDARY_ALPHA : MESHDISPLAY_SECONDARY; + } +} + MeshDisplayPipelines D3D12DebugManager::CacheMeshDisplayPipelines(const MeshFormat &primary, const MeshFormat &secondary) { @@ -93,7 +103,7 @@ MeshDisplayPipelines D3D12DebugManager::CacheMeshDisplayPipelines(const MeshForm MeshDisplayPipelines &cache = m_CachedMeshPipelines[key]; - if(cache.pipes[(uint32_t)SolidShade::NoSolid] != NULL) + if(cache.pipes[(uint32_t)Visualisation::NoSolid] != NULL) return cache; cache.rootsig = m_MeshRootSig; @@ -276,6 +286,12 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon vertexData.ModelViewProj = projMat.Mul(camMat.Mul(axisMapMat)); vertexData.SpriteSize = Vec2f(); vertexData.homogenousInput = cfg.position.unproject; + vertexData.vtxExploderSNorm = cfg.vtxExploderSliderSNorm; + vertexData.exploderCentre = + Vec3f((cfg.minBounds.x + cfg.maxBounds.x) * 0.5f, (cfg.minBounds.y + cfg.maxBounds.y) * 0.5f, + (cfg.minBounds.z + cfg.maxBounds.z) * 0.5f); + vertexData.exploderScale = + (cfg.visualisationMode == Visualisation::Explode) ? cfg.exploderScale : 0.0f; vertexData.vertMeshDisplayFormat = MESHDISPLAY_SOLID; MeshPixelCBuffer pixelData; @@ -425,13 +441,13 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_POINTLIST); } - SolidShade solidShadeMode = cfg.solidShadeMode; - // can't support secondary shading without a buffer - no pipeline will have been created - if(solidShadeMode == SolidShade::Secondary && cfg.second.vertexResourceId == ResourceId()) - solidShadeMode = SolidShade::NoSolid; + const Visualisation finalVisualisation = (cfg.visualisationMode == Visualisation::Secondary && + cfg.second.vertexResourceId == ResourceId()) + ? Visualisation::NoSolid + : cfg.visualisationMode; - if(solidShadeMode == SolidShade::Secondary) + if(finalVisualisation == Visualisation::Secondary) { D3D12MarkerRegion::Set(list, "Secondary"); @@ -454,28 +470,32 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon } // solid render - if(solidShadeMode != SolidShade::NoSolid && cfg.position.topology < Topology::PatchList) + if(finalVisualisation != Visualisation::NoSolid && cfg.position.topology < Topology::PatchList) { D3D12MarkerRegion region(list, "Solid render"); ID3D12PipelineState *pipe = NULL; - switch(solidShadeMode) + switch(finalVisualisation) { default: - case SolidShade::Solid: pipe = cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]; break; - case SolidShade::Lit: + case Visualisation::Solid: pipe = cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]; break; + case Visualisation::Lit: + case Visualisation::Explode: pipe = cache.pipes[MeshDisplayPipelines::ePipe_Lit]; // point list topologies don't have lighting obvious, just render them as solid if(!pipe) pipe = cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]; break; - case SolidShade::Secondary: pipe = cache.pipes[MeshDisplayPipelines::ePipe_Secondary]; break; - case SolidShade::Meshlet: pipe = cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]; break; + case Visualisation::Secondary: + pipe = cache.pipes[MeshDisplayPipelines::ePipe_Secondary]; + break; + case Visualisation::Meshlet: + pipe = cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]; + break; } - pixelData.MeshDisplayFormat = (int)cfg.solidShadeMode; - if(cfg.solidShadeMode == SolidShade::Secondary && cfg.second.showAlpha) - pixelData.MeshDisplayFormat = MESHDISPLAY_SECONDARY_ALPHA; + pixelData.MeshDisplayFormat = VisModeToMeshDisplayFormat(cfg); + pixelData.MeshColour = Vec3f(0.8f, 0.8f, 0.0f); list->SetPipelineState(pipe); @@ -483,7 +503,7 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon size_t numMeshlets = RDCMIN(cfg.position.meshletSizes.size(), (size_t)MAX_NUM_MESHLETS); - if(cfg.solidShadeMode == SolidShade::Meshlet) + if(finalVisualisation == Visualisation::Meshlet) { vertexData.meshletCount = (uint32_t)numMeshlets; vertexData.meshletOffset = (uint32_t)cfg.position.meshletOffset; @@ -496,7 +516,7 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon list->SetGraphicsRootConstantBufferView(0, vsCBSolid); - if(solidShadeMode == SolidShade::Lit) + if(finalVisualisation == Visualisation::Lit || finalVisualisation == Visualisation::Explode) { MeshGeometryCBuffer geomData; geomData.InvProj = projMat.Inverse(); @@ -540,7 +560,7 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon } // wireframe render - if(solidShadeMode == SolidShade::NoSolid || cfg.wireframeDraw || + if(finalVisualisation == Visualisation::NoSolid || cfg.wireframeDraw || cfg.position.topology >= Topology::PatchList) { D3D12MarkerRegion region(list, "Wireframe render"); @@ -603,6 +623,8 @@ void D3D12Replay::RenderMesh(uint32_t eventId, const rdcarray &secon pixelData.MeshDisplayFormat = MESHDISPLAY_SOLID; vertexData.homogenousInput = 0U; + vertexData.vtxExploderSNorm = 0.0f; + vertexData.exploderScale = 0.0f; vsCB = GetDebugManager()->UploadConstants(&vertexData, sizeof(vertexData)); diff --git a/renderdoc/driver/gl/gl_rendermesh.cpp b/renderdoc/driver/gl/gl_rendermesh.cpp index ed4efcb024..a017fdfc61 100644 --- a/renderdoc/driver/gl/gl_rendermesh.cpp +++ b/renderdoc/driver/gl/gl_rendermesh.cpp @@ -33,6 +33,16 @@ #define OPENGL 1 #include "data/glsl/glsl_ubos_cpp.h" +static int VisModeToMeshDisplayFormat(const MeshDisplay &cfg) +{ + switch(cfg.visualisationMode) + { + default: return (int)cfg.visualisationMode; + case Visualisation::Secondary: + return cfg.second.showAlpha ? MESHDISPLAY_SECONDARY_ALPHA : MESHDISPLAY_SECONDARY; + } +} + void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondaryDraws, const MeshDisplay &cfg) { @@ -113,6 +123,12 @@ void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondar uboParams.mvp = ModelViewProj; uboParams.homogenousInput = cfg.position.unproject; uboParams.pointSpriteSize = Vec2f(0.0f, 0.0f); + uboParams.vtxExploderSNorm = cfg.vtxExploderSliderSNorm; + uboParams.exploderScale = + (cfg.visualisationMode == Visualisation::Explode) ? cfg.exploderScale : 0.0f; + uboParams.exploderCentre = + Vec3f((cfg.minBounds.x + cfg.maxBounds.x) * 0.5f, (cfg.minBounds.y + cfg.maxBounds.y) * 0.5f, + (cfg.minBounds.z + cfg.maxBounds.z) * 0.5f); if(!secondaryDraws.empty()) { @@ -333,13 +349,15 @@ void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondar drv.glEnable(eGL_DEPTH_TEST); // solid render - if(cfg.solidShadeMode != SolidShade::NoSolid && topo != eGL_PATCHES) + if(cfg.visualisationMode != Visualisation::NoSolid && topo != eGL_PATCHES) { drv.glDepthFunc(eGL_LESS); GLuint solidProg = prog; - if(cfg.solidShadeMode == SolidShade::Lit && DebugData.meshgsProg[0]) + if((cfg.visualisationMode == Visualisation::Lit || + cfg.visualisationMode == Visualisation::Explode) && + DebugData.meshgsProg[0]) { // pick program with GS for per-face lighting solidProg = DebugData.meshgsProg[progidx]; @@ -363,18 +381,11 @@ void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondar return; } - soliddata->mvp = ModelViewProj; - soliddata->pointSpriteSize = Vec2f(0.0f, 0.0f); - soliddata->homogenousInput = cfg.position.unproject; - - soliddata->color = Vec4f(0.8f, 0.8f, 0.0f, 1.0f); + uboParams.color = Vec4f(0.8f, 0.8f, 0.0f, 1.0f); + uboParams.displayFormat = VisModeToMeshDisplayFormat(cfg); + *soliddata = uboParams; - uint32_t OutputDisplayFormat = (uint32_t)cfg.solidShadeMode; - if(cfg.solidShadeMode == SolidShade::Secondary && cfg.second.showAlpha) - OutputDisplayFormat = MESHDISPLAY_SECONDARY_ALPHA; - soliddata->displayFormat = OutputDisplayFormat; - - if(cfg.solidShadeMode == SolidShade::Lit) + if(cfg.visualisationMode == Visualisation::Lit || cfg.visualisationMode == Visualisation::Explode) soliddata->invProj = projMat.Inverse(); drv.glUnmapBuffer(eGL_UNIFORM_BUFFER); @@ -417,14 +428,14 @@ void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondar drv.glDepthFunc(eGL_ALWAYS); + uboParams.displayFormat = MESHDISPLAY_SOLID; + // wireframe render - if(cfg.solidShadeMode == SolidShade::NoSolid || cfg.wireframeDraw || topo == eGL_PATCHES) + if(cfg.visualisationMode == Visualisation::NoSolid || cfg.wireframeDraw || topo == eGL_PATCHES) { uboParams.color = Vec4f(cfg.position.meshColor.x, cfg.position.meshColor.y, cfg.position.meshColor.z, cfg.position.meshColor.w); - uboParams.displayFormat = MESHDISPLAY_SOLID; - if(!IsGLES) drv.glPolygonMode(eGL_FRONT_AND_BACK, eGL_LINE); @@ -469,6 +480,10 @@ void GLReplay::RenderMesh(uint32_t eventId, const rdcarray &secondar // helpers always use basic float-input program drv.glUseProgram(DebugData.meshProg[0]); + uboParams.vtxExploderSNorm = 0.0f; + uboParams.exploderScale = 0.0f; + uboParams.exploderCentre = Vec3f(); + if(cfg.showBBox) { Vec4f a = Vec4f(cfg.minBounds.x, cfg.minBounds.y, cfg.minBounds.z, cfg.minBounds.w); diff --git a/renderdoc/driver/vulkan/vk_overlay.cpp b/renderdoc/driver/vulkan/vk_overlay.cpp index 0eddfe3872..278c2f2756 100644 --- a/renderdoc/driver/vulkan/vk_overlay.cpp +++ b/renderdoc/driver/vulkan/vk_overlay.cpp @@ -2732,7 +2732,9 @@ ResourceId VulkanReplay::RenderOverlay(ResourceId texid, FloatVector clearCol, D data->displayFormat = 0; data->rawoutput = 1; data->flipY = 0; - data->padding = Vec2f(); + data->vtxExploderSNorm = 0.0f; + data->exploderScale = 0.0f; + data->exploderCentre = Vec3f(); m_MeshRender.UBO.Unmap(); uint32_t viewOffs = 0; diff --git a/renderdoc/driver/vulkan/vk_rendermesh.cpp b/renderdoc/driver/vulkan/vk_rendermesh.cpp index 7df16e4013..1c18077ad0 100644 --- a/renderdoc/driver/vulkan/vk_rendermesh.cpp +++ b/renderdoc/driver/vulkan/vk_rendermesh.cpp @@ -37,6 +37,16 @@ RDOC_EXTERN_CONFIG(bool, Vulkan_Debug_SingleSubmitFlushing); +static int VisModeToMeshDisplayFormat(Visualisation vis, bool showAlpha) +{ + switch(vis) + { + default: return (int)vis; + case Visualisation::Secondary: + return showAlpha ? MESHDISPLAY_SECONDARY_ALPHA : MESHDISPLAY_SECONDARY; + } +} + VKMeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(VkPipelineLayout pipeLayout, const MeshFormat &primary, const MeshFormat &secondary) @@ -138,7 +148,7 @@ VKMeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(VkPipelineL VKMeshDisplayPipelines &cache = m_CachedMeshPipelines[key]; - if(cache.pipes[(uint32_t)SolidShade::NoSolid] != VK_NULL_HANDLE) + if(cache.pipes[(uint32_t)Visualisation::NoSolid] != VK_NULL_HANDLE) return cache; const VkDevDispatchTable *vt = ObjDisp(m_Device); @@ -526,6 +536,25 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco ModelViewProj = projMat.Mul(camMat.Mul(guessProjInv)); } + // can't support secondary shading without a buffer - no pipeline will have been created + const Visualisation finalVisualisation = (cfg.visualisationMode == Visualisation::Secondary && + cfg.second.vertexResourceId == ResourceId()) + ? Visualisation::NoSolid + : cfg.visualisationMode; + + MeshUBOData meshUniforms = {}; + meshUniforms.mvp = ModelViewProj; + meshUniforms.displayFormat = MESHDISPLAY_SOLID; + meshUniforms.homogenousInput = cfg.position.unproject; + meshUniforms.pointSpriteSize = Vec2f(0.0f, 0.0f); + meshUniforms.rawoutput = 0; + meshUniforms.vtxExploderSNorm = cfg.vtxExploderSliderSNorm; + meshUniforms.exploderScale = + (finalVisualisation == Visualisation::Explode) ? cfg.exploderScale : 0.0f; + meshUniforms.exploderCentre = + Vec3f((cfg.minBounds.x + cfg.maxBounds.x) * 0.5f, (cfg.minBounds.y + cfg.maxBounds.y) * 0.5f, + (cfg.minBounds.z + cfg.maxBounds.z) * 0.5f); + uint32_t dynOffs[2] = {}; if(!secondaryDraws.empty()) @@ -543,13 +572,10 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(fmt.meshColor.x, fmt.meshColor.y, fmt.meshColor.z, fmt.meshColor.w); - data->homogenousInput = cfg.position.unproject; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->displayFormat = MESHDISPLAY_SOLID; - data->rawoutput = 0; - data->flipY = (cfg.position.flipY == fmt.flipY) ? 0 : 1; + meshUniforms.color = + Vec4f(fmt.meshColor.x, fmt.meshColor.y, fmt.meshColor.z, fmt.meshColor.w); + meshUniforms.flipY = (cfg.position.flipY == fmt.flipY) ? 0 : 1; + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -663,13 +689,7 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco vt->CmdBindVertexBuffers(Unwrap(cmd), 0, 1, UnwrapPtr(vb), &offs); } - SolidShade solidShadeMode = cfg.solidShadeMode; - - // can't support secondary shading without a buffer - no pipeline will have been created - if(solidShadeMode == SolidShade::Secondary && cfg.second.vertexResourceId == ResourceId()) - solidShadeMode = SolidShade::NoSolid; - - if(solidShadeMode == SolidShade::Secondary) + if(finalVisualisation == Visualisation::Secondary) { VkBuffer vb = m_pDriver->GetResourceManager()->GetCurrentHandle(cfg.second.vertexResourceId); @@ -687,53 +707,49 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco } // solid render - if(solidShadeMode != SolidShade::NoSolid && cfg.position.topology < Topology::PatchList) + if(finalVisualisation != Visualisation::NoSolid && cfg.position.topology < Topology::PatchList) { VkPipeline pipe = VK_NULL_HANDLE; - switch(solidShadeMode) + switch(finalVisualisation) { default: - case SolidShade::Solid: pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; break; - case SolidShade::Lit: + case Visualisation::Solid: + pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; + break; + case Visualisation::Lit: + case Visualisation::Explode: pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_Lit]; // point list topologies don't have lighting obvious, just render them as solid + // Also, can't support lit rendering without the pipeline - maybe geometry shader wasn't supported. if(pipe == VK_NULL_HANDLE) pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; break; - case SolidShade::Secondary: + case Visualisation::Secondary: pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_Secondary]; break; - case SolidShade::Meshlet: pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; break; + case Visualisation::Meshlet: + pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; + break; } - // can't support lit rendering without the pipeline - maybe geometry shader wasn't supported. - if(solidShadeMode == SolidShade::Lit && pipe == VK_NULL_HANDLE) - pipe = cache.pipes[VKMeshDisplayPipelines::ePipe_SolidDepth]; - MeshUBOData *data = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!data) return; - if(solidShadeMode == SolidShade::Lit) - data->invProj = projMat.Inverse(); + if(finalVisualisation == Visualisation::Lit || finalVisualisation == Visualisation::Explode) + meshUniforms.invProj = projMat.Inverse(); - data->mvp = ModelViewProj; - data->color = Vec4f(0.8f, 0.8f, 0.0f, 1.0f); - data->homogenousInput = cfg.position.unproject; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->displayFormat = (uint32_t)solidShadeMode; - data->rawoutput = 0; + meshUniforms.color = Vec4f(0.8f, 0.8f, 0.0f, 1.0f); + meshUniforms.displayFormat = VisModeToMeshDisplayFormat(finalVisualisation, cfg.second.showAlpha); + meshUniforms.flipY = 0; - if(solidShadeMode == SolidShade::Secondary && cfg.second.showAlpha) - data->displayFormat = MESHDISPLAY_SECONDARY_ALPHA; - - if(solidShadeMode == SolidShade::Meshlet) + if(finalVisualisation == Visualisation::Meshlet) { size_t numMeshlets = RDCMIN(cfg.position.meshletSizes.size(), (size_t)MAX_NUM_MESHLETS); uint32_t *meshletCounts = (uint32_t *)m_MeshRender.MeshletSSBO.Map( &dynOffs[1], AlignUp4(numMeshlets + 4) * sizeof(uint32_t)); - if(!data) + if(!meshletCounts) return; if(cfg.position.meshletSizes.size() > MAX_NUM_MESHLETS) @@ -751,13 +767,15 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco meshletCounts[i] = prefixCount; } - memcpy(&data->meshletColours[0].x, uniqueColors, sizeof(uniqueColors)); - RDCCOMPILE_ASSERT(sizeof(data->meshletColours) == sizeof(uniqueColors), + memcpy(&meshUniforms.meshletColours[0].x, uniqueColors, sizeof(uniqueColors)); + RDCCOMPILE_ASSERT(sizeof(meshUniforms.meshletColours) == sizeof(uniqueColors), "Unique colors array is wrongly sized"); m_MeshRender.MeshletSSBO.Unmap(); } + *data = meshUniforms; + m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, @@ -789,8 +807,10 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco } } + meshUniforms.displayFormat = MESHDISPLAY_SOLID; + // wireframe render - if(solidShadeMode == SolidShade::NoSolid || cfg.wireframeDraw || + if(finalVisualisation == Visualisation::NoSolid || cfg.wireframeDraw || cfg.position.topology >= Topology::PatchList) { Vec4f wireCol = @@ -800,12 +820,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = wireCol; - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = cfg.position.unproject; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = wireCol; + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -851,6 +867,11 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco helper.vertexByteStride = sizeof(Vec4f); + meshUniforms.homogenousInput = 0; + meshUniforms.vtxExploderSNorm = 0.0f; + meshUniforms.exploderScale = 0.0f; + meshUniforms.exploderCentre = Vec3f(); + // cache pipelines for use in drawing wireframe helpers cache = GetDebugManager()->CacheMeshDisplayPipelines(m_MeshRender.PipeLayout, helper, helper); @@ -893,12 +914,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(0.2f, 0.2f, 1.0f, 1.0f); - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = 0; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = Vec4f(0.2f, 0.2f, 1.0f, 1.0f); + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -922,12 +939,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f); - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = 0; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f); + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -945,12 +958,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = 0; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -963,12 +972,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f); - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = 0; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f); + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -988,12 +993,8 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco if(!data) return; - data->mvp = ModelViewProj; - data->color = Vec4f(1.0f, 1.0f, 1.0f, 1.0f); - data->displayFormat = (uint32_t)SolidShade::Solid; - data->homogenousInput = 0; - data->pointSpriteSize = Vec2f(0.0f, 0.0f); - data->rawoutput = 0; + meshUniforms.color = Vec4f(1.0f, 1.0f, 1.0f, 1.0f); + *data = meshUniforms; m_MeshRender.UBO.Unmap(); @@ -1088,17 +1089,14 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco else ModelViewProj = projMat.Mul(camMat.Mul(axisMapMat)); - MeshUBOData uniforms = {}; - uniforms.mvp = ModelViewProj; - uniforms.color = Vec4f(1.0f, 1.0f, 1.0f, 1.0f); - uniforms.displayFormat = (uint32_t)SolidShade::Solid; - uniforms.homogenousInput = cfg.position.unproject; - uniforms.pointSpriteSize = Vec2f(0.0f, 0.0f); + meshUniforms.mvp = ModelViewProj; + meshUniforms.color = Vec4f(1.0f, 1.0f, 1.0f, 1.0f); + meshUniforms.homogenousInput = cfg.position.unproject; MeshUBOData *ubodata = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!ubodata) return; - *ubodata = uniforms; + *ubodata = meshUniforms; m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, @@ -1112,12 +1110,12 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco // render primitives // Draw active primitive (red) - uniforms.color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f); + meshUniforms.color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f); // poke the color (this would be a good candidate for a push constant) ubodata = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!ubodata) return; - *ubodata = uniforms; + *ubodata = meshUniforms; m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(m_MeshRender.PipeLayout), 0, 1, @@ -1140,12 +1138,12 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco } // Draw adjacent primitives (green) - uniforms.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); + meshUniforms.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); // poke the color (this would be a good candidate for a push constant) ubodata = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!ubodata) return; - *ubodata = uniforms; + *ubodata = meshUniforms; m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(m_MeshRender.PipeLayout), 0, 1, @@ -1173,15 +1171,15 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco float scale = 800.0f / float(m_DebugHeight); float asp = float(m_DebugWidth) / float(m_DebugHeight); - uniforms.pointSpriteSize = Vec2f(scale / asp, scale); + meshUniforms.pointSpriteSize = Vec2f(scale / asp, scale); // Draw active vertex (blue) - uniforms.color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f); + meshUniforms.color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f); // poke the color (this would be a good candidate for a push constant) ubodata = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!ubodata) return; - *ubodata = uniforms; + *ubodata = meshUniforms; m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(m_MeshRender.PipeLayout), 0, 1, @@ -1221,12 +1219,12 @@ void VulkanReplay::RenderMesh(uint32_t eventId, const rdcarray &seco } // Draw inactive vertices (green) - uniforms.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); + meshUniforms.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f); // poke the color (this would be a good candidate for a push constant) ubodata = (MeshUBOData *)m_MeshRender.UBO.Map(&dynOffs[0]); if(!ubodata) return; - *ubodata = uniforms; + *ubodata = meshUniforms; m_MeshRender.UBO.Unmap(); vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(m_MeshRender.PipeLayout), 0, 1, diff --git a/util/test/rdtest/shared/Mesh_Zoo.py b/util/test/rdtest/shared/Mesh_Zoo.py index 7f194602c2..1fbfb3a43f 100644 --- a/util/test/rdtest/shared/Mesh_Zoo.py +++ b/util/test/rdtest/shared/Mesh_Zoo.py @@ -118,7 +118,7 @@ def check_capture(self, capture_filename: str, controller: rd.ReplayController): rdtest.log.success("Base rendering is as expected") - self.cfg.solidShadeMode = rd.SolidShade.Secondary + self.cfg.visualisationMode = rd.Visualisation.Secondary self.cfg.wireframeDraw = False # allow for blending with white for the frustum @@ -244,7 +244,7 @@ def check_capture(self, capture_filename: str, controller: rd.ReplayController): rdtest.log.success("Rendering of float2 color secondary in instance 1 is as expected") - self.cfg.solidShadeMode = rd.SolidShade.NoSolid + self.cfg.visualisationMode = rd.Visualisation.NoSolid self.cfg.showAllInstances = True self.cache_output() @@ -370,10 +370,10 @@ def check_capture(self, capture_filename: str, controller: rd.ReplayController): rdtest.log.success("Point picking is as expected") self.cfg.highlightVert = rd.MeshDisplay.NoHighlight - self.cfg.solidShadeMode = rd.SolidShade.Solid + self.cfg.visualisationMode = rd.Visualisation.Solid self.cache_output() - self.cfg.solidShadeMode = rd.SolidShade.Lit + self.cfg.visualisationMode = rd.Visualisation.Lit self.cache_output() rdtest.log.success("Point solid and lit rendering works as expected") @@ -381,7 +381,7 @@ def check_capture(self, capture_filename: str, controller: rd.ReplayController): self.controller.SetFrameEvent(self.find_action("Lines").next.eventId, False) self.cache_output() - self.cfg.solidShadeMode = rd.SolidShade.Lit + self.cfg.visualisationMode = rd.Visualisation.Lit self.cache_output() rdtest.log.success("Lines solid and lit rendering works as expected")