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 | |
11 | static 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 | } |
30 | static 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 | |
52 | static 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 | |
61 | QsbInspectorHelper::QsbInspectorHelper(QObject *parent) |
62 | : QObject{parent} |
63 | { |
64 | |
65 | } |
66 | |
67 | QsbShaderData QsbInspectorHelper::shaderData() const |
68 | { |
69 | return m_shaderData; |
70 | } |
71 | |
72 | QVariantList QsbInspectorHelper::sourceSelectorModel() const |
73 | { |
74 | return m_sourceSelectorModel; |
75 | } |
76 | |
77 | int QsbInspectorHelper::currentSourceIndex() const |
78 | { |
79 | return m_currentSourceIndex; |
80 | } |
81 | |
82 | void 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 | |
92 | QString 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 |
100 | bool 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 | |