| 1 | // Copyright (C) 2020 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquickgraphicsconfiguration_p.h" |
| 5 | #include <QCoreApplication> |
| 6 | #include <rhi/qrhi.h> |
| 7 | |
| 8 | QT_BEGIN_NAMESPACE |
| 9 | |
| 10 | /*! |
| 11 | \class QQuickGraphicsConfiguration |
| 12 | \since 6.0 |
| 13 | \inmodule QtQuick |
| 14 | |
| 15 | \brief QQuickGraphicsConfiguration controls lower level graphics settings |
| 16 | for the QQuickWindow. |
| 17 | |
| 18 | The QQuickGraphicsConfiguration class is a container for low-level graphics |
| 19 | settings that can affect how the underlying graphics API, such as Vulkan, |
| 20 | is initialized by the Qt Quick scene graph. It can also control certain |
| 21 | aspects of the scene graph renderer. |
| 22 | |
| 23 | \note Setting a QQuickGraphicsConfiguration on a QQuickWindow must happen |
| 24 | early enough, before the scene graph is initialized for the first time for |
| 25 | that window. With on-screen windows this means the call must be done before |
| 26 | invoking show() on the QQuickWindow or QQuickView. With QQuickRenderControl |
| 27 | the configuration must be finalized before calling |
| 28 | \l{QQuickRenderControl::initialize()}{initialize()}. |
| 29 | |
| 30 | \section1 Configuration for External Rendering Engines or XR APIs |
| 31 | |
| 32 | When constructing and showing a QQuickWindow that uses Vulkan to render, a |
| 33 | Vulkan instance (\c VkInstance), a physical device (\c VkPhysicalDevice), a |
| 34 | device (\c VkDevice) and associated objects (queues, pools) are initialized |
| 35 | through the Vulkan API. The same is mostly true when using |
| 36 | QQuickRenderControl to redirect the rendering into a custom render target, |
| 37 | such as a texture. While QVulkanInstance construction is under the |
| 38 | application's control then, the initialization of other graphics objects |
| 39 | happen the same way in QQuickRenderControl::initialize() as with an |
| 40 | on-screen QQuickWindow. |
| 41 | |
| 42 | For the majority of applications no additional configuration is needed |
| 43 | because Qt Quick provides reasonable defaults for many low-level graphics |
| 44 | settings, for example which device extensions to enable. |
| 45 | |
| 46 | This will not alway be sufficient, however. In advanced use cases, when |
| 47 | integrating direct Vulkan or other graphics API content, or when |
| 48 | integrating with an external 3D or VR engine, such as, OpenXR, the |
| 49 | application will want to specify its own set of settings when it comes to |
| 50 | details, such as which device extensions to enable. |
| 51 | |
| 52 | That is what this class enables. It allows specifying, for example, a list |
| 53 | of device extensions that is then picked up by the scene graph when using |
| 54 | Vulkan, or graphics APIs where the concept is applicable. Where some |
| 55 | concepts are not applicable, the related settings are simply ignored. |
| 56 | |
| 57 | Examples of functions in this category are setDeviceExtensions() and |
| 58 | preferredInstanceExtensions(). The latter is useful when the application |
| 59 | manages its own \l QVulkanInstance which is then associated with the |
| 60 | QQuickWindow via \l QWindow::setVulkanInstance(). |
| 61 | |
| 62 | \section1 Qt Quick Scene Graph Renderer Configuration |
| 63 | |
| 64 | Another class of settings are related to the scene graph's renderer. In |
| 65 | some cases applications may want to control certain behavior,such as using |
| 66 | the depth buffer when rendering 2D content. In Qt 5 such settings were |
| 67 | either not controllable at all, or were managed through environment |
| 68 | variables. In Qt 6, QQuickGraphicsConfiguration provides a new home for |
| 69 | these settings, while keeping support for the legacy environment variables, |
| 70 | where applicable. |
| 71 | |
| 72 | An example in this category is setDepthBufferFor2D(). |
| 73 | |
| 74 | \section1 Graphics Device Configuration |
| 75 | |
| 76 | When the graphics instance and device objects (for example, the VkInstance |
| 77 | and VkDevice with Vulkan, the ID3D11Device with Direct 3D, etc.) are |
| 78 | created by Qt when initializing a QQuickWindow, there are settings which |
| 79 | applications or libraries will want to control under certain circumstances. |
| 80 | |
| 81 | Before Qt 6.5, some of such settings were available to control via |
| 82 | environment variables. For example, \c QSG_RHI_DEBUG_LAYER or \c |
| 83 | QSG_RHI_PREFER_SOFTWARE_RENDERER. These are still available and continue to |
| 84 | function as before. QQuickGraphicsConfiguration provides C++ setters in |
| 85 | addition. |
| 86 | |
| 87 | For example, the following main() function opens a QQuickView while |
| 88 | specifying that the Vulkan validation or Direct3D debug layer should be |
| 89 | enabled: |
| 90 | |
| 91 | \code |
| 92 | int main(int argc, char *argv[]) |
| 93 | { |
| 94 | QGuiApplication app(argc, argv); |
| 95 | |
| 96 | QQuickGraphicsConfiguration config; |
| 97 | config.setDebugLayer(true); |
| 98 | |
| 99 | QQuickView *view = new QQuickView; |
| 100 | view->setGraphicsConfiguration(config); |
| 101 | |
| 102 | view->setSource(QUrl::fromLocalFile("myqmlfile.qml")); |
| 103 | view->show(); |
| 104 | return app.exec(); |
| 105 | } |
| 106 | \endcode |
| 107 | |
| 108 | \section1 Pipeline Cache Save and Load |
| 109 | |
| 110 | Qt Quick supports storing the graphics/compute pipeline cache to disk, and |
| 111 | reloading it in subsequent runs of an application. What exactly the |
| 112 | pipeline cache contains, how lookups work, and what exactly gets |
| 113 | accelerated all depend on the Qt RHI backend and the underlying native |
| 114 | graphics API that is used at run time. Different 3D APIs have different |
| 115 | concepts when it comes to shaders, programs, and pipeline state objects, |
| 116 | and corresponding cache mechanisms. The high level pipeline cache concept |
| 117 | here abstracts all this to storing and retrieving a single binary blob to |
| 118 | and from a file. |
| 119 | |
| 120 | \note Storing the cache on disk can lead to improvements, sometimes |
| 121 | significant, in subsequent runs of the application. |
| 122 | |
| 123 | When the same shader program and/or pipeline state is encountered as in a |
| 124 | previous run, a number of operations are likely skipped, leading to faster |
| 125 | shader and material initialization times, which means startup may become |
| 126 | faster and lags and "janks" during rendering may be reduced or avoided. |
| 127 | |
| 128 | When running with a graphics API where retrieving and reloading the |
| 129 | pipeline cache (or shader/program binaries) is not applicable or not |
| 130 | supported, attempting to use a file to save and load the cache has no |
| 131 | effect. |
| 132 | |
| 133 | \note In many cases the retrieved data is dependent on and tied to the |
| 134 | graphics driver (and possibly the exact version of it). Qt performs the |
| 135 | necessary checks automatically, by storing additional metadata in the |
| 136 | pipeline cache file. If the data in the file does not match the graphics |
| 137 | device and driver version at run time, the contents will be ignored |
| 138 | transparently to the application. It is therefore safe to reference a cache |
| 139 | that was generated on another device or driver. |
| 140 | |
| 141 | There are exceptions to the driver dependency problem, most notably Direct |
| 142 | 3D 11, where the "pipeline cache" is used only to store the results of |
| 143 | runtime HLSL->DXBC compilation and is therefore device and vendor |
| 144 | independent. |
| 145 | |
| 146 | In some cases it may be desirable to improve the very first run of the |
| 147 | application, by "pre-seeding" the cache. This is possible by shipping the |
| 148 | cache file saved from a previous run, and referencing it on another machine |
| 149 | or device. This way, the application or device has the shader |
| 150 | programs/pipelines that have been encountered before in the run that saved |
| 151 | the cache file available already during its first run. Shipping and |
| 152 | deploying the cache file only makes sense if the device and graphics |
| 153 | drivers are the same on the target system, otherwise the cache file is |
| 154 | ignored if the device or driver version does not match (with the exception |
| 155 | of D3D11), as described above. |
| 156 | |
| 157 | Once the cache contents is loaded, there is still a chance that the |
| 158 | application builds graphics and compute pipelines that have not been |
| 159 | encountered in previous runs. In this cases the cache is grown, with the |
| 160 | pipelines / shader programs added to it. If the application also chooses to |
| 161 | save the contents (perhaps to the same file even), then both the old and |
| 162 | new pipelines will get stored. Loading from and saving to the same file in |
| 163 | every run allows an ever growing cache that stores all encountered |
| 164 | pipelines and shader programs. |
| 165 | |
| 166 | In practice the Qt pipeline cache can be expected to map to the following |
| 167 | native graphics API features: |
| 168 | |
| 169 | \list |
| 170 | |
| 171 | \li Vulkan - |
| 172 | \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPipelineCache.html}{VkPipelineCache} |
| 173 | - Saving the pipeline cache effectively stores the blob retrieved from |
| 174 | \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetPipelineCacheData.html}{vkGetPipelineCacheData}, |
| 175 | with additional metadata to safely identify the device and the driver |
| 176 | since the pipeline cache blob is dependent on the exact driver. |
| 177 | |
| 178 | \li Metal - |
| 179 | \l{https://developer.apple.com/documentation/metal/mtlbinaryarchive?language=objc}{MTLBinaryArchive} |
| 180 | - With pipeline cache saving enabled, Qt stores all render and compute |
| 181 | pipelines encountered into an MTLBinaryArchive. Saving the pipeline cache |
| 182 | stores the blob retrieved from the archive, with additional metadata to |
| 183 | identify the device. \b{Note:} currently MTLBinaryArchive usage is disabled |
| 184 | on macOS and iOS due to various issues on some hardware and OS versions. |
| 185 | |
| 186 | \li OpenGL - There is no native concept of pipelines, the "pipeline cache" |
| 187 | stores a collection of program binaries retrieved via |
| 188 | \l{https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetProgramBinary.xhtml}{glGetProgramBinary}. |
| 189 | The program binaries are packaged into a single blob, with additional |
| 190 | metadata to identify the device, driver, and its version that the binaries |
| 191 | were retrieved from. Persistent caching of program binaries is not new in |
| 192 | Qt: Qt 5 already had similar functionality in QOpenGLShaderProgram, see |
| 193 | \l{QOpenGLShaderProgram::}{addCacheableShaderFromSourceCode()} |
| 194 | for example. In fact that mechanism is always active in Qt 6 as well when |
| 195 | using Qt Quick with OpenGL. However, when using the new, graphics API |
| 196 | independent pipeline cache abstraction provided here, the Qt 5 era program |
| 197 | binary cache gets automatically disabled, since the same content is |
| 198 | packaged in the "pipeline cache" now. |
| 199 | |
| 200 | \li Direct 3D 11 - There is no native concept of pipelines or retrieving |
| 201 | binaries for the second phase compilation (where the vendor independent, |
| 202 | intermediate bytecode is compiled into the device specific instruction |
| 203 | set). Drivers will typically employ their own caching system on that level. |
| 204 | Instead, the Qt Quick "pipeline cache" is used to speed up cases where the |
| 205 | shaders contain HLSL source code that needs to be compiled into the |
| 206 | intermediate bytecode format first. This can present significant |
| 207 | performance improvements in application and libraries that compose shader |
| 208 | code at run time, because in subsequent runs the potentially expensive, |
| 209 | uncached calls to |
| 210 | \l{https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompile}{D3DCompile()} |
| 211 | can be avoided if the bytecode is already available for the encountered |
| 212 | HLSL shader. A good example is Qt Quick 3D, where the runtime-generated |
| 213 | shaders for materials imply having to deal with HLSL source code. Saving |
| 214 | and reloading the Qt Quick pipeline cache can therefore bring considerable |
| 215 | improvements in scenes with one or more \l{View3D} items in |
| 216 | them. A counterexample may be Qt Quick itself: as most built-in shaders for |
| 217 | 2D content ship with DirectX bytecode generated at build time, the cache is |
| 218 | not going to present any significant improvements. |
| 219 | |
| 220 | \endlist |
| 221 | |
| 222 | All this is independent from the shader processing performed by the |
| 223 | \l [QtShaderTools]{Qt Shader Tools} module and its command-line tools such |
| 224 | as \c qsb. As an example, take Vulkan. Having the Vulkan-compatible GLSL |
| 225 | source code compiled to SPIR-V either at offline or build time (directly |
| 226 | via qsb or CMake) is good, because the expensive compilation from source |
| 227 | form is avoided at run time. SPIR-V is however a vendor-independent |
| 228 | intermediate format. At runtime, when constructing graphics or compute |
| 229 | pipelines, there is likely another round of compilation happening, this |
| 230 | time from the intermediate format to the vendor-specific instruction set of |
| 231 | the GPU (and this may be dependent on certain state in the graphics |
| 232 | pipeline and the render targets as well). The pipeline cache helps with |
| 233 | this latter phase. |
| 234 | |
| 235 | \note Many graphics API implementation employ their own persistent disk |
| 236 | cache transparently to the applications. Using the pipeline cache feature |
| 237 | of Qt Quick will likely provide improvements in this case, but the gains |
| 238 | might be smaller. |
| 239 | |
| 240 | Call setPipelineCacheSaveFile() and setPipelineCacheLoadFile() to control |
| 241 | which files a QQuickWindow or QQuickView saves and loads the pipeline cache |
| 242 | to/from. |
| 243 | |
| 244 | To get an idea of the effects of enabling disk storage of the pipeline |
| 245 | cache, enable the most important scenegraph and graphics logs either via |
| 246 | the environment variable \c{QSG_INFO=1}, or both the |
| 247 | \c{qt.scenegraph.general} and \c{qt.rhi.general} logging categories. When |
| 248 | closing the QQuickWindow, there is log message like the following: |
| 249 | |
| 250 | \badcode |
| 251 | Total time spent on pipeline creation during the lifetime of the QRhi was 123 ms |
| 252 | \endcode |
| 253 | |
| 254 | This gives an approximate idea of how much time was spent in graphics and |
| 255 | compute pipeline creation (which may include various stages of shader |
| 256 | compilation) during the lifetime of the window. |
| 257 | |
| 258 | When loading from a pipeline cache file is enabled, this is confirmed with |
| 259 | a message: |
| 260 | |
| 261 | \badcode |
| 262 | Attempting to seed pipeline cache from 'filename' |
| 263 | \endcode |
| 264 | |
| 265 | Similarly, to check if saving of the cache is successfully enabled, look |
| 266 | for a message such as this: |
| 267 | |
| 268 | \badcode |
| 269 | Writing pipeline cache contents to 'filename' |
| 270 | \endcode |
| 271 | |
| 272 | \section1 The Automatic Pipeline Cache |
| 273 | |
| 274 | When no filename is provided for save and load, the automatic pipeline |
| 275 | caching strategy is used. This involves storing data to the |
| 276 | application-specific cache location of the system (\l |
| 277 | QStandardPaths::CacheLocation). |
| 278 | |
| 279 | This can be disabled by one of the following means: |
| 280 | |
| 281 | \list |
| 282 | |
| 283 | \li Set the application attribute Qt::AA_DisableShaderDiskCache. |
| 284 | (completely disables the automatic storage) |
| 285 | |
| 286 | \li Set the environment variable QT_DISABLE_SHADER_DISK_CACHE to a non-zero |
| 287 | value. (completely disables the automatic storage) |
| 288 | |
| 289 | \li Set the environment variable QSG_RHI_DISABLE_SHADER_DISK_CACHE to a |
| 290 | non-zero value. (completely disables the automatic storage) |
| 291 | |
| 292 | \li Call setAutomaticPiplineCache() with the enable argument set to false. |
| 293 | (completely disables the automatic storage) |
| 294 | |
| 295 | \li Set a filename by calling setPipelineCacheLoadFile(). (only disables |
| 296 | loading from the automatic storage, prefering the specified file instead) |
| 297 | |
| 298 | \li Set a filename by calling setPipelineCacheSaveFile(). (only disables |
| 299 | writing to the automatic storage, prefering the specified file instead) |
| 300 | |
| 301 | \endlist |
| 302 | |
| 303 | The first two are existing mechanisms that are used since Qt 5.9 to control |
| 304 | the OpenGL program binary cache. For compatibility and familiarity the same |
| 305 | attribute and environment variable are supported for Qt 6's enhanced |
| 306 | pipeline cache. |
| 307 | |
| 308 | The automatic pipeline cache uses a single file per application, but a |
| 309 | different one for each RHI backend (graphics API). This means that changing |
| 310 | to another graphics API in the next run of the application will not lead to |
| 311 | losing the pipeline cache generated in the previous run. Applications with |
| 312 | multiple QQuickWindow instances shown simultaneously may however not |
| 313 | benefit 100% since the automatic cache can only store the data collected |
| 314 | from one RHI object at a time. (and with the default \c threaded render |
| 315 | loop each window has its own RHI as rendering operates independently on |
| 316 | dedicated threads). To fully benefit from the disk cache in application |
| 317 | with multiple windows, prefer setting the filename explicitly, per-window |
| 318 | via setPipelineCacheSaveFile(). |
| 319 | |
| 320 | \sa QQuickWindow::setGraphicsConfiguration(), QQuickWindow, QQuickRenderControl |
| 321 | */ |
| 322 | |
| 323 | /*! |
| 324 | Constructs a default QQuickGraphicsConfiguration that does not specify any |
| 325 | additional settings for the scene graph to take into account. |
| 326 | */ |
| 327 | QQuickGraphicsConfiguration::QQuickGraphicsConfiguration() |
| 328 | : d(new QQuickGraphicsConfigurationPrivate) |
| 329 | { |
| 330 | } |
| 331 | |
| 332 | /*! |
| 333 | \internal |
| 334 | */ |
| 335 | void QQuickGraphicsConfiguration::detach() |
| 336 | { |
| 337 | qAtomicDetach(d); |
| 338 | } |
| 339 | |
| 340 | /*! |
| 341 | \internal |
| 342 | */ |
| 343 | QQuickGraphicsConfiguration::QQuickGraphicsConfiguration(const QQuickGraphicsConfiguration &other) |
| 344 | : d(other.d) |
| 345 | { |
| 346 | d->ref.ref(); |
| 347 | } |
| 348 | |
| 349 | /*! |
| 350 | \internal |
| 351 | */ |
| 352 | QQuickGraphicsConfiguration &QQuickGraphicsConfiguration::operator=(const QQuickGraphicsConfiguration &other) |
| 353 | { |
| 354 | qAtomicAssign(d, x: other.d); |
| 355 | return *this; |
| 356 | } |
| 357 | |
| 358 | /*! |
| 359 | Destructor. |
| 360 | */ |
| 361 | QQuickGraphicsConfiguration::~QQuickGraphicsConfiguration() |
| 362 | { |
| 363 | if (!d->ref.deref()) |
| 364 | delete d; |
| 365 | } |
| 366 | |
| 367 | /*! |
| 368 | \return the list of Vulkan instance extensions Qt Quick prefers to |
| 369 | have enabled on the VkInstance. |
| 370 | |
| 371 | In most cases Qt Quick is responsible for creating a QVulkanInstance. This |
| 372 | function is not relevant then. On the other hand, when using |
| 373 | QQuickRenderControl in combination with Vulkan-based rendering, it is the |
| 374 | application's responsibility to create a QVulkanInstance and associate it |
| 375 | with the (offscreen) QQuickWindow. In this case, it is expected that the |
| 376 | application queries the list of instance extensions to enable, and passes |
| 377 | them to QVulkanInstance::setExtensions() before calling |
| 378 | QVulkanInstance::create(). |
| 379 | |
| 380 | \since 6.1 |
| 381 | */ |
| 382 | QByteArrayList QQuickGraphicsConfiguration::preferredInstanceExtensions() |
| 383 | { |
| 384 | #if QT_CONFIG(vulkan) |
| 385 | return QRhiVulkanInitParams::preferredInstanceExtensions(); |
| 386 | #else |
| 387 | return {}; |
| 388 | #endif |
| 389 | } |
| 390 | |
| 391 | /*! |
| 392 | Sets the list of additional \a extensions to enable on the graphics device |
| 393 | (such as, the \c VkDevice). |
| 394 | |
| 395 | When rendering with a graphics API where the concept is not applicable, \a |
| 396 | extensions will be ignored. |
| 397 | |
| 398 | \note The list specifies additional, extra extensions. Qt Quick always |
| 399 | enables extensions that are required by the scene graph. |
| 400 | */ |
| 401 | void QQuickGraphicsConfiguration::setDeviceExtensions(const QByteArrayList &extensions) |
| 402 | { |
| 403 | if (d->deviceExtensions != extensions) { |
| 404 | detach(); |
| 405 | d->deviceExtensions = extensions; |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /*! |
| 410 | \return the list of the requested additional device extensions. |
| 411 | */ |
| 412 | QByteArrayList QQuickGraphicsConfiguration::deviceExtensions() const |
| 413 | { |
| 414 | return d->deviceExtensions; |
| 415 | } |
| 416 | |
| 417 | /*! |
| 418 | Sets the usage of depth buffer for 2D content to \a enable. When disabled, |
| 419 | the Qt Quick scene graph never writes into the depth buffer. |
| 420 | |
| 421 | By default the value is true, unless the \c{QSG_NO_DEPTH_BUFFER} |
| 422 | environment variable is set. |
| 423 | |
| 424 | The default value of true is the most optimal setting for the vast majority |
| 425 | of scenes. Disabling depth buffer usage reduces the efficiency of the scene |
| 426 | graph's batching. |
| 427 | |
| 428 | There are cases however, when allowing the 2D content write to the depth |
| 429 | buffer is not ideal. Consider a 3D scene as an "overlay" on top the 2D |
| 430 | scene, rendered via Qt Quick 3D using a \l View3D with |
| 431 | \l{View3D::renderMode}{renderMode} set to \c Overlay. In this case, having |
| 432 | the depth buffer filled by 2D content can cause unexpected results. This is |
| 433 | because the way the 2D scene graph renderer generates and handles depth |
| 434 | values is not necessarily compatible with how a 3D scene works. This may end |
| 435 | up in depth value clashes, collisions, and unexpected depth test |
| 436 | failures. Therefore, the robust approach here is to call this function with |
| 437 | \a enable set to false, and disable depth buffer writes for the 2D content |
| 438 | in the QQuickWindow. |
| 439 | |
| 440 | \note This flag is not fully identical to setting the |
| 441 | \c{QSG_NO_DEPTH_BUFFER} environment variable. This flag does not control the |
| 442 | depth-stencil buffers' presence. It is rather relevant for the rendering |
| 443 | pipeline. To force not having depth/stencil attachments at all, set |
| 444 | \c{QSG_NO_DEPTH_BUFFER} and \c{QSG_NO_STENCIL_BUFFER}. Be aware however |
| 445 | that such a QQuickWindow, and any Item layers in it, may then become |
| 446 | incompatible with items, such as View3D with certain operating modes, |
| 447 | because 3D content requires a depth buffer. Calling this function is always |
| 448 | safe, but can mean that resources, such as depth buffers, are created even |
| 449 | though they are not actively used. |
| 450 | */ |
| 451 | void QQuickGraphicsConfiguration::setDepthBufferFor2D(bool enable) |
| 452 | { |
| 453 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D) != enable) { |
| 454 | detach(); |
| 455 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D, on: enable); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | /*! |
| 460 | \return true if depth buffer usage is enabled for 2D content. |
| 461 | |
| 462 | By default the value is true, unless the \c{QSG_NO_DEPTH_BUFFER} |
| 463 | environment variable is set. |
| 464 | */ |
| 465 | bool QQuickGraphicsConfiguration::isDepthBufferEnabledFor2D() const |
| 466 | { |
| 467 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D); |
| 468 | } |
| 469 | |
| 470 | /*! |
| 471 | Enables the graphics API implementation's debug or validation layers, if |
| 472 | available. |
| 473 | |
| 474 | In practice this is supported with Vulkan and Direct 3D 11, assuming the |
| 475 | necessary support (validation layers, Windows SDK) is installed and |
| 476 | available at runtime. When \a enable is true, Qt will attempt to enable the |
| 477 | standard validation layer on the VkInstance, or set |
| 478 | \c{D3D11_CREATE_DEVICE_DEBUG} on the graphics device. |
| 479 | |
| 480 | For Metal on \macos, set the environment variable |
| 481 | \c{METAL_DEVICE_WRAPPER_TYPE=1} instead before launching the application. |
| 482 | |
| 483 | Calling this function with \a enable set to true is equivalent to setting |
| 484 | the environment variable \c{QSG_RHI_DEBUG_LAYER} to a non-zero value. |
| 485 | |
| 486 | The default value is false. |
| 487 | |
| 488 | \note Enabling debug or validation layers may have a non-insignificant |
| 489 | performance impact. Shipping applications to production with the flag |
| 490 | enabled is strongly discouraged. |
| 491 | |
| 492 | \note Be aware that due to differences in the design of the underlying |
| 493 | graphics APIs, this setting cannot always be a per-QQuickWindow setting, |
| 494 | even though each QQuickWindow has their own QQuickGraphicsConfiguration. |
| 495 | With Vulkan in particular, the instance object (VkInstance) is only created |
| 496 | once and then used by all windows in the application. Therefore, enabling |
| 497 | the validation layer is something that affects all windows. This also means |
| 498 | that attempting to enable validation via a window that only gets shown after |
| 499 | some other windows have already started rendering has no effect with Vulkan. |
| 500 | Other APIs, such as D3D11, expose the debug layer concept as a per-device |
| 501 | (ID3D11Device) setting, and so it is controlled on a true per-window basis |
| 502 | (assuming the scenegraph render loop uses a dedicated graphics |
| 503 | device/context for each QQuickWindow). |
| 504 | |
| 505 | \since 6.5 |
| 506 | |
| 507 | \sa isDebugLayerEnabled() |
| 508 | */ |
| 509 | void QQuickGraphicsConfiguration::setDebugLayer(bool enable) |
| 510 | { |
| 511 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugLayer) != enable) { |
| 512 | detach(); |
| 513 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugLayer, on: enable); |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | /*! |
| 518 | \return true if the debug/validation layers are to be enabled. |
| 519 | |
| 520 | By default the value is false. |
| 521 | |
| 522 | \sa setDebugLayer() |
| 523 | */ |
| 524 | bool QQuickGraphicsConfiguration::isDebugLayerEnabled() const |
| 525 | { |
| 526 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugLayer); |
| 527 | } |
| 528 | |
| 529 | /*! |
| 530 | Where applicable, \a enable controls inserting debug markers and object |
| 531 | names into the graphics command stream. |
| 532 | |
| 533 | Some frameworks, such as Qt Quick 3D, have the ability to annotate the |
| 534 | graphics objects they create (buffers, textures) with names and also |
| 535 | indicate the beginning and end of render passes in the command buffer. These |
| 536 | are then visible in frame captures made with tools like |
| 537 | \l{https://renderdoc.org/}{RenderDoc} or XCode. |
| 538 | |
| 539 | Graphics APIs where this can be expected to be supported are Vulkan (if |
| 540 | VK_EXT_debug_utils is available), Direct 3D 11, and Metal. |
| 541 | |
| 542 | Calling this function with \a enable set to true is equivalent to setting |
| 543 | the environment variable \c{QSG_RHI_PROFILE} to a non-zero |
| 544 | value. |
| 545 | |
| 546 | The default value is false. |
| 547 | |
| 548 | \note Enabling debug markers may have a performance impact. Shipping |
| 549 | applications to production with the flag enabled is not recommended. |
| 550 | |
| 551 | \since 6.5 |
| 552 | |
| 553 | \sa isDebugMarkersEnabled() |
| 554 | */ |
| 555 | void QQuickGraphicsConfiguration::setDebugMarkers(bool enable) |
| 556 | { |
| 557 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugMarkers) != enable) { |
| 558 | detach(); |
| 559 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugMarkers, on: enable); |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | /*! |
| 564 | \return true if debug markers are enabled. |
| 565 | |
| 566 | By default the value is false. |
| 567 | |
| 568 | \sa setDebugMarkers() |
| 569 | */ |
| 570 | bool QQuickGraphicsConfiguration::isDebugMarkersEnabled() const |
| 571 | { |
| 572 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableDebugMarkers); |
| 573 | } |
| 574 | |
| 575 | /*! |
| 576 | When enabled, GPU timing data is collected from command buffers on |
| 577 | platforms and 3D APIs where this is supported. This data is then printed in |
| 578 | the renderer logs that can be enabled via \c{QSG_RENDER_TIMING} environment |
| 579 | variable or logging categories such as \c{qt.scenegraph.time.renderloop}, |
| 580 | and may also be made visible to other modules, such as Qt Quick 3D's |
| 581 | \l DebugView item. |
| 582 | |
| 583 | By default this is disabled, because collecting the data may involve |
| 584 | additional work, such as inserting timestamp queries in the command stream, |
| 585 | depending on the underlying graphics API. To enable, either call this |
| 586 | function with \a enable set to true, or set the \c{QSG_RHI_PROFILE} |
| 587 | environment variable to a non-zero value. |
| 588 | |
| 589 | Graphics APIs where this can be expected to be supported are Direct 3D 11, |
| 590 | Direct 3D 12, Vulkan (as long as the underlying Vulkan implementation |
| 591 | supports timestamp queries), Metal, and OpenGL with a core or compatibility |
| 592 | profile context for version 3.3 or newer. Timestamps are not supported with |
| 593 | OpenGL ES. |
| 594 | |
| 595 | \since 6.6 |
| 596 | |
| 597 | \sa timestampsEnabled(), setDebugMarkers() |
| 598 | */ |
| 599 | void QQuickGraphicsConfiguration::setTimestamps(bool enable) |
| 600 | { |
| 601 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableTimestamps) != enable) { |
| 602 | detach(); |
| 603 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::EnableTimestamps, on: enable); |
| 604 | } |
| 605 | } |
| 606 | |
| 607 | /*! |
| 608 | \return true if GPU timing collection is enabled. |
| 609 | |
| 610 | By default the value is false. |
| 611 | |
| 612 | \since 6.6 |
| 613 | \sa setTimestamps() |
| 614 | */ |
| 615 | bool QQuickGraphicsConfiguration::timestampsEnabled() const |
| 616 | { |
| 617 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::EnableTimestamps); |
| 618 | } |
| 619 | |
| 620 | /*! |
| 621 | Requests choosing an adapter or physical device that uses software-based |
| 622 | rasterization. Applicable only when the underlying API has support for |
| 623 | enumerating adapters (for example, Direct 3D or Vulkan), and is ignored |
| 624 | otherwise. |
| 625 | |
| 626 | If the graphics API implementation has no such graphics adapter or physical |
| 627 | device available, the request is ignored. With Direct 3D it can be expected |
| 628 | that a |
| 629 | \l{https://docs.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp}{WARP}-based |
| 630 | rasterizer is always available. With Vulkan, the flag only has an effect if |
| 631 | Mesa's \c lavapipe, or some other physical device reporting |
| 632 | \c{VK_PHYSICAL_DEVICE_TYPE_CPU} is available. |
| 633 | |
| 634 | Calling this function with \a enable set to true is equivalent to setting |
| 635 | the environment variable \c{QSG_RHI_PREFER_SOFTWARE_RENDERER} to a non-zero |
| 636 | value. |
| 637 | |
| 638 | The default value is false. |
| 639 | |
| 640 | \since 6.5 |
| 641 | |
| 642 | \sa prefersSoftwareDevice() |
| 643 | */ |
| 644 | void QQuickGraphicsConfiguration::setPreferSoftwareDevice(bool enable) |
| 645 | { |
| 646 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice) != enable) { |
| 647 | detach(); |
| 648 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice, on: enable); |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | /*! |
| 653 | \return true if a software rasterizer-based graphics device is prioritized. |
| 654 | |
| 655 | By default the value is false. |
| 656 | |
| 657 | \sa setPreferSoftwareDevice() |
| 658 | */ |
| 659 | bool QQuickGraphicsConfiguration::() const |
| 660 | { |
| 661 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice); |
| 662 | } |
| 663 | |
| 664 | /*! |
| 665 | Changes the usage of the automatic pipeline cache based on \a enable. |
| 666 | |
| 667 | The default value is true, unless certain application attributes or |
| 668 | environment variables are set. See \l{The Automatic Pipeline Cache} for |
| 669 | more information. |
| 670 | |
| 671 | \since 6.5 |
| 672 | |
| 673 | \sa isAutomaticPipelineCacheEnabled() |
| 674 | */ |
| 675 | void QQuickGraphicsConfiguration::setAutomaticPipelineCache(bool enable) |
| 676 | { |
| 677 | if (d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::AutoPipelineCache) != enable) { |
| 678 | detach(); |
| 679 | d->flags.setFlag(flag: QQuickGraphicsConfigurationPrivate::AutoPipelineCache, on: enable); |
| 680 | } |
| 681 | } |
| 682 | |
| 683 | /*! |
| 684 | \return true if the automatic pipeline cache is enabled. |
| 685 | |
| 686 | By default this is true, unless certain application attributes or |
| 687 | environment variables are set. See \l{The Automatic Pipeline Cache} for |
| 688 | more information. |
| 689 | |
| 690 | \since 6.5 |
| 691 | |
| 692 | \sa setAutomaticPipelineCache() |
| 693 | */ |
| 694 | bool QQuickGraphicsConfiguration::isAutomaticPipelineCacheEnabled() const |
| 695 | { |
| 696 | return d->flags.testFlag(flag: QQuickGraphicsConfigurationPrivate::AutoPipelineCache); |
| 697 | } |
| 698 | |
| 699 | /*! |
| 700 | Sets the \a filename where the QQuickWindow is expected to store its |
| 701 | graphics/compute pipeline cache contents. The default value is empty, which |
| 702 | means pipeline cache loading is disabled. |
| 703 | |
| 704 | See \l{Pipeline Cache Save and Load} for a discussion on pipeline caches. |
| 705 | |
| 706 | Persistently storing the pipeline cache can lead to performance |
| 707 | improvements in future runs of the application since expensive shader |
| 708 | compilation and pipeline construction steps may be avoided. |
| 709 | |
| 710 | If and when the writing of the file happens is not defined. It will likely |
| 711 | happen at some point when tearing down the scenegraph due to closing the |
| 712 | window. Therefore, applications should not assume availability of the file |
| 713 | until the QQuickWindow is fully destructed. QQuickGraphicsConfiguration |
| 714 | only stores the filename, it does not perform any actual I/O and graphics |
| 715 | operations on its own. |
| 716 | |
| 717 | When running with a graphics API where retrieving the pipeline cache (or |
| 718 | shader/program binaries) is not applicable or not supported, calling this |
| 719 | function has no effect. |
| 720 | |
| 721 | Calling this function is mostly equivalent to setting the environment |
| 722 | variable \c{QSG_RHI_PIPELINE_CACHE_SAVE} to \a filename, with one important |
| 723 | difference: this function controls the pipeline cache storage for the |
| 724 | associated QQuickWindow only. Applications with multiple QQuickWindow or |
| 725 | QQuickView instances can therefore store and later reload the cache contents |
| 726 | via files dedicated to each window. The environment variable does not allow |
| 727 | this. |
| 728 | |
| 729 | \since 6.5 |
| 730 | |
| 731 | \sa pipelineCacheLoadFile(), setPipelineCacheSaveFile() |
| 732 | */ |
| 733 | void QQuickGraphicsConfiguration::setPipelineCacheSaveFile(const QString &filename) |
| 734 | { |
| 735 | if (d->pipelineCacheSaveFile != filename) { |
| 736 | detach(); |
| 737 | d->pipelineCacheSaveFile = filename; |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | /*! |
| 742 | \return the currently set filename for storing the pipeline cache. |
| 743 | |
| 744 | By default the value is an empty string. |
| 745 | */ |
| 746 | QString QQuickGraphicsConfiguration::pipelineCacheSaveFile() const |
| 747 | { |
| 748 | return d->pipelineCacheSaveFile; |
| 749 | } |
| 750 | |
| 751 | /*! |
| 752 | Sets the \a filename where the QQuickWindow is expected to load the initial |
| 753 | contents of its graphics/compute pipeline cache from. The default value is |
| 754 | empty, which means pipeline cache loading is disabled. |
| 755 | |
| 756 | See \l{Pipeline Cache Save and Load} for a discussion on pipeline caches. |
| 757 | |
| 758 | Persistently storing the pipeline cache can lead to performance |
| 759 | improvements in future runs of the application since expensive shader |
| 760 | compilation and pipeline construction steps may be avoided. |
| 761 | |
| 762 | If and when the loading of the file's contents happens is not defined, apart |
| 763 | from that it will happen at some point during the initialization of the |
| 764 | scenegraph of the QQuickWindow. Therefore, the file must continue to exist |
| 765 | after calling this function. QQuickGraphicsConfiguration only stores the |
| 766 | filename, it cannot perform any actual I/O and graphics operations on its |
| 767 | own. The real work is going to happen later on, possibly on another thread. |
| 768 | |
| 769 | When running with a graphics API where retrieving and reloading the |
| 770 | pipeline cache (or shader/program binaries) is not applicable or not |
| 771 | supported, calling this function has no effect. |
| 772 | |
| 773 | Calling this function is mostly equivalent to setting the environment |
| 774 | variable \c{QSG_RHI_PIPELINE_CACHE_LOAD} to \a filename, with one important |
| 775 | difference: this function controls the pipeline cache storage for the |
| 776 | associated QQuickWindow only. Applications with multiple QQuickWindow or |
| 777 | QQuickView instances can therefore store and later reload the cache contents |
| 778 | via files dedicated to each window. The environment variable does not allow |
| 779 | this. |
| 780 | |
| 781 | \note If the data in the file does not match the graphics device and driver |
| 782 | version at run time, the contents will be ignored, transparently to the |
| 783 | application. This applies to a number of graphics APIs, and the necessary |
| 784 | checks are taken care of by Qt. There are exceptions, most notably Direct |
| 785 | 3D 11, where the "pipeline cache" is used only to store the results of |
| 786 | runtime HLSL->DXBC compilation and is therefore device and vendor |
| 787 | independent. |
| 788 | |
| 789 | \since 6.5 |
| 790 | |
| 791 | \sa pipelineCacheLoadFile(), setPipelineCacheSaveFile() |
| 792 | */ |
| 793 | void QQuickGraphicsConfiguration::setPipelineCacheLoadFile(const QString &filename) |
| 794 | { |
| 795 | if (d->pipelineCacheLoadFile != filename) { |
| 796 | detach(); |
| 797 | d->pipelineCacheLoadFile = filename; |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | /*! |
| 802 | \return the currently set filename for loading the pipeline cache. |
| 803 | |
| 804 | By default the value is an empty string. |
| 805 | */ |
| 806 | QString QQuickGraphicsConfiguration::pipelineCacheLoadFile() const |
| 807 | { |
| 808 | return d->pipelineCacheLoadFile; |
| 809 | } |
| 810 | |
| 811 | QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate() |
| 812 | : ref(1) |
| 813 | { |
| 814 | // Defaults based on env.vars. NB! many of these variables are documented |
| 815 | // and should be considered (semi-)public API. Changing the env.var. names |
| 816 | // is therefore not allowed. |
| 817 | |
| 818 | flags = {}; |
| 819 | |
| 820 | static const bool useDepthBufferFor2D = qEnvironmentVariableIsEmpty(varName: "QSG_NO_DEPTH_BUFFER" ); |
| 821 | if (useDepthBufferFor2D) |
| 822 | flags |= UseDepthBufferFor2D; |
| 823 | |
| 824 | static const bool enableDebugLayer = qEnvironmentVariableIntValue(varName: "QSG_RHI_DEBUG_LAYER" ); |
| 825 | if (enableDebugLayer) |
| 826 | flags |= EnableDebugLayer; |
| 827 | |
| 828 | static const bool enableProfilingRelated = qEnvironmentVariableIntValue(varName: "QSG_RHI_PROFILE" ); |
| 829 | if (enableProfilingRelated) |
| 830 | flags |= EnableDebugMarkers | EnableTimestamps; |
| 831 | |
| 832 | static const bool preferSoftwareDevice = qEnvironmentVariableIntValue(varName: "QSG_RHI_PREFER_SOFTWARE_RENDERER" ); |
| 833 | if (preferSoftwareDevice) |
| 834 | flags |= PreferSoftwareDevice; |
| 835 | |
| 836 | // here take the existing QOpenGL disk cache attribute and env.var. into account as well |
| 837 | static const bool autoPipelineCache = !QCoreApplication::instance()->testAttribute(attribute: Qt::AA_DisableShaderDiskCache) |
| 838 | && !qEnvironmentVariableIntValue(varName: "QT_DISABLE_SHADER_DISK_CACHE" ) |
| 839 | && !qEnvironmentVariableIntValue(varName: "QSG_RHI_DISABLE_DISK_CACHE" ); |
| 840 | if (autoPipelineCache) |
| 841 | flags |= AutoPipelineCache; |
| 842 | |
| 843 | static const QString pipelineCacheSaveFileEnv = qEnvironmentVariable(varName: "QSG_RHI_PIPELINE_CACHE_SAVE" ); |
| 844 | pipelineCacheSaveFile = pipelineCacheSaveFileEnv; |
| 845 | |
| 846 | static const QString pipelineCacheLoadFileEnv = qEnvironmentVariable(varName: "QSG_RHI_PIPELINE_CACHE_LOAD" ); |
| 847 | pipelineCacheLoadFile = pipelineCacheLoadFileEnv; |
| 848 | } |
| 849 | |
| 850 | QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate &other) |
| 851 | : ref(1), |
| 852 | deviceExtensions(other.deviceExtensions), |
| 853 | flags(other.flags), |
| 854 | pipelineCacheSaveFile(other.pipelineCacheSaveFile), |
| 855 | pipelineCacheLoadFile(other.pipelineCacheLoadFile) |
| 856 | { |
| 857 | } |
| 858 | |
| 859 | #ifndef QT_NO_DEBUG_STREAM |
| 860 | QDebug operator<<(QDebug dbg, const QQuickGraphicsConfiguration &config) |
| 861 | { |
| 862 | QDebugStateSaver saver(dbg); |
| 863 | const QQuickGraphicsConfigurationPrivate *cd = QQuickGraphicsConfigurationPrivate::get(p: &config); |
| 864 | dbg.nospace() << "QQuickGraphicsConfiguration(" |
| 865 | << "flags=0x" << Qt::hex << cd->flags << Qt::dec |
| 866 | << " flag-isDepthBufferEnabledFor2D=" << config.isDepthBufferEnabledFor2D() |
| 867 | << " flag-isDebugLayerEnabled=" << config.isDebugLayerEnabled() |
| 868 | << " flag-isDebugMarkersEnabled=" << config.isDebugMarkersEnabled() |
| 869 | << " flag-prefersSoftwareDevice=" << config.prefersSoftwareDevice() |
| 870 | << " flag-isAutomaticPipelineCacheEnabled=" << config.isAutomaticPipelineCacheEnabled() |
| 871 | << " pipelineCacheSaveFile=" << cd->pipelineCacheSaveFile |
| 872 | << " piplineCacheLoadFile=" << cd->pipelineCacheLoadFile |
| 873 | << " extra-device-extension-requests=" << cd->deviceExtensions |
| 874 | << ')'; |
| 875 | return dbg; |
| 876 | } |
| 877 | #endif // QT_NO_DEBUG_STREAM |
| 878 | |
| 879 | QT_END_NAMESPACE |
| 880 | |