From a2d40f80616a964da790e87faf338d4cc5546629 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 25 Nov 2016 14:17:20 +0100 Subject: [PATCH] Add constant buffer previewer for qrenderdoc --- qrenderdoc/Code/CaptureContext.h | 9 - qrenderdoc/Code/FormatElement.cpp | 877 ++++++++++++++++++ qrenderdoc/Code/QRDUtils.cpp | 23 + qrenderdoc/Code/QRDUtils.h | 47 + qrenderdoc/Widgets/BufferFormatSpecifier.cpp | 86 ++ qrenderdoc/Widgets/BufferFormatSpecifier.h | 56 ++ qrenderdoc/Widgets/BufferFormatSpecifier.ui | 146 +++ .../Windows/ConstantBufferPreviewer.cpp | 263 ++++++ qrenderdoc/Windows/ConstantBufferPreviewer.h | 80 ++ qrenderdoc/Windows/ConstantBufferPreviewer.ui | 242 +++++ qrenderdoc/Windows/MainWindow.cpp | 1 + .../VulkanPipelineStateViewer.cpp | 155 ++-- .../PipelineState/VulkanPipelineStateViewer.h | 3 +- qrenderdoc/qrenderdoc.pro | 13 +- qrenderdoc/qrenderdoc_local.vcxproj | 11 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 33 + 16 files changed, 1962 insertions(+), 83 deletions(-) create mode 100644 qrenderdoc/Code/FormatElement.cpp create mode 100644 qrenderdoc/Widgets/BufferFormatSpecifier.cpp create mode 100644 qrenderdoc/Widgets/BufferFormatSpecifier.h create mode 100644 qrenderdoc/Widgets/BufferFormatSpecifier.ui create mode 100644 qrenderdoc/Windows/ConstantBufferPreviewer.cpp create mode 100644 qrenderdoc/Windows/ConstantBufferPreviewer.h create mode 100644 qrenderdoc/Windows/ConstantBufferPreviewer.ui diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 168a54a79..8e156f7ee 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -53,15 +53,6 @@ class TextureViewer; class CaptureDialog; class QProgressDialog; -struct Formatter -{ - static QString Format(float f) { return QString::number(f); } - static QString Format(double d) { return QString::number(d); } - static QString Format(uint32_t u) { return QString::number(u); } - static QString Format(uint16_t u) { return QString::number(u); } - static QString Format(int32_t i) { return QString::number(i); } -}; - class CaptureContext { public: diff --git a/qrenderdoc/Code/FormatElement.cpp b/qrenderdoc/Code/FormatElement.cpp new file mode 100644 index 000000000..f2d581734 --- /dev/null +++ b/qrenderdoc/Code/FormatElement.cpp @@ -0,0 +1,877 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 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 +#include +#include "QRDUtils.h" + +static QVariant interpret(const ResourceFormat &f, uint16_t comp) +{ + if(f.compByteWidth != 2 || f.compType == eCompType_Float) + return QVariant(); + + if(f.compType == eCompType_SInt) + { + return (int16_t)comp; + } + else if(f.compType == eCompType_UInt) + { + return comp; + } + else if(f.compType == eCompType_SScaled) + { + return (float)((int16_t)comp); + } + else if(f.compType == eCompType_UScaled) + { + return (float)comp; + } + else if(f.compType == eCompType_UNorm) + { + return (float)comp / (float)0xffff; + } + else if(f.compType == eCompType_SNorm) + { + int16_t cast = (int16_t)comp; + + float ret = -1.0f; + + if(cast == -32768) + ret = -1.0f; + else + ret = ((float)cast) / 32767.0f; + + return ret; + } + + return QVariant(); +} + +static QVariant interpret(const ResourceFormat &f, byte comp) +{ + if(f.compByteWidth != 1 || f.compType == eCompType_Float) + return QVariant(); + + if(f.compType == eCompType_SInt) + { + return (int8_t)comp; + } + else if(f.compType == eCompType_UInt) + { + return comp; + } + else if(f.compType == eCompType_SScaled) + { + return (float)((int8_t)comp); + } + else if(f.compType == eCompType_UScaled) + { + return (float)comp; + } + else if(f.compType == eCompType_UNorm) + { + return ((float)comp) / 255.0f; + } + else if(f.compType == eCompType_SNorm) + { + int8_t cast = (int8_t)comp; + + float ret = -1.0f; + + if(cast == -128) + ret = -1.0f; + else + ret = ((float)cast) / 127.0f; + + return ret; + } + + return QVariant(); +} + +FormatElement::FormatElement() +{ + name = ""; + buffer = 0; + offset = 0; + perinstance = false; + instancerate = 1; + rowmajor = false; + matrixdim = 0; + hex = false; + systemValue = eAttr_None; +} + +FormatElement::FormatElement(const QString &Name, int buf, uint offs, bool pi, int ir, bool rowMat, + uint matDim, ResourceFormat f, bool h) +{ + name = Name; + buffer = buf; + offset = offs; + format = f; + perinstance = pi; + instancerate = ir; + rowmajor = rowMat; + matrixdim = matDim; + hex = h; + systemValue = eAttr_None; +} + +QList FormatElement::ParseFormatString(const QString &formatString, uint64_t maxLen, + bool tightPacking, QString &errors) +{ + QList elems; + + // regex doesn't account for trailing or preceeding whitespace, or comments + + QRegularExpression regExpr( + "^(row_major\\s+)?" // row_major matrix + "(" + "uintten|unormten" + "|unormh|unormb" + "|snormh|snormb" + "|bool" // bool is stored as 4-byte int + "|byte|short|int" // signed ints + "|ubyte|ushort|uint" // unsigned ints + "|xbyte|xshort|xint" // hex ints + "|half|float|double" // float types + "|vec|uvec|ivec" // OpenGL vector types + "|mat|umat|imat" // OpenGL matrix types + ")" + "([1-9])?" // might be a vector + "(x[1-9])?" // or a matrix + "(\\s+[A-Za-z_][A-Za-z0-9_]*)?" // get identifier name + "(\\[[0-9]+\\])?" // optional array dimension + "(\\s*:\\s*[A-Za-z_][A-Za-z0-9_]*)?" // optional semantic + "$"); + + bool success = true; + errors = ""; + + QString text = formatString; + + text = text.replace("{", "").replace("}", ""); + + QRegularExpression c_comments("/\\*[^*]*\\*+(?:[^*/][^*]*\\*+)*/"); + QRegularExpression cpp_comments("//.*"); + text = text.replace(c_comments, "").replace(cpp_comments, ""); + + uint32_t offset = 0; + + // get each line and parse it to determine the format the user wanted + for(QString &l : text.split(QChar(';'))) + { + QString line = l.trimmed(); + + if(line.isEmpty()) + continue; + + QRegularExpressionMatch match = regExpr.match(line); + + if(!match.hasMatch()) + { + errors = "Couldn't parse line:\n" + line; + success = false; + break; + } + + QString basetype = match.captured(2); + bool row_major = !match.captured(1).isEmpty(); + QString vectorDim = !match.captured(3).isEmpty() ? match.captured(3) : "1"; + QString matrixDim = !match.captured(4).isEmpty() ? match.captured(4).mid(1) : "1"; + QString name = !match.captured(5).isEmpty() ? match.captured(5).trimmed() : "data"; + QString arrayDim = !match.captured(6).isEmpty() ? match.captured(6).trimmed() : "[1]"; + arrayDim = arrayDim.mid(1, arrayDim.count() - 2); + + if(!match.captured(4).isEmpty()) + { + vectorDim.swap(matrixDim); + } + + ResourceFormat fmt; + fmt.compType = eCompType_None; + + bool hex = false; + + FormatComponentType type = eCompType_Float; + uint32_t count = 0; + uint32_t arrayCount = 1; + uint32_t matrixCount = 0; + uint32_t width = 0; + + // check for square matrix declarations like 'mat4' and 'mat3' + if(basetype == "mat" && !match.captured(4).isEmpty()) + matrixDim = vectorDim; + + // calculate format + { + bool ok = false; + + count = vectorDim.toUInt(&ok); + if(!ok) + { + errors = "Invalid vector dimension on line:\n" + line; + success = false; + break; + } + + arrayCount = arrayDim.toUInt(&ok); + if(!ok) + { + arrayCount = 1; + } + arrayCount = qMax(0U, arrayCount); + + matrixCount = matrixDim.toUInt(&ok); + if(!ok) + { + errors = "Invalid matrix second dimension on line:\n" + line; + success = false; + break; + } + + if(basetype == "bool") + { + type = eCompType_UInt; + width = 4; + } + else if(basetype == "byte") + { + type = eCompType_SInt; + width = 1; + } + else if(basetype == "ubyte" || basetype == "xbyte") + { + type = eCompType_UInt; + width = 1; + } + else if(basetype == "short") + { + type = eCompType_SInt; + width = 2; + } + else if(basetype == "ushort" || basetype == "xshort") + { + type = eCompType_UInt; + width = 2; + } + else if(basetype == "int" || basetype == "ivec" || basetype == "imat") + { + type = eCompType_SInt; + width = 4; + } + else if(basetype == "uint" || basetype == "xint" || basetype == "uvec" || basetype == "umat") + { + type = eCompType_UInt; + width = 4; + } + else if(basetype == "half") + { + type = eCompType_Float; + width = 2; + } + else if(basetype == "float" || basetype == "vec" || basetype == "mat") + { + type = eCompType_Float; + width = 4; + } + else if(basetype == "double") + { + type = eCompType_Double; + width = 8; + } + else if(basetype == "unormh") + { + type = eCompType_UNorm; + width = 2; + } + else if(basetype == "unormb") + { + type = eCompType_UNorm; + width = 1; + } + else if(basetype == "snormh") + { + type = eCompType_SNorm; + width = 2; + } + else if(basetype == "snormb") + { + type = eCompType_SNorm; + width = 1; + } + else if(basetype == "uintten") + { + fmt.compType = eCompType_UInt; + fmt.compCount = 4 * count; + fmt.compByteWidth = 1; + fmt.special = true; + fmt.specialFormat = eSpecial_R10G10B10A2; + } + else if(basetype == "unormten") + { + fmt.compType = eCompType_UInt; + fmt.compCount = 4 * count; + fmt.compByteWidth = 1; + fmt.special = true; + fmt.specialFormat = eSpecial_R10G10B10A2; + } + else + { + errors = "Unrecognised basic type on line:\n" + line; + success = false; + break; + } + } + + if(basetype == "xint" || basetype == "xshort" || basetype == "xbyte") + hex = true; + + if(fmt.compType == eCompType_None) + { + fmt.compType = type; + fmt.compCount = count; + fmt.compByteWidth = width; + } + + if(arrayCount == 1) + { + FormatElement elem(name, 0, offset, false, 1, row_major, matrixCount, fmt, hex); + + uint32_t advance = elem.byteSize(); + + if(!tightPacking) + { + // cbuffer packing always works in floats + advance = (advance + 3U) & (~3U); + + // cbuffer packing doesn't allow elements to cross float4 boundaries, nudge up if this was + // the case + if(offset / 16 != (offset + elem.byteSize() - 1) / 16) + { + elem.offset = offset = (offset + 0xFU) & (~0xFU); + } + } + + elems.push_back(elem); + + offset += advance; + } + else + { + // when cbuffer packing, arrays are always aligned at float4 boundary + if(!tightPacking) + { + if(offset % 16 != 0) + { + offset = (offset + 0xFU) & (~0xFU); + } + } + + for(uint a = 0; a < arrayCount; a++) + { + FormatElement elem(QString("%1[%2]").arg(name).arg(a), 0, offset, false, 1, row_major, + matrixCount, fmt, hex); + + elems.push_back(elem); + + uint32_t advance = elem.byteSize(); + + // cbuffer packing each array element is always float4 aligned + if(!tightPacking) + { + advance = (advance + 0xFU) & (~0xFU); + } + + offset += advance; + } + } + } + + if(!success || elems.isEmpty()) + { + elems.clear(); + + ResourceFormat fmt; + fmt.compType = eCompType_UInt; + fmt.compByteWidth = 4; + fmt.compCount = 4; + + if(maxLen > 0 && maxLen < 16) + fmt.compCount = 1; + if(maxLen > 0 && maxLen < 4) + fmt.compByteWidth = 1; + + elems.push_back(FormatElement("data", 0, 0, false, 1, false, 1, fmt, true)); + } + + return elems; +} + +template +inline T readObj(const byte *&data, const byte *end, bool &ok) +{ + if(data + sizeof(T) > end) + { + ok = false; + return T(); + } + + T ret = *(T *)data; + + data += sizeof(T); + + return ret; +} + +QVariantList FormatElement::GetVariants(const byte *&data, const byte *end) +{ + QVariantList ret; + + bool ok = true; + + if(format.special && format.specialFormat == eSpecial_R5G5B5A1) + { + uint16_t packed = readObj(data, end, ok); + + ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f); + ret.push_back((float)((packed >> 5) & 0x1f) / 31.0f); + ret.push_back((float)((packed >> 10) & 0x1f) / 31.0f); + ret.push_back(((packed & 0x8000) > 0) ? 1.0f : 0.0f); + + if(format.bgraOrder) + { + QVariant tmp = ret[2]; + ret[2] = ret[0]; + ret[0] = tmp; + } + } + else if(format.special && format.specialFormat == eSpecial_R5G6B5) + { + uint16_t packed = readObj(data, end, ok); + + ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f); + ret.push_back((float)((packed >> 5) & 0x3f) / 63.0f); + ret.push_back((float)((packed >> 11) & 0x1f) / 31.0f); + + if(format.bgraOrder) + { + QVariant tmp = ret[2]; + ret[2] = ret[0]; + ret[0] = tmp; + } + } + else if(format.special && format.specialFormat == eSpecial_R4G4B4A4) + { + uint16_t packed = readObj(data, end, ok); + + ret.push_back((float)((packed >> 0) & 0xf) / 15.0f); + ret.push_back((float)((packed >> 4) & 0xf) / 15.0f); + ret.push_back((float)((packed >> 8) & 0xf) / 15.0f); + ret.push_back((float)((packed >> 12) & 0xf) / 15.0f); + + if(format.bgraOrder) + { + QVariant tmp = ret[2]; + ret[2] = ret[0]; + ret[0] = tmp; + } + } + else if(format.special && format.specialFormat == eSpecial_R10G10B10A2) + { + // allow for vectors of this format - for raw buffer viewer + for(int i = 0; i < int(format.compCount / 4); i++) + { + uint32_t packed = readObj(data, end, ok); + + uint32_t r = (packed >> 0) & 0x3ff; + uint32_t g = (packed >> 10) & 0x3ff; + uint32_t b = (packed >> 20) & 0x3ff; + uint32_t a = (packed >> 30) & 0x003; + + if(format.bgraOrder) + { + uint32_t tmp = b; + b = r; + r = tmp; + } + + if(format.compType == eCompType_UInt) + { + ret.push_back(r); + ret.push_back(g); + ret.push_back(b); + ret.push_back(a); + } + else if(format.compType == eCompType_UScaled) + { + ret.push_back((float)r); + ret.push_back((float)g); + ret.push_back((float)b); + ret.push_back((float)a); + } + else if(format.compType == eCompType_SInt || format.compType == eCompType_SScaled) + { + int ir, ig, ib, ia; + + // interpret RGB as 10-bit signed integers + if(r <= 511) + ir = (int)r; + else + ir = ((int)r) - 1024; + + if(g <= 511) + ig = (int)g; + else + ig = ((int)g) - 1024; + + if(b <= 511) + ib = (int)b; + else + ib = ((int)b) - 1024; + + // 2-bit signed integer + if(a <= 1) + ia = (int)a; + else + ia = ((int)a) - 4; + + if(format.compType == eCompType_SInt) + { + ret.push_back(ir); + ret.push_back(ig); + ret.push_back(ib); + ret.push_back(ia); + } + else if(format.compType == eCompType_SScaled) + { + ret.push_back((float)ir); + ret.push_back((float)ig); + ret.push_back((float)ib); + ret.push_back((float)ia); + } + } + else + { + ret.push_back((float)r / 1023.0f); + ret.push_back((float)g / 1023.0f); + ret.push_back((float)b / 1023.0f); + ret.push_back((float)a / 3.0f); + } + } + } + else if(format.special && format.specialFormat == eSpecial_R11G11B10) + { + uint32_t packed = readObj(data, end, ok); + + uint32_t xMantissa = ((packed >> 0) & 0x3f); + uint32_t xExponent = ((packed >> 6) & 0x1f); + uint32_t yMantissa = ((packed >> 11) & 0x3f); + uint32_t yExponent = ((packed >> 17) & 0x1f); + uint32_t zMantissa = ((packed >> 22) & 0x1f); + uint32_t zExponent = ((packed >> 27) & 0x1f); + + ret.push_back(((float)(xMantissa) / 64.0f) * qPow(2.0f, (float)xExponent - 15.0f)); + ret.push_back(((float)(yMantissa) / 32.0f) * qPow(2.0f, (float)yExponent - 15.0f)); + ret.push_back(((float)(zMantissa) / 32.0f) * qPow(2.0f, (float)zExponent - 15.0f)); + } + else + { + int dim = (int)(qMax(matrixdim, 1U) * format.compCount); + + for(int i = 0; i < dim; i++) + { + if(format.compType == eCompType_Float) + { + if(format.compByteWidth == 8) + ret.push_back(readObj(data, end, ok)); + else if(format.compByteWidth == 4) + ret.push_back(readObj(data, end, ok)); + else if(format.compByteWidth == 2) + ret.push_back(Maths_HalfToFloat(readObj(data, end, ok))); + } + else if(format.compType == eCompType_SInt) + { + if(format.compByteWidth == 4) + ret.push_back((int)readObj(data, end, ok)); + else if(format.compByteWidth == 2) + ret.push_back((int)readObj(data, end, ok)); + else if(format.compByteWidth == 1) + ret.push_back((int)readObj(data, end, ok)); + } + else if(format.compType == eCompType_UInt) + { + if(format.compByteWidth == 4) + ret.push_back((uint32_t)readObj(data, end, ok)); + else if(format.compByteWidth == 2) + ret.push_back((uint32_t)readObj(data, end, ok)); + else if(format.compByteWidth == 1) + ret.push_back((uint32_t)readObj(data, end, ok)); + } + else if(format.compType == eCompType_UScaled) + { + if(format.compByteWidth == 4) + ret.push_back((float)readObj(data, end, ok)); + else if(format.compByteWidth == 2) + ret.push_back((float)readObj(data, end, ok)); + else if(format.compByteWidth == 1) + ret.push_back((float)readObj(data, end, ok)); + } + else if(format.compType == eCompType_SScaled) + { + if(format.compByteWidth == 4) + ret.push_back((float)readObj(data, end, ok)); + else if(format.compByteWidth == 2) + ret.push_back((float)readObj(data, end, ok)); + else if(format.compByteWidth == 1) + ret.push_back((float)readObj(data, end, ok)); + } + else if(format.compType == eCompType_Depth) + { + if(format.compByteWidth == 4) + { + // 32-bit depth is native floats + ret.push_back(readObj(data, end, ok)); + } + else if(format.compByteWidth == 3) + { + // 32-bit depth is normalised, masked against non-stencil bits + uint32_t f = readObj(data, end, ok); + f &= 0x00ffffff; + ret.push_back((float)f / (float)0x00ffffff); + } + else if(format.compByteWidth == 2) + { + // 16-bit depth is normalised + float f = (float)readObj(data, end, ok); + ret.push_back(f / (float)0x0000ffff); + } + } + else if(format.compType == eCompType_Double) + { + ret.push_back(readObj(data, end, ok)); + } + else + { + // unorm/snorm + + if(format.compByteWidth == 4) + { + // should never hit this - no 32bit unorm/snorm type + qCritical() << "Unexpected 4-byte unorm/snorm value"; + ret.push_back((float)readObj(data, end, ok) / (float)0xffffffff); + } + else if(format.compByteWidth == 2) + { + ret.push_back(interpret(format, readObj(data, end, ok))); + } + else if(format.compByteWidth == 1) + { + ret.push_back(interpret(format, readObj(data, end, ok))); + } + } + } + + if(format.bgraOrder) + { + QVariant tmp = ret[2]; + ret[2] = ret[0]; + ret[0] = tmp; + } + } + + // we read off the end, return empty set + if(!ok) + ret.clear(); + + return ret; +} + +ShaderVariable FormatElement::GetShaderVar(const byte *&data, const byte *end) +{ + QVariantList objs = GetVariants(data, end); + + ShaderVariable ret; + + ret.name = name.toUtf8().data(); + ret.type = eVar_Float; + if(format.compType == eCompType_UInt) + ret.type = eVar_UInt; + if(format.compType == eCompType_SInt) + ret.type = eVar_Int; + if(format.compType == eCompType_Double) + ret.type = eVar_Double; + + ret.columns = qMin(format.compCount, 4U); + ret.rows = qMin(matrixdim, 4U); + + ret.displayAsHex = hex; + + for(uint32_t row = 0; row < ret.rows; row++) + { + for(uint32_t col = 0; col < ret.columns; col++) + { + uint32_t dst = row * ret.columns + col; + uint32_t src = row * format.compCount + col; + + // if we partially read a failure, reset the variable and return + if((int)src >= objs.size()) + { + ret.name = "-"; + memset(ret.value.dv, 0, sizeof(ret.value.dv)); + return ret; + } + + const QVariant &o = objs[src]; + + if(ret.type == eVar_Double) + ret.value.dv[dst] = o.toDouble(); + else if(ret.type == eVar_UInt) + ret.value.uv[dst] = o.toUInt(); + else if(ret.type == eVar_Int) + ret.value.iv[dst] = o.toInt(); + else + ret.value.fv[dst] = o.toFloat(); + } + } + + return ret; +} + +uint32_t FormatElement::byteSize() +{ + uint32_t vecSize = format.compByteWidth * format.compCount; + + if(format.special) + { + if(format.specialFormat == eSpecial_R5G5B5A1 || format.specialFormat == eSpecial_R5G6B5 || + format.specialFormat == eSpecial_R4G4B4A4) + vecSize = 2; + + if(format.specialFormat == eSpecial_R10G10B10A2 || format.specialFormat == eSpecial_R11G11B10) + vecSize = 4; + } + + return vecSize * matrixdim; +} + +QString TypeString(const ShaderVariable &v) +{ + if(v.members.count > 0) + { + if(v.isStruct) + return "struct"; + else + return QString("%1[%2]").arg(TypeString(v.members[0]), v.members.count); + } + + QString typeStr = ToQStr(v.type); + + if(v.displayAsHex && v.type == eVar_UInt) + typeStr = "xint"; + + if(v.rows == 1 && v.columns == 1) + return typeStr; + if(v.rows == 1) + return QString("%1%2").arg(typeStr).arg(v.columns); + else + return QString("%1%2x%3").arg(typeStr).arg(v.rows).arg(v.columns); +} + +template +static QString RowValuesToString(int cols, el x, el y, el z, el w) +{ + if(cols == 1) + return Formatter::Format(x); + else if(cols == 2) + return Formatter::Format(x) + ", " + Formatter::Format(y); + else if(cols == 3) + return Formatter::Format(x) + ", " + Formatter::Format(y) + ", " + Formatter::Format(z); + else + return Formatter::Format(x) + ", " + Formatter::Format(y) + ", " + Formatter::Format(z) + ", " + + Formatter::Format(w); +} + +QString RowString(const ShaderVariable &v, uint32_t row) +{ + if(v.type == eVar_Double) + return RowValuesToString((int)v.columns, v.value.dv[row * v.columns + 0], + v.value.dv[row * v.columns + 1], v.value.dv[row * v.columns + 2], + v.value.dv[row * v.columns + 3]); + else if(v.type == eVar_Int) + return RowValuesToString((int)v.columns, v.value.iv[row * v.columns + 0], + v.value.iv[row * v.columns + 1], v.value.iv[row * v.columns + 2], + v.value.iv[row * v.columns + 3]); + else if(v.type == eVar_UInt) + return RowValuesToString((int)v.columns, v.value.uv[row * v.columns + 0], + v.value.uv[row * v.columns + 1], v.value.uv[row * v.columns + 2], + v.value.uv[row * v.columns + 3]); + else + return RowValuesToString((int)v.columns, v.value.fv[row * v.columns + 0], + v.value.fv[row * v.columns + 1], v.value.fv[row * v.columns + 2], + v.value.fv[row * v.columns + 3]); +} + +QString VarString(const ShaderVariable &v) +{ + if(v.members.count > 0) + return ""; + + if(v.rows == 1) + return RowString(v, 0); + + QString ret = ""; + for(int i = 0; i < (int)v.rows; i++) + { + if(i > 0) + ret += ", "; + ret += "{" + RowString(v, i) + "}"; + } + + return "{ " + ret + " }"; +} + +QString RowTypeString(const ShaderVariable &v) +{ + if(v.members.count > 0) + { + if(v.isStruct) + return "struct"; + else + return "flibbertygibbet"; + } + + if(v.rows == 0 && v.columns == 0) + return "-"; + + QString typeStr = ToQStr(v.type); + + if(v.displayAsHex && v.type == eVar_UInt) + typeStr = "xint"; + + if(v.columns == 1) + return typeStr; + + return QString("%1%2").arg(typeStr).arg(v.columns); +} diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index 3744170b7..e4d48e35c 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -29,6 +29,7 @@ #include #include #include +#include QString ToQStr(const ResourceUsage usage, const GraphicsAPI apitype) { @@ -478,3 +479,25 @@ void addGridLines(QGridLayout *grid) } } } + +QTreeWidgetItem *makeTreeNode(const std::initializer_list &values) +{ + QTreeWidgetItem *ret = new QTreeWidgetItem(); + + int i = 0; + for(const QVariant &v : values) + ret->setData(i++, Qt::DisplayRole, v); + + return ret; +} + +QTreeWidgetItem *makeTreeNode(const QVariantList &values) +{ + QTreeWidgetItem *ret = new QTreeWidgetItem(); + + int i = 0; + for(const QVariant &v : values) + ret->setData(i++, Qt::DisplayRole, v); + + return ret; +} diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 7fb265e73..1f35dc1f6 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -314,6 +314,48 @@ QString ToQStr(const ResourceUsage usage, const GraphicsAPI apitype); // overload for a couple of things that need to know the pipeline type when converting QString ToQStr(const ShaderStageType stage, const GraphicsAPI apitype); +struct FormatElement +{ + FormatElement(); + FormatElement(const QString &Name, int buf, uint offs, bool pi, int ir, bool rowMat, uint matDim, + ResourceFormat f, bool h); + + static QList ParseFormatString(const QString &formatString, uint64_t maxLen, + bool tightPacking, QString &errors); + + QVariantList GetVariants(const byte *&data, const byte *end); + ShaderVariable GetShaderVar(const byte *&data, const byte *end); + + QString ElementString(const QVariant &var); + + uint32_t byteSize(); + + QString name; + int buffer; + uint32_t offset; + bool perinstance; + int instancerate; + bool rowmajor; + uint32_t matrixdim; + ResourceFormat format; + bool hex; + SystemAttribute systemValue; +}; + +QString TypeString(const ShaderVariable &v); +QString RowString(const ShaderVariable &v, uint32_t row); +QString VarString(const ShaderVariable &v); +QString RowTypeString(const ShaderVariable &v); + +struct Formatter +{ + static QString Format(float f) { return QString::number(f); } + static QString Format(double d) { return QString::number(d); } + static QString Format(uint32_t u) { return QString::number(u); } + static QString Format(uint16_t u) { return QString::number(u); } + static QString Format(int32_t i) { return QString::number(i); } +}; + bool SaveToJSON(QVariantMap &data, QIODevice &f, const char *magicIdentifier, uint32_t magicVersion); bool LoadFromJSON(QVariantMap &data, QIODevice &f, const char *magicIdentifier, uint32_t magicVersion); @@ -506,3 +548,8 @@ public: class QGridLayout; void addGridLines(QGridLayout *grid); + +class QTreeWidgetItem; + +QTreeWidgetItem *makeTreeNode(const std::initializer_list &values); +QTreeWidgetItem *makeTreeNode(const QVariantList &values); diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.cpp b/qrenderdoc/Widgets/BufferFormatSpecifier.cpp new file mode 100644 index 000000000..c46b607b3 --- /dev/null +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 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 "BufferFormatSpecifier.h" +#include +#include "Code/QRDUtils.h" +#include "ui_BufferFormatSpecifier.h" + +BufferFormatSpecifier::BufferFormatSpecifier(QWidget *parent) + : QWidget(parent), ui(new Ui::BufferFormatSpecifier) +{ + ui->setupUi(this); + + QObject::connect(ui->toggleHelp, &QPushButton::clicked, this, &BufferFormatSpecifier::toggleHelp); + + setErrors(""); + + ui->formatText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); +} + +BufferFormatSpecifier::~BufferFormatSpecifier() +{ + delete ui; +} + +void BufferFormatSpecifier::toggleHelp() +{ + ui->helpText->setVisible(!ui->helpText->isVisible()); + + if(ui->helpText->isVisible()) + showHelp(true); + else + showHelp(false); +} + +void BufferFormatSpecifier::setErrors(const QString &errors) +{ + ui->errors->setText(errors); + if(errors.isEmpty()) + ui->errors->setVisible(false); + else + ui->errors->setVisible(true); +} + +void BufferFormatSpecifier::showHelp(bool help) +{ + ui->helpText->setVisible(help); + + if(help) + { + ui->gridLayout->removeWidget(ui->formatGroup); + ui->gridLayout->addWidget(ui->formatGroup, 1, 0, 1, 3); + } + else + { + ui->gridLayout->removeWidget(ui->formatGroup); + ui->gridLayout->addWidget(ui->formatGroup, 0, 0, 2, 3); + } +} + +void BufferFormatSpecifier::on_apply_clicked() +{ + setErrors(""); + emit processFormat(ui->formatText->toPlainText()); +} diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.h b/qrenderdoc/Widgets/BufferFormatSpecifier.h new file mode 100644 index 000000000..83e9039d4 --- /dev/null +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 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. + ******************************************************************************/ + +#pragma once + +#include + +namespace Ui +{ +class BufferFormatSpecifier; +} + +class BufferFormatSpecifier : public QWidget +{ + Q_OBJECT + +public: + explicit BufferFormatSpecifier(QWidget *parent = 0); + ~BufferFormatSpecifier(); + + void setErrors(const QString &errors); + void showHelp(bool help); + +signals: + void processFormat(const QString &format); + +public slots: + void toggleHelp(); + +private slots: + void on_apply_clicked(); + +private: + Ui::BufferFormatSpecifier *ui; +}; diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.ui b/qrenderdoc/Widgets/BufferFormatSpecifier.ui new file mode 100644 index 000000000..461ea1a9b --- /dev/null +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.ui @@ -0,0 +1,146 @@ + + + BufferFormatSpecifier + + + + 0 + 0 + 713 + 633 + + + + Buffer Format + + + + 2 + + + 2 + + + 2 + + + 2 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Toggle Help + + + + + + + + 0 + 0 + + + + Apply + + + + + + + + + Type in a buffer format declaration. Comments and {} braces are skipped, : semantics are ignored. +Declare each element as an hlsl/glsl variable, e.g: "float4 first; float2 second; uint2 third;" or vec4/vec2. + +Basic types accepted: bool, byte, short, int, half, float, double. +Unsigned integer types: ubyte, ushort, uint +Hex-formatted integer types: xbyte, xshort, xint + +Additionally special formats: unorm[hb] (half, byte) and snorm[hb], and uintten/unormten (10:10:10:2 packing) + +Vectors (e.g. float4), matrices ([row_major] half3x4) and arrays (float[16]) are supported. + + + true + + + + + + + + 0 + 0 + + + + Format + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + + + + + 0 + 0 + + + + + 14 + + + + color: rgb(171, 32, 32); + + + + + + + + diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp new file mode 100644 index 000000000..42f608239 --- /dev/null +++ b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp @@ -0,0 +1,263 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 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 "ConstantBufferPreviewer.h" +#include +#include "ui_ConstantBufferPreviewer.h" + +QList ConstantBufferPreviewer::m_Previews; + +ConstantBufferPreviewer::ConstantBufferPreviewer(CaptureContext *ctx, const ShaderStageType stage, + uint32_t slot, uint32_t idx, QWidget *parent) + : QFrame(parent), ui(new Ui::ConstantBufferPreviewer), m_Ctx(ctx) +{ + ui->setupUi(this); + + m_stage = stage; + m_slot = slot; + m_arrayIdx = idx; + + QObject::connect(ui->formatSpecifier, &BufferFormatSpecifier::processFormat, this, + &ConstantBufferPreviewer::processFormat); + + ui->formatSpecifier->showHelp(false); + + ui->splitter->setCollapsible(1, true); + ui->splitter->setSizes({1, 0}); + ui->splitter->handle(1)->setEnabled(false); + + { + // Name | Value | Type + ui->variables->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + ui->variables->header()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->variables->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + } + + ui->variables->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + m_Previews.push_back(this); + m_Ctx->AddLogViewer(this); +} + +ConstantBufferPreviewer::~ConstantBufferPreviewer() +{ + m_Ctx->RemoveLogViewer(this); + m_Previews.removeOne(this); + delete ui; +} + +ConstantBufferPreviewer *ConstantBufferPreviewer::has(ShaderStageType stage, uint32_t slot, + uint32_t idx) +{ + for(ConstantBufferPreviewer *c : m_Previews) + { + if(c->m_stage == stage && c->m_slot == slot && c->m_arrayIdx == idx) + return c; + } + + return NULL; +} + +void ConstantBufferPreviewer::OnLogfileLoaded() +{ + OnLogfileClosed(); +} + +void ConstantBufferPreviewer::OnLogfileClosed() +{ + ui->variables->clear(); + + ui->saveCSV->setEnabled(false); +} + +void ConstantBufferPreviewer::OnEventSelected(uint32_t eventID) +{ + uint64_t offs = 0; + uint64_t size = 0; + m_Ctx->CurPipelineState.GetConstantBuffer(m_stage, m_slot, m_arrayIdx, m_cbuffer, offs, size); + + m_shader = m_Ctx->CurPipelineState.GetShader(m_stage); + QString entryPoint = m_Ctx->CurPipelineState.GetShaderEntryPoint(m_stage); + ShaderReflection *reflection = m_Ctx->CurPipelineState.GetShaderReflection(m_stage); + + updateLabels(); + + if(reflection == NULL || reflection->ConstantBlocks.count <= (int)m_slot) + { + setVariables({}); + return; + } + + if(!m_formatOverride.empty()) + { + m_Ctx->Renderer()->AsyncInvoke([this, offs, size](IReplayRenderer *r) { + rdctype::array data; + r->GetBufferData(m_cbuffer, offs, size, &data); + rdctype::array vars = applyFormatOverride(data); + GUIInvoke::call([this, vars] { setVariables(vars); }); + }); + } + else + { + m_Ctx->Renderer()->AsyncInvoke([this, entryPoint, offs](IReplayRenderer *r) { + rdctype::array vars; + r->GetCBufferVariableContents(m_shader, entryPoint.toUtf8().data(), m_slot, m_cbuffer, offs, + &vars); + GUIInvoke::call([this, vars] { setVariables(vars); }); + }); + } +} + +void ConstantBufferPreviewer::on_setFormat_toggled(bool checked) +{ + if(!checked) + { + ui->splitter->setCollapsible(1, true); + ui->splitter->setSizes({1, 0}); + ui->splitter->handle(1)->setEnabled(false); + + processFormat(""); + return; + } + + ui->splitter->setCollapsible(1, false); + ui->splitter->setSizes({1, 1}); + ui->splitter->handle(1)->setEnabled(true); +} + +void ConstantBufferPreviewer::on_saveCSV_clicked() +{ +} + +void ConstantBufferPreviewer::processFormat(const QString &format) +{ + if(format.isEmpty()) + { + m_formatOverride.clear(); + ui->formatSpecifier->setErrors(""); + } + else + { + QString errors; + + m_formatOverride = FormatElement::ParseFormatString(format, 0, false, errors); + ui->formatSpecifier->setErrors(errors); + } + + OnEventSelected(m_Ctx->CurEvent()); +} + +void ConstantBufferPreviewer::addVariables(QTreeWidgetItem *root, + const rdctype::array &vars) +{ + for(const ShaderVariable &v : vars) + { + QTreeWidgetItem *n = makeTreeNode({ToQStr(v.name), VarString(v), TypeString(v)}); + + root->addChild(n); + + if(v.rows > 1) + { + for(uint32_t i = 0; i < v.rows; i++) + n->addChild(makeTreeNode( + {QString("%1.row%2").arg(ToQStr(v.name)).arg(i), RowString(v, i), RowTypeString(v)})); + } + + if(v.members.count > 0) + addVariables(n, v.members); + } +} + +void ConstantBufferPreviewer::setVariables(const rdctype::array &vars) +{ + ui->variables->setUpdatesEnabled(false); + ui->variables->clear(); + + ui->saveCSV->setEnabled(false); + + if(vars.count > 0) + { + addVariables(ui->variables->invisibleRootItem(), vars); + ui->saveCSV->setEnabled(true); + } + + ui->variables->setUpdatesEnabled(true); +} + +void ConstantBufferPreviewer::updateLabels() +{ + QString bufName = ""; + + bool needName = true; + + FetchBuffer *buf = m_Ctx ? m_Ctx->GetBuffer(m_cbuffer) : NULL; + if(buf) + { + bufName = buf->name; + if(buf->customName) + needName = false; + } + + ShaderReflection *reflection = m_Ctx ? m_Ctx->CurPipelineState.GetShaderReflection(m_stage) : NULL; + + if(reflection != NULL) + { + if(needName && (int)m_slot < reflection->ConstantBlocks.count && + reflection->ConstantBlocks[m_slot].name.count > 0) + bufName = "<" + ToQStr(reflection->ConstantBlocks[m_slot].name) + ">"; + } + + ui->nameLabel->setText(bufName); + + GraphicsAPI pipeType = eGraphicsAPI_D3D11; + if(m_Ctx != NULL) + pipeType = m_Ctx->APIProps().pipelineType; + + QString title = + QString("%1 %2 %3").arg(ToQStr(m_stage, pipeType)).arg(IsD3D(pipeType) ? "CB" : "UBO").arg(m_slot); + + if(m_Ctx != NULL && m_Ctx->CurPipelineState.SupportsResourceArrays()) + title += QString(" [%1]").arg(m_arrayIdx); + + ui->slotLabel->setText(title); + setWindowTitle(title); +} + +rdctype::array ConstantBufferPreviewer::applyFormatOverride( + const rdctype::array &bytes) +{ + QVector variables; + + variables.resize(m_formatOverride.length()); + + for(int i = 0; i < m_formatOverride.length(); i++) + { + const byte *b = bytes.begin() + m_formatOverride[i].offset; + variables[i] = m_formatOverride[i].GetShaderVar(b, bytes.end()); + } + + rdctype::array ret; + ret = variables.toStdVector(); + return ret; +} diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.h b/qrenderdoc/Windows/ConstantBufferPreviewer.h new file mode 100644 index 000000000..0e4acfc66 --- /dev/null +++ b/qrenderdoc/Windows/ConstantBufferPreviewer.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 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. + ******************************************************************************/ + +#pragma once + +#include +#include "Code/CaptureContext.h" + +namespace Ui +{ +class ConstantBufferPreviewer; +} + +struct FormatElement; + +class ConstantBufferPreviewer : public QFrame, public ILogViewerForm +{ + Q_OBJECT + +public: + explicit ConstantBufferPreviewer(CaptureContext *ctx, const ShaderStageType stage, uint32_t slot, + uint32_t idx, QWidget *parent = 0); + ~ConstantBufferPreviewer(); + + static ConstantBufferPreviewer *has(ShaderStageType stage, uint32_t slot, uint32_t idx); + + void OnLogfileLoaded(); + void OnLogfileClosed(); + void OnEventSelected(uint32_t eventID); + +private slots: + // automatic slots + void on_setFormat_toggled(bool checked); + void on_saveCSV_clicked(); + + // manual slots + void processFormat(const QString &format); + +private: + Ui::ConstantBufferPreviewer *ui; + CaptureContext *m_Ctx = NULL; + + ResourceId m_cbuffer; + ResourceId m_shader; + ShaderStageType m_stage = eShaderStage_Vertex; + uint32_t m_slot = 0; + uint32_t m_arrayIdx = 0; + + rdctype::array applyFormatOverride(const rdctype::array &data); + + void addVariables(QTreeWidgetItem *root, const rdctype::array &vars); + void setVariables(const rdctype::array &vars); + + void updateLabels(); + + static QList m_Previews; + + QList m_formatOverride; +}; diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.ui b/qrenderdoc/Windows/ConstantBufferPreviewer.ui new file mode 100644 index 000000000..4f31faa86 --- /dev/null +++ b/qrenderdoc/Windows/ConstantBufferPreviewer.ui @@ -0,0 +1,242 @@ + + + ConstantBufferPreviewer + + + + 0 + 0 + 400 + 300 + + + + Constant Buffer + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + false + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 28 + + + + QFrame::Panel + + + QFrame::Raised + + + + 2 + + + 6 + + + 2 + + + 6 + + + 2 + + + + + Subresource + + + + + + + Qt::Vertical + + + + + + + Mip + + + + + + + Qt::Vertical + + + + + + + {} + + + true + + + Qt::ToolButtonTextOnly + + + true + + + + + + + Qt::Vertical + + + + + + + + + + + :/Resources/save.png:/Resources/save.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::Panel + + + QFrame::Plain + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + QAbstractItemView::ContiguousSelection + + + true + + + false + + + + Name + + + + + Value + + + + + Type + + + + + + + + + + + + + + BufferFormatSpecifier + QWidget +
Widgets/BufferFormatSpecifier.h
+ 1 +
+
+ + + + +
diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 4ccd2fcc1..b198b7a75 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -36,6 +36,7 @@ #include "Windows/Dialogs/CaptureDialog.h" #include "Windows/Dialogs/LiveCapture.h" #include "APIInspector.h" +#include "ConstantBufferPreviewer.h" #include "EventBrowser.h" #include "TextureViewer.h" #include "ui_MainWindow.h" diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index 2ec35156c..74dbca33d 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -25,6 +25,9 @@ #include "VulkanPipelineStateViewer.h" #include #include +#include "3rdparty/toolwindowmanager/ToolWindowManager.h" +#include "Windows/ConstantBufferPreviewer.h" +#include "Windows/MainWindow.h" #include "Windows/TextureViewer.h" #include "ui_VulkanPipelineStateViewer.h" @@ -134,6 +137,10 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(CaptureContext *ctx, QWidge QObject::connect(res, &RDTreeWidget::itemActivated, this, &VulkanPipelineStateViewer::resource_itemActivated); + for(RDTreeWidget *ubo : ubos) + QObject::connect(ubo, &RDTreeWidget::itemActivated, this, + &VulkanPipelineStateViewer::ubo_itemActivated); + addGridLines(ui->rasterizerGridLayout); addGridLines(ui->MSAAGridLayout); addGridLines(ui->blendStateGridLayout); @@ -422,28 +429,6 @@ void VulkanPipelineStateViewer::setViewDetails(QTreeWidgetItem *node, const bind } } -QTreeWidgetItem *VulkanPipelineStateViewer::makeNode(const std::initializer_list &values) -{ - QTreeWidgetItem *ret = new QTreeWidgetItem(); - - int i = 0; - for(const QVariant &v : values) - ret->setData(i++, Qt::DisplayRole, v); - - return ret; -} - -QTreeWidgetItem *VulkanPipelineStateViewer::makeNode(const QVariantList &values) -{ - QTreeWidgetItem *ret = new QTreeWidgetItem(); - - int i = 0; - for(const QVariant &v : values) - ret->setData(i++, Qt::DisplayRole, v); - - return ret; -} - bool VulkanPipelineStateViewer::showNode(bool usedSlot, bool filledSlot) { const bool showDisabled = ui->showDisabled->isChecked(); @@ -762,7 +747,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, if(arrayLength > 1) { QTreeWidgetItem *node = - makeNode({"", setname, slotname, tr("Array[%1]").arg(arrayLength), "", "", "", ""}); + makeTreeNode({"", setname, slotname, tr("Array[%1]").arg(arrayLength), "", "", "", ""}); if(!filledSlot) setEmptyRow(node); @@ -864,7 +849,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(!isbuf) { - node = makeNode({ + node = makeTreeNode({ "", bindset, slotname, ToQStr(bindType), "-", "-", "", }); @@ -876,7 +861,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, if(descriptorBind != NULL) range = QString("%1 - %2").arg(descriptorBind->offset).arg(descriptorBind->size); - node = makeNode({ + node = makeTreeNode({ "", bindset, slotname, ToQStr(bindType), name, QString("%1 bytes").arg(len), range, }); @@ -895,7 +880,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(descriptorBind == NULL || descriptorBind->sampler == ResourceId()) { - node = makeNode({ + node = makeTreeNode({ "", bindset, slotname, ToQStr(bindType), "-", "-", "", }); @@ -903,7 +888,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, } else { - node = makeNode(makeSampler(QString::number(bindset), slotname, *descriptorBind)); + node = makeTreeNode(makeSampler(QString::number(bindset), slotname, *descriptorBind)); if(!filledSlot) setEmptyRow(node); @@ -923,7 +908,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(descriptorBind == NULL || descriptorBind->res == ResourceId()) { - node = makeNode({ + node = makeTreeNode({ "", bindset, slotname, ToQStr(bindType), "-", "-", "", }); @@ -963,7 +948,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, if(restype == eResType_Texture2DMS || restype == eResType_Texture2DMSArray) dim += QString(", %1x MSAA").arg(samples); - node = makeNode({ + node = makeTreeNode({ "", bindset, slotname, typeName, name, dim, format, }); @@ -982,7 +967,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(descriptorBind == NULL || descriptorBind->sampler == ResourceId()) { - samplerNode = makeNode({ + samplerNode = makeTreeNode({ "", bindset, slotname, bindType, "-", "-", "", }); @@ -992,7 +977,7 @@ void VulkanPipelineStateViewer::addResourceRow(ShaderReflection *shaderDetails, { if(!samplers.contains(descriptorBind->sampler)) { - samplerNode = makeNode(makeSampler("", "", *descriptorBind)); + samplerNode = makeTreeNode(makeSampler("", "", *descriptorBind)); if(!filledSlot) setEmptyRow(samplerNode); @@ -1111,7 +1096,7 @@ void VulkanPipelineStateViewer::addConstantBlockRow(ShaderReflection *shaderDeta if(arrayLength > 1) { QTreeWidgetItem *node = - makeNode({"", setname, slotname, tr("Array[%1]").arg(arrayLength), "", ""}); + makeTreeNode({"", setname, slotname, tr("Array[%1]").arg(arrayLength), "", ""}); if(!filledSlot) setEmptyRow(node); @@ -1192,7 +1177,7 @@ void VulkanPipelineStateViewer::addConstantBlockRow(ShaderReflection *shaderDeta filledSlot = false; } - QTreeWidgetItem *node = makeNode({"", setname, slotname, name, vecrange, sizestr}); + QTreeWidgetItem *node = makeTreeNode({"", setname, slotname, name, vecrange, sizestr}); ubos->setHoverIcons(node, action, action_hover); @@ -1393,8 +1378,8 @@ void VulkanPipelineStateViewer::setShaderState(const VulkanPipelineState::Shader // could maybe get range from ShaderVariable.reg if it's filled out // from SPIR-V side. - QTreeWidgetItem *node = makeNode({"", "", ToQStr(cblock.name), "Push constants", "", - tr("%1 Variable(s)", "", cblock.variables.count)}); + QTreeWidgetItem *node = makeTreeNode({"", "", ToQStr(cblock.name), "Push constants", "", + tr("%1 Variable(s)", "", cblock.variables.count)}); ubos->setHoverIcons(node, action, action_hover); node->setData(0, Qt::UserRole, QVariant::fromValue(CBufferTag(cb, 0))); @@ -1465,7 +1450,7 @@ void VulkanPipelineStateViewer::setState() if(showNode(usedSlot, filledSlot)) { QTreeWidgetItem *node = - makeNode({i, name, a.location, a.binding, ToQStr(a.format.strname), a.byteoffset}); + makeTreeNode({i, name, a.location, a.binding, ToQStr(a.format.strname), a.byteoffset}); usedBindings[a.binding] = true; @@ -1569,8 +1554,8 @@ void VulkanPipelineStateViewer::setState() length = buf->length; } - QTreeWidgetItem *node = makeNode({"Index", name, "Index", (qulonglong)state.IA.ibuffer.offs, - draw->indexByteWidth, (qulonglong)length, ""}); + QTreeWidgetItem *node = makeTreeNode({"Index", name, "Index", (qulonglong)state.IA.ibuffer.offs, + draw->indexByteWidth, (qulonglong)length, ""}); ui->viBuffers->setHoverIcons(node, action, action_hover); @@ -1591,7 +1576,8 @@ void VulkanPipelineStateViewer::setState() { if(ibufferUsed || showEmpty) { - QTreeWidgetItem *node = makeNode({"Index", tr("No Buffer Set"), "Index", "-", "-", "-", ""}); + QTreeWidgetItem *node = + makeTreeNode({"Index", tr("No Buffer Set"), "Index", "-", "-", "-", ""}); ui->viBuffers->setHoverIcons(node, action, action_hover); @@ -1661,9 +1647,9 @@ void VulkanPipelineStateViewer::setState() QTreeWidgetItem *node = NULL; if(filledSlot) - node = makeNode({i, name, rate, (qulonglong)offset, stride, (qulonglong)length, ""}); + node = makeTreeNode({i, name, rate, (qulonglong)offset, stride, (qulonglong)length, ""}); else - node = makeNode({i, tr("No Binding"), "-", "-", "-", "-", ""}); + node = makeTreeNode({i, tr("No Binding"), "-", "-", "-", "-", ""}); ui->viBuffers->setHoverIcons(node, action, action_hover); @@ -1687,7 +1673,7 @@ void VulkanPipelineStateViewer::setState() { if(usedBindings[i]) { - QTreeWidgetItem *node = makeNode({i, tr("No Binding"), "-", "-", "-", "-", ""}); + QTreeWidgetItem *node = makeTreeNode({i, tr("No Binding"), "-", "-", "-", "-", ""}); ui->viBuffers->setHoverIcons(node, action, action_hover); @@ -1728,8 +1714,8 @@ void VulkanPipelineStateViewer::setState() if(state.Pass.renderpass.obj != ResourceId()) { ui->scissors->addTopLevelItem( - makeNode({"Render Area", state.Pass.renderArea.x, state.Pass.renderArea.y, - state.Pass.renderArea.width, state.Pass.renderArea.height})); + makeTreeNode({"Render Area", state.Pass.renderArea.x, state.Pass.renderArea.y, + state.Pass.renderArea.width, state.Pass.renderArea.height})); } { @@ -1737,13 +1723,13 @@ void VulkanPipelineStateViewer::setState() for(const VulkanPipelineState::ViewState::ViewportScissor &v : state.VP.viewportScissors) { QTreeWidgetItem *node = - makeNode({i, v.vp.x, v.vp.y, v.vp.width, v.vp.height, v.vp.minDepth, v.vp.maxDepth}); + makeTreeNode({i, v.vp.x, v.vp.y, v.vp.width, v.vp.height, v.vp.minDepth, v.vp.maxDepth}); ui->viewports->addTopLevelItem(node); if(v.vp.width == 0 || v.vp.height == 0) setEmptyRow(node); - node = makeNode({i, v.scissor.x, v.scissor.y, v.scissor.width, v.scissor.height}); + node = makeTreeNode({i, v.scissor.x, v.scissor.y, v.scissor.width, v.scissor.height}); ui->scissors->addTopLevelItem(node); if(v.scissor.width == 0 || v.scissor.height == 0) @@ -1854,7 +1840,7 @@ void VulkanPipelineStateViewer::setState() .arg(ToQStr(p.swizzle[3])); } - QTreeWidgetItem *node = makeNode({i, name, typeName, w, h, d, a, format, ""}); + QTreeWidgetItem *node = makeTreeNode({i, name, typeName, w, h, d, a, format, ""}); ui->framebuffer->setHoverIcons(node, action, action_hover); @@ -1900,19 +1886,19 @@ void VulkanPipelineStateViewer::setState() if(showNode(usedSlot, filledSlot)) { QTreeWidgetItem *node = - makeNode({i, blend.blendEnable, + makeTreeNode({i, blend.blendEnable, - ToQStr(blend.blend.Source), ToQStr(blend.blend.Destination), - ToQStr(blend.blend.Operation), + ToQStr(blend.blend.Source), ToQStr(blend.blend.Destination), + ToQStr(blend.blend.Operation), - ToQStr(blend.alphaBlend.Source), ToQStr(blend.alphaBlend.Destination), - ToQStr(blend.alphaBlend.Operation), + ToQStr(blend.alphaBlend.Source), ToQStr(blend.alphaBlend.Destination), + ToQStr(blend.alphaBlend.Operation), - QString("%1%2%3%4") - .arg((blend.writeMask & 0x1) == 0 ? "_" : "R") - .arg((blend.writeMask & 0x2) == 0 ? "_" : "G") - .arg((blend.writeMask & 0x4) == 0 ? "_" : "B") - .arg((blend.writeMask & 0x8) == 0 ? "_" : "A")}); + QString("%1%2%3%4") + .arg((blend.writeMask & 0x1) == 0 ? "_" : "R") + .arg((blend.writeMask & 0x2) == 0 ? "_" : "G") + .arg((blend.writeMask & 0x4) == 0 ? "_" : "B") + .arg((blend.writeMask & 0x8) == 0 ? "_" : "A")}); if(!filledSlot) setEmptyRow(node); @@ -1959,21 +1945,21 @@ void VulkanPipelineStateViewer::setState() if(state.DS.stencilTestEnable) { ui->stencils->addTopLevelItems( - {makeNode({"Front", ToQStr(state.DS.front.func), ToQStr(state.DS.front.failOp), - ToQStr(state.DS.front.depthFailOp), ToQStr(state.DS.front.passOp), - QString("%1").arg(state.DS.front.writeMask, 2, 16, QChar('0')), - QString("%1").arg(state.DS.front.compareMask, 2, 16, QChar('0')), - QString("%1").arg(state.DS.front.ref, 2, 16, QChar('0'))}), - makeNode({"Back", ToQStr(state.DS.back.func), ToQStr(state.DS.back.failOp), - ToQStr(state.DS.back.depthFailOp), ToQStr(state.DS.back.passOp), - QString("%1").arg(state.DS.back.writeMask, 2, 16, QChar('0')), - QString("%1").arg(state.DS.back.compareMask, 2, 16, QChar('0')), - QString("%1").arg(state.DS.back.ref, 2, 16, QChar('0'))})}); + {makeTreeNode({"Front", ToQStr(state.DS.front.func), ToQStr(state.DS.front.failOp), + ToQStr(state.DS.front.depthFailOp), ToQStr(state.DS.front.passOp), + QString("%1").arg(state.DS.front.writeMask, 2, 16, QChar('0')), + QString("%1").arg(state.DS.front.compareMask, 2, 16, QChar('0')), + QString("%1").arg(state.DS.front.ref, 2, 16, QChar('0'))}), + makeTreeNode({"Back", ToQStr(state.DS.back.func), ToQStr(state.DS.back.failOp), + ToQStr(state.DS.back.depthFailOp), ToQStr(state.DS.back.passOp), + QString("%1").arg(state.DS.back.writeMask, 2, 16, QChar('0')), + QString("%1").arg(state.DS.back.compareMask, 2, 16, QChar('0')), + QString("%1").arg(state.DS.back.ref, 2, 16, QChar('0'))})}); } else { - ui->stencils->addTopLevelItems({makeNode({"Front", "-", "-", "-", "-", "-", "-", "-"}), - makeNode({"Back", "-", "-", "-", "-", "-", "-", "-"})}); + ui->stencils->addTopLevelItems({makeTreeNode({"Front", "-", "-", "-", "-", "-", "-", "-"}), + makeTreeNode({"Back", "-", "-", "-", "-", "-", "-", "-"})}); } ui->stencils->clearSelection(); ui->stencils->setUpdatesEnabled(true); @@ -2134,6 +2120,37 @@ void VulkanPipelineStateViewer::resource_itemActivated(QTreeWidgetItem *item, in } } +void VulkanPipelineStateViewer::ubo_itemActivated(QTreeWidgetItem *item, int column) +{ + const VulkanPipelineState::ShaderStage *stage = stageForSender(item->treeWidget()); + + if(stage == NULL) + return; + + QVariant tag = item->data(0, Qt::UserRole); + + if(!tag.canConvert()) + return; + + CBufferTag cb = tag.value(); + + ConstantBufferPreviewer *existing = + ConstantBufferPreviewer::has(stage->stage, cb.slotIdx, cb.arrayIdx); + if(existing != NULL) + { + ToolWindowManager::raiseToolWindow(existing); + return; + } + + ConstantBufferPreviewer *prev = + new ConstantBufferPreviewer(m_Ctx, stage->stage, cb.slotIdx, cb.arrayIdx, m_Ctx->mainWindow()); + + ToolWindowManager *manager = ToolWindowManager::managerOf(this); + + ToolWindowManager::AreaReference ref(ToolWindowManager::RightOf, manager->areaOf(this), 0.3f); + manager->addToolWindow(prev, ref); +} + void VulkanPipelineStateViewer::on_viAttrs_itemActivated(QTreeWidgetItem *item, int column) { // TODO Buffer Viewer diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index 8c9b26061..eca9893ce 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -70,6 +70,7 @@ private slots: void shaderEdit_clicked(); void shaderSave_clicked(); void resource_itemActivated(QTreeWidgetItem *item, int column); + void ubo_itemActivated(QTreeWidgetItem *item, int column); void vertex_leave(QEvent *e); private: @@ -107,8 +108,6 @@ private: template void setViewDetails(QTreeWidgetItem *node, const viewType &view, FetchBuffer *buf); - QTreeWidgetItem *makeNode(const std::initializer_list &values); - QTreeWidgetItem *makeNode(const QVariantList &values); bool showNode(bool usedSlot, bool filledSlot); // keep track of the VB nodes (we want to be able to highlight them easily on hover) diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 04cdc871c..a32ed6268 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -117,7 +117,10 @@ SOURCES += 3rdparty/toolwindowmanager/ToolWindowManager.cpp \ Windows/PipelineState/D3D11PipelineStateViewer.cpp \ Windows/PipelineState/D3D12PipelineStateViewer.cpp \ Windows/PipelineState/GLPipelineStateViewer.cpp \ - Widgets/Extended/RDTreeWidget.cpp + Widgets/Extended/RDTreeWidget.cpp \ + Windows/ConstantBufferPreviewer.cpp \ + Widgets/BufferFormatSpecifier.cpp \ + Code/FormatElement.cpp HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ 3rdparty/toolwindowmanager/ToolWindowManagerArea.h \ @@ -152,7 +155,9 @@ HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ Windows/PipelineState/D3D11PipelineStateViewer.h \ Windows/PipelineState/D3D12PipelineStateViewer.h \ Windows/PipelineState/GLPipelineStateViewer.h \ - Widgets/Extended/RDTreeWidget.h + Widgets/Extended/RDTreeWidget.h \ + Windows/ConstantBufferPreviewer.h \ + Widgets/BufferFormatSpecifier.h FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/MainWindow.ui \ @@ -168,7 +173,9 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/PipelineState/VulkanPipelineStateViewer.ui \ Windows/PipelineState/D3D11PipelineStateViewer.ui \ Windows/PipelineState/D3D12PipelineStateViewer.ui \ - Windows/PipelineState/GLPipelineStateViewer.ui + Windows/PipelineState/GLPipelineStateViewer.ui \ + Windows/ConstantBufferPreviewer.ui \ + Widgets/BufferFormatSpecifier.ui RESOURCES += \ resources.qrc diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 042e9b777..a5f61e12d 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -281,12 +281,15 @@ + + + @@ -313,6 +316,7 @@ + @@ -325,6 +329,7 @@ + @@ -360,7 +365,9 @@ + + @@ -374,6 +381,7 @@ + @@ -386,6 +394,7 @@ + @@ -406,9 +415,11 @@ + + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 13e4c459f..7dd74c17e 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -238,6 +238,21 @@ Generated Files + + Windows + + + Widgets + + + Generated Files + + + Generated Files + + + Generated Files + @@ -378,6 +393,18 @@ Widgets\Extended + + Windows + + + Widgets + + + Generated Files + + + Generated Files + @@ -602,6 +629,12 @@ Resources\Files + + Windows + + + Widgets +