// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qquickgraphicsconfiguration_p.h"
#include <QCoreApplication>
#include <rhi/qrhi.h>

QT_BEGIN_NAMESPACE

/*!
    \class QQuickGraphicsConfiguration
    \since 6.0
    \inmodule QtQuick

    \brief QQuickGraphicsConfiguration controls lower level graphics settings
    for the QQuickWindow.

    The QQuickGraphicsConfiguration class is a container for low-level graphics
    settings that can affect how the underlying graphics API, such as Vulkan,
    is initialized by the Qt Quick scene graph. It can also control certain
    aspects of the scene graph renderer.

    \note Setting a QQuickGraphicsConfiguration on a QQuickWindow must happen
    early enough, before the scene graph is initialized for the first time for
    that window. With on-screen windows this means the call must be done before
    invoking show() on the QQuickWindow or QQuickView. With QQuickRenderControl
    the configuration must be finalized before calling
    \l{QQuickRenderControl::initialize()}{initialize()}.

    \section1 Configuration for External Rendering Engines or XR APIs

    When constructing and showing a QQuickWindow that uses Vulkan to render, a
    Vulkan instance (\c VkInstance), a physical device (\c VkPhysicalDevice), a
    device (\c VkDevice) and associated objects (queues, pools) are initialized
    through the Vulkan API. The same is mostly true when using
    QQuickRenderControl to redirect the rendering into a custom render target,
    such as a texture. While QVulkanInstance construction is under the
    application's control then, the initialization of other graphics objects
    happen the same way in QQuickRenderControl::initialize() as with an
    on-screen QQuickWindow.

    For the majority of applications no additional configuration is needed
    because Qt Quick provides reasonable defaults for many low-level graphics
    settings, for example which device extensions to enable.

    This will not alway be sufficient, however. In advanced use cases, when
    integrating direct Vulkan or other graphics API content, or when
    integrating with an external 3D or VR engine, such as, OpenXR, the
    application will want to specify its own set of settings when it comes to
    details, such as which device extensions to enable.

    That is what this class enables. It allows specifying, for example, a list
    of device extensions that is then picked up by the scene graph when using
    Vulkan, or graphics APIs where the concept is applicable. Where some
    concepts are not applicable, the related settings are simply ignored.

    Examples of functions in this category are setDeviceExtensions() and
    preferredInstanceExtensions(). The latter is useful when the application
    manages its own \l QVulkanInstance which is then associated with the
    QQuickWindow via \l QWindow::setVulkanInstance().

    \section1 Qt Quick Scene Graph Renderer Configuration

    Another class of settings are related to the scene graph's renderer. In
    some cases applications may want to control certain behavior,such as using
    the depth buffer when rendering 2D content. In Qt 5 such settings were
    either not controllable at all, or were managed through environment
    variables. In Qt 6, QQuickGraphicsConfiguration provides a new home for
    these settings, while keeping support for the legacy environment variables,
    where applicable.

    An example in this category is setDepthBufferFor2D().

    \section1 Graphics Device Configuration

    When the graphics instance and device objects (for example, the VkInstance
    and VkDevice with Vulkan, the ID3D11Device with Direct 3D, etc.) are
    created by Qt when initializing a QQuickWindow, there are settings which
    applications or libraries will want to control under certain circumstances.

    Before Qt 6.5, some of such settings were available to control via
    environment variables. For example, \c QSG_RHI_DEBUG_LAYER or \c
    QSG_RHI_PREFER_SOFTWARE_RENDERER. These are still available and continue to
    function as before. QQuickGraphicsConfiguration provides C++ setters in
    addition.

    For example, the following main() function opens a QQuickView while
    specifying that the Vulkan validation or Direct3D debug layer should be
    enabled:

    \code
        int main(int argc, char *argv[])
        {
            QGuiApplication app(argc, argv);

            QQuickGraphicsConfiguration config;
            config.setDebugLayer(true);

            QQuickView *view = new QQuickView;
            view->setGraphicsConfiguration(config);

            view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));
            view->show();
            return app.exec();
        }
    \endcode

    \section1 Pipeline Cache Save and Load

    Qt Quick supports storing the graphics/compute pipeline cache to disk, and
    reloading it in subsequent runs of an application. What exactly the
    pipeline cache contains, how lookups work, and what exactly gets
    accelerated all depend on the Qt RHI backend and the underlying native
    graphics API that is used at run time. Different 3D APIs have different
    concepts when it comes to shaders, programs, and pipeline state objects,
    and corresponding cache mechanisms. The high level pipeline cache concept
    here abstracts all this to storing and retrieving a single binary blob to
    and from a file.

    \note Storing the cache on disk can lead to improvements, sometimes
    significant, in subsequent runs of the application.

    When the same shader program and/or pipeline state is encountered as in a
    previous run, a number of operations are likely skipped, leading to faster
    shader and material initialization times, which means startup may become
    faster and lags and "janks" during rendering may be reduced or avoided.

    When running with a graphics API where retrieving and reloading the
    pipeline cache (or shader/program binaries) is not applicable or not
    supported, attempting to use a file to save and load the cache has no
    effect.

    \note In many cases the retrieved data is dependent on and tied to the
    graphics driver (and possibly the exact version of it). Qt performs the
    necessary checks automatically, by storing additional metadata in the
    pipeline cache file. If the data in the file does not match the graphics
    device and driver version at run time, the contents will be ignored
    transparently to the application. It is therefore safe to reference a cache
    that was generated on another device or driver.

    There are exceptions to the driver dependency problem, most notably Direct
    3D 11, where the "pipeline cache" is used only to store the results of
    runtime HLSL->DXBC compilation and is therefore device and vendor
    independent.

    In some cases it may be desirable to improve the very first run of the
    application, by "pre-seeding" the cache. This is possible by shipping the
    cache file saved from a previous run, and referencing it on another machine
    or device. This way, the application or device has the shader
    programs/pipelines that have been encountered before in the run that saved
    the cache file available already during its first run. Shipping and
    deploying the cache file only makes sense if the device and graphics
    drivers are the same on the target system, otherwise the cache file is
    ignored if the device or driver version does not match (with the exception
    of D3D11), as described above.

    Once the cache contents is loaded, there is still a chance that the
    application builds graphics and compute pipelines that have not been
    encountered in previous runs. In this cases the cache is grown, with the
    pipelines / shader programs added to it. If the application also chooses to
    save the contents (perhaps to the same file even), then both the old and
    new pipelines will get stored. Loading from and saving to the same file in
    every run allows an ever growing cache that stores all encountered
    pipelines and shader programs.

    In practice the Qt pipeline cache can be expected to map to the following
    native graphics API features:

    \list

    \li Vulkan -
    \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPipelineCache.html}{VkPipelineCache}
    - Saving the pipeline cache effectively stores the blob retrieved from
    \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetPipelineCacheData.html}{vkGetPipelineCacheData},
    with additional metadata to safely identify the device and the driver
    since the pipeline cache blob is dependent on the exact driver.

    \li Metal -
    \l{https://developer.apple.com/documentation/metal/mtlbinaryarchive?language=objc}{MTLBinaryArchive}
    - With pipeline cache saving enabled, Qt stores all render and compute
    pipelines encountered into an MTLBinaryArchive. Saving the pipeline cache
    stores the blob retrieved from the archive, with additional metadata to
    identify the device. \b{Note:} currently MTLBinaryArchive usage is disabled
    on macOS and iOS due to various issues on some hardware and OS versions.

    \li OpenGL - There is no native concept of pipelines, the "pipeline cache"
    stores a collection of program binaries retrieved via
    \l{https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetProgramBinary.xhtml}{glGetProgramBinary}.
    The program binaries are packaged into a single blob, with additional
    metadata to identify the device, driver, and its version that the binaries
    were retrieved from. Persistent caching of program binaries is not new in
    Qt: Qt 5 already had similar functionality in QOpenGLShaderProgram, see
    \l{QOpenGLShaderProgram::}{addCacheableShaderFromSourceCode()}
    for example. In fact that mechanism is always active in Qt 6 as well when
    using Qt Quick with OpenGL. However, when using the new, graphics API
    independent pipeline cache abstraction provided here, the Qt 5 era program
    binary cache gets automatically disabled, since the same content is
    packaged in the "pipeline cache" now.

    \li Direct 3D 11 - There is no native concept of pipelines or retrieving
    binaries for the second phase compilation (where the vendor independent,
    intermediate bytecode is compiled into the device specific instruction
    set). Drivers will typically employ their own caching system on that level.
    Instead, the Qt Quick "pipeline cache" is used to speed up cases where the
    shaders contain HLSL source code that needs to be compiled into the
    intermediate bytecode format first. This can present significant
    performance improvements in application and libraries that compose shader
    code at run time, because in subsequent runs the potentially expensive,
    uncached calls to
    \l{https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompile}{D3DCompile()}
    can be avoided if the bytecode is already available for the encountered
    HLSL shader. A good example is Qt Quick 3D, where the runtime-generated
    shaders for materials imply having to deal with HLSL source code. Saving
    and reloading the Qt Quick pipeline cache can therefore bring considerable
    improvements in scenes with one or more \l{View3D} items in
    them. A counterexample may be Qt Quick itself: as most built-in shaders for
    2D content ship with DirectX bytecode generated at build time, the cache is
    not going to present any significant improvements.

    \endlist

    All this is independent from the shader processing performed by the
    \l [QtShaderTools]{Qt Shader Tools} module and its command-line tools such
    as \c qsb. As an example, take Vulkan. Having the Vulkan-compatible GLSL
    source code compiled to SPIR-V either at offline or build time (directly
    via qsb or CMake) is good, because the expensive compilation from source
    form is avoided at run time. SPIR-V is however a vendor-independent
    intermediate format. At runtime, when constructing graphics or compute
    pipelines, there is likely another round of compilation happening, this
    time from the intermediate format to the vendor-specific instruction set of
    the GPU (and this may be dependent on certain state in the graphics
    pipeline and the render targets as well). The pipeline cache helps with
    this latter phase.

    \note Many graphics API implementation employ their own persistent disk
    cache transparently to the applications. Using the pipeline cache feature
    of Qt Quick will likely provide improvements in this case, but the gains
    might be smaller.

    Call setPipelineCacheSaveFile() and setPipelineCacheLoadFile() to control
    which files a QQuickWindow or QQuickView saves and loads the pipeline cache
    to/from.

    To get an idea of the effects of enabling disk storage of the pipeline
    cache, enable the most important scenegraph and graphics logs either via
    the environment variable \c{QSG_INFO=1}, or both the
    \c{qt.scenegraph.general} and \c{qt.rhi.general} logging categories. When
    closing the QQuickWindow, there is log message like the following:

    \badcode
      Total time spent on pipeline creation during the lifetime of the QRhi was 123 ms
    \endcode

    This gives an approximate idea of how much time was spent in graphics and
    compute pipeline creation (which may include various stages of shader
    compilation) during the lifetime of the window.

    When loading from a pipeline cache file is enabled, this is confirmed with
    a message:

    \badcode
      Attempting to seed pipeline cache from 'filename'
    \endcode

    Similarly, to check if saving of the cache is successfully enabled, look
    for a message such as this:

    \badcode
      Writing pipeline cache contents to 'filename'
    \endcode

    \section1 The Automatic Pipeline Cache

    When no filename is provided for save and load, the automatic pipeline
    caching strategy is used. This involves storing data to the
    application-specific cache location of the system (\l
    QStandardPaths::CacheLocation).

    This can be disabled by one of the following means:

    \list

    \li Set the application attribute Qt::AA_DisableShaderDiskCache.
    (completely disables the automatic storage)

    \li Set the environment variable QT_DISABLE_SHADER_DISK_CACHE to a non-zero
    value. (completely disables the automatic storage)

    \li Set the environment variable QSG_RHI_DISABLE_SHADER_DISK_CACHE to a
    non-zero value. (completely disables the automatic storage)

    \li Call setAutomaticPiplineCache() with the enable argument set to false.
    (completely disables the automatic storage)

    \li Set a filename by calling setPipelineCacheLoadFile(). (only disables
    loading from the automatic storage, prefering the specified file instead)

    \li Set a filename by calling setPipelineCacheSaveFile(). (only disables
    writing to the automatic storage, prefering the specified file instead)

    \endlist

    The first two are existing mechanisms that are used since Qt 5.9 to control
    the OpenGL program binary cache. For compatibility and familiarity the same
    attribute and environment variable are supported for Qt 6's enhanced
    pipeline cache.

    The automatic pipeline cache uses a single file per application, but a
    different one for each RHI backend (graphics API). This means that changing
    to another graphics API in the next run of the application will not lead to
    losing the pipeline cache generated in the previous run. Applications with
    multiple QQuickWindow instances shown simultaneously may however not
    benefit 100% since the automatic cache can only store the data collected
    from one RHI object at a time. (and with the default \c threaded render
    loop each window has its own RHI as rendering operates independently on
    dedicated threads). To fully benefit from the disk cache in application
    with multiple windows, prefer setting the filename explicitly, per-window
    via setPipelineCacheSaveFile().

    \sa QQuickWindow::setGraphicsConfiguration(), QQuickWindow, QQuickRenderControl
*/

/*!
    Constructs a default QQuickGraphicsConfiguration that does not specify any
    additional settings for the scene graph to take into account.
 */
QQuickGraphicsConfiguration::QQuickGraphicsConfiguration()
    : d(new QQuickGraphicsConfigurationPrivate)
{
}

/*!
    \internal
 */
void QQuickGraphicsConfiguration::detach()
{
    qAtomicDetach(d);
}

/*!
    \internal
 */
QQuickGraphicsConfiguration::QQuickGraphicsConfiguration(const QQuickGraphicsConfiguration &other)
    : d(other.d)
{
    d->ref.ref();
}

/*!
    \internal
 */
QQuickGraphicsConfiguration &QQuickGraphicsConfiguration::operator=(const QQuickGraphicsConfiguration &other)
{
    qAtomicAssign(d, other.d);
    return *this;
}

/*!
    Destructor.
 */
QQuickGraphicsConfiguration::~QQuickGraphicsConfiguration()
{
    if (!d->ref.deref())
        delete d;
}

/*!
    \return the list of Vulkan instance extensions Qt Quick prefers to
    have enabled on the VkInstance.

    In most cases Qt Quick is responsible for creating a QVulkanInstance. This
    function is not relevant then. On the other hand, when using
    QQuickRenderControl in combination with Vulkan-based rendering, it is the
    application's responsibility to create a QVulkanInstance and associate it
    with the (offscreen) QQuickWindow. In this case, it is expected that the
    application queries the list of instance extensions to enable, and passes
    them to QVulkanInstance::setExtensions() before calling
    QVulkanInstance::create().

    \since 6.1
 */
QByteArrayList QQuickGraphicsConfiguration::preferredInstanceExtensions()
{
#if QT_CONFIG(vulkan)
    return QRhiVulkanInitParams::preferredInstanceExtensions();
#else
    return {};
#endif
}

/*!
    Sets the list of additional \a extensions to enable on the graphics device
    (such as, the \c VkDevice).

    When rendering with a graphics API where the concept is not applicable, \a
    extensions will be ignored.

    \note The list specifies additional, extra extensions. Qt Quick always
    enables extensions that are required by the scene graph.
 */
void QQuickGraphicsConfiguration::setDeviceExtensions(const QByteArrayList &extensions)
{
    if (d->deviceExtensions != extensions) {
        detach();
        d->deviceExtensions = extensions;
    }
}

/*!
    \return the list of the requested additional device extensions.
 */
QByteArrayList QQuickGraphicsConfiguration::deviceExtensions() const
{
    return d->deviceExtensions;
}

/*!
    Sets the usage of depth buffer for 2D content to \a enable. When disabled,
    the Qt Quick scene graph never writes into the depth buffer.

    By default the value is true, unless the \c{QSG_NO_DEPTH_BUFFER}
    environment variable is set.

    The default value of true is the most optimal setting for the vast majority
    of scenes. Disabling depth buffer usage reduces the efficiency of the scene
    graph's batching.

    There are cases however, when allowing the 2D content write to the depth
    buffer is not ideal. Consider a 3D scene as an "overlay" on top the 2D
    scene, rendered via Qt Quick 3D using a \l View3D with
    \l{View3D::renderMode}{renderMode} set to \c Overlay. In this case, having
    the depth buffer filled by 2D content can cause unexpected results. This is
    because the way the 2D scene graph renderer generates and handles depth
    values is not necessarily compatible with how a 3D scene works. This may end
    up in depth value clashes, collisions, and unexpected depth test
    failures. Therefore, the robust approach here is to call this function with
    \a enable set to false, and disable depth buffer writes for the 2D content
    in the QQuickWindow.

    \note This flag is not fully identical to setting the
    \c{QSG_NO_DEPTH_BUFFER} environment variable. This flag does not control the
    depth-stencil buffers' presence. It is rather relevant for the rendering
    pipeline. To force not having depth/stencil attachments at all, set
    \c{QSG_NO_DEPTH_BUFFER} and \c{QSG_NO_STENCIL_BUFFER}. Be aware however
    that such a QQuickWindow, and any Item layers in it, may then become
    incompatible with items, such as View3D with certain operating modes,
    because 3D content requires a depth buffer. Calling this function is always
    safe, but can mean that resources, such as depth buffers, are created even
    though they are not actively used.
 */
void QQuickGraphicsConfiguration::setDepthBufferFor2D(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D, enable);
    }
}

/*!
    \return true if depth buffer usage is enabled for 2D content.

    By default the value is true, unless the \c{QSG_NO_DEPTH_BUFFER}
    environment variable is set.
 */
bool QQuickGraphicsConfiguration::isDepthBufferEnabledFor2D() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::UseDepthBufferFor2D);
}

/*!
   Enables the graphics API implementation's debug or validation layers, if
   available.

   In practice this is supported with Vulkan and Direct 3D 11, assuming the
   necessary support (validation layers, Windows SDK) is installed and
   available at runtime. When \a enable is true, Qt will attempt to enable the
   standard validation layer on the VkInstance, or set
   \c{D3D11_CREATE_DEVICE_DEBUG} on the graphics device.

   For Metal on \macos, set the environment variable
   \c{METAL_DEVICE_WRAPPER_TYPE=1} instead before launching the application.

   Calling this function with \a enable set to true is equivalent to setting
   the environment variable \c{QSG_RHI_DEBUG_LAYER} to a non-zero value.

   The default value is false.

   \note Enabling debug or validation layers may have a non-insignificant
   performance impact. Shipping applications to production with the flag
   enabled is strongly discouraged.

   \note Be aware that due to differences in the design of the underlying
   graphics APIs, this setting cannot always be a per-QQuickWindow setting,
   even though each QQuickWindow has their own QQuickGraphicsConfiguration.
   With Vulkan in particular, the instance object (VkInstance) is only created
   once and then used by all windows in the application. Therefore, enabling
   the validation layer is something that affects all windows. This also means
   that attempting to enable validation via a window that only gets shown after
   some other windows have already started rendering has no effect with Vulkan.
   Other APIs, such as D3D11, expose the debug layer concept as a per-device
   (ID3D11Device) setting, and so it is controlled on a true per-window basis
   (assuming the scenegraph render loop uses a dedicated graphics
   device/context for each QQuickWindow).

   \since 6.5

   \sa isDebugLayerEnabled()
 */
void QQuickGraphicsConfiguration::setDebugLayer(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableDebugLayer) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::EnableDebugLayer, enable);
    }
}

/*!
    \return true if the debug/validation layers are to be enabled.

    By default the value is false.

    \sa setDebugLayer()
 */
bool QQuickGraphicsConfiguration::isDebugLayerEnabled() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableDebugLayer);
}

/*!
    Where applicable, \a enable controls inserting debug markers and object
    names into the graphics command stream.

    Some frameworks, such as Qt Quick 3D, have the ability to annotate the
    graphics objects they create (buffers, textures) with names and also
    indicate the beginning and end of render passes in the command buffer. These
    are then visible in frame captures made with tools like
    \l{https://renderdoc.org/}{RenderDoc} or XCode.

    Graphics APIs where this can be expected to be supported are Vulkan (if
    VK_EXT_debug_utils is available), Direct 3D 11, and Metal.

    Calling this function with \a enable set to true is equivalent to setting
    the environment variable \c{QSG_RHI_PROFILE} to a non-zero
    value.

    The default value is false.

    \note Enabling debug markers may have a performance impact. Shipping
    applications to production with the flag enabled is not recommended.

    \since 6.5

    \sa isDebugMarkersEnabled()
 */
void QQuickGraphicsConfiguration::setDebugMarkers(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableDebugMarkers) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::EnableDebugMarkers, enable);
    }
}

/*!
    \return true if debug markers are enabled.

    By default the value is false.

    \sa setDebugMarkers()
 */
bool QQuickGraphicsConfiguration::isDebugMarkersEnabled() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableDebugMarkers);
}

/*!
    When enabled, GPU timing data is collected from command buffers on
    platforms and 3D APIs where this is supported. This data is then printed in
    the renderer logs that can be enabled via \c{QSG_RENDER_TIMING} environment
    variable or logging categories such as \c{qt.scenegraph.time.renderloop},
    and may also be made visible to other modules, such as Qt Quick 3D's
    \l DebugView item.

    By default this is disabled, because collecting the data may involve
    additional work, such as inserting timestamp queries in the command stream,
    depending on the underlying graphics API. To enable, either call this
    function with \a enable set to true, or set the \c{QSG_RHI_PROFILE}
    environment variable to a non-zero value.

    Graphics APIs where this can be expected to be supported are Direct 3D 11,
    Direct 3D 12, Vulkan (as long as the underlying Vulkan implementation
    supports timestamp queries), Metal, and OpenGL with a core or compatibility
    profile context for version 3.3 or newer. Timestamps are not supported with
    OpenGL ES.

    \since 6.6

    \sa timestampsEnabled(), setDebugMarkers()
 */
void QQuickGraphicsConfiguration::setTimestamps(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps, enable);
    }
}

/*!
    \return true if GPU timing collection is enabled.

    By default the value is false.

    \since 6.6
    \sa setTimestamps()
 */
bool QQuickGraphicsConfiguration::timestampsEnabled() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps);
}

/*!
    Requests choosing an adapter or physical device that uses software-based
    rasterization. Applicable only when the underlying API has support for
    enumerating adapters (for example, Direct 3D or Vulkan), and is ignored
    otherwise.

    If the graphics API implementation has no such graphics adapter or physical
    device available, the request is ignored. With Direct 3D it can be expected
    that a
    \l{https://docs.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp}{WARP}-based
    rasterizer is always available. With Vulkan, the flag only has an effect if
    Mesa's \c lavapipe, or some other physical device reporting
    \c{VK_PHYSICAL_DEVICE_TYPE_CPU} is available.

    Calling this function with \a enable set to true is equivalent to setting
    the environment variable \c{QSG_RHI_PREFER_SOFTWARE_RENDERER} to a non-zero
    value.

    The default value is false.

    \since 6.5

    \sa prefersSoftwareDevice()
 */
void QQuickGraphicsConfiguration::setPreferSoftwareDevice(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice, enable);
    }
}

/*!
    \return true if a software rasterizer-based graphics device is prioritized.

    By default the value is false.

    \sa setPreferSoftwareDevice()
 */
bool QQuickGraphicsConfiguration::prefersSoftwareDevice() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::PreferSoftwareDevice);
}

/*!
    Changes the usage of the automatic pipeline cache based on \a enable.

    The default value is true, unless certain application attributes or
    environment variables are set. See \l{The Automatic Pipeline Cache} for
    more information.

    \since 6.5

    \sa isAutomaticPipelineCacheEnabled()
 */
void QQuickGraphicsConfiguration::setAutomaticPipelineCache(bool enable)
{
    if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::AutoPipelineCache) != enable) {
        detach();
        d->flags.setFlag(QQuickGraphicsConfigurationPrivate::AutoPipelineCache, enable);
    }
}

/*!
    \return true if the automatic pipeline cache is enabled.

    By default this is true, unless certain application attributes or
    environment variables are set. See \l{The Automatic Pipeline Cache} for
    more information.

    \since 6.5

    \sa setAutomaticPipelineCache()
 */
bool QQuickGraphicsConfiguration::isAutomaticPipelineCacheEnabled() const
{
    return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::AutoPipelineCache);
}

/*!
    Sets the \a filename where the QQuickWindow is expected to store its
    graphics/compute pipeline cache contents. The default value is empty, which
    means pipeline cache loading is disabled.

    See \l{Pipeline Cache Save and Load} for a discussion on pipeline caches.

    Persistently storing the pipeline cache can lead to performance
    improvements in future runs of the application since expensive shader
    compilation and pipeline construction steps may be avoided.

    If and when the writing of the file happens is not defined. It will likely
    happen at some point when tearing down the scenegraph due to closing the
    window. Therefore, applications should not assume availability of the file
    until the QQuickWindow is fully destructed. QQuickGraphicsConfiguration
    only stores the filename, it does not perform any actual I/O and graphics
    operations on its own.

    When running with a graphics API where retrieving the pipeline cache (or
    shader/program binaries) is not applicable or not supported, calling this
    function has no effect.

    Calling this function is mostly equivalent to setting the environment
    variable \c{QSG_RHI_PIPELINE_CACHE_SAVE} to \a filename, with one important
    difference: this function controls the pipeline cache storage for the
    associated QQuickWindow only. Applications with multiple QQuickWindow or
    QQuickView instances can therefore store and later reload the cache contents
    via files dedicated to each window. The environment variable does not allow
    this.

    \since 6.5

    \sa pipelineCacheLoadFile(), setPipelineCacheSaveFile()
 */
void QQuickGraphicsConfiguration::setPipelineCacheSaveFile(const QString &filename)
{
    if (d->pipelineCacheSaveFile != filename) {
        detach();
        d->pipelineCacheSaveFile = filename;
    }
}

/*!
    \return the currently set filename for storing the pipeline cache.

    By default the value is an empty string.
 */
QString QQuickGraphicsConfiguration::pipelineCacheSaveFile() const
{
    return d->pipelineCacheSaveFile;
}

/*!
    Sets the \a filename where the QQuickWindow is expected to load the initial
    contents of its graphics/compute pipeline cache from. The default value is
    empty, which means pipeline cache loading is disabled.

    See \l{Pipeline Cache Save and Load} for a discussion on pipeline caches.

    Persistently storing the pipeline cache can lead to performance
    improvements in future runs of the application since expensive shader
    compilation and pipeline construction steps may be avoided.

    If and when the loading of the file's contents happens is not defined, apart
    from that it will happen at some point during the initialization of the
    scenegraph of the QQuickWindow. Therefore, the file must continue to exist
    after calling this function. QQuickGraphicsConfiguration only stores the
    filename, it cannot perform any actual I/O and graphics operations on its
    own. The real work is going to happen later on, possibly on another thread.

    When running with a graphics API where retrieving and reloading the
    pipeline cache (or shader/program binaries) is not applicable or not
    supported, calling this function has no effect.

    Calling this function is mostly equivalent to setting the environment
    variable \c{QSG_RHI_PIPELINE_CACHE_LOAD} to \a filename, with one important
    difference: this function controls the pipeline cache storage for the
    associated QQuickWindow only. Applications with multiple QQuickWindow or
    QQuickView instances can therefore store and later reload the cache contents
    via files dedicated to each window. The environment variable does not allow
    this.

    \note If the data in the file does not match the graphics device and driver
    version at run time, the contents will be ignored, transparently to the
    application. This applies to a number of graphics APIs, and the necessary
    checks are taken care of by Qt. There are exceptions, most notably Direct
    3D 11, where the "pipeline cache" is used only to store the results of
    runtime HLSL->DXBC compilation and is therefore device and vendor
    independent.

    \since 6.5

    \sa pipelineCacheLoadFile(), setPipelineCacheSaveFile()
 */
void QQuickGraphicsConfiguration::setPipelineCacheLoadFile(const QString &filename)
{
    if (d->pipelineCacheLoadFile != filename) {
        detach();
        d->pipelineCacheLoadFile = filename;
    }
}

/*!
    \return the currently set filename for loading the pipeline cache.

    By default the value is an empty string.
 */
QString QQuickGraphicsConfiguration::pipelineCacheLoadFile() const
{
    return d->pipelineCacheLoadFile;
}

QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate()
    : ref(1)
{
    // Defaults based on env.vars. NB! many of these variables are documented
    // and should be considered (semi-)public API. Changing the env.var. names
    // is therefore not allowed.

    flags = {};

    static const bool useDepthBufferFor2D = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
    if (useDepthBufferFor2D)
        flags |= UseDepthBufferFor2D;

    static const bool enableDebugLayer = qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER");
    if (enableDebugLayer)
        flags |= EnableDebugLayer;

    static const bool enableProfilingRelated = qEnvironmentVariableIntValue("QSG_RHI_PROFILE");
    if (enableProfilingRelated)
        flags |= EnableDebugMarkers | EnableTimestamps;

    static const bool preferSoftwareDevice = qEnvironmentVariableIntValue("QSG_RHI_PREFER_SOFTWARE_RENDERER");
    if (preferSoftwareDevice)
        flags |= PreferSoftwareDevice;

    // here take the existing QOpenGL disk cache attribute and env.var. into account as well
    static const bool autoPipelineCache = !QCoreApplication::instance()->testAttribute(Qt::AA_DisableShaderDiskCache)
            && !qEnvironmentVariableIntValue("QT_DISABLE_SHADER_DISK_CACHE")
            && !qEnvironmentVariableIntValue("QSG_RHI_DISABLE_DISK_CACHE");
    if (autoPipelineCache)
        flags |= AutoPipelineCache;

    static const QString pipelineCacheSaveFileEnv = qEnvironmentVariable("QSG_RHI_PIPELINE_CACHE_SAVE");
    pipelineCacheSaveFile = pipelineCacheSaveFileEnv;

    static const QString pipelineCacheLoadFileEnv = qEnvironmentVariable("QSG_RHI_PIPELINE_CACHE_LOAD");
    pipelineCacheLoadFile = pipelineCacheLoadFileEnv;
}

QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate &other)
    : ref(1),
      deviceExtensions(other.deviceExtensions),
      flags(other.flags),
      pipelineCacheSaveFile(other.pipelineCacheSaveFile),
      pipelineCacheLoadFile(other.pipelineCacheLoadFile)
{
}

#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QQuickGraphicsConfiguration &config)
{
    QDebugStateSaver saver(dbg);
    const QQuickGraphicsConfigurationPrivate *cd = QQuickGraphicsConfigurationPrivate::get(&config);
    dbg.nospace() << "QQuickGraphicsConfiguration("
                  << "flags=0x" << Qt::hex << cd->flags << Qt::dec
                  << " flag-isDepthBufferEnabledFor2D=" << config.isDepthBufferEnabledFor2D()
                  << " flag-isDebugLayerEnabled=" << config.isDebugLayerEnabled()
                  << " flag-isDebugMarkersEnabled=" << config.isDebugMarkersEnabled()
                  << " flag-prefersSoftwareDevice=" << config.prefersSoftwareDevice()
                  << " flag-isAutomaticPipelineCacheEnabled=" << config.isAutomaticPipelineCacheEnabled()
                  << " pipelineCacheSaveFile=" << cd->pipelineCacheSaveFile
                  << " piplineCacheLoadFile=" << cd->pipelineCacheLoadFile
                  << " extra-device-extension-requests=" << cd->deviceExtensions
                  << ')';
    return dbg;
}
#endif // QT_NO_DEBUG_STREAM

QT_END_NAMESPACE
