QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GraphicsSetup.cc
Go to the documentation of this file.
1#include "GraphicsSetup.h"
2
3#include <QtCore/QDir>
4#include <QtCore/QFileInfo>
5#include <QtCore/QRunnable>
6#include <QtCore/QStandardPaths>
7#include <QtCore/QString>
8#include <QtCore/QStringList>
9#include <QtQuick/QQuickGraphicsConfiguration>
10#include <QtQuick/QQuickGraphicsDevice>
11#include <QtQuick/QQuickWindow>
12#include <memory>
13#include <rhi/qrhi.h>
14
15#include "QGCLoggingCategory.h"
16
17QGC_LOGGING_CATEGORY(GraphicsSetupLog, "API.GraphicsSetup")
18
19namespace GraphicsSetup {
20
21namespace {
22
23constexpr const char* kEnvRhiDebug = "QGC_RHI_DEBUG";
24constexpr const char* kEnvRhiPipelineCache = "QGC_RHI_PIPELINE_CACHE";
25constexpr const char* kEnvHdrOutput = "QGC_HDR_OUTPUT";
26constexpr const char* kEnvForceVideoGpu = "QGC_FORCE_VIDEO_GPU";
27
28bool envFlag(const char* name)
29{
30 return qEnvironmentVariableIsSet(name) && (qEnvironmentVariable(name) != QLatin1String("0"));
31}
32
33bool envEnabledByDefault(const char* name)
34{
35 return !qEnvironmentVariableIsSet(name) || (qEnvironmentVariable(name) != QLatin1String("0"));
36}
37
38void configurePipelineCache(QQuickGraphicsConfiguration& config)
39{
40 if (!envEnabledByDefault(kEnvRhiPipelineCache)) {
41 return;
42 }
43
44 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
45 if (cacheDir.isEmpty()) {
46 qCWarning(GraphicsSetupLog) << "RHI pipeline cache disabled: no writable cache location";
47 return;
48 }
49
50 QDir dir;
51 if (!dir.mkpath(cacheDir)) {
52 qCWarning(GraphicsSetupLog) << "RHI pipeline cache disabled: failed to create" << cacheDir;
53 return;
54 }
55
56 const QString cacheFile = cacheDir + QStringLiteral("/qgc_rhi_pipeline.cache");
57 // Only load when the cache already exists: Qt 6.11 warns if the load file
58 // can't be opened, which is expected on first run. Always set the save file
59 // so the cache is written at shutdown and picked up on the next launch.
60 if (QFileInfo::exists(cacheFile)) {
61 config.setPipelineCacheLoadFile(cacheFile);
62 }
63 config.setPipelineCacheSaveFile(cacheFile);
64 qCDebug(GraphicsSetupLog) << "RHI pipeline cache:" << cacheFile;
65}
66
67#if defined(Q_OS_WIN)
68bool parseHex32(const QString& spec, quint32& value)
69{
70 QString token = spec.trimmed();
71 if (token.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
72 token.remove(0, 2);
73 }
74
75 if (token.isEmpty()) {
76 return false;
77 }
78
79 bool ok = false;
80 value = token.toUInt(&ok, 16);
81 return ok;
82}
83
84qint32 signExtend32(quint32 value)
85{
86 const qint64 signedValue =
87 (value <= 0x7FFFFFFFU) ? static_cast<qint64>(value) : (static_cast<qint64>(value) - 0x100000000LL);
88 return static_cast<qint32>(signedValue);
89}
90
91bool parseLuid(const QString& spec, quint32& low, qint32& high)
92{
93 const QStringList parts = spec.split(QLatin1Char(':'));
94 if (parts.size() != 2) {
95 return false;
96 }
97
98 quint32 highRaw = 0;
99 if (!parseHex32(parts.at(0), low) || !parseHex32(parts.at(1), highRaw)) {
100 return false;
101 }
102
103 high = signExtend32(highRaw);
104 return true;
105}
106
107void configureForcedD3DAdapter(QQuickWindow* window)
108{
109 if (!qEnvironmentVariableIsSet(kEnvForceVideoGpu)) {
110 return;
111 }
112
113 const QString spec = qEnvironmentVariable(kEnvForceVideoGpu);
114 quint32 luidLow = 0;
115 qint32 luidHigh = 0;
116 if (!parseLuid(spec, luidLow, luidHigh)) {
117 qCWarning(GraphicsSetupLog) << "QGC_FORCE_VIDEO_GPU malformed (expected hex 'low:high'):" << spec;
118 return;
119 }
120
121 // Must run before QRhi creation; Qt Quick owns the adapter after scene graph init.
122 window->setGraphicsDevice(QQuickGraphicsDevice::fromAdapter(luidLow, luidHigh));
123 qCWarning(GraphicsSetupLog).nospace()
124 << "QGC_FORCE_VIDEO_GPU: forcing adapter LUID low=0x" << QString::number(luidLow, 16) << " high=0x"
125 << QString::number(static_cast<quint32>(luidHigh), 16);
126}
127#else
128void warnForcedGpuUnsupported()
129{
130 if (qEnvironmentVariableIsSet(kEnvForceVideoGpu)) {
131 qCWarning(GraphicsSetupLog)
132 << "QGC_FORCE_VIDEO_GPU is Windows/D3D-only; ignoring LUID" << qEnvironmentVariable(kEnvForceVideoGpu);
133 }
134}
135#endif
136
137const char* hdrFormatName(QRhiSwapChain::Format f)
138{
139 switch (f) {
140 case QRhiSwapChain::SDR:
141 return "SDR";
142 case QRhiSwapChain::HDRExtendedSrgbLinear:
143 return "HDRExtendedSrgbLinear";
144 case QRhiSwapChain::HDR10:
145 return "HDR10";
146 case QRhiSwapChain::HDRExtendedDisplayP3Linear:
147 return "HDRExtendedDisplayP3Linear";
148 }
149 return "Unknown";
150}
151
152void probeHdr(QQuickWindow* window)
153{
154 QRhi* rhi = window->rhi();
155 if (!rhi) {
156 qCWarning(GraphicsSetupLog) << "HDR probe: no QRhi on render thread";
157 return;
158 }
159
160 std::unique_ptr<QRhiSwapChain> sc(rhi->newSwapChain());
161 if (!sc) {
162 qCWarning(GraphicsSetupLog) << "HDR probe: backend has no swapchain support";
163 return;
164 }
165 sc->setWindow(window);
166
167 const QRhiSwapChainHdrInfo info = sc->hdrInfo();
168 qCDebug(GraphicsSetupLog).nospace()
169 << "HDR display info: limitsType=" << static_cast<int>(info.limitsType)
170 << " luminanceBehavior=" << static_cast<int>(info.luminanceBehavior) << " sdrWhiteLevel=" << info.sdrWhiteLevel;
171
172 for (const QRhiSwapChain::Format f :
173 {QRhiSwapChain::HDRExtendedSrgbLinear, QRhiSwapChain::HDR10, QRhiSwapChain::HDRExtendedDisplayP3Linear}) {
174 qCDebug(GraphicsSetupLog) << "HDR format" << hdrFormatName(f) << "supported:" << sc->isFormatSupported(f);
175 }
176
177 if (envFlag(kEnvHdrOutput)) {
178 qCWarning(GraphicsSetupLog)
179 << "QGC_HDR_OUTPUT set but Qt Quick exposes no public swapchain-format setter in this"
180 << "Qt version; staying on SDR. (Diagnostic only.)";
181 }
182}
183
184} // namespace
185
186void configureMainWindow(QQuickWindow* window)
187{
188 if (!window) {
189 qCWarning(GraphicsSetupLog) << "configureMainWindow: null window";
190 return;
191 }
192
193 if (window->isSceneGraphInitialized()) {
194 qCWarning(GraphicsSetupLog)
195 << "Scene graph already initialized before configureMainWindow; skipping RHI config";
196 } else {
197 QQuickGraphicsConfiguration config = window->graphicsConfiguration();
198
199 if (envFlag(kEnvRhiDebug)) {
200 config.setDebugLayer(true);
201 config.setDebugMarkers(true);
202 config.setTimestamps(true);
203 qCDebug(GraphicsSetupLog) << "RHI debug layer/markers/timestamps enabled";
204 }
205
206 configurePipelineCache(config);
207 window->setGraphicsConfiguration(config);
208
209#if defined(Q_OS_WIN)
210 configureForcedD3DAdapter(window);
211#else
212 warnForcedGpuUnsupported();
213#endif
214 }
215
216 if (!envFlag(kEnvRhiDebug) && !envFlag(kEnvHdrOutput)) {
217 return;
218 }
219 QQuickWindow* win = window;
220 if (win->isSceneGraphInitialized()) {
221 win->scheduleRenderJob(QRunnable::create([win]() { probeHdr(win); }), QQuickWindow::BeforeSynchronizingStage);
222 win->update();
223 } else {
224 QObject::connect(
225 win, &QQuickWindow::sceneGraphInitialized, win, [win]() { probeHdr(win); }, Qt::DirectConnection);
226 }
227}
228
229} // namespace GraphicsSetup
Config config
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void configureMainWindow(QQuickWindow *window)