From ccccb9a1a9a4f8cb1aebc31a541b993f69e7cef6 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:13:35 -0700 Subject: [PATCH 01/18] Create pixel history shader out 2 variable Currently, no backend sets this, and it is not displayed. --- renderdoc/api/replay/data_types.h | 19 +++++++++++++------ renderdoc/driver/d3d11/d3d11_pixelhistory.cpp | 2 ++ renderdoc/driver/gl/gl_pixelhistory.cpp | 2 ++ renderdoc/driver/vulkan/vk_pixelhistory.cpp | 4 ++++ renderdoc/replay/renderdoc_serialise.inl | 3 ++- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 3dbb1f1b7f..4ad92db0d9 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -2100,12 +2100,12 @@ struct PixelModification { return eventId == o.eventId && directShaderWrite == o.directShaderWrite && unboundPS == o.unboundPS && fragIndex == o.fragIndex && primitiveID == o.primitiveID && - preMod == o.preMod && shaderOut == o.shaderOut && postMod == o.postMod && - sampleMasked == o.sampleMasked && backfaceCulled == o.backfaceCulled && - depthClipped == o.depthClipped && depthBoundsFailed == o.depthBoundsFailed && - viewClipped == o.viewClipped && scissorClipped == o.scissorClipped && - shaderDiscarded == o.shaderDiscarded && depthTestFailed == o.depthTestFailed && - stencilTestFailed == o.stencilTestFailed; + preMod == o.preMod && shaderOut == o.shaderOut && shaderOutDualSrc == o.shaderOutDualSrc && + postMod == o.postMod && sampleMasked == o.sampleMasked && + backfaceCulled == o.backfaceCulled && depthClipped == o.depthClipped && + depthBoundsFailed == o.depthBoundsFailed && viewClipped == o.viewClipped && + scissorClipped == o.scissorClipped && shaderDiscarded == o.shaderDiscarded && + depthTestFailed == o.depthTestFailed && stencilTestFailed == o.stencilTestFailed; } bool operator<(const PixelModification &o) const { @@ -2123,6 +2123,8 @@ struct PixelModification return preMod < o.preMod; if(!(shaderOut == o.shaderOut)) return shaderOut < o.shaderOut; + if(!(shaderOutDualSrc == o.shaderOutDualSrc)) + return shaderOutDualSrc < o.shaderOutDualSrc; if(!(postMod == o.postMod)) return postMod < o.postMod; if(!(sampleMasked == o.sampleMasked)) @@ -2175,6 +2177,11 @@ pixel. :type: ModificationValue )"); ModificationValue shaderOut; + DOCUMENT(R"(The value that this fragment wrote from the pixel shader to the second output. + +:type: ModificationValue +)"); + ModificationValue shaderOutDualSrc; DOCUMENT(R"(The value of the texture after this fragment ran. :type: ModificationValue diff --git a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp index 77f436b73f..4cb6c49d8c 100644 --- a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp +++ b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp @@ -1940,6 +1940,7 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event // data[3].x (depth) unused // fragments writing to the pixel in this event with original shader mod.shaderOut.col.intValue[1] = int32_t(data[3].y); + mod.shaderOutDualSrc.SetInvalid(); } } @@ -2316,6 +2317,7 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event byte *data = shadoutStoreData + sizeof(Vec4f) * pixstoreStride * offsettedSlot; memcpy(&history[h].shaderOut.col.uintValue[0], data, 4 * sizeof(float)); + history[h].shaderOutDualSrc.SetInvalid(); // primitive ID is in the next slot after that memcpy(&history[h].primitiveID, data + sizeof(Vec4f), sizeof(uint32_t)); diff --git a/renderdoc/driver/gl/gl_pixelhistory.cpp b/renderdoc/driver/gl/gl_pixelhistory.cpp index 515608be3e..2dfe4c6906 100644 --- a/renderdoc/driver/gl/gl_pixelhistory.cpp +++ b/renderdoc/driver/gl/gl_pixelhistory.cpp @@ -1303,6 +1303,7 @@ std::map QueryNumFragmentsByEvent( numFragments = history[i].shaderOut.stencil; history[i].shaderOut.stencil = history[i].postMod.stencil; } + history[i].shaderOutDualSrc.SetInvalid(); eventFragments.emplace(modEvents[i].eventId, numFragments); @@ -1587,6 +1588,7 @@ void QueryShaderOutPerFragment(WrappedOpenGL *driver, GLReplay *replay, int(historyIndex - history.begin())); historyIndex->shaderOut.stencil = oldStencil; } + historyIndex->shaderOutDualSrc.SetInvalid(); historyIndex++; } diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index 500ef7b28a..db81a43490 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -3972,6 +3972,7 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even mod.preMod.SetInvalid(); mod.postMod.SetInvalid(); mod.shaderOut.SetInvalid(); + mod.shaderOutDualSrc.SetInvalid(); h++; continue; } @@ -3999,6 +4000,7 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[4]); mod.shaderOut.col.intValue[0] = frags; mod.shaderOut.col.intValue[1] = fragsClipped; + mod.shaderOutDualSrc.SetInvalid(); bool someFragsClipped = (fragsClipped < frags); mod.primitiveID = someFragsClipped; // Draws in secondary command buffers will fail this check, @@ -4145,6 +4147,8 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even { history[h].preMod = history[h - 1].postMod; } + + history[h].shaderOutDualSrc.SetInvalid(); } // check the depth value between premod/shaderout against the known test if we have valid diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 058149e3bb..ec8f3e060b 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -893,6 +893,7 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(preMod); SERIALISE_MEMBER(shaderOut); + SERIALISE_MEMBER(shaderOutDualSrc); SERIALISE_MEMBER(postMod); SERIALISE_MEMBER(sampleMasked); @@ -906,7 +907,7 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(stencilTestFailed); SERIALISE_MEMBER(predicationSkipped); - SIZE_CHECK(100); + SIZE_CHECK(124); } template From 8104d2687bd788909be547e64be59972da3aa13a Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:21:34 -0700 Subject: [PATCH 02/18] Create pixel history blend source and blend dest variables Currently, no backend sets them, and they are not displayed. --- renderdoc/api/replay/data_types.h | 19 +++++++++++++++++-- renderdoc/driver/d3d11/d3d11_pixelhistory.cpp | 4 ++++ renderdoc/driver/gl/gl_pixelhistory.cpp | 4 ++++ renderdoc/driver/vulkan/vk_pixelhistory.cpp | 7 +++++++ renderdoc/replay/renderdoc_serialise.inl | 4 +++- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 4ad92db0d9..2423a486d1 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -2100,8 +2100,9 @@ struct PixelModification { return eventId == o.eventId && directShaderWrite == o.directShaderWrite && unboundPS == o.unboundPS && fragIndex == o.fragIndex && primitiveID == o.primitiveID && - preMod == o.preMod && shaderOut == o.shaderOut && shaderOutDualSrc == o.shaderOutDualSrc && - postMod == o.postMod && sampleMasked == o.sampleMasked && + preMod == o.preMod && shaderOut == o.shaderOut && + shaderOutDualSrc == o.shaderOutDualSrc && blendSrc == o.blendSrc && + blendDst == o.blendDst && postMod == o.postMod && sampleMasked == o.sampleMasked && backfaceCulled == o.backfaceCulled && depthClipped == o.depthClipped && depthBoundsFailed == o.depthBoundsFailed && viewClipped == o.viewClipped && scissorClipped == o.scissorClipped && shaderDiscarded == o.shaderDiscarded && @@ -2125,6 +2126,10 @@ struct PixelModification return shaderOut < o.shaderOut; if(!(shaderOutDualSrc == o.shaderOutDualSrc)) return shaderOutDualSrc < o.shaderOutDualSrc; + if(!(blendSrc == o.blendSrc)) + return blendSrc < o.blendSrc; + if(!(blendDst == o.blendDst)) + return blendDst < o.blendDst; if(!(postMod == o.postMod)) return postMod < o.postMod; if(!(sampleMasked == o.sampleMasked)) @@ -2182,6 +2187,16 @@ pixel. :type: ModificationValue )"); ModificationValue shaderOutDualSrc; + DOCUMENT(R"(The source component in the blend equation for this fragment. + +:type: ModificationValue +)"); + ModificationValue blendSrc; + DOCUMENT(R"(The destination component in the blend equation for this fragment. + +:type: ModificationValue +)"); + ModificationValue blendDst; DOCUMENT(R"(The value of the texture after this fragment ran. :type: ModificationValue diff --git a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp index 4cb6c49d8c..efa940f10b 100644 --- a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp +++ b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp @@ -1941,6 +1941,8 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event // fragments writing to the pixel in this event with original shader mod.shaderOut.col.intValue[1] = int32_t(data[3].y); mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); } } @@ -2318,6 +2320,8 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event memcpy(&history[h].shaderOut.col.uintValue[0], data, 4 * sizeof(float)); history[h].shaderOutDualSrc.SetInvalid(); + history[h].blendSrc.SetInvalid(); + history[h].blendDst.SetInvalid(); // primitive ID is in the next slot after that memcpy(&history[h].primitiveID, data + sizeof(Vec4f), sizeof(uint32_t)); diff --git a/renderdoc/driver/gl/gl_pixelhistory.cpp b/renderdoc/driver/gl/gl_pixelhistory.cpp index 2dfe4c6906..648f81805a 100644 --- a/renderdoc/driver/gl/gl_pixelhistory.cpp +++ b/renderdoc/driver/gl/gl_pixelhistory.cpp @@ -1304,6 +1304,8 @@ std::map QueryNumFragmentsByEvent( history[i].shaderOut.stencil = history[i].postMod.stencil; } history[i].shaderOutDualSrc.SetInvalid(); + history[i].blendSrc.SetInvalid(); + history[i].blendDst.SetInvalid(); eventFragments.emplace(modEvents[i].eventId, numFragments); @@ -1589,6 +1591,8 @@ void QueryShaderOutPerFragment(WrappedOpenGL *driver, GLReplay *replay, historyIndex->shaderOut.stencil = oldStencil; } historyIndex->shaderOutDualSrc.SetInvalid(); + historyIndex->blendSrc.SetInvalid(); + historyIndex->blendDst.SetInvalid(); historyIndex++; } diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index db81a43490..dfbe367477 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -3973,6 +3973,8 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even mod.postMod.SetInvalid(); mod.shaderOut.SetInvalid(); mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); h++; continue; } @@ -4001,6 +4003,8 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even mod.shaderOut.col.intValue[0] = frags; mod.shaderOut.col.intValue[1] = fragsClipped; mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); bool someFragsClipped = (fragsClipped < frags); mod.primitiveID = someFragsClipped; // Draws in secondary command buffers will fail this check, @@ -4149,6 +4153,9 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even } history[h].shaderOutDualSrc.SetInvalid(); + + history[h].blendSrc.SetInvalid(); + history[h].blendDst.SetInvalid(); } // check the depth value between premod/shaderout against the known test if we have valid diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index ec8f3e060b..4fd593c975 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -894,6 +894,8 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(preMod); SERIALISE_MEMBER(shaderOut); SERIALISE_MEMBER(shaderOutDualSrc); + SERIALISE_MEMBER(blendSrc); + SERIALISE_MEMBER(blendDst); SERIALISE_MEMBER(postMod); SERIALISE_MEMBER(sampleMasked); @@ -907,7 +909,7 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(stencilTestFailed); SERIALISE_MEMBER(predicationSkipped); - SIZE_CHECK(124); + SIZE_CHECK(172); } template From 5df9762453adb04c79f021bf1cc99d22ac76d28c Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:30:37 -0700 Subject: [PATCH 03/18] Blend pixel history widget background color with selection color Before, if a row was selected, the color was completely replaced, which makes the color preview column completely useless for that row. This change also fixes some inconsistencies with how the selection hover works on tree widgets; previously part of the row was not highlighted. --- qrenderdoc/Styles/RDStyle/RDStyle.cpp | 22 +++++++++++++++++++--- qrenderdoc/Widgets/Extended/RDTreeView.cpp | 17 +++++------------ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/qrenderdoc/Styles/RDStyle/RDStyle.cpp b/qrenderdoc/Styles/RDStyle/RDStyle.cpp index 38263e84e5..acf5d2b96a 100644 --- a/qrenderdoc/Styles/RDStyle/RDStyle.cpp +++ b/qrenderdoc/Styles/RDStyle/RDStyle.cpp @@ -1450,9 +1450,25 @@ void RDStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *opt, Q group = QPalette::Inactive; if(viewitem->state & QStyle::State_Selected) - p->fillRect(viewitem->rect, viewitem->palette.brush(group, QPalette::Highlight)); - else if(viewitem->backgroundBrush.style() != Qt::NoBrush) - p->fillRect(viewitem->rect, viewitem->backgroundBrush); + { + if(viewitem->backgroundBrush.style() != Qt::NoBrush) + { + p->fillRect(viewitem->rect, viewitem->backgroundBrush); + // If we have a custom color, use the selection color at half opacity over the custom color + QColor col = viewitem->palette.color(group, QPalette::Highlight); + col.setAlphaF(0.5f); + p->fillRect(viewitem->rect, QBrush(col)); + } + else + { + p->fillRect(viewitem->rect, viewitem->palette.brush(group, QPalette::Highlight)); + } + } + else + { + if(viewitem->backgroundBrush.style() != Qt::NoBrush) + p->fillRect(viewitem->rect, viewitem->backgroundBrush); + } return; } diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index 5715a25b8e..31c1ffdb0e 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -771,18 +771,11 @@ void RDTreeView::drawBranches(QPainter *painter, const QRect &rect, const QModel idx = idx.parent(); } - opt.rect = rect; - opt.rect.setWidth(depth * indentation()); + opt.rect = allLinesRect; opt.showDecorationSelected = true; + opt.backgroundBrush = index.data(Qt::BackgroundRole).value(); style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, this); - QBrush back = index.data(Qt::BackgroundRole).value(); - - if(!selectionModel()->isSelected(index) && back.style() != Qt::NoBrush) - { - painter->fillRect(allLinesRect, back); - } - if(m_VisibleBranches) { QTreeView::drawBranches(painter, rect, index); @@ -821,12 +814,12 @@ void RDTreeView::drawBranches(QPainter *painter, const QRect &rect, const QModel { parent = parents.pop(); - back = parent.data(RDTreeView::TreeLineColorRole).value(); + QBrush line = parent.data(RDTreeView::TreeLineColorRole).value(); - if(back.style() != Qt::NoBrush) + if(line.style() != Qt::NoBrush) { // draw a centred pen vertically down the middle of branchRect - painter->setPen(QPen(QBrush(back), m_treeColorLineWidth)); + painter->setPen(QPen(line, m_treeColorLineWidth)); QPoint topCentre = QRect(branchRect).center(); QPoint bottomCentre = topCentre; From 70b412dc5d32ab5651ce674aedbeb58f10161d40 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:34:10 -0700 Subject: [PATCH 04/18] Fix debug pixel at primitive and event message mixing parameter order up --- qrenderdoc/Windows/PixelHistoryView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 64a7c23540..d54d549580 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -848,8 +848,8 @@ void PixelHistoryView::on_events_customContextMenuRequested(const QPoint &pos) debugText = tr("&Debug Pixel (%1, %2) primitive %3 at Event %4") .arg(m_Pixel.x()) .arg(m_Pixel.y()) - .arg(tag.eventId) - .arg(tag.primitive); + .arg(tag.primitive) + .arg(tag.eventId); contextMenu.addAction(&jumpAction); } From 33f4b4bb7365c9c45c9895ed345df3d5450dcd20 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:35:16 -0700 Subject: [PATCH 05/18] Use an enum for pixel history column ids instead of magic numbers --- qrenderdoc/Windows/PixelHistoryView.cpp | 54 +++++++++++++++++-------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index d54d549580..2305dad978 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -39,6 +39,16 @@ struct EventTag Q_DECLARE_METATYPE(EventTag); +enum +{ + COL_EVENT, + COL_SHADER_OUT, + COL_SHADER_OUT_COLOR, + COL_TEX_AFTER, + COL_TEX_AFTER_COLOR, + COL_COUNT, +}; + class PixelHistoryItemModel : public QAbstractItemModel { public: @@ -71,6 +81,16 @@ class PixelHistoryItemModel : public QAbstractItemModel } } + static bool isColorColumn(int column) + { + switch(column) + { + case COL_SHADER_OUT_COLOR: + case COL_TEX_AFTER_COLOR: return true; + default: return false; + } + } + void setHistory(const rdcarray &history) { m_ModList.reserve(history.count()); @@ -144,7 +164,7 @@ class PixelHistoryItemModel : public QAbstractItemModel return 0; } - int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 5; } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return COL_COUNT; } Qt::ItemFlags flags(const QModelIndex &index) const override { if(!index.isValid()) @@ -155,11 +175,11 @@ class PixelHistoryItemModel : public QAbstractItemModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override { - if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) + if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == COL_EVENT) return lit("Event"); // sizes for the colour previews - if(orientation == Qt::Horizontal && role == Qt::SizeHintRole && (section == 2 || section == 4)) + if(orientation == Qt::Horizontal && role == Qt::SizeHintRole && isColorColumn(section)) return QSize(18, 0); return QVariant(); @@ -172,7 +192,7 @@ class PixelHistoryItemModel : public QAbstractItemModel int col = index.column(); // preview columns - if(col == 2 || col == 4) + if(isColorColumn(col)) { if(role == Qt::SizeHintRole) return QSize(16, 0); @@ -180,7 +200,7 @@ class PixelHistoryItemModel : public QAbstractItemModel if(m_Loading) { - if(role == Qt::DisplayRole && col == 0) + if(role == Qt::DisplayRole && col == COL_EVENT) return tr("Loading..."); return QVariant(); @@ -190,7 +210,7 @@ class PixelHistoryItemModel : public QAbstractItemModel { QString uavName = IsD3D(m_Ctx.APIProps().pipelineType) ? lit("UAV") : lit("Storage"); // main text - if(col == 0) + if(col == COL_EVENT) { if(isEvent(index)) { @@ -292,7 +312,7 @@ class PixelHistoryItemModel : public QAbstractItemModel } // pre mod/shader out text - if(col == 1) + if(col == COL_SHADER_OUT) { if(isEvent(index)) { @@ -310,7 +330,7 @@ class PixelHistoryItemModel : public QAbstractItemModel } // post mod text - if(col == 3) + if(col == COL_TEX_AFTER) { if(isEvent(index)) return tr("Tex After\n\n") + modString(getMods(index).last().postMod); @@ -322,7 +342,7 @@ class PixelHistoryItemModel : public QAbstractItemModel if(role == Qt::BackgroundRole && (m_IsDepth || m_IsFloat)) { // pre mod color - if(col == 2) + if(col == COL_SHADER_OUT_COLOR) { if(isEvent(index)) { @@ -336,7 +356,7 @@ class PixelHistoryItemModel : public QAbstractItemModel return backgroundBrush(mod.shaderOut); } } - else if(col == 4) + else if(col == COL_TEX_AFTER_COLOR) { if(isEvent(index)) return backgroundBrush(getMods(index).last().postMod); @@ -346,7 +366,7 @@ class PixelHistoryItemModel : public QAbstractItemModel } // text backgrounds marking pass/fail - if(role == Qt::BackgroundRole && (col == 0 || col == 1 || col == 3)) + if(role == Qt::BackgroundRole && !isColorColumn(col)) { // rest if(isEvent(index)) @@ -373,7 +393,7 @@ class PixelHistoryItemModel : public QAbstractItemModel // Since we change the background color for some cells, also change the foreground color to // ensure contrast with all UI themes - if(role == Qt::ForegroundRole && (col == 0 || col == 1 || col == 3)) + if(role == Qt::ForegroundRole && !isColorColumn(col)) { QColor textColor = contrastingColor(QColor::fromRgb(235, 235, 235), m_Palette.color(QPalette::Text)); @@ -639,11 +659,11 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->hideBranches(); - ui->events->header()->setSectionResizeMode(0, QHeaderView::Stretch); - ui->events->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - ui->events->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - ui->events->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); - ui->events->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_EVENT, QHeaderView::Stretch); + ui->events->header()->setSectionResizeMode(COL_SHADER_OUT, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_COLOR, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_TEX_AFTER, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_TEX_AFTER_COLOR, QHeaderView::ResizeToContents); m_Ctx.AddCaptureViewer(this); } From c10467e16fe1b3d0866d8c8dc031a6616457b9e9 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:37:03 -0700 Subject: [PATCH 06/18] Allow changing the columns for pixel history --- qrenderdoc/Windows/PixelHistoryView.cpp | 48 +++++++++++++++++++++++-- qrenderdoc/Windows/PixelHistoryView.h | 5 ++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 2305dad978..e7cc17533c 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -175,8 +175,25 @@ class PixelHistoryItemModel : public QAbstractItemModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override { - if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == COL_EVENT) - return lit("Event"); + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + QString result; + switch(section) + { + case COL_EVENT: result = lit("Event"); break; + case COL_SHADER_OUT: + case COL_SHADER_OUT_COLOR: result = lit("Shader Out"); break; + case COL_TEX_AFTER: + case COL_TEX_AFTER_COLOR: result = lit("Tex After"); break; + } + if(isColorColumn(section)) + { + // Pad with spaces so that the text isn't visible in the headers, only on the column chooser + // (it wouldn't fit anyways) + result = lit(" ") + result + lit(" color"); + } + return result; + } // sizes for the colour previews if(orientation == Qt::Horizontal && role == Qt::SizeHintRole && isColorColumn(section)) @@ -665,6 +682,13 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->header()->setSectionResizeMode(COL_TEX_AFTER, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER_COLOR, QHeaderView::ResizeToContents); + QObject::connect(ui->events, &RDTreeView::customContextMenuRequested, this, + &PixelHistoryView::events_contextMenu); + + ui->events->header()->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->events->header(), &QHeaderView::customContextMenuRequested, this, + &PixelHistoryView::events_contextMenu); + m_Ctx.AddCaptureViewer(this); } @@ -822,8 +846,19 @@ void PixelHistoryView::jumpToPrimitive(EventTag tag) } } -void PixelHistoryView::on_events_customContextMenuRequested(const QPoint &pos) +void PixelHistoryView::colSelect_clicked() +{ + QStringList headers; + for(int i = 0; i < ui->events->model()->columnCount(); i++) + headers << ui->events->model()->headerData(i, Qt::Horizontal).toString(); + UpdateVisibleColumns(tr("Select Pixel History Columns"), COL_COUNT, ui->events->header(), headers); +} + +void PixelHistoryView::events_contextMenu(const QPoint &pos) { + // TODO: this uses coordinates that are supposed to be relative to the header as coordinates in + // the table, so right-clicking the header uses the first entry instead (same applies to + // EventBrowser.cpp) QModelIndex index = ui->events->indexAt(pos); QMenu contextMenu(this); @@ -839,6 +874,13 @@ void PixelHistoryView::on_events_customContextMenuRequested(const QPoint &pos) ui->eventsHidden->setVisible(!m_ShowFailures); }); + QAction selectCols(tr("&Select Columns..."), this); + + contextMenu.addAction(&selectCols); + selectCols.setIcon(Icons::timeline_marker()); + + QObject::connect(&selectCols, &QAction::triggered, this, &PixelHistoryView::colSelect_clicked); + if(!index.isValid()) { RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos)); diff --git a/qrenderdoc/Windows/PixelHistoryView.h b/qrenderdoc/Windows/PixelHistoryView.h index e468c65b2a..a11b6a57f1 100644 --- a/qrenderdoc/Windows/PixelHistoryView.h +++ b/qrenderdoc/Windows/PixelHistoryView.h @@ -55,9 +55,12 @@ class PixelHistoryView : public QFrame, public IPixelHistoryView, public ICaptur void OnEventChanged(uint32_t eventId) override; private slots: // automatic slots - void on_events_customContextMenuRequested(const QPoint &pos); void on_events_doubleClicked(const QModelIndex &index); + // manual slots + void colSelect_clicked(); + void events_contextMenu(const QPoint &pos); + protected: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; From 0605594db2a2f5d559017e99527ab8dd4155a999 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:38:16 -0700 Subject: [PATCH 07/18] Create pixel history tex before column This is already implemented by all backends, but did not get displayed beforehand. This column is not visible by default. --- qrenderdoc/Windows/PixelHistoryView.cpp | 71 +++++++++++++++++-------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index e7cc17533c..733e2090d9 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -42,6 +42,8 @@ Q_DECLARE_METATYPE(EventTag); enum { COL_EVENT, + COL_TEX_BEFORE, + COL_TEX_BEFORE_COLOR, COL_SHADER_OUT, COL_SHADER_OUT_COLOR, COL_TEX_AFTER, @@ -85,6 +87,7 @@ class PixelHistoryItemModel : public QAbstractItemModel { switch(column) { + case COL_TEX_BEFORE_COLOR: case COL_SHADER_OUT_COLOR: case COL_TEX_AFTER_COLOR: return true; default: return false; @@ -181,6 +184,8 @@ class PixelHistoryItemModel : public QAbstractItemModel switch(section) { case COL_EVENT: result = lit("Event"); break; + case COL_TEX_BEFORE: + case COL_TEX_BEFORE_COLOR: result = lit("Tex Before"); break; case COL_SHADER_OUT: case COL_SHADER_OUT_COLOR: result = lit("Shader Out"); break; case COL_TEX_AFTER: @@ -328,57 +333,73 @@ class PixelHistoryItemModel : public QAbstractItemModel } } - // pre mod/shader out text - if(col == COL_SHADER_OUT) + if(isEvent(index)) { - if(isEvent(index)) + // We can't provide meaningful shader out messages for events, only individual primitives. + // So, use the Tex Before value for all of them. + if(col == COL_TEX_BEFORE || col == COL_SHADER_OUT) { return tr("Tex Before\n\n") + modString(getMods(index).first().preMod); } - else + else if(col == COL_TEX_AFTER) + { + return tr("Tex After\n\n") + modString(getMods(index).last().postMod); + } + } + else + { + if(col == COL_TEX_BEFORE) + { + return tr("Tex Before\n\n") + modString(getMod(index).preMod); + } + else if(col == COL_SHADER_OUT) { const PixelModification &mod = getMod(index); if(mod.unboundPS) return tr("No Pixel\nShader\nBound"); if(mod.directShaderWrite) return tr("Tex Before\n\n") + modString(mod.preMod); + return tr("Shader Out\n\n") + modString(mod.shaderOut, 4); } - } - - // post mod text - if(col == COL_TEX_AFTER) - { - if(isEvent(index)) - return tr("Tex After\n\n") + modString(getMods(index).last().postMod); - else + else if(col == COL_TEX_AFTER) + { return tr("Tex After\n\n") + modString(getMod(index).postMod); + } } } if(role == Qt::BackgroundRole && (m_IsDepth || m_IsFloat)) { - // pre mod color - if(col == COL_SHADER_OUT_COLOR) + if(isEvent(index)) { - if(isEvent(index)) + if(col == COL_TEX_BEFORE_COLOR || col == COL_SHADER_OUT_COLOR) { return backgroundBrush(getMods(index).first().preMod); } - else + else if(col == COL_TEX_AFTER_COLOR) + { + return backgroundBrush(getMods(index).last().postMod); + } + } + else + { + if(col == COL_TEX_BEFORE_COLOR) + { + return backgroundBrush(getMod(index).preMod); + } + else if(col == COL_SHADER_OUT_COLOR) { const PixelModification &mod = getMod(index); if(mod.directShaderWrite) return backgroundBrush(mod.preMod); + return backgroundBrush(mod.shaderOut); } - } - else if(col == COL_TEX_AFTER_COLOR) - { - if(isEvent(index)) - return backgroundBrush(getMods(index).last().postMod); - else + else if(col == COL_TEX_AFTER_COLOR) + { return backgroundBrush(getMod(index).postMod); + } } } @@ -677,11 +698,17 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->hideBranches(); ui->events->header()->setSectionResizeMode(COL_EVENT, QHeaderView::Stretch); + ui->events->header()->setSectionResizeMode(COL_TEX_BEFORE, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_TEX_BEFORE_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER_COLOR, QHeaderView::ResizeToContents); + // Hide these by default; only shader out and tex after are visible by default + ui->events->header()->hideSection(COL_TEX_BEFORE); + ui->events->header()->hideSection(COL_TEX_BEFORE_COLOR); + QObject::connect(ui->events, &RDTreeView::customContextMenuRequested, this, &PixelHistoryView::events_contextMenu); From 551f1479bc75e448e18ce2b54fd570a909db8bbf Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:40:01 -0700 Subject: [PATCH 08/18] Create pixel history shader out 2 column This has not yet been implemented by any backends, not is the column visible by default. --- qrenderdoc/Windows/PixelHistoryView.cpp | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 733e2090d9..0c821fa993 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -46,6 +46,8 @@ enum COL_TEX_BEFORE_COLOR, COL_SHADER_OUT, COL_SHADER_OUT_COLOR, + COL_SHADER_OUT_2, + COL_SHADER_OUT_2_COLOR, COL_TEX_AFTER, COL_TEX_AFTER_COLOR, COL_COUNT, @@ -89,6 +91,7 @@ class PixelHistoryItemModel : public QAbstractItemModel { case COL_TEX_BEFORE_COLOR: case COL_SHADER_OUT_COLOR: + case COL_SHADER_OUT_2_COLOR: case COL_TEX_AFTER_COLOR: return true; default: return false; } @@ -188,6 +191,8 @@ class PixelHistoryItemModel : public QAbstractItemModel case COL_TEX_BEFORE_COLOR: result = lit("Tex Before"); break; case COL_SHADER_OUT: case COL_SHADER_OUT_COLOR: result = lit("Shader Out"); break; + case COL_SHADER_OUT_2: + case COL_SHADER_OUT_2_COLOR: result = lit("Shader Out 2"); break; case COL_TEX_AFTER: case COL_TEX_AFTER_COLOR: result = lit("Tex After"); break; } @@ -337,7 +342,7 @@ class PixelHistoryItemModel : public QAbstractItemModel { // We can't provide meaningful shader out messages for events, only individual primitives. // So, use the Tex Before value for all of them. - if(col == COL_TEX_BEFORE || col == COL_SHADER_OUT) + if(col == COL_TEX_BEFORE || col == COL_SHADER_OUT || col == COL_SHADER_OUT_2) { return tr("Tex Before\n\n") + modString(getMods(index).first().preMod); } @@ -352,7 +357,7 @@ class PixelHistoryItemModel : public QAbstractItemModel { return tr("Tex Before\n\n") + modString(getMod(index).preMod); } - else if(col == COL_SHADER_OUT) + else if(col == COL_SHADER_OUT || col == COL_SHADER_OUT_2) { const PixelModification &mod = getMod(index); if(mod.unboundPS) @@ -360,7 +365,10 @@ class PixelHistoryItemModel : public QAbstractItemModel if(mod.directShaderWrite) return tr("Tex Before\n\n") + modString(mod.preMod); - return tr("Shader Out\n\n") + modString(mod.shaderOut, 4); + if(col == COL_SHADER_OUT) + return tr("Shader Out\n\n") + modString(mod.shaderOut, 4); + else + return tr("Shader Out 2\n\n") + modString(mod.shaderOutDualSrc, 4); } else if(col == COL_TEX_AFTER) { @@ -373,7 +381,8 @@ class PixelHistoryItemModel : public QAbstractItemModel { if(isEvent(index)) { - if(col == COL_TEX_BEFORE_COLOR || col == COL_SHADER_OUT_COLOR) + if(col == COL_TEX_BEFORE_COLOR || col == COL_SHADER_OUT_COLOR || + col == COL_SHADER_OUT_2_COLOR) { return backgroundBrush(getMods(index).first().preMod); } @@ -388,13 +397,16 @@ class PixelHistoryItemModel : public QAbstractItemModel { return backgroundBrush(getMod(index).preMod); } - else if(col == COL_SHADER_OUT_COLOR) + else if(col == COL_SHADER_OUT_COLOR || col == COL_SHADER_OUT_2_COLOR) { const PixelModification &mod = getMod(index); if(mod.directShaderWrite) return backgroundBrush(mod.preMod); - return backgroundBrush(mod.shaderOut); + if(col == COL_SHADER_OUT_COLOR) + return backgroundBrush(mod.shaderOut); + else + return backgroundBrush(mod.shaderOutDualSrc); } else if(col == COL_TEX_AFTER_COLOR) { @@ -702,12 +714,16 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->header()->setSectionResizeMode(COL_TEX_BEFORE_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_COLOR, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_2, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_2_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER_COLOR, QHeaderView::ResizeToContents); // Hide these by default; only shader out and tex after are visible by default ui->events->header()->hideSection(COL_TEX_BEFORE); ui->events->header()->hideSection(COL_TEX_BEFORE_COLOR); + ui->events->header()->hideSection(COL_SHADER_OUT_2); + ui->events->header()->hideSection(COL_SHADER_OUT_2_COLOR); QObject::connect(ui->events, &RDTreeView::customContextMenuRequested, this, &PixelHistoryView::events_contextMenu); From 2098868cddadf347e99a84284f5403404a21cca8 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:41:05 -0700 Subject: [PATCH 09/18] Create pixel history blend source and blend dest columns They have not yet been implemented by any backends, not are the columns visible by default. --- qrenderdoc/Windows/PixelHistoryView.cpp | 43 ++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 0c821fa993..9db98d5420 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -48,6 +48,10 @@ enum COL_SHADER_OUT_COLOR, COL_SHADER_OUT_2, COL_SHADER_OUT_2_COLOR, + COL_BLEND_SRC, + COL_BLEND_SRC_COLOR, + COL_BLEND_DST, + COL_BLEND_DST_COLOR, COL_TEX_AFTER, COL_TEX_AFTER_COLOR, COL_COUNT, @@ -92,6 +96,8 @@ class PixelHistoryItemModel : public QAbstractItemModel case COL_TEX_BEFORE_COLOR: case COL_SHADER_OUT_COLOR: case COL_SHADER_OUT_2_COLOR: + case COL_BLEND_SRC_COLOR: + case COL_BLEND_DST_COLOR: case COL_TEX_AFTER_COLOR: return true; default: return false; } @@ -193,6 +199,10 @@ class PixelHistoryItemModel : public QAbstractItemModel case COL_SHADER_OUT_COLOR: result = lit("Shader Out"); break; case COL_SHADER_OUT_2: case COL_SHADER_OUT_2_COLOR: result = lit("Shader Out 2"); break; + case COL_BLEND_SRC: + case COL_BLEND_SRC_COLOR: result = lit("Blend Source"); break; + case COL_BLEND_DST: + case COL_BLEND_DST_COLOR: result = lit("Blend Dest"); break; case COL_TEX_AFTER: case COL_TEX_AFTER_COLOR: result = lit("Tex After"); break; } @@ -342,11 +352,12 @@ class PixelHistoryItemModel : public QAbstractItemModel { // We can't provide meaningful shader out messages for events, only individual primitives. // So, use the Tex Before value for all of them. - if(col == COL_TEX_BEFORE || col == COL_SHADER_OUT || col == COL_SHADER_OUT_2) + if(col == COL_TEX_BEFORE || col == COL_SHADER_OUT || col == COL_SHADER_OUT_2 || + col == COL_BLEND_SRC) { return tr("Tex Before\n\n") + modString(getMods(index).first().preMod); } - else if(col == COL_TEX_AFTER) + else if(col == COL_TEX_AFTER || col == COL_BLEND_DST) { return tr("Tex After\n\n") + modString(getMods(index).last().postMod); } @@ -370,6 +381,14 @@ class PixelHistoryItemModel : public QAbstractItemModel else return tr("Shader Out 2\n\n") + modString(mod.shaderOutDualSrc, 4); } + else if(col == COL_BLEND_SRC) + { + return tr("Blend Source\n\n") + modString(getMod(index).blendSrc); + } + else if(col == COL_BLEND_DST) + { + return tr("Blend Dest\n\n") + modString(getMod(index).blendDst); + } else if(col == COL_TEX_AFTER) { return tr("Tex After\n\n") + modString(getMod(index).postMod); @@ -382,11 +401,11 @@ class PixelHistoryItemModel : public QAbstractItemModel if(isEvent(index)) { if(col == COL_TEX_BEFORE_COLOR || col == COL_SHADER_OUT_COLOR || - col == COL_SHADER_OUT_2_COLOR) + col == COL_SHADER_OUT_2_COLOR || col == COL_BLEND_SRC_COLOR) { return backgroundBrush(getMods(index).first().preMod); } - else if(col == COL_TEX_AFTER_COLOR) + else if(col == COL_TEX_AFTER_COLOR || col == COL_BLEND_DST_COLOR) { return backgroundBrush(getMods(index).last().postMod); } @@ -408,6 +427,14 @@ class PixelHistoryItemModel : public QAbstractItemModel else return backgroundBrush(mod.shaderOutDualSrc); } + else if(col == COL_BLEND_SRC_COLOR) + { + return backgroundBrush(getMod(index).blendSrc); + } + else if(col == COL_BLEND_DST_COLOR) + { + return backgroundBrush(getMod(index).blendDst); + } else if(col == COL_TEX_AFTER_COLOR) { return backgroundBrush(getMod(index).postMod); @@ -716,6 +743,10 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_2, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_SHADER_OUT_2_COLOR, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_BLEND_SRC, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_BLEND_SRC_COLOR, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_BLEND_DST, QHeaderView::ResizeToContents); + ui->events->header()->setSectionResizeMode(COL_BLEND_DST_COLOR, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER, QHeaderView::ResizeToContents); ui->events->header()->setSectionResizeMode(COL_TEX_AFTER_COLOR, QHeaderView::ResizeToContents); @@ -724,6 +755,10 @@ PixelHistoryView::PixelHistoryView(ICaptureContext &ctx, ResourceId id, QPoint p ui->events->header()->hideSection(COL_TEX_BEFORE_COLOR); ui->events->header()->hideSection(COL_SHADER_OUT_2); ui->events->header()->hideSection(COL_SHADER_OUT_2_COLOR); + ui->events->header()->hideSection(COL_BLEND_SRC); + ui->events->header()->hideSection(COL_BLEND_SRC_COLOR); + ui->events->header()->hideSection(COL_BLEND_DST); + ui->events->header()->hideSection(COL_BLEND_DST_COLOR); QObject::connect(ui->events, &RDTreeView::customContextMenuRequested, this, &PixelHistoryView::events_contextMenu); From 3040f8cb8c97047056b16fac609f744f681ce4e3 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:42:18 -0700 Subject: [PATCH 10/18] Add constants for pixel history colors --- qrenderdoc/Windows/PixelHistoryView.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 9db98d5420..d719974285 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -442,6 +442,10 @@ class PixelHistoryItemModel : public QAbstractItemModel } } + const QColor GRAY_BACKGROUND = QColor::fromRgb(235, 235, 235); + const QColor GREEN_BACKGROUND = QColor::fromRgb(235, 255, 235); + const QColor RED_BACKGROUND = QColor::fromRgb(255, 235, 235); + // text backgrounds marking pass/fail if(role == Qt::BackgroundRole && !isColorColumn(col)) { @@ -456,15 +460,14 @@ class PixelHistoryItemModel : public QAbstractItemModel if(mods[0].directShaderWrite && mods[0].preMod.col.uintValue == mods[0].postMod.col.uintValue) - return QBrush(QColor::fromRgb(235, 235, 235)); + return QBrush(GRAY_BACKGROUND); - return passed ? QBrush(QColor::fromRgb(235, 255, 235)) - : QBrush(QColor::fromRgb(255, 235, 235)); + return passed ? QBrush(GREEN_BACKGROUND) : QBrush(RED_BACKGROUND); } else { if(!getMod(index).Passed()) - return QBrush(QColor::fromRgb(255, 235, 235)); + return QBrush(RED_BACKGROUND); } } @@ -472,8 +475,7 @@ class PixelHistoryItemModel : public QAbstractItemModel // ensure contrast with all UI themes if(role == Qt::ForegroundRole && !isColorColumn(col)) { - QColor textColor = - contrastingColor(QColor::fromRgb(235, 235, 235), m_Palette.color(QPalette::Text)); + QColor textColor = contrastingColor(GRAY_BACKGROUND, m_Palette.color(QPalette::Text)); if(isEvent(index)) { return QBrush(textColor); From 7ec39b979c66d2d553b5d4839c878517c728c6a2 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 15:47:04 -0700 Subject: [PATCH 11/18] Clean up 3 small non-behavioral issues with Vulkan pixel history The first one is a simply a typo. The second one seems to be an unnecessary value that doesn't make sense (as blend factors are ignored when blendEnable is false, and setting the source color blend factor to the destination color seems like an uncommon situation (the equivalent to blendEnable being false is source = 1, dest = 0). This does not seem to be a driver bug, as the commit that added it (50438987df78d66d40a5eb75446b9f97896ce432) does not mention that nor does it seem to work around other driver bugs. The third one is that VulkanDebugManager::PixelHistoryCopyPixel was declared but never defined. VulkanPixelHistoryCallback::CopyImagePixel serves the same purpose. --- renderdoc/driver/vulkan/vk_debug.h | 4 ---- renderdoc/driver/vulkan/vk_pixelhistory.cpp | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/renderdoc/driver/vulkan/vk_debug.h b/renderdoc/driver/vulkan/vk_debug.h index 84c5b2f076..54e5d1a7b9 100644 --- a/renderdoc/driver/vulkan/vk_debug.h +++ b/renderdoc/driver/vulkan/vk_debug.h @@ -49,8 +49,6 @@ struct VKMeshDisplayPipelines uint32_t secondaryStridePadding = 0; }; -struct VkCopyPixelParams; - struct PixelHistoryResources; class VulkanResourceManager; @@ -99,8 +97,6 @@ class VulkanDebugManager const Subresource &sub, uint32_t numEvents); bool PixelHistoryDestroyResources(const PixelHistoryResources &resources); - void PixelHistoryCopyPixel(VkCommandBuffer cmd, VkCopyPixelParams &p, size_t offset); - VkImageLayout GetImageLayout(ResourceId image, VkImageAspectFlagBits aspect, uint32_t mip, uint32_t slice); diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index dfbe367477..bb3cb2b38d 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -1378,7 +1378,7 @@ struct VulkanPixelHistoryCallback : public VulkanActionCallback return (rpInfo.subpasses.size() > 1); } - // Returns teh color attachment index that corresponds to the target image for + // Returns the color attachment index that corresponds to the target image for // pixel history. uint32_t GetColorAttachmentIndex(const VulkanRenderState &renderstate, uint32_t *framebufferIndex = NULL) @@ -3105,7 +3105,6 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback else { newAtt.blendEnable = VK_FALSE; - newAtt.srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR; } newAtts[cbs->attachmentCount] = newAtt; cbs->attachmentCount = (uint32_t)newAtts.size(); From b57b488aa71d3c8159ed2b2e83a3e77f3885227c Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:24:19 -0700 Subject: [PATCH 12/18] Fix undersized buffer for Vulkan pixel history with many fragments Before, the buffer could be overrun, which could result in anything from garbage data to the GPU device being lost to segfaults. Now, correct data is gathered. We can't know in advance how many primitives will hit the targetted pixel, so it's not possible to create the buffer until after our first pass for each events (fortunately we do know the number of events in advance). It is possible that we don't need a larger buffer, though, in which case the original one can be re-used. --- renderdoc/driver/vulkan/vk_debug.h | 2 + renderdoc/driver/vulkan/vk_pixelhistory.cpp | 90 +++++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/renderdoc/driver/vulkan/vk_debug.h b/renderdoc/driver/vulkan/vk_debug.h index 54e5d1a7b9..2e0e4cf3a9 100644 --- a/renderdoc/driver/vulkan/vk_debug.h +++ b/renderdoc/driver/vulkan/vk_debug.h @@ -95,6 +95,8 @@ class VulkanDebugManager bool PixelHistorySetupResources(PixelHistoryResources &resources, VkImage targetImage, VkExtent3D extent, VkFormat format, VkSampleCountFlagBits samples, const Subresource &sub, uint32_t numEvents); + bool PixelHistorySetupPerFragResources(PixelHistoryResources &resources, uint32_t numEvents, + uint32_t numFragments); bool PixelHistoryDestroyResources(const PixelHistoryResources &resources); VkImageLayout GetImageLayout(ResourceId image, VkImageAspectFlagBits aspect, uint32_t mip, diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index bb3cb2b38d..427c349d00 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -3360,9 +3360,9 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou VkFormat format, VkSampleCountFlagBits samples, const Subresource &sub, uint32_t numEvents) { - VkMarkerRegion region(StringFormat::Fmt("PixelHistorySetupResources %ux%ux%u %s %ux MSAA", - extent.width, extent.height, extent.depth, - ToStr(format).c_str(), samples)); + VkMarkerRegion region( + StringFormat::Fmt("PixelHistorySetupResources %ux%ux%u %s %ux MSAA, %u events", extent.width, + extent.height, extent.depth, ToStr(format).c_str(), samples, numEvents)); VulkanCreationInfo::Image targetImageInfo = GetImageInfo(GetResID(targetImage)); VkImage colorImage; @@ -3472,8 +3472,6 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou CheckVkResult(vkr); VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; - // TODO: the size for memory is calculated to fit pre and post modification values and - // stencil values. But we might run out of space when getting per fragment data. bufferInfo.size = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 4096U); bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -3529,6 +3527,75 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou return true; } +bool VulkanDebugManager::PixelHistorySetupPerFragResources(PixelHistoryResources &resources, + uint32_t numEvents, uint32_t numFrags) +{ + const uint32_t existingBufferSize = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 4096U); + const uint32_t requiredBufferSize = AlignUp((uint32_t)(numFrags * sizeof(PerFragmentInfo)), 4096U); + + // If the existing buffer is big enough for all of the fragments, we can re-use it. + const bool canReuseBuffer = existingBufferSize >= requiredBufferSize; + + VkMarkerRegion region(StringFormat::Fmt( + "PixelHistorySetupPerFragResources %u events %u frags, buffer size %u -> %u, %s old buffer", + numEvents, numFrags, existingBufferSize, requiredBufferSize, + canReuseBuffer ? "reusing" : "NOT reusing")); + + if(canReuseBuffer) + return true; + + // Otherwise, destroy it and create a new one that's big enough in its place. + VkDevice dev = m_pDriver->GetDev(); + + if(resources.dstBuffer != VK_NULL_HANDLE) + m_pDriver->vkDestroyBuffer(dev, resources.dstBuffer, NULL); + if(resources.bufferMemory != VK_NULL_HANDLE) + m_pDriver->vkFreeMemory(dev, resources.bufferMemory, NULL); + resources.dstBuffer = VK_NULL_HANDLE; + resources.bufferMemory = VK_NULL_HANDLE; + + VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferInfo.size = requiredBufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + + VkResult vkr = m_pDriver->vkCreateBuffer(m_Device, &bufferInfo, NULL, &resources.dstBuffer); + CheckVkResult(vkr); + + // Allocate memory + VkMemoryRequirements mrq = {}; + m_pDriver->vkGetBufferMemoryRequirements(m_Device, resources.dstBuffer, &mrq); + VkMemoryAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, + m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits), + }; + vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &resources.bufferMemory); + CheckVkResult(vkr); + + if(vkr != VK_SUCCESS) + return false; + + vkr = m_pDriver->vkBindBufferMemory(m_Device, resources.dstBuffer, resources.bufferMemory, 0); + CheckVkResult(vkr); + + VkCommandBuffer cmd = m_pDriver->GetNextCmd(); + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + if(cmd == VK_NULL_HANDLE) + return false; + + vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + CheckVkResult(vkr); + ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(resources.dstBuffer), 0, VK_WHOLE_SIZE, 0); + + vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); + CheckVkResult(vkr); + m_pDriver->SubmitCmds(); + m_pDriver->FlushQ(); + + return true; +} + VkDescriptorSet VulkanReplay::GetPixelHistoryDescriptor() { VkDescriptorSet descSet; @@ -4034,6 +4101,17 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even if(eventsWithFrags.size() > 0) { + uint32_t numFrags = 0; + for(auto &item : eventsWithFrags) + { + numFrags += item.second; + } + + GetDebugManager()->PixelHistorySetupPerFragResources(resources, (uint32_t)events.size(), + numFrags); + + callbackInfo.dstBuffer = resources.dstBuffer; + // Replay to get shader output value, post modification value and primitive ID for every // fragment. VulkanPixelHistoryPerFragmentCallback perFragmentCB(m_pDriver, shaderCache, callbackInfo, @@ -4204,6 +4282,8 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even history[h].depthBoundsFailed = true; } } + + m_pDriver->vkUnmapMemory(dev, resources.bufferMemory); } SAFE_DELETE(tfCb); From a3d23a3cccf3646ead92419e8e3d00cfda714c48 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:28:34 -0700 Subject: [PATCH 13/18] Create GetDualSrcSwappedShader for Vulkan dual source pixel history --- renderdoc/driver/vulkan/vk_pixelhistory.cpp | 225 +++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-) diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index 427c349d00..df42b30d4d 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -81,6 +81,25 @@ * * We slot the per frament data correctly accounting for the fragments that were discarded. * + * Dual Source: + * + * To capture the output for both output indices when dual source blending is in use, we need to + * patch the shader so that ouptut index 1 is directed to output index 0. This might seem as simple + * as iterating through all of the outputs, and then for each Index decoration, swapping 0 with 1. + * Unfortunately, this does not work because index 0 is implicitly used if there is no Index + * decoration, meaning index 1 would get swapped into index 0, but index 0 would not get swapped to + * index 1, so multiple outputs end up at index 0. + * + * Another possible implementation would be to swap the target of any Index decorations which use + * index 1 to those which use index 0. However, there's no guarantee that there are the same number + * of outputs decorated with index 1 as with index 0, because the Component decoration means that + * e.g. index 0 could be written separately as ra and gb while index 1 is written as one output. + * + * The current implementation first computes what index values are currently in use for each output, + * and then NOPing out all Index decorations and adding new ones with the correctly swapped ones. + * Care is also taken to ensure that built-in outputs (those with the BuiltIn decoration) aren't + * given an Index decoration. + * * Current Limitations: * * - Multiple subpasses @@ -100,6 +119,9 @@ */ #include +#include +#include +#include #include "driver/shaders/spirv/spirv_editor.h" #include "driver/shaders/spirv/spirv_op_helpers.h" #include "maths/formatpacking.h" @@ -314,14 +336,31 @@ struct PixelHistoryShaderCache if(it != m_ShaderReplacements.end()) return it->second; - VkShaderModule shaderModule = CreateShaderReplacement(shaderId, entryPoint, stage); + VkShaderModule shaderModule = CreateShaderReplacement(shaderId, entryPoint, stage, false); m_ShaderReplacements.insert(std::make_pair(shaderKey, shaderModule)); return shaderModule; } + // Returns a shader that is equivalent to the given shader, except that index 0 outputs + // are directed to index 1 and index 1 outputs are directed to index 0 (as long as both exist). + // The shader is also stripped of side effects as is done by GetShaderWithoutSideEffects. + VkShaderModule GetDualSrcSwappedShader(ResourceId shaderId, const rdcstr &entryPoint, + ShaderStage stage) + { + ShaderKey shaderKey = make_rdcpair(shaderId, entryPoint); + auto it = m_ShaderReplacementsDualSrc.find(shaderKey); + // Check if we processed this shader before. + if(it != m_ShaderReplacementsDualSrc.end()) + return it->second; + + VkShaderModule shaderModule = CreateShaderReplacement(shaderId, entryPoint, stage, true); + m_ShaderReplacementsDualSrc.insert(std::make_pair(shaderKey, shaderModule)); + return shaderModule; + } + private: VkShaderModule CreateShaderReplacement(ResourceId shaderId, const rdcstr &entryName, - ShaderStage stage) + ShaderStage stage, bool swapDualSrc) { const VulkanCreationInfo::ShaderModule &moduleInfo = m_pDriver->GetDebugManager()->GetShaderInfo(shaderId); @@ -343,6 +382,8 @@ struct PixelHistoryShaderCache // just insert VK_NULL_HANDLE to indicate that this shader has been processed. found = true; modified = StripShaderSideEffects(editor, entry.id); + if(swapDualSrc) + modified |= SwapOutputIndex(editor, entry.usedIds, 1); break; } } @@ -589,6 +630,185 @@ struct PixelHistoryShaderCache return modified; } + bool SwapOutputIndex(rdcspv::Editor &editor, const rdcarray usedIds, + uint32_t index_to_swap_with_zero) + { + if(index_to_swap_with_zero == 0) + { + // Swapping 0 with 0 does nothing interesting + return false; + } + + bool modified = false; + + std::set outputs; + for(const rdcspv::Variable var : editor.GetGlobals()) + { + if(var.storage == rdcspv::StorageClass::Output && usedIds.contains(var.id)) + { + outputs.insert(var.id); + } + } + + // First pass: build up maps for location and index. + // Also NOP out any existing index declarations (but don't flag the shader as modified + // while doing so, as we might still decide that it doesn't need to be modified + // afterwards) + std::map id_to_location; + std::unordered_multimap location_to_id; + std::map id_to_index; + + std::set builtins; + + for(rdcspv::Iter it = editor.Begin(rdcspv::Section::Annotations), + end = editor.End(rdcspv::Section::Annotations); + it < end; ++it) + { + if(it.opcode() == rdcspv::Op::Decorate) + { + rdcspv::OpDecorate dec(it); + const rdcspv::Id decorated_id = dec.target; + if(dec.decoration == rdcspv::Decoration::Location && outputs.count(decorated_id) > 0) + { + const uint32_t decorated_location = dec.decoration.location; + + const auto insert_result = + id_to_location.emplace(std::make_pair(decorated_id, decorated_location)); + if(!insert_result.second) + { + // Note: by definition insert_result.first->first == decorated_id + const uint32_t existing_location = insert_result.first->second; + RDCWARN("Variable %u is decorated with multiple Locations: was %u, is %u", + decorated_id.value(), existing_location, decorated_location); + } + else + { + location_to_id.emplace(std::make_pair(decorated_location, decorated_id)); + } + } + if(dec.decoration == rdcspv::Decoration::Index && outputs.count(decorated_id) > 0) + { + const uint32_t decorated_index = dec.decoration.index; + + const auto insert_result = + id_to_index.emplace(std::make_pair(decorated_id, decorated_index)); + if(!insert_result.second) + { + // Note: by definition insert_result.first->first == decorated_id + const uint32_t existing_index = insert_result.first->second; + RDCWARN("Variable %u is decorated with multiple Indexes: was %u, is %u", + decorated_id.value(), existing_index, decorated_index); + } + + it.nopRemove(); + } + if(dec.decoration == rdcspv::Decoration::BuiltIn && outputs.count(decorated_id) > 0) + { + builtins.insert(decorated_id); + } + } + } + + std::unordered_map> location_to_indexes; + for(const auto entry : location_to_id) + { + const uint32_t location = entry.first; + const rdcspv::Id id = entry.second; + + const auto index_itr = id_to_index.find(id); + // Index is allowed to be absent, in which case it is implicitly zero + const uint32_t index = (index_itr != id_to_index.end()) ? index_itr->second : 0; + + location_to_indexes[location].insert(index); + } + + std::set locations_to_swap; + for(const auto &entry : location_to_indexes) + { + // https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.html#output-layout-qualifiers + // The GLSL spec says that "Compile-time errors may also be given if at compile time it is + // known the link will fail. [...] It is also a compile-time error if a fragment shader sets a + // layout index to less than 0 or greater than 1." This is enforced by glslang. However, the + // Vulkan spec does not make this requirement (nor does the SPIRV spec). + // Note also that glslang will still compile a shader that uses index 1 but not index 0. + const uint32_t location = entry.first; + const std::set &indexes = entry.second; + + if(indexes.empty() || indexes.count(0) == 0) + { + RDCWARN("Output location %u does not output anything to index 0", location); + continue; + } + + const uint32_t highest_index = *indexes.rbegin(); + if(highest_index != (indexes.size() - 1)) + { + RDCWARN( + "Indexes for output location %u has a gap in the used indexes (highest used: %u, but " + "should be %zu based on size)", + location, highest_index, indexes.size() - 1); + continue; + } + + if(index_to_swap_with_zero <= highest_index) + { + // This location contains index 0 and the index we want to swap, + // so we can safely do the swap. + locations_to_swap.insert(location); + } + } + + for(const rdcspv::Id output : outputs) + { + if(builtins.count(output) > 0) + { + // VUID-StandaloneSpirv-Location-04915: "The Location or Component decorations must not be + // used with BuiltIn" + const auto location_itr = id_to_location.find(output); + if(location_itr != id_to_location.end()) + { + RDCWARN("Variable %u is a BuiltIn but is decorated with Location %u", output.value(), + location_itr->second); + continue; + } + } + else + { + // VUID-StandaloneSpirv-Location-04916: "The Location decorations must be used on + // user-defined variables" (Sec 15.1.2: "The non-built-in variables listed by OpEntryPoint + // with the Input or Output storage class form the user-defined variable interface.") + const auto location_itr = id_to_location.find(output); + if(location_itr == id_to_location.end()) + { + RDCWARN("Variable %u is missing a Location decoration", output.value()); + continue; + } + + const uint32_t location = location_itr->second; + + const auto index_itr = id_to_index.find(output); + // Index is allowed to be absent, in which case it is implicitly zero + const uint32_t existing_index = (index_itr != id_to_index.end()) ? index_itr->second : 0; + + uint32_t new_index = existing_index; + + if(locations_to_swap.count(location) != 0) + { + if(existing_index == 0) + new_index = index_to_swap_with_zero; + else if(existing_index == index_to_swap_with_zero) + new_index = 0; + modified = true; + } + + editor.AddDecoration(rdcspv::OpDecorate( + output, rdcspv::DecorationParam(new_index))); + } + } + + return modified; + } + WrappedVulkan *m_pDriver; std::map m_FixedColFS; std::map m_PrimIDFS; @@ -596,6 +816,7 @@ struct PixelHistoryShaderCache // ShaderKey consists of original shader module ID and entry point name. typedef rdcpair ShaderKey; std::map m_ShaderReplacements; + std::map m_ShaderReplacementsDualSrc; GPUBuffer dummybuf; }; From f6fed2b5ee8dc8e1ea00eb4aff9fe623d59ef271 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:32:28 -0700 Subject: [PATCH 14/18] Implement Vulkan dual source pixel history --- renderdoc/driver/vulkan/vk_pixelhistory.cpp | 75 ++++++++++++++++----- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index df42b30d4d..5081f90a78 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -63,11 +63,12 @@ * * - Fourth callback: Per fragment callback (VulkanPixelHistoryPerFragmentCallback) * This callback is used to get per fragment data for each event and fragment (primitive ID, - * shader output value, post event value for each fragment). - * For each fragment the draw is replayed 3 times: + * shader output value, dual source shader output value, post event value for each fragment). + * For each fragment the draw is replayed 4 times: * 1) with a fragment shader that outputs primitive ID only * 2) with blending OFF, to get shader output value - * 3) with blending ON, to get post modification value + * 3) with blending OFF and a modified shader, to get the output at index 1 (dual source) + * 4) with blending ON, to get post modification value * For each such replay we set the stencil reference to the fragment number and set the * stencil compare to equal, so it passes for that particular fragment only. * @@ -250,6 +251,7 @@ struct PerFragmentInfo int32_t primitiveID; uint32_t padding[3]; PixelHistoryValue shaderOut; + PixelHistoryValue shaderOutDualSrc; PixelHistoryValue postMod; }; @@ -2875,6 +2877,8 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback VkPipeline primitiveIdPipe; // Turn off blending. VkPipeline shaderOutPipe; + // Turn off blending, and swaps output indexes to get the dual source output. + VkPipeline shaderOutDualSrcPipe; // Enable blending to get post event values. VkPipeline postModPipe; }; @@ -2940,9 +2944,14 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback state.scissors[i].extent = {2, 2}; } - VkPipeline pipesIter[2]; + VkPipeline pipesIter[3] = {}; + uint32_t outputOffsetIter[3] = {}; pipesIter[0] = pipes.primitiveIdPipe; + outputOffsetIter[0] = offsetof(struct PerFragmentInfo, primitiveID); pipesIter[1] = pipes.shaderOutPipe; + outputOffsetIter[1] = offsetof(struct PerFragmentInfo, shaderOut); + pipesIter[2] = pipes.shaderOutDualSrcPipe; + outputOffsetIter[2] = offsetof(struct PerFragmentInfo, shaderOutDualSrc); VkCopyPixelParams colourCopyParams = {}; colourCopyParams.srcImage = m_CallbackInfo.subImage; @@ -2971,12 +2980,13 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback // Get primitive ID and shader output value for each fragment. for(uint32_t f = 0; f < numFragmentsInEvent; f++) { - for(uint32_t i = 0; i < 2; i++) - { - uint32_t storeOffset = (fragsProcessed + f) * sizeof(PerFragmentInfo); + const uint32_t fragStoreOffset = (fragsProcessed + f) * sizeof(PerFragmentInfo); - VkMarkerRegion region(cmd, StringFormat::Fmt("Getting %s for %u", - i == 0 ? "primitive ID" : "shader output", eid)); + for(uint32_t i = 0; i < (m_HasDualSrc ? 3u : 2u); i++) + { + const uint32_t storeOffset = fragStoreOffset + outputOffsetIter[i]; + const char *type = i == 0 ? "primitive ID" : i == 1 ? "shader output" : "shader output 2"; + VkMarkerRegion region(cmd, StringFormat::Fmt("Getting %s for %u", type, eid)); if(i == 0 && !m_pDriver->GetDeviceEnabledFeatures().geometryShader) { @@ -2993,7 +3003,7 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback // without one of the pipelines (e.g. if there was a geometry shader in use and we can't // read primitive ID in the fragment shader) we can't continue. // technically we can if the geometry shader outs a primitive ID, but that is unlikely. - VkMarkerRegion::Set("Can't get primitive ID with geometry shader in use", cmd); + VkMarkerRegion::Set(StringFormat::Fmt("No replacement shader in pipesIter[%d]", i), cmd); ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(m_CallbackInfo.dstBuffer), storeOffset, 16, ~0U); @@ -3059,9 +3069,9 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback m_pDriver->ReplayDraw(cmd, *action); state.EndRenderPass(cmd); - if(i == 1) + CopyImagePixel(cmd, colourCopyParams, storeOffset); + if(i != 0) { - storeOffset += offsetof(struct PerFragmentInfo, shaderOut); if(depthEnabled) { VkCopyPixelParams depthCopyParams = colourCopyParams; @@ -3072,7 +3082,6 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback storeOffset + offsetof(struct PixelHistoryValue, depth)); } } - CopyImagePixel(cmd, colourCopyParams, storeOffset); } } @@ -3276,14 +3285,24 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback EventFlags eventFlags = m_pDriver->GetEventFlags(eid); VkShaderModule replacementShaders[5] = {}; + VkShaderModule replacementDualSourceFragmentShader = VK_NULL_HANDLE; // Clean shaders uint32_t numberOfStages = 5; for(size_t i = 0; i < numberOfStages; i++) { - if((eventFlags & PipeStageRWEventFlags(StageFromIndex(i))) != EventFlags::NoFlags) + ShaderStage stage = StageFromIndex(i); + if(m_pDriver->GetDeviceEnabledFeatures().dualSrcBlend && stage == ShaderStage::Fragment) + { + replacementDualSourceFragmentShader = m_ShaderCache->GetDualSrcSwappedShader( + p.shaders[i].module, p.shaders[i].entryPoint, p.shaders[i].stage); + } + bool rwInStage = (eventFlags & PipeStageRWEventFlags(stage)) != EventFlags::NoFlags; + if(rwInStage) + { replacementShaders[i] = m_ShaderCache->GetShaderWithoutSideEffects( p.shaders[i].module, p.shaders[i].entryPoint, p.shaders[i].stage); + } } for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) { @@ -3353,6 +3372,27 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback m_PipesToDestroy.push_back(pipes.shaderOutPipe); + if(replacementDualSourceFragmentShader != VK_NULL_HANDLE) + { + m_HasDualSrc = true; + rdcarray dual_src_stages = stages; + for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) + { + if(dual_src_stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT) + dual_src_stages[i].module = replacementDualSourceFragmentShader; + } + + pipeCreateInfo.pStages = dual_src_stages.data(); + + vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1, + &pipeCreateInfo, NULL, &pipes.shaderOutDualSrcPipe); + m_pDriver->CheckVkResult(vkr); + + m_PipesToDestroy.push_back(pipes.shaderOutDualSrcPipe); + + pipeCreateInfo.pStages = stages.data(); + } + { ds->depthTestEnable = VK_FALSE; ds->depthWriteEnable = VK_FALSE; @@ -3432,6 +3472,8 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback return it->second; } + bool m_HasDualSrc = false; + private: // For each event, specifies where the occlusion query results start. std::map m_EventIndices; @@ -4450,7 +4492,10 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even history[h].preMod = history[h - 1].postMod; } - history[h].shaderOutDualSrc.SetInvalid(); + if(perFragmentCB.m_HasDualSrc) + FillInColor(shaderOutFormat, bp[offset].shaderOutDualSrc, history[h].shaderOutDualSrc); + else + history[h].shaderOutDualSrc.SetInvalid(); history[h].blendSrc.SetInvalid(); history[h].blendDst.SetInvalid(); From 88436bf468d7fcd35b960c7b5e9b95b1a99bdf78 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:34:31 -0700 Subject: [PATCH 15/18] Implement Vulkan blend source/blend dest pixel history --- renderdoc/driver/vulkan/vk_pixelhistory.cpp | 221 ++++++++++++++++++-- 1 file changed, 201 insertions(+), 20 deletions(-) diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index 5081f90a78..edde480857 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -63,12 +63,17 @@ * * - Fourth callback: Per fragment callback (VulkanPixelHistoryPerFragmentCallback) * This callback is used to get per fragment data for each event and fragment (primitive ID, - * shader output value, dual source shader output value, post event value for each fragment). - * For each fragment the draw is replayed 4 times: + * shader output value, dual source shader output value, blending source/destination components, + * and post event value for each fragment). + * For each fragment the draw is replayed 8 times: * 1) with a fragment shader that outputs primitive ID only * 2) with blending OFF, to get shader output value * 3) with blending OFF and a modified shader, to get the output at index 1 (dual source) - * 4) with blending ON, to get post modification value + * 4) with blending ON, but with the stencil configured to only draw previous fragments + * 5) with blending ON but the destination factor set to 0, to get the source blend component + * 6) with blending ON, but with the stencil configured to only draw previous fragments + * 7) with blending ON but the source factor set to 0, to get the destination blend component + * 8) with blending ON, to get post modification value * For each such replay we set the stencil reference to the fragment number and set the * stencil compare to equal, so it passes for that particular fragment only. * @@ -252,6 +257,8 @@ struct PerFragmentInfo uint32_t padding[3]; PixelHistoryValue shaderOut; PixelHistoryValue shaderOutDualSrc; + PixelHistoryValue blendSrc; + PixelHistoryValue blendDst; PixelHistoryValue postMod; }; @@ -2879,8 +2886,15 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback VkPipeline shaderOutPipe; // Turn off blending, and swaps output indexes to get the dual source output. VkPipeline shaderOutDualSrcPipe; + // Enable blending but zero out the destination factor + VkPipeline blendSrcPipe; + // Enable blending but zero out the source factor + VkPipeline blendDstPipe; // Enable blending to get post event values. VkPipeline postModPipe; + // Enable blending to get post event values, and configure stencil to only draw up to + // immediately before the current event. + VkPipeline redrawToBeforePipe; }; void PreDraw(uint32_t eid, VkCommandBuffer cmd) @@ -3124,6 +3138,99 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback } } + // Get the blend source and destination components + const ModificationValue &premod = m_EventPremods[eid]; + if(m_HasBlend) + { + for(uint32_t f = 0; f < numFragmentsInEvent; f++) + { + for(uint32_t i = 0; i < 2; i++) + { + const uint32_t offset = (i == 0) ? offsetof(struct PerFragmentInfo, blendSrc) + : offsetof(struct PerFragmentInfo, blendDst); + VkMarkerRegion::Begin(StringFormat::Fmt("Prepare getting blend %s for fragment %u in %u", + i == 0 ? "source" : "destination", f, eid)); + + // Apply all draws BEFORE this point + // It's obvious why this is needed for the destination component; but for the source + // component, it is needed if the source factor uses the destination color or alpha. + state.graphics.pipeline = GetResID(pipes.redrawToBeforePipe); + state.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics, false); + + // Have to reset stencil. + VkClearAttachment stencilAtt = {}; + stencilAtt.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + VkClearRect rect = {}; + rect.rect.offset.x = m_CallbackInfo.x; + rect.rect.offset.y = m_CallbackInfo.y; + rect.rect.extent.width = 1; + rect.rect.extent.height = 1; + rect.baseArrayLayer = 0; + rect.layerCount = 1; + ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &stencilAtt, 1, &rect); + + // Before starting the draw, initialize the pixel to the premodification value + // for this event, for both color and depth. + VkClearAttachment clearAtts[2] = {}; + + clearAtts[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clearAtts[0].colorAttachment = colorOutputIndex; + memcpy(clearAtts[0].clearValue.color.float32, premod.col.floatValue.data(), + sizeof(clearAtts[0].clearValue.color)); + + clearAtts[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clearAtts[1].clearValue.depthStencil.depth = premod.depth; + + if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat)) + ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, clearAtts + 1, 1, &rect); + else + ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 2, clearAtts, 1, &rect); + + // Note: pipes.redrawToBeforePipe has the stencil compare op set to VK_COMPARE_OP_GREATER, + // so this reference is not inclusive + ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff); + ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff); + ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f); + + const ActionDescription *action = m_pDriver->GetAction(eid); + m_pDriver->ReplayDraw(cmd, *action); + + // Now actually draw the current one. Stencil op is set to VK_COMPARE_OP_EQUAL. + VkMarkerRegion::Set(StringFormat::Fmt("Getting blend %s for fragment %u in %u", + i == 0 ? "source" : "destination", f, eid), + cmd); + + state.graphics.pipeline = GetResID(i == 0 ? pipes.blendSrcPipe : pipes.blendDstPipe); + state.BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics, false); + + ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &stencilAtt, 1, &rect); + ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff); + ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff); + ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f); + + m_pDriver->ReplayDraw(cmd, *action); + state.EndRenderPass(cmd); + + CopyImagePixel(cmd, colourCopyParams, + (fragsProcessed + f) * sizeof(PerFragmentInfo) + offset); + + // TODO: is this useful? + if(depthImage != VK_NULL_HANDLE) + { + VkCopyPixelParams depthCopyParams = colourCopyParams; + depthCopyParams.srcImage = depthImage; + depthCopyParams.srcImageLayout = depthLayout; + depthCopyParams.srcImageFormat = depthFormat; + CopyImagePixel(cmd, depthCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) + + offset + + offsetof(struct PixelHistoryValue, depth)); + } + + VkMarkerRegion::End(); + } + } + } + // use the original renderpass and framebuffer attachment, but ensure we have depth and stencil state.SetRenderPass(prevState.GetRenderPass()); state.SetFramebuffer(prevState.GetFramebuffer(), prevState.GetFramebufferAttachments()); @@ -3142,7 +3249,6 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback GetResID(m_CallbackInfo.targetImage), aspect, m_CallbackInfo.targetSubresource.mip, m_CallbackInfo.targetSubresource.slice); - const ModificationValue &premod = m_EventPremods[eid]; // For every fragment except the last one, retrieve post-modification // value. for(uint32_t f = 0; f < numFragmentsInEvent - 1; f++) @@ -3322,19 +3428,32 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback pipeCreateInfo.renderPass = rp; + ds->front.compareOp = VK_COMPARE_OP_GREATER; + ds->back.compareOp = VK_COMPARE_OP_GREATER; + + // Note: this uses the modified renderpass, as we switch to a different pipeline with the + // modified renderpass + vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1, + &pipeCreateInfo, NULL, &pipes.redrawToBeforePipe); + m_pDriver->CheckVkResult(vkr); + m_PipesToDestroy.push_back(pipes.redrawToBeforePipe); + + // Revert to the previous stencil op + ds->front.compareOp = VK_COMPARE_OP_EQUAL; + ds->back.compareOp = VK_COMPARE_OP_EQUAL; + VkPipelineColorBlendStateCreateInfo *cbs = (VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState; - // Turn off blending so that we can get shader output values. - VkPipelineColorBlendAttachmentState *atts = - (VkPipelineColorBlendAttachmentState *)cbs->pAttachments; + rdcarray newAtts; + newAtts.resize(colorOutputIndex == cbs->attachmentCount ? cbs->attachmentCount + 1 + : cbs->attachmentCount); + memcpy(newAtts.data(), cbs->pAttachments, + cbs->attachmentCount * sizeof(VkPipelineColorBlendAttachmentState)); // Check if we need to add a new color attachment. if(colorOutputIndex == cbs->attachmentCount) { - newAtts.resize(cbs->attachmentCount + 1); - memcpy(newAtts.data(), cbs->pAttachments, - cbs->attachmentCount * sizeof(VkPipelineColorBlendAttachmentState)); VkPipelineColorBlendAttachmentState newAtt = {}; if(cbs->attachmentCount > 0) { @@ -3346,20 +3465,27 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback { newAtt.blendEnable = VK_FALSE; } - newAtts[cbs->attachmentCount] = newAtt; - cbs->attachmentCount = (uint32_t)newAtts.size(); - cbs->pAttachments = newAtts.data(); - - atts = newAtts.data(); + newAtts[colorOutputIndex] = newAtt; + cbs->logicOpEnable = VK_FALSE; } + cbs->attachmentCount = (uint32_t)newAtts.size(); + cbs->pAttachments = newAtts.data(); + + // Turn off blending so that we can get shader output values. for(uint32_t i = 0; i < cbs->attachmentCount; i++) { - atts[i].blendEnable = 0; - atts[i].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + if(newAtts[i].blendEnable) + m_HasBlend = true; + newAtts[i].blendEnable = VK_FALSE; + newAtts[i].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; } + // Logic ops treat it as if blendEnable were false + if(cbs->logicOpEnable) + m_HasBlend = false; + { ds->depthBoundsTestEnable = VK_FALSE; ds->depthWriteEnable = VK_TRUE; @@ -3393,6 +3519,52 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback pipeCreateInfo.pStages = stages.data(); } + if(m_HasBlend) + { + rdcarray blendModifiedAtts; + blendModifiedAtts.resize(newAtts.size()); + cbs->pAttachments = blendModifiedAtts.data(); + + // Get the source component (shader out * source factor) of the blend equation + for(uint32_t i = 0; i < cbs->attachmentCount; i++) + { + blendModifiedAtts[i] = newAtts[i]; + blendModifiedAtts[i].blendEnable = VK_TRUE; + // Without changing the op, VK_BLEND_OP_REVERSE_SUBTRACT would cause problems + blendModifiedAtts[i].colorBlendOp = VK_BLEND_OP_ADD; + blendModifiedAtts[i].dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + blendModifiedAtts[i].alphaBlendOp = VK_BLEND_OP_ADD; + blendModifiedAtts[i].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + } + + vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1, + &pipeCreateInfo, NULL, &pipes.blendSrcPipe); + m_pDriver->CheckVkResult(vkr); + + m_PipesToDestroy.push_back(pipes.blendSrcPipe); + + // Get the destination component (pre mod * destination factor) of the blend equation + for(uint32_t i = 0; i < cbs->attachmentCount; i++) + { + blendModifiedAtts[i] = newAtts[i]; + blendModifiedAtts[i].blendEnable = VK_TRUE; + // Without changing the op, VK_BLEND_OP_SUBTRACT would cause problems + blendModifiedAtts[i].colorBlendOp = VK_BLEND_OP_ADD; + blendModifiedAtts[i].srcColorBlendFactor = VK_BLEND_FACTOR_ZERO; + blendModifiedAtts[i].alphaBlendOp = VK_BLEND_OP_ADD; + blendModifiedAtts[i].srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + } + + vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1, + &pipeCreateInfo, NULL, &pipes.blendDstPipe); + m_pDriver->CheckVkResult(vkr); + + m_PipesToDestroy.push_back(pipes.blendDstPipe); + + // Return to blending being disabled + cbs->pAttachments = newAtts.data(); + } + { ds->depthTestEnable = VK_FALSE; ds->depthWriteEnable = VK_FALSE; @@ -3472,6 +3644,7 @@ struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback return it->second; } + bool m_HasBlend = false; bool m_HasDualSrc = false; private: @@ -4497,8 +4670,16 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even else history[h].shaderOutDualSrc.SetInvalid(); - history[h].blendSrc.SetInvalid(); - history[h].blendDst.SetInvalid(); + if(perFragmentCB.m_HasBlend) + { + FillInColor(shaderOutFormat, bp[offset].blendSrc, history[h].blendSrc); + FillInColor(shaderOutFormat, bp[offset].blendDst, history[h].blendDst); + } + else + { + history[h].blendSrc.SetInvalid(); + history[h].blendDst.SetInvalid(); + } } // check the depth value between premod/shaderout against the known test if we have valid From 94f9b139677f4476a8eab99c47c3e3e3566a3bdf Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:46:41 -0700 Subject: [PATCH 16/18] Allow configuring the surface format for Vulkan demos --- util/test/demos/vk/vk_test.cpp | 16 +++++++++++----- util/test/demos/vk/vk_test.h | 8 ++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/util/test/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp index 1ed14b92cc..2267f43cea 100644 --- a/util/test/demos/vk/vk_test.cpp +++ b/util/test/demos/vk/vk_test.cpp @@ -706,7 +706,7 @@ bool VulkanGraphicsTest::Init() if(!headless) { - mainWindow = MakeWindow(screenWidth, screenHeight, "Autotesting"); + mainWindow = MakeWindow(screenWidth, screenHeight, "Autotesting", requestedSwapChainFormat); if(!mainWindow->Initialised()) { @@ -784,7 +784,8 @@ bool VulkanGraphicsTest::Init() return true; } -VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char *title) +VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char *title, + VkFormat requestedFormat) { #if defined(WIN32) GraphicsWindow *platWin = new Win32Window(width, height, title); @@ -798,7 +799,7 @@ VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char * #error UNKNOWN PLATFORM #endif - return new VulkanWindow(this, platWin); + return new VulkanWindow(this, platWin, requestedFormat); } void VulkanGraphicsTest::Shutdown() @@ -1350,11 +1351,12 @@ void VulkanCommands::Submit(const std::vector &cmds, pendingCommandBuffers[1].push_back(std::make_pair(cmd, fence)); } -VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) +VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win, VkFormat requestedFormat) : GraphicsWindow(win->title), VulkanCommands(test) { m_Test = test; m_Win = win; + m_requestedFormat = requestedFormat; { std::lock_guard lock(m_Test->mutex); @@ -1464,7 +1466,7 @@ bool VulkanWindow::CreateSwapchain() for(const VkSurfaceFormatKHR &f : formats) { - if(f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + if(f.format == m_requestedFormat && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { surfaceFormat = f; break; @@ -1478,6 +1480,10 @@ bool VulkanWindow::CreateSwapchain() } format = surfaceFormat.format; + if(format != m_requestedFormat) + { + TEST_WARN("Wanted surface format %d but got %d", m_requestedFormat, format); + } std::vector modes; CHECK_VKR(vkh::getSurfacePresentModesKHR(modes, m_Test->phys, surface)); diff --git a/util/test/demos/vk/vk_test.h b/util/test/demos/vk/vk_test.h index e5fbc815b1..83271de777 100644 --- a/util/test/demos/vk/vk_test.h +++ b/util/test/demos/vk/vk_test.h @@ -129,13 +129,14 @@ struct VulkanCommands struct VulkanWindow : public GraphicsWindow, public VulkanCommands { + VkFormat m_requestedFormat; VkFormat format; uint32_t imgIndex = 0; VkRenderPass rp = VK_NULL_HANDLE; VkViewport viewport; VkRect2D scissor; - VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win); + VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win, VkFormat requestedFormat); virtual ~VulkanWindow(); void Shutdown(); @@ -197,7 +198,8 @@ struct VulkanGraphicsTest : public GraphicsTest void Prepare(int argc, char **argv); bool Init(); void Shutdown(); - VulkanWindow *MakeWindow(int width, int height, const char *title); + VulkanWindow *MakeWindow(int width, int height, const char *title, + VkFormat requestedFormat = VK_FORMAT_B8G8R8A8_SRGB); bool Running(); VkImage StartUsingBackbuffer(VkCommandBuffer cmd, @@ -288,6 +290,8 @@ struct VulkanGraphicsTest : public GraphicsTest uint32_t computeQueueFamilyIndex = ~0U; uint32_t transferQueueFamilyIndex = ~0U; + VkFormat requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_SRGB; + bool hasExt(const char *ext); // a custom struct to pass to vkDeviceCreateInfo::pNext From 3a689af36838515dcf6c4f329d8620d57b699ca2 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 16:53:40 -0700 Subject: [PATCH 17/18] Add dual source blending demo and test --- util/test/demos/CMakeLists.txt | 1 + util/test/demos/demos.vcxproj | 1 + util/test/demos/demos.vcxproj.filters | 3 + util/test/demos/vk/vk_dual_source.cpp | 266 +++++++++++++++++++++++ util/test/tests/Vulkan/VK_Dual_Source.py | 128 +++++++++++ 5 files changed, 399 insertions(+) create mode 100644 util/test/demos/vk/vk_dual_source.cpp create mode 100644 util/test/tests/Vulkan/VK_Dual_Source.py diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 108bf6d083..eb6f45f222 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -98,6 +98,7 @@ set(VULKAN_SRC vk/vk_discard_rects.cpp vk/vk_discard_zoo.cpp vk/vk_draw_zoo.cpp + vk/vk_dual_source.cpp vk/vk_dynamic_rendering.cpp vk/vk_empty_capture.cpp vk/vk_ext_buffer_address.cpp diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 19b54ca8fd..cbf13cc444 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -302,6 +302,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index d3cc6524c0..7c49195ca4 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -673,6 +673,9 @@ D3D12\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_dual_source.cpp b/util/test/demos/vk/vk_dual_source.cpp new file mode 100644 index 0000000000..ee5116032f --- /dev/null +++ b/util/test/demos/vk/vk_dual_source.cpp @@ -0,0 +1,266 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "vk_test.h" + +static std::string common = R"EOSHADER( + +#version 460 core + +#define v2f v2f_block \ +{ \ + vec4 pos; \ + vec4 col; \ + vec4 uv; \ +} + +)EOSHADER"; + +const std::string pixel = common + R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0) out vec4 outColor0; +layout(location = 0, index = 1) out vec4 outColor1; + +void main() { + vec3 fragColor = vertIn.col.rgb; + + outColor0 = vec4(fragColor, 1.0); + outColor1 = vec4(fragColor.brg, 1.0); +} +)EOSHADER"; + +// This shader is equivalent to the other one, just with a much more complicated-looking layout +const std::string pixel_complicated = common + R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0, component = 0) out float outColor0_r; +layout(location = 0, /* index = 0, */ component = 1) out float outColor0_g; +layout(location = 0, /* index = 0, */ component = 2) out vec2 outColor0_ba; +layout(location = 0, index = 1/* , component = 0 */) out float outColor1_r; +layout(location = 0, index = 1, component = 1) out vec2 outColor1_gb; +layout(location = 0, index = 1, component = 3) out float outColor1_a; +// Test so that we have an output builtin. depth_unchanged means nothing interesting +// actually happens (not sure why it exists, but it's nice here) +layout(depth_unchanged) out float gl_FragDepth; + +void main() { + vec4 outColor0, outColor1; + + vec3 fragColor = vertIn.col.rgb; + + outColor0 = vec4(fragColor, 1.0); + outColor1 = vec4(fragColor.brg, 1.0); + + outColor0_r = outColor0.r; + outColor0_g = outColor0.g; + outColor0_ba = outColor0.ba; + outColor1_r = outColor1.r; + outColor1_gb = outColor1.gb; + outColor1_a = outColor1.a; + gl_FragDepth = gl_FragCoord.z; +} +)EOSHADER"; + +RD_TEST(VK_Dual_Source, VulkanGraphicsTest) +{ + static constexpr const char *Description = "Draws a pair of triangles using dual source blending"; + + const DefaultA2V Triangles[12] = { + // Two partially overlapping triangles + {Vec3f(+0.0f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(+0.5f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-0.5f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(1.0f, 0.0f)}, + {Vec3f(-0.25f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(+0.75f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(+0.25f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(1.0f, 0.0f)}, + }; + + void Prepare(int argc, char **argv) + { + features.dualSrcBlend = VK_TRUE; + VulkanGraphicsTest::Prepare(argc, argv); + } + + void clear(VkCommandBuffer cmd, VkImage swapimg, const AllocatedImage &offimg, + const AllocatedImage &offimgMS, const VkClearColorValue *pColor) + { + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + } + + int main() + { + // initialise, create window, create context, etc + requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_UNORM; + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC1_COLOR; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + pipeCreateInfo.colorBlendState.attachments = {colorBlendAttachment}; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + pipeCreateInfo.stages[1] = + CompileShaderModule(pixel_complicated, ShaderLang::glsl, ShaderStage::frag, "main"); + + VkPipeline complicatedPipe = createGraphicsPipeline(pipeCreateInfo); + + AllocatedBuffer vb( + this, vkh::BufferCreateInfo(sizeof(Triangles), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(Triangles); + + AllocatedImage offimg(this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + AllocatedImage offimgMS( + this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1, 1, VK_SAMPLE_COUNT_4_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + + setMarker(cmd, "Begin test A1"); + vkCmdDraw(cmd, 6, 1, 0, 0); + setMarker(cmd, "End test A1"); + + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + setMarker(cmd, "Begin test A2"); + vkCmdDraw(cmd, 3, 1, 0, 0); + vkCmdDraw(cmd, 3, 1, 3, 0); + setMarker(cmd, "End test A2"); + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, complicatedPipe); + setMarker(cmd, "Begin test B1"); + vkCmdDraw(cmd, 6, 1, 0, 0); + setMarker(cmd, "End test B1"); + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + setMarker(cmd, "Begin test B2"); + vkCmdDraw(cmd, 3, 1, 0, 0); + vkCmdDraw(cmd, 3, 1, 3, 0); + setMarker(cmd, "End test B2"); + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/tests/Vulkan/VK_Dual_Source.py b/util/test/tests/Vulkan/VK_Dual_Source.py new file mode 100644 index 0000000000..2b3fce03bb --- /dev/null +++ b/util/test/tests/Vulkan/VK_Dual_Source.py @@ -0,0 +1,128 @@ +import renderdoc as rd +import rdtest +from typing import List + +def value_selector(x): return x.floatValue +def passed(x): return x.Passed() +def event_id(x): return x.eventId +def culled(x): return x.backfaceCulled +def depth_test_failed(x): return x.depthTestFailed +def depth_clipped(x): return x.depthClipped +def depth_bounds_failed(x): return x.depthBoundsFailed +def scissor_clipped(x): return x.scissorClipped +def stencil_test_failed(x): return x.stencilTestFailed +def shader_discarded(x): return x.shaderDiscarded +def shader_out_col(x): return value_selector(x.shaderOut.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_col(x): return value_selector(x.preMod.col) +def post_mod_col(x): return value_selector(x.postMod.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_depth(x): return x.preMod.depth +def post_mod_depth(x): return x.postMod.depth +def primitive_id(x): return x.primitiveID +def unboundPS(x): return x.unboundPS + +NUM_TRIANGLES_RED = 16 +NUM_TRIANGLES_RED_REAL = NUM_TRIANGLES_RED * 2 - 1 +NUM_TRIANGLES_GREEN = 255 +NUM_TRIANGLES_BLUE = 512 + +class VK_Dual_Source(rdtest.TestCase): + demos_test_name = 'VK_Dual_Source' + demos_frame_cap = 5 + + def check_capture(self): + apiprops: rd.APIProperties = self.controller.GetAPIProperties() + + if not apiprops.pixelHistory: + rdtest.log.print("Vulkan pixel history not tested") + return + + self.primary_test() + + def primary_test(self): + test_marker: rd.ActionDescription = self.find_action("End test B2") + self.controller.SetFrameEvent(test_marker.eventId, True) + + pipe: rd.PipeState = self.controller.GetPipelineState() + + rt: rd.BoundResource = pipe.GetOutputTargets()[0] + + tex = rt.resourceId + tex_details = self.get_texture(tex) + + sub = rd.Subresource() + if tex_details.arraysize > 1: + sub.slice = rt.firstSlice + if tex_details.mips > 1: + sub.mip = rt.firstMip + + # Pixel inside first triangle + modifs_1: List[rd.PixelModification] = self.controller.PixelHistory(tex, 175, 125, sub, rt.typeCast) + # Pixel inside second triangle + modifs_2: List[rd.PixelModification] = self.controller.PixelHistory(tex, 275, 175, sub, rt.typeCast) + # Pixel inside both triangles + modifs_both: List[rd.PixelModification] = self.controller.PixelHistory(tex, 225, 150, sub, rt.typeCast) + + self.check_range(modifs_1, modifs_2, modifs_both, "A1") + self.check_range(modifs_1, modifs_2, modifs_both, "A2") + self.check_range(modifs_1, modifs_2, modifs_both, "B1") + self.check_range(modifs_1, modifs_2, modifs_both, "B2") + + def check_range(self, orig_modifs_1, orig_modifs_2, orig_modifs_both, group): + start_eid = self.find_action("Begin test " + group).eventId + end_eid = self.find_action("End test " + group).eventId + modifs_1 = [m for m in orig_modifs_1 if m.eventId > start_eid and m.eventId < end_eid] + modifs_2 = [m for m in orig_modifs_2 if m.eventId > start_eid and m.eventId < end_eid] + modifs_both = [m for m in orig_modifs_both if m.eventId > start_eid and m.eventId < end_eid] + if not len(modifs_1) == 1: + raise rdtest.TestFailureException( + "group {}: expected 1 modification but found {}".format(group, len(modifs_1))) + if not len(modifs_2) == 1: + raise rdtest.TestFailureException( + "group {}: expected 1 modification but found {}".format(group, len(modifs_2))) + if not len(modifs_both) == 2: + raise rdtest.TestFailureException( + "group {}: expected 2 modifications but found {}".format(group, len(modifs_both))) + modif_1 = modifs_1[0] + modif_2 = modifs_2[0] + modif_both_1 = modifs_both[0] + modif_both_2 = modifs_both[1] + # modif_both_1 should match modif_1 as both are the same triangle on the same background + assert(value_selector(modif_1.preMod.col) == value_selector(modif_both_1.preMod.col)) + assert(value_selector(modif_1.shaderOut.col) == value_selector(modif_both_1.shaderOut.col)) + assert(value_selector(modif_1.shaderOutDualSrc.col) == value_selector(modif_both_1.shaderOutDualSrc.col)) + assert(value_selector(modif_1.blendSrc.col) == value_selector(modif_both_1.blendSrc.col)) + assert(value_selector(modif_1.blendDst.col) == value_selector(modif_both_1.blendDst.col)) + assert(value_selector(modif_1.postMod.col) == value_selector(modif_both_1.postMod.col)) + + # Triangle 1 outputs (0.5, 0.0, 0.0, 1.0) to source 0, (0.0, 0.5, 0.0, 1.0) to source 1 + # Background is (0.0, 1.0, 1.0, 1.0) + # Blend is source0*1 + dest*source1, so the final output should be (0.5, 0.5, 0.0, 1.0) + # eps=.01 because different GPUs handle blending differently + epsilon = .01 + assert(rdtest.value_compare(value_selector(modif_1.preMod.col), (0.0, 1.0, 1.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.shaderOut.col), (0.5, 0.0, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.shaderOutDualSrc.col), (0.0, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.blendSrc.col), (0.5, 0.0, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.blendDst.col), (0.0, 0.5, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.postMod.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + + # Triangle 2 outputs (0.5, 0.0, 0.5, 1.0) to source 0, (0.5, 0.5, 0.0, 1.0) to source 1 + # On the (0.0, 1.0, 1.0, 1.0) background the final output is (0.5, 0.0, 0.5, 1.0) added + # to (0.0, 0.5, 0.0, 0.0) = (0.5, 0.5, 0.5, 0.0) + assert(rdtest.value_compare(value_selector(modif_2.preMod.col), (0.0, 1.0, 1.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.shaderOut.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.shaderOutDualSrc.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.blendSrc.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.blendDst.col), (0.0, 0.5, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.postMod.col), (0.5, 0.5, 0.5, 1.0), eps=epsilon)) + + # Triangle 2 on top of triangle 1's output of (0.5, 0.5, 0.0, 1.0) is (0.5, 0.0, 0.5, 1.0) + # added to (0.25, 0.25, 0.0, 0.0) = (0.75, 0.25, 0.5, 1.0) + assert(rdtest.value_compare(value_selector(modif_both_2.preMod.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.shaderOut.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.shaderOutDualSrc.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.blendSrc.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.blendDst.col), (0.25, 0.25, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.postMod.col), (0.75, 0.25, 0.5, 1.0), eps=epsilon)) From c115cb68d2ed433cfb094c95ca09a1b968ce0f55 Mon Sep 17 00:00:00 2001 From: William Pearson Date: Fri, 28 Jul 2023 17:12:23 -0700 Subject: [PATCH 18/18] Add demo/test for many primitives hitting the same pixel in one drawcall --- util/test/demos/CMakeLists.txt | 1 + util/test/demos/demos.vcxproj | 1 + util/test/demos/demos.vcxproj.filters | 3 + util/test/demos/vk/vk_blend.cpp | 246 ++++++++++++++++++ .../tests/Vulkan/VK_Blend_Pixel_History.py | 208 +++++++++++++++ 5 files changed, 459 insertions(+) create mode 100644 util/test/demos/vk/vk_blend.cpp create mode 100644 util/test/tests/Vulkan/VK_Blend_Pixel_History.py diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index eb6f45f222..80fd454306 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -87,6 +87,7 @@ set(VULKAN_SRC vk/vk_test.cpp vk/vk_test.h vk/vk_adv_cbuffer_zoo.cpp + vk/vk_blend.cpp vk/vk_buffer_truncation.cpp vk/vk_cbuffer_zoo.cpp vk/vk_compute_only.cpp diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index cbf13cc444..5ef7b36956 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -296,6 +296,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 7c49195ca4..798331d145 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -676,6 +676,9 @@ Vulkan\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_blend.cpp b/util/test/demos/vk/vk_blend.cpp new file mode 100644 index 0000000000..232343c7af --- /dev/null +++ b/util/test/demos/vk/vk_blend.cpp @@ -0,0 +1,246 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "vk_test.h" + +RD_TEST(VK_Blend, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Draws a triangle repeatedly to test blending within a single drawcall"; + + const DefaultA2V TemplateTriangleRed[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_RED_INDEX = 0; + const int NUM_TRIANGLES_RED = 16; + const DefaultA2V TemplateTriangleGreen[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_GREEN_INDEX = TRIANGLES_RED_INDEX + NUM_TRIANGLES_RED; + const int NUM_TRIANGLES_GREEN = 255; + const DefaultA2V TemplateTriangleBlue[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_BLUE_INDEX = TRIANGLES_GREEN_INDEX + NUM_TRIANGLES_GREEN; + const int NUM_TRIANGLES_BLUE = 512; + + const int NUM_TRIANGLES_TOTAL = TRIANGLES_BLUE_INDEX + NUM_TRIANGLES_BLUE; + + int main() + { + // initialise, create window, create context, etc + requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_UNORM; + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + pipeCreateInfo.colorBlendState.attachments = {colorBlendAttachment}; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + std::vector triangles; + triangles.reserve(3 * NUM_TRIANGLES_TOTAL); + for(int i = 0; i < NUM_TRIANGLES_RED; i++) + { + triangles.push_back(TemplateTriangleRed[0]); + triangles.push_back(TemplateTriangleRed[1]); + triangles.push_back(TemplateTriangleRed[2]); + } + for(int i = 0; i < NUM_TRIANGLES_GREEN; i++) + { + triangles.push_back(TemplateTriangleGreen[0]); + triangles.push_back(TemplateTriangleGreen[1]); + triangles.push_back(TemplateTriangleGreen[2]); + } + for(int i = 0; i < NUM_TRIANGLES_BLUE; i++) + { + triangles.push_back(TemplateTriangleBlue[0]); + triangles.push_back(TemplateTriangleBlue[1]); + triangles.push_back(TemplateTriangleBlue[2]); + } + + AllocatedBuffer vb(this, vkh::BufferCreateInfo(sizeof(DefaultA2V) * 3 * NUM_TRIANGLES_TOTAL, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(triangles.data(), sizeof(DefaultA2V) * 3 * NUM_TRIANGLES_TOTAL); + + AllocatedImage offimg(this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + AllocatedImage offimgMS( + this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1, 1, VK_SAMPLE_COUNT_4_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + pushMarker(cmd, "Red: groups of repeated draws"); + for(int i = 1; i <= NUM_TRIANGLES_RED; i *= 2) + { + vkCmdDraw(cmd, 3 * i, 1, TRIANGLES_RED_INDEX, 0); + } + setMarker(cmd, "End of red"); + popMarker(cmd); + pushMarker(cmd, "Green: 255 (the maximum we can handle) in a single drawcall"); + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_GREEN, 1, 3 * TRIANGLES_GREEN_INDEX, 0); + popMarker(cmd); + // Note: this is indistinguishable from 255 triangles since 255 * 1/255 is 1, + // and 512 * 1/255 > 1 but gets clamped to 1 + pushMarker(cmd, "Blue: 512 (more than the maximum) in a single drawcall"); + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_BLUE, 1, 3 * TRIANGLES_BLUE_INDEX, 0); + popMarker(cmd); + + vkCmdEndRenderPass(cmd); + + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + pushMarker(cmd, "All of the above in a single drawcall"); + // This _is_ distinguishable from 512 blue triangles since we have a mix of red, green, and + // blue + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_TOTAL, 1, 3 * TRIANGLES_RED_INDEX, 0); + popMarker(cmd); + + setMarker(cmd, "Test End"); + + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/tests/Vulkan/VK_Blend_Pixel_History.py b/util/test/tests/Vulkan/VK_Blend_Pixel_History.py new file mode 100644 index 0000000000..55b0cf260f --- /dev/null +++ b/util/test/tests/Vulkan/VK_Blend_Pixel_History.py @@ -0,0 +1,208 @@ +import renderdoc as rd +import rdtest +from typing import List + +def value_selector(x): return x.floatValue +def passed(x): return x.Passed() +def event_id(x): return x.eventId +def culled(x): return x.backfaceCulled +def depth_test_failed(x): return x.depthTestFailed +def depth_clipped(x): return x.depthClipped +def depth_bounds_failed(x): return x.depthBoundsFailed +def scissor_clipped(x): return x.scissorClipped +def stencil_test_failed(x): return x.stencilTestFailed +def shader_discarded(x): return x.shaderDiscarded +def shader_out_col(x): return value_selector(x.shaderOut.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_col(x): return value_selector(x.preMod.col) +def post_mod_col(x): return value_selector(x.postMod.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_depth(x): return x.preMod.depth +def post_mod_depth(x): return x.postMod.depth +def primitive_id(x): return x.primitiveID +def unboundPS(x): return x.unboundPS + +NUM_TRIANGLES_RED = 16 +NUM_TRIANGLES_RED_REAL = NUM_TRIANGLES_RED * 2 - 1 +NUM_TRIANGLES_GREEN = 255 +NUM_TRIANGLES_BLUE = 512 + +class VK_Blend_Pixel_History(rdtest.TestCase): + demos_test_name = 'VK_Blend' + demos_frame_cap = 5 + + def check_capture(self): + apiprops: rd.APIProperties = self.controller.GetAPIProperties() + + if not apiprops.pixelHistory: + rdtest.log.print("Vulkan pixel history not tested") + return + + self.primary_test() + + def primary_test(self): + test_marker: rd.ActionDescription = self.find_action("Test End") + self.controller.SetFrameEvent(test_marker.eventId, True) + + pipe: rd.PipeState = self.controller.GetPipelineState() + + rt: rd.BoundResource = pipe.GetOutputTargets()[0] + + tex = rt.resourceId + tex_details = self.get_texture(tex) + + sub = rd.Subresource() + if tex_details.arraysize > 1: + sub.slice = rt.firstSlice + if tex_details.mips > 1: + sub.mip = rt.firstMip + + red_eid = self.find_action("Red: groups of repeated draws").next.eventId + red_last_eid = self.find_action("End of red").next.eventId + green_eid = self.find_action("Green: 255 (the maximum we can handle) in a single drawcall").next.eventId + blue_eid = self.find_action("Blue: 512 (more than the maximum) in a single drawcall").next.eventId + all_eid = self.find_action("All of the above in a single drawcall").next.eventId + + # Pixel inside of all of the triangles + x, y = 200, 150 + rdtest.log.print("Testing pixel {}, {}".format(x, y)) + modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) + self.check_modifs_consistent(modifs) + red_modifs = [m for m in modifs if m.eventId >= red_eid and m.eventId < red_last_eid] + green_modifs = [m for m in modifs if m.eventId == green_eid] + blue_modifs = [m for m in modifs if m.eventId == blue_eid] + all_modifs = [m for m in modifs if m.eventId == all_eid] + + if len(red_modifs) != NUM_TRIANGLES_RED_REAL: + raise rdtest.TestFailureException("Expected {} modifications for red triangles (EIDS {} until {}) but got {}".format(NUM_TRIANGLES_RED_REAL, red_eid, red_last_eid, len(red_modifs))) + + for i, modif in enumerate(red_modifs): + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for red triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for red triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0))) + + i = 1 + eid_counter = 0 + modif_counter = 0 + while i <= NUM_TRIANGLES_RED: + for primitive_id in range(i): + if red_modifs[modif_counter].eventId != red_eid + eid_counter: + raise rdtest.TestFailureException("Expected red triangle {} to be part of EID {} but was {}".format(modif_counter, red_eid + eid_counter, red_modifs[modif_counter].eventId)) + if red_modifs[modif_counter].primitiveID != primitive_id: + raise rdtest.TestFailureException("Expected red triangle {} to have primitive ID {} but was {}".format(modif_counter, primitive_id, red_modifs[modif_counter].primitiveID)) + modif_counter += 1 + eid_counter += 1 + i *= 2 + + if len(green_modifs) != NUM_TRIANGLES_GREEN: + raise rdtest.TestFailureException("Expected {} modifications for green triangles (EID {}) but got {}".format(NUM_TRIANGLES_GREEN, green_eid, len(gren_modifs))) + + for i, modif in enumerate(green_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected green triangle {} to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for green triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, (i+1)/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for green triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, (i+1)/255.0, 0.0, 1.0))) + + # We can only record 255 modifications due to the stencil format + if len(blue_modifs) != 255: + raise rdtest.TestFailureException("Expected {} modifications for blue triangles (EID {}) but got {}".format(255, blue_eid, len(blue_modifs))) + + for i, modif in enumerate(blue_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected blue triangle {} to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 0.0, 1.0/255.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for blue triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 0.0, 1.0/255.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, (i+1)/255.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for blue triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, (i+1)/255.0, 1.0))) + + # Once again, we can only record 255 modifications due to the stencil format + if len(all_modifs) != 255: + raise rdtest.TestFailureException("Expected {} modifications for all triangles (EID {}) but got {}".format(255, all_eid, len(all_modifs))) + + for i, modif in enumerate(all_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected triangle {} in all to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + + if i < NUM_TRIANGLES_RED: + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for red triangle in all {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for red triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0))) + else: + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for green triangle in all {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0))) + if i != 254: + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, (i+1-NUM_TRIANGLES_RED)/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for green triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, (i+1-NUM_TRIANGLES_RED)/255.0, 0.0, 1.0))) + else: + # For i = 254 (the last triangle), the post-mod value is always set to the final post-mod value, but everything else is correctly set to the 255th modification + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, 1.0, 1.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for final (blue) triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, 1.0, 1.0, 1.0))) + + def check_modifs_consistent(self, modifs): + # postmod of each should match premod of the next + for i in range(len(modifs) - 1): + a = value_selector(modifs[i].postMod.col) + b = value_selector(modifs[i + 1].preMod.col) + + if a != b: + raise rdtest.TestFailureException( + "postmod at {} primitive {}: {} doesn't match premod at {} primitive {}: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, + modifs[i + 1].eventId, + modifs[i + 1].primitiveID, + b)) + + + # With this test's blend configuration, shaderOut and blendSrc should match + for i in range(len(modifs)): + if not modifs[i].blendSrc.IsValid(): # Applies to clears + continue + + a = value_selector(modifs[i].shaderOut.col) + b = value_selector(modifs[i].blendSrc.col) + + if not rdtest.value_compare(a, b, eps=1.0/256.0): + raise rdtest.TestFailureException( + "shaderOut at {} primitive {}: {} doesn't match blendSrc: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b)) + + # With this test's blend configuration, preMod and blendDst should match + for i in range(len(modifs)): + if not modifs[i].blendDst.IsValid(): # Applies to clears + continue + + a = value_selector(modifs[i].preMod.col)[:3] + b = value_selector(modifs[i].blendDst.col)[:3] + + if not rdtest.value_compare(a, b, eps=1.0/256.0): + raise rdtest.TestFailureException( + "preMod at {} primitive {}: {} doesn't match blendDst: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b)) + + # With this test's blend configuration, and good driver behavior, blendSrc + blendDst should be about equal to postMod + for i in range(len(modifs)): + if not modifs[i].blendDst.IsValid(): # Applies to clears + continue + + if i == len(modifs) - 1: + # Final modifs entry, doesn't behave nicely because postMod is wrong + # This doesn't affect the blue trianges since everything after 255 has blue of 1.0 + continue + + a = value_selector(modifs[i].blendSrc.col) + b = value_selector(modifs[i].blendDst.col) + sum_a_b = [sum(x) for x in zip(a, b)] + c = value_selector(modifs[i].postMod.col) + + if not rdtest.value_compare(sum_a_b, c, eps=1.0/256.0): + raise rdtest.TestFailureException( + "blendSrc at {} primitive {}: {} plus blendDst: {} (sum: {}) should equal postMod: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b, sum_a_b, c))