mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 17:40:39 +00:00
925 lines
27 KiB
C++
925 lines
27 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016-2018 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 "PipelineStateViewer.h"
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QSvgRenderer>
|
|
#include <QXmlStreamWriter>
|
|
#include "3rdparty/toolwindowmanager/ToolWindowManager.h"
|
|
#include "Code/QRDUtils.h"
|
|
#include "Code/Resources.h"
|
|
#include "Widgets/Extended/RDLabel.h"
|
|
#include "D3D11PipelineStateViewer.h"
|
|
#include "D3D12PipelineStateViewer.h"
|
|
#include "GLPipelineStateViewer.h"
|
|
#include "VulkanPipelineStateViewer.h"
|
|
#include "ui_PipelineStateViewer.h"
|
|
|
|
PipelineStateViewer::PipelineStateViewer(ICaptureContext &ctx, QWidget *parent)
|
|
: QFrame(parent), ui(new Ui::PipelineStateViewer), m_Ctx(ctx)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
m_D3D11 = NULL;
|
|
m_D3D12 = NULL;
|
|
m_GL = NULL;
|
|
m_Vulkan = NULL;
|
|
|
|
m_Current = NULL;
|
|
|
|
setToD3D11();
|
|
|
|
m_Ctx.AddCaptureViewer(this);
|
|
}
|
|
|
|
PipelineStateViewer::~PipelineStateViewer()
|
|
{
|
|
reset();
|
|
|
|
m_Ctx.BuiltinWindowClosed(this);
|
|
m_Ctx.RemoveCaptureViewer(this);
|
|
|
|
delete ui;
|
|
}
|
|
|
|
void PipelineStateViewer::OnCaptureLoaded()
|
|
{
|
|
if(m_Ctx.APIProps().pipelineType == GraphicsAPI::D3D11)
|
|
setToD3D11();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::D3D12)
|
|
setToD3D12();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::OpenGL)
|
|
setToGL();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan)
|
|
setToVulkan();
|
|
|
|
if(m_Current)
|
|
m_Current->OnCaptureLoaded();
|
|
}
|
|
|
|
void PipelineStateViewer::OnCaptureClosed()
|
|
{
|
|
if(m_Current)
|
|
m_Current->OnCaptureClosed();
|
|
}
|
|
|
|
void PipelineStateViewer::OnEventChanged(uint32_t eventId)
|
|
{
|
|
if(m_Ctx.APIProps().pipelineType == GraphicsAPI::D3D11)
|
|
setToD3D11();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::D3D12)
|
|
setToD3D12();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::OpenGL)
|
|
setToGL();
|
|
else if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan)
|
|
setToVulkan();
|
|
|
|
if(m_Current)
|
|
m_Current->OnEventChanged(eventId);
|
|
}
|
|
|
|
QString PipelineStateViewer::GetCurrentAPI()
|
|
{
|
|
if(m_Current == m_D3D11)
|
|
return lit("D3D11");
|
|
else if(m_Current == m_D3D12)
|
|
return lit("D3D12");
|
|
else if(m_Current == m_GL)
|
|
return lit("OpenGL");
|
|
else if(m_Current == m_Vulkan)
|
|
return lit("Vulkan");
|
|
|
|
return lit("");
|
|
}
|
|
|
|
QVariant PipelineStateViewer::persistData()
|
|
{
|
|
QVariantMap state;
|
|
|
|
state[lit("type")] = GetCurrentAPI();
|
|
|
|
return state;
|
|
}
|
|
|
|
void PipelineStateViewer::setPersistData(const QVariant &persistData)
|
|
{
|
|
QString str = persistData.toMap()[lit("type")].toString();
|
|
|
|
if(str == lit("D3D11"))
|
|
setToD3D11();
|
|
else if(str == lit("D3D12"))
|
|
setToD3D12();
|
|
else if(str == lit("GL"))
|
|
setToGL();
|
|
else if(str == lit("Vulkan"))
|
|
setToVulkan();
|
|
}
|
|
|
|
void PipelineStateViewer::reset()
|
|
{
|
|
delete m_D3D11;
|
|
delete m_D3D12;
|
|
delete m_GL;
|
|
delete m_Vulkan;
|
|
|
|
m_D3D11 = NULL;
|
|
m_D3D12 = NULL;
|
|
m_GL = NULL;
|
|
m_Vulkan = NULL;
|
|
|
|
m_Current = NULL;
|
|
}
|
|
|
|
void PipelineStateViewer::setToD3D11()
|
|
{
|
|
if(m_D3D11)
|
|
return;
|
|
|
|
reset();
|
|
|
|
m_D3D11 = new D3D11PipelineStateViewer(m_Ctx, *this, this);
|
|
ui->layout->addWidget(m_D3D11);
|
|
m_Current = m_D3D11;
|
|
}
|
|
|
|
void PipelineStateViewer::setToD3D12()
|
|
{
|
|
if(m_D3D12)
|
|
return;
|
|
|
|
reset();
|
|
|
|
m_D3D12 = new D3D12PipelineStateViewer(m_Ctx, *this, this);
|
|
ui->layout->addWidget(m_D3D12);
|
|
m_Current = m_D3D12;
|
|
}
|
|
|
|
void PipelineStateViewer::setToGL()
|
|
{
|
|
if(m_GL)
|
|
return;
|
|
|
|
reset();
|
|
|
|
m_GL = new GLPipelineStateViewer(m_Ctx, *this, this);
|
|
ui->layout->addWidget(m_GL);
|
|
m_Current = m_GL;
|
|
}
|
|
|
|
void PipelineStateViewer::setToVulkan()
|
|
{
|
|
if(m_Vulkan)
|
|
return;
|
|
|
|
reset();
|
|
|
|
m_Vulkan = new VulkanPipelineStateViewer(m_Ctx, *this, this);
|
|
ui->layout->addWidget(m_Vulkan);
|
|
m_Current = m_Vulkan;
|
|
}
|
|
|
|
QXmlStreamWriter *PipelineStateViewer::beginHTMLExport()
|
|
{
|
|
QString filename = RDDialog::getSaveFileName(this, tr("Export pipeline state as HTML"), QString(),
|
|
tr("HTML files (*.html)"));
|
|
|
|
if(!filename.isEmpty())
|
|
{
|
|
ANALYTIC_SET(Export.PipelineState, true);
|
|
|
|
QDir dirinfo = QFileInfo(filename).dir();
|
|
if(dirinfo.exists())
|
|
{
|
|
QFile *f = new QFile(filename, this);
|
|
if(f->open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
{
|
|
QXmlStreamWriter *xmlptr = new QXmlStreamWriter(f);
|
|
|
|
QXmlStreamWriter &xml = *xmlptr;
|
|
|
|
xml.setAutoFormatting(true);
|
|
xml.setAutoFormattingIndent(4);
|
|
xml.writeStartDocument();
|
|
xml.writeDTD(lit("<!DOCTYPE html>"));
|
|
|
|
xml.writeStartElement(lit("html"));
|
|
xml.writeAttribute(lit("lang"), lit("en"));
|
|
|
|
QString title = tr("%1 EID %2 - %3 Pipeline export")
|
|
.arg(QFileInfo(m_Ctx.GetCaptureFilename()).fileName())
|
|
.arg(m_Ctx.CurEvent())
|
|
.arg(GetCurrentAPI());
|
|
|
|
{
|
|
xml.writeStartElement(lit("head"));
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("charset"), lit("utf-8"));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("http-equiv"), lit("X-UA-Compatible"));
|
|
xml.writeAttribute(lit("content"), lit("IE=edge"));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("name"), lit("viewport"));
|
|
xml.writeAttribute(lit("content"), lit("width=device-width, initial-scale=1"));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("name"), lit("description"));
|
|
xml.writeAttribute(lit("content"), lit(""));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("name"), lit("author"));
|
|
xml.writeAttribute(lit("content"), lit(""));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("meta"));
|
|
xml.writeAttribute(lit("http-equiv"), lit("Content-Type"));
|
|
xml.writeAttribute(lit("content"), lit("text/html;charset=utf-8"));
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("title"));
|
|
xml.writeCharacters(title);
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("style"));
|
|
xml.writeComment(lit(R"(
|
|
|
|
/* If you think this css is ugly/bad, open a pull request! */
|
|
body { margin: 20px; }
|
|
div.stage { border: 1px solid #BBBBBB; border-radius: 5px; padding: 16px; margin-bottom: 32px; }
|
|
div.stage h1 { text-decoration: underline; margin-top: 0px; }
|
|
div.stage table { border: 1px solid #AAAAAA; border-collapse: collapse; }
|
|
div.stage table thead tr { border-bottom: 1px solid #AAAAAA; background-color: #EEEEFF; }
|
|
div.stage table tr th { border-right: 1px solid #AAAAAA; padding: 6px; }
|
|
div.stage table tr td { border-right: 1px solid #AAAAAA; background-color: #EEEEEE; padding: 3px; }
|
|
|
|
)"));
|
|
xml.writeEndElement(); // </style>
|
|
|
|
xml.writeEndElement(); // </head>
|
|
}
|
|
|
|
{
|
|
xml.writeStartElement(lit("body"));
|
|
|
|
xml.writeStartElement(lit("h1"));
|
|
xml.writeCharacters(title);
|
|
xml.writeEndElement();
|
|
|
|
xml.writeStartElement(lit("h3"));
|
|
{
|
|
QString context = tr("Frame %1").arg(m_Ctx.FrameInfo().frameNumber);
|
|
|
|
const DrawcallDescription *draw = m_Ctx.CurDrawcall();
|
|
|
|
QList<const DrawcallDescription *> drawstack;
|
|
const DrawcallDescription *parent = draw->parent;
|
|
while(parent)
|
|
{
|
|
drawstack.push_front(parent);
|
|
parent = parent->parent;
|
|
}
|
|
|
|
for(const DrawcallDescription *d : drawstack)
|
|
{
|
|
context += QFormatStr(" > %1").arg(d->name);
|
|
}
|
|
|
|
context += QFormatStr(" => %1").arg(draw->name);
|
|
|
|
xml.writeCharacters(context);
|
|
}
|
|
xml.writeEndElement(); // </h3>
|
|
}
|
|
|
|
// body is open
|
|
|
|
return xmlptr;
|
|
}
|
|
|
|
RDDialog::critical(
|
|
this, tr("Error exporting pipeline state"),
|
|
tr("Couldn't open path %1 for write.\n%2").arg(filename).arg(f->errorString()));
|
|
|
|
delete f;
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
RDDialog::critical(this, tr("Invalid directory"),
|
|
tr("Cannot find target directory to save to"));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void PipelineStateViewer::exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols,
|
|
const QList<QVariantList> &rows)
|
|
{
|
|
xml.writeStartElement(lit("table"));
|
|
|
|
{
|
|
xml.writeStartElement(lit("thead"));
|
|
xml.writeStartElement(lit("tr"));
|
|
|
|
for(const QString &col : cols)
|
|
{
|
|
xml.writeStartElement(lit("th"));
|
|
xml.writeCharacters(col);
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
xml.writeEndElement();
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
{
|
|
xml.writeStartElement(lit("tbody"));
|
|
|
|
if(rows.isEmpty())
|
|
{
|
|
xml.writeStartElement(lit("tr"));
|
|
|
|
for(int i = 0; i < cols.count(); i++)
|
|
{
|
|
xml.writeStartElement(lit("td"));
|
|
xml.writeCharacters(lit("-"));
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
xml.writeEndElement();
|
|
}
|
|
else
|
|
{
|
|
for(const QVariantList &row : rows)
|
|
{
|
|
xml.writeStartElement(lit("tr"));
|
|
|
|
for(const QVariant &el : row)
|
|
{
|
|
xml.writeStartElement(lit("td"));
|
|
|
|
if(el.type() == QVariant::Bool)
|
|
xml.writeCharacters(el.toBool() ? tr("True") : tr("False"));
|
|
else
|
|
xml.writeCharacters(el.toString());
|
|
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
xml.writeEndElement();
|
|
}
|
|
}
|
|
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
xml.writeEndElement();
|
|
}
|
|
|
|
void PipelineStateViewer::exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols,
|
|
const QVariantList &row)
|
|
{
|
|
exportHTMLTable(xml, cols, QList<QVariantList>({row}));
|
|
}
|
|
|
|
void PipelineStateViewer::endHTMLExport(QXmlStreamWriter *xml)
|
|
{
|
|
xml->writeEndElement(); // </body>
|
|
|
|
xml->writeEndElement(); // </html>
|
|
|
|
xml->writeEndDocument();
|
|
|
|
// delete the file the writer was writing to
|
|
QFile *f = qobject_cast<QFile *>(xml->device());
|
|
delete f;
|
|
|
|
delete xml;
|
|
}
|
|
|
|
void PipelineStateViewer::setTopologyDiagram(QLabel *diagram, Topology topo)
|
|
{
|
|
int idx = qMin((int)topo, (int)Topology::PatchList);
|
|
|
|
if(m_TopoPixmaps[idx].isNull())
|
|
{
|
|
QSvgRenderer svg;
|
|
switch(topo)
|
|
{
|
|
case Topology::PointList: svg.load(lit(":/topologies/topo_pointlist.svg")); break;
|
|
case Topology::LineList: svg.load(lit(":/topologies/topo_linelist.svg")); break;
|
|
case Topology::LineStrip: svg.load(lit(":/topologies/topo_linestrip.svg")); break;
|
|
case Topology::TriangleList: svg.load(lit(":/topologies/topo_trilist.svg")); break;
|
|
case Topology::TriangleStrip: svg.load(lit(":/topologies/topo_tristrip.svg")); break;
|
|
case Topology::LineList_Adj: svg.load(lit(":/topologies/topo_linelist_adj.svg")); break;
|
|
case Topology::LineStrip_Adj: svg.load(lit(":/topologies/topo_linestrip_adj.svg")); break;
|
|
case Topology::TriangleList_Adj: svg.load(lit(":/topologies/topo_trilist_adj.svg")); break;
|
|
case Topology::TriangleStrip_Adj: svg.load(lit(":/topologies/topo_tristrip_adj.svg")); break;
|
|
default: svg.load(lit(":/topologies/topo_patch.svg")); break;
|
|
}
|
|
|
|
QRect rect = svg.viewBox();
|
|
|
|
QImage im(rect.size() * diagram->devicePixelRatio(), QImage::Format_ARGB32);
|
|
|
|
im.fill(QColor(0, 0, 0, 0));
|
|
|
|
QPainter p(&im);
|
|
|
|
svg.render(&p);
|
|
|
|
// convert the colors - black maps to Text (foreground) and white maps to Base (background)
|
|
QColor white = diagram->palette().color(QPalette::Active, QPalette::Base);
|
|
QColor black = diagram->palette().color(QPalette::Active, QPalette::Text);
|
|
|
|
const float br = black.redF();
|
|
const float bg = black.greenF();
|
|
const float bb = black.blueF();
|
|
|
|
const float wr = white.redF();
|
|
const float wg = white.greenF();
|
|
const float wb = white.blueF();
|
|
|
|
for(int y = 0; y < im.height(); y++)
|
|
{
|
|
QRgb *line = (QRgb *)im.scanLine(y);
|
|
|
|
for(int x = 0; x < im.width(); x++)
|
|
{
|
|
// delta of 0 is black, delta of 255 is white
|
|
const float delta = float(qRed(*line));
|
|
const float bd = 255.0f - delta;
|
|
const float wd = delta;
|
|
|
|
const int r = int(br * bd + wr * wd);
|
|
const int g = int(bg * bd + wg * wd);
|
|
const int b = int(bb * bd + wb * wd);
|
|
|
|
*line = qRgba(r, g, b, qAlpha(*line));
|
|
|
|
line++;
|
|
}
|
|
}
|
|
|
|
m_TopoPixmaps[idx] = QPixmap::fromImage(im);
|
|
m_TopoPixmaps[idx].setDevicePixelRatio(diagram->devicePixelRatioF());
|
|
}
|
|
|
|
diagram->setPixmap(m_TopoPixmaps[idx]);
|
|
}
|
|
|
|
void PipelineStateViewer::setMeshViewPixmap(RDLabel *meshView)
|
|
{
|
|
QImage meshIcon = Pixmaps::wireframe_mesh(meshView->devicePixelRatio()).toImage();
|
|
QImage colSwapped(meshIcon.size(), QImage::Format_ARGB32);
|
|
colSwapped.fill(meshView->palette().color(QPalette::WindowText));
|
|
|
|
for(int y = 0; y < meshIcon.height(); y++)
|
|
{
|
|
const QRgb *in = (const QRgb *)meshIcon.constScanLine(y);
|
|
QRgb *out = (QRgb *)colSwapped.scanLine(y);
|
|
|
|
for(int x = 0; x < meshIcon.width(); x++)
|
|
{
|
|
*out = qRgba(qRed(*out), qGreen(*out), qBlue(*out), qAlpha(*in));
|
|
|
|
in++;
|
|
out++;
|
|
}
|
|
}
|
|
|
|
QPixmap p = QPixmap::fromImage(colSwapped);
|
|
p.setDevicePixelRatio(meshView->devicePixelRatioF());
|
|
|
|
meshView->setPixmap(p);
|
|
meshView->setPreserveAspectRatio(true);
|
|
|
|
QPalette pal = meshView->palette();
|
|
pal.setColor(QPalette::Shadow, pal.color(QPalette::Window).darker(120));
|
|
meshView->setPalette(pal);
|
|
meshView->setBackgroundRole(QPalette::Window);
|
|
meshView->setMouseTracking(true);
|
|
|
|
QObject::connect(meshView, &RDLabel::mouseMoved, [meshView](QMouseEvent *) {
|
|
meshView->setBackgroundRole(QPalette::Shadow);
|
|
meshView->setAutoFillBackground(true);
|
|
});
|
|
QObject::connect(meshView, &RDLabel::leave, [meshView]() {
|
|
meshView->setBackgroundRole(QPalette::Window);
|
|
meshView->setAutoFillBackground(false);
|
|
});
|
|
}
|
|
|
|
bool PipelineStateViewer::PrepareShaderEditing(const ShaderReflection *shaderDetails,
|
|
QString &entryFunc, rdcstrpairs &files)
|
|
{
|
|
if(!shaderDetails->debugInfo.files.empty())
|
|
{
|
|
entryFunc = shaderDetails->entryPoint;
|
|
|
|
QStringList uniqueFiles;
|
|
|
|
for(const ShaderSourceFile &s : shaderDetails->debugInfo.files)
|
|
{
|
|
QString filename = s.filename;
|
|
if(uniqueFiles.contains(filename.toLower()))
|
|
{
|
|
qWarning() << lit("Duplicate full filename") << filename;
|
|
continue;
|
|
}
|
|
uniqueFiles.push_back(filename.toLower());
|
|
|
|
files.push_back(make_rdcpair(s.filename, s.contents));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PipelineStateViewer::MakeShaderVariablesHLSL(bool cbufferContents,
|
|
const rdcarray<ShaderConstant> &vars,
|
|
QString &struct_contents, QString &struct_defs)
|
|
{
|
|
for(const ShaderConstant &v : vars)
|
|
{
|
|
if(!v.type.members.isEmpty())
|
|
{
|
|
QString def = lit("struct %1 {\n").arg(v.type.descriptor.name);
|
|
|
|
if(!struct_defs.contains(def))
|
|
{
|
|
QString contents;
|
|
MakeShaderVariablesHLSL(false, v.type.members, contents, struct_defs);
|
|
|
|
struct_defs += def + contents + lit("};\n\n");
|
|
}
|
|
}
|
|
|
|
struct_contents += lit("\t%1 %2").arg(v.type.descriptor.name).arg(v.name);
|
|
|
|
char comp = 'x';
|
|
if(v.reg.comp == 1)
|
|
comp = 'y';
|
|
if(v.reg.comp == 2)
|
|
comp = 'z';
|
|
if(v.reg.comp == 3)
|
|
comp = 'w';
|
|
|
|
if(cbufferContents)
|
|
struct_contents += lit(" : packoffset(c%1.%2);").arg(v.reg.vec).arg(QLatin1Char(comp));
|
|
else
|
|
struct_contents += lit(";");
|
|
|
|
struct_contents += lit("\n");
|
|
}
|
|
}
|
|
|
|
QString PipelineStateViewer::GenerateHLSLStub(const ShaderReflection *shaderDetails,
|
|
const QString &entryFunc)
|
|
{
|
|
QString hlsl = lit("// No HLSL available - function stub generated\n\n");
|
|
|
|
const QString textureDim[ENUM_ARRAY_SIZE(TextureType)] = {
|
|
lit("Unknown"), lit("Buffer"), lit("Texture1D"), lit("Texture1DArray"),
|
|
lit("Texture2D"), lit("TextureRect"), lit("Texture2DArray"), lit("Texture2DMS"),
|
|
lit("Texture2DMSArray"), lit("Texture3D"), lit("TextureCube"), lit("TextureCubeArray"),
|
|
};
|
|
|
|
for(const ShaderSampler &samp : shaderDetails->samplers)
|
|
{
|
|
hlsl += lit("//SamplerComparisonState %1 : register(s%2); // can't disambiguate\n"
|
|
"SamplerState %1 : register(s%2); // can't disambiguate\n")
|
|
.arg(samp.name)
|
|
.arg(samp.bindPoint);
|
|
}
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
const rdcarray<ShaderResource> &resources =
|
|
(i == 0 ? shaderDetails->readOnlyResources : shaderDetails->readWriteResources);
|
|
for(const ShaderResource &res : resources)
|
|
{
|
|
char regChar = 't';
|
|
|
|
if(i == 1)
|
|
{
|
|
hlsl += lit("RW");
|
|
regChar = 'u';
|
|
}
|
|
|
|
if(res.isTexture)
|
|
{
|
|
hlsl += lit("%1<%2> %3 : register(%4%5);\n")
|
|
.arg(textureDim[(size_t)res.resType])
|
|
.arg(res.variableType.descriptor.name)
|
|
.arg(res.name)
|
|
.arg(QLatin1Char(regChar))
|
|
.arg(res.bindPoint);
|
|
}
|
|
else
|
|
{
|
|
if(res.variableType.descriptor.rows > 1)
|
|
hlsl += lit("Structured");
|
|
|
|
hlsl += lit("Buffer<%1> %2 : register(%3%4);\n")
|
|
.arg(res.variableType.descriptor.name)
|
|
.arg(res.name)
|
|
.arg(QLatin1Char(regChar))
|
|
.arg(res.bindPoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
hlsl += lit("\n\n");
|
|
|
|
QString cbuffers;
|
|
|
|
int cbufIdx = 0;
|
|
for(const ConstantBlock &cbuf : shaderDetails->constantBlocks)
|
|
{
|
|
if(!cbuf.name.isEmpty() && !cbuf.variables.isEmpty())
|
|
{
|
|
QString cbufName = cbuf.name;
|
|
if(cbufName == lit("$Globals"))
|
|
cbufName = lit("_Globals");
|
|
cbuffers += lit("cbuffer %1 : register(b%2) {\n").arg(cbufName).arg(cbuf.bindPoint);
|
|
MakeShaderVariablesHLSL(true, cbuf.variables, cbuffers, hlsl);
|
|
cbuffers += lit("};\n\n");
|
|
}
|
|
cbufIdx++;
|
|
}
|
|
|
|
hlsl += cbuffers;
|
|
|
|
hlsl += lit("\n\n");
|
|
|
|
hlsl += lit("struct InputStruct {\n");
|
|
for(const SigParameter &sig : shaderDetails->inputSignature)
|
|
hlsl += lit("\t%1 %2 : %3;\n")
|
|
.arg(TypeString(sig))
|
|
.arg(!sig.varName.isEmpty() ? QString(sig.varName) : lit("param%1").arg(sig.regIndex))
|
|
.arg(D3DSemanticString(sig));
|
|
hlsl += lit("};\n\n");
|
|
|
|
hlsl += lit("struct OutputStruct {\n");
|
|
for(const SigParameter &sig : shaderDetails->outputSignature)
|
|
hlsl += lit("\t%1 %2 : %3;\n")
|
|
.arg(TypeString(sig))
|
|
.arg(!sig.varName.isEmpty() ? QString(sig.varName) : lit("param%1").arg(sig.regIndex))
|
|
.arg(D3DSemanticString(sig));
|
|
hlsl += lit("};\n\n");
|
|
|
|
hlsl += lit("OutputStruct %1(in InputStruct IN)\n"
|
|
"{\n"
|
|
"\tOutputStruct OUT = (OutputStruct)0;\n"
|
|
"\n"
|
|
"\t// ...\n"
|
|
"\n"
|
|
"\treturn OUT;\n"
|
|
"}\n")
|
|
.arg(entryFunc);
|
|
|
|
return hlsl;
|
|
}
|
|
|
|
void PipelineStateViewer::EditShader(ShaderStage shaderType, ResourceId id,
|
|
const ShaderReflection *shaderDetails,
|
|
const QString &entryFunc, const rdcstrpairs &files)
|
|
{
|
|
IShaderViewer *sv = m_Ctx.EditShader(
|
|
false, entryFunc, files,
|
|
// save callback
|
|
[entryFunc, shaderType, id, shaderDetails](ICaptureContext *ctx, IShaderViewer *viewer,
|
|
const rdcstrpairs &updatedfiles) {
|
|
QString compileSource = updatedfiles[0].second;
|
|
|
|
ANALYTIC_SET(UIFeatures.ShaderEditing, true);
|
|
|
|
// try and match up #includes against the files that we have. This isn't always
|
|
// possible as fxc only seems to include the source for files if something in
|
|
// that file was included in the compiled output. So you might end up with
|
|
// dangling #includes - we just have to ignore them
|
|
int offs = compileSource.indexOf(lit("#include"));
|
|
|
|
while(offs >= 0)
|
|
{
|
|
// search back to ensure this is a valid #include (ie. not in a comment).
|
|
// Must only see whitespace before, then a newline.
|
|
int ws = qMax(0, offs - 1);
|
|
while(ws >= 0 &&
|
|
(compileSource[ws] == QLatin1Char(' ') || compileSource[ws] == QLatin1Char('\t')))
|
|
ws--;
|
|
|
|
// not valid? jump to next.
|
|
if(ws > 0 && compileSource[ws] != QLatin1Char('\n'))
|
|
{
|
|
offs = compileSource.indexOf(lit("#include"), offs + 1);
|
|
continue;
|
|
}
|
|
|
|
int start = ws + 1;
|
|
|
|
bool tail = true;
|
|
|
|
int lineEnd = compileSource.indexOf(QLatin1Char('\n'), start + 1);
|
|
if(lineEnd == -1)
|
|
{
|
|
lineEnd = compileSource.length();
|
|
tail = false;
|
|
}
|
|
|
|
ws = offs + sizeof("#include") - 1;
|
|
while(compileSource[ws] == QLatin1Char(' ') || compileSource[ws] == QLatin1Char('\t'))
|
|
ws++;
|
|
|
|
QString line = compileSource.mid(offs, lineEnd - offs + 1);
|
|
|
|
if(compileSource[ws] != QLatin1Char('<') && compileSource[ws] != QLatin1Char('"'))
|
|
{
|
|
viewer->ShowErrors(lit("Invalid #include directive found:\r\n") + line);
|
|
return;
|
|
}
|
|
|
|
// find matching char, either <> or "";
|
|
int end = compileSource.indexOf(
|
|
compileSource[ws] == QLatin1Char('"') ? QLatin1Char('"') : QLatin1Char('>'), ws + 1);
|
|
|
|
if(end == -1)
|
|
{
|
|
viewer->ShowErrors(lit("Invalid #include directive found:\r\n") + line);
|
|
return;
|
|
}
|
|
|
|
QString fname = compileSource.mid(ws + 1, end - ws - 1);
|
|
|
|
QString fileText;
|
|
|
|
// look for exact match first
|
|
for(int i = 0; i < updatedfiles.count(); i++)
|
|
{
|
|
if(QString(updatedfiles[i].first) == fname)
|
|
{
|
|
fileText = updatedfiles[i].second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(fileText.isEmpty())
|
|
{
|
|
QString search = QFileInfo(fname).fileName();
|
|
|
|
// if not, try and find the same filename (this is not proper include handling!)
|
|
for(const rdcstrpair &kv : updatedfiles)
|
|
{
|
|
if(QFileInfo(kv.first).fileName().compare(search, Qt::CaseInsensitive) == 0)
|
|
{
|
|
fileText = kv.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(fileText.isEmpty())
|
|
fileText = QFormatStr("// Can't find file %1\n").arg(fname);
|
|
}
|
|
|
|
compileSource = compileSource.left(offs) + lit("\n\n") + fileText + lit("\n\n") +
|
|
(tail ? compileSource.mid(lineEnd + 1) : QString());
|
|
|
|
// need to start searching from the beginning - wasteful but allows nested includes to
|
|
// work
|
|
offs = compileSource.indexOf(lit("#include"));
|
|
}
|
|
|
|
for(const rdcstrpair &kv : updatedfiles)
|
|
{
|
|
if(kv.first == "@cmdline")
|
|
compileSource = QString(kv.second) + lit("\n\n") + compileSource;
|
|
}
|
|
|
|
// invoke off to the ReplayController to replace the capture's shader
|
|
// with our edited one
|
|
ctx->Replay().AsyncInvoke([ctx, entryFunc, compileSource, shaderType, id, shaderDetails,
|
|
viewer](IReplayController *r) {
|
|
rdcstr errs;
|
|
|
|
const ShaderCompileFlags &flags = shaderDetails->debugInfo.compileFlags;
|
|
|
|
ResourceId from = id;
|
|
ResourceId to;
|
|
|
|
std::tie(to, errs) = r->BuildTargetShader(
|
|
entryFunc.toUtf8().data(), compileSource.toUtf8().data(), flags, shaderType);
|
|
|
|
GUIInvoke::call(viewer->Widget(), [viewer, errs]() { viewer->ShowErrors(errs); });
|
|
if(to == ResourceId())
|
|
{
|
|
r->RemoveReplacement(from);
|
|
GUIInvoke::call(viewer->Widget(), [ctx]() { ctx->RefreshStatus(); });
|
|
}
|
|
else
|
|
{
|
|
r->ReplaceResource(from, to);
|
|
GUIInvoke::call(viewer->Widget(), [ctx]() { ctx->RefreshStatus(); });
|
|
}
|
|
});
|
|
},
|
|
|
|
// Close Callback
|
|
[id](ICaptureContext *ctx) {
|
|
// remove the replacement on close (we could make this more sophisticated if there
|
|
// was a place to control replaced resources/shaders).
|
|
ctx->Replay().AsyncInvoke([ctx, id](IReplayController *r) {
|
|
r->RemoveReplacement(id);
|
|
GUIInvoke::call(ctx->GetMainWindow()->Widget(), [ctx] { ctx->RefreshStatus(); });
|
|
});
|
|
});
|
|
|
|
m_Ctx.AddDockWindow(sv->Widget(), DockReference::AddTo, this);
|
|
}
|
|
|
|
bool PipelineStateViewer::SaveShaderFile(const ShaderReflection *shader)
|
|
{
|
|
if(!shader)
|
|
return false;
|
|
|
|
QString filter;
|
|
|
|
if(m_Ctx.CurPipelineState().IsCaptureD3D11() || m_Ctx.CurPipelineState().IsCaptureD3D12())
|
|
{
|
|
filter = tr("DXBC Shader files (*.dxbc)");
|
|
}
|
|
else if(m_Ctx.CurPipelineState().IsCaptureGL())
|
|
{
|
|
filter = tr("GLSL files (*.glsl)");
|
|
}
|
|
else if(m_Ctx.CurPipelineState().IsCaptureVK())
|
|
{
|
|
filter = tr("SPIR-V files (*.spv)");
|
|
}
|
|
|
|
QString filename = RDDialog::getSaveFileName(this, tr("Save Shader As"), QString(), filter);
|
|
|
|
if(!filename.isEmpty())
|
|
{
|
|
ANALYTIC_SET(Export.Shader, true);
|
|
|
|
QDir dirinfo = QFileInfo(filename).dir();
|
|
if(dirinfo.exists())
|
|
{
|
|
QFile f(filename);
|
|
if(f.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
{
|
|
f.write((const char *)shader->rawBytes.data(), (qint64)shader->rawBytes.size());
|
|
}
|
|
else
|
|
{
|
|
RDDialog::critical(
|
|
this, tr("Error saving shader"),
|
|
tr("Couldn't open path %1 for write.\n%2").arg(filename).arg(f.errorString()));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDDialog::critical(this, tr("Invalid directory"),
|
|
tr("Cannot find target directory to save to"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|