1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qsbinspectorhelper.h"
5#include <QFile>
6#include <QCollator>
7#include <QUrl>
8
9// Note: These methods should be kept up-to-date with the qsb tool.
10
11static QString stageStr(QShader::Stage stage)
12{
13 switch (stage) {
14 case QShader::VertexStage:
15 return QStringLiteral("Vertex");
16 case QShader::TessellationControlStage:
17 return QStringLiteral("TessellationControl");
18 case QShader::TessellationEvaluationStage:
19 return QStringLiteral("TessellationEvaluation");
20 case QShader::GeometryStage:
21 return QStringLiteral("Geometry");
22 case QShader::FragmentStage:
23 return QStringLiteral("Fragment");
24 case QShader::ComputeStage:
25 return QStringLiteral("Compute");
26 default:
27 Q_UNREACHABLE();
28 }
29}
30static QString sourceStr(QShader::Source source)
31{
32 switch (source) {
33 case QShader::SpirvShader:
34 return QStringLiteral("SPIR-V");
35 case QShader::GlslShader:
36 return QStringLiteral("GLSL");
37 case QShader::HlslShader:
38 return QStringLiteral("HLSL");
39 case QShader::DxbcShader:
40 return QStringLiteral("DXBC");
41 case QShader::MslShader:
42 return QStringLiteral("MSL");
43 case QShader::DxilShader:
44 return QStringLiteral("DXIL");
45 case QShader::MetalLibShader:
46 return QStringLiteral("metallib");
47 default:
48 Q_UNREACHABLE();
49 }
50}
51
52static QString sourceVersionStr(const QShaderVersion &v)
53{
54 QString s = v.version() ? QString::number(v.version()) : QString();
55 if (v.flags().testFlag(flag: QShaderVersion::GlslEs))
56 s += QLatin1String(" es");
57
58 return s;
59}
60
61QsbInspectorHelper::QsbInspectorHelper(QObject *parent)
62 : QObject{parent}
63{
64
65}
66
67QsbShaderData QsbInspectorHelper::shaderData() const
68{
69 return m_shaderData;
70}
71
72QVariantList QsbInspectorHelper::sourceSelectorModel() const
73{
74 return m_sourceSelectorModel;
75}
76
77int QsbInspectorHelper::currentSourceIndex() const
78{
79 return m_currentSourceIndex;
80}
81
82void QsbInspectorHelper::setCurrentSourceIndex(int index)
83{
84 if (m_currentSourceIndex == index)
85 return;
86 m_currentSourceIndex = index;
87 Q_EMIT currentSourceIndexChanged();
88 // When index changes, update source also
89 Q_EMIT currentSourceCodeChanged();
90}
91
92QString QsbInspectorHelper::currentSourceCode() const
93{
94 if (m_currentSourceIndex == -1 || m_currentSourceIndex >= m_sourceCodes.size())
95 return QString();
96 return m_sourceCodes.at(i: m_currentSourceIndex);
97}
98
99// Loads QSB from filename and updates all data
100bool QsbInspectorHelper::loadQsb(const QString &filename)
101{
102 QString qsbFilename = filename;
103 QUrl url(filename);
104 if (url.scheme() == QStringLiteral("file"))
105 qsbFilename = url.toLocalFile();
106
107 QFile qsbFile(qsbFilename);
108 if (!qsbFile.open(flags: QIODevice::ReadOnly)) {
109 qWarning(msg: "Failed to open %s", qPrintable(qsbFilename));
110 return false;
111 }
112
113 QByteArray buf = qsbFile.readAll();
114 if (buf.isEmpty()) {
115 qWarning(msg: "Empty QSB file %s", qPrintable(qsbFilename));
116 return false;
117 }
118
119 QShader bs = QShader::fromSerialized(data: buf);
120 if (!bs.isValid()) {
121 qWarning(msg: "Invalid QSB file %s", qPrintable(qsbFilename));
122 return false;
123 }
124
125 const auto keys = bs.availableShaders();
126
127 m_shaderData.m_currentFile = qsbFile.fileName();
128 m_shaderData.m_stage = stageStr(stage: bs.stage());
129 m_shaderData.m_size = qsbFile.size();
130 m_shaderData.m_shaderCount = keys.count();
131 m_shaderData.m_qsbVersion = QShaderPrivate::get(s: &bs)->qsbVersion;
132 m_shaderData.m_reflectionInfo = bs.description().toJson();
133
134 m_sourceCodes.clear();
135 QVariantList sourceSelectorModel;
136 for (int i = 0; i < m_shaderData.m_shaderCount; ++i) {
137 const auto shaderKey = keys[i];
138 ShaderSelectorData selectorData;
139 selectorData.m_sourceIndex = i;
140 QString name = QString("%1 %2").arg(a: sourceStr(source: shaderKey.source()))
141 .arg(a: sourceVersionStr(v: shaderKey.sourceVersion()));
142 selectorData.m_name = name;
143 sourceSelectorModel << QVariant::fromValue(value: selectorData);
144
145 QShaderCode shaderCode = bs.shader(key: keys[i]);
146 switch (shaderKey.source()) {
147 case QShader::SpirvShader:
148 Q_FALLTHROUGH();
149 case QShader::DxbcShader:
150 Q_FALLTHROUGH();
151 case QShader::DxilShader:
152 Q_FALLTHROUGH();
153 case QShader::MetalLibShader:
154 // For binary shaders show just information
155 m_sourceCodes << QString("%1 binary of %2 bytes").arg(a: selectorData.m_name).arg(a: shaderCode.shader().size());
156 break;
157 default:
158 m_sourceCodes << shaderCode.shader();
159 break;
160 }
161 }
162
163 // QSB can contain shaders in any order, for our use
164 // sort them to alphaberical order in UI.
165 struct {
166 bool operator()(QVariant a, QVariant b) const {
167 ShaderSelectorData t1 = a.value<ShaderSelectorData>();
168 ShaderSelectorData t2 = b.value<ShaderSelectorData>();
169 return t1.m_name < t2.m_name;
170 }
171 } selectorDataSort;
172 std::sort(first: sourceSelectorModel.begin(), last: sourceSelectorModel.end(), comp: selectorDataSort);
173
174 // Add info as the first element, with index -1
175 ShaderSelectorData infoSelectorData;
176 infoSelectorData.m_sourceIndex = -1;
177 infoSelectorData.m_name = "DETAILS";
178 sourceSelectorModel.prepend(t: QVariant::fromValue(value: infoSelectorData));
179
180 if (sourceSelectorModel.size() != m_sourceSelectorModel.size()) {
181 // When the QSB shaders model has changed, select the deltails
182 m_sourceSelectorModel = sourceSelectorModel;
183 setCurrentSourceIndex(-1);
184 Q_EMIT sourceSelectorModelChanged();
185 }
186
187 // Source codes and shader data are always updated
188 Q_EMIT currentSourceCodeChanged();
189 Q_EMIT shaderDataChanged();
190
191 return true;
192}
193

source code of qtquickeffectmaker/tools/qqem/qsbinspectorhelper.cpp