summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-03-22 09:55:03 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-06-13 10:13:45 +0200
commit53599592e09edd215bfa1eaa7e6f3a9f3fc50ae6 (patch)
tree1083ec3a18030bb6f09de58b2fe0ac1cb8aedc0b /src/gui
parentc143161608ded130919006f151bf92c44a0991d0 (diff)
Introduce the Qt graphics abstraction as private QtGui helpers
Comes with backends for Vulkan, Metal, Direct3D 11.1, and OpenGL (ES). All APIs are private for now. Shader conditioning (i.e. generating a QRhiShader in memory or on disk from some shader source code) is done via the tools and APIs provided by qt-labs/qtshadertools. The OpenGL support follows the cross-platform tradition of requiring ES 2.0 only, while optionally using some (ES) 3.x features. It can operate in core profile contexts as well. Task-number: QTBUG-70287 Change-Id: I246f2e36d562e404012c05db2aa72487108aa7cc Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.pro1
-rw-r--r--src/gui/rhi/qrhi.cpp4891
-rw-r--r--src/gui/rhi/qrhi_p.h1375
-rw-r--r--src/gui/rhi/qrhi_p_p.h544
-rw-r--r--src/gui/rhi/qrhid3d11.cpp3388
-rw-r--r--src/gui/rhi/qrhid3d11_p.h76
-rw-r--r--src/gui/rhi/qrhid3d11_p_p.h624
-rw-r--r--src/gui/rhi/qrhigles2.cpp2975
-rw-r--r--src/gui/rhi/qrhigles2_p.h84
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h695
-rw-r--r--src/gui/rhi/qrhimetal.mm3249
-rw-r--r--src/gui/rhi/qrhimetal_p.h80
-rw-r--r--src/gui/rhi/qrhimetal_p_p.h410
-rw-r--r--src/gui/rhi/qrhinull.cpp709
-rw-r--r--src/gui/rhi/qrhinull_p.h69
-rw-r--r--src/gui/rhi/qrhinull_p_p.h279
-rw-r--r--src/gui/rhi/qrhiprofiler.cpp603
-rw-r--r--src/gui/rhi/qrhiprofiler_p.h120
-rw-r--r--src/gui/rhi/qrhiprofiler_p_p.h121
-rw-r--r--src/gui/rhi/qrhivulkan.cpp5681
-rw-r--r--src/gui/rhi/qrhivulkan_p.h85
-rw-r--r--src/gui/rhi/qrhivulkan_p_p.h859
-rw-r--r--src/gui/rhi/qrhivulkanext_p.h81
-rw-r--r--src/gui/rhi/qshader.cpp586
-rw-r--r--src/gui/rhi/qshader_p.h227
-rw-r--r--src/gui/rhi/qshader_p_p.h84
-rw-r--r--src/gui/rhi/qshaderdescription.cpp1088
-rw-r--r--src/gui/rhi/qshaderdescription_p.h278
-rw-r--r--src/gui/rhi/qshaderdescription_p_p.h95
-rw-r--r--src/gui/rhi/rhi.pri57
30 files changed, 29414 insertions, 0 deletions
diff --git a/src/gui/gui.pro b/src/gui/gui.pro
index edf8124081..45c8c05162 100644
--- a/src/gui/gui.pro
+++ b/src/gui/gui.pro
@@ -49,6 +49,7 @@ qtConfig(animation): include(animation/animation.pri)
include(itemmodels/itemmodels.pri)
include(vulkan/vulkan.pri)
include(platform/platform.pri)
+include(rhi/rhi.pri)
QMAKE_LIBS += $$QMAKE_LIBS_GUI
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
new file mode 100644
index 0000000000..8cd4813723
--- /dev/null
+++ b/src/gui/rhi/qrhi.cpp
@@ -0,0 +1,4891 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhi_p_p.h"
+#include <qmath.h>
+
+#include "qrhinull_p_p.h"
+#ifndef QT_NO_OPENGL
+#include "qrhigles2_p_p.h"
+#endif
+#if QT_CONFIG(vulkan)
+#include "qrhivulkan_p_p.h"
+#endif
+#ifdef Q_OS_WIN
+#include "qrhid3d11_p_p.h"
+#endif
+//#ifdef Q_OS_DARWIN
+#ifdef Q_OS_MACOS
+#include "qrhimetal_p_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhi
+ \inmodule QtRhi
+
+ \brief Accelerated 2D/3D graphics API abstraction.
+
+ The Qt Rendering Hardware Interface is an abstraction for hardware accelerated
+ graphics APIs, such as, \l{https://www.khronos.org/opengl/}{OpenGL},
+ \l{https://www.khronos.org/opengles/}{OpenGL ES},
+ \l{https://docs.microsoft.com/en-us/windows/desktop/direct3d}{Direct3D},
+ \l{https://developer.apple.com/metal/}{Metal}, and
+ \l{https://www.khronos.org/vulkan/}{Vulkan}.
+
+ Some of the main design goals are:
+
+ \list
+
+ \li Simple, minimal, understandable, extensible. Follow the proven path of the
+ Qt Quick scenegraph.
+
+ \li Aim to be a product - and in the bigger picture, part of a product (Qt) -
+ that is usable out of the box both by internal (such as, Qt Quick) and,
+ eventually, external users.
+
+ \li Not a complete 1:1 wrapper for any of the underlying APIs. The feature set
+ is tuned towards the needs of Qt's 2D and 3D offering (QPainter, Qt Quick, Qt
+ 3D Studio). Iterate and evolve in a sustainable manner.
+
+ \li Intrinsically cross-platform, without reinventing: abstracting
+ cross-platform aspects of certain APIs (such as, OpenGL context creation and
+ windowing system interfaces, Vulkan instance and surface management) is not in
+ scope here. These are delegated to the existing QtGui facilities (QWindow,
+ QOpenGLContext, QVulkanInstance) and its backing QPA architecture.
+
+ \endlist
+
+ Each QRhi instance is backed by a backend for a specific graphics API. The
+ selection of the backend is a run time choice and is up to the application
+ or library that creates the QRhi instance. Some backends are available on
+ multiple platforms (OpenGL, Vulkan, Null), while APIs specific to a given
+ platform are only available when running on the platform in question (Metal
+ on macOS/iOS/tvOS, Direct3D on Windows).
+
+ The available backends currently are:
+
+ \list
+
+ \li OpenGL 2.1 or OpenGL ES 2.0 or newer. Some extensions are utilized when
+ present, for example to enable multisample framebuffers.
+
+ \li Direct3D 11.1
+
+ \li Metal
+
+ \li Vulkan 1.0, optionally with some extensions that are part of Vulkan 1.1
+
+ \li Null - A "dummy" backend that issues no graphics calls at all.
+
+ \endlist
+
+ In order to allow shader code to be written once in Qt applications and
+ libraries, all shaders are expected to be written in a single language
+ which is then compiled into SPIR-V. Versions for various shading language
+ are then generated from that, together with reflection information (inputs,
+ outputs, shader resources). This is then packed into easily and efficiently
+ serializable QShader instances. The compilers and tools to generate such
+ shaders are not part of QRhi, but the core classes for using such shaders,
+ QShader and QShaderDescription, are.
+
+ \section2 Design Fundamentals
+
+ A QRhi cannot be instantiated directly. Instead, use the create()
+ function. Delete the QRhi instance normally to release the graphics device.
+
+ \section3 Resources
+
+ Instances of classes deriving from QRhiResource, such as, QRhiBuffer,
+ QRhiTexture, etc., encapsulate zero, one, or more native graphics
+ resources. Instances of such classes are always created via the \c new
+ functions of the QRhi, such as, newBuffer(), newTexture(),
+ newTextureRenderTarget(), newSwapChain().
+
+ \badcode
+ vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
+ if (!vbuf->build()) { error }
+ ...
+ delete vbuf;
+ \endcode
+
+ \list
+
+ \li The returned value from both create() and functions like newBuffer() is
+ owned by the caller.
+
+ \li Just creating a QRhiResource subclass never allocates or initializes any
+ native resources. That is only done when calling the \c build function of a
+ subclass, for example, QRhiBuffer::build() or QRhiTexture::build().
+
+ \li The exception is
+ QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() and
+ QRhiSwapChain::newCompatibleRenderPassDescriptor(). There is no \c build
+ operation for these and the returned object is immediately active.
+
+ \li The resource objects themselves are treated as immutable: once a
+ resource is built, changing any parameters via the setters, such as,
+ QRhiTexture::setPixelSize(), has no effect, unless the underlying native
+ resource is released and \c build is called again. See more about resource
+ reuse in the sections below.
+
+ \li The underlying native resources are scheduled for releasing by the
+ QRhiResource destructor, or by calling QRhiResource::release(). Backends
+ often queue release requests and defer executing them to an unspecified
+ time, this is hidden from the applications. This way applications do not
+ have to worry about releasing native resources that may still be in use by
+ an in-flight frame.
+
+ \li Note that this does not mean that a QRhiResource can freely be
+ destroyed or release()'d within a frame (that is, in a
+ \l{QRhiCommandBuffer::beginFrame()}{beginFrame()} -
+ \l{QRhiCommandBuffer::endFrame()}{endFrame()} section). As a general rule,
+ all referenced QRhiResource objects must stay unchanged until the frame is
+ submitted by calling \l{QRhiCommandBuffer::endFrame()}{endFrame()}. To ease
+ this, QRhiResource::releaseAndDestroyLater() is provided as a convenience.
+
+ \endlist
+
+ \section3 Command buffers and deferred command execution
+
+ Regardless of the design and capabilities of the underlying graphics API,
+ all QRhi backends implement some level of command buffers. No
+ QRhiCommandBuffer function issues any native bind or draw command (such as,
+ \c glDrawElements) directly. Commands are always recorded in a queue,
+ either native or provided by the QRhi backend. The command buffer is
+ submitted, and so execution starts only upon QRhi::endFrame() or
+ QRhi::finish().
+
+ The deferred nature has consequences for some types of objects. For example,
+ writing to a dynamic buffer multiple times within a frame, in case such
+ buffers are backed by host-visible memory, will result in making the
+ results of all writes are visible to all draw calls in the command buffer
+ of the frame, regardless of when the dynamic buffer update was recorded
+ relative to a draw call.
+
+ Furthermore, instances of QRhiResource subclasses must be treated immutable
+ within a frame in which they are referenced in any way. Create or rebuild
+ all resources upfront, before starting to record commands for the next
+ frame. Reusing a QRhiResource instance within a frame (by rebuilding it and
+ then referencing it again in the same \c{beginFrame - endFrame} section)
+ should be avoided as it may lead to unexpected results, depending on the
+ backend.
+
+ As a general rule, all referenced QRhiResource objects must stay valid and
+ unmodified until the frame is submitted by calling
+ \l{QRhiCommandBuffer::endFrame()}{endFrame()}. On the other hand, calling
+ \l{QRhiResource::release()}{release()} or destroying the QRhiResource are
+ always safe once the frame is submitted, regardless of the status of the
+ underlying native resources (which may still be in use by the GPU - but
+ that is taken care of internally).
+
+ Unlike APIs like OpenGL, upload and copy type of commands cannot be mixed
+ with draw commands. The typical renderer will involve a sequence similar to
+ the following: \c{(re)build resources} - \c{begin frame} - \c{record
+ uploads and copies} - \c{start renderpass} - \c{record draw calls} - \c{end
+ renderpass} - \c{end frame}. Recording copy type of operations happens via
+ QRhiResourceUpdateBatch. Such operations are committed typically on
+ \l{QRhiCommandBuffer::beginPass()}{beginPass()}.
+
+ When working with legacy rendering engines designed for OpenGL, the
+ migration to QRhi often involves redesigning from having a single \c render
+ step (that performs copies and uploads, clears buffers, and issues draw
+ calls, all mixed together) to a clearly separated, two phase \c prepare -
+ \c render setup where the \c render step only starts a renderpass and
+ records draw calls, while all resource creation and queuing of updates,
+ uploads and copies happens beforehand, in the \c prepare step.
+
+ QRhi does not at the moment allow freely creating and submitting command
+ buffers. This may be lifted in the future to some extent, in particular if
+ compute support is introduced, but the model of well defined
+ \c{frame-start} and \c{frame-end} points, combined with a dedicated,
+ "frame" command buffer, where \c{frame-end} implies presenting, is going to
+ remain the primary way of operating since this is what fits Qt's various UI
+ technologies best.
+
+ \section3 Threading
+
+ A QRhi instance and the associated resources can be created and used on any
+ thread but all usage must be limited to that one single thread. When
+ rendering to multiple QWindows in an application, having a dedicated thread
+ and QRhi instance for each window is often advisable, as this can eliminate
+ issues with unexpected throttling caused by presenting to multiple windows.
+ Conceptually that is then the same as how Qt Quick scene graph's threaded
+ render loop operates when working directly with OpenGL: one thread for each
+ window, one QOpenGLContext for each thread. When moving onto QRhi,
+ QOpenGLContext is replaced by QRhi, making the migration straightforward.
+
+ When it comes to externally created native objects, such as OpenGL contexts
+ passed in via QRhiGles2NativeHandles, it is up to the application to ensure
+ they are not misused by other threads.
+
+ Resources are not shareable between QRhi instances. This is an intentional
+ choice since QRhi hides most queue, command buffer, and resource
+ synchronization related tasks, and provides no API for them. Safe and
+ efficient concurrent use of graphics resources from multiple threads is
+ tied to those concepts, however, and is thus a topic that is currently out
+ of scope, but may be introduced in the future.
+
+ \section3 Resource synchronization
+
+ QRhi does not expose APIs for resource barriers or image layout
+ transitions. Such synchronization is done implicitly by the backends, where
+ applicable (for example, Vulkan), by tracking resource usage as necessary.
+
+ \section3 Resource reuse
+
+ From the user's point of view a QRhiResource is reusable immediately after
+ calling QRhiResource::release(). With the exception of swapchains, calling
+ \c build() on an already built object does an implicit \c release(). This
+ provides a handy shortcut to reuse a QRhiResource instance with different
+ parameters, with a new native graphics object underneath.
+
+ The importance of reusing the same object lies in the fact that some
+ objects reference other objects: for example, a QRhiShaderResourceBindings
+ can reference QRhiBuffer, QRhiTexture, and QRhiSampler instances. If in a
+ later frame one of these buffers need to be resized or a sampler parameter
+ needs changing, destroying and creating a whole new QRhiBuffer or
+ QRhiSampler would invalidate all references to the old instance. By just
+ changing the appropriate parameters via QRhiBuffer::setSize() or similar
+ and then calling QRhiBuffer::build(), everything works as expected and
+ there is no need to touch the QRhiShaderResourceBindings at all, even
+ though there is a good chance that under the hood the QRhiBuffer is now
+ backed by a whole new native buffer.
+
+ \badcode
+ ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
+ ubuf->build();
+
+ srb = rhi->newShaderResourceBindings()
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf)
+ });
+ srb->build();
+
+ ...
+
+ // now in a later frame we need to grow the buffer to a larger size
+ ubuf->setSize(512);
+ ubuf->build(); // same as ubuf->release(); ubuf->build();
+
+ // that's it, srb needs no changes whatsoever
+ \endcode
+
+ \section3 Pooled objects
+
+ In addition to resources, there are pooled objects as well, such as,
+ QRhiResourceUpdateBatch. An instance is retrieved via a \c next function,
+ such as, nextResourceUpdateBatch(). The caller does not own the returned
+ instance in this case. The only valid way of operating here is calling
+ functions on the QRhiResourceUpdateBatch and then passing it to
+ QRhiCommandBuffer::beginPass() or QRhiCommandBuffer::endPass(). These
+ functions take care of returning the batch to the pool. Alternatively, a
+ batch can be "canceled" and returned to the pool without processing by
+ calling QRhiResourceUpdateBatch::release().
+
+ A typical pattern is thus:
+
+ \badcode
+ QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch();
+ ...
+ resUpdates->updateDynamicBuffer(ubuf, 0, 64, mvp.constData());
+ if (!image.isNull()) {
+ resUpdates->uploadTexture(texture, image);
+ image = QImage();
+ }
+ ...
+ QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
+ cb->beginPass(swapchain->currentFrameRenderTarget(), clearCol, clearDs, resUpdates);
+ \endcode
+
+ \section3 Swapchain specifics
+
+ QRhiSwapChain features some special semantics due to the peculiar nature of
+ swapchains.
+
+ \list
+
+ \li It has no \c build but rather a QRhiSwapChain::buildOrResize().
+ Repeatedly calling this function is \b not the same as calling
+ QRhiSwapChain::release() followed by QRhiSwapChain::buildOrResize(). This
+ is because swapchains often have ways to handle the case where buffers need
+ to be resized in a manner that is more efficient than a brute force
+ destroying and recreating from scratch.
+
+ \li An active QRhiSwapChain must be released by calling
+ \l{QRhiSwapChain::release()}{release()}, or by destroying the object, before
+ the QWindow's underlying QPlatformWindow, and so the associated native
+ window object, is destroyed. It should not be postponed because releasing
+ the swapchain may become problematic (and with some APIs, like Vulkan, is
+ explicitly disallowed) when the native window is not around anymore, for
+ example because the QPlatformWindow got destroyed upon getting a
+ QWindow::close(). Therefore, releasing the swapchain must happen whenever
+ the targeted QWindow sends the
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed event. If the event does
+ not arrive before the destruction of the QWindow - this can happen when
+ using QCoreApplication::quit() -, then check QWindow::handle() after the
+ event loop exits and invoke the swapchain release when non-null (meaning
+ the underlying native window is still around).
+
+ \endlist
+
+ \section3 Ownership
+
+ The general rule is no ownership transfer. Creating a QRhi with an already
+ existing graphics device does not mean the QRhi takes ownership of the
+ device object. Similarly, ownership is not given away when a device or
+ texture object is "exported" via QRhi::nativeHandles() or
+ QRhiTexture::nativeHandles(). Most importantly, passing pointers in structs
+ and via setters does not transfer ownership.
+ */
+
+/*!
+ \enum QRhi::Implementation
+ Describes which graphics API-specific backend gets used by a QRhi instance.
+
+ \value Null
+ \value Vulkan
+ \value OpenGLES2
+ \value D3D11
+ \value Metal
+ */
+
+/*!
+ \enum QRhi::Flag
+ Describes what special features to enable.
+
+ \value EnableProfiling Enables gathering timing (CPU, GPU) and resource
+ (QRhiBuffer, QRhiTexture, etc.) information and additional metadata. See
+ QRhiProfiler. Avoid enabling in production builds as it may involve a
+ performance penalty.
+
+ \value EnableDebugMarkers Enables debug marker groups. Without this frame
+ debugging features like making debug groups and custom resource name
+ visible in external GPU debugging tools will not be available and functions
+ like QRhiCommandBuffer::debugMarkBegin() will become a no-op. Avoid
+ enabling in production builds as it may involve a performance penalty.
+ */
+
+/*!
+ \enum QRhi::FrameOpResult
+ Describes the result of operations that can have a soft failure.
+
+ \value FrameOpSuccess Success
+
+ \value FrameOpError Unspecified error
+
+ \value FrameOpSwapChainOutOfDate The swapchain is in an inconsistent state
+ internally. This can be recoverable by attempting to repeat the operation
+ (such as, beginFrame()) later.
+
+ \value FrameOpDeviceLost The graphics device was lost. This can be
+ recoverable by attempting to repeat the operation (such as, beginFrame())
+ and releasing and reinitializing all objects backed by native graphics
+ resources.
+ */
+
+/*!
+ \enum QRhi::Feature
+ Flag values to indicate what features are supported by the backend currently in use.
+
+ \value MultisampleTexture Indicates that textures with a sample count larger
+ than 1 are supported.
+
+ \value MultisampleRenderBuffer Indicates that renderbuffers with a sample
+ count larger than 1 are supported.
+
+ \value DebugMarkers Indicates that debug marker groups (and so
+ QRhiCommandBuffer::debugMarkBegin()) are supported.
+
+ \value Timestamps Indicates that command buffer timestamps are supported.
+ Relevant for QRhiProfiler::gpuFrameTimes().
+
+ \value Instancing Indicates that instanced drawing is supported.
+
+ \value CustomInstanceStepRate Indicates that instance step rates other than
+ 1 are supported.
+
+ \value PrimitiveRestart Indicates that restarting the assembly of
+ primitives when encountering an index value of 0xFFFF
+ (\l{QRhiCommandBuffer::IndexUInt16}{IndexUInt16}) or 0xFFFFFFFF
+ (\l{QRhiCommandBuffer::IndexUInt32}{IndexUInt32}) is enabled, for certain
+ primitive topologies at least. QRhi will try to enable this with all
+ backends, but in some cases it will not be supported. Dynamically
+ controlling primitive restart is not possible since with some APIs
+ primitive restart with a fixed index is always on. Applications must assume
+ that whenever this feature is reported as supported, the above mentioned
+ index values \c may be treated specially, depending on the topology. The
+ only two topologies where primitive restart is guaranteed to behave
+ identically across backends, as long as this feature is reported as
+ supported, are \l{QRhiGraphicsPipeline::LineStrip}{LineStrip} and
+ \l{QRhiGraphicsPipeline::TriangleStrip}{TriangleStrip}.
+
+ \value NonDynamicUniformBuffers Indicates that creating buffers with the
+ usage \l{QRhiBuffer::UniformBuffer}{UniformBuffer} and the types
+ \l{QRhiBuffer::Immutable}{Immutable} or \l{QRhiBuffer::Static}{Static} is
+ supported. When reported as unsupported, uniform (constant) buffers must be
+ created as \l{QRhiBuffer::Dynamic}{Dynamic}. (which is recommended
+ regardless)
+
+ \value NonFourAlignedEffectiveIndexBufferOffset Indicates that effective
+ index buffer offsets (\c{indexOffset + firstIndex * indexComponentSize})
+ that are not 4 byte aligned are supported. When not supported, attempting
+ to issue a \l{QRhiCommandBuffer::drawIndexed()}{drawIndexed()} with a
+ non-aligned effective offset may lead to unspecified behavior.
+
+ \value NPOTTextureRepeat Indicates that the \l{QRhiSampler::Repeat}{Repeat}
+ mode is supported for textures with a non-power-of-two size.
+
+ \value RedOrAlpha8IsRed Indicates that the
+ \l{QRhiTexture::RED_OR_ALPHA8}{RED_OR_ALPHA8} format maps to a one
+ component 8-bit \c red format. This is the case for all backends except
+ OpenGL, where \c{GL_ALPHA}, a one component 8-bit \c alpha format, is used
+ instead. This is relevant for shader code that samples from the texture.
+
+ \value ElementIndexUint Indicates that 32-bit unsigned integer elements are
+ supported in the index buffer. In practice this is true everywhere except
+ when running on plain OpenGL ES 2.0 implementations without the necessary
+ extension. When false, only 16-bit unsigned elements are supported in the
+ index buffer.
+ */
+
+/*!
+ \enum QRhi::BeginFrameFlag
+ Flag values for QRhi::beginFrame()
+ */
+
+/*!
+ \enum QRhi::EndFrameFlag
+ Flag values for QRhi::endFrame()
+
+ \value SkipPresent Specifies that no present command is to be queued or no
+ swapBuffers call is to be made. This way no image is presented. Generating
+ multiple frames with all having this flag set is not recommended (except,
+ for example, for benchmarking purposes - but keep in mind that backends may
+ behave differently when it comes to waiting for command completion without
+ presenting so the results are not comparable between them)
+ */
+
+/*!
+ \enum QRhi::ResourceLimit
+ Describes the resource limit to query.
+
+ \value TextureSizeMin Minimum texture width and height. This is typically
+ 1. The minimum texture size is handled gracefully, meaning attempting to
+ create a texture with an empty size will instead create a texture with the
+ minimum size.
+
+ \value TextureSizeMax Maximum texture width and height. This depends on the
+ graphics API and sometimes the platform or implementation as well.
+ Typically the value is in the range 4096 - 16384. Attempting to create
+ textures larger than this is expected to fail.
+
+ \value MaxColorAttachments The maximum number of color attachments for a
+ QRhiTextureRenderTarget, in case multiple render targets are supported. When
+ MRT is not supported, the value is 1. Otherwise this is typically 8, but
+ watch out for the fact that OpenGL only mandates 4 as the minimum, and that
+ is what some OpenGL ES implementations provide.
+
+ \value FramesInFlight The number of frames the backend may keep "in
+ flight". The value has no relevance, and is unspecified, with backends like
+ OpenGL and Direct3D 11. With backends like Vulkan or Metal, it is the
+ responsibility of QRhi to block whenever starting a new frame and finding
+ the CPU is already \c{N - 1} frames ahead of the GPU (because the command
+ buffer submitted in frame no. \c{current} - \c{N} has not yet completed).
+ The value N is what is returned from here, and is typically 2. This can be
+ relevant to applications that integrate rendering done directly with the
+ graphics API, as such rendering code may want to perform double (if the
+ value is 2) buffering for resources, such as, buffers, similarly to the
+ QRhi backends themselves. The current frame slot index (a value running 0,
+ 1, .., N-1, then wrapping around) is retrievable from
+ QRhi::currentFrameSlot().
+ */
+
+/*!
+ \class QRhiInitParams
+ \inmodule QtRhi
+ \brief Base class for backend-specific initialization parameters.
+
+ Contains fields that are relevant to all backends.
+ */
+
+/*!
+ \class QRhiDepthStencilClearValue
+ \inmodule QtRhi
+ \brief Specifies clear values for a depth or stencil buffer.
+ */
+
+/*!
+ \fn QRhiDepthStencilClearValue::QRhiDepthStencilClearValue()
+
+ Constructs a depth/stencil clear value with depth clear value 1.0f and
+ stencil clear value 0.
+ */
+
+/*!
+ Constructs a depth/stencil clear value with depth clear value \a d and
+ stencil clear value \a s.
+ */
+QRhiDepthStencilClearValue::QRhiDepthStencilClearValue(float d, quint32 s)
+ : m_d(d),
+ m_s(s)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiDepthStencilClearValue objects
+ \a a and \a b are equal.
+
+ \relates QRhiDepthStencilClearValue
+ */
+bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
+{
+ return a.depthClearValue() == b.depthClearValue()
+ && a.stencilClearValue() == b.stencilClearValue();
+}
+
+/*!
+ \return \c false if the values in the two QRhiDepthStencilClearValue
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiDepthStencilClearValue
+*/
+bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiDepthStencilClearValue
+ */
+uint qHash(const QRhiDepthStencilClearValue &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed * (qFloor(v.depthClearValue() * 100) + v.stencilClearValue());
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiDepthStencilClearValue(depth-clear=" << v.depthClearValue()
+ << " stencil-clear=" << v.stencilClearValue()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiViewport
+ \inmodule QtRhi
+ \brief Specifies a viewport rectangle.
+
+ Used with QRhiCommandBuffer::setViewport().
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ Typical usage is like the following:
+
+ \badcode
+ const QSize outputSizeInPixels = swapchain->currentPixelSize();
+ const QRhiViewport viewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height());
+ cb->beginPass(swapchain->currentFrameRenderTarget(), { 0, 0, 0, 1 }, { 1, 0 });
+ cb->setGraphicsPipeline(ps);
+ cb->setViewport(viewport);
+ ...
+ \endcode
+
+ \sa QRhiCommandBuffer::setViewport(), QRhi::clipSpaceCorrMatrix(), QRhiScissor
+ */
+
+/*!
+ \fn QRhiViewport::QRhiViewport()
+
+ Constructs a viewport description with an empty rectangle and a depth range
+ of 0.0f - 1.0f.
+
+ \sa QRhi::clipSpaceCorrMatrix()
+ */
+
+/*!
+ Constructs a viewport description with the rectangle specified by \a x, \a
+ y, \a w, \a h and the depth range \a minDepth and \a maxDepth.
+
+ \note x and y are assumed to be the bottom-left position.
+
+ \sa QRhi::clipSpaceCorrMatrix()
+ */
+QRhiViewport::QRhiViewport(float x, float y, float w, float h, float minDepth, float maxDepth)
+ : m_rect { { x, y, w, h } },
+ m_minDepth(minDepth),
+ m_maxDepth(maxDepth)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiViewport objects
+ \a a and \a b are equal.
+
+ \relates QRhiViewport
+ */
+bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
+{
+ return a.viewport() == b.viewport()
+ && a.minDepth() == b.minDepth()
+ && a.maxDepth() == b.maxDepth();
+}
+
+/*!
+ \return \c false if the values in the two QRhiViewport
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiViewport
+*/
+bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiViewport
+ */
+uint qHash(const QRhiViewport &v, uint seed) Q_DECL_NOTHROW
+{
+ const std::array<float, 4> r = v.viewport();
+ return seed + r[0] + r[1] + r[2] + r[3] + qFloor(v.minDepth() * 100) + qFloor(v.maxDepth() * 100);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiViewport &v)
+{
+ QDebugStateSaver saver(dbg);
+ const std::array<float, 4> r = v.viewport();
+ dbg.nospace() << "QRhiViewport(bottom-left-x=" << r[0]
+ << " bottom-left-y=" << r[1]
+ << " width=" << r[2]
+ << " height=" << r[3]
+ << " minDepth=" << v.minDepth()
+ << " maxDepth=" << v.maxDepth()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiScissor
+ \inmodule QtRhi
+ \brief Specifies a scissor rectangle.
+
+ Used with QRhiCommandBuffer::setScissor(). Setting a scissor rectangle is
+ only possible with a QRhiGraphicsPipeline that has
+ QRhiGraphicsPipeline::UsesScissor set.
+
+ \note QRhi assumes OpenGL-style scissor coordinates, meaning x and y are
+ bottom-left.
+
+ \sa QRhiCommandBuffer::setScissor(), QRhiViewport
+ */
+
+/*!
+ \fn QRhiScissor::QRhiScissor()
+
+ Constructs an empty scissor.
+ */
+
+/*!
+ Constructs a scissor with the rectangle specified by \a x, \a y, \a w, and
+ \a h.
+
+ \note x and y are assumed to be the bottom-left position.
+ */
+QRhiScissor::QRhiScissor(int x, int y, int w, int h)
+ : m_rect { { x, y, w, h } }
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiScissor objects
+ \a a and \a b are equal.
+
+ \relates QRhiScissor
+ */
+bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
+{
+ return a.scissor() == b.scissor();
+}
+
+/*!
+ \return \c false if the values in the two QRhiScissor
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiScissor
+*/
+bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiScissor
+ */
+uint qHash(const QRhiScissor &v, uint seed) Q_DECL_NOTHROW
+{
+ const std::array<int, 4> r = v.scissor();
+ return seed + r[0] + r[1] + r[2] + r[3];
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiScissor &s)
+{
+ QDebugStateSaver saver(dbg);
+ const std::array<int, 4> r = s.scissor();
+ dbg.nospace() << "QRhiScissor(bottom-left-x=" << r[0]
+ << " bottom-left-y=" << r[1]
+ << " width=" << r[2]
+ << " height=" << r[3]
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputBinding
+ \inmodule QtRhi
+ \brief Describes a vertex input binding.
+
+ Specifies the stride (in bytes, must be a multiple of 4), the
+ classification and optionally the instance step rate.
+
+ As an example, assume a vertex shader with the following inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec2 texcoord;
+ \endcode
+
+ Now let's assume also that 3 component vertex positions \c{(x, y, z)} and 2
+ component texture coordinates \c{(u, v)} are provided in a non-interleaved
+ format in a buffer (or separate buffers even). Definining two bindings
+ could then be done like this:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) },
+ { 2 * sizeof(float) }
+ });
+ \endcode
+
+ Only the stride is interesting here since instancing is not used. The
+ binding number is given by the index of the QRhiVertexInputBinding
+ element in the bindings vector of the QRhiVertexInputLayout.
+
+ Once a graphics pipeline with this vertex input layout is bound, the vertex
+ inputs could be set up like the following for drawing a cube with 36
+ vertices, assuming we have a single buffer with first the positions and
+ then the texture coordinates:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { cubeBuf, 0 },
+ { cubeBuf, 36 * 3 * sizeof(float) }
+ };
+ cb->setVertexInput(0, 2, vbufBindings);
+ \endcode
+
+ Note how the index defined by \c {startBinding + i}, where \c i is the
+ index in the second argument of
+ \l{QRhiCommandBuffer::setVertexInput()}{setVertexInput()}, matches the
+ index of the corresponding entry in the \c bindings vector of the
+ QRhiVertexInputLayout.
+
+ \note the stride must always be a multiple of 4.
+
+ \sa QRhiCommandBuffer::setVertexInput()
+ */
+
+/*!
+ \enum QRhiVertexInputBinding::Classification
+ Describes the input data classification.
+
+ \value PerVertex Data is per-vertex
+ \value PerInstance Data is per-instance
+ */
+
+/*!
+ \fn QRhiVertexInputBinding::QRhiVertexInputBinding()
+
+ Constructs a default vertex input binding description.
+ */
+
+/*!
+ Constructs a vertex input binding description with the specified \a stride,
+ classification \a cls, and instance step rate \a stepRate.
+
+ \note \a stepRate other than 1 is only supported when
+ QRhi::CustomInstanceStepRate is reported to be supported.
+ */
+QRhiVertexInputBinding::QRhiVertexInputBinding(quint32 stride, Classification cls, int stepRate)
+ : m_stride(stride),
+ m_classification(cls),
+ m_instanceStepRate(stepRate)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputBinding objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputBinding
+ */
+bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
+{
+ return a.stride() == b.stride()
+ && a.classification() == b.classification()
+ && a.instanceStepRate() == b.instanceStepRate();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputBinding
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputBinding
+*/
+bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputBinding
+ */
+uint qHash(const QRhiVertexInputBinding &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed + v.stride() + v.classification();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputBinding(stride=" << b.stride()
+ << " cls=" << b.classification()
+ << " step-rate=" << b.instanceStepRate()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputAttribute
+ \inmodule QtRhi
+ \brief Describes a single vertex input element.
+
+ The members specify the binding number, location, format, and offset for a
+ single vertex input element.
+
+ \note For HLSL it is assumed that the vertex shader uses
+ \c{TEXCOORD<location>} as the semantic for each input. Hence no separate
+ semantic name and index.
+
+ As an example, assume a vertex shader with the following inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec2 texcoord;
+ \endcode
+
+ Now let's assume that we have 3 component vertex positions \c{(x, y, z)}
+ and 2 component texture coordinates \c{(u, v)} are provided in a
+ non-interleaved format in a buffer (or separate buffers even). Once two
+ bindings are defined, the attributes could be specified as:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) },
+ { 2 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 1, 1, QRhiVertexInputAttribute::Float2, 0 }
+ });
+ \endcode
+
+ Once a graphics pipeline with this vertex input layout is bound, the vertex
+ inputs could be set up like the following for drawing a cube with 36
+ vertices, assuming we have a single buffer with first the positions and
+ then the texture coordinates:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { cubeBuf, 0 },
+ { cubeBuf, 36 * 3 * sizeof(float) }
+ };
+ cb->setVertexInput(0, 2, vbufBindings);
+ \endcode
+
+ When working with interleaved data, there will typically be just one
+ binding, with multiple attributes referring to that same buffer binding
+ point:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float2, 3 * sizeof(float) }
+ });
+ \endcode
+
+ and then:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBinding(interleavedCubeBuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ \endcode
+
+ \sa QRhiCommandBuffer::setVertexInput()
+ */
+
+/*!
+ \enum QRhiVertexInputAttribute::Format
+ Specifies the type of the element data.
+
+ \value Float4 Four component float vector
+ \value Float3 Three component float vector
+ \value Float2 Two component float vector
+ \value Float Float
+ \value UNormByte4 Four component normalized unsigned byte vector
+ \value UNormByte2 Two component normalized unsigned byte vector
+ \value UNormByte Normalized unsigned byte
+ */
+
+/*!
+ \fn QRhiVertexInputAttribute::QRhiVertexInputAttribute()
+
+ Constructs a default vertex input attribute description.
+ */
+
+/*!
+ Constructs a vertex input attribute description with the specified \a
+ binding number, \a location, \a format, and \a offset.
+ */
+QRhiVertexInputAttribute::QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset)
+ : m_binding(binding),
+ m_location(location),
+ m_format(format),
+ m_offset(offset)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputAttribute objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputAttribute
+ */
+bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
+{
+ return a.binding() == b.binding()
+ && a.location() == b.location()
+ && a.format() == b.format()
+ && a.offset() == b.offset();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputAttribute
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputAttribute
+*/
+bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputAttribute
+ */
+uint qHash(const QRhiVertexInputAttribute &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed + v.binding() + v.location() + v.format() + v.offset();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputAttribute &a)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputAttribute(binding=" << a.binding()
+ << " location=" << a.location()
+ << " format=" << a.format()
+ << " offset=" << a.offset()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputLayout
+ \inmodule QtRhi
+ \brief Describes the layout of vertex inputs consumed by a vertex shader.
+
+ The vertex input layout is defined by the collections of
+ QRhiVertexInputBinding and QRhiVertexInputAttribute.
+ */
+
+/*!
+ \fn QRhiVertexInputLayout::QRhiVertexInputLayout()
+
+ Constructs an empty vertex input layout description.
+ */
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputLayout objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputLayout
+ */
+bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
+{
+ return a.bindings() == b.bindings()
+ && a.attributes() == b.attributes();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputLayout
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputLayout
+*/
+bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputLayout
+ */
+uint qHash(const QRhiVertexInputLayout &v, uint seed) Q_DECL_NOTHROW
+{
+ return qHash(v.bindings(), seed) + qHash(v.attributes(), seed);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputLayout(bindings=" << v.bindings()
+ << " attributes=" << v.attributes()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiGraphicsShaderStage
+ \inmodule QtRhi
+ \brief Specifies the type and the shader code for a shader stage in the graphics pipeline.
+ */
+
+/*!
+ \enum QRhiGraphicsShaderStage::Type
+ Specifies the type of the shader stage.
+
+ \value Vertex Vertex stage
+ \value Fragment Fragment (pixel) stage
+ */
+
+/*!
+ \fn QRhiGraphicsShaderStage::QRhiGraphicsShaderStage()
+
+ Constructs a shader stage description for the vertex stage with an empty
+ QShader.
+ */
+
+/*!
+ Constructs a shader stage description with the \a type of the stage and the
+ \a shader.
+
+ The shader variant \a v defaults to QShader::StandardShader. A
+ QShader contains multiple source and binary versions of a shader.
+ In addition, it can also contain variants of the shader with slightly
+ modified code. \a v can then be used to select the desired variant.
+ */
+QRhiGraphicsShaderStage::QRhiGraphicsShaderStage(Type type, const QShader &shader, QShader::Variant v)
+ : m_type(type),
+ m_shader(shader),
+ m_shaderVariant(v)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiGraphicsShaderStage objects
+ \a a and \a b are equal.
+
+ \relates QRhiGraphicsShaderStage
+ */
+bool operator==(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW
+{
+ return a.type() == b.type()
+ && a.shader() == b.shader()
+ && a.shaderVariant() == b.shaderVariant();
+}
+
+/*!
+ \return \c false if the values in the two QRhiGraphicsShaderStage
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiGraphicsShaderStage
+*/
+bool operator!=(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiGraphicsShaderStage
+ */
+uint qHash(const QRhiGraphicsShaderStage &v, uint seed) Q_DECL_NOTHROW
+{
+ return v.type() + qHash(v.shader(), seed) + v.shaderVariant();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiGraphicsShaderStage &s)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiGraphicsShaderStage(type=" << s.type()
+ << " shader=" << s.shader()
+ << " variant=" << s.shaderVariant()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiColorAttachment
+ \inmodule QtRhi
+ \brief Describes the a single color attachment of a render target.
+
+ A color attachment is either a QRhiTexture or a QRhiRenderBuffer. The
+ former, when texture() is set, is used in most cases.
+
+ \note texture() and renderBuffer() cannot be both set (be non-null at the
+ same time).
+
+ Setting renderBuffer instead is recommended only when multisampling is
+ needed. Relying on QRhi::MultisampleRenderBuffer is a better choice than
+ QRhi::MultisampleTexture in practice since the former is available in more
+ run time configurations (e.g. when running on OpenGL ES 3.0 which has no
+ support for multisample textures, but does support multisample
+ renderbuffers).
+
+ When targeting a non-multisample texture, the layer() and level()
+ indicate the targeted layer (face index \c{0-5} for cubemaps) and mip
+ level.
+
+ When texture() or renderBuffer() is multisample, resolveTexture() can be
+ set optionally. When set, samples are resolved automatically into that
+ (non-multisample) texture at the end of the render pass. When rendering
+ into a multisample renderbuffers, this is the only way to get resolved,
+ non-multisample content out of them. Multisample textures allow sampling in
+ shaders so for them this is just one option.
+
+ \note when resolving is enabled, the multisample data may not be written
+ out at all. This means that the multisample texture() must not be used
+ afterwards with shaders for sampling when resolveTexture() is set.
+ */
+
+/*!
+ \fn QRhiColorAttachment::QRhiColorAttachment()
+
+ Constructs an empty color attachment description.
+ */
+
+/*!
+ Constructs a color attachment description that specifies \a texture as the
+ associated color buffer.
+ */
+QRhiColorAttachment::QRhiColorAttachment(QRhiTexture *texture)
+ : m_texture(texture)
+{
+}
+
+/*!
+ Constructs a color attachment description that specifies \a renderBuffer as
+ the associated color buffer.
+ */
+QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
+ : m_renderBuffer(renderBuffer)
+{
+}
+
+/*!
+ \class QRhiTextureRenderTargetDescription
+ \inmodule QtRhi
+ \brief Describes the color and depth or depth/stencil attachments of a render target.
+
+ A texture render target has zero or more textures as color attachments,
+ zero or one renderbuffer as combined depth/stencil buffer or zero or one
+ texture as depth buffer.
+
+ \note depthStencilBuffer() and depthTexture() cannot be both set (cannot be
+ non-null at the same time).
+ */
+
+/*!
+ \fn QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription()
+
+ Constructs an empty texture render target description.
+ */
+
+/*!
+ Constructs a texture render target description with one attachment
+ described by \a colorAttachment.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ Constructs a texture render target description with two attachments, a
+ color attachment described by \a colorAttachment, and a depth/stencil
+ attachment with \a depthStencilBuffer.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment,
+ QRhiRenderBuffer *depthStencilBuffer)
+ : m_depthStencilBuffer(depthStencilBuffer)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ Constructs a texture render target description with two attachments, a
+ color attachment described by \a colorAttachment, and a depth attachment
+ with \a depthTexture.
+
+ \note \a depthTexture must have a suitable format, such as QRhiTexture::D16
+ or QRhiTexture::D32F.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment,
+ QRhiTexture *depthTexture)
+ : m_depthTexture(depthTexture)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ \class QRhiTextureSubresourceUploadDescription
+ \inmodule QtRhi
+ \brief Describes the source for one mip level in a layer in a texture upload operation.
+
+ The source content is specified either as a QImage or as a raw blob. The
+ former is only allowed for uncompressed textures with a format that can be
+ mapped to QImage, while the latter is supported for all formats, including
+ floating point and compressed.
+
+ \note image() and data() cannot be both set at the same time.
+
+ destinationTopLeft() specifies the top-left corner of the target
+ rectangle. Defaults to (0, 0).
+
+ An empty sourceSize() (the default) indicates that size is assumed to be
+ the size of the subresource. With QImage-based uploads this implies that
+ the size of the source image() must match the subresource. When providing
+ raw data instead, sufficient number of bytes must be provided in data().
+
+ \note With compressed textures the first upload must always match the
+ subresource size due to graphics API limitations with some backends.
+
+ sourceTopLeft() is supported only for QImage-based uploads, and specifies
+ the top-left corner of the source rectangle.
+
+ \note Setting sourceSize() or sourceTopLeft() may trigger a QImage copy
+ internally, depending on the format and the backend.
+
+ When providing raw data, the stride (row pitch, row length in bytes) of the
+ provided data must be equal to \c{width * pixelSize} where \c pixelSize is
+ the number of bytes used for one pixel, and there must be no additional
+ padding between rows. There is no row start alignment requirement.
+
+ \note The format of the source data must be compatible with the texture
+ format. With many graphics APIs the data is copied as-is into a staging
+ buffer, there is no intermediate format conversion provided by QRhi. This
+ applies to floating point formats as well, with, for example, RGBA16F
+ requiring half floats in the source data.
+ */
+
+/*!
+ \fn QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription()
+
+ Constructs an empty subresource description.
+
+ \note an empty QRhiTextureSubresourceUploadDescription is not useful on its
+ own and should not be submitted to a QRhiTextureUploadEntry. At minimum
+ image or data must be set first.
+ */
+
+/*!
+ Constructs a mip level description with a \a image.
+
+ The \l{QImage::size()}{size} of \a image must match the size of the mip
+ level. For level 0 that is the \l{QRhiTexture::pixelSize()}{texture size}.
+
+ The bit depth of \a image must be compatible with the
+ \l{QRhiTexture::Format}{texture format}.
+
+ To describe a partial upload, call setSourceSize(), setSourceTopLeft(), or
+ setDestinationTopLeft() afterwards.
+ */
+QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(const QImage &image)
+ : m_image(image)
+{
+}
+
+/*!
+ Constructs a mip level description with the image data is specified by \a
+ data and \a size. This is suitable for floating point and compressed
+ formats as well.
+
+ \a data can safely be destroyed or changed once this function returns.
+ */
+QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(const void *data, int size)
+ : m_data(reinterpret_cast<const char *>(data), size)
+{
+}
+
+/*!
+ \class QRhiTextureUploadEntry
+ \inmodule QtRhi
+ \brief Describes one layer (face for cubemaps) in a texture upload operation.
+ */
+
+/*!
+ \fn QRhiTextureUploadEntry::QRhiTextureUploadEntry()
+
+ Constructs an empty QRhiTextureUploadEntry targeting layer 0 and level 0.
+
+ \note an empty QRhiTextureUploadEntry should not be submitted without
+ setting a QRhiTextureSubresourceUploadDescription via setDescription()
+ first.
+ */
+
+/*!
+ Constructs a QRhiTextureUploadEntry targeting the given \a layer and mip
+ \a level, with the subresource contents described by \a desc.
+ */
+QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &desc)
+ : m_layer(layer),
+ m_level(level),
+ m_desc(desc)
+{
+}
+
+/*!
+ \class QRhiTextureUploadDescription
+ \inmodule QtRhi
+ \brief Describes a texture upload operation.
+
+ Used with QRhiResourceUpdateBatch::uploadTexture(). That function has two
+ variants: one taking a QImage and one taking a
+ QRhiTextureUploadDescription. The former is a convenience version,
+ internally creating a QRhiTextureUploadDescription with a single image
+ targeting level 0 for layer 0. However, when cubemaps, pre-generated mip
+ images, or compressed textures are involved, applications will have to work
+ directly with this class instead.
+
+ QRhiTextureUploadDescription also enables specifying batched uploads, which
+ are useful for example when generating an atlas or glyph cache texture:
+ multiple, partial uploads for the same subresource (meaning the same layer
+ and level) are supported, and can be, depending on the backend and the
+ underlying graphics API, more efficient when batched into the same
+ QRhiTextureUploadDescription as opposed to issuing individual
+ \l{QRhiResourceUpdateBatch::uploadTexture()}{uploadTexture()} commands for
+ each of them.
+
+ \note Cubemaps have one layer for each of the six faces in the order +X,
+ -X, +Y, -Y, +Z, -Z.
+
+ For example, specifying the faces of a cubemap could look like the following:
+
+ \badcode
+ QImage faces[6];
+ ...
+ QVector<QRhiTextureUploadEntry> entries;
+ for (int i = 0; i < 6; ++i)
+ entries.append(QRhiTextureUploadEntry(i, 0, faces[i]));
+ QRhiTextureUploadDescription desc(entries);
+ resourceUpdates->uploadTexture(texture, desc);
+ \endcode
+
+ Another example that specifies mip images for a compressed texture:
+
+ \badcode
+ QRhiTextureUploadDescription desc;
+ const int mipCount = rhi->mipLevelsForSize(compressedTexture->pixelSize());
+ for (int level = 0; level < mipCount; ++level) {
+ const QByteArray compressedDataForLevel = ..
+ desc.append(QRhiTextureUploadEntry(0, level, compressedDataForLevel));
+ }
+ resourceUpdates->uploadTexture(compressedTexture, desc);
+ \endcode
+
+ With partial uploads targeting the same subresource, it is recommended to
+ batch them into a single upload request, whenever possible:
+
+ \badcode
+ QRhiTextureSubresourceUploadDescription subresDesc(image);
+ subresDesc.setSourceSize(QSize(10, 10));
+ subResDesc.setDestinationTopLeft(QPoint(50, 40));
+ QRhiTextureUploadEntry entry(0, 0, subresDesc); // layer 0, level 0
+
+ QRhiTextureSubresourceUploadDescription subresDesc2(image);
+ subresDesc2.setSourceSize(QSize(30, 40));
+ subResDesc2.setDestinationTopLeft(QPoint(100, 200));
+ QRhiTextureUploadEntry entry2(0, 0, subresDesc2); // layer 0, level 0, i.e. same subresource
+
+ QRhiTextureUploadDescription desc({ entry, entry2});
+ resourceUpdates->uploadTexture(texture, desc);
+ \endcode
+ */
+
+/*!
+ \fn QRhiTextureUploadDescription::QRhiTextureUploadDescription()
+
+ Constructs an empty texture upload description.
+ */
+
+/*!
+ Constructs a texture upload description with a single subresource upload
+ described by \a entry.
+ */
+QRhiTextureUploadDescription::QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry)
+{
+ m_entries.append(entry);
+}
+
+/*!
+ Constructs a texture upload description with the specified list of \a entries.
+
+ \note \a entries can also contain multiple QRhiTextureUploadEntry elements
+ with the the same layer and level. This makes sense when those uploads are
+ partial, meaning their subresource description has a source size or image
+ smaller than the subresource dimensions, and can be more efficient than
+ issuing separate uploadTexture()'s.
+ */
+QRhiTextureUploadDescription::QRhiTextureUploadDescription(const QVector<QRhiTextureUploadEntry> &entries)
+ : m_entries(entries)
+{
+}
+
+/*!
+ Adds \a entry to the list of subresource uploads.
+ */
+void QRhiTextureUploadDescription::append(const QRhiTextureUploadEntry &entry)
+{
+ m_entries.append(entry);
+}
+
+/*!
+ \class QRhiTextureCopyDescription
+ \inmodule QtRhi
+ \brief Describes a texture-to-texture copy operation.
+
+ An empty pixelSize() indicates that the entire subresource is to be copied.
+ A default constructed copy description therefore leads to copying the
+ entire subresource at level 0 of layer 0.
+
+ \note The source texture must be created with
+ QRhiTexture::UsedAsTransferSource.
+
+ \note The source and destination rectangles defined by pixelSize(),
+ sourceTopLeft(), and destinationTopLeft() must fit the source and
+ destination textures, respectively. The behavior is undefined otherwise.
+ */
+
+/*!
+ \fn QRhiTextureCopyDescription::QRhiTextureCopyDescription()
+
+ Constructs an empty texture copy description.
+ */
+
+/*!
+ \class QRhiReadbackDescription
+ \inmodule QtRhi
+ \brief Describes a readback (reading back texture contents from possibly GPU-only memory) operation.
+
+ The source of the readback operation is either a QRhiTexture or the
+ current backbuffer of the currently targeted QRhiSwapChain. When
+ texture() is not set, the swapchain is used. Otherwise the specified
+ QRhiTexture is treated as the source.
+
+ \note Textures used in readbacks must be created with
+ QRhiTexture::UsedAsTransferSource.
+
+ \note Swapchains used in readbacks must be created with
+ QRhiSwapChain::UsedAsTransferSource.
+
+ layer() and level() are only applicable when the source is a QRhiTexture.
+
+ \note Multisample textures cannot be read back. Readbacks are supported for
+ multisample swapchain buffers however.
+ */
+
+/*!
+ \fn QRhiReadbackDescription::QRhiReadbackDescription()
+
+ Constructs an empty texture readback description.
+
+ \note The source texture is set to null by default, which is still a valid
+ readback: it specifies that the backbuffer of the current swapchain is to
+ be read back. (current meaning the frame's target swapchain at the time of
+ committing the QRhiResourceUpdateBatch with the
+ \l{QRhiResourceUpdateBatch::readBackTexture()}{texture readback} on it)
+ */
+
+/*!
+ Constructs an texture readback description that specifies that level 0 of
+ layer 0 of \a texture is to be read back.
+
+ \note \a texture can also be null in which case this constructor is
+ identical to the argumentless variant.
+ */
+QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
+ : m_texture(texture)
+{
+}
+
+/*!
+ \class QRhiReadbackResult
+ \inmodule QtRhi
+ \brief Describes the results of a potentially asynchronous readback operation.
+
+ When \l completed is set, the function is invoked when the \l data is
+ available. \l format and \l pixelSize are set upon completion together with
+ \l data.
+ */
+
+/*!
+ \class QRhiNativeHandles
+ \inmodule QtRhi
+ \brief Base class for classes exposing backend-specific collections of native resource objects.
+ */
+
+/*!
+ \class QRhiResource
+ \inmodule QtRhi
+ \brief Base class for classes encapsulating native resource objects.
+ */
+
+/*!
+ \fn QRhiResource::Type QRhiResource::resourceType() const
+
+ \return the type of the resource.
+ */
+
+/*!
+ \internal
+ */
+QRhiResource::QRhiResource(QRhiImplementation *rhi)
+ : m_rhi(rhi)
+{
+ m_id = QRhiGlobalObjectIdGenerator::newId();
+}
+
+/*!
+ Destructor.
+
+ Releases (or requests deferred releasing of) the underlying native graphics
+ resources, if there are any.
+
+ \note Resources referenced by commands for the current frame should not be
+ released until the frame is submitted by QRhi::endFrame().
+
+ \sa release()
+ */
+QRhiResource::~QRhiResource()
+{
+ // release() cannot be called here, it being virtual; it is up to the
+ // subclasses to do that.
+}
+
+/*!
+ \fn void QRhiResource::release()
+
+ Releases (or requests deferred releasing of) the underlying native graphics
+ resources. Safe to call multiple times, subsequent invocations will be a
+ no-op then.
+
+ Once release() is called, the QRhiResource instance can be reused, by
+ calling \c build() again. That will then result in creating new native
+ graphics resources underneath.
+
+ \note Resources referenced by commands for the current frame should not be
+ released until the frame is submitted by QRhi::endFrame().
+
+ The QRhiResource destructor also performs the same task, so calling this
+ function is not necessary before destroying a QRhiResource.
+
+ \sa releaseAndDestroyLater()
+ */
+
+/*!
+ When called without a frame being recorded, this function is equivalent to
+ deleting the object. Between a QRhi::beginFrame() and QRhi::endFrame()
+ however the behavior is different: the QRhiResource will not be destroyed
+ until the frame is submitted via QRhi::endFrame(), thus satisfying the QRhi
+ requirement of not altering QRhiResource objects that are referenced by the
+ frame being recorded.
+
+ \sa release()
+ */
+void QRhiResource::releaseAndDestroyLater()
+{
+ m_rhi->addReleaseAndDestroyLater(this);
+}
+
+/*!
+ \return the currently set object name. By default the name is empty.
+ */
+QByteArray QRhiResource::name() const
+{
+ return m_objectName;
+}
+
+/*!
+ Sets a \a name for the object.
+
+ This has two uses: to get descriptive names for the native graphics
+ resources visible in graphics debugging tools, such as
+ \l{https://renderdoc.org/}{RenderDoc} and
+ \l{https://developer.apple.com/xcode/}{XCode}, and in the output stream of
+ QRhiProfiler.
+
+ When it comes to naming native objects by relaying the name via the
+ appropriate graphics API, note that the name is ignored when
+ QRhi::DebugMarkers are not supported, and may, depending on the backend,
+ also be ignored when QRhi::EnableDebugMarkers is not set.
+
+ \note The name may be ignored for objects other than buffers,
+ renderbuffers, and textures, depending on the backend.
+
+ \note The name may be modified. For slotted resources, such as a QRhiBuffer
+ backed by multiple native buffers, QRhi will append a suffix to make the
+ underlying native buffers easily distinguishable from each other.
+ */
+void QRhiResource::setName(const QByteArray &name)
+{
+ m_objectName = name;
+ m_objectName.replace(',', '_'); // cannot contain comma for QRhiProfiler
+}
+
+/*!
+ \return the global, unique identifier of this QRhiResource.
+
+ User code rarely needs to deal with the value directly. It is used
+ internally for tracking and bookkeeping purposes.
+ */
+quint64 QRhiResource::globalResourceId() const
+{
+ return m_id;
+}
+
+/*!
+ \class QRhiBuffer
+ \inmodule QtRhi
+ \brief Vertex, index, or uniform (constant) buffer resource.
+ */
+
+/*!
+ \enum QRhiBuffer::Type
+ Specifies storage type of buffer resource.
+
+ \value Immutable Indicates that the data is not expected to change ever
+ after the initial upload. Under the hood such buffer resources are
+ typically placed in device local (GPU) memory (on systems where
+ applicable). Uploading new data is possible, but may be expensive. The
+ upload typically happens by copying to a separate, host visible staging
+ buffer from which a GPU buffer-to-buffer copy is issued into the actual
+ GPU-only buffer.
+
+ \value Static Indicates that the data is expected to change only
+ infrequently. Typically placed in device local (GPU) memory, where
+ applicable. On backends where host visible staging buffers are used for
+ uploading, the staging buffers are kept around for this type, unlike with
+ Immutable, so subsequent uploads do not suffer in performance. Frequent
+ updates, especially updates in consecutive frames, should be avoided.
+
+ \value Dynamic Indicates that the data is expected to change frequently.
+ Not recommended for large buffers. Typically backed by host visible memory
+ in 2 copies in order to allow for changing without stalling the graphics
+ pipeline. The double buffering is managed transparently to the applications
+ and is not exposed in the API here in any form. This is the recommended,
+ and, with some backends, the only possible, type for buffers with
+ UniformBuffer usage.
+ */
+
+/*!
+ \enum QRhiBuffer::UsageFlag
+ Flag values to specify how the buffer is going to be used.
+
+ \value VertexBuffer Vertex buffer
+ \value IndexBuffer Index buffer
+ \value UniformBuffer Uniform (constant) buffer
+ */
+
+/*!
+ \fn void QRhiBuffer::setSize(int sz)
+
+ Sets the size of the buffer in bytes. The size is normally specified in
+ QRhi::newBuffer() so this function is only used when the size has to be
+ changed. As with other setters, the size only takes effect when calling
+ build(), and for already built buffers this involves releasing the previous
+ native resource and creating new ones under the hood.
+
+ Backends may choose to allocate buffers bigger than \a sz in order to
+ fulfill alignment requirements. This is hidden from the applications and
+ size() will always report the size requested in \a sz.
+ */
+
+/*!
+ \internal
+ */
+QRhiBuffer::QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_)
+ : QRhiResource(rhi),
+ m_type(type_), m_usage(usage_), m_size(size_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiBuffer::resourceType() const
+{
+ return Buffer;
+}
+
+/*!
+ \fn bool QRhiBuffer::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiRenderBuffer
+ \inmodule QtRhi
+ \brief Renderbuffer resource.
+
+ Renderbuffers cannot be sampled or read but have some benefits over
+ textures in some cases:
+
+ A DepthStencil renderbuffer may be lazily allocated and be backed by
+ transient memory with some APIs. On some platforms this may mean the
+ depth/stencil buffer uses no physical backing at all.
+
+ Color renderbuffers are useful since QRhi::MultisampleRenderBuffer may be
+ supported even when QRhi::MultisampleTexture is not.
+
+ How the renderbuffer is implemented by a backend is not exposed to the
+ applications. In some cases it may be backed by ordinary textures, while in
+ others there may be a different kind of native resource used.
+ */
+
+/*!
+ \enum QRhiRenderBuffer::Type
+ Specifies the type of the renderbuffer
+
+ \value DepthStencil Combined depth/stencil
+ \value Color Color
+ */
+
+/*!
+ \enum QRhiRenderBuffer::Flag
+ Flag values for flags() and setFlags()
+
+ \value UsedWithSwapChainOnly For DepthStencil renderbuffers this indicates
+ that the renderbuffer is only used in combination with a QRhiSwapChain and
+ never in other ways. Relevant with some backends, while others ignore it.
+ With OpenGL where a separate windowing system interface API is in use (EGL,
+ GLX, etc.), the flag is important since it avoids creating any actual
+ resource as there is already a windowing system provided depth/stencil
+ buffer as requested by QSurfaceFormat.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderBuffer::QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_)
+ : QRhiResource(rhi),
+ m_type(type_), m_pixelSize(pixelSize_), m_sampleCount(sampleCount_), m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderBuffer::resourceType() const
+{
+ return RenderBuffer;
+}
+
+/*!
+ \fn bool QRhiRenderBuffer::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \fn QRhiTexture::Format QRhiRenderBuffer::backingFormat() const
+
+ \internal
+ */
+
+/*!
+ \class QRhiTexture
+ \inmodule QtRhi
+ \brief Texture resource.
+ */
+
+/*!
+ \enum QRhiTexture::Flag
+
+ Flag values to specify how the texture is going to be used. Not honoring
+ the flags set before build() and attempting to use the texture in ways that
+ was not declared upfront can lead to unspecified behavior or decreased
+ performance depending on the backend and the underlying graphics API.
+
+ \value RenderTarget The texture going to be used in combination with
+ QRhiTextureRenderTarget.
+
+ \value CubeMap The texture is a cubemap. Such textures have 6 layers, one
+ for each face in the order of +X, -X, +Y, -Y, +Z, -Z. Cubemap textures
+ cannot be multisample.
+
+ \value MipMapped The texture has mipmaps. The appropriate mip count is
+ calculated automatically and can also be retrieved via
+ QRhi::mipLevelsForSize(). The images for the mip levels have to be
+ provided in the texture uploaded or generated via
+ QRhiResourceUpdateBatch::generateMips(). Multisample textures cannot have
+ mipmaps.
+
+ \value sRGB Use an sRGB format.
+
+ \value UsedAsTransferSource The texture is used as the source of a texture
+ copy or readback, meaning the texture is given as the source in
+ QRhiResourceUpdateBatch::copyTexture() or
+ QRhiResourceUpdateBatch::readBackTexture().
+
+ \value UsedWithGenerateMips The texture is going to be used with
+ QRhiResourceUpdateBatch::generateMips().
+ */
+
+/*!
+ \enum QRhiTexture::Format
+
+ Specifies the texture format. See also QRhi::isTextureFormatSupported() and
+ note that flags() can modify the format when QRhiTexture::sRGB is set.
+
+ \value UnknownFormat Not a valid format. This cannot be passed to setFormat().
+
+ \value RGBA8 Four component, unsigned normalized 8 bit per component. Always supported.
+
+ \value BGRA8 Four component, unsigned normalized 8 bit per component.
+
+ \value R8 One component, unsigned normalized 8 bit.
+
+ \value R16 One component, unsigned normalized 16 bit.
+
+ \value RED_OR_ALPHA8 Either same as R8, or is a similar format with the component swizzled to alpha,
+ depending on \l{QRhi::RedOrAlpha8IsRed}{RedOrAlpha8IsRed}.
+
+ \value RGBA16F Four components, 16-bit float per component.
+
+ \value RGBA32F Four components, 32-bit float per component.
+
+ \value D16 16-bit depth (normalized unsigned integer)
+
+ \value D32F 32-bit depth (32-bit float)
+
+ \value BC1
+ \value BC2
+ \value BC3
+ \value BC4
+ \value BC5
+ \value BC6H
+ \value BC7
+
+ \value ETC2_RGB8
+ \value ETC2_RGB8A1
+ \value ETC2_RGBA8
+
+ \value ASTC_4x4
+ \value ASTC_5x4
+ \value ASTC_5x5
+ \value ASTC_6x5
+ \value ASTC_6x6
+ \value ASTC_8x5
+ \value ASTC_8x6
+ \value ASTC_8x8
+ \value ASTC_10x5
+ \value ASTC_10x6
+ \value ASTC_10x8
+ \value ASTC_10x10
+ \value ASTC_12x10
+ \value ASTC_12x12
+ */
+
+/*!
+ \internal
+ */
+QRhiTexture::QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_)
+ : QRhiResource(rhi),
+ m_format(format_), m_pixelSize(pixelSize_), m_sampleCount(sampleCount_), m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiTexture::resourceType() const
+{
+ return Texture;
+}
+
+/*!
+ \fn bool QRhiTexture::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \return a pointer to a backend-specific QRhiNativeHandles subclass, such as
+ QRhiVulkanTextureNativeHandles. The returned value is null when exposing
+ the underlying native resources is not supported by the backend.
+
+ \sa QRhiVulkanTextureNativeHandles, QRhiD3D11TextureNativeHandles,
+ QRhiMetalTextureNativeHandles, QRhiGles2TextureNativeHandles
+ */
+const QRhiNativeHandles *QRhiTexture::nativeHandles()
+{
+ return nullptr;
+}
+
+/*!
+ Similar to build() except that no new native textures are created. Instead,
+ the texture from \a src is used.
+
+ This allows importing an existing native texture object (which must belong
+ to the same device or sharing context, depending on the graphics API) from
+ an external graphics engine.
+
+ \note format(), pixelSize(), sampleCount(), and flags() must still be set
+ correctly. Passing incorrect sizes and other values to QRhi::newTexture()
+ and then following it with a buildFrom() expecting that the native texture
+ object alone is sufficient to deduce such values is \b wrong and will lead
+ to problems.
+
+ \note QRhiTexture does not take ownership of the texture object. release()
+ does not free the object or any associated memory.
+
+ The opposite of this operation, exposing a QRhiTexture-created native
+ texture object to a foreign engine, is possible via nativeHandles().
+
+ \sa QRhiVulkanTextureNativeHandles, QRhiD3D11TextureNativeHandles,
+ QRhiMetalTextureNativeHandles, QRhiGles2TextureNativeHandles
+ */
+bool QRhiTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ Q_UNUSED(src);
+ return false;
+}
+
+/*!
+ \class QRhiSampler
+ \inmodule QtRhi
+ \brief Sampler resource.
+ */
+
+/*!
+ \enum QRhiSampler::Filter
+ Specifies the minification, magnification, or mipmap filtering
+
+ \value None Applicable only for mipmapMode(), indicates no mipmaps to be used
+ \value Nearest
+ \value Linear
+ */
+
+/*!
+ \enum QRhiSampler::AddressMode
+ Specifies the addressing mode
+
+ \value Repeat
+ \value ClampToEdge
+ \value Border
+ \value Mirror
+ \value MirrorOnce
+ */
+
+/*!
+ \enum QRhiSampler::CompareOp
+ Specifies the texture comparison function.
+
+ \value Never (default)
+ \value Less
+ \value Equal
+ \value LessOrEqual
+ \value Greater
+ \value NotEqual
+ \value GreaterOrEqual
+ \value Always
+ */
+
+/*!
+ \internal
+ */
+QRhiSampler::QRhiSampler(QRhiImplementation *rhi,
+ Filter magFilter_, Filter minFilter_, Filter mipmapMode_,
+ AddressMode u_, AddressMode v_)
+ : QRhiResource(rhi),
+ m_magFilter(magFilter_), m_minFilter(minFilter_), m_mipmapMode(mipmapMode_),
+ m_addressU(u_), m_addressV(v_),
+ m_addressW(QRhiSampler::ClampToEdge),
+ m_compareOp(QRhiSampler::Never)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiSampler::resourceType() const
+{
+ return Sampler;
+}
+
+/*!
+ \class QRhiRenderPassDescriptor
+ \inmodule QtRhi
+ \brief Render pass resource.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderPassDescriptor::QRhiRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const
+{
+ return RenderPassDescriptor;
+}
+
+/*!
+ \class QRhiRenderTarget
+ \inmodule QtRhi
+ \brief Represents an onscreen (swapchain) or offscreen (texture) render target.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderTarget::QRhiRenderTarget(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderTarget::resourceType() const
+{
+ return RenderTarget;
+}
+
+/*!
+ \fn QSize QRhiRenderTarget::pixelSize() const
+
+ \return the size in pixels.
+ */
+
+/*!
+ \fn float QRhiRenderTarget::devicePixelRatio() const
+
+ \return the device pixel ratio. For QRhiTextureRenderTarget this is always
+ 1. For targets retrieved from a QRhiSwapChain the value reflects the
+ \l{QWindow::devicePixelRatio()}{device pixel ratio} of the targeted
+ QWindow.
+ */
+
+/*!
+ \class QRhiTextureRenderTarget
+ \inmodule QtRhi
+ \brief Texture render target resource.
+
+ A texture render target allows rendering into one or more textures,
+ optionally with a depth texture or depth/stencil renderbuffer.
+
+ \note Textures used in combination with QRhiTextureRenderTarget must be
+ created with the QRhiTexture::RenderTarget flag.
+
+ The simplest example of creating a render target with a texture as its
+ single color attachment:
+
+ \badcode
+ texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget);
+ texture->build();
+ rt = rhi->newTextureRenderTarget({ texture });
+ rp = rt->newCompatibleRenderPassDescriptor();
+ rt->setRenderPassDescriptor(rt);
+ rt->build();
+ // rt can now be used with beginPass()
+ \endcode
+ */
+
+/*!
+ \enum QRhiTextureRenderTarget::Flag
+
+ Flag values describing the load/store behavior for the render target. The
+ load/store behavior may be baked into native resources under the hood,
+ depending on the backend, and therefore it needs to be known upfront and
+ cannot be changed without rebuilding (and so releasing and creating new
+ native resources).
+
+ \value PreserveColorContents Indicates that the contents of the color
+ attachments is to be loaded when starting a render pass, instead of
+ clearing. This is potentially more expensive, especially on mobile (tiled)
+ GPUs, but allows preserving the existing contents between passes.
+
+ \value PreserveDepthStencilContents Indicates that the contents of the
+ depth texture is to be loaded when starting a render pass, instead
+ clearing. Only applicable when a texture is used as the depth buffer
+ (QRhiTextureRenderTargetDescription::depthTexture() is set) because
+ depth/stencil renderbuffers may not have any physical backing and data may
+ not be written out in the first place.
+ */
+
+/*!
+ \internal
+ */
+QRhiTextureRenderTarget::QRhiTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc_,
+ Flags flags_)
+ : QRhiRenderTarget(rhi),
+ m_desc(desc_),
+ m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiTextureRenderTarget::resourceType() const
+{
+ return TextureRenderTarget;
+}
+
+/*!
+ \fn QRhiRenderPassDescriptor *QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor()
+
+ \return a new QRhiRenderPassDescriptor that is compatible with this render
+ target.
+
+ The returned value is used in two ways: it can be passed to
+ setRenderPassDescriptor() and
+ QRhiGraphicsPipeline::setRenderPassDescriptor(). A render pass descriptor
+ describes the attachments (color, depth/stencil) and the load/store
+ behavior that can be affected by flags(). A QRhiGraphicsPipeline can only
+ be used in combination with a render target that has the same
+ QRhiRenderPassDescriptor set.
+
+ Two QRhiTextureRenderTarget instances can share the same render pass
+ descriptor as long as they have the same number and type of attachments.
+ The associated QRhiTexture or QRhiRenderBuffer instances are not part of
+ the render pass descriptor so those can differ in the two
+ QRhiTextureRenderTarget intances.
+
+ \note resources, such as QRhiTexture instances, referenced in description()
+ must already be built
+
+ \sa build()
+ */
+
+/*!
+ \fn bool QRhiTextureRenderTarget::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \note renderPassDescriptor() must be set before calling build(). To obtain
+ a QRhiRenderPassDescriptor compatible with the render target, call
+ newCompatibleRenderPassDescriptor() before build() but after setting all
+ other parameters, such as description() and flags(). To save resources,
+ reuse the same QRhiRenderPassDescriptor with multiple
+ QRhiTextureRenderTarget instances, whenever possible. Sharing the same
+ render pass descriptor is only possible when the render targets have the
+ same number and type of attachments (the actual textures can differ) and
+ the same flags.
+
+ \note resources, such as QRhiTexture instances, referenced in description()
+ must already be built
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiShaderResourceBindings
+ \inmodule QtRhi
+ \brief Encapsulates resources for making buffer, texture, sampler resources visible to shaders.
+
+ A QRhiShaderResourceBindings is a collection of QRhiShaderResourceBinding
+ objects, each of which describe a single binding.
+
+ Take a fragment shader with the following interface:
+
+ \badcode
+ layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ int flip;
+ } ubuf;
+
+ layout(binding = 1) uniform sampler2D tex;
+ \endcode
+
+ To make resources visible to the shader, the following
+ QRhiShaderResourceBindings could be created and then passed to
+ QRhiGraphicsPipeline::setShaderResourceBindings():
+
+ \badcode
+ srb = rhi->newShaderResourceBindings();
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler)
+ });
+ srb->build();
+ ...
+ ps = rhi->newGraphicsPipeline();
+ ...
+ ps->setShaderResourceBindings(srb);
+ ps->build();
+ ...
+ cb->setGraphicsPipeline(ps);
+ cb->setShaderResources(); // binds srb
+ \endcode
+
+ This assumes that \c ubuf is a QRhiBuffer, \c texture is a QRhiTexture,
+ while \a sampler is a QRhiSampler. The example also assumes that the
+ uniform block is present in the vertex shader as well so the same buffer is
+ made visible to the vertex stage too.
+
+ \section3 Advanced usage
+
+ Building on the above example, let's assume that a pass now needs to use
+ the exact same pipeline and shaders with a different texture. Creating a
+ whole separate QRhiGraphicsPipeline just for this would be an overkill.
+ This is why QRhiCommandBuffer::setShaderResources() allows specifying a \a
+ srb argument. As long as the layouts (so the number of bindings and the
+ binding points) match between two QRhiShaderResourceBindings, they can both
+ be used with the same pipeline, assuming the pipeline was built with one of
+ them in the first place.
+
+ Creating and then using a new \c srb2 that is very similar to \c srb with
+ the exception of referencing another texture could be implemented like the
+ following:
+
+ \badcode
+ srb2 = rhi->newShaderResourceBindings();
+ QVector<QRhiShaderResourceBinding> bindings = srb->bindings();
+ bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, anotherTexture, sampler);
+ srb2->setBindings(bindings);
+ srb2->build();
+ ...
+ cb->setGraphicsPipeline(ps);
+ cb->setShaderResources(srb2); // binds srb2
+ \endcode
+ */
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBindings::QRhiShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiShaderResourceBindings::resourceType() const
+{
+ return ShaderResourceBindings;
+}
+
+/*!
+ \return \c true if the layout is compatible with \a other. The layout does
+ not include the actual resource (such as, buffer or texture) and related
+ parameters (such as, offset or size). It does include the binding point,
+ pipeline stage, and resource type, however. The number and order of the
+ bindings must also match in order to be compatible.
+
+ When there is a QRhiGraphicsPipeline created with this
+ QRhiShaderResourceBindings, and the function returns \c true, \a other can
+ then safely be passed to QRhiCommandBuffer::setShaderResources(), and so
+ be used with the pipeline in place of this QRhiShaderResourceBindings.
+
+ This function can be called before build() as well. The bindings must
+ already be set via setBindings() however.
+ */
+bool QRhiShaderResourceBindings::isLayoutCompatible(const QRhiShaderResourceBindings *other) const
+{
+ const int count = m_bindings.count();
+ if (count != other->m_bindings.count())
+ return false;
+
+ for (int i = 0; i < count; ++i) {
+ if (!m_bindings[i].isLayoutCompatible(other->m_bindings.at(i)))
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \class QRhiShaderResourceBinding
+ \inmodule QtRhi
+ \brief Describes the shader resource for a single binding point.
+
+ A QRhiShaderResourceBinding cannot be constructed directly. Instead, use
+ the static functions uniformBuffer(), sampledTexture() to get an instance.
+ */
+
+/*!
+ \enum QRhiShaderResourceBinding::Type
+ Specifies type of the shader resource bound to a binding point
+
+ \value UniformBuffer Uniform buffer
+ \value SampledTexture Combined image sampler
+ */
+
+/*!
+ \enum QRhiShaderResourceBinding::StageFlag
+ Flag values to indicate which stages the shader resource is visible in
+
+ \value VertexStage Vertex stage
+ \value FragmentStage Fragment (pixel) stage
+ */
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding::QRhiShaderResourceBinding()
+ : d(new QRhiShaderResourceBindingPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QRhiShaderResourceBinding::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding::QRhiShaderResourceBinding(const QRhiShaderResourceBinding &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding &QRhiShaderResourceBinding::operator=(const QRhiShaderResourceBinding &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QRhiShaderResourceBinding::~QRhiShaderResourceBinding()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return \c true if the layout is compatible with \a other. The layout does not
+ include the actual resource (such as, buffer or texture) and related
+ parameters (such as, offset or size).
+
+ For example, \c a and \c b below are not equal, but are compatible layout-wise:
+
+ \badcode
+ auto a = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buffer);
+ auto b = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, someOtherBuffer, 256);
+ \endcode
+ */
+bool QRhiShaderResourceBinding::isLayoutCompatible(const QRhiShaderResourceBinding &other) const
+{
+ return (d == other.d)
+ || (d->binding == other.d->binding && d->stage == other.d->stage && d->type == other.d->type);
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer(
+ int binding, StageFlags stage, QRhiBuffer *buf)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.load() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = UniformBuffer;
+ d->u.ubuf.buf = buf;
+ d->u.ubuf.offset = 0;
+ d->u.ubuf.maybeSize = 0; // entire buffer
+ d->u.ubuf.hasDynamicOffset = false;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf. This
+ overload binds a region only, as specified by \a offset and \a size.
+
+ \note It is up to the user to ensure the offset is aligned to
+ QRhi::ubufAlignment().
+
+ \note \a size must be greater than 0.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer(
+ int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size)
+{
+ Q_ASSERT(size > 0);
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.load() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = UniformBuffer;
+ d->u.ubuf.buf = buf;
+ d->u.ubuf.offset = offset;
+ d->u.ubuf.maybeSize = size;
+ d->u.ubuf.hasDynamicOffset = false;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf. The
+ uniform buffer is assumed to have dynamic offset. The dynamic offset can be
+ specified in QRhiCommandBuffer::setShaderResources(), thus allowing using
+ varying offset values without creating new bindings for the buffer. The
+ size of the bound region is specified by \a size. Like with non-dynamic
+ offsets, \c{offset + size} cannot exceed the size of \a buf.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
+ int binding, StageFlags stage, QRhiBuffer *buf, int size)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.load() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = UniformBuffer;
+ d->u.ubuf.buf = buf;
+ d->u.ubuf.offset = 0;
+ d->u.ubuf.maybeSize = size;
+ d->u.ubuf.hasDynamicOffset = true;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, texture, and sampler specified by \a binding, \a stage, \a tex,
+ \a sampler.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture(
+ int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.load() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = SampledTexture;
+ d->u.stex.tex = tex;
+ d->u.stex.sampler = sampler;
+ return b;
+}
+
+/*!
+ \return \c true if the contents of the two QRhiShaderResourceBinding
+ objects \a a and \a b are equal. This includes the resources (buffer,
+ texture) and related parameters (offset, size) as well. To only compare
+ layouts (binding point, pipeline stage, resource type), use
+ \l{QRhiShaderResourceBinding::isLayoutCompatible()}{isLayoutCompatible()}
+ instead.
+
+ \relates QRhiShaderResourceBinding
+ */
+bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
+{
+ if (a.d == b.d)
+ return true;
+
+ if (a.d->binding != b.d->binding
+ || a.d->stage != b.d->stage
+ || a.d->type != b.d->type)
+ {
+ return false;
+ }
+
+ switch (a.d->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (a.d->u.ubuf.buf != b.d->u.ubuf.buf
+ || a.d->u.ubuf.offset != b.d->u.ubuf.offset
+ || a.d->u.ubuf.maybeSize != b.d->u.ubuf.maybeSize)
+ {
+ return false;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ if (a.d->u.stex.tex != b.d->u.stex.tex
+ || a.d->u.stex.sampler != b.d->u.stex.sampler)
+ {
+ return false;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \return \c false if all the bindings in the two QRhiShaderResourceBinding
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiShaderResourceBinding
+ */
+bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a b, using \a seed to seed the calculation.
+
+ \relates QRhiShaderResourceBinding
+ */
+uint qHash(const QRhiShaderResourceBinding &b, uint seed) Q_DECL_NOTHROW
+{
+ const char *u = reinterpret_cast<const char *>(&b.d->u);
+ return seed + b.d->binding + 10 * b.d->stage + 100 * b.d->type
+ + qHash(QByteArray::fromRawData(u, sizeof(b.d->u)), seed);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b)
+{
+ const QRhiShaderResourceBindingPrivate *d = b.d;
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiShaderResourceBinding("
+ << "binding=" << d->binding
+ << " stage=" << d->stage
+ << " type=" << d->type;
+ switch (d->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ dbg.nospace() << " UniformBuffer("
+ << "buffer=" << d->u.ubuf.buf
+ << " offset=" << d->u.ubuf.offset
+ << " maybeSize=" << d->u.ubuf.maybeSize
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ dbg.nospace() << " SampledTexture("
+ << "texture=" << d->u.stex.tex
+ << " sampler=" << d->u.stex.sampler
+ << ')';
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ dbg.nospace() << ')';
+ return dbg;
+}
+#endif
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiShaderResourceBindings("
+ << srb.m_bindings
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiGraphicsPipeline
+ \inmodule QtRhi
+ \brief Graphics pipeline state resource.
+
+ \note Setting the shader resource bindings is mandatory. The referenced
+ QRhiShaderResourceBindings must already be built by the time build() is
+ called.
+
+ \note Setting the render pass descriptor is mandatory. To obtain a
+ QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(),
+ use either QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() or
+ QRhiSwapChain::newCompatibleRenderPassDescriptor().
+
+ \note Setting the vertex input layout is mandatory.
+
+ \note Setting the shader stages is mandatory.
+
+ \note sampleCount() defaults to 1 and must match the sample count of the
+ render target's color and depth stencil attachments.
+
+ \note The depth test, depth write, and stencil test are disabled by
+ default.
+
+ \note stencilReadMask() and stencilWriteMask() apply to both faces. They
+ both default to 0xFF.
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setTargetBlends(const QVector<TargetBlend> &blends)
+
+ Sets the blend specification for color attachments. Each element in \a
+ blends corresponds to a color attachment of the render target.
+
+ By default no blends are set, which is a shortcut to disabling blending and
+ enabling color write for all four channels.
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::Flag
+
+ Flag values for describing the dynamic state of the pipeline. The viewport is always dynamic.
+
+ \value UsesBlendConstants Indicates that a blend color constant will be set
+ via QRhiCommandBuffer::setBlendConstants()
+
+ \value UsesStencilRef Indicates that a stencil reference value will be set
+ via QRhiCommandBuffer::setStencilRef()
+
+ \value UsesScissor Indicates that a scissor rectangle will be set via
+ QRhiCommandBuffer::setScissor()
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::Topology
+ Specifies the primitive topology
+
+ \value Triangles (default)
+ \value TriangleStrip
+ \value Lines
+ \value LineStrip
+ \value Points
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::CullMode
+ Specifies the culling mode
+
+ \value None No culling (default)
+ \value Front Cull front faces
+ \value Back Cull back faces
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::FrontFace
+ Specifies the front face winding order
+
+ \value CCW Counter clockwise (default)
+ \value CW Clockwise
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::ColorMaskComponent
+ Flag values for specifying the color write mask
+
+ \value R
+ \value G
+ \value B
+ \value A
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::BlendFactor
+ Specifies the blend factor
+
+ \value Zero
+ \value One
+ \value SrcColor
+ \value OneMinusSrcColor
+ \value DstColor
+ \value OneMinusDstColor
+ \value SrcAlpha
+ \value OneMinusSrcAlpha
+ \value DstAlpha
+ \value OneMinusDstAlpha
+ \value ConstantColor
+ \value OneMinusConstantColor
+ \value ConstantAlpha
+ \value OneMinusConstantAlpha
+ \value SrcAlphaSaturate
+ \value Src1Color
+ \value OneMinusSrc1Color
+ \value Src1Alpha
+ \value OneMinusSrc1Alpha
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::BlendOp
+ Specifies the blend operation
+
+ \value Add
+ \value Subtract
+ \value ReverseSubtract
+ \value Min
+ \value Max
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::CompareOp
+ Specifies the depth or stencil comparison function
+
+ \value Never
+ \value Less (default for depth)
+ \value Equal
+ \value LessOrEqual
+ \value Greater
+ \value NotEqual
+ \value GreaterOrEqual
+ \value Always (default for stencil)
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::StencilOp
+ Specifies the stencil operation
+
+ \value StencilZero
+ \value Keep (default)
+ \value Replace
+ \value IncrementAndClamp
+ \value DecrementAndClamp
+ \value Invert
+ \value IncrementAndWrap
+ \value DecrementAndWrap
+ */
+
+/*!
+ \class QRhiGraphicsPipeline::TargetBlend
+ \inmodule QtRhi
+ \brief Describes the blend state for one color attachment.
+
+ Defaults to color write enabled, blending disabled. The blend values are
+ set up for pre-multiplied alpha (One, OneMinusSrcAlpha, One,
+ OneMinusSrcAlpha) by default.
+ */
+
+/*!
+ \class QRhiGraphicsPipeline::StencilOpState
+ \inmodule QtRhi
+ \brief Describes the stencil operation state.
+ */
+
+/*!
+ \internal
+ */
+QRhiGraphicsPipeline::QRhiGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
+{
+ return GraphicsPipeline;
+}
+
+/*!
+ \fn bool QRhiGraphicsPipeline::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthTest(bool enable)
+
+ Enables or disables depth testing. Both depth test and the writing out of
+ depth data are disabled by default.
+
+ \sa setDepthWrite()
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthWrite(bool enable)
+
+ Controls the writing out of depth data into the depth buffer. By default
+ this is disabled. Depth write is typically enabled together with the depth
+ test.
+
+ \note Enabling depth write without having depth testing enabled may not
+ lead to the desired result, and should be avoided.
+
+ \sa setDepthTest()
+ */
+
+/*!
+ \class QRhiSwapChain
+ \inmodule QtRhi
+ \brief Swapchain resource.
+
+ A swapchain enables presenting rendering results to a surface. A swapchain
+ is typically backed by a set of color buffers. Of these, one is displayed
+ at a time.
+
+ Below is a typical pattern for creating and managing a swapchain and some
+ associated resources in order to render onto a QWindow:
+
+ \badcode
+ void init()
+ {
+ sc = rhi->newSwapChain();
+ ds = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
+ QSize(), // no need to set the size yet
+ 1,
+ QRhiRenderBuffer::UsedWithSwapChainOnly);
+ sc->setWindow(window);
+ sc->setDepthStencil(ds);
+ rp = sc->newCompatibleRenderPassDescriptor();
+ sc->setRenderPassDescriptor(rp);
+ resizeSwapChain();
+ }
+
+ void resizeSwapChain()
+ {
+ const QSize outputSize = sc->surfacePixelSize();
+ ds->setPixelSize(outputSize);
+ ds->build();
+ hasSwapChain = sc->buildOrResize();
+ }
+
+ void render()
+ {
+ if (!hasSwapChain || notExposed)
+ return;
+
+ if (sc->currentPixelSize() != sc->surfacePixelSize() || newlyExposed) {
+ resizeSwapChain();
+ if (!hasSwapChain)
+ return;
+ newlyExposed = false;
+ }
+
+ rhi->beginFrame(sc);
+ // ...
+ rhi->endFrame(sc);
+ }
+ \endcode
+
+ Avoid relying on QWindow resize events to resize swapchains, especially
+ considering that surface sizes may not always fully match the QWindow
+ reported dimensions. The safe, cross-platform approach is to do the check
+ via surfacePixelSize() whenever starting a new frame.
+
+ Releasing the swapchain must happen while the QWindow and the underlying
+ native window is fully up and running. Building on the previous example:
+
+ \badcode
+ void releaseSwapChain()
+ {
+ if (hasSwapChain) {
+ sc->release();
+ hasSwapChain = false;
+ }
+ }
+
+ // assuming Window is our QWindow subclass
+ bool Window::event(QEvent *e)
+ {
+ switch (e->type()) {
+ case QEvent::UpdateRequest: // for QWindow::requestUpdate()
+ render();
+ break;
+ case QEvent::PlatformSurface:
+ if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ releaseSwapChain();
+ break;
+ default:
+ break;
+ }
+ return QWindow::event(e);
+ }
+ \endcode
+
+ Initializing the swapchain and starting to render the first frame cannot
+ start at any time. The safe, cross-platform approach is to rely on expose
+ events. QExposeEvent is a loosely specified event that is sent whenever a
+ window gets mapped, obscured, and resized, depending on the platform.
+
+ \badcode
+ void Window::exposeEvent(QExposeEvent *)
+ {
+ // initialize and start rendering when the window becomes usable for graphics purposes
+ if (isExposed() && !running) {
+ running = true;
+ init();
+ }
+
+ // stop pushing frames when not exposed or size becomes 0
+ if ((!isExposed() || (hasSwapChain && sc->surfacePixelSize().isEmpty())) && running)
+ notExposed = true;
+
+ // continue when exposed again and the surface has a valid size
+ if (isExposed() && running && notExposed && !sc->surfacePixelSize().isEmpty()) {
+ notExposed = false;
+ newlyExposed = true;
+ }
+
+ if (isExposed() && !sc->surfacePixelSize().isEmpty())
+ render();
+ }
+ \endcode
+
+ Once the rendering has started, a simple way to request a new frame is
+ QWindow::requestUpdate(). While on some platforms this is merely a small
+ timer, on others it has a specific implementation: for instance on macOS or
+ iOS it may be backed by
+ \l{https://developer.apple.com/documentation/corevideo/cvdisplaylink?language=objc}{CVDisplayLink}.
+ The example above is already prepared for update requests by handling
+ QEvent::UpdateRequest.
+
+ While acting as a QRhiRenderTarget, QRhiSwapChain also manages a
+ QRhiCommandBuffer. Calling QRhi::endFrame() submits the recorded commands
+ and also enqueues a \c present request. The default behavior is to do this
+ with a swap interval of 1, meaning synchronizing to the display's vertical
+ refresh is enabled. Thus the rendering thread calling beginFrame() and
+ endFrame() will get throttled to vsync. On some backends this can be
+ disabled by passing QRhiSwapChain:NoVSync in flags().
+
+ Multisampling (MSAA) is handled transparently to the applications when
+ requested via setSampleCount(). Where applicable, QRhiSwapChain will take
+ care of creating additional color buffers and issuing a multisample resolve
+ command at the end of a frame. For OpenGL, it is necessary to request the
+ appropriate sample count also via QSurfaceFormat, by calling
+ QSurfaceFormat::setDefaultFormat() before initializing the QRhi.
+ */
+
+/*!
+ \enum QRhiSwapChain::Flag
+ Flag values to describe swapchain properties
+
+ \value SurfaceHasPreMulAlpha Indicates that the target surface has
+ transparency with premultiplied alpha.
+
+ \value SurfaceHasNonPreMulAlpha Indicates the target surface has
+ transparencyt with non-premultiplied alpha.
+
+ \value sRGB Requests to pick an sRGB format for the swapchain and/or its
+ render target views, where applicable. Note that this implies that sRGB
+ framebuffer update and blending will get enabled for all content targeting
+ this swapchain, and opting out is not possible. For OpenGL, set
+ \l{QSurfaceFormat::sRGBColorSpace}{sRGBColorSpace} on the QSurfaceFormat of
+ the QWindow in addition.
+
+ \value UsedAsTransferSource Indicates the the swapchain will be used as the
+ source of a readback in QRhiResourceUpdateBatch::readBackTexture().
+
+ \value NoVSync Requests disabling waiting for vertical sync, also avoiding
+ throttling the rendering thread. The behavior is backend specific and
+ applicable only where it is possible to control this. Some may ignore the
+ request altogether. For OpenGL, try instead setting the swap interval to 0
+ on the QWindow via QSurfaceFormat::setSwapInterval().
+
+ \value MinimalBufferCount Requests creating the swapchain with the minimum
+ number of buffers, which is in practice 2, unless the graphics
+ implementation has a higher minimum number than that. Only applicable with
+ backends where such control is available via the graphics API, for example,
+ Vulkan. By default it is up to the backend to decide what number of buffers
+ it requests (in practice this is almost always either 2 or 3), and it is
+ not the applications' concern. However, on Vulkan for instance the backend
+ will likely prefer the higher number (3), for example to avoid odd
+ performance issues with some Vulkan implementations on mobile devices. It
+ could be that on some platforms it can prove to be beneficial to force the
+ lower buffer count (2), so this flag allows forcing that. Note that all
+ this has no effect on the number of frames kept in flight, so the CPU
+ (QRhi) will still prepare frames at most \c{N - 1} frames ahead of the GPU,
+ even when the swapchain image buffer count larger than \c N. (\c{N} =
+ QRhi::FramesInFlight and typically 2).
+ */
+
+/*!
+ \internal
+ */
+QRhiSwapChain::QRhiSwapChain(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiSwapChain::resourceType() const
+{
+ return SwapChain;
+}
+
+/*!
+ \fn QSize QRhiSwapChain::currentPixelSize() const
+
+ \return the size with which the swapchain was last successfully built. Use
+ this to decide if buildOrResize() needs to be called again: if
+ \c{currentPixelSize() != surfacePixelSize()} then the swapchain needs to be
+ resized.
+
+ \sa surfacePixelSize()
+ */
+
+/*!
+ \fn QSize QRhiSwapChain::surfacePixelSize()
+
+ \return The size of the window's associated surface or layer. Do not assume
+ this is the same as QWindow::size() * QWindow::devicePixelRatio().
+
+ Can be called before buildOrResize() (but with window() already set), which
+ allows setting the correct size for the depth-stencil buffer that is then
+ used together with the swapchain's color buffers. Also used in combination
+ with currentPixelSize() to detect size changes.
+
+ \sa currentPixelSize()
+ */
+
+/*!
+ \fn QRhiCommandBuffer *QRhiSwapChain::currentFrameCommandBuffer()
+
+ \return a command buffer on which rendering commands can be recorded. Only
+ valid within a QRhi::beginFrame() - QRhi::endFrame() block where
+ beginFrame() was called with this swapchain.
+
+ \note the value must not be cached and reused between frames
+*/
+
+/*!
+ \fn QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget()
+
+ \return a render target that can used with beginPass() in order to render
+ the the swapchain's current backbuffer. Only valid within a
+ QRhi::beginFrame() - QRhi::endFrame() block where beginFrame() was called
+ with this swapchain.
+
+ \note the value must not be cached and reused between frames
+ */
+
+/*!
+ \fn bool QRhiSwapChain::buildOrResize()
+
+ Creates the swapchain if not already done and resizes the swapchain buffers
+ to match the current size of the targeted surface. Call this whenever the
+ size of the target surface is different than before.
+
+ \note call release() only when the swapchain needs to be released
+ completely, typically upon
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed. To perform resizing, just
+ call buildOrResize().
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiCommandBuffer
+ \inmodule QtRhi
+ \brief Command buffer resource.
+
+ Not creatable by applications at the moment. The only ways to obtain a
+ valid QRhiCommandBuffer are to get it from the targeted swapchain via
+ QRhiSwapChain::currentFrameCommandBuffer(), or, in case of rendering
+ completely offscreen, initializing one via QRhi::beginOffscreenFrame().
+ */
+
+/*!
+ \enum QRhiCommandBuffer::IndexFormat
+ Specifies the index data type
+
+ \value IndexUInt16 Unsigned 16-bit (quint16)
+ \value IndexUInt32 Unsigned 32-bit (quint32)
+ */
+
+/*!
+ \typedef QRhiCommandBuffer::DynamicOffset
+
+ Synonym for QPair<int, quint32>. The first entry is the binding, the second
+ is the offset in the buffer.
+*/
+
+/*!
+ \typedef QRhiCommandBuffer::VertexInput
+
+ Synonym for QPair<QRhiBuffer *, quint32>. The second entry is an offset in
+ the buffer specified by the first.
+*/
+
+/*!
+ \internal
+ */
+QRhiCommandBuffer::QRhiCommandBuffer(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiCommandBuffer::resourceType() const
+{
+ return CommandBuffer;
+}
+
+QRhiImplementation::~QRhiImplementation()
+{
+ qDeleteAll(resUpdPool);
+
+ // Be nice and show something about leaked stuff. Though we may not get
+ // this far with some backends where the allocator or the api may check
+ // and freak out for unfreed graphics objects in the derived dtor already.
+#ifndef QT_NO_DEBUG
+ if (!resources.isEmpty()) {
+ qWarning("QRhi %p going down with %d unreleased resources. This is not nice.",
+ q, resources.count());
+ for (QRhiResource *res : qAsConst(resources)) {
+ qWarning(" Resource %p (%s)", res, res->m_objectName.constData());
+ res->m_rhi = nullptr;
+ }
+ }
+#endif
+}
+
+bool QRhiImplementation::isCompressedFormat(QRhiTexture::Format format) const
+{
+ return (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
+ || (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
+ || (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12);
+}
+
+void QRhiImplementation::compressedFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize,
+ QSize *blockDim) const
+{
+ int xdim = 4;
+ int ydim = 4;
+ quint32 blockSize = 0;
+
+ switch (format) {
+ case QRhiTexture::BC1:
+ blockSize = 8;
+ break;
+ case QRhiTexture::BC2:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC3:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC4:
+ blockSize = 8;
+ break;
+ case QRhiTexture::BC5:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC6H:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC7:
+ blockSize = 16;
+ break;
+
+ case QRhiTexture::ETC2_RGB8:
+ blockSize = 8;
+ break;
+ case QRhiTexture::ETC2_RGB8A1:
+ blockSize = 8;
+ break;
+ case QRhiTexture::ETC2_RGBA8:
+ blockSize = 16;
+ break;
+
+ case QRhiTexture::ASTC_4x4:
+ blockSize = 16;
+ break;
+ case QRhiTexture::ASTC_5x4:
+ blockSize = 16;
+ xdim = 5;
+ break;
+ case QRhiTexture::ASTC_5x5:
+ blockSize = 16;
+ xdim = ydim = 5;
+ break;
+ case QRhiTexture::ASTC_6x5:
+ blockSize = 16;
+ xdim = 6;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_6x6:
+ blockSize = 16;
+ xdim = ydim = 6;
+ break;
+ case QRhiTexture::ASTC_8x5:
+ blockSize = 16;
+ xdim = 8;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_8x6:
+ blockSize = 16;
+ xdim = 8;
+ ydim = 6;
+ break;
+ case QRhiTexture::ASTC_8x8:
+ blockSize = 16;
+ xdim = ydim = 8;
+ break;
+ case QRhiTexture::ASTC_10x5:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_10x6:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 6;
+ break;
+ case QRhiTexture::ASTC_10x8:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 8;
+ break;
+ case QRhiTexture::ASTC_10x10:
+ blockSize = 16;
+ xdim = ydim = 10;
+ break;
+ case QRhiTexture::ASTC_12x10:
+ blockSize = 16;
+ xdim = 12;
+ ydim = 10;
+ break;
+ case QRhiTexture::ASTC_12x12:
+ blockSize = 16;
+ xdim = ydim = 12;
+ break;
+
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ const quint32 wblocks = (size.width() + xdim - 1) / xdim;
+ const quint32 hblocks = (size.height() + ydim - 1) / ydim;
+
+ if (bpl)
+ *bpl = wblocks * blockSize;
+ if (byteSize)
+ *byteSize = wblocks * hblocks * blockSize;
+ if (blockDim)
+ *blockDim = QSize(xdim, ydim);
+}
+
+void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize) const
+{
+ if (isCompressedFormat(format)) {
+ compressedFormatInfo(format, size, bpl, byteSize, nullptr);
+ return;
+ }
+
+ quint32 bpc = 0;
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ bpc = 4;
+ break;
+ case QRhiTexture::BGRA8:
+ bpc = 4;
+ break;
+ case QRhiTexture::R8:
+ bpc = 1;
+ break;
+ case QRhiTexture::R16:
+ bpc = 2;
+ break;
+ case QRhiTexture::RED_OR_ALPHA8:
+ bpc = 1;
+ break;
+
+ case QRhiTexture::RGBA16F:
+ bpc = 8;
+ break;
+ case QRhiTexture::RGBA32F:
+ bpc = 16;
+ break;
+
+ case QRhiTexture::D16:
+ bpc = 2;
+ break;
+ case QRhiTexture::D32F:
+ bpc = 4;
+ break;
+
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ if (bpl)
+ *bpl = size.width() * bpc;
+ if (byteSize)
+ *byteSize = size.width() * size.height() * bpc;
+}
+
+// Approximate because it excludes subresource alignment or multisampling.
+quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format, const QSize &baseSize,
+ int mipCount, int layerCount)
+{
+ quint32 approxSize = 0;
+ for (int level = 0; level < mipCount; ++level) {
+ quint32 byteSize = 0;
+ const QSize size(qFloor(float(qMax(1, baseSize.width() >> level))),
+ qFloor(float(qMax(1, baseSize.height() >> level))));
+ textureFormatInfo(format, size, nullptr, &byteSize);
+ approxSize += byteSize;
+ }
+ approxSize *= layerCount;
+ return approxSize;
+}
+
+/*!
+ \internal
+ */
+QRhi::QRhi()
+{
+}
+
+/*!
+ Destructor. Destroys the backend and releases resources.
+ */
+QRhi::~QRhi()
+{
+ if (!d)
+ return;
+
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ runCleanup();
+
+ d->destroy();
+ delete d;
+}
+
+/*!
+ \return a new QRhi instance with a backend for the graphics API specified by \a impl.
+
+ \a params must point to an instance of one of the backend-specific
+ subclasses of QRhiInitParams, such as, QRhiVulkanInitParams,
+ QRhiMetalInitParams, QRhiD3D11InitParams, QRhiGles2InitParams. See these
+ classes for examples on creating a QRhi.
+
+ \a flags is optional. It is used to enable profile and debug related
+ features that are potentially expensive and should only be used during
+ development.
+ */
+QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRhiNativeHandles *importDevice)
+{
+ QScopedPointer<QRhi> r(new QRhi);
+
+ switch (impl) {
+ case Null:
+ r->d = new QRhiNull(static_cast<QRhiNullInitParams *>(params));
+ break;
+ case Vulkan:
+#if QT_CONFIG(vulkan)
+ r->d = new QRhiVulkan(static_cast<QRhiVulkanInitParams *>(params),
+ static_cast<QRhiVulkanNativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This build of Qt has no Vulkan support");
+ break;
+#endif
+ case OpenGLES2:
+#ifndef QT_NO_OPENGL
+ r->d = new QRhiGles2(static_cast<QRhiGles2InitParams *>(params),
+ static_cast<QRhiGles2NativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This build of Qt has no OpenGL support");
+ break;
+#endif
+ case D3D11:
+#ifdef Q_OS_WIN
+ r->d = new QRhiD3D11(static_cast<QRhiD3D11InitParams *>(params),
+ static_cast<QRhiD3D11NativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This platform has no Direct3D 11 support");
+ break;
+#endif
+ case Metal:
+//#ifdef Q_OS_DARWIN
+#ifdef Q_OS_MACOS
+ r->d = new QRhiMetal(static_cast<QRhiMetalInitParams *>(params),
+ static_cast<QRhiMetalNativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This platform has no Metal support");
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (r->d) {
+ r->d->q = r.data();
+ if (flags.testFlag(EnableProfiling)) {
+ QRhiProfilerPrivate *profD = QRhiProfilerPrivate::get(&r->d->profiler);
+ profD->rhiDWhenEnabled = r->d;
+ }
+ r->d->debugMarkers = flags.testFlag(EnableDebugMarkers);
+ if (r->d->create(flags)) {
+ r->d->implType = impl;
+ r->d->implThread = QThread::currentThread();
+ return r.take();
+ }
+ }
+
+ return nullptr;
+}
+
+/*!
+ \return the backend type for this QRhi.
+ */
+QRhi::Implementation QRhi::backend() const
+{
+ return d->implType;
+}
+
+/*!
+ \return the thread on which the QRhi was \l{QRhi::create()}{initialized}.
+ */
+QThread *QRhi::thread() const
+{
+ return d->implThread;
+}
+
+/*!
+ Registers a \a callback that is invoked either when the QRhi is destroyed,
+ or when runCleanup() is called.
+
+ The callback will run with the graphics resource still available, so this
+ provides an opportunity for the application to cleanly release QRhiResource
+ instances belonging to the QRhi. This is particularly useful for managing
+ the lifetime of resources stored in \c cache type of objects, where the
+ cache holds QRhiResources or objects containing QRhiResources.
+
+ \sa runCleanup(), ~QRhi()
+ */
+void QRhi::addCleanupCallback(const CleanupCallback &callback)
+{
+ d->addCleanupCallback(callback);
+}
+
+/*!
+ Invokes all registered cleanup functions. The list of cleanup callbacks it
+ then cleared. Normally destroying the QRhi does this automatically, but
+ sometimes it can be useful to trigger cleanup in order to release all
+ cached, non-essential resources.
+
+ \sa addCleanupCallback()
+ */
+void QRhi::runCleanup()
+{
+ for (const CleanupCallback &f : qAsConst(d->cleanupCallbacks))
+ f(this);
+
+ d->cleanupCallbacks.clear();
+}
+
+/*!
+ \class QRhiResourceUpdateBatch
+ \inmodule QtRhi
+ \brief Records upload and copy type of operations.
+
+ With QRhi it is no longer possible to perform copy type of operations at
+ arbitrary times. Instead, all such operations are recorded into batches
+ that are then passed, most commonly, to QRhiCommandBuffer::beginPass().
+ What then happens under the hood is hidden from the application: the
+ underlying implementations can defer and implement these operations in
+ various different ways.
+
+ A resource update batch owns no graphics resources and does not perform any
+ actual operations on its own. It should rather be viewed as a command
+ buffer for update, upload, and copy type of commands.
+
+ To get an available, empty batch from the pool, call
+ QRhi::nextResourceUpdateBatch().
+ */
+
+/*!
+ \internal
+ */
+QRhiResourceUpdateBatch::QRhiResourceUpdateBatch(QRhiImplementation *rhi)
+ : d(new QRhiResourceUpdateBatchPrivate)
+{
+ d->q = this;
+ d->rhi = rhi;
+}
+
+QRhiResourceUpdateBatch::~QRhiResourceUpdateBatch()
+{
+ delete d;
+}
+
+/*!
+ \return the batch to the pool. This should only be used when the batch is
+ not passed to one of QRhiCommandBuffer::beginPass(),
+ QRhiCommandBuffer::endPass(), or QRhiCommandBuffer::resourceUpdate()
+ because these implicitly call release().
+
+ \note QRhiResourceUpdateBatch instances must never by \c deleted by
+ applications.
+ */
+void QRhiResourceUpdateBatch::release()
+{
+ d->free();
+}
+
+/*!
+ Copies all queued operations from the \a other batch into this one.
+
+ \note \a other is not changed in any way, typically it will still need a
+ release()
+
+ This allows for a convenient pattern where resource updates that are
+ already known during the initialization step are collected into a batch
+ that is then merged into another when starting to first render pass later
+ on:
+
+ \badcode
+ void init()
+ {
+ ...
+ initialUpdates = rhi->nextResourceUpdateBatch();
+ initialUpdates->uploadStaticBuffer(vbuf, vertexData);
+ initialUpdates->uploadStaticBuffer(ibuf, indexData);
+ ...
+ }
+
+ void render()
+ {
+ ...
+ QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch();
+ if (initialUpdates) {
+ resUpdates->merge(initialUpdates);
+ initialUpdates->release();
+ initialUpdates = nullptr;
+ }
+ resUpdates->updateDynamicBuffer(...);
+ ...
+ cb->beginPass(rt, clearCol, clearDs, resUpdates);
+ }
+ \endcode
+ */
+void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other)
+{
+ d->merge(other->d);
+}
+
+/*!
+ Enqueues updating a region of a QRhiBuffer \a buf created with the type
+ QRhiBuffer::Dynamic.
+
+ The region is specified \a offset and \a size. The actual bytes to write
+ are specified by \a data which must have at least \a size bytes available.
+ \a data can safely be destroyed or changed once this function returns.
+
+ \note If host writes are involved, which is the case with
+ updateDynamicBuffer() typically as such buffers are backed by host visible
+ memory with most backends, they may accumulate within a frame. Thus pass 1
+ reading a region changed by a batch passed to pass 2 may see the changes
+ specified in pass 2's update batch.
+
+ \note QRhi transparently manages double buffering in order to prevent
+ stalling the graphics pipeline. The fact that a QRhiBuffer may have
+ multiple native underneath can be safely ignored when using the QRhi and
+ QRhiResourceUpdateBatch.
+ */
+void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
+{
+ if (size > 0)
+ d->dynamicBufferUpdates.append({ buf, offset, size, data });
+}
+
+/*!
+ Enqueues updating a region of a QRhiBuffer \a buf created with the type
+ QRhiBuffer::Immutable or QRhiBuffer::Static.
+
+ The region is specified \a offset and \a size. The actual bytes to write
+ are specified by \a data which must have at least \a size bytes available.
+ \a data can safely be destroyed or changed once this function returns.
+ */
+void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
+{
+ if (size > 0)
+ d->staticBufferUploads.append({ buf, offset, size, data });
+}
+
+/*!
+ Enqueues updating the entire QRhiBuffer \a buf created with the type
+ QRhiBuffer::Immutable or QRhiBuffer::Static.
+ */
+void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *data)
+{
+ if (buf->size() > 0)
+ d->staticBufferUploads.append({ buf, 0, 0, data });
+}
+
+/*!
+ Enqueues uploading the image data for one or more mip levels in one or more
+ layers of the texture \a tex.
+
+ The details of the copy (source QImage or compressed texture data, regions,
+ target layers and levels) are described in \a desc.
+ */
+void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
+{
+ if (!desc.entries().isEmpty())
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureUpload(tex, desc));
+}
+
+/*!
+ Enqueues uploading the image data for mip level 0 of layer 0 of the texture
+ \a tex.
+
+ \a tex must have an uncompressed format. Its format must also be compatible
+ with the QImage::format() of \a image. The source data is given in \a
+ image.
+ */
+void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QImage &image)
+{
+ uploadTexture(tex, QRhiTextureUploadEntry(0, 0, image));
+}
+
+/*!
+ Enqueues a texture-to-texture copy operation from \a src into \a dst as
+ described by \a desc.
+
+ \note The source texture \a src must be created with
+ QRhiTexture::UsedAsTransferSource.
+ */
+void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureCopy(dst, src, desc));
+}
+
+/*!
+ Enqueues a texture-to-host copy operation as described by \a rb.
+
+ Normally \a rb will specify a QRhiTexture as the source. However, when the
+ swapchain in the current frame was created with
+ QRhiSwapChain::UsedAsTransferSource, it can also be the source of the
+ readback. For this, leave the texture set to null in \a rb.
+
+ Unlike other operations, the results here need to be processed by the
+ application. Therefore, \a result provides not just the data but also a
+ callback as operations on the batch are asynchronous by nature:
+
+ \badcode
+ beginFrame(sc);
+ beginPass
+ ...
+ QRhiReadbackResult *rbResult = new QRhiReadbackResult;
+ rbResult->completed = [rbResult] {
+ {
+ const QImage::Format fmt = QImage::Format_RGBA8888_Premultiplied; // fits QRhiTexture::RGBA8
+ const uchar *p = reinterpret_cast<const uchar *>(rbResult->data.constData());
+ QImage image(p, rbResult->pixelSize.width(), rbResult->pixelSize.height(), fmt);
+ image.save("result.png");
+ }
+ delete rbResult;
+ };
+ u = nextResourceUpdateBatch();
+ QRhiReadbackDescription rb; // no texture -> uses the current backbuffer of sc
+ u->readBackTexture(rb, rbResult);
+ endPass(u);
+ endFrame(sc);
+ \endcode
+
+ \note The texture must be created with QRhiTexture::UsedAsTransferSource.
+
+ \note Multisample textures cannot be read back.
+
+ \note The readback returns raw byte data, in order to allow the applications
+ to interpret it in any way they see fit. Be aware of the blending settings
+ of rendering code: if the blending is set up to rely on premultiplied alpha,
+ the results of the readback must also be interpreted as Premultiplied.
+
+ \note When interpreting the resulting raw data, be aware that the readback
+ happens with a byte ordered format. A \l{QRhiTexture::RGBA8}{RGBA8} texture
+ maps therefore to byte ordered QImage formats, such as,
+ QImage::Format_RGBA8888.
+ */
+void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureRead(rb, result));
+}
+
+/*!
+ Enqueues a mipmap generation operation for the specified \a layer of texture
+ \a tex.
+
+ \note The texture must be created with QRhiTexture::MipMapped and
+ QRhiTexture::UsedWithGenerateMips.
+ */
+void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureMipGen(tex, layer));
+}
+
+/*!
+ \return an available, empty batch to which copy type of operations can be
+ recorded.
+
+ \note the return value is not owned by the caller and must never be
+ destroyed. Instead, the batch is returned the the pool for reuse by passing
+ it to QRhiCommandBuffer::beginPass(), QRhiCommandBuffer::endPass(), or
+ QRhiCommandBuffer::resourceUpdate(), or by calling
+ QRhiResourceUpdateBatch::release() on it.
+
+ \note Can be called outside beginFrame() - endFrame() as well since a batch
+ instance just collects data on its own, it does not perform any operations.
+ */
+QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch()
+{
+ auto nextFreeBatch = [this]() -> QRhiResourceUpdateBatch * {
+ for (int i = 0, ie = d->resUpdPoolMap.count(); i != ie; ++i) {
+ if (!d->resUpdPoolMap.testBit(i)) {
+ d->resUpdPoolMap.setBit(i);
+ QRhiResourceUpdateBatch *u = d->resUpdPool[i];
+ QRhiResourceUpdateBatchPrivate::get(u)->poolIndex = i;
+ return u;
+ }
+ }
+ return nullptr;
+ };
+
+ QRhiResourceUpdateBatch *u = nextFreeBatch();
+ if (!u) {
+ const int oldSize = d->resUpdPool.count();
+ const int newSize = oldSize + 4;
+ d->resUpdPool.resize(newSize);
+ d->resUpdPoolMap.resize(newSize);
+ for (int i = oldSize; i < newSize; ++i)
+ d->resUpdPool[i] = new QRhiResourceUpdateBatch(d);
+ u = nextFreeBatch();
+ Q_ASSERT(u);
+ }
+
+ return u;
+}
+
+void QRhiResourceUpdateBatchPrivate::free()
+{
+ Q_ASSERT(poolIndex >= 0 && rhi->resUpdPool[poolIndex] == q);
+
+ dynamicBufferUpdates.clear();
+ staticBufferUploads.clear();
+ textureOps.clear();
+
+ rhi->resUpdPoolMap.clearBit(poolIndex);
+ poolIndex = -1;
+}
+
+void QRhiResourceUpdateBatchPrivate::merge(QRhiResourceUpdateBatchPrivate *other)
+{
+ dynamicBufferUpdates += other->dynamicBufferUpdates;
+ staticBufferUploads += other->staticBufferUploads;
+ textureOps += other->textureOps;
+}
+
+/*!
+ Sometimes committing resource updates is necessary without starting a
+ render pass. Not often needed, updates should typically be passed to
+ beginPass (or endPass, in case of readbacks) instead.
+
+ \note Cannot be called inside a pass.
+ */
+void QRhiCommandBuffer::resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ m_rhi->resourceUpdate(this, resourceUpdates);
+}
+
+/*!
+ Records starting a new render pass targeting the render target \a rt.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+
+ The color and depth/stencil buffers of the render target are normally
+ cleared. The clear values are specified in \a colorClearValue and \a
+ depthStencilClearValue. The exception is when the render target was created
+ with QRhiTextureRenderTarget::PreserveColorContents and/or
+ QRhiTextureRenderTarget::PreserveDepthStencilContents. The clear values are
+ ignored then.
+
+ \note Enabling preserved color or depth contents leads to decreased
+ performance depending on the underlying hardware. Mobile GPUs with tiled
+ architecture benefit from not having to reload the previous contents into
+ the tile buffer. Similarly, a QRhiTextureRenderTarget with a QRhiTexture as
+ the depth buffer is less efficient than a QRhiRenderBuffer since using a
+ depth texture triggers requiring writing the data out to it, while with
+ renderbuffers this is not needed (as the API does not allow sampling or
+ reading from a renderbuffer).
+
+ \note Do not assume that any state or resource bindings persist between
+ passes.
+
+ \note The QRhiCommandBuffer's \c set and \c draw functions can only be
+ called inside a pass. Also, with the exception of setGraphicsPipeline(),
+ they expect to have a pipeline set already on the command buffer.
+ Unspecified issues may arise otherwise, depending on the backend.
+ */
+void QRhiCommandBuffer::beginPass(QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->beginPass(this, rt, colorClearValue, depthStencilClearValue, resourceUpdates);
+}
+
+/*!
+ Records ending the current render pass.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+ */
+void QRhiCommandBuffer::endPass(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->endPass(this, resourceUpdates);
+}
+
+/*!
+ Records setting a new graphics pipeline \a ps.
+
+ \note This function must be called before recording other \c set or \c draw
+ commands on the command buffer.
+
+ \note QRhi will optimize out unnecessary invocations within a pass, so
+ therefore overoptimizing to avoid calls to this function is not necessary
+ on the applications' side.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setGraphicsPipeline(QRhiGraphicsPipeline *ps)
+{
+ m_rhi->setGraphicsPipeline(this, ps);
+}
+
+/*!
+ Records binding a set of shader resources, such as, uniform buffers or
+ textures, that are made visible to one or more shader stages.
+
+ \a srb can be null in which case the current graphics pipeline's associated
+ QRhiGraphicsPipeline::shaderResourceBindings() is used. When \a srb is
+ non-null, it must be
+ \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible},
+ meaning the layout (number of bindings, the type and binding number of each
+ binding) must fully match the QRhiShaderResourceBindings that was
+ associated with the pipeline at the time of calling
+ QRhiGraphicsPipeline::build().
+
+ There are cases when a seemingly unnecessary setShaderResources() call is
+ mandatory: when rebuilding a resource referenced from \a srb, for example
+ changing the size of a QRhiBuffer followed by a QRhiBuffer::build(), this
+ is the place where associated native objects (such as descriptor sets in
+ case of Vulkan) are updated to refer to the current native resources that
+ back the QRhiBuffer, QRhiTexture, QRhiSampler objects referenced from \a
+ srb. In this case setShaderResources() must be called even if \a srb is
+ the same as in the last call.
+
+ \a dynamicOffsets allows specifying buffer offsets for uniform buffers that
+ were associated with \a srb via
+ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(). This is
+ different from providing the offset in the \a srb itself: dynamic offsets
+ do not require building a new QRhiShaderResourceBindings for every
+ different offset, can avoid writing the underlying descriptors (with
+ backends where applicable), and so they may be more efficient. Each element
+ of \a dynamicOffsets is a \c binding - \c offset pair.
+ \a dynamicOffsetCount specifies the number of elements in \a dynamicOffsets.
+
+ \note All offsets in \a dynamicOffsets must be byte aligned to the value
+ returned from QRhi::ubufAlignment().
+
+ \note QRhi will optimize out unnecessary invocations within a pass (taking
+ the conditions described above into account), so therefore overoptimizing
+ to avoid calls to this function is not necessary on the applications' side.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setShaderResources(QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const DynamicOffset *dynamicOffsets)
+{
+ m_rhi->setShaderResources(this, srb, dynamicOffsetCount, dynamicOffsets);
+}
+
+/*!
+ Records vertex input bindings.
+
+ The index buffer used by subsequent drawIndexed() commands is specified by
+ \a indexBuf, \a indexOffset, and \a indexFormat. \a indexBuf can be set to
+ null when indexed drawing is not needed.
+
+ Vertex buffer bindings are batched. \a startBinding specifies the first
+ binding number. The recorded command then binds each buffer from \a
+ bindings to the binding point \c{startBinding + i} where \c i is the index
+ in \a bindings. Each element in \a bindings specifies a QRhiBuffer and an
+ offset.
+
+ Superfluous vertex input and index changes in the same pass are ignored
+ automatically with most backends and therefore applications do not need to
+ overoptimize to avoid calls to this function.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+
+ As a simple example, take a vertex shader with two inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec3 color;
+ \endcode
+
+ and assume we have the data available in interleaved format, using only 2
+ floats for position (so 5 floats per vertex: x, y, r, g, b). A QRhiGraphicsPipeline for
+ this shader can then be created using the input layout:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
+ });
+ \endcode
+
+ Here there is one buffer binding (binding number 0), with two inputs
+ referencing it. When recording the pass, once the pipeline is set, the
+ vertex bindings can be specified simply like the following (using C++11
+ initializer syntax), assuming vbuf is the QRhiBuffer with all the
+ interleaved position+color data:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBinding(vbuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ \endcode
+ */
+void QRhiCommandBuffer::setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ IndexFormat indexFormat)
+{
+ m_rhi->setVertexInput(this, startBinding, bindingCount, bindings, indexBuf, indexOffset, indexFormat);
+}
+
+/*!
+ Records setting the active viewport rectangle specified in \a viewport.
+
+ With backends where the underlying graphics API has scissoring always
+ enabled, this function also sets the scissor to match the viewport whenever
+ the active QRhiGraphicsPipeline does not have
+ \l{QRhiGraphicsPipeline::UsesScissor}{UsesScissor} set.
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setViewport(const QRhiViewport &viewport)
+{
+ m_rhi->setViewport(this, viewport);
+}
+
+/*!
+ Records setting the active scissor rectangle specified in \a scissor.
+
+ This can only be called when the bound pipeline has
+ \l{QRhiGraphicsPipeline::UsesScissor}{UsesScissor} set. When the flag is
+ set on the active pipeline, this function must be called because scissor
+ testing will get enabled and so a scissor rectangle must be provided.
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setScissor(const QRhiScissor &scissor)
+{
+ m_rhi->setScissor(this, scissor);
+}
+
+/*!
+ Records setting the active blend constants to \a c.
+
+ This can only be called when the bound pipeline has
+ QRhiGraphicsPipeline::UsesBlendConstants set.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setBlendConstants(const QColor &c)
+{
+ m_rhi->setBlendConstants(this, c);
+}
+
+/*!
+ Records setting the active stencil reference value to \a refValue.
+
+ This can only be called when the bound pipeline has
+ QRhiGraphicsPipeline::UsesStencilRef set.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::setStencilRef(quint32 refValue)
+{
+ m_rhi->setStencilRef(this, refValue);
+}
+
+/*!
+ Records a non-indexed draw.
+
+ The number of vertices is specified in \a vertexCount. For instanced
+ drawing set \a instanceCount to a value other than 1. \a firstVertex is
+ the index of the first vertex to draw. \a firstInstance is the instance ID
+ of the first instance to draw.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::draw(quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ m_rhi->draw(this, vertexCount, instanceCount, firstVertex, firstInstance);
+}
+
+/*!
+ Records an indexed draw.
+
+ The number of vertices is specified in \a indexCount. \a firstIndex is the
+ base index. The effective offset in the index buffer is given by
+ \c{indexOffset + firstIndex * n} where \c n is 2 or 4 depending on the
+ index element type. \c indexOffset is specified in setVertexInput().
+
+ \note The effective offset in the index buffer must be 4 byte aligned with
+ some backends (for example, Metal). With these backends the
+ \l{QRhi::NonFourAlignedEffectiveIndexBufferOffset}{NonFourAlignedEffectiveIndexBufferOffset}
+ feature will be reported as not-supported.
+
+ For instanced drawing set \a instanceCount to a value other than 1. \a
+ firstInstance is the instance ID of the first instance to draw.
+
+ \a vertexOffset is added to the vertex index.
+
+ \note This function can only be called inside a pass, meaning between a
+ beginPass() end endPass() call.
+ */
+void QRhiCommandBuffer::drawIndexed(quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance)
+{
+ m_rhi->drawIndexed(this, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance);
+}
+
+/*!
+ Records a named debug group on the command buffer. This is shown in
+ graphics debugging tools such as \l{https://renderdoc.org/}{RenderDoc} and
+ \l{https://developer.apple.com/xcode/}{XCode}. The end of the grouping is
+ indicated by debugMarkEnd().
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note Can be called anywhere within the frame, both inside and outside of passes.
+ */
+void QRhiCommandBuffer::debugMarkBegin(const QByteArray &name)
+{
+ m_rhi->debugMarkBegin(this, name);
+}
+
+/*!
+ Records the end of a debug group.
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note Can be called anywhere within the frame, both inside and outside of passes.
+ */
+void QRhiCommandBuffer::debugMarkEnd()
+{
+ m_rhi->debugMarkEnd(this);
+}
+
+/*!
+ Inserts a debug message \a msg into the command stream.
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note With some backends debugMarkMsg() is only supported inside a pass and
+ is ignored when called outside a pass. With others it is recorded anywhere
+ within the frame.
+ */
+void QRhiCommandBuffer::debugMarkMsg(const QByteArray &msg)
+{
+ m_rhi->debugMarkMsg(this, msg);
+}
+
+/*!
+ \return a pointer to a backend-specific QRhiNativeHandles subclass, such as
+ QRhiVulkanCommandBufferNativeHandles. The returned value is null when
+ exposing the underlying native resources is not supported by, or not
+ applicable to, the backend.
+
+ \sa QRhiVulkanCommandBufferNativeHandles,
+ QRhiMetalCommandBufferNativeHandles, beginExternal(), endExternal()
+ */
+const QRhiNativeHandles *QRhiCommandBuffer::nativeHandles()
+{
+ return m_rhi->nativeHandles(this);
+}
+
+/*!
+ To be called when the application before the application is about to
+ enqueue commands to the current pass' command buffer by calling graphics
+ API functions directly.
+
+ With Vulkan or Metal one can query the native command buffer or encoder
+ objects via nativeHandles() and enqueue commands to them. With OpenGL or
+ Direct3D 11 the (device) context can be retrieved from
+ QRhi::nativeHandles(). However, this must never be done without ensuring
+ the QRhiCommandBuffer's state stays up-to-date. Hence the requirement for
+ wrapping any externally added command recording between beginExternal() and
+ endExternal(). Conceptually this is the same as QPainter's
+ \l{QPainter::beginNativePainting()}{beginNativePainting()} and
+ \l{QPainter::endNativePainting()}{endNativePainting()} functions.
+
+ For OpenGL in particular, this function has an additional task: it makes
+ sure the context is made current on the current thread.
+
+ \note Once beginExternal() is called, no other render pass specific
+ functions (\c set* or \c draw*) must be called on the
+ QRhiCommandBuffer until endExternal().
+
+ \sa endExternal(), nativeHandles()
+ */
+void QRhiCommandBuffer::beginExternal()
+{
+ m_rhi->beginExternal(this);
+}
+
+/*!
+ To be called once the externally added commands are recorded to the command
+ buffer or context.
+
+ \note All QRhiCommandBuffer state must be assumed as invalid after calling
+ this function. Pipelines, vertex and index buffers, and other state must be
+ set again if more draw calls are recorded after the external commands.
+
+ \sa beginExternal(), nativeHandles()
+ */
+void QRhiCommandBuffer::endExternal()
+{
+ m_rhi->endExternal(this);
+}
+
+/*!
+ \return the value (typically an offset) \a v aligned to the uniform buffer
+ alignment given by by ubufAlignment().
+ */
+int QRhi::ubufAligned(int v) const
+{
+ const int byteAlign = ubufAlignment();
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+/*!
+ \return the number of mip levels for a given \a size.
+ */
+int QRhi::mipLevelsForSize(const QSize &size) const
+{
+ return qFloor(std::log2(qMax(size.width(), size.height()))) + 1;
+}
+
+/*!
+ \return the texture image size for a given \a mipLevel, calculated based on
+ the level 0 size given in \a baseLevelSize.
+ */
+QSize QRhi::sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const
+{
+ const int w = qMax(1, baseLevelSize.width() >> mipLevel);
+ const int h = qMax(1, baseLevelSize.height() >> mipLevel);
+ return QSize(w, h);
+}
+
+/*!
+ \return \c true if the underlying graphics API has the Y axis pointing up
+ in framebuffers and images.
+
+ In practice this is \c true for OpenGL only.
+ */
+bool QRhi::isYUpInFramebuffer() const
+{
+ return d->isYUpInFramebuffer();
+}
+
+/*!
+ \return \c true if the underlying graphics API has the Y axis pointing up
+ in its normalized device coordinate system.
+
+ In practice this is \c false for Vulkan only.
+
+ \note clipSpaceCorrMatrix() includes the corresponding adjustment (to make
+ Y point up) in its returned matrix.
+ */
+bool QRhi::isYUpInNDC() const
+{
+ return d->isYUpInNDC();
+}
+
+/*!
+ \return \c true if the underlying graphics API uses depth 0 - 1 in clip
+ space.
+
+ In practice this is \c false for OpenGL only.
+
+ \note clipSpaceCorrMatrix() includes the corresponding adjustment in its
+ returned matrix.
+ */
+bool QRhi::isClipDepthZeroToOne() const
+{
+ return d->isClipDepthZeroToOne();
+}
+
+/*!
+ \return a matrix that can be used to allow applications keep using
+ OpenGL-targeted vertex data and perspective projection matrices (such as,
+ the ones generated by QMatrix4x4::perspective()), regardless of the
+ backend. Once \c{this_matrix * mvp} is used instead of just \c mvp, vertex
+ data with Y up and viewports with depth range 0 - 1 can be used without
+ considering what backend and so graphics API is going to be used at run
+ time.
+
+ See
+ \l{https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/}{this
+ page} for a discussion of the topic from Vulkan perspective.
+ */
+QMatrix4x4 QRhi::clipSpaceCorrMatrix() const
+{
+ return d->clipSpaceCorrMatrix();
+}
+
+/*!
+ \return \c true if the specified texture \a format modified by \a flags is
+ supported.
+
+ The query is supported both for uncompressed and compressed formats.
+ */
+bool QRhi::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ return d->isTextureFormatSupported(format, flags);
+}
+
+/*!
+ \return \c true if the specified \a feature is supported
+ */
+bool QRhi::isFeatureSupported(QRhi::Feature feature) const
+{
+ return d->isFeatureSupported(feature);
+}
+
+/*!
+ \return the value for the specified resource \a limit.
+
+ The values are expected to be queried by the backends upon initialization,
+ meaning calling this function is a light operation.
+ */
+int QRhi::resourceLimit(ResourceLimit limit) const
+{
+ return d->resourceLimit(limit);
+}
+
+/*!
+ \return a pointer to the backend-specific collection of native objects
+ for the device, context, and similar concepts used by the backend.
+
+ Cast to QRhiVulkanNativeHandles, QRhiD3D11NativeHandles,
+ QRhiGles2NativeHandles, QRhiMetalNativeHandles as appropriate.
+
+ \note No ownership is transferred, neither for the returned pointer nor for
+ any native objects.
+ */
+const QRhiNativeHandles *QRhi::nativeHandles()
+{
+ return d->nativeHandles();
+}
+
+/*!
+ \return the associated QRhiProfiler instance.
+
+ An instance is always available for each QRhi, but it is not very useful
+ without EnableProfiling because no data is collected without setting the
+ flag upon creation.
+ */
+QRhiProfiler *QRhi::profiler()
+{
+ return &d->profiler;
+}
+
+/*!
+ \return a new graphics pipeline resource.
+
+ \sa QRhiResource::release()
+ */
+QRhiGraphicsPipeline *QRhi::newGraphicsPipeline()
+{
+ return d->createGraphicsPipeline();
+}
+
+/*!
+ \return a new shader resource binding collection resource.
+
+ \sa QRhiResource::release()
+ */
+QRhiShaderResourceBindings *QRhi::newShaderResourceBindings()
+{
+ return d->createShaderResourceBindings();
+}
+
+/*!
+ \return a new buffer with the specified \a type, \a usage, and \a size.
+
+ \note Some \a usage and \a type combinations may not be supported by all
+ backends. See \l{QRhi::NonDynamicUniformBuffers}{the feature flags}.
+
+ \sa QRhiResource::release()
+ */
+QRhiBuffer *QRhi::newBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size)
+{
+ return d->createBuffer(type, usage, size);
+}
+
+/*!
+ \return a new renderbuffer with the specified \a type, \a pixelSize, \a
+ sampleCount, and \a flags.
+
+ \sa QRhiResource::release()
+ */
+QRhiRenderBuffer *QRhi::newRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags)
+{
+ return d->createRenderBuffer(type, pixelSize, sampleCount, flags);
+}
+
+/*!
+ \return a new texture with the specified \a format, \a pixelSize, \a
+ sampleCount, and \a flags.
+
+ \note \a format specifies the requested internal and external format,
+ meaning the data to be uploaded to the texture will need to be in a
+ compatible format, while the native texture may (but is not guaranteed to,
+ in case of OpenGL at least) use this format internally.
+
+ \sa QRhiResource::release()
+ */
+QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags)
+{
+ return d->createTexture(format, pixelSize, sampleCount, flags);
+}
+
+/*!
+ \return a new sampler with the specified magnification filter \a magFilter,
+ minification filter \a minFilter, mipmapping mode \a mipmapMpde, and S/T
+ addressing modes \a u and \a v.
+
+ \sa QRhiResource::release()
+ */
+QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v)
+{
+ return d->createSampler(magFilter, minFilter, mipmapMode, u, v);
+}
+
+/*!
+ \return a new texture render target with color and depth/stencil
+ attachments given in \a desc, and with the specified \a flags.
+
+ \sa QRhiResource::release()
+ */
+
+QRhiTextureRenderTarget *QRhi::newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return d->createTextureRenderTarget(desc, flags);
+}
+
+/*!
+ \return a new swapchain.
+
+ \sa QRhiResource::release(), QRhiSwapChain::buildOrResize()
+ */
+QRhiSwapChain *QRhi::newSwapChain()
+{
+ return d->createSwapChain();
+}
+
+/*!
+ Starts a new frame targeting the next available buffer of \a swapChain.
+
+ The high level pattern of rendering into a QWindow using a swapchain:
+
+ \list
+
+ \li Create a swapchain.
+
+ \li Call QRhiSwapChain::buildOrResize() whenever the surface size is
+ different than before.
+
+ \li Call QRhiSwapChain::release() on
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed.
+
+ \li Then on every frame:
+ \badcode
+ beginFrame(sc);
+ updates = nextResourceUpdateBatch();
+ updates->...
+ QRhiCommandBuffer *cb = sc->currentFrameCommandBuffer();
+ cb->beginPass(sc->currentFrameRenderTarget(), colorClear, dsClear, updates);
+ ...
+ cb->endPass();
+ ... // more passes as necessary
+ endFrame(sc);
+ \endcode
+
+ \endlist
+
+ \a flags is currently unused.
+
+ \sa endFrame()
+ */
+QRhi::FrameOpResult QRhi::beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags)
+{
+ if (d->inFrame)
+ qWarning("Attempted to call beginFrame() within a still active frame; ignored");
+
+ QRhi::FrameOpResult r = !d->inFrame ? d->beginFrame(swapChain, flags) : FrameOpSuccess;
+ if (r == FrameOpSuccess)
+ d->inFrame = true;
+
+ return r;
+}
+
+/*!
+ Ends, commits, and presents a frame that was started in the last
+ beginFrame() on \a swapChain.
+
+ Double (or triple) buffering is managed internally by the QRhiSwapChain and
+ QRhi.
+
+ \a flags can optionally be used to change the behavior in certain ways.
+ Passing QRhi::SkipPresent skips queuing the Present command or calling
+ swapBuffers.
+
+ \sa beginFrame()
+ */
+QRhi::FrameOpResult QRhi::endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags)
+{
+ if (!d->inFrame)
+ qWarning("Attempted to call endFrame() without an active frame; ignored");
+
+ QRhi::FrameOpResult r = d->inFrame ? d->endFrame(swapChain, flags) : FrameOpSuccess;
+ d->inFrame = false;
+ // releaseAndDestroyLater is a high level QRhi concept the backends know
+ // nothing about - handle it here.
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ return r;
+}
+
+/*!
+ \return true when there is an active frame, meaning there was a
+ beginFrame() (or beginOffscreenFrame()) with no corresponding endFrame()
+ (or endOffscreenFrame()) yet.
+
+ \sa currentFrameSlot(), beginFrame(), endFrame()
+ */
+bool QRhi::isRecordingFrame() const
+{
+ return d->inFrame;
+}
+
+/*!
+ \return the current frame slot index while recording a frame. Unspecified
+ when called outside an active frame (that is, when isRecordingFrame() is \c
+ false).
+
+ With backends like Vulkan or Metal, it is the responsibility of the QRhi
+ backend to block whenever starting a new frame and finding the CPU is
+ already \c{FramesInFlight - 1} frames ahead of the GPU (because the command
+ buffer submitted in frame no. \c{current} - \c{FramesInFlight} has not yet
+ completed).
+
+ Resources that tend to change between frames (such as, the native buffer
+ object backing a QRhiBuffer with type QRhiBuffer::Dynamic) exist in
+ multiple versions, so that each frame, that can be submitted while a
+ previous one is still being processed, works with its own copy, thus
+ avoiding the need to stall the pipeline when preparing the frame. (The
+ contents of a resource that may still be in use in the GPU should not be
+ touched, but simply always waiting for the previous frame to finish would
+ reduce GPU utilization and ultimately, performance and efficiency.)
+
+ Conceptually this is somewhat similar to copy-on-write schemes used by some
+ C++ containers and other types. It may also be similar to what an OpenGL or
+ Direct 3D 11 implementation performs internally for certain type of objects.
+
+ In practice, such double (or tripple) buffering resources is realized in
+ the Vulkan, Metal, and similar QRhi backends by having a fixed number of
+ native resource (such as, VkBuffer) \c slots behind a QRhiResource. That
+ can then be indexed by a frame slot index running 0, 1, ..,
+ FramesInFlight-1, and then wrapping around.
+
+ All this is managed transparently to the users of QRhi. However,
+ applications that integrate rendering done directly with the graphics API
+ may want to perform a similar double or tripple buffering of their own
+ graphics resources. That is then most easily achieved by knowing the values
+ of the maximum number of in-flight frames (retrievable via resourceLimit())
+ and the current frame (slot) index (returned by this function).
+
+ \sa isRecordingFrame(), beginFrame(), endFrame()
+ */
+int QRhi::currentFrameSlot() const
+{
+ return d->currentFrameSlot;
+}
+
+/*!
+ Starts a new offscreen frame. Provides a command buffer suitable for
+ recording rendering commands in \a cb.
+
+ \note The QRhiCommandBuffer stored to *cb is not owned by the caller.
+
+ Rendering without a swapchain is possible as well. The typical use case is
+ to use it in completely offscreen applications, e.g. to generate image
+ sequences by rendering and reading back without ever showing a window.
+
+ Usage in on-screen applications (so beginFrame, endFrame,
+ beginOffscreenFrame, endOffscreenFrame, beginFrame, ...) is possible too
+ but it does reduce parallelism so it should be done only infrequently.
+
+ Offscreen frames do not let the CPU - potentially - generate another frame
+ while the GPU is still processing the previous one. This has the side
+ effect that if readbacks are scheduled, the results are guaranteed to be
+ available once endOffscreenFrame() returns. That is not the case with
+ frames targeting a swapchain.
+
+ The skeleton of rendering a frame without a swapchain and then reading the
+ frame contents back could look like the following:
+
+ \badcode
+ QRhiReadbackResult rbResult;
+ QRhiCommandBuffer *cb;
+ beginOffscreenFrame(&cb);
+ beginPass
+ ...
+ u = nextResourceUpdateBatch();
+ u->readBackTexture(rb, &rbResult);
+ endPass(u);
+ endOffscreenFrame();
+ // image data available in rbResult
+ \endcode
+
+ \sa endOffscreenFrame()
+ */
+QRhi::FrameOpResult QRhi::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ if (d->inFrame)
+ qWarning("Attempted to call beginOffscreenFrame() within a still active frame; ignored");
+
+ QRhi::FrameOpResult r = !d->inFrame ? d->beginOffscreenFrame(cb) : FrameOpSuccess;
+ if (r == FrameOpSuccess)
+ d->inFrame = true;
+
+ return r;
+}
+
+/*!
+ Ends and waits for the offscreen frame.
+
+ \sa beginOffscreenFrame()
+ */
+QRhi::FrameOpResult QRhi::endOffscreenFrame()
+{
+ if (!d->inFrame)
+ qWarning("Attempted to call endOffscreenFrame() without an active frame; ignored");
+
+ QRhi::FrameOpResult r = d->inFrame ? d->endOffscreenFrame() : FrameOpSuccess;
+ d->inFrame = false;
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ return r;
+}
+
+/*!
+ Waits for any work on the graphics queue (where applicable) to complete,
+ then executes all deferred operations, like completing readbacks and
+ resource releases. Can be called inside and outside of a frame, but not
+ inside a pass. Inside a frame it implies submitting any work on the
+ command buffer.
+
+ \note Avoid this function. One case where it may be needed is when the
+ results of an enqueued readback in a swapchain-based frame are needed at a
+ fixed given point and so waiting for the results is desired.
+ */
+QRhi::FrameOpResult QRhi::finish()
+{
+ return d->finish();
+}
+
+/*!
+ \return the list of supported sample counts.
+
+ A typical example would be (1, 2, 4, 8).
+
+ With some backend this list of supported values is fixed in advance, while
+ with some others the (physical) device properties indicate what is
+ supported at run time.
+ */
+QVector<int> QRhi::supportedSampleCounts() const
+{
+ return d->supportedSampleCounts();
+}
+
+/*!
+ \return the minimum uniform buffer offset alignment in bytes. This is
+ typically 256.
+
+ Attempting to bind a uniform buffer region with an offset not aligned to
+ this value will lead to failures depending on the backend and the
+ underlying graphics API.
+
+ \sa ubufAligned()
+ */
+int QRhi::ubufAlignment() const
+{
+ return d->ubufAlignment();
+}
+
+QRhiGlobalObjectIdGenerator::Type QRhiGlobalObjectIdGenerator::newId()
+{
+ static QRhiGlobalObjectIdGenerator inst;
+ return ++inst.counter;
+}
+
+bool QRhiPassResourceTracker::isEmpty() const
+{
+ return m_buffers.isEmpty() && m_textures.isEmpty();
+}
+
+void QRhiPassResourceTracker::reset()
+{
+ m_buffers.clear();
+ m_textures.clear();
+}
+
+static inline QRhiPassResourceTracker::BufferStage earlierStage(QRhiPassResourceTracker::BufferStage a,
+ QRhiPassResourceTracker::BufferStage b)
+{
+ return QRhiPassResourceTracker::BufferStage(qMin(int(a), int(b)));
+}
+
+void QRhiPassResourceTracker::registerBufferOnce(QRhiBuffer *buf, int slot, BufferAccess access, BufferStage stage,
+ const UsageState &stateAtPassBegin)
+{
+ auto it = std::find_if(m_buffers.begin(), m_buffers.end(), [buf](const Buffer &b) { return b.buf == buf; });
+ if (it != m_buffers.end()) {
+ if (it->access != access) {
+ const QByteArray name = buf->name();
+ qWarning("Buffer %p (%s) used with different accesses within the same pass, this is not allowed.",
+ buf, name.constData());
+ return;
+ }
+ if (it->stage != stage)
+ it->stage = earlierStage(it->stage, stage);
+ // Multiple registrations of the same buffer is fine as long is it is
+ // a compatible usage. stateAtPassBegin is not actually the state at
+ // pass begin in the second, third, etc. invocation but that's fine
+ // since we'll just return here.
+ return;
+ }
+
+ Buffer b;
+ b.buf = buf;
+ b.slot = slot;
+ b.access = access;
+ b.stage = stage;
+ b.stateAtPassBegin = stateAtPassBegin;
+ m_buffers.append(b);
+}
+
+static inline QRhiPassResourceTracker::TextureStage earlierStage(QRhiPassResourceTracker::TextureStage a,
+ QRhiPassResourceTracker::TextureStage b)
+{
+ return QRhiPassResourceTracker::TextureStage(qMin(int(a), int(b)));
+}
+
+void QRhiPassResourceTracker::registerTextureOnce(QRhiTexture *tex, TextureAccess access, TextureStage stage,
+ const UsageState &stateAtPassBegin)
+{
+ auto it = std::find_if(m_textures.begin(), m_textures.end(), [tex](const Texture &t) { return t.tex == tex; });
+ if (it != m_textures.end()) {
+ if (it->access != access) {
+ const QByteArray name = tex->name();
+ qWarning("Texture %p (%s) used with different accesses within the same pass, this is not allowed.",
+ tex, name.constData());
+ }
+ if (it->stage != stage)
+ it->stage = earlierStage(it->stage, stage);
+ // Multiple registrations of the same texture is fine as long is it is
+ // a compatible usage. stateAtPassBegin is not actually the state at
+ // pass begin in the second, third, etc. invocation but that's fine
+ // since we'll just return here.
+ return;
+ }
+
+ Texture t;
+ t.tex = tex;
+ t.access = access;
+ t.stage = stage;
+ t.stateAtPassBegin = stateAtPassBegin;
+ m_textures.append(t);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
new file mode 100644
index 0000000000..a0f57819e4
--- /dev/null
+++ b/src/gui/rhi/qrhi_p.h
@@ -0,0 +1,1375 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHI_H
+#define QRHI_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <QSize>
+#include <QMatrix4x4>
+#include <QVector>
+#include <QThread>
+#include <QColor>
+#include <QImage>
+#include <functional>
+#include <array>
+#include <private/qshader_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindow;
+class QRhiImplementation;
+class QRhiBuffer;
+class QRhiRenderBuffer;
+class QRhiTexture;
+class QRhiSampler;
+class QRhiCommandBuffer;
+class QRhiResourceUpdateBatch;
+class QRhiResourceUpdateBatchPrivate;
+class QRhiProfiler;
+class QRhiShaderResourceBindingPrivate;
+
+class Q_GUI_EXPORT QRhiDepthStencilClearValue
+{
+public:
+ QRhiDepthStencilClearValue() = default;
+ QRhiDepthStencilClearValue(float d, quint32 s);
+
+ float depthClearValue() const { return m_d; }
+ void setDepthClearValue(float d) { m_d = d; }
+
+ quint32 stencilClearValue() const { return m_s; }
+ void setStencilClearValue(quint32 s) { m_s = s; }
+
+private:
+ float m_d = 1.0f;
+ quint32 m_s = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiDepthStencilClearValue, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiDepthStencilClearValue &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDepthStencilClearValue &);
+#endif
+
+class Q_GUI_EXPORT QRhiViewport
+{
+public:
+ QRhiViewport() = default;
+ QRhiViewport(float x, float y, float w, float h, float minDepth = 0.0f, float maxDepth = 1.0f);
+
+ std::array<float, 4> viewport() const { return m_rect; }
+ void setViewport(float x, float y, float w, float h) {
+ m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h;
+ }
+
+ float minDepth() const { return m_minDepth; }
+ void setMinDepth(float minDepth) { m_minDepth = minDepth; }
+
+ float maxDepth() const { return m_maxDepth; }
+ void setMaxDepth(float maxDepth) { m_maxDepth = maxDepth; }
+
+private:
+ std::array<float, 4> m_rect { { 0.0f, 0.0f, 0.0f, 0.0f } };
+ float m_minDepth = 0.0f;
+ float m_maxDepth = 1.0f;
+};
+
+Q_DECLARE_TYPEINFO(QRhiViewport, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiViewport &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiViewport &);
+#endif
+
+class Q_GUI_EXPORT QRhiScissor
+{
+public:
+ QRhiScissor() = default;
+ QRhiScissor(int x, int y, int w, int h);
+
+ std::array<int, 4> scissor() const { return m_rect; }
+ void setScissor(int x, int y, int w, int h) {
+ m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h;
+ }
+
+private:
+ std::array<int, 4> m_rect { { 0, 0, 0, 0 } };
+};
+
+Q_DECLARE_TYPEINFO(QRhiScissor, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiScissor &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiScissor &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputBinding
+{
+public:
+ enum Classification {
+ PerVertex,
+ PerInstance
+ };
+
+ QRhiVertexInputBinding() = default;
+ QRhiVertexInputBinding(quint32 stride, Classification cls = PerVertex, int stepRate = 1);
+
+ quint32 stride() const { return m_stride; }
+ void setStride(quint32 s) { m_stride = s; }
+
+ Classification classification() const { return m_classification; }
+ void setClassification(Classification c) { m_classification = c; }
+
+ int instanceStepRate() const { return m_instanceStepRate; }
+ void setInstanceStepRate(int rate) { m_instanceStepRate = rate; }
+
+private:
+ quint32 m_stride = 0;
+ Classification m_classification = PerVertex;
+ int m_instanceStepRate = 1;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputBinding, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputBinding &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputBinding &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputAttribute
+{
+public:
+ enum Format {
+ Float4,
+ Float3,
+ Float2,
+ Float,
+ UNormByte4,
+ UNormByte2,
+ UNormByte
+ };
+
+ QRhiVertexInputAttribute() = default;
+ QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset);
+
+ int binding() const { return m_binding; }
+ void setBinding(int b) { m_binding = b; }
+
+ int location() const { return m_location; }
+ void setLocation(int loc) { m_location = loc; }
+
+ Format format() const { return m_format; }
+ void setFormt(Format f) { m_format = f; }
+
+ quint32 offset() const { return m_offset; }
+ void setOffset(quint32 ofs) { m_offset = ofs; }
+
+private:
+ int m_binding = 0;
+ int m_location = 0;
+ Format m_format = Float4;
+ quint32 m_offset = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputAttribute, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputAttribute &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputAttribute &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputLayout
+{
+public:
+ QRhiVertexInputLayout() = default;
+
+ QVector<QRhiVertexInputBinding> bindings() const { return m_bindings; }
+ void setBindings(const QVector<QRhiVertexInputBinding> &v) { m_bindings = v; }
+
+ QVector<QRhiVertexInputAttribute> attributes() const { return m_attributes; }
+ void setAttributes(const QVector<QRhiVertexInputAttribute> &v) { m_attributes = v; }
+
+private:
+ QVector<QRhiVertexInputBinding> m_bindings;
+ QVector<QRhiVertexInputAttribute> m_attributes;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputLayout, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputLayout &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &);
+#endif
+
+class Q_GUI_EXPORT QRhiGraphicsShaderStage
+{
+public:
+ enum Type {
+ Vertex,
+ Fragment
+ };
+
+ QRhiGraphicsShaderStage() = default;
+ QRhiGraphicsShaderStage(Type type, const QShader &shader,
+ QShader::Variant v = QShader::StandardShader);
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ QShader shader() const { return m_shader; }
+ void setShader(const QShader &s) { m_shader = s; }
+
+ QShader::Variant shaderVariant() const { return m_shaderVariant; }
+ void setShaderVariant(QShader::Variant v) { m_shaderVariant = v; }
+
+private:
+ Type m_type = Vertex;
+ QShader m_shader;
+ QShader::Variant m_shaderVariant = QShader::StandardShader;
+};
+
+Q_DECLARE_TYPEINFO(QRhiGraphicsShaderStage, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiGraphicsShaderStage &s, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiGraphicsShaderStage &);
+#endif
+
+class Q_GUI_EXPORT QRhiShaderResourceBinding
+{
+public:
+ enum Type {
+ UniformBuffer,
+ SampledTexture
+ };
+
+ enum StageFlag {
+ VertexStage = 1 << 0,
+ FragmentStage = 1 << 1
+ };
+ Q_DECLARE_FLAGS(StageFlags, StageFlag)
+
+ QRhiShaderResourceBinding();
+ QRhiShaderResourceBinding(const QRhiShaderResourceBinding &other);
+ QRhiShaderResourceBinding &operator=(const QRhiShaderResourceBinding &other);
+ ~QRhiShaderResourceBinding();
+ void detach();
+
+ bool isLayoutCompatible(const QRhiShaderResourceBinding &other) const;
+
+ static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf);
+ static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size);
+ static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, int size);
+ static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler);
+
+private:
+ QRhiShaderResourceBindingPrivate *d;
+ friend class QRhiShaderResourceBindingPrivate;
+ friend Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT uint qHash(const QRhiShaderResourceBinding &, uint) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
+#endif
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBinding::StageFlags)
+
+Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiShaderResourceBinding &b, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
+#endif
+
+class Q_GUI_EXPORT QRhiColorAttachment
+{
+public:
+ QRhiColorAttachment() = default;
+ QRhiColorAttachment(QRhiTexture *texture);
+ QRhiColorAttachment(QRhiRenderBuffer *renderBuffer);
+
+ QRhiTexture *texture() const { return m_texture; }
+ void setTexture(QRhiTexture *tex) { m_texture = tex; }
+
+ QRhiRenderBuffer *renderBuffer() const { return m_renderBuffer; }
+ void setRenderBuffer(QRhiRenderBuffer *rb) { m_renderBuffer = rb; }
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+ QRhiTexture *resolveTexture() const { return m_resolveTexture; }
+ void setResolveTexture(QRhiTexture *tex) { m_resolveTexture = tex; }
+
+ int resolveLayer() const { return m_resolveLayer; }
+ void setResolveLayer(int layer) { m_resolveLayer = layer; }
+
+ int resolveLevel() const { return m_resolveLevel; }
+ void setResolveLevel(int level) { m_resolveLevel = level; }
+
+private:
+ QRhiTexture *m_texture = nullptr;
+ QRhiRenderBuffer *m_renderBuffer = nullptr;
+ int m_layer = 0;
+ int m_level = 0;
+ QRhiTexture *m_resolveTexture = nullptr;
+ int m_resolveLayer = 0;
+ int m_resolveLevel = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureRenderTargetDescription
+{
+public:
+ QRhiTextureRenderTargetDescription() = default;
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment);
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiRenderBuffer *depthStencilBuffer);
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiTexture *depthTexture);
+
+ QVector<QRhiColorAttachment> colorAttachments() const { return m_colorAttachments; }
+ void setColorAttachments(const QVector<QRhiColorAttachment> &att) { m_colorAttachments = att; }
+
+ QRhiRenderBuffer *depthStencilBuffer() const { return m_depthStencilBuffer; }
+ void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer) { m_depthStencilBuffer = renderBuffer; }
+
+ QRhiTexture *depthTexture() const { return m_depthTexture; }
+ void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; }
+
+private:
+ QVector<QRhiColorAttachment> m_colorAttachments;
+ QRhiRenderBuffer *m_depthStencilBuffer = nullptr;
+ QRhiTexture *m_depthTexture = nullptr;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureRenderTargetDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription
+{
+public:
+ QRhiTextureSubresourceUploadDescription() = default;
+ QRhiTextureSubresourceUploadDescription(const QImage &image);
+ QRhiTextureSubresourceUploadDescription(const void *data, int size);
+
+ QImage image() const { return m_image; }
+ void setImage(const QImage &image) { m_image = image; }
+
+ QByteArray data() const { return m_data; }
+ void setData(const QByteArray &data) { m_data = data; }
+
+ QPoint destinationTopLeft() const { return m_destinationTopLeft; }
+ void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; }
+
+ QSize sourceSize() const { return m_sourceSize; }
+ void setSourceSize(const QSize &size) { m_sourceSize = size; }
+
+ QPoint sourceTopLeft() const { return m_sourceTopLeft; }
+ void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; }
+
+private:
+ QImage m_image;
+ QByteArray m_data;
+ QPoint m_destinationTopLeft;
+ QSize m_sourceSize;
+ QPoint m_sourceTopLeft;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureSubresourceUploadDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureUploadEntry
+{
+public:
+ QRhiTextureUploadEntry() = default;
+ QRhiTextureUploadEntry(int layer, int level, const QRhiTextureSubresourceUploadDescription &desc);
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+ QRhiTextureSubresourceUploadDescription description() const { return m_desc; }
+ void setDescription(const QRhiTextureSubresourceUploadDescription &desc) { m_desc = desc; }
+
+private:
+ int m_layer = 0;
+ int m_level = 0;
+ QRhiTextureSubresourceUploadDescription m_desc;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureUploadEntry, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureUploadDescription
+{
+public:
+ QRhiTextureUploadDescription() = default;
+ QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry);
+ QRhiTextureUploadDescription(const QVector<QRhiTextureUploadEntry> &entries);
+
+ QVector<QRhiTextureUploadEntry> entries() const { return m_entries; }
+ void setEntries(const QVector<QRhiTextureUploadEntry> &entries) { m_entries = entries; }
+ void append(const QRhiTextureUploadEntry &entry);
+
+private:
+ QVector<QRhiTextureUploadEntry> m_entries;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureUploadDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureCopyDescription
+{
+public:
+ QRhiTextureCopyDescription() = default;
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ int sourceLayer() const { return m_sourceLayer; }
+ void setSourceLayer(int layer) { m_sourceLayer = layer; }
+
+ int sourceLevel() const { return m_sourceLevel; }
+ void setSourceLevel(int level) { m_sourceLevel = level; }
+
+ QPoint sourceTopLeft() const { return m_sourceTopLeft; }
+ void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; }
+
+ int destinationLayer() const { return m_destinationLayer; }
+ void setDestinationLayer(int layer) { m_destinationLayer = layer; }
+
+ int destinationLevel() const { return m_destinationLevel; }
+ void setDestinationLevel(int level) { m_destinationLevel = level; }
+
+ QPoint destinationTopLeft() const { return m_destinationTopLeft; }
+ void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; }
+
+private:
+ QSize m_pixelSize;
+ int m_sourceLayer = 0;
+ int m_sourceLevel = 0;
+ QPoint m_sourceTopLeft;
+ int m_destinationLayer = 0;
+ int m_destinationLevel = 0;
+ QPoint m_destinationTopLeft;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureCopyDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiReadbackDescription
+{
+public:
+ QRhiReadbackDescription() = default;
+ QRhiReadbackDescription(QRhiTexture *texture);
+
+ QRhiTexture *texture() const { return m_texture; }
+ void setTexture(QRhiTexture *tex) { m_texture = tex; }
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+private:
+ QRhiTexture *m_texture = nullptr;
+ int m_layer = 0;
+ int m_level = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_MOVABLE_TYPE);
+
+struct Q_GUI_EXPORT QRhiNativeHandles
+{
+};
+
+class Q_GUI_EXPORT QRhiResource
+{
+public:
+ enum Type {
+ Buffer,
+ Texture,
+ Sampler,
+ RenderBuffer,
+ RenderPassDescriptor,
+ RenderTarget,
+ TextureRenderTarget,
+ ShaderResourceBindings,
+ GraphicsPipeline,
+ SwapChain,
+ CommandBuffer
+ };
+
+ virtual ~QRhiResource();
+
+ virtual Type resourceType() const = 0;
+
+ virtual void release() = 0;
+ void releaseAndDestroyLater();
+
+ QByteArray name() const;
+ void setName(const QByteArray &name);
+
+ quint64 globalResourceId() const;
+
+protected:
+ QRhiResource(QRhiImplementation *rhi);
+ Q_DISABLE_COPY(QRhiResource)
+ friend class QRhiImplementation;
+ QRhiImplementation *m_rhi = nullptr;
+ quint64 m_id;
+ QByteArray m_objectName;
+};
+
+class Q_GUI_EXPORT QRhiBuffer : public QRhiResource
+{
+public:
+ enum Type {
+ Immutable,
+ Static,
+ Dynamic
+ };
+
+ enum UsageFlag {
+ VertexBuffer = 1 << 0,
+ IndexBuffer = 1 << 1,
+ UniformBuffer = 1 << 2
+ };
+ Q_DECLARE_FLAGS(UsageFlags, UsageFlag)
+
+ QRhiResource::Type resourceType() const override;
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ UsageFlags usage() const { return m_usage; }
+ void setUsage(UsageFlags u) { m_usage = u; }
+
+ int size() const { return m_size; }
+ void setSize(int sz) { m_size = sz; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_);
+ Type m_type;
+ UsageFlags m_usage;
+ int m_size;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiBuffer::UsageFlags)
+
+class Q_GUI_EXPORT QRhiTexture : public QRhiResource
+{
+public:
+ enum Flag {
+ RenderTarget = 1 << 0,
+ CubeMap = 1 << 2,
+ MipMapped = 1 << 3,
+ sRGB = 1 << 4,
+ UsedAsTransferSource = 1 << 5,
+ UsedWithGenerateMips = 1 << 6
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum Format {
+ UnknownFormat,
+
+ RGBA8,
+ BGRA8,
+ R8,
+ R16,
+ RED_OR_ALPHA8,
+
+ RGBA16F,
+ RGBA32F,
+
+ D16,
+ D32F,
+
+ BC1,
+ BC2,
+ BC3,
+ BC4,
+ BC5,
+ BC6H,
+ BC7,
+
+ ETC2_RGB8,
+ ETC2_RGB8A1,
+ ETC2_RGBA8,
+
+ ASTC_4x4,
+ ASTC_5x4,
+ ASTC_5x5,
+ ASTC_6x5,
+ ASTC_6x6,
+ ASTC_8x5,
+ ASTC_8x6,
+ ASTC_8x8,
+ ASTC_10x5,
+ ASTC_10x6,
+ ASTC_10x8,
+ ASTC_10x10,
+ ASTC_12x10,
+ ASTC_12x12
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Format format() const { return m_format; }
+ void setFormat(Format fmt) { m_format = fmt; }
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ virtual bool build() = 0;
+ virtual const QRhiNativeHandles *nativeHandles();
+ virtual bool buildFrom(const QRhiNativeHandles *src);
+
+protected:
+ QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_);
+ Format m_format;
+ QSize m_pixelSize;
+ int m_sampleCount;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTexture::Flags)
+
+class Q_GUI_EXPORT QRhiSampler : public QRhiResource
+{
+public:
+ enum Filter {
+ None,
+ Nearest,
+ Linear
+ };
+
+ enum AddressMode {
+ Repeat,
+ ClampToEdge,
+ Border,
+ Mirror,
+ MirrorOnce
+ };
+
+ enum CompareOp {
+ Never,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Filter magFilter() const { return m_magFilter; }
+ void setMagFilter(Filter f) { m_magFilter = f; }
+
+ Filter minFilter() const { return m_minFilter; }
+ void setMinFilter(Filter f) { m_minFilter = f; }
+
+ Filter mipmapMode() const { return m_mipmapMode; }
+ void setMipmapMode(Filter f) { m_mipmapMode = f; }
+
+ AddressMode addressU() const { return m_addressU; }
+ void setAddressU(AddressMode mode) { m_addressU = mode; }
+
+ AddressMode addressV() const { return m_addressV; }
+ void setAddressV(AddressMode mode) { m_addressV = mode; }
+
+ AddressMode addressW() const { return m_addressW; }
+ void setAddressW(AddressMode mode) { m_addressW = mode; }
+
+ CompareOp textureCompareOp() const { return m_compareOp; }
+ void setTextureCompareOp(CompareOp op) { m_compareOp = op; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiSampler(QRhiImplementation *rhi,
+ Filter magFilter_, Filter minFilter_, Filter mipmapMode_,
+ AddressMode u_, AddressMode v_);
+ Filter m_magFilter;
+ Filter m_minFilter;
+ Filter m_mipmapMode;
+ AddressMode m_addressU;
+ AddressMode m_addressV;
+ AddressMode m_addressW;
+ CompareOp m_compareOp;
+};
+
+class Q_GUI_EXPORT QRhiRenderBuffer : public QRhiResource
+{
+public:
+ enum Type {
+ DepthStencil,
+ Color
+ };
+
+ enum Flag {
+ UsedWithSwapChainOnly = 1 << 0
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags h) { m_flags = h; }
+
+ virtual bool build() = 0;
+
+ virtual QRhiTexture::Format backingFormat() const = 0;
+
+protected:
+ QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_);
+ Type m_type;
+ QSize m_pixelSize;
+ int m_sampleCount;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiRenderBuffer::Flags)
+
+class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+protected:
+ QRhiRenderPassDescriptor(QRhiImplementation *rhi);
+};
+
+class Q_GUI_EXPORT QRhiRenderTarget : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+ virtual QSize pixelSize() const = 0;
+ virtual float devicePixelRatio() const = 0;
+ virtual int sampleCount() const = 0;
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+protected:
+ QRhiRenderTarget(QRhiImplementation *rhi);
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+};
+
+class Q_GUI_EXPORT QRhiTextureRenderTarget : public QRhiRenderTarget
+{
+public:
+ enum Flag {
+ PreserveColorContents = 1 << 0,
+ PreserveDepthStencilContents = 1 << 1
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ QRhiTextureRenderTargetDescription description() const { return m_desc; }
+ void setDescription(const QRhiTextureRenderTargetDescription &desc) { m_desc = desc; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0;
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc_, Flags flags_);
+ QRhiTextureRenderTargetDescription m_desc;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTextureRenderTarget::Flags)
+
+class Q_GUI_EXPORT QRhiShaderResourceBindings : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+ QVector<QRhiShaderResourceBinding> bindings() const { return m_bindings; }
+ void setBindings(const QVector<QRhiShaderResourceBinding> &b) { m_bindings = b; }
+
+ bool isLayoutCompatible(const QRhiShaderResourceBindings *other) const;
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiShaderResourceBindings(QRhiImplementation *rhi);
+ QVector<QRhiShaderResourceBinding> m_bindings;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &);
+#endif
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &);
+#endif
+
+class Q_GUI_EXPORT QRhiGraphicsPipeline : public QRhiResource
+{
+public:
+ enum Flag {
+ UsesBlendConstants = 1 << 0,
+ UsesStencilRef = 1 << 1,
+ UsesScissor = 1 << 2
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum Topology {
+ Triangles,
+ TriangleStrip,
+ Lines,
+ LineStrip,
+ Points
+ };
+
+ enum CullMode {
+ None,
+ Front,
+ Back
+ };
+
+ enum FrontFace {
+ CCW,
+ CW
+ };
+
+ enum ColorMaskComponent {
+ R = 1 << 0,
+ G = 1 << 1,
+ B = 1 << 2,
+ A = 1 << 3
+ };
+ Q_DECLARE_FLAGS(ColorMask, ColorMaskComponent)
+
+ enum BlendFactor {
+ Zero,
+ One,
+ SrcColor,
+ OneMinusSrcColor,
+ DstColor,
+ OneMinusDstColor,
+ SrcAlpha,
+ OneMinusSrcAlpha,
+ DstAlpha,
+ OneMinusDstAlpha,
+ ConstantColor,
+ OneMinusConstantColor,
+ ConstantAlpha,
+ OneMinusConstantAlpha,
+ SrcAlphaSaturate,
+ Src1Color,
+ OneMinusSrc1Color,
+ Src1Alpha,
+ OneMinusSrc1Alpha
+ };
+
+ enum BlendOp {
+ Add,
+ Subtract,
+ ReverseSubtract,
+ Min,
+ Max
+ };
+
+ struct TargetBlend {
+ ColorMask colorWrite = ColorMask(0xF); // R | G | B | A
+ bool enable = false;
+ BlendFactor srcColor = One;
+ BlendFactor dstColor = OneMinusSrcAlpha;
+ BlendOp opColor = Add;
+ BlendFactor srcAlpha = One;
+ BlendFactor dstAlpha = OneMinusSrcAlpha;
+ BlendOp opAlpha = Add;
+ };
+
+ enum CompareOp {
+ Never,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always
+ };
+
+ enum StencilOp {
+ StencilZero,
+ Keep,
+ Replace,
+ IncrementAndClamp,
+ DecrementAndClamp,
+ Invert,
+ IncrementAndWrap,
+ DecrementAndWrap
+ };
+
+ struct StencilOpState {
+ StencilOp failOp = Keep;
+ StencilOp depthFailOp = Keep;
+ StencilOp passOp = Keep;
+ CompareOp compareOp = Always;
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ Topology topology() const { return m_topology; }
+ void setTopology(Topology t) { m_topology = t; }
+
+ CullMode cullMode() const { return m_cullMode; }
+ void setCullMode(CullMode mode) { m_cullMode = mode; }
+
+ FrontFace frontFace() const { return m_frontFace; }
+ void setFrontFace(FrontFace f) { m_frontFace = f; }
+
+ QVector<TargetBlend> targetBlends() const { return m_targetBlends; }
+ void setTargetBlends(const QVector<TargetBlend> &blends) { m_targetBlends = blends; }
+
+ bool hasDepthTest() const { return m_depthTest; }
+ void setDepthTest(bool enable) { m_depthTest = enable; }
+
+ bool hasDepthWrite() const { return m_depthWrite; }
+ void setDepthWrite(bool enable) { m_depthWrite = enable; }
+
+ CompareOp depthOp() const { return m_depthOp; }
+ void setDepthOp(CompareOp op) { m_depthOp = op; }
+
+ bool hasStencilTest() const { return m_stencilTest; }
+ void setStencilTest(bool enable) { m_stencilTest = enable; }
+
+ StencilOpState stencilFront() const { return m_stencilFront; }
+ void setStencilFront(const StencilOpState &state) { m_stencilFront = state; }
+
+ StencilOpState stencilBack() const { return m_stencilBack; }
+ void setStencilBack(const StencilOpState &state) { m_stencilBack = state; }
+
+ quint32 stencilReadMask() const { return m_stencilReadMask; }
+ void setStencilReadMask(quint32 mask) { m_stencilReadMask = mask; }
+
+ quint32 stencilWriteMask() const { return m_stencilWriteMask; }
+ void setStencilWriteMask(quint32 mask) { m_stencilWriteMask = mask; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ QVector<QRhiGraphicsShaderStage> shaderStages() const { return m_shaderStages; }
+ void setShaderStages(const QVector<QRhiGraphicsShaderStage> &stages) { m_shaderStages = stages; }
+
+ QRhiVertexInputLayout vertexInputLayout() const { return m_vertexInputLayout; }
+ void setVertexInputLayout(const QRhiVertexInputLayout &layout) { m_vertexInputLayout = layout; }
+
+ QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; }
+ void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; }
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiGraphicsPipeline(QRhiImplementation *rhi);
+ Flags m_flags;
+ Topology m_topology = Triangles;
+ CullMode m_cullMode = None;
+ FrontFace m_frontFace = CCW;
+ QVector<TargetBlend> m_targetBlends;
+ bool m_depthTest = false;
+ bool m_depthWrite = false;
+ CompareOp m_depthOp = Less;
+ bool m_stencilTest = false;
+ StencilOpState m_stencilFront;
+ StencilOpState m_stencilBack;
+ quint32 m_stencilReadMask = 0xFF;
+ quint32 m_stencilWriteMask = 0xFF;
+ int m_sampleCount = 1;
+ QVector<QRhiGraphicsShaderStage> m_shaderStages;
+ QRhiVertexInputLayout m_vertexInputLayout;
+ QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr;
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::Flags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::ColorMask)
+Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiSwapChain : public QRhiResource
+{
+public:
+ enum Flag {
+ SurfaceHasPreMulAlpha = 1 << 0,
+ SurfaceHasNonPreMulAlpha = 1 << 1,
+ sRGB = 1 << 2,
+ UsedAsTransferSource = 1 << 3,
+ NoVSync = 1 << 4,
+ MinimalBufferCount = 1 << 5
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ QWindow *window() const { return m_window; }
+ void setWindow(QWindow *window) { m_window = window; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ QRhiRenderBuffer *depthStencil() const { return m_depthStencil; }
+ void setDepthStencil(QRhiRenderBuffer *ds) { m_depthStencil = ds; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int samples) { m_sampleCount = samples; }
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+ QSize currentPixelSize() const { return m_currentPixelSize; }
+
+ virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0;
+ virtual QRhiRenderTarget *currentFrameRenderTarget() = 0;
+ virtual QSize surfacePixelSize() = 0;
+ virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0;
+ virtual bool buildOrResize() = 0;
+
+protected:
+ QRhiSwapChain(QRhiImplementation *rhi);
+ QWindow *m_window = nullptr;
+ Flags m_flags;
+ QRhiRenderBuffer *m_depthStencil = nullptr;
+ int m_sampleCount = 1;
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+ QSize m_currentPixelSize;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags)
+
+class Q_GUI_EXPORT QRhiCommandBuffer : public QRhiResource
+{
+public:
+ enum IndexFormat {
+ IndexUInt16,
+ IndexUInt32
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates);
+
+ void beginPass(QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+ void endPass(QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+
+ void setGraphicsPipeline(QRhiGraphicsPipeline *ps);
+ using DynamicOffset = QPair<int, quint32>; // binding, offset
+ void setShaderResources(QRhiShaderResourceBindings *srb = nullptr,
+ int dynamicOffsetCount = 0,
+ const DynamicOffset *dynamicOffsets = nullptr);
+ using VertexInput = QPair<QRhiBuffer *, quint32>; // buffer, offset
+ void setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings,
+ QRhiBuffer *indexBuf = nullptr, quint32 indexOffset = 0,
+ IndexFormat indexFormat = IndexUInt16);
+
+ void setViewport(const QRhiViewport &viewport);
+ void setScissor(const QRhiScissor &scissor);
+ void setBlendConstants(const QColor &c);
+ void setStencilRef(quint32 refValue);
+
+ void draw(quint32 vertexCount,
+ quint32 instanceCount = 1,
+ quint32 firstVertex = 0,
+ quint32 firstInstance = 0);
+
+ void drawIndexed(quint32 indexCount,
+ quint32 instanceCount = 1,
+ quint32 firstIndex = 0,
+ qint32 vertexOffset = 0,
+ quint32 firstInstance = 0);
+
+ void debugMarkBegin(const QByteArray &name);
+ void debugMarkEnd();
+ void debugMarkMsg(const QByteArray &msg);
+
+ const QRhiNativeHandles *nativeHandles();
+ void beginExternal();
+ void endExternal();
+
+protected:
+ QRhiCommandBuffer(QRhiImplementation *rhi);
+};
+
+struct Q_GUI_EXPORT QRhiReadbackResult
+{
+ std::function<void()> completed = nullptr;
+ QRhiTexture::Format format;
+ QSize pixelSize;
+ QByteArray data;
+}; // non-movable due to the std::function
+
+class Q_GUI_EXPORT QRhiResourceUpdateBatch
+{
+public:
+ ~QRhiResourceUpdateBatch();
+
+ void release();
+
+ void merge(QRhiResourceUpdateBatch *other);
+
+ void updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
+ void uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
+ void uploadStaticBuffer(QRhiBuffer *buf, const void *data);
+ void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc);
+ void uploadTexture(QRhiTexture *tex, const QImage &image);
+ void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription());
+ void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result);
+ void generateMips(QRhiTexture *tex, int layer = 0);
+
+private:
+ QRhiResourceUpdateBatch(QRhiImplementation *rhi);
+ Q_DISABLE_COPY(QRhiResourceUpdateBatch)
+ QRhiResourceUpdateBatchPrivate *d;
+ friend class QRhiResourceUpdateBatchPrivate;
+ friend class QRhi;
+};
+
+struct Q_GUI_EXPORT QRhiInitParams
+{
+};
+
+class Q_GUI_EXPORT QRhi
+{
+public:
+ enum Implementation {
+ Null,
+ Vulkan,
+ OpenGLES2,
+ D3D11,
+ Metal
+ };
+
+ enum Flag {
+ EnableProfiling = 1 << 0,
+ EnableDebugMarkers = 1 << 1
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum FrameOpResult {
+ FrameOpSuccess = 0,
+ FrameOpError,
+ FrameOpSwapChainOutOfDate,
+ FrameOpDeviceLost
+ };
+
+ enum Feature {
+ MultisampleTexture = 1,
+ MultisampleRenderBuffer,
+ DebugMarkers,
+ Timestamps,
+ Instancing,
+ CustomInstanceStepRate,
+ PrimitiveRestart,
+ NonDynamicUniformBuffers,
+ NonFourAlignedEffectiveIndexBufferOffset,
+ NPOTTextureRepeat,
+ RedOrAlpha8IsRed,
+ ElementIndexUint
+ };
+
+ enum BeginFrameFlag {
+ };
+ Q_DECLARE_FLAGS(BeginFrameFlags, BeginFrameFlag)
+
+ enum EndFrameFlag {
+ SkipPresent = 1 << 0
+ };
+ Q_DECLARE_FLAGS(EndFrameFlags, EndFrameFlag)
+
+ enum ResourceLimit {
+ TextureSizeMin = 1,
+ TextureSizeMax,
+ MaxColorAttachments,
+ FramesInFlight
+ };
+
+ ~QRhi();
+
+ static QRhi *create(Implementation impl,
+ QRhiInitParams *params,
+ Flags flags = Flags(),
+ QRhiNativeHandles *importDevice = nullptr);
+
+ Implementation backend() const;
+ QThread *thread() const;
+
+ using CleanupCallback = std::function<void(QRhi *)>;
+ void addCleanupCallback(const CleanupCallback &callback);
+ void runCleanup();
+
+ QRhiGraphicsPipeline *newGraphicsPipeline();
+ QRhiShaderResourceBindings *newShaderResourceBindings();
+
+ QRhiBuffer *newBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size);
+
+ QRhiRenderBuffer *newRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount = 1,
+ QRhiRenderBuffer::Flags flags = QRhiRenderBuffer::Flags());
+
+ QRhiTexture *newTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount = 1,
+ QRhiTexture::Flags flags = QRhiTexture::Flags());
+
+ QRhiSampler *newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v);
+
+ QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags = QRhiTextureRenderTarget::Flags());
+
+ QRhiSwapChain *newSwapChain();
+ FrameOpResult beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags = BeginFrameFlags());
+ FrameOpResult endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags = EndFrameFlags());
+ bool isRecordingFrame() const;
+ int currentFrameSlot() const;
+
+ FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb);
+ FrameOpResult endOffscreenFrame();
+
+ QRhi::FrameOpResult finish();
+
+ QRhiResourceUpdateBatch *nextResourceUpdateBatch();
+
+ QVector<int> supportedSampleCounts() const;
+
+ int ubufAlignment() const;
+ int ubufAligned(int v) const;
+
+ int mipLevelsForSize(const QSize &size) const;
+ QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const;
+
+ bool isYUpInFramebuffer() const;
+ bool isYUpInNDC() const;
+ bool isClipDepthZeroToOne() const;
+
+ QMatrix4x4 clipSpaceCorrMatrix() const;
+
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = QRhiTexture::Flags()) const;
+ bool isFeatureSupported(QRhi::Feature feature) const;
+ int resourceLimit(ResourceLimit limit) const;
+
+ const QRhiNativeHandles *nativeHandles();
+
+ QRhiProfiler *profiler();
+
+ static const int MAX_LAYERS = 6; // cubemaps only
+ static const int MAX_LEVELS = 16; // a width and/or height of 65536 should be enough for everyone
+
+protected:
+ QRhi();
+
+private:
+ Q_DISABLE_COPY(QRhi)
+ QRhiImplementation *d = nullptr;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::Flags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::BeginFrameFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::EndFrameFlags)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h
new file mode 100644
index 0000000000..7c110431fb
--- /dev/null
+++ b/src/gui/rhi/qrhi_p_p.h
@@ -0,0 +1,544 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHI_P_H
+#define QRHI_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhi_p.h"
+#include "qrhiprofiler_p_p.h"
+#include <QBitArray>
+#include <QAtomicInt>
+#include <QAtomicInteger>
+
+QT_BEGIN_NAMESPACE
+
+#define QRHI_RES(t, x) static_cast<t *>(x)
+#define QRHI_RES_RHI(t) t *rhiD = static_cast<t *>(m_rhi)
+#define QRHI_PROF QRhiProfilerPrivate *rhiP = m_rhi->profilerPrivateOrNull()
+#define QRHI_PROF_F(f) for (bool qrhip_enabled = rhiP != nullptr; qrhip_enabled; qrhip_enabled = false) rhiP->f
+
+class QRhiImplementation
+{
+public:
+ virtual ~QRhiImplementation();
+
+ virtual bool create(QRhi::Flags flags) = 0;
+ virtual void destroy() = 0;
+
+ virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0;
+ virtual QRhiShaderResourceBindings *createShaderResourceBindings() = 0;
+ virtual QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) = 0;
+ virtual QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) = 0;
+ virtual QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) = 0;
+ virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) = 0;
+
+ virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) = 0;
+
+ virtual QRhiSwapChain *createSwapChain() = 0;
+ virtual QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) = 0;
+ virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) = 0;
+ virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) = 0;
+ virtual QRhi::FrameOpResult endOffscreenFrame() = 0;
+ virtual QRhi::FrameOpResult finish() = 0;
+
+ virtual void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+
+ virtual void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) = 0;
+ virtual void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+
+ virtual void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) = 0;
+
+ virtual void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) = 0;
+
+ virtual void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) = 0;
+
+ virtual void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) = 0;
+ virtual void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) = 0;
+ virtual void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) = 0;
+ virtual void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) = 0;
+
+ virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0;
+ virtual void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) = 0;
+
+ virtual void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) = 0;
+ virtual void debugMarkEnd(QRhiCommandBuffer *cb) = 0;
+ virtual void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) = 0;
+
+ virtual const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) = 0;
+ virtual void beginExternal(QRhiCommandBuffer *cb) = 0;
+ virtual void endExternal(QRhiCommandBuffer *cb) = 0;
+
+ virtual QVector<int> supportedSampleCounts() const = 0;
+ virtual int ubufAlignment() const = 0;
+ virtual bool isYUpInFramebuffer() const = 0;
+ virtual bool isYUpInNDC() const = 0;
+ virtual bool isClipDepthZeroToOne() const = 0;
+ virtual QMatrix4x4 clipSpaceCorrMatrix() const = 0;
+ virtual bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const = 0;
+ virtual bool isFeatureSupported(QRhi::Feature feature) const = 0;
+ virtual int resourceLimit(QRhi::ResourceLimit limit) const = 0;
+ virtual const QRhiNativeHandles *nativeHandles() = 0;
+ virtual void sendVMemStatsToProfiler() = 0;
+
+ bool isCompressedFormat(QRhiTexture::Format format) const;
+ void compressedFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize,
+ QSize *blockDim) const;
+ void textureFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize) const;
+ quint32 approxByteSizeForTexture(QRhiTexture::Format format, const QSize &baseSize,
+ int mipCount, int layerCount);
+
+ QRhiProfilerPrivate *profilerPrivateOrNull()
+ {
+ // return null when QRhi::EnableProfiling was not set
+ QRhiProfilerPrivate *p = QRhiProfilerPrivate::get(&profiler);
+ return p->rhiDWhenEnabled ? p : nullptr;
+ }
+
+ // only really care about resources that own native graphics resources underneath
+ void registerResource(QRhiResource *res)
+ {
+ resources.insert(res);
+ }
+
+ void unregisterResource(QRhiResource *res)
+ {
+ resources.remove(res);
+ }
+
+ QSet<QRhiResource *> activeResources() const
+ {
+ return resources;
+ }
+
+ void addReleaseAndDestroyLater(QRhiResource *res)
+ {
+ if (inFrame)
+ pendingReleaseAndDestroyResources.insert(res);
+ else
+ delete res;
+ }
+
+ void addCleanupCallback(const QRhi::CleanupCallback &callback)
+ {
+ cleanupCallbacks.append(callback);
+ }
+
+ QRhi *q;
+
+protected:
+ bool debugMarkers = false;
+ int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11.
+
+private:
+ QRhi::Implementation implType;
+ QThread *implThread;
+ QRhiProfiler profiler;
+ QVector<QRhiResourceUpdateBatch *> resUpdPool;
+ QBitArray resUpdPoolMap;
+ QSet<QRhiResource *> resources;
+ QSet<QRhiResource *> pendingReleaseAndDestroyResources;
+ QVector<QRhi::CleanupCallback> cleanupCallbacks;
+ bool inFrame = false;
+
+ friend class QRhi;
+ friend class QRhiResourceUpdateBatchPrivate;
+};
+
+template<typename T, size_t N>
+bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array<T, N> &r,
+ T *x, T *y, T *w, T *h)
+{
+ // x,y are bottom-left in QRhiScissor and QRhiViewport but top-left in
+ // Vulkan/Metal/D3D. We also need proper clamping since some
+ // validation/debug layers are allergic to out of bounds scissor or
+ // viewport rects.
+
+ const T outputWidth = outputSize.width();
+ const T outputHeight = outputSize.height();
+ const T inputWidth = r[2];
+ const T inputHeight = r[3];
+
+ *x = qMax<T>(0, r[0]);
+ *y = qMax<T>(0, outputHeight - (r[1] + inputHeight));
+ *w = inputWidth;
+ *h = inputHeight;
+
+ if (*x >= outputWidth || *y >= outputHeight)
+ return false;
+
+ if (*x + *w > outputWidth)
+ *w = outputWidth - *x;
+ if (*y + *h > outputHeight)
+ *h = outputHeight - *y;
+
+ return true;
+}
+
+class QRhiResourceUpdateBatchPrivate
+{
+public:
+ struct DynamicBufferUpdate {
+ DynamicBufferUpdate() { }
+ DynamicBufferUpdate(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
+ : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_)
+ { }
+
+ QRhiBuffer *buf = nullptr;
+ int offset = 0;
+ QByteArray data;
+ };
+
+ struct StaticBufferUpload {
+ StaticBufferUpload() { }
+ StaticBufferUpload(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
+ : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_ ? size_ : buf_->size())
+ { }
+
+ QRhiBuffer *buf = nullptr;
+ int offset = 0;
+ QByteArray data;
+ };
+
+ struct TextureOp {
+ enum Type {
+ Upload,
+ Copy,
+ Read,
+ MipGen
+ };
+ Type type;
+ struct SUpload {
+ QRhiTexture *tex = nullptr;
+ // Specifying multiple uploads for a subresource must be supported.
+ // In the backend this can then end up, where applicable, as a
+ // single, batched copy operation with only one set of barriers.
+ // This helps when doing for example glyph cache fills.
+ QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
+ } upload;
+ struct SCopy {
+ QRhiTexture *dst = nullptr;
+ QRhiTexture *src = nullptr;
+ QRhiTextureCopyDescription desc;
+ } copy;
+ struct SRead {
+ QRhiReadbackDescription rb;
+ QRhiReadbackResult *result;
+ } read;
+ struct SMipGen {
+ QRhiTexture *tex = nullptr;
+ int layer = 0;
+ } mipgen;
+
+ static TextureOp textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
+ {
+ TextureOp op;
+ op.type = Upload;
+ op.upload.tex = tex;
+ const QVector<QRhiTextureUploadEntry> &entries(desc.entries());
+ for (const QRhiTextureUploadEntry &entry : entries)
+ op.upload.subresDesc[entry.layer()][entry.level()].append(entry.description());
+ return op;
+ }
+
+ static TextureOp textureCopy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
+ {
+ TextureOp op;
+ op.type = Copy;
+ op.copy.dst = dst;
+ op.copy.src = src;
+ op.copy.desc = desc;
+ return op;
+ }
+
+ static TextureOp textureRead(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
+ {
+ TextureOp op;
+ op.type = Read;
+ op.read.rb = rb;
+ op.read.result = result;
+ return op;
+ }
+
+ static TextureOp textureMipGen(QRhiTexture *tex, int layer)
+ {
+ TextureOp op;
+ op.type = MipGen;
+ op.mipgen.tex = tex;
+ op.mipgen.layer = layer;
+ return op;
+ }
+ };
+
+ QVector<DynamicBufferUpdate> dynamicBufferUpdates;
+ QVector<StaticBufferUpload> staticBufferUploads;
+ QVector<TextureOp> textureOps;
+
+ QRhiResourceUpdateBatch *q = nullptr;
+ QRhiImplementation *rhi = nullptr;
+ int poolIndex = -1;
+
+ void free();
+ void merge(QRhiResourceUpdateBatchPrivate *other);
+
+ static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; }
+};
+
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::StaticBufferUpload, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::TextureOp, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiShaderResourceBindingPrivate
+{
+public:
+ QRhiShaderResourceBindingPrivate()
+ : ref(1)
+ {
+ }
+
+ QRhiShaderResourceBindingPrivate(const QRhiShaderResourceBindingPrivate *other)
+ : ref(1),
+ binding(other->binding),
+ stage(other->stage),
+ type(other->type),
+ u(other->u)
+ {
+ }
+
+ static QRhiShaderResourceBindingPrivate *get(QRhiShaderResourceBinding *s) { return s->d; }
+ static const QRhiShaderResourceBindingPrivate *get(const QRhiShaderResourceBinding *s) { return s->d; }
+
+ QAtomicInt ref;
+ int binding;
+ QRhiShaderResourceBinding::StageFlags stage;
+ QRhiShaderResourceBinding::Type type;
+ struct UniformBufferData {
+ QRhiBuffer *buf;
+ int offset;
+ int maybeSize;
+ bool hasDynamicOffset;
+ };
+ struct SampledTextureData {
+ QRhiTexture *tex;
+ QRhiSampler *sampler;
+ };
+ union {
+ UniformBufferData ubuf;
+ SampledTextureData stex;
+ } u;
+};
+
+template<typename T>
+struct QRhiBatchedBindings
+{
+ void feed(int binding, T resource) { // binding must be strictly increasing
+ if (curBinding == -1 || binding > curBinding + 1) {
+ finish();
+ curBatch.startBinding = binding;
+ curBatch.resources.clear();
+ curBatch.resources.append(resource);
+ } else {
+ Q_ASSERT(binding == curBinding + 1);
+ curBatch.resources.append(resource);
+ }
+ curBinding = binding;
+ }
+
+ void finish() {
+ if (!curBatch.resources.isEmpty())
+ batches.append(curBatch);
+ }
+
+ void clear() {
+ batches.clear();
+ curBatch.resources.clear();
+ curBinding = -1;
+ }
+
+ struct Batch {
+ uint startBinding;
+ QVarLengthArray<T, 4> resources;
+
+ bool operator==(const Batch &other) const
+ {
+ return startBinding == other.startBinding && resources == other.resources;
+ }
+
+ bool operator!=(const Batch &other) const
+ {
+ return !operator==(other);
+ }
+ };
+
+ QVarLengthArray<Batch, 4> batches; // sorted by startBinding
+
+ bool operator==(const QRhiBatchedBindings<T> &other) const
+ {
+ return batches == other.batches;
+ }
+
+ bool operator!=(const QRhiBatchedBindings<T> &other) const
+ {
+ return !operator==(other);
+ }
+
+private:
+ Batch curBatch;
+ int curBinding = -1;
+};
+
+class QRhiGlobalObjectIdGenerator
+{
+public:
+#ifdef Q_ATOMIC_INT64_IS_SUPPORTED
+ using Type = quint64;
+#else
+ using Type = quint32;
+#endif
+ static Type newId();
+
+private:
+ QAtomicInteger<Type> counter;
+};
+
+class QRhiPassResourceTracker
+{
+public:
+ bool isEmpty() const;
+ void reset();
+
+ struct UsageState {
+ int layout;
+ int access;
+ int stage;
+ };
+
+ enum BufferStage {
+ BufVertexInputStage,
+ BufVertexStage,
+ BufFragmentStage
+ };
+
+ enum BufferAccess {
+ BufVertexInput,
+ BufIndexRead,
+ BufUniformRead
+ };
+
+ void registerBufferOnce(QRhiBuffer *buf, int slot, BufferAccess access, BufferStage stage,
+ const UsageState &stateAtPassBegin);
+
+ enum TextureStage {
+ TexVertexStage,
+ TexFragmentStage,
+ TexColorOutputStage,
+ TexDepthOutputStage
+ };
+
+ enum TextureAccess {
+ TexSample,
+ TexColorOutput,
+ TexDepthOutput
+ };
+
+ void registerTextureOnce(QRhiTexture *tex, TextureAccess access, TextureStage stage,
+ const UsageState &stateAtPassBegin);
+
+ struct Buffer {
+ QRhiBuffer *buf;
+ int slot;
+ BufferAccess access;
+ BufferStage stage;
+ UsageState stateAtPassBegin;
+ };
+ const QVector<Buffer> *buffers() const { return &m_buffers; }
+
+ struct Texture {
+ QRhiTexture *tex;
+ TextureAccess access;
+ TextureStage stage;
+ UsageState stateAtPassBegin;
+ };
+ const QVector<Texture> *textures() const { return &m_textures; }
+
+private:
+ QVector<Buffer> m_buffers;
+ QVector<Texture> m_textures;
+};
+
+Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Buffer, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Texture, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
new file mode 100644
index 0000000000..07e67d95d9
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -0,0 +1,3388 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhid3d11_p_p.h"
+#include "qshader_p.h"
+#include <QWindow>
+#include <QOperatingSystemVersion>
+#include <qmath.h>
+
+#include <d3dcompiler.h>
+#include <comdef.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Direct3D 11 backend. Provides a double-buffered flip model (FLIP_DISCARD)
+ swapchain. Textures and "static" buffers are USAGE_DEFAULT, leaving it to
+ UpdateSubResource to upload the data in any way it sees fit. "Dynamic"
+ buffers are USAGE_DYNAMIC and updating is done by mapping with WRITE_DISCARD.
+ (so here QRhiBuffer keeps a copy of the buffer contents and all of it is
+ memcpy'd every time, leaving the rest (juggling with the memory area Map
+ returns) to the driver).
+*/
+
+/*!
+ \class QRhiD3D11InitParams
+ \inmodule QtRhi
+ \brief Direct3D 11 specific initialization parameters.
+
+ A D3D11-based QRhi needs no special parameters for initialization. If
+ desired, enableDebugLayer can be set to \c true to enable the Direct3D
+ debug layer. This can be useful during development, but should be avoided
+ in production builds.
+
+ \badcode
+ QRhiD3D11InitParams params;
+ params.enableDebugLayer = true;
+ rhi = QRhi::create(QRhi::D3D11, &params);
+ \endcode
+
+ \note QRhiSwapChain should only be used in combination with QWindow
+ instances that have their surface type set to QSurface::OpenGLSurface.
+ There are currently no Direct3D specifics in the Windows platform support
+ of Qt and therefore there is no separate QSurface type available.
+
+ \section2 Working with existing Direct3D 11 devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Direct3D device. This can be
+ achieved by passing a pointer to a QRhiD3D11NativeHandles to
+ QRhi::create(). Both the device and the device context must be set to a
+ non-null value then.
+
+ The QRhi does not take ownership of any of the external objects.
+
+ \note QRhi works with immediate contexts only. Deferred contexts are not
+ used in any way.
+
+ \note Regardless of using an imported or a QRhi-created device context, the
+ \c ID3D11DeviceContext1 interface (Direct3D 11.1) must be supported.
+ Initialization will fail otherwise.
+ */
+
+/*!
+ \class QRhiD3D11NativeHandles
+ \inmodule QtRhi
+ \brief Holds the D3D device and device context used by the QRhi.
+
+ \note The class uses \c{void *} as the type since including the COM-based
+ \c{d3d11.h} headers is not acceptable here. The actual types are
+ \c{ID3D11Device *} and \c{ID3D11DeviceContext *}.
+ */
+
+/*!
+ \class QRhiD3D11TextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the D3D texture object that is backing a QRhiTexture instance.
+
+ \note The class uses \c{void *} as the type since including the COM-based
+ \c{d3d11.h} headers is not acceptable here. The actual type is
+ \c{ID3D11Texture2D *}.
+ */
+
+QRhiD3D11::QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice)
+ : ofr(this)
+{
+ debugLayer = params->enableDebugLayer;
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ dev = reinterpret_cast<ID3D11Device *>(importDevice->dev);
+ if (dev) {
+ ID3D11DeviceContext *ctx = reinterpret_cast<ID3D11DeviceContext *>(importDevice->context);
+ if (SUCCEEDED(ctx->QueryInterface(IID_ID3D11DeviceContext1, reinterpret_cast<void **>(&context)))) {
+ // get rid of the ref added by QueryInterface
+ ctx->Release();
+ } else {
+ qWarning("ID3D11DeviceContext1 not supported by context, cannot import");
+ importedDevice = false;
+ }
+ } else {
+ qWarning("No ID3D11Device given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+static QString comErrorMessage(HRESULT hr)
+{
+#ifndef Q_OS_WINRT
+ const _com_error comError(hr);
+#else
+ const _com_error comError(hr, nullptr);
+#endif
+ QString result = QLatin1String("Error 0x") + QString::number(ulong(hr), 16);
+ if (const wchar_t *msg = comError.ErrorMessage())
+ result += QLatin1String(": ") + QString::fromWCharArray(msg);
+ return result;
+}
+
+static inline uint aligned(uint v, uint byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+bool QRhiD3D11::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+
+ uint devFlags = 0;
+ if (debugLayer)
+ devFlags |= D3D11_CREATE_DEVICE_DEBUG;
+
+ HRESULT hr;
+#if !defined(Q_CC_MINGW)
+ hasDxgi2 = QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7;
+ if (hasDxgi2)
+ hr = CreateDXGIFactory2(0, IID_IDXGIFactory2, reinterpret_cast<void **>(&dxgiFactory));
+ else
+#endif
+ hr = CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast<void **>(&dxgiFactory));
+
+ if (FAILED(hr)) {
+ qWarning("Failed to create DXGI factory: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (!importedDevice) {
+ IDXGIAdapter1 *adapterToUse = nullptr;
+ IDXGIAdapter1 *adapter;
+ int requestedAdapterIndex = -1;
+ if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
+ requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
+ for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+ DXGI_ADAPTER_DESC1 desc;
+ adapter->GetDesc1(&desc);
+ const QString name = QString::fromUtf16((char16_t *) desc.Description);
+ qDebug("Adapter %d: '%s' (flags 0x%x)", adapterIndex, qPrintable(name), desc.Flags);
+ if (!adapterToUse && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
+ adapterToUse = adapter;
+ qDebug(" using this adapter");
+ } else {
+ adapter->Release();
+ }
+ }
+ if (!adapterToUse) {
+ qWarning("No adapter");
+ return false;
+ }
+
+ ID3D11DeviceContext *ctx = nullptr;
+ HRESULT hr = D3D11CreateDevice(adapterToUse, D3D_DRIVER_TYPE_UNKNOWN, nullptr, devFlags,
+ nullptr, 0, D3D11_SDK_VERSION,
+ &dev, &featureLevel, &ctx);
+ adapterToUse->Release();
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 device and context: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (SUCCEEDED(ctx->QueryInterface(IID_ID3D11DeviceContext1, reinterpret_cast<void **>(&context)))) {
+ ctx->Release();
+ } else {
+ qWarning("ID3D11DeviceContext1 not supported");
+ return false;
+ }
+ } else {
+ Q_ASSERT(dev && context);
+ featureLevel = dev->GetFeatureLevel();
+ }
+
+ if (FAILED(context->QueryInterface(IID_ID3DUserDefinedAnnotation, reinterpret_cast<void **>(&annotations))))
+ annotations = nullptr;
+
+ nativeHandlesStruct.dev = dev;
+ nativeHandlesStruct.context = context;
+
+ return true;
+}
+
+void QRhiD3D11::destroy()
+{
+ finishActiveReadbacks();
+
+ if (annotations) {
+ annotations->Release();
+ annotations = nullptr;
+ }
+
+ if (!importedDevice) {
+ if (context) {
+ context->Release();
+ context = nullptr;
+ }
+ if (dev) {
+ dev->Release();
+ dev = nullptr;
+ }
+ }
+
+ if (dxgiFactory) {
+ dxgiFactory->Release();
+ dxgiFactory = nullptr;
+ }
+}
+
+void QRhiD3D11::reportLiveObjects(ID3D11Device *device)
+{
+ // this works only when params.enableDebugLayer was true
+ ID3D11Debug *debug;
+ if (SUCCEEDED(device->QueryInterface(IID_ID3D11Debug, reinterpret_cast<void **>(&debug)))) {
+ debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
+ debug->Release();
+ }
+}
+
+QVector<int> QRhiD3D11::supportedSampleCounts() const
+{
+ return { 1, 2, 4, 8 };
+}
+
+DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleCount(int sampleCount) const
+{
+ DXGI_SAMPLE_DESC desc;
+ desc.Count = 1;
+ desc.Quality = 0;
+
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ int s = qBound(1, sampleCount, 64);
+
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return desc;
+ }
+
+ desc.Count = s;
+ if (s > 1)
+ desc.Quality = D3D11_STANDARD_MULTISAMPLE_PATTERN;
+ else
+ desc.Quality = 0;
+
+ return desc;
+}
+
+QRhiSwapChain *QRhiD3D11::createSwapChain()
+{
+ return new QD3D11SwapChain(this);
+}
+
+QRhiBuffer *QRhiD3D11::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QD3D11Buffer(this, type, usage, size);
+}
+
+int QRhiD3D11::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiD3D11::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiD3D11::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiD3D11::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiD3D11::clipSpaceCorrMatrix() const
+{
+ // Like with Vulkan, but Y is already good.
+
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiD3D11::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(flags);
+
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12)
+ return false;
+
+ return true;
+}
+
+bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return annotations != nullptr;
+ case QRhi::Timestamps:
+ return true;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return true;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return false; // because UpdateSubresource cannot deal with this
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiD3D11::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QD3D11RenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiD3D11::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QD3D11Texture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiD3D11::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QD3D11Sampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QD3D11TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiD3D11::createGraphicsPipeline()
+{
+ return new QD3D11GraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiD3D11::createShaderResourceBindings()
+{
+ return new QD3D11ShaderResourceBindings(this);
+}
+
+void QRhiD3D11::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_ASSERT(inPass);
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11GraphicsPipeline *psD = QRHI_RES(QD3D11GraphicsPipeline, ps);
+ const bool pipelineChanged = cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+ if (pipelineChanged) {
+ cbD->currentPipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindGraphicsPipeline;
+ cmd.args.bindGraphicsPipeline.ps = psD;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_ASSERT(inPass);
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline);
+
+ if (!srb)
+ srb = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline)->m_shaderResourceBindings;
+
+ QD3D11ShaderResourceBindings *srbD = QRHI_RES(QD3D11ShaderResourceBindings, srb);
+
+ bool hasDynamicOffsetInSrb = false;
+ bool srbUpdate = false;
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ srbUpdate = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
+ QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ srbUpdate = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (srbUpdate)
+ updateShaderResourceBindings(srbD);
+
+ const bool srbChanged = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation;
+
+ if (srbChanged || srbUpdate || hasDynamicOffsetInSrb) {
+ cbD->currentSrb = srb;
+ cbD->currentSrbGeneration = srbD->generation;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindShaderResources;
+ cmd.args.bindShaderResources.srb = srbD;
+ // dynamic offsets have to be applied at the time of executing the bind
+ // operations, not here
+ cmd.args.bindShaderResources.offsetOnlyChange = !srbChanged && !srbUpdate && hasDynamicOffsetInSrb;
+ cmd.args.bindShaderResources.dynamicOffsetCount = 0;
+ if (hasDynamicOffsetInSrb) {
+ if (dynamicOffsetCount < QD3D11CommandBuffer::Command::MAX_UBUF_BINDINGS) {
+ cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
+ uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ const uint binding = dynOfs.first;
+ Q_ASSERT(aligned(dynOfs.second, 256) == dynOfs.second);
+ const uint offsetInConstants = dynOfs.second / 16;
+ *p++ = binding;
+ *p++ = offsetInConstants;
+ }
+ } else {
+ qWarning("Too many dynamic offsets (%d, max is %d)",
+ dynamicOffsetCount, QD3D11CommandBuffer::Command::MAX_UBUF_BINDINGS);
+ }
+ }
+
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiD3D11::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+
+ bool needsBindVBuf = false;
+ for (int i = 0; i < bindingCount; ++i) {
+ const int inputSlot = startBinding + i;
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ if (cbD->currentVertexBuffers[inputSlot] != bufD->buffer
+ || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+ {
+ needsBindVBuf = true;
+ cbD->currentVertexBuffers[inputSlot] = bufD->buffer;
+ cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+ }
+ }
+
+ if (needsBindVBuf) {
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindVertexBuffers;
+ cmd.args.bindVertexBuffers.startSlot = startBinding;
+ cmd.args.bindVertexBuffers.slotCount = bindingCount;
+ const QVector<QRhiVertexInputBinding> inputBindings =
+ QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline)->m_vertexInputLayout.bindings();
+ for (int i = 0, ie = qMin(bindingCount, inputBindings.count()); i != ie; ++i) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+ cmd.args.bindVertexBuffers.buffers[i] = bufD->buffer;
+ cmd.args.bindVertexBuffers.offsets[i] = bindings[i].second;
+ cmd.args.bindVertexBuffers.strides[i] = inputBindings[i].stride();
+ }
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QD3D11Buffer *ibufD = QRHI_RES(QD3D11Buffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ if (ibufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(ibufD);
+
+ const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT
+ : DXGI_FORMAT_R32_UINT;
+ if (cbD->currentIndexBuffer != ibufD->buffer
+ || cbD->currentIndexOffset != indexOffset
+ || cbD->currentIndexFormat != dxgiFormat)
+ {
+ cbD->currentIndexBuffer = ibufD->buffer;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = dxgiFormat;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
+ cmd.args.bindIndexBuffer.offset = indexOffset;
+ cmd.args.bindIndexBuffer.format = dxgiFormat;
+ cbD->commands.append(cmd);
+ }
+ }
+}
+
+void QRhiD3D11::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Viewport;
+
+ // d3d expects top-left, QRhiViewport is bottom-left
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ cmd.args.viewport.x = x;
+ cmd.args.viewport.y = y;
+ cmd.args.viewport.w = w;
+ cmd.args.viewport.h = h;
+ cmd.args.viewport.d0 = viewport.minDepth();
+ cmd.args.viewport.d1 = viewport.maxDepth();
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Scissor;
+
+ // d3d expects top-left, QRhiScissor is bottom-left
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ cmd.args.scissor.x = x;
+ cmd.args.scissor.y = y;
+ cmd.args.scissor.w = w;
+ cmd.args.scissor.h = h;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BlendConstants;
+ cmd.args.blendConstants.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline);
+ cmd.args.blendConstants.c[0] = c.redF();
+ cmd.args.blendConstants.c[1] = c.greenF();
+ cmd.args.blendConstants.c[2] = c.blueF();
+ cmd.args.blendConstants.c[3] = c.alphaF();
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::StencilRef;
+ cmd.args.stencilRef.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline);
+ cmd.args.stencilRef.ref = refValue;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Draw;
+ cmd.args.draw.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline);
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.instanceCount = instanceCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cmd.args.draw.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentPipeline);
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.instanceCount = instanceCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cmd.args.drawIndexed.vertexOffset = vertexOffset;
+ cmd.args.drawIndexed.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkBegin;
+ strncpy(cmd.args.debugMark.s, name.constData(), sizeof(cmd.args.debugMark.s));
+ cmd.args.debugMark.s[sizeof(cmd.args.debugMark.s) - 1] = '\0';
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkEnd;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkMsg;
+ strncpy(cmd.args.debugMark.s, msg.constData(), sizeof(cmd.args.debugMark.s));
+ cmd.args.debugMark.s[sizeof(cmd.args.debugMark.s) - 1] = '\0';
+ cbD->commands.append(cmd);
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiD3D11::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_ASSERT(inPass);
+ Q_UNUSED(cb);
+ flushCommandBuffer();
+}
+
+void QRhiD3D11::endExternal(QRhiCommandBuffer *cb)
+{
+ Q_ASSERT(inPass);
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->currentTarget);
+ Q_ASSERT(cbD->commands.isEmpty());
+ cbD->resetCachedState();
+ enqueueSetRenderTarget(cbD, cbD->currentTarget);
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+
+ Q_ASSERT(!inFrame);
+ inFrame = true;
+
+ QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+ contextState.currentSwapChain = swapChainD;
+ const int currentFrameSlot = swapChainD->currentFrameSlot;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ if (swapChainD->timestampActive[currentFrameSlot]) {
+ ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx];
+ ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1];
+ quint64 timestamps[2];
+ D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+ bool ok = true;
+ ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ ok &= context->GetData(tsEnd, &timestamps[1], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ // this above is often not ready, not even in frame_where_recorded+2,
+ // not clear why. so make the whole thing async and do not touch the
+ // queries until they are finally all available in frame this+2 or
+ // this+4 or ...
+ ok &= context->GetData(tsStart, &timestamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ if (ok) {
+ if (!dj.Disjoint && dj.Frequency) {
+ const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
+ // finally got a value, just report it, the profiler cares about min/max/avg anyway
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs));
+ }
+ swapChainD->timestampActive[currentFrameSlot] = false;
+ } // else leave timestampActive set to true, will retry in a subsequent beginFrame
+ }
+
+ swapChainD->cb.resetState();
+
+ swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ?
+ swapChainD->msaaRtv[currentFrameSlot] : swapChainD->rtv[currentFrameSlot];
+ swapChainD->rt.d.dsv = swapChainD->ds ? swapChainD->ds->dsv : nullptr;
+
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_ASSERT(inFrame);
+ inFrame = false;
+
+ QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+ Q_ASSERT(contextState.currentSwapChain = swapChainD);
+ const int currentFrameSlot = swapChainD->currentFrameSlot;
+
+ ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx];
+ ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1];
+ const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestampActive[currentFrameSlot];
+
+ // send all commands to the context
+ if (recordTimestamps)
+ executeCommandBuffer(&swapChainD->cb, swapChainD);
+ else
+ executeCommandBuffer(&swapChainD->cb);
+
+ if (swapChainD->sampleDesc.Count > 1) {
+ context->ResolveSubresource(swapChainD->tex[currentFrameSlot], 0,
+ swapChainD->msaaTex[currentFrameSlot], 0,
+ swapChainD->colorFormat);
+ }
+
+ // this is here because we want to include the time spent on the resolve as well
+ if (recordTimestamps) {
+ context->End(tsEnd);
+ context->End(tsDisjoint);
+ swapChainD->timestampActive[currentFrameSlot] = true;
+ }
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the Present
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (!flags.testFlag(QRhi::SkipPresent)) {
+ const UINT presentFlags = 0;
+ HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
+ if (FAILED(hr))
+ qWarning("Failed to present: %s", qPrintable(comErrorMessage(hr)));
+
+ // move on to the next buffer
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D11SwapChain::BUFFER_COUNT;
+ } else {
+ context->Flush();
+ }
+
+ swapChainD->frameCount += 1;
+ contextState.currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ Q_ASSERT(!inFrame);
+ inFrame = true;
+ ofr.active = true;
+
+ ofr.cbWrapper.resetState();
+ *cb = &ofr.cbWrapper;
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame()
+{
+ Q_ASSERT(inFrame && ofr.active);
+ inFrame = false;
+ ofr.active = false;
+
+ executeCommandBuffer(&ofr.cbWrapper);
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+ case QRhiTexture::BGRA8:
+ return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM;
+ case QRhiTexture::R8:
+ return DXGI_FORMAT_R8_UNORM;
+ case QRhiTexture::R16:
+ return DXGI_FORMAT_R16_UNORM;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return DXGI_FORMAT_R8_UNORM;
+
+ case QRhiTexture::RGBA16F:
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ case QRhiTexture::RGBA32F:
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+
+ case QRhiTexture::D16:
+ return DXGI_FORMAT_R16_TYPELESS;
+ case QRhiTexture::D32F:
+ return DXGI_FORMAT_R32_TYPELESS;
+
+ case QRhiTexture::BC1:
+ return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM;
+ case QRhiTexture::BC2:
+ return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM;
+ case QRhiTexture::BC3:
+ return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM;
+ case QRhiTexture::BC4:
+ return DXGI_FORMAT_BC4_UNORM;
+ case QRhiTexture::BC5:
+ return DXGI_FORMAT_BC5_UNORM;
+ case QRhiTexture::BC6H:
+ return DXGI_FORMAT_BC6H_UF16;
+ case QRhiTexture::BC7:
+ return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM;
+
+ case QRhiTexture::ETC2_RGB8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ETC2_RGB8A1:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ETC2_RGBA8:
+ qWarning("QRhiD3D11 does not support ETC2 textures");
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ case QRhiTexture::ASTC_4x4:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_5x4:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_5x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_6x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_6x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x10:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_12x10:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_12x12:
+ qWarning("QRhiD3D11 does not support ASTC textures");
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
+static inline QRhiTexture::Format colorTextureFormatFromDxgiFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags)
+{
+ switch (format) {
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ return QRhiTexture::RGBA8;
+ case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::RGBA8;
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ return QRhiTexture::BGRA8;
+ case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::BGRA8;
+ case DXGI_FORMAT_R8_UNORM:
+ return QRhiTexture::R8;
+ case DXGI_FORMAT_R16_UNORM:
+ return QRhiTexture::R16;
+ default: // this cannot assert, must warn and return unknown
+ qWarning("DXGI_FORMAT %d is not a recognized uncompressed color format", format);
+ break;
+ }
+ return QRhiTexture::UnknownFormat;
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::Format::D32F:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+QRhi::FrameOpResult QRhiD3D11::finish()
+{
+ Q_ASSERT(!inPass);
+
+ if (inFrame)
+ flushCommandBuffer();
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiD3D11::flushCommandBuffer()
+{
+ if (ofr.active) {
+ Q_ASSERT(!contextState.currentSwapChain);
+ executeCommandBuffer(&ofr.cbWrapper);
+ ofr.cbWrapper.resetCommands();
+ } else {
+ Q_ASSERT(contextState.currentSwapChain);
+ executeCommandBuffer(&contextState.currentSwapChain->cb); // no timestampSwapChain, in order to avoid timestamp mess
+ contextState.currentSwapChain->cb.resetCommands();
+ }
+}
+
+void QRhiD3D11::enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
+{
+ UINT subres = D3D11CalcSubresource(level, layer, texD->mipLevelCount);
+ const QPoint dp = subresDesc.destinationTopLeft();
+ D3D11_BOX box;
+ box.front = 0;
+ // back, right, bottom are exclusive
+ box.back = 1;
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+ cmd.args.updateSubRes.dst = texD->tex;
+ cmd.args.updateSubRes.dstSubRes = subres;
+
+ bool cmdValid = true;
+ if (!subresDesc.image().isNull()) {
+ QImage img = subresDesc.image();
+ QSize size = img.size();
+ int bpl = img.bytesPerLine();
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const QPoint sp = subresDesc.sourceTopLeft();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ if (img.depth() == 32) {
+ const int offset = sp.y() * img.bytesPerLine() + sp.x() * 4;
+ cmd.args.updateSubRes.src = cbD->retainImage(img) + offset;
+ } else {
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ bpl = img.bytesPerLine();
+ cmd.args.updateSubRes.src = cbD->retainImage(img);
+ }
+ } else {
+ cmd.args.updateSubRes.src = cbD->retainImage(img);
+ }
+ box.left = dp.x();
+ box.top = dp.y();
+ box.right = dp.x() + size.width();
+ box.bottom = dp.y() + size.height();
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, size, &bpl, nullptr, &blockDim);
+ // Everything must be a multiple of the block width and
+ // height, so e.g. a mip level of size 2x2 will be 4x4 when it
+ // comes to the actual data.
+ box.left = aligned(dp.x(), blockDim.width());
+ box.top = aligned(dp.y(), blockDim.height());
+ box.right = aligned(dp.x() + size.width(), blockDim.width());
+ box.bottom = aligned(dp.y() + size.height(), blockDim.height());
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else if (!subresDesc.data().isEmpty()) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ QSize blockDim;
+ textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ box.left = dp.x();
+ box.top = dp.y();
+ box.right = dp.x() + size.width();
+ box.bottom = dp.y() + size.height();
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ cmdValid = false;
+ }
+ if (cmdValid)
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), u.data.size());
+ bufD->hasPendingDynamicUpdates = true;
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+ cmd.args.updateSubRes.dst = bufD->buffer;
+ cmd.args.updateSubRes.dstSubRes = 0;
+ cmd.args.updateSubRes.src = cbD->retainData(u.data);
+ cmd.args.updateSubRes.srcRowPitch = 0;
+ // Specify the region (even when offset is 0 and all data is provided)
+ // since the ID3D11Buffer's size is rounded up to be a multiple of 256
+ // while the data we have has the original size.
+ D3D11_BOX box;
+ box.left = u.offset;
+ box.top = box.front = 0;
+ box.back = box.bottom = 1;
+ box.right = u.offset + u.data.size(); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cbD->commands.append(cmd);
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.upload.tex);
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
+ }
+ }
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.copy.src);
+ QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.copy.dst);
+ UINT srcSubRes = D3D11CalcSubresource(u.copy.desc.sourceLevel(), u.copy.desc.sourceLayer(), srcD->mipLevelCount);
+ UINT dstSubRes = D3D11CalcSubresource(u.copy.desc.destinationLevel(), u.copy.desc.destinationLayer(), dstD->mipLevelCount);
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+ D3D11_BOX srcBox;
+ srcBox.left = sp.x();
+ srcBox.top = sp.y();
+ srcBox.front = 0;
+ // back, right, bottom are exclusive
+ srcBox.right = srcBox.left + size.width();
+ srcBox.bottom = srcBox.top + size.height();
+ srcBox.back = 1;
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+ cmd.args.copySubRes.dst = dstD->tex;
+ cmd.args.copySubRes.dstSubRes = dstSubRes;
+ cmd.args.copySubRes.dstX = dp.x();
+ cmd.args.copySubRes.dstY = dp.y();
+ cmd.args.copySubRes.src = srcD->tex;
+ cmd.args.copySubRes.srcSubRes = srcSubRes;
+ cmd.args.copySubRes.hasSrcBox = true;
+ cmd.args.copySubRes.srcBox = srcBox;
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ ActiveReadback aRb;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ ID3D11Resource *src;
+ DXGI_FORMAT dxgiFormat;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ UINT subres = 0;
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.read.rb.texture());
+ QD3D11SwapChain *swapChainD = nullptr;
+
+ if (texD) {
+ if (texD->sampleDesc.Count > 1) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ src = texD->tex;
+ dxgiFormat = texD->dxgiFormat;
+ pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) : texD->m_pixelSize;
+ format = texD->m_format;
+ subres = D3D11CalcSubresource(u.read.rb.level(), u.read.rb.layer(), texD->mipLevelCount);
+ } else {
+ Q_ASSERT(contextState.currentSwapChain);
+ swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain);
+ if (swapChainD->sampleDesc.Count > 1) {
+ // Unlike with textures, reading back a multisample swapchain image
+ // has to be supported. Insert a resolve.
+ QD3D11CommandBuffer::Command rcmd;
+ rcmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+ rcmd.args.resolveSubRes.dst = swapChainD->tex[swapChainD->currentFrameSlot];
+ rcmd.args.resolveSubRes.dstSubRes = 0;
+ rcmd.args.resolveSubRes.src = swapChainD->msaaTex[swapChainD->currentFrameSlot];
+ rcmd.args.resolveSubRes.srcSubRes = 0;
+ rcmd.args.resolveSubRes.format = swapChainD->colorFormat;
+ cbD->commands.append(rcmd);
+ }
+ src = swapChainD->tex[swapChainD->currentFrameSlot];
+ dxgiFormat = swapChainD->colorFormat;
+ pixelSize = swapChainD->pixelSize;
+ format = colorTextureFormatFromDxgiFormat(dxgiFormat, nullptr);
+ if (format == QRhiTexture::UnknownFormat)
+ continue;
+ }
+ quint32 bufSize = 0;
+ quint32 bpl = 0;
+ textureFormatInfo(format, pixelSize, &bpl, &bufSize);
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = pixelSize.width();
+ desc.Height = pixelSize.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = dxgiFormat;
+ desc.SampleDesc.Count = 1;
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ ID3D11Texture2D *stagingTex;
+ HRESULT hr = dev->CreateTexture2D(&desc, nullptr, &stagingTex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create readback staging texture: %s", qPrintable(comErrorMessage(hr)));
+ return;
+ }
+ QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(stagingTex)),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ bufSize));
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+ cmd.args.copySubRes.dst = stagingTex;
+ cmd.args.copySubRes.dstSubRes = 0;
+ cmd.args.copySubRes.dstX = 0;
+ cmd.args.copySubRes.dstY = 0;
+ cmd.args.copySubRes.src = src;
+ cmd.args.copySubRes.srcSubRes = subres;
+ cmd.args.copySubRes.hasSrcBox = false;
+ cbD->commands.append(cmd);
+
+ aRb.stagingTex = stagingTex;
+ aRb.bufSize = bufSize;
+ aRb.bpl = bpl;
+ aRb.pixelSize = pixelSize;
+ aRb.format = format;
+
+ activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ Q_ASSERT(u.mipgen.tex->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::GenMip;
+ cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.mipgen.tex)->srv;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ ud->free();
+}
+
+void QRhiD3D11::finishActiveReadbacks()
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiD3D11::ActiveReadback &aRb(activeReadbacks[i]);
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+
+ D3D11_MAPPED_SUBRESOURCE mp;
+ HRESULT hr = context->Map(aRb.stagingTex, 0, D3D11_MAP_READ, 0, &mp);
+ if (FAILED(hr)) {
+ qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr)));
+ aRb.stagingTex->Release();
+ continue;
+ }
+ // nothing says the rows are tightly packed in the texture, must take
+ // the stride into account
+ char *dst = aRb.result->data.data();
+ char *src = static_cast<char *>(mp.pData);
+ for (int y = 0, h = aRb.pixelSize.height(); y != h; ++y) {
+ memcpy(dst, src, aRb.bpl);
+ dst += aRb.bpl;
+ src += mp.RowPitch;
+ }
+ context->Unmap(aRb.stagingTex, 0);
+
+ aRb.stagingTex->Release();
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.stagingTex))));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ activeReadbacks.removeAt(i);
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
+{
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ return &QRHI_RES(QD3D11ReferenceRenderTarget, rt)->d;
+ case QRhiResource::TextureRenderTarget:
+ return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
+ default:
+ Q_UNREACHABLE();
+ return nullptr;
+ }
+}
+
+void QRhiD3D11::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::enqueueSetRenderTarget(QD3D11CommandBuffer *cbD, QRhiRenderTarget *rt)
+{
+ QD3D11CommandBuffer::Command fbCmd;
+ fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget;
+ fbCmd.args.setRenderTarget.rt = rt;
+ cbD->commands.append(fbCmd);
+}
+
+void QRhiD3D11::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ bool wantsColorClear = true;
+ bool wantsDsClear = true;
+ QD3D11RenderTargetData *rtD = rtData(rt);
+ if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
+ QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, rt);
+ wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+ wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+ }
+ enqueueSetRenderTarget(cbD, rt);
+
+ QD3D11CommandBuffer::Command clearCmd;
+ clearCmd.cmd = QD3D11CommandBuffer::Command::Clear;
+ clearCmd.args.clear.rt = rt;
+ clearCmd.args.clear.mask = 0;
+ if (rtD->colorAttCount && wantsColorClear)
+ clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Color;
+ if (rtD->dsAttCount && wantsDsClear)
+ clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Depth | QD3D11CommandBuffer::Command::Stencil;
+
+ clearCmd.args.clear.c[0] = colorClearValue.redF();
+ clearCmd.args.clear.c[1] = colorClearValue.greenF();
+ clearCmd.args.clear.c[2] = colorClearValue.blueF();
+ clearCmd.args.clear.c[3] = colorClearValue.alphaF();
+ clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
+ clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
+ cbD->commands.append(clearCmd);
+
+ cbD->currentTarget = rt;
+
+ inPass = true;
+}
+
+void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inPass);
+ inPass = false;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+ QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, cbD->currentTarget);
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ for (int att = 0, attCount = colorAttachments.count(); att != attCount; ++att) {
+ const QRhiColorAttachment &colorAtt(colorAttachments[att]);
+ if (!colorAtt.resolveTexture())
+ continue;
+
+ QD3D11Texture *dstTexD = QRHI_RES(QD3D11Texture, colorAtt.resolveTexture());
+ QD3D11Texture *srcTexD = QRHI_RES(QD3D11Texture, colorAtt.texture());
+ QD3D11RenderBuffer *srcRbD = QRHI_RES(QD3D11RenderBuffer, colorAtt.renderBuffer());
+ Q_ASSERT(srcTexD || srcRbD);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+ cmd.args.resolveSubRes.dst = dstTexD->tex;
+ cmd.args.resolveSubRes.dstSubRes = D3D11CalcSubresource(colorAtt.resolveLevel(),
+ colorAtt.resolveLayer(),
+ dstTexD->mipLevelCount);
+ if (srcTexD) {
+ cmd.args.resolveSubRes.src = srcTexD->tex;
+ if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) {
+ qWarning("Resolve source and destination formats do not match");
+ continue;
+ }
+ if (srcTexD->sampleDesc.Count <= 1) {
+ qWarning("Cannot resolve a non-multisample texture");
+ continue;
+ }
+ if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) {
+ qWarning("Resolve source and destination sizes do not match");
+ continue;
+ }
+ } else {
+ cmd.args.resolveSubRes.src = srcRbD->tex;
+ if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) {
+ qWarning("Resolve source and destination formats do not match");
+ continue;
+ }
+ if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) {
+ qWarning("Resolve source and destination sizes do not match");
+ continue;
+ }
+ }
+ cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, colorAtt.layer(), 1);
+ cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD)
+{
+ srbD->vsubufs.clear();
+ srbD->vsubufoffsets.clear();
+ srbD->vsubufsizes.clear();
+
+ srbD->fsubufs.clear();
+ srbD->fsubufoffsets.clear();
+ srbD->fsubufsizes.clear();
+
+ srbD->vssamplers.clear();
+ srbD->vsshaderresources.clear();
+
+ srbD->fssamplers.clear();
+ srbD->fsshaderresources.clear();
+
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+ Q_ASSERT(aligned(b->u.ubuf.offset, 256) == b->u.ubuf.offset);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ // dynamic ubuf offsets are not considered here, those are baked in
+ // at a later stage, which is good as vsubufoffsets and friends are
+ // per-srb, not per-setShaderResources call
+ const uint offsetInConstants = b->u.ubuf.offset / 16;
+ // size must be 16 mult. (in constants, i.e. multiple of 256 bytes).
+ // We can round up if needed since the buffers's actual size
+ // (ByteWidth) is always a multiple of 256.
+ const uint sizeInConstants = aligned(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size, 256) / 16;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ srbD->vsubufs.feed(b->binding, bufD->buffer);
+ srbD->vsubufoffsets.feed(b->binding, offsetInConstants);
+ srbD->vsubufsizes.feed(b->binding, sizeInConstants);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ srbD->fsubufs.feed(b->binding, bufD->buffer);
+ srbD->fsubufoffsets.feed(b->binding, offsetInConstants);
+ srbD->fsubufsizes.feed(b->binding, sizeInConstants);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ // A sampler with binding N is mapped to a HLSL sampler and texture
+ // with registers sN and tN by SPIRV-Cross.
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
+ QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ srbD->vssamplers.feed(b->binding, samplerD->samplerState);
+ srbD->vsshaderresources.feed(b->binding, texD->srv);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ srbD->fssamplers.feed(b->binding, samplerD->samplerState);
+ srbD->fsshaderresources.feed(b->binding, texD->srv);
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ srbD->vsubufs.finish();
+ srbD->vsubufoffsets.finish();
+ srbD->vsubufsizes.finish();
+
+ srbD->fsubufs.finish();
+ srbD->fsubufoffsets.finish();
+ srbD->fsubufsizes.finish();
+
+ srbD->vssamplers.finish();
+ srbD->vsshaderresources.finish();
+
+ srbD->fssamplers.finish();
+ srbD->fsshaderresources.finish();
+}
+
+void QRhiD3D11::executeBufferHostWritesForCurrentFrame(QD3D11Buffer *bufD)
+{
+ if (!bufD->hasPendingDynamicUpdates)
+ return;
+
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ bufD->hasPendingDynamicUpdates = false;
+ D3D11_MAPPED_SUBRESOURCE mp;
+ HRESULT hr = context->Map(bufD->buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
+ if (SUCCEEDED(hr)) {
+ memcpy(mp.pData, bufD->dynBuf.constData(), bufD->dynBuf.size());
+ context->Unmap(bufD->buffer, 0);
+ } else {
+ qWarning("Failed to map buffer: %s", qPrintable(comErrorMessage(hr)));
+ }
+}
+
+void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+ const uint *dynOfsPairs, int dynOfsPairCount,
+ bool offsetOnlyChange)
+{
+ if (!offsetOnlyChange) {
+ for (const auto &batch : srbD->vssamplers.batches)
+ context->VSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData());
+
+ for (const auto &batch : srbD->vsshaderresources.batches) {
+ context->VSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData());
+ contextState.vsHighestActiveSrvBinding = qMax<int>(contextState.vsHighestActiveSrvBinding,
+ batch.startBinding + batch.resources.count() - 1);
+ }
+
+ for (const auto &batch : srbD->fssamplers.batches)
+ context->PSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData());
+
+ for (const auto &batch : srbD->fsshaderresources.batches) {
+ context->PSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData());
+ contextState.fsHighestActiveSrvBinding = qMax<int>(contextState.fsHighestActiveSrvBinding,
+ batch.startBinding + batch.resources.count() - 1);
+ }
+ }
+
+ for (int i = 0, ie = srbD->vsubufs.batches.count(); i != ie; ++i) {
+ if (!dynOfsPairCount) {
+ context->VSSetConstantBuffers1(srbD->vsubufs.batches[i].startBinding,
+ srbD->vsubufs.batches[i].resources.count(),
+ srbD->vsubufs.batches[i].resources.constData(),
+ srbD->vsubufoffsets.batches[i].resources.constData(),
+ srbD->vsubufsizes.batches[i].resources.constData());
+ } else {
+ const UINT count = srbD->vsubufs.batches[i].resources.count();
+ const UINT startBinding = srbD->vsubufs.batches[i].startBinding;
+ QVarLengthArray<UINT, 4> offsets = srbD->vsubufoffsets.batches[i].resources;
+ for (UINT b = 0; b < count; ++b) {
+ for (int di = 0; di < dynOfsPairCount; ++di) {
+ const uint binding = dynOfsPairs[2 * di];
+ if (binding == startBinding + b) {
+ const uint offsetInConstants = dynOfsPairs[2 * di + 1];
+ offsets[b] = offsetInConstants;
+ break;
+ }
+ }
+ }
+ context->VSSetConstantBuffers1(startBinding,
+ count,
+ srbD->vsubufs.batches[i].resources.constData(),
+ offsets.constData(),
+ srbD->vsubufsizes.batches[i].resources.constData());
+ }
+ }
+
+ for (int i = 0, ie = srbD->fsubufs.batches.count(); i != ie; ++i) {
+ if (!dynOfsPairCount) {
+ context->PSSetConstantBuffers1(srbD->fsubufs.batches[i].startBinding,
+ srbD->fsubufs.batches[i].resources.count(),
+ srbD->fsubufs.batches[i].resources.constData(),
+ srbD->fsubufoffsets.batches[i].resources.constData(),
+ srbD->fsubufsizes.batches[i].resources.constData());
+ } else {
+ const UINT count = srbD->fsubufs.batches[i].resources.count();
+ const UINT startBinding = srbD->fsubufs.batches[i].startBinding;
+ QVarLengthArray<UINT, 4> offsets = srbD->fsubufoffsets.batches[i].resources;
+ for (UINT b = 0; b < count; ++b) {
+ for (int di = 0; di < dynOfsPairCount; ++di) {
+ const uint binding = dynOfsPairs[2 * di];
+ if (binding == startBinding + b) {
+ const uint offsetInConstants = dynOfsPairs[2 * di + 1];
+ offsets[b] = offsetInConstants;
+ break;
+ }
+ }
+ }
+ context->PSSetConstantBuffers1(startBinding,
+ count,
+ srbD->fsubufs.batches[i].resources.constData(),
+ offsets.constData(),
+ srbD->fsubufsizes.batches[i].resources.constData());
+ }
+ }
+}
+
+void QRhiD3D11::setRenderTarget(QRhiRenderTarget *rt)
+{
+ // The new output cannot be bound as input from the previous frame,
+ // otherwise the debug layer complains. Avoid this.
+ const int nullsrvCount = qMax(contextState.vsHighestActiveSrvBinding, contextState.fsHighestActiveSrvBinding) + 1;
+ if (nullsrvCount > 0) {
+ QVarLengthArray<ID3D11ShaderResourceView *,
+ D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nullsrvs(nullsrvCount);
+ for (int i = 0; i < nullsrvs.count(); ++i)
+ nullsrvs[i] = nullptr;
+ if (contextState.vsHighestActiveSrvBinding >= 0) {
+ context->VSSetShaderResources(0, contextState.vsHighestActiveSrvBinding + 1, nullsrvs.constData());
+ contextState.vsHighestActiveSrvBinding = -1;
+ }
+ if (contextState.fsHighestActiveSrvBinding >= 0) {
+ context->PSSetShaderResources(0, contextState.fsHighestActiveSrvBinding + 1, nullsrvs.constData());
+ contextState.fsHighestActiveSrvBinding = -1;
+ }
+ }
+ QD3D11RenderTargetData *rtD = rtData(rt);
+ context->OMSetRenderTargets(rtD->colorAttCount, rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+}
+
+void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain)
+{
+ quint32 stencilRef = 0;
+ float blendConstants[] = { 1, 1, 1, 1 };
+
+ if (timestampSwapChain) {
+ const int currentFrameSlot = timestampSwapChain->currentFrameSlot;
+ ID3D11Query *tsDisjoint = timestampSwapChain->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = timestampSwapChain->timestampQuery[tsIdx];
+ if (tsDisjoint && tsStart && !timestampSwapChain->timestampActive[currentFrameSlot]) {
+ // The timestamps seem to include vsync time with Present(1), except
+ // when running on a non-primary gpu. This is not ideal. So try working
+ // it around by issuing a semi-fake OMSetRenderTargets early and
+ // writing the first timestamp only afterwards.
+ context->Begin(tsDisjoint);
+ setRenderTarget(&timestampSwapChain->rt);
+ context->End(tsStart); // just record a timestamp, no Begin needed
+ }
+ }
+
+ for (const QD3D11CommandBuffer::Command &cmd : qAsConst(cbD->commands)) {
+ switch (cmd.cmd) {
+ case QD3D11CommandBuffer::Command::SetRenderTarget:
+ setRenderTarget(cmd.args.setRenderTarget.rt);
+ break;
+ case QD3D11CommandBuffer::Command::Clear:
+ {
+ QD3D11RenderTargetData *rtD = rtData(cmd.args.clear.rt);
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Color) {
+ for (int i = 0; i < rtD->colorAttCount; ++i)
+ context->ClearRenderTargetView(rtD->rtv[i], cmd.args.clear.c);
+ }
+ uint ds = 0;
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Depth)
+ ds |= D3D11_CLEAR_DEPTH;
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Stencil)
+ ds |= D3D11_CLEAR_STENCIL;
+ if (ds)
+ context->ClearDepthStencilView(rtD->dsv, ds, cmd.args.clear.d, cmd.args.clear.s);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::Viewport:
+ {
+ D3D11_VIEWPORT v;
+ v.TopLeftX = cmd.args.viewport.x;
+ v.TopLeftY = cmd.args.viewport.y;
+ v.Width = cmd.args.viewport.w;
+ v.Height = cmd.args.viewport.h;
+ v.MinDepth = cmd.args.viewport.d0;
+ v.MaxDepth = cmd.args.viewport.d1;
+ context->RSSetViewports(1, &v);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::Scissor:
+ {
+ D3D11_RECT r;
+ r.left = cmd.args.scissor.x;
+ r.top = cmd.args.scissor.y;
+ // right and bottom are exclusive
+ r.right = cmd.args.scissor.x + cmd.args.scissor.w;
+ r.bottom = cmd.args.scissor.y + cmd.args.scissor.h;
+ context->RSSetScissorRects(1, &r);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::BindVertexBuffers:
+ context->IASetVertexBuffers(cmd.args.bindVertexBuffers.startSlot,
+ cmd.args.bindVertexBuffers.slotCount,
+ cmd.args.bindVertexBuffers.buffers,
+ cmd.args.bindVertexBuffers.strides,
+ cmd.args.bindVertexBuffers.offsets);
+ break;
+ case QD3D11CommandBuffer::Command::BindIndexBuffer:
+ context->IASetIndexBuffer(cmd.args.bindIndexBuffer.buffer,
+ cmd.args.bindIndexBuffer.format,
+ cmd.args.bindIndexBuffer.offset);
+ break;
+ case QD3D11CommandBuffer::Command::BindGraphicsPipeline:
+ {
+ QD3D11GraphicsPipeline *psD = cmd.args.bindGraphicsPipeline.ps;
+ context->VSSetShader(psD->vs, nullptr, 0);
+ context->PSSetShader(psD->fs, nullptr, 0);
+ context->IASetPrimitiveTopology(psD->d3dTopology);
+ context->IASetInputLayout(psD->inputLayout);
+ context->OMSetDepthStencilState(psD->dsState, stencilRef);
+ context->OMSetBlendState(psD->blendState, blendConstants, 0xffffffff);
+ context->RSSetState(psD->rastState);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::BindShaderResources:
+ bindShaderResources(cmd.args.bindShaderResources.srb,
+ cmd.args.bindShaderResources.dynamicOffsetPairs,
+ cmd.args.bindShaderResources.dynamicOffsetCount,
+ cmd.args.bindShaderResources.offsetOnlyChange);
+ break;
+ case QD3D11CommandBuffer::Command::StencilRef:
+ stencilRef = cmd.args.stencilRef.ref;
+ context->OMSetDepthStencilState(cmd.args.stencilRef.ps->dsState, stencilRef);
+ break;
+ case QD3D11CommandBuffer::Command::BlendConstants:
+ memcpy(blendConstants, cmd.args.blendConstants.c, 4 * sizeof(float));
+ context->OMSetBlendState(cmd.args.blendConstants.ps->blendState, blendConstants, 0xffffffff);
+ break;
+ case QD3D11CommandBuffer::Command::Draw:
+ if (cmd.args.draw.ps) {
+ if (cmd.args.draw.instanceCount == 1)
+ context->Draw(cmd.args.draw.vertexCount, cmd.args.draw.firstVertex);
+ else
+ context->DrawInstanced(cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
+ cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
+ } else {
+ qWarning("No graphics pipeline active for draw; ignored");
+ }
+ break;
+ case QD3D11CommandBuffer::Command::DrawIndexed:
+ if (cmd.args.drawIndexed.ps) {
+ if (cmd.args.drawIndexed.instanceCount == 1)
+ context->DrawIndexed(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.firstIndex,
+ cmd.args.drawIndexed.vertexOffset);
+ else
+ context->DrawIndexedInstanced(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
+ cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
+ cmd.args.drawIndexed.firstInstance);
+ } else {
+ qWarning("No graphics pipeline active for drawIndexed; ignored");
+ }
+ break;
+ case QD3D11CommandBuffer::Command::UpdateSubRes:
+ context->UpdateSubresource(cmd.args.updateSubRes.dst, cmd.args.updateSubRes.dstSubRes,
+ cmd.args.updateSubRes.hasDstBox ? &cmd.args.updateSubRes.dstBox : nullptr,
+ cmd.args.updateSubRes.src, cmd.args.updateSubRes.srcRowPitch, 0);
+ break;
+ case QD3D11CommandBuffer::Command::CopySubRes:
+ context->CopySubresourceRegion(cmd.args.copySubRes.dst, cmd.args.copySubRes.dstSubRes,
+ cmd.args.copySubRes.dstX, cmd.args.copySubRes.dstY, 0,
+ cmd.args.copySubRes.src, cmd.args.copySubRes.srcSubRes,
+ cmd.args.copySubRes.hasSrcBox ? &cmd.args.copySubRes.srcBox : nullptr);
+ break;
+ case QD3D11CommandBuffer::Command::ResolveSubRes:
+ context->ResolveSubresource(cmd.args.resolveSubRes.dst, cmd.args.resolveSubRes.dstSubRes,
+ cmd.args.resolveSubRes.src, cmd.args.resolveSubRes.srcSubRes,
+ cmd.args.resolveSubRes.format);
+ break;
+ case QD3D11CommandBuffer::Command::GenMip:
+ context->GenerateMips(cmd.args.genMip.srv);
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkBegin:
+ annotations->BeginEvent(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkEnd:
+ annotations->EndEvent();
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkMsg:
+ annotations->SetMarker(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+QD3D11Buffer::QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QD3D11Buffer::~QD3D11Buffer()
+{
+ release();
+}
+
+void QD3D11Buffer::release()
+{
+ if (!buffer)
+ return;
+
+ dynBuf.clear();
+
+ buffer->Release();
+ buffer = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline uint toD3DBufferUsage(QRhiBuffer::UsageFlags usage)
+{
+ int u = 0;
+ if (usage.testFlag(QRhiBuffer::VertexBuffer))
+ u |= D3D11_BIND_VERTEX_BUFFER;
+ if (usage.testFlag(QRhiBuffer::IndexBuffer))
+ u |= D3D11_BIND_INDEX_BUFFER;
+ if (usage.testFlag(QRhiBuffer::UniformBuffer))
+ u |= D3D11_BIND_CONSTANT_BUFFER;
+ return u;
+}
+
+bool QD3D11Buffer::build()
+{
+ if (buffer)
+ release();
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+ const int roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256) : nonZeroSize;
+
+ D3D11_BUFFER_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.ByteWidth = roundedSize;
+ desc.Usage = m_type == Dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
+ desc.BindFlags = toD3DBufferUsage(m_usage);
+ desc.CPUAccessFlags = m_type == Dynamic ? D3D11_CPU_ACCESS_WRITE : 0;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateBuffer(&desc, nullptr, &buffer);
+ if (FAILED(hr)) {
+ qWarning("Failed to create buffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (m_type == Dynamic) {
+ dynBuf.resize(m_size);
+ hasPendingDynamicUpdates = false;
+ }
+
+ if (!m_objectName.isEmpty())
+ buffer->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, roundedSize, m_type == Dynamic ? 2 : 1, m_type == Dynamic ? 1 : 0));
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QD3D11RenderBuffer::QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QD3D11RenderBuffer::~QD3D11RenderBuffer()
+{
+ release();
+}
+
+void QD3D11RenderBuffer::release()
+{
+ if (!tex)
+ return;
+
+ if (dsv) {
+ dsv->Release();
+ dsv = nullptr;
+ }
+
+ if (rtv) {
+ rtv->Release();
+ rtv = nullptr;
+ }
+
+ tex->Release();
+ tex = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QD3D11RenderBuffer::build()
+{
+ if (tex)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = m_pixelSize.width();
+ desc.Height = m_pixelSize.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+
+ if (m_type == Color) {
+ dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ desc.Format = dxgiFormat;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color renderbuffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = dxgiFormat;
+ rtvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS
+ : D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(tex, &rtvDesc, &rtv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else if (m_type == DepthStencil) {
+ dxgiFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ desc.Format = dxgiFormat;
+ desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create depth-stencil buffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
+ memset(&dsvDesc, 0, sizeof(dsvDesc));
+ dsvDesc.Format = dxgiFormat;
+ dsvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+ : D3D11_DSV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateDepthStencilView(tex, &dsvDesc, &dsv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create dsv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ if (!m_objectName.isEmpty())
+ tex->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, sampleDesc.Count));
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QD3D11RenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QD3D11Texture::QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+}
+
+QD3D11Texture::~QD3D11Texture()
+{
+ release();
+}
+
+void QD3D11Texture::release()
+{
+ if (!tex)
+ return;
+
+ if (srv) {
+ srv->Release();
+ srv = nullptr;
+ }
+
+ if (owns)
+ tex->Release();
+
+ tex = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ return DXGI_FORMAT_R16_FLOAT;
+ case QRhiTexture::Format::D32F:
+ return DXGI_FORMAT_R32_FLOAT;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R32_FLOAT;
+ }
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ return DXGI_FORMAT_D16_UNORM;
+ case QRhiTexture::Format::D32F:
+ return DXGI_FORMAT_D32_FLOAT;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_D32_FLOAT;
+ }
+}
+
+bool QD3D11Texture::prepareBuild(QSize *adjustedSize)
+{
+ if (tex)
+ release();
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ QRHI_RES_RHI(QRhiD3D11);
+ dxgiFormat = toD3DTextureFormat(m_format, m_flags);
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+ if (sampleDesc.Count > 1) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+ if (isDepth && hasMipMaps) {
+ qWarning("Depth texture cannot have mipmaps");
+ return false;
+ }
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QD3D11Texture::finishBuild()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
+ memset(&srvDesc, 0, sizeof(srvDesc));
+ srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
+ if (isCube) {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
+ srvDesc.TextureCube.MipLevels = mipLevelCount;
+ } else {
+ if (sampleDesc.Count > 1) {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
+ } else {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ srvDesc.Texture2D.MipLevels = mipLevelCount;
+ }
+ }
+
+ HRESULT hr = rhiD->dev->CreateShaderResourceView(tex, &srvDesc, &srv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create srv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ nativeHandlesStruct.texture = tex;
+
+ generation += 1;
+ return true;
+}
+
+bool QD3D11Texture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ uint bindFlags = D3D11_BIND_SHADER_RESOURCE;
+ uint miscFlags = isCube ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;
+ if (m_flags.testFlag(RenderTarget)) {
+ if (isDepth)
+ bindFlags |= D3D11_BIND_DEPTH_STENCIL;
+ else
+ bindFlags |= D3D11_BIND_RENDER_TARGET;
+ }
+ if (m_flags.testFlag(UsedWithGenerateMips)) {
+ if (isDepth) {
+ qWarning("Depth texture cannot have mipmaps generated");
+ return false;
+ }
+ bindFlags |= D3D11_BIND_RENDER_TARGET;
+ miscFlags |= D3D11_RESOURCE_MISC_GENERATE_MIPS;
+ }
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = size.width();
+ desc.Height = size.height();
+ desc.MipLevels = mipLevelCount;
+ desc.ArraySize = isCube ? 6 : 1;
+ desc.Format = dxgiFormat;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = bindFlags;
+ desc.MiscFlags = miscFlags;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (!finishBuild())
+ return false;
+
+ if (!m_objectName.isEmpty())
+ tex->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, sampleDesc.Count));
+
+ owns = true;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QD3D11Texture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiD3D11TextureNativeHandles *h = static_cast<const QRhiD3D11TextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ tex = static_cast<ID3D11Texture2D *>(h->texture);
+
+ if (!finishBuild())
+ return false;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, sampleDesc.Count));
+
+ owns = false;
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QD3D11Texture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QD3D11Sampler::QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QD3D11Sampler::~QD3D11Sampler()
+{
+ release();
+}
+
+void QD3D11Sampler::release()
+{
+ if (!samplerState)
+ return;
+
+ samplerState->Release();
+ samplerState = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->unregisterResource(this);
+}
+
+static inline D3D11_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter)
+{
+ if (minFilter == QRhiSampler::Nearest) {
+ if (magFilter == QRhiSampler::Nearest) {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_MAG_MIP_POINT;
+ } else {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
+ }
+ } else {
+ if (magFilter == QRhiSampler::Nearest) {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT;
+ } else {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;
+ }
+ }
+
+ Q_UNREACHABLE();
+ return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+}
+
+static inline D3D11_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return D3D11_TEXTURE_ADDRESS_WRAP;
+ case QRhiSampler::ClampToEdge:
+ return D3D11_TEXTURE_ADDRESS_CLAMP;
+ case QRhiSampler::Border:
+ return D3D11_TEXTURE_ADDRESS_BORDER;
+ case QRhiSampler::Mirror:
+ return D3D11_TEXTURE_ADDRESS_MIRROR;
+ case QRhiSampler::MirrorOnce:
+ return D3D11_TEXTURE_ADDRESS_MIRROR_ONCE;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_TEXTURE_ADDRESS_CLAMP;
+ }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return D3D11_COMPARISON_NEVER;
+ case QRhiSampler::Less:
+ return D3D11_COMPARISON_LESS;
+ case QRhiSampler::Equal:
+ return D3D11_COMPARISON_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return D3D11_COMPARISON_LESS_EQUAL;
+ case QRhiSampler::Greater:
+ return D3D11_COMPARISON_GREATER;
+ case QRhiSampler::NotEqual:
+ return D3D11_COMPARISON_NOT_EQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return D3D11_COMPARISON_GREATER_EQUAL;
+ case QRhiSampler::Always:
+ return D3D11_COMPARISON_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_COMPARISON_NEVER;
+ }
+}
+
+bool QD3D11Sampler::build()
+{
+ if (samplerState)
+ release();
+
+ D3D11_SAMPLER_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode);
+ if (m_compareOp != Never)
+ desc.Filter = D3D11_FILTER(desc.Filter | 0x80);
+ desc.AddressU = toD3DAddressMode(m_addressU);
+ desc.AddressV = toD3DAddressMode(m_addressV);
+ desc.AddressW = toD3DAddressMode(m_addressW);
+ desc.MaxAnisotropy = 1.0f;
+ desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp);
+ desc.MaxLOD = m_mipmapMode == None ? 0.0f : 1000.0f;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateSamplerState(&desc, &samplerState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create sampler state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here
+QD3D11RenderPassDescriptor::QD3D11RenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QD3D11RenderPassDescriptor::~QD3D11RenderPassDescriptor()
+{
+ release();
+}
+
+void QD3D11RenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QD3D11ReferenceRenderTarget::QD3D11ReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QD3D11ReferenceRenderTarget::~QD3D11ReferenceRenderTarget()
+{
+ release();
+}
+
+void QD3D11ReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QD3D11ReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QD3D11ReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QD3D11ReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QD3D11TextureRenderTarget::QD3D11TextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+ ownsRtv[i] = false;
+ rtv[i] = nullptr;
+ }
+}
+
+QD3D11TextureRenderTarget::~QD3D11TextureRenderTarget()
+{
+ release();
+}
+
+void QD3D11TextureRenderTarget::release()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+
+ if (!rtv[0] && !dsv)
+ return;
+
+ if (dsv) {
+ if (ownsDsv)
+ dsv->Release();
+ dsv = nullptr;
+ }
+
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+ if (rtv[i]) {
+ if (ownsRtv[i])
+ rtv[i]->Release();
+ rtv[i] = nullptr;
+ }
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QD3D11TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QD3D11RenderPassDescriptor(m_rhi);
+}
+
+bool QD3D11TextureRenderTarget::build()
+{
+ if (rtv[0] || dsv)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ QRHI_RES_RHI(QRhiD3D11);
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ QRhiTexture *texture = colorAttachments[i].texture();
+ QRhiRenderBuffer *rb = colorAttachments[i].renderBuffer();
+ Q_ASSERT(texture || rb);
+ if (texture) {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, texture);
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
+ if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+ rtvDesc.Texture2DArray.MipSlice = colorAttachments[i].level();
+ rtvDesc.Texture2DArray.FirstArraySlice = colorAttachments[i].layer();
+ rtvDesc.Texture2DArray.ArraySize = 1;
+ } else {
+ if (texD->sampleDesc.Count > 1) {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
+ } else {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ rtvDesc.Texture2D.MipSlice = colorAttachments[i].level();
+ }
+ }
+ HRESULT hr = rhiD->dev->CreateRenderTargetView(texD->tex, &rtvDesc, &rtv[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ ownsRtv[i] = true;
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = texD->sampleDesc.Count;
+ }
+ } else if (rb) {
+ QD3D11RenderBuffer *rbD = QRHI_RES(QD3D11RenderBuffer, rb);
+ ownsRtv[i] = false;
+ rtv[i] = rbD->rtv;
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->sampleDesc.Count;
+ }
+ }
+ }
+ d.dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ ownsDsv = true;
+ QD3D11Texture *depthTexD = QRHI_RES(QD3D11Texture, m_desc.depthTexture());
+ D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
+ memset(&dsvDesc, 0, sizeof(dsvDesc));
+ dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
+ dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+ : D3D11_DSV_DIMENSION_TEXTURE2D;
+ HRESULT hr = rhiD->dev->CreateDepthStencilView(depthTexD->tex, &dsvDesc, &dsv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create dsv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = depthTexD->sampleDesc.Count;
+ }
+ } else {
+ ownsDsv = false;
+ QD3D11RenderBuffer *depthRbD = QRHI_RES(QD3D11RenderBuffer, m_desc.depthStencilBuffer());
+ dsv = depthRbD->dsv;
+ if (d.colorAttCount == 0) {
+ d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+ d.sampleCount = depthRbD->sampleDesc.Count;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i)
+ d.rtv[i] = i < d.colorAttCount ? rtv[i] : nullptr;
+
+ d.dsv = dsv;
+ d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QD3D11TextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QD3D11TextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QD3D11TextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QD3D11ShaderResourceBindings::QD3D11ShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QD3D11ShaderResourceBindings::~QD3D11ShaderResourceBindings()
+{
+ release();
+}
+
+void QD3D11ShaderResourceBindings::release()
+{
+ sortedBindings.clear();
+}
+
+bool QD3D11ShaderResourceBindings::build()
+{
+ if (!sortedBindings.isEmpty())
+ release();
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+
+ boundResourceData.resize(sortedBindings.count());
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->updateShaderResourceBindings(this);
+
+ generation += 1;
+ return true;
+}
+
+QD3D11GraphicsPipeline::QD3D11GraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QD3D11GraphicsPipeline::~QD3D11GraphicsPipeline()
+{
+ release();
+}
+
+void QD3D11GraphicsPipeline::release()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+
+ if (!dsState)
+ return;
+
+ dsState->Release();
+ dsState = nullptr;
+
+ if (blendState) {
+ blendState->Release();
+ blendState = nullptr;
+ }
+
+ if (inputLayout) {
+ inputLayout->Release();
+ inputLayout = nullptr;
+ }
+
+ if (rastState) {
+ rastState->Release();
+ rastState = nullptr;
+ }
+
+ if (vs) {
+ vs->Release();
+ vs = nullptr;
+ }
+
+ if (fs) {
+ fs->Release();
+ fs = nullptr;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+static inline D3D11_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return D3D11_CULL_NONE;
+ case QRhiGraphicsPipeline::Front:
+ return D3D11_CULL_FRONT;
+ case QRhiGraphicsPipeline::Back:
+ return D3D11_CULL_BACK;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_CULL_NONE;
+ }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return D3D11_COMPARISON_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return D3D11_COMPARISON_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return D3D11_COMPARISON_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return D3D11_COMPARISON_LESS_EQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return D3D11_COMPARISON_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return D3D11_COMPARISON_NOT_EQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return D3D11_COMPARISON_GREATER_EQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return D3D11_COMPARISON_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_COMPARISON_ALWAYS;
+ }
+}
+
+static inline D3D11_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return D3D11_STENCIL_OP_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return D3D11_STENCIL_OP_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return D3D11_STENCIL_OP_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return D3D11_STENCIL_OP_INCR_SAT;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return D3D11_STENCIL_OP_DECR_SAT;
+ case QRhiGraphicsPipeline::Invert:
+ return D3D11_STENCIL_OP_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return D3D11_STENCIL_OP_INCR;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return D3D11_STENCIL_OP_DECR;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_STENCIL_OP_KEEP;
+ }
+}
+
+static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+ case QRhiVertexInputAttribute::Float3:
+ return DXGI_FORMAT_R32G32B32_FLOAT;
+ case QRhiVertexInputAttribute::Float2:
+ return DXGI_FORMAT_R32G32_FLOAT;
+ case QRhiVertexInputAttribute::Float:
+ return DXGI_FORMAT_R32_FLOAT;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return DXGI_FORMAT_R8G8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte:
+ return DXGI_FORMAT_R8_UNORM;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+ }
+}
+
+static inline D3D11_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
+ case QRhiGraphicsPipeline::LineStrip:
+ return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
+ case QRhiGraphicsPipeline::Points:
+ return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ }
+}
+
+static inline uint toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+ uint f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= D3D11_COLOR_WRITE_ENABLE_RED;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= D3D11_COLOR_WRITE_ENABLE_GREEN;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= D3D11_COLOR_WRITE_ENABLE_BLUE;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
+ return f;
+}
+
+static inline D3D11_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return D3D11_BLEND_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return D3D11_BLEND_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return D3D11_BLEND_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return D3D11_BLEND_INV_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return D3D11_BLEND_DEST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return D3D11_BLEND_INV_DEST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return D3D11_BLEND_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return D3D11_BLEND_INV_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return D3D11_BLEND_DEST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return D3D11_BLEND_INV_DEST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return D3D11_BLEND_BLEND_FACTOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return D3D11_BLEND_INV_BLEND_FACTOR;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return D3D11_BLEND_SRC_ALPHA_SAT;
+ case QRhiGraphicsPipeline::Src1Color:
+ return D3D11_BLEND_SRC1_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return D3D11_BLEND_INV_SRC1_COLOR;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return D3D11_BLEND_SRC1_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return D3D11_BLEND_INV_SRC1_ALPHA;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_BLEND_ZERO;
+ }
+}
+
+static inline D3D11_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return D3D11_BLEND_OP_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return D3D11_BLEND_OP_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return D3D11_BLEND_OP_REV_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return D3D11_BLEND_OP_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return D3D11_BLEND_OP_MAX;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_BLEND_OP_ADD;
+ }
+}
+
+static QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, QString *error)
+{
+ QShaderCode dxbc = shader.shader({ QShader::DxbcShader, 50, shaderVariant });
+ if (!dxbc.shader().isEmpty())
+ return dxbc.shader();
+
+ QShaderCode hlslSource = shader.shader({ QShader::HlslShader, 50, shaderVariant });
+ if (hlslSource.shader().isEmpty()) {
+ qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader;
+ return QByteArray();
+ }
+
+ const char *target;
+ switch (shader.stage()) {
+ case QShader::VertexStage:
+ target = "vs_5_0";
+ break;
+ case QShader::TessellationControlStage:
+ target = "hs_5_0";
+ break;
+ case QShader::TessellationEvaluationStage:
+ target = "ds_5_0";
+ break;
+ case QShader::GeometryStage:
+ target = "gs_5_0";
+ break;
+ case QShader::FragmentStage:
+ target = "ps_5_0";
+ break;
+ case QShader::ComputeStage:
+ target = "cs_5_0";
+ break;
+ default:
+ Q_UNREACHABLE();
+ return QByteArray();
+ }
+
+ ID3DBlob *bytecode = nullptr;
+ ID3DBlob *errors = nullptr;
+ HRESULT hr = D3DCompile(hlslSource.shader().constData(), hlslSource.shader().size(),
+ nullptr, nullptr, nullptr,
+ hlslSource.entryPoint().constData(), target, 0, 0, &bytecode, &errors);
+ if (FAILED(hr) || !bytecode) {
+ qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
+ if (errors) {
+ *error = QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()),
+ errors->GetBufferSize());
+ errors->Release();
+ }
+ return QByteArray();
+ }
+
+ QByteArray result;
+ result.resize(bytecode->GetBufferSize());
+ memcpy(result.data(), bytecode->GetBufferPointer(), result.size());
+ bytecode->Release();
+ return result;
+}
+
+bool QD3D11GraphicsPipeline::build()
+{
+ if (dsState)
+ release();
+
+ QRHI_RES_RHI(QRhiD3D11);
+
+ D3D11_RASTERIZER_DESC rastDesc;
+ memset(&rastDesc, 0, sizeof(rastDesc));
+ rastDesc.FillMode = D3D11_FILL_SOLID;
+ rastDesc.CullMode = toD3DCullMode(m_cullMode);
+ rastDesc.FrontCounterClockwise = m_frontFace == CCW;
+ rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
+ rastDesc.MultisampleEnable = rhiD->effectiveSampleCount(m_sampleCount).Count > 1;
+ HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rasterizer state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_DEPTH_STENCIL_DESC dsDesc;
+ memset(&dsDesc, 0, sizeof(dsDesc));
+ dsDesc.DepthEnable = m_depthTest;
+ dsDesc.DepthWriteMask = m_depthWrite ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
+ dsDesc.DepthFunc = toD3DCompareOp(m_depthOp);
+ dsDesc.StencilEnable = m_stencilTest;
+ if (m_stencilTest) {
+ dsDesc.StencilReadMask = m_stencilReadMask;
+ dsDesc.StencilWriteMask = m_stencilWriteMask;
+ dsDesc.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
+ dsDesc.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
+ dsDesc.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
+ dsDesc.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
+ dsDesc.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
+ dsDesc.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
+ }
+ hr = rhiD->dev->CreateDepthStencilState(&dsDesc, &dsState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create depth-stencil state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_BLEND_DESC blendDesc;
+ memset(&blendDesc, 0, sizeof(blendDesc));
+ blendDesc.IndependentBlendEnable = m_targetBlends.count() > 1;
+ for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+ D3D11_RENDER_TARGET_BLEND_DESC blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.BlendEnable = b.enable;
+ blend.SrcBlend = toD3DBlendFactor(b.srcColor);
+ blend.DestBlend = toD3DBlendFactor(b.dstColor);
+ blend.BlendOp = toD3DBlendOp(b.opColor);
+ blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha);
+ blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha);
+ blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
+ blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
+ blendDesc.RenderTarget[i] = blend;
+ }
+ if (m_targetBlends.isEmpty()) {
+ D3D11_RENDER_TARGET_BLEND_DESC blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+ blendDesc.RenderTarget[0] = blend;
+ }
+ hr = rhiD->dev->CreateBlendState(&blendDesc, &blendState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create blend state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ QByteArray vsByteCode;
+ for (const QRhiGraphicsShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ QString error;
+ QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), shaderStage.shaderVariant(), &error);
+ if (bytecode.isEmpty()) {
+ qWarning("HLSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ switch (shaderStage.type()) {
+ case QRhiGraphicsShaderStage::Vertex:
+ hr = rhiD->dev->CreateVertexShader(bytecode.constData(), bytecode.size(), nullptr, &vs);
+ if (FAILED(hr)) {
+ qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ vsByteCode = bytecode;
+ break;
+ case QRhiGraphicsShaderStage::Fragment:
+ hr = rhiD->dev->CreatePixelShader(bytecode.constData(), bytecode.size(), nullptr, &fs);
+ if (FAILED(hr)) {
+ qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ d3dTopology = toD3DTopology(m_topology);
+
+ if (!vsByteCode.isEmpty()) {
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ QVarLengthArray<D3D11_INPUT_ELEMENT_DESC, 4> inputDescs;
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ D3D11_INPUT_ELEMENT_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ // the output from SPIRV-Cross uses TEXCOORD<location> as the semantic
+ desc.SemanticName = "TEXCOORD";
+ desc.SemanticIndex = attribute.location();
+ desc.Format = toD3DAttributeFormat(attribute.format());
+ desc.InputSlot = attribute.binding();
+ desc.AlignedByteOffset = attribute.offset();
+ const QRhiVertexInputBinding &binding(bindings[attribute.binding()]);
+ if (binding.classification() == QRhiVertexInputBinding::PerInstance) {
+ desc.InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
+ desc.InstanceDataStepRate = binding.instanceStepRate();
+ } else {
+ desc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ }
+ inputDescs.append(desc);
+ }
+ hr = rhiD->dev->CreateInputLayout(inputDescs.constData(), inputDescs.count(), vsByteCode, vsByteCode.size(), &inputLayout);
+ if (FAILED(hr)) {
+ qWarning("Failed to create input layout: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ }
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QD3D11CommandBuffer::QD3D11CommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QD3D11CommandBuffer::~QD3D11CommandBuffer()
+{
+ release();
+}
+
+void QD3D11CommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ tex[i] = nullptr;
+ rtv[i] = nullptr;
+ msaaTex[i] = nullptr;
+ msaaRtv[i] = nullptr;
+ timestampActive[i] = false;
+ timestampDisjointQuery[i] = nullptr;
+ timestampQuery[2 * i] = nullptr;
+ timestampQuery[2 * i + 1] = nullptr;
+ }
+}
+
+QD3D11SwapChain::~QD3D11SwapChain()
+{
+ release();
+}
+
+void QD3D11SwapChain::releaseBuffers()
+{
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (rtv[i]) {
+ rtv[i]->Release();
+ rtv[i] = nullptr;
+ }
+ if (tex[i]) {
+ tex[i]->Release();
+ tex[i] = nullptr;
+ }
+ if (msaaRtv[i]) {
+ msaaRtv[i]->Release();
+ msaaRtv[i] = nullptr;
+ }
+ if (msaaTex[i]) {
+ msaaTex[i]->Release();
+ msaaTex[i] = nullptr;
+ }
+ }
+}
+
+void QD3D11SwapChain::release()
+{
+ if (!swapChain)
+ return;
+
+ releaseBuffers();
+
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (timestampDisjointQuery[i]) {
+ timestampDisjointQuery[i]->Release();
+ timestampDisjointQuery[i] = nullptr;
+ }
+ for (int j = 0; j < 2; ++j) {
+ const int idx = BUFFER_COUNT * i + j;
+ if (timestampQuery[idx]) {
+ timestampQuery[idx]->Release();
+ timestampQuery[idx] = nullptr;
+ }
+ }
+ }
+
+ swapChain->Release();
+ swapChain = nullptr;
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QD3D11SwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QD3D11SwapChain::surfacePixelSize()
+{
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
+}
+
+QRhiRenderPassDescriptor *QD3D11SwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QD3D11RenderPassDescriptor(m_rhi);
+}
+
+bool QD3D11SwapChain::newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+ ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const
+{
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = size.width();
+ desc.Height = size.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = format;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color buffer texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = format;
+ rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(*tex, &rtvDesc, rtv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color buffer rtv: %s", qPrintable(comErrorMessage(hr)));
+ (*tex)->Release();
+ *tex = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool QD3D11SwapChain::buildOrResize()
+{
+ // Can be called multiple times due to window resizes - that is not the
+ // same as a simple release+build (as with other resources). Just need to
+ // resize the buffers then.
+
+ const bool needsRegistration = !window || window != m_window;
+
+ // except if the window actually changes
+ if (window && window != m_window)
+ release();
+
+ window = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ if (pixelSize.isEmpty())
+ return false;
+
+ colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ const DXGI_FORMAT srgbAdjustedFormat = m_flags.testFlag(sRGB) ?
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ const UINT swapChainFlags = 0;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ if (!swapChain) {
+ HWND hwnd = reinterpret_cast<HWND>(window->winId());
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+
+ // We use FLIP_DISCARD which implies a buffer count of 2 (as opposed to the
+ // old DISCARD with back buffer count == 1). This makes no difference for
+ // the rest of the stuff except that automatic MSAA is unsupported and
+ // needs to be implemented via a custom multisample render target and an
+ // explicit resolve.
+
+ HRESULT hr;
+ if (rhiD->hasDxgi2) {
+ DXGI_SWAP_CHAIN_DESC1 desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = pixelSize.width();
+ desc.Height = pixelSize.height();
+ desc.Format = colorFormat;
+ desc.SampleDesc.Count = 1;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.BufferCount = BUFFER_COUNT;
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ if (m_flags.testFlag(SurfaceHasPreMulAlpha))
+ desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+ else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha))
+ desc.AlphaMode = DXGI_ALPHA_MODE_STRAIGHT;
+ desc.Flags = swapChainFlags;
+
+ IDXGISwapChain1 *sc1;
+ hr = static_cast<IDXGIFactory2 *>(rhiD->dxgiFactory)->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc,
+ nullptr, nullptr, &sc1);
+ if (SUCCEEDED(hr))
+ swapChain = sc1;
+ } else {
+ // Windows 7
+ DXGI_SWAP_CHAIN_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.BufferDesc.Width = pixelSize.width();
+ desc.BufferDesc.Height = pixelSize.height();
+ desc.BufferDesc.RefreshRate.Numerator = 60;
+ desc.BufferDesc.RefreshRate.Denominator = 1;
+ desc.BufferDesc.Format = colorFormat;
+ desc.SampleDesc.Count = 1;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.BufferCount = BUFFER_COUNT;
+ desc.OutputWindow = hwnd;
+ desc.Windowed = true;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ desc.Flags = swapChainFlags;
+
+ hr = rhiD->dxgiFactory->CreateSwapChain(rhiD->dev, &desc, &swapChain);
+ }
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else {
+ releaseBuffers();
+ HRESULT hr = swapChain->ResizeBuffers(2, pixelSize.width(), pixelSize.height(), colorFormat, swapChainFlags);
+ if (FAILED(hr)) {
+ qWarning("Failed to resize D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ }
+
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ HRESULT hr = swapChain->GetBuffer(0, IID_ID3D11Texture2D, reinterpret_cast<void **>(&tex[i]));
+ if (FAILED(hr)) {
+ qWarning("Failed to query swapchain buffer %d: %s", i, qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = srgbAdjustedFormat;
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(tex[i], &rtvDesc, &rtv[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv for swapchain buffer %d: %s", i, qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (sampleDesc.Count > 1) {
+ if (!newColorBuffer(pixelSize, srgbAdjustedFormat, sampleDesc, &msaaTex[i], &msaaRtv[i]))
+ return false;
+ }
+ }
+
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ currentFrameSlot = 0;
+ frameCount = 0;
+ ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr;
+ swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1;
+
+ QD3D11ReferenceRenderTarget *rtD = QRHI_RES(QD3D11ReferenceRenderTarget, &rt);
+ rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+ rtD->d.pixelSize = pixelSize;
+ rtD->d.dpr = window->devicePixelRatio();
+ rtD->d.sampleCount = sampleDesc.Count;
+ rtD->d.colorAttCount = 1;
+ rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, BUFFER_COUNT, sampleDesc.Count > 1 ? BUFFER_COUNT : 0, sampleDesc.Count));
+ if (rhiP) {
+ D3D11_QUERY_DESC queryDesc;
+ memset(&queryDesc, 0, sizeof(queryDesc));
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (!timestampDisjointQuery[i]) {
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
+ HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &timestampDisjointQuery[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp disjoint query: %s", qPrintable(comErrorMessage(hr)));
+ break;
+ }
+ }
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP;
+ for (int j = 0; j < 2; ++j) {
+ const int idx = BUFFER_COUNT * i + j; // one pair per buffer (frame)
+ if (!timestampQuery[idx]) {
+ HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &timestampQuery[idx]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp query: %s", qPrintable(comErrorMessage(hr)));
+ break;
+ }
+ }
+ }
+ }
+ // timestamp queries are optional so we can go on even if they failed
+ }
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h
new file mode 100644
index 0000000000..3e2e492d9c
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11_p.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHID3D11_H
+#define QRHID3D11_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+// no d3d includes here, to prevent precompiled header mess (due to this being
+// a public header)
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiD3D11InitParams : public QRhiInitParams
+{
+ bool enableDebugLayer = false;
+};
+
+struct Q_GUI_EXPORT QRhiD3D11NativeHandles : public QRhiNativeHandles
+{
+ void *dev = nullptr;
+ void *context = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiD3D11TextureNativeHandles : public QRhiNativeHandles
+{
+ void *texture = nullptr; // ID3D11Texture2D*
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h
new file mode 100644
index 0000000000..fd5247b0b4
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11_p_p.h
@@ -0,0 +1,624 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHID3D11_P_H
+#define QRHID3D11_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhid3d11_p.h"
+#include "qrhi_p_p.h"
+#include "qshaderdescription_p.h"
+#include <QWindow>
+
+#include <d3d11_1.h>
+#include <dxgi1_3.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QD3D11Buffer : public QRhiBuffer
+{
+ QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QD3D11Buffer();
+ void release() override;
+ bool build() override;
+
+ ID3D11Buffer *buffer = nullptr;
+ QByteArray dynBuf;
+ bool hasPendingDynamicUpdates = false;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11RenderBuffer : public QRhiRenderBuffer
+{
+ QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QD3D11RenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ ID3D11Texture2D *tex = nullptr;
+ ID3D11DepthStencilView *dsv = nullptr;
+ ID3D11RenderTargetView *rtv = nullptr;
+ DXGI_FORMAT dxgiFormat;
+ DXGI_SAMPLE_DESC sampleDesc;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11Texture : public QRhiTexture
+{
+ QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QD3D11Texture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+ bool finishBuild();
+
+ ID3D11Texture2D *tex = nullptr;
+ bool owns = true;
+ ID3D11ShaderResourceView *srv = nullptr;
+ DXGI_FORMAT dxgiFormat;
+ uint mipLevelCount = 0;
+ DXGI_SAMPLE_DESC sampleDesc;
+ QRhiD3D11TextureNativeHandles nativeHandlesStruct;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11Sampler : public QRhiSampler
+{
+ QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QD3D11Sampler();
+ void release() override;
+ bool build() override;
+
+ ID3D11SamplerState *samplerState = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QD3D11RenderPassDescriptor(QRhiImplementation *rhi);
+ ~QD3D11RenderPassDescriptor();
+ void release() override;
+};
+
+struct QD3D11RenderTargetData
+{
+ QD3D11RenderTargetData(QRhiImplementation *)
+ {
+ for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
+ rtv[i] = nullptr;
+ }
+
+ QD3D11RenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+ ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS];
+ ID3D11DepthStencilView *dsv = nullptr;
+};
+
+struct QD3D11ReferenceRenderTarget : public QRhiRenderTarget
+{
+ QD3D11ReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QD3D11ReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QD3D11RenderTargetData d;
+};
+
+struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QD3D11TextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QD3D11RenderTargetData d;
+ bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+ ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+ bool ownsDsv = false;
+ ID3D11DepthStencilView *dsv = nullptr;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QD3D11ShaderResourceBindings(QRhiImplementation *rhi);
+ ~QD3D11ShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ uint generation = 0;
+
+ // Keep track of the generation number of each referenced QRhi* to be able
+ // to detect that the batched bindings are out of date.
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData;
+
+ QRhiBatchedBindings<ID3D11Buffer *> vsubufs;
+ QRhiBatchedBindings<UINT> vsubufoffsets;
+ QRhiBatchedBindings<UINT> vsubufsizes;
+
+ QRhiBatchedBindings<ID3D11Buffer *> fsubufs;
+ QRhiBatchedBindings<UINT> fsubufoffsets;
+ QRhiBatchedBindings<UINT> fsubufsizes;
+
+ QRhiBatchedBindings<ID3D11SamplerState *> vssamplers;
+ QRhiBatchedBindings<ID3D11ShaderResourceView *> vsshaderresources;
+
+ QRhiBatchedBindings<ID3D11SamplerState *> fssamplers;
+ QRhiBatchedBindings<ID3D11ShaderResourceView *> fsshaderresources;
+
+ friend class QRhiD3D11;
+};
+
+Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_MOVABLE_TYPE);
+
+struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QD3D11GraphicsPipeline(QRhiImplementation *rhi);
+ ~QD3D11GraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ ID3D11DepthStencilState *dsState = nullptr;
+ ID3D11BlendState *blendState = nullptr;
+ ID3D11VertexShader *vs = nullptr;
+ ID3D11PixelShader *fs = nullptr;
+ ID3D11InputLayout *inputLayout = nullptr;
+ D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ ID3D11RasterizerState *rastState = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11SwapChain;
+
+struct QD3D11CommandBuffer : public QRhiCommandBuffer
+{
+ QD3D11CommandBuffer(QRhiImplementation *rhi);
+ ~QD3D11CommandBuffer();
+ void release() override;
+
+ struct Command {
+ enum Cmd {
+ SetRenderTarget,
+ Clear,
+ Viewport,
+ Scissor,
+ BindVertexBuffers,
+ BindIndexBuffer,
+ BindGraphicsPipeline,
+ BindShaderResources,
+ StencilRef,
+ BlendConstants,
+ Draw,
+ DrawIndexed,
+ UpdateSubRes,
+ CopySubRes,
+ ResolveSubRes,
+ GenMip,
+ DebugMarkBegin,
+ DebugMarkEnd,
+ DebugMarkMsg
+ };
+ enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 };
+ Cmd cmd;
+
+ static const int MAX_UBUF_BINDINGS = 32; // should be D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT but 128 is a waste of space for our purposes
+
+ // QRhi*/QD3D11* references should be kept at minimum (so no
+ // QRhiTexture/Buffer/etc. pointers).
+ union {
+ struct {
+ QRhiRenderTarget *rt;
+ } setRenderTarget;
+ struct {
+ QRhiRenderTarget *rt;
+ int mask;
+ float c[4];
+ float d;
+ quint32 s;
+ } clear;
+ struct {
+ float x, y, w, h;
+ float d0, d1;
+ } viewport;
+ struct {
+ int x, y, w, h;
+ } scissor;
+ struct {
+ int startSlot;
+ int slotCount;
+ ID3D11Buffer *buffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ UINT offsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ UINT strides[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ } bindVertexBuffers;
+ struct {
+ ID3D11Buffer *buffer;
+ quint32 offset;
+ DXGI_FORMAT format;
+ } bindIndexBuffer;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ } bindGraphicsPipeline;
+ struct {
+ QD3D11ShaderResourceBindings *srb;
+ bool offsetOnlyChange;
+ int dynamicOffsetCount;
+ uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offsetInConstants
+ } bindShaderResources;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 ref;
+ } stencilRef;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ float c[4];
+ } blendConstants;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 vertexCount;
+ quint32 instanceCount;
+ quint32 firstVertex;
+ quint32 firstInstance;
+ } draw;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 indexCount;
+ quint32 instanceCount;
+ quint32 firstIndex;
+ qint32 vertexOffset;
+ quint32 firstInstance;
+ } drawIndexed;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ bool hasDstBox;
+ D3D11_BOX dstBox;
+ const void *src; // must come from retain*()
+ UINT srcRowPitch;
+ } updateSubRes;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ UINT dstX;
+ UINT dstY;
+ ID3D11Resource *src;
+ UINT srcSubRes;
+ bool hasSrcBox;
+ D3D11_BOX srcBox;
+ } copySubRes;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ ID3D11Resource *src;
+ UINT srcSubRes;
+ DXGI_FORMAT format;
+ } resolveSubRes;
+ struct {
+ ID3D11ShaderResourceView *srv;
+ } genMip;
+ struct {
+ char s[64];
+ } debugMark;
+ } args;
+ };
+
+ QVector<Command> commands;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentPipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentSrb;
+ uint currentSrbGeneration;
+ ID3D11Buffer *currentIndexBuffer;
+ quint32 currentIndexOffset;
+ DXGI_FORMAT currentIndexFormat;
+ ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+
+ QVector<QByteArray> dataRetainPool;
+ QVector<QImage> imageRetainPool;
+
+ // relies heavily on implicit sharing (no copies of the actual data will be made)
+ const uchar *retainData(const QByteArray &data) {
+ dataRetainPool.append(data);
+ return reinterpret_cast<const uchar *>(dataRetainPool.constLast().constData());
+ }
+ const uchar *retainImage(const QImage &image) {
+ imageRetainPool.append(image);
+ return imageRetainPool.constLast().constBits();
+ }
+ void resetCommands() {
+ commands.clear();
+ dataRetainPool.clear();
+ imageRetainPool.clear();
+ }
+ void resetState() {
+ resetCommands();
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+ void resetCachedState() {
+ currentPipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentIndexBuffer = nullptr;
+ currentIndexOffset = 0;
+ currentIndexFormat = DXGI_FORMAT_R16_UINT;
+ memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
+ memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
+ }
+};
+
+Q_DECLARE_TYPEINFO(QD3D11CommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QD3D11SwapChain : public QRhiSwapChain
+{
+ QD3D11SwapChain(QRhiImplementation *rhi);
+ ~QD3D11SwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ void releaseBuffers();
+ bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+ ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ QD3D11ReferenceRenderTarget rt;
+ QD3D11CommandBuffer cb;
+ DXGI_FORMAT colorFormat;
+ IDXGISwapChain *swapChain = nullptr;
+ static const int BUFFER_COUNT = 2;
+ ID3D11Texture2D *tex[BUFFER_COUNT];
+ ID3D11RenderTargetView *rtv[BUFFER_COUNT];
+ ID3D11Texture2D *msaaTex[BUFFER_COUNT];
+ ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
+ DXGI_SAMPLE_DESC sampleDesc;
+ int currentFrameSlot = 0;
+ int frameCount = 0;
+ QD3D11RenderBuffer *ds = nullptr;
+ bool timestampActive[BUFFER_COUNT];
+ ID3D11Query *timestampDisjointQuery[BUFFER_COUNT];
+ ID3D11Query *timestampQuery[BUFFER_COUNT * 2];
+ UINT swapInterval = 1;
+};
+
+class QRhiD3D11 : public QRhiImplementation
+{
+public:
+ QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+
+ void flushCommandBuffer();
+ void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD);
+ void executeBufferHostWritesForCurrentFrame(QD3D11Buffer *bufD);
+ void bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+ const uint *dynOfsPairs, int dynOfsPairCount,
+ bool offsetOnlyChange);
+ void setRenderTarget(QRhiRenderTarget *rt);
+ void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr);
+ DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
+ void finishActiveReadbacks();
+ void enqueueSetRenderTarget(QD3D11CommandBuffer *cbD, QRhiRenderTarget *rt);
+ void reportLiveObjects(ID3D11Device *device);
+
+ bool debugLayer = false;
+ bool importedDevice = false;
+ ID3D11Device *dev = nullptr;
+ ID3D11DeviceContext1 *context = nullptr;
+ D3D_FEATURE_LEVEL featureLevel;
+ ID3DUserDefinedAnnotation *annotations = nullptr;
+ IDXGIFactory1 *dxgiFactory = nullptr;
+ bool hasDxgi2 = false;
+ QRhiD3D11NativeHandles nativeHandlesStruct;
+
+ bool inFrame = false;
+ bool inPass = false;
+
+ struct {
+ int vsHighestActiveSrvBinding = -1;
+ int fsHighestActiveSrvBinding = -1;
+ QD3D11SwapChain *currentSwapChain = nullptr;
+ } contextState;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QD3D11CommandBuffer cbWrapper;
+ } ofr;
+
+ struct ActiveReadback {
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ ID3D11Texture2D *stagingTex;
+ quint32 bufSize;
+ quint32 bpl;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+};
+
+Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
new file mode 100644
index 0000000000..0c89a9284f
--- /dev/null
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -0,0 +1,2975 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhigles2_p_p.h"
+#include <QWindow>
+#include <QOffscreenSurface>
+#include <QOpenGLContext>
+#include <QtGui/private/qopenglextensions_p.h>
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ OpenGL backend. Binding vertex attribute locations and decomposing uniform
+ buffers into uniforms are handled transparently to the application via the
+ reflection data (QShaderDescription). Real uniform buffers are never used,
+ regardless of the GLSL version. Textures and buffers feature no special logic,
+ it's all just glTexSubImage2D and glBufferSubData (with "dynamic" buffers set
+ to GL_DYNAMIC_DRAW). The swapchain and the associated renderbuffer for
+ depth-stencil will be dummies since we have no control over the underlying
+ buffers here. While we try to keep this backend clean GLES 2.0, some GL(ES)
+ 3.0 features like multisample renderbuffers and blits are used when available.
+*/
+
+/*!
+ \class QRhiGles2InitParams
+ \inmodule QtRhi
+ \brief OpenGL specific initialization parameters.
+
+ An OpenGL-based QRhi needs an already created QOffscreenSurface at minimum.
+ Additionally, while optional, it is recommended that the QWindow the first
+ QRhiSwapChain will target is passed in as well.
+
+ \badcode
+ QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
+ QRhiGles2InitParams params;
+ params.fallbackSurface = fallbackSurface;
+ params.window = window;
+ rhi = QRhi::create(QRhi::OpenGLES2, &params);
+ \endcode
+
+ By default QRhi creates a QOpenGLContext on its own. This approach works
+ well in most cases, included threaded scenarios, where there is a dedicated
+ QRhi for each rendering thread. As there will be a QOpenGLContext for each
+ QRhi, the OpenGL context requirements (a context can only be current on one
+ thread) are satisfied. The implicitly created context is destroyed
+ automatically together with the QRhi.
+
+ The QSurfaceFormat for the context is specified in \l format. The
+ constructor sets this to QSurfaceFormat::defaultFormat() so applications
+ that use QSurfaceFormat::setDefaultFormat() do not need to set the format
+ again.
+
+ \note The depth and stencil buffer sizes are set automatically to 24 and 8
+ when no size was explicitly set for these buffers in \l format. As there
+ are possible adjustments to \l format, applications can use
+ adjustedFormat() to query the effective format that is passed to
+ QOpenGLContext::setFormat() internally.
+
+ A QOffscreenSurface has to be specified in \l fallbackSurface. In order to
+ prevent mistakes in threaded situations, this is never created
+ automatically by the QRhi since, like QWindow, QOffscreenSurface can only
+ be created on the gui/main thread.
+
+ As a convenience, applications can use newFallbackSurface() which creates
+ and returns a QOffscreenSurface that is compatible with the QOpenGLContext
+ that is going to be created by the QRhi afterwards. Note that the ownership
+ of the returned QOffscreenSurface is transferred to the caller and the QRhi
+ will not destroy it.
+
+ \note QRhiSwapChain can only target QWindow instances that have their
+ surface type set to QSurface::OpenGLSurface.
+
+ \note \l window is optional. It is recommended to specify it whenever
+ possible, in order to avoid problems on multi-adapter and multi-screen
+ systems. When \l window is not set, the very first
+ QOpenGLContext::makeCurrent() happens with \l fallbackSurface which may be
+ an invisible window on some platforms (for example, Windows) and that may
+ trigger unexpected problems in some cases.
+
+ \section2 Working with existing OpenGL contexts
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same OpenGL context. This can be achieved
+ by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The
+ \l{QRhiGles2NativeHandles::context}{context} must be set to a non-null
+ value.
+
+ An alternative approach is to create a QOpenGLContext that
+ \l{QOpenGLContext::setShareContext()}{shares resources} with the other
+ engine's context and passing in that context via QRhiGles2NativeHandles.
+
+ The QRhi does not take ownership of the QOpenGLContext passed in via
+ QRhiGles2NativeHandles.
+ */
+
+/*!
+ \class QRhiGles2NativeHandles
+ \inmodule QtRhi
+ \brief Holds the OpenGL context used by the QRhi.
+ */
+
+/*!
+ \class QRhiGles2TextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the OpenGL texture object that is backing a QRhiTexture instance.
+ */
+
+#ifndef GL_BGRA
+#define GL_BGRA 0x80E1
+#endif
+
+#ifndef GL_R8
+#define GL_R8 0x8229
+#endif
+
+#ifndef GL_R16
+#define GL_R16 0x822A
+#endif
+
+#ifndef GL_RED
+#define GL_RED 0x1903
+#endif
+
+#ifndef GL_RGBA8
+#define GL_RGBA8 0x8058
+#endif
+
+#ifndef GL_RGBA32F
+#define GL_RGBA32F 0x8814
+#endif
+
+#ifndef GL_RGBA16F
+#define GL_RGBA16F 0x881A
+#endif
+
+#ifndef GL_HALF_FLOAT
+#define GL_HALF_FLOAT 0x140B
+#endif
+
+#ifndef GL_DEPTH_COMPONENT16
+#define GL_DEPTH_COMPONENT16 0x81A5
+#endif
+
+#ifndef GL_DEPTH_COMPONENT32F
+#define GL_DEPTH_COMPONENT32F 0x8CAC
+#endif
+
+#ifndef GL_DEPTH24_STENCIL8
+#define GL_DEPTH24_STENCIL8 0x88F0
+#endif
+
+#ifndef GL_PRIMITIVE_RESTART_FIXED_INDEX
+#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
+#endif
+
+#ifndef GL_FRAMEBUFFER_SRGB
+#define GL_FRAMEBUFFER_SRGB 0x8DB9
+#endif
+
+#ifndef GL_READ_FRAMEBUFFER
+#define GL_READ_FRAMEBUFFER 0x8CA8
+#endif
+
+#ifndef GL_DRAW_FRAMEBUFFER
+#define GL_DRAW_FRAMEBUFFER 0x8CA9
+#endif
+
+#ifndef GL_MAX_DRAW_BUFFERS
+#define GL_MAX_DRAW_BUFFERS 0x8824
+#endif
+
+#ifndef GL_TEXTURE_COMPARE_MODE
+#define GL_TEXTURE_COMPARE_MODE 0x884C
+#endif
+
+#ifndef GL_COMPARE_REF_TO_TEXTURE
+#define GL_COMPARE_REF_TO_TEXTURE 0x884E
+#endif
+
+#ifndef GL_TEXTURE_COMPARE_FUNC
+#define GL_TEXTURE_COMPARE_FUNC 0x884D
+#endif
+
+#ifndef GL_MAX_SAMPLES
+#define GL_MAX_SAMPLES 0x8D57
+#endif
+
+/*!
+ Constructs a new QRhiGles2InitParams.
+
+ \l format is set to QSurfaceFormat::defaultFormat().
+ */
+QRhiGles2InitParams::QRhiGles2InitParams()
+{
+ format = QSurfaceFormat::defaultFormat();
+}
+
+/*!
+ \return the QSurfaceFormat that will be set on the QOpenGLContext before
+ calling QOpenGLContext::create(). This format is based on \a format, but
+ may be adjusted. Applicable only when QRhi creates the context.
+ Applications are advised to set this format on their QWindow in order to
+ avoid potential BAD_MATCH failures.
+ */
+QSurfaceFormat QRhiGles2InitParams::adjustedFormat(const QSurfaceFormat &format)
+{
+ QSurfaceFormat fmt = format;
+
+ if (fmt.depthBufferSize() == -1)
+ fmt.setDepthBufferSize(24);
+ if (fmt.stencilBufferSize() == -1)
+ fmt.setStencilBufferSize(8);
+
+ return fmt;
+}
+
+/*!
+ \return a new QOffscreenSurface that can be used with a QRhi by passing it
+ via a QRhiGles2InitParams.
+
+ \a format is adjusted as appropriate in order to avoid having problems
+ afterwards due to an incompatible context and surface.
+
+ \note This function must only be called on the gui/main thread.
+
+ \note It is the application's responsibility to destroy the returned
+ QOffscreenSurface on the gui/main thread once the associated QRhi has been
+ destroyed. The QRhi will not destroy the QOffscreenSurface.
+ */
+QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format)
+{
+ QSurfaceFormat fmt = adjustedFormat(format);
+
+ // To resolve all fields in the format as much as possible, create a context.
+ // This may be heavy, but allows avoiding BAD_MATCH on some systems.
+ QOpenGLContext tempContext;
+ tempContext.setFormat(fmt);
+ if (tempContext.create())
+ fmt = tempContext.format();
+ else
+ qWarning("QRhiGles2: Failed to create temporary context");
+
+ QOffscreenSurface *s = new QOffscreenSurface;
+ s->setFormat(fmt);
+ s->create();
+
+ return s;
+}
+
+QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice)
+ : ofr(this)
+{
+ requestedFormat = QRhiGles2InitParams::adjustedFormat(params->format);
+ fallbackSurface = params->fallbackSurface;
+ maybeWindow = params->window; // may be null
+
+ importedContext = importDevice != nullptr;
+ if (importedContext) {
+ ctx = importDevice->context;
+ if (!ctx) {
+ qWarning("No OpenGL context given, cannot import");
+ importedContext = false;
+ }
+ }
+}
+
+bool QRhiGles2::ensureContext(QSurface *surface) const
+{
+ bool nativeWindowGone = false;
+ if (surface && surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) {
+ surface = fallbackSurface;
+ nativeWindowGone = true;
+ }
+
+ if (!surface)
+ surface = fallbackSurface;
+
+ if (needsMakeCurrent)
+ needsMakeCurrent = false;
+ else if (!nativeWindowGone && QOpenGLContext::currentContext() == ctx && (surface == fallbackSurface || ctx->surface() == surface))
+ return true;
+
+ if (!ctx->makeCurrent(surface)) {
+ qWarning("QRhiGles2: Failed to make context current. Expect bad things to happen.");
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiGles2::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(fallbackSurface);
+
+ if (!importedContext) {
+ ctx = new QOpenGLContext;
+ ctx->setFormat(requestedFormat);
+ if (!ctx->create()) {
+ qWarning("QRhiGles2: Failed to create context");
+ delete ctx;
+ ctx = nullptr;
+ return false;
+ }
+ qDebug() << "Created OpenGL context" << ctx->format();
+ }
+
+ if (!ensureContext(maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments
+ return false;
+
+ f = static_cast<QOpenGLExtensions *>(ctx->extraFunctions());
+
+ const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR));
+ const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER));
+ const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION));
+ if (vendor && renderer && version)
+ qDebug("OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version);
+
+ const QSurfaceFormat actualFormat = ctx->format();
+
+ caps.ctxMajor = actualFormat.majorVersion();
+ caps.ctxMinor = actualFormat.minorVersion();
+
+ GLint n = 0;
+ f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &n);
+ supportedCompressedFormats.resize(n);
+ if (n > 0)
+ f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, supportedCompressedFormats.data());
+
+ f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize);
+
+ if (caps.ctxMajor >= 3 || actualFormat.renderableType() == QSurfaceFormat::OpenGL) {
+ f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers);
+ f->glGetIntegerv(GL_MAX_SAMPLES, &caps.maxSamples);
+ caps.maxSamples = qMax(1, caps.maxSamples);
+ } else {
+ caps.maxDrawBuffers = 1;
+ caps.maxSamples = 1;
+ }
+
+ caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)
+ && f->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit);
+
+ caps.npotTexture = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
+ caps.npotTextureRepeat = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat);
+
+ caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES;
+ if (caps.gles)
+ caps.fixedIndexPrimitiveRestart = caps.ctxMajor >= 3;
+ else
+ caps.fixedIndexPrimitiveRestart = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3);
+
+ if (caps.fixedIndexPrimitiveRestart)
+ f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+
+ caps.bgraExternalFormat = f->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat);
+ caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles;
+ caps.r8Format = f->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats);
+ caps.r16Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats);
+ caps.floatFormats = caps.ctxMajor >= 3;
+ caps.depthTexture = caps.ctxMajor >= 3;
+ caps.packedDepthStencil = f->hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil);
+ caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer);
+ caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile;
+ caps.uniformBuffers = caps.ctxMajor >= 3 && (caps.gles || caps.ctxMinor >= 1);
+ caps.elementIndexUint = f->hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint);
+
+ nativeHandlesStruct.context = ctx;
+
+ return true;
+}
+
+void QRhiGles2::destroy()
+{
+ if (!f)
+ return;
+
+ ensureContext();
+ executeDeferredReleases();
+
+ if (!importedContext) {
+ delete ctx;
+ ctx = nullptr;
+ }
+
+ f = nullptr;
+}
+
+void QRhiGles2::executeDeferredReleases()
+{
+ for (int i = releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]);
+ switch (e.type) {
+ case QRhiGles2::DeferredReleaseEntry::Buffer:
+ f->glDeleteBuffers(1, &e.buffer.buffer);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::Pipeline:
+ f->glDeleteProgram(e.pipeline.program);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::Texture:
+ f->glDeleteTextures(1, &e.texture.texture);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::RenderBuffer:
+ f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer);
+ f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer2);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget:
+ f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer);
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ releaseQueue.removeAt(i);
+ }
+}
+
+QVector<int> QRhiGles2::supportedSampleCounts() const
+{
+ if (supportedSampleCountList.isEmpty()) {
+ // 1, 2, 4, 8, ...
+ for (int i = 1; i <= caps.maxSamples; i *= 2)
+ supportedSampleCountList.append(i);
+ }
+ return supportedSampleCountList;
+}
+
+int QRhiGles2::effectiveSampleCount(int sampleCount) const
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ const int s = qBound(1, sampleCount, 64);
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return 1;
+ }
+ return s;
+}
+
+QRhiSwapChain *QRhiGles2::createSwapChain()
+{
+ return new QGles2SwapChain(this);
+}
+
+QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QGles2Buffer(this, type, usage, size);
+}
+
+int QRhiGles2::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiGles2::isYUpInFramebuffer() const
+{
+ return true;
+}
+
+bool QRhiGles2::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiGles2::isClipDepthZeroToOne() const
+{
+ return false;
+}
+
+QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const
+{
+ return QMatrix4x4(); // identity
+}
+
+static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::BC1:
+ return srgb ? 0x8C4C : 0x83F0;
+ case QRhiTexture::BC3:
+ return srgb ? 0x8C4E : 0x83F2;
+ case QRhiTexture::BC5:
+ return srgb ? 0x8C4F : 0x83F3;
+
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? 0x9275 : 0x9274;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? 0x9277 : 0x9276;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? 0x9279 : 0x9278;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? 0x93D0 : 0x93B0;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? 0x93D1 : 0x93B1;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? 0x93D2 : 0x93B2;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? 0x93D3 : 0x93B3;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? 0x93D4 : 0x93B4;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? 0x93D5 : 0x93B5;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? 0x93D6 : 0x93B6;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? 0x93D7 : 0x93B7;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? 0x93D8 : 0x93B8;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? 0x93D9 : 0x93B9;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? 0x93DA : 0x93BA;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? 0x93DB : 0x93BB;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? 0x93DC : 0x93BC;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? 0x93DD : 0x93BD;
+
+ default:
+ return 0; // this is reachable, just return an invalid format
+ }
+}
+
+bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ if (isCompressedFormat(format))
+ return supportedCompressedFormats.contains(toGlCompressedTextureFormat(format, flags));
+
+ switch (format) {
+ case QRhiTexture::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::D32F:
+ return caps.depthTexture;
+
+ case QRhiTexture::BGRA8:
+ return caps.bgraExternalFormat;
+
+ case QRhiTexture::R8:
+ return caps.r8Format;
+
+ case QRhiTexture::R16:
+ return caps.r16Format;
+
+ case QRhiTexture::RGBA16F:
+ Q_FALLTHROUGH();
+ case QRhiTexture::RGBA32F:
+ return caps.floatFormats;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return false;
+ case QRhi::MultisampleRenderBuffer:
+ return caps.msaaRenderBuffer;
+ case QRhi::DebugMarkers:
+ return false;
+ case QRhi::Timestamps:
+ return false;
+ case QRhi::Instancing:
+ return false;
+ case QRhi::CustomInstanceStepRate:
+ return false;
+ case QRhi::PrimitiveRestart:
+ return caps.fixedIndexPrimitiveRestart;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return caps.npotTextureRepeat;
+ case QRhi::RedOrAlpha8IsRed:
+ return caps.coreProfile;
+ case QRhi::ElementIndexUint:
+ return caps.elementIndexUint;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return caps.maxTextureSize;
+ case QRhi::MaxColorAttachments:
+ return caps.maxDrawBuffers;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiGles2::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiGles2::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QGles2Texture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QGles2TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline()
+{
+ return new QGles2GraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings()
+{
+ return new QGles2ShaderResourceBindings(this);
+}
+
+void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_ASSERT(inPass);
+
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+ const bool pipelineChanged = cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+ if (pipelineChanged) {
+ cbD->currentPipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline;
+ cmd.args.bindGraphicsPipeline.ps = ps;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_ASSERT(inPass);
+
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline);
+
+ if (!srb)
+ srb = QRHI_RES(QGles2GraphicsPipeline, cbD->currentPipeline)->m_shaderResourceBindings;
+
+ QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
+ bool hasDynamicOffsetInSrb = false;
+ for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->m_bindings[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ const bool srbChanged = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation;
+
+ if (srbChanged || hasDynamicOffsetInSrb) {
+ cbD->currentSrb = srb;
+ cbD->currentSrbGeneration = srbD->generation;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindShaderResources;
+ cmd.args.bindShaderResources.ps = cbD->currentPipeline;
+ cmd.args.bindShaderResources.srb = srb;
+ cmd.args.bindShaderResources.dynamicOffsetCount = 0;
+ if (hasDynamicOffsetInSrb) {
+ if (dynamicOffsetCount < QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS) {
+ cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
+ uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ *p++ = dynOfs.first;
+ *p++ = dynOfs.second;
+ }
+ } else {
+ qWarning("Too many dynamic offsets (%d, max is %d)",
+ dynamicOffsetCount, QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS);
+ }
+ }
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+
+ for (int i = 0; i < bindingCount; ++i) {
+ QRhiBuffer *buf = bindings[i].first;
+ quint32 ofs = bindings[i].second;
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer;
+ cmd.args.bindVertexBuffer.ps = cbD->currentPipeline;
+ cmd.args.bindVertexBuffer.buffer = bufD->buffer;
+ cmd.args.bindVertexBuffer.offset = ofs;
+ cmd.args.bindVertexBuffer.binding = startBinding + i;
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
+ cmd.args.bindIndexBuffer.offset = indexOffset;
+ cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Viewport;
+ const std::array<float, 4> r = viewport.viewport();
+ cmd.args.viewport.x = qMax(0.0f, r[0]);
+ cmd.args.viewport.y = qMax(0.0f, r[1]);
+ cmd.args.viewport.w = r[2];
+ cmd.args.viewport.h = r[3];
+ cmd.args.viewport.d0 = viewport.minDepth();
+ cmd.args.viewport.d1 = viewport.maxDepth();
+ QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd);
+}
+
+void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Scissor;
+ const std::array<int, 4> r = scissor.scissor();
+ cmd.args.scissor.x = qMax(0, r[0]);
+ cmd.args.scissor.y = qMax(0, r[1]);
+ cmd.args.scissor.w = r[2];
+ cmd.args.scissor.h = r[3];
+ QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd);
+}
+
+void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BlendConstants;
+ cmd.args.blendConstants.r = c.redF();
+ cmd.args.blendConstants.g = c.greenF();
+ cmd.args.blendConstants.b = c.blueF();
+ cmd.args.blendConstants.a = c.alphaF();
+ QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd);
+}
+
+void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::StencilRef;
+ cmd.args.stencilRef.ref = refValue;
+ cmd.args.stencilRef.ps = cbD->currentPipeline;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ Q_UNUSED(instanceCount); // no instancing
+ Q_UNUSED(firstInstance);
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Draw;
+ cmd.args.draw.ps = cbD->currentPipeline;
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ Q_UNUSED(instanceCount); // no instancing
+ Q_UNUSED(firstInstance);
+ Q_UNUSED(vertexOffset); // no glDrawElementsBaseVertex
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.ps = cbD->currentPipeline;
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+ Q_UNUSED(name);
+}
+
+void QRhiGles2::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+}
+
+void QRhiGles2::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+ Q_UNUSED(msg);
+}
+
+const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiGles2::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_ASSERT(inPass);
+ Q_UNUSED(cb);
+ flushCommandBuffer(); // also ensures the context is current
+}
+
+void QRhiGles2::endExternal(QRhiCommandBuffer *cb)
+{
+ Q_ASSERT(inPass);
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->currentTarget);
+ Q_ASSERT(cbD->commands.isEmpty());
+ cbD->resetCachedState();
+ enqueueBindFramebuffer(cbD->currentTarget, cbD);
+}
+
+static void addBoundaryCommand(QGles2CommandBuffer *cb, QGles2CommandBuffer::Command::Cmd type)
+{
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = type;
+ cb->commands.append(cmd);
+}
+
+QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(!inFrame);
+
+ QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
+ if (!ensureContext(swapChainD->surface))
+ return QRhi::FrameOpError;
+
+ inFrame = true;
+ currentSwapChain = swapChainD;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ executeDeferredReleases();
+ swapChainD->cb.resetState();
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_ASSERT(inFrame);
+ inFrame = false;
+
+ QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame);
+
+ if (!ensureContext(swapChainD->surface))
+ return QRhi::FrameOpError;
+
+ executeCommandBuffer(&swapChainD->cb);
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the swap
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (swapChainD->surface && !flags.testFlag(QRhi::SkipPresent)) {
+ ctx->swapBuffers(swapChainD->surface);
+ needsMakeCurrent = true;
+ } else {
+ f->glFlush();
+ }
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ Q_ASSERT(!inFrame);
+
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+
+ inFrame = true;
+ ofr.active = true;
+
+ executeDeferredReleases();
+ ofr.cbWrapper.resetState();
+
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame);
+ *cb = &ofr.cbWrapper;
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::endOffscreenFrame()
+{
+ Q_ASSERT(inFrame && ofr.active);
+ inFrame = false;
+ ofr.active = false;
+
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame);
+
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+
+ executeCommandBuffer(&ofr.cbWrapper);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::finish()
+{
+ Q_ASSERT(!inPass); // because that's what the QRhi docs say, even though not required by this backend
+ return inFrame ? flushCommandBuffer() : QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::flushCommandBuffer()
+{
+ if (ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+ executeCommandBuffer(&ofr.cbWrapper);
+ ofr.cbWrapper.resetCommands();
+ } else {
+ Q_ASSERT(currentSwapChain);
+ if (!ensureContext(currentSwapChain->surface))
+ return QRhi::FrameOpError;
+ executeCommandBuffer(&currentSwapChain->cb);
+ currentSwapChain->cb.resetCommands();
+ }
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
+{
+ const bool isCompressed = isCompressedFormat(texD->m_format);
+ const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap);
+ const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ const QPoint dp = subresDesc.destinationTopLeft();
+ const QByteArray rawData = subresDesc.data();
+ if (!subresDesc.image().isNull()) {
+ QImage img = subresDesc.image();
+ QSize size = img.size();
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::SubImage;
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const QPoint sp = subresDesc.sourceTopLeft();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ }
+ cmd.args.subImage.target = texD->target;
+ cmd.args.subImage.texture = texD->texture;
+ cmd.args.subImage.faceTarget = faceTargetBase + layer;
+ cmd.args.subImage.level = level;
+ cmd.args.subImage.dx = dp.x();
+ cmd.args.subImage.dy = dp.y();
+ cmd.args.subImage.w = size.width();
+ cmd.args.subImage.h = size.height();
+ cmd.args.subImage.glformat = texD->glformat;
+ cmd.args.subImage.gltype = texD->gltype;
+ cmd.args.subImage.rowStartAlign = 4;
+ cmd.args.subImage.data = cbD->retainImage(img);
+ cbD->commands.append(cmd);
+ } else if (!rawData.isEmpty() && isCompressed) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ if (texD->specified) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage;
+ cmd.args.compressedSubImage.target = texD->target;
+ cmd.args.compressedSubImage.texture = texD->texture;
+ cmd.args.compressedSubImage.faceTarget = faceTargetBase + layer;
+ cmd.args.compressedSubImage.level = level;
+ cmd.args.compressedSubImage.dx = dp.x();
+ cmd.args.compressedSubImage.dy = dp.y();
+ cmd.args.compressedSubImage.w = size.width();
+ cmd.args.compressedSubImage.h = size.height();
+ cmd.args.compressedSubImage.glintformat = texD->glintformat;
+ cmd.args.compressedSubImage.size = rawData.size();
+ cmd.args.compressedSubImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CompressedImage;
+ cmd.args.compressedImage.target = texD->target;
+ cmd.args.compressedImage.texture = texD->texture;
+ cmd.args.compressedImage.faceTarget = faceTargetBase + layer;
+ cmd.args.compressedImage.level = level;
+ cmd.args.compressedImage.glintformat = texD->glintformat;
+ cmd.args.compressedImage.w = size.width();
+ cmd.args.compressedImage.h = size.height();
+ cmd.args.compressedImage.size = rawData.size();
+ cmd.args.compressedImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ }
+ } else if (!rawData.isEmpty()) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::SubImage;
+ cmd.args.subImage.target = texD->target;
+ cmd.args.subImage.texture = texD->texture;
+ cmd.args.subImage.faceTarget = faceTargetBase + layer;
+ cmd.args.subImage.level = level;
+ cmd.args.subImage.dx = dp.x();
+ cmd.args.subImage.dy = dp.y();
+ cmd.args.subImage.w = size.width();
+ cmd.args.subImage.h = size.height();
+ cmd.args.subImage.glformat = texD->glformat;
+ cmd.args.subImage.gltype = texD->gltype;
+ // Default unpack alignment (row start aligment
+ // requirement) is 4. QImage guarantees 4 byte aligned
+ // row starts, but our raw data here does not.
+ cmd.args.subImage.rowStartAlign = (bpl & 3) ? 1 : 4;
+ cmd.args.subImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+}
+
+void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size());
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
+ cmd.args.bufferSubData.target = bufD->target;
+ cmd.args.bufferSubData.buffer = bufD->buffer;
+ cmd.args.bufferSubData.offset = u.offset;
+ cmd.args.bufferSubData.size = u.data.size();
+ cmd.args.bufferSubData.data = cbD->retainData(u.data);
+ cbD->commands.append(cmd);
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size());
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
+ cmd.args.bufferSubData.target = bufD->target;
+ cmd.args.bufferSubData.buffer = bufD->buffer;
+ cmd.args.bufferSubData.offset = u.offset;
+ cmd.args.bufferSubData.size = u.data.size();
+ cmd.args.bufferSubData.data = cbD->retainData(u.data);
+ cbD->commands.append(cmd);
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.upload.tex);
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
+ }
+ }
+ texD->specified = true;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.copy.src);
+ QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.copy.dst);
+
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ // do not translate coordinates, even if sp is bottom-left from gl's pov
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+
+ const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target;
+ const GLenum dstFaceTargetBase = dstD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CopyTex;
+
+ cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + u.copy.desc.sourceLayer();
+ cmd.args.copyTex.srcTexture = srcD->texture;
+ cmd.args.copyTex.srcLevel = u.copy.desc.sourceLevel();
+ cmd.args.copyTex.srcX = sp.x();
+ cmd.args.copyTex.srcY = sp.y();
+
+ cmd.args.copyTex.dstTarget = dstD->target;
+ cmd.args.copyTex.dstTexture = dstD->texture;
+ cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + u.copy.desc.destinationLayer();
+ cmd.args.copyTex.dstLevel = u.copy.desc.destinationLevel();
+ cmd.args.copyTex.dstX = dp.x();
+ cmd.args.copyTex.dstY = dp.y();
+
+ cmd.args.copyTex.w = size.width();
+ cmd.args.copyTex.h = size.height();
+
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::ReadPixels;
+ cmd.args.readPixels.result = u.read.result;
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.read.rb.texture());
+ cmd.args.readPixels.texture = texD ? texD->texture : 0;
+ if (texD) {
+ cmd.args.readPixels.w = texD->m_pixelSize.width();
+ cmd.args.readPixels.h = texD->m_pixelSize.height();
+ cmd.args.readPixels.format = texD->m_format;
+ const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ cmd.args.readPixels.readTarget = faceTargetBase + u.read.rb.layer();
+ cmd.args.readPixels.level = u.read.rb.level();
+ }
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::GenMip;
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.mipgen.tex);
+ cmd.args.genMip.target = texD->target;
+ cmd.args.genMip.texture = texD->texture;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ ud->free();
+}
+
+static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return GL_TRIANGLES;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return GL_TRIANGLE_STRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return GL_LINES;
+ case QRhiGraphicsPipeline::LineStrip:
+ return GL_LINE_STRIP;
+ case QRhiGraphicsPipeline::Points:
+ return GL_POINTS;
+ default:
+ Q_UNREACHABLE();
+ return GL_TRIANGLES;
+ }
+}
+
+static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::Front:
+ return GL_FRONT;
+ case QRhiGraphicsPipeline::Back:
+ return GL_BACK;
+ default:
+ Q_UNREACHABLE();
+ return GL_BACK;
+ }
+}
+
+static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::CCW:
+ return GL_CCW;
+ case QRhiGraphicsPipeline::CW:
+ return GL_CW;
+ default:
+ Q_UNREACHABLE();
+ return GL_CCW;
+ }
+}
+
+static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return GL_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return GL_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return GL_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return GL_ONE_MINUS_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return GL_DST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return GL_ONE_MINUS_DST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return GL_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return GL_ONE_MINUS_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return GL_DST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return GL_ONE_MINUS_DST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return GL_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return GL_ONE_MINUS_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return GL_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return GL_ONE_MINUS_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return GL_SRC_ALPHA_SATURATE;
+ case QRhiGraphicsPipeline::Src1Color:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::Src1Alpha:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ qWarning("Unsupported blend factor %d", f);
+ return GL_ZERO;
+ default:
+ Q_UNREACHABLE();
+ return GL_ZERO;
+ }
+}
+
+static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return GL_FUNC_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return GL_FUNC_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return GL_FUNC_REVERSE_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return GL_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return GL_MAX;
+ default:
+ Q_UNREACHABLE();
+ return GL_FUNC_ADD;
+ }
+}
+
+static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return GL_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return GL_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return GL_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return GL_LEQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return GL_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return GL_NOTEQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return GL_GEQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return GL_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return GL_ALWAYS;
+ }
+}
+
+static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return GL_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return GL_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return GL_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return GL_INCR;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return GL_DECR;
+ case QRhiGraphicsPipeline::Invert:
+ return GL_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return GL_INCR_WRAP;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return GL_DECR_WRAP;
+ default:
+ Q_UNREACHABLE();
+ return GL_KEEP;
+ }
+}
+
+static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ if (m == QRhiSampler::None)
+ return GL_NEAREST;
+ else
+ return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR;
+ case QRhiSampler::Linear:
+ if (m == QRhiSampler::None)
+ return GL_LINEAR;
+ else
+ return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return GL_LINEAR;
+ }
+}
+
+static inline GLenum toGlMagFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return GL_NEAREST;
+ case QRhiSampler::Linear:
+ return GL_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return GL_LINEAR;
+ }
+}
+
+static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return GL_REPEAT;
+ case QRhiSampler::ClampToEdge:
+ return GL_CLAMP_TO_EDGE;
+ case QRhiSampler::Mirror:
+ return GL_MIRRORED_REPEAT;
+ case QRhiSampler::MirrorOnce:
+ Q_FALLTHROUGH();
+ case QRhiSampler::Border:
+ qWarning("Unsupported wrap mode %d", m);
+ return GL_CLAMP_TO_EDGE;
+ default:
+ Q_UNREACHABLE();
+ return GL_CLAMP_TO_EDGE;
+ }
+}
+
+static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return GL_NEVER;
+ case QRhiSampler::Less:
+ return GL_LESS;
+ case QRhiSampler::Equal:
+ return GL_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return GL_LEQUAL;
+ case QRhiSampler::Greater:
+ return GL_GREATER;
+ case QRhiSampler::NotEqual:
+ return GL_NOTEQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return GL_GEQUAL;
+ case QRhiSampler::Always:
+ return GL_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return GL_NEVER;
+ }
+}
+
+void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ GLenum indexType = GL_UNSIGNED_SHORT;
+ quint32 indexStride = sizeof(quint16);
+ quint32 indexOffset = 0;
+
+ for (const QGles2CommandBuffer::Command &cmd : qAsConst(cbD->commands)) {
+ switch (cmd.cmd) {
+ case QGles2CommandBuffer::Command::BeginFrame:
+ if (caps.coreProfile) {
+ if (!vao)
+ f->glGenVertexArrays(1, &vao);
+ f->glBindVertexArray(vao);
+ }
+ break;
+ case QGles2CommandBuffer::Command::EndFrame:
+ if (vao)
+ f->glBindVertexArray(0);
+ break;
+ case QGles2CommandBuffer::Command::Viewport:
+ f->glViewport(cmd.args.viewport.x, cmd.args.viewport.y, cmd.args.viewport.w, cmd.args.viewport.h);
+ f->glDepthRangef(cmd.args.viewport.d0, cmd.args.viewport.d1);
+ break;
+ case QGles2CommandBuffer::Command::Scissor:
+ f->glScissor(cmd.args.scissor.x, cmd.args.scissor.y, cmd.args.scissor.w, cmd.args.scissor.h);
+ break;
+ case QGles2CommandBuffer::Command::BlendConstants:
+ f->glBlendColor(cmd.args.blendConstants.r, cmd.args.blendConstants.g, cmd.args.blendConstants.b, cmd.args.blendConstants.a);
+ break;
+ case QGles2CommandBuffer::Command::StencilRef:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps);
+ if (psD) {
+ f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask);
+ f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask);
+ } else {
+ qWarning("No graphics pipeline active for setStencilRef; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindVertexBuffer:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps);
+ if (psD) {
+ const QVector<QRhiVertexInputBinding> bindings = psD->m_vertexInputLayout.bindings();
+ const QVector<QRhiVertexInputAttribute> attributes = psD->m_vertexInputLayout.attributes();
+ for (const QRhiVertexInputAttribute &a : attributes) {
+ if (a.binding() != cmd.args.bindVertexBuffer.binding)
+ continue;
+
+ // we do not support more than one vertex buffer
+ f->glBindBuffer(GL_ARRAY_BUFFER, cmd.args.bindVertexBuffer.buffer);
+
+ const int stride = bindings[a.binding()].stride();
+ int size = 1;
+ GLenum type = GL_FLOAT;
+ bool normalize = false;
+ switch (a.format()) {
+ case QRhiVertexInputAttribute::Float4:
+ type = GL_FLOAT;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::Float3:
+ type = GL_FLOAT;
+ size = 3;
+ break;
+ case QRhiVertexInputAttribute::Float2:
+ type = GL_FLOAT;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::Float:
+ type = GL_FLOAT;
+ size = 1;
+ break;
+ case QRhiVertexInputAttribute::UNormByte4:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::UNormByte2:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::UNormByte:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 1;
+ break;
+ default:
+ break;
+ }
+ quint32 ofs = a.offset() + cmd.args.bindVertexBuffer.offset;
+ f->glVertexAttribPointer(a.location(), size, type, normalize, stride,
+ reinterpret_cast<const GLvoid *>(quintptr(ofs)));
+ f->glEnableVertexAttribArray(a.location());
+ }
+ } else {
+ qWarning("No graphics pipeline active for setVertexInput; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindIndexBuffer:
+ indexType = cmd.args.bindIndexBuffer.type;
+ indexStride = indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32);
+ indexOffset = cmd.args.bindIndexBuffer.offset;
+ f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cmd.args.bindIndexBuffer.buffer);
+ break;
+ case QGles2CommandBuffer::Command::Draw:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps);
+ if (psD)
+ f->glDrawArrays(psD->drawMode, cmd.args.draw.firstVertex, cmd.args.draw.vertexCount);
+ else
+ qWarning("No graphics pipeline active for draw; ignored");
+ }
+ break;
+ case QGles2CommandBuffer::Command::DrawIndexed:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps);
+ if (psD) {
+ quint32 ofs = cmd.args.drawIndexed.firstIndex * indexStride + indexOffset;
+ f->glDrawElements(psD->drawMode,
+ cmd.args.drawIndexed.indexCount,
+ indexType,
+ reinterpret_cast<const GLvoid *>(quintptr(ofs)));
+ } else {
+ qWarning("No graphics pipeline active for drawIndexed; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindGraphicsPipeline:
+ executeBindGraphicsPipeline(cmd.args.bindGraphicsPipeline.ps);
+ break;
+ case QGles2CommandBuffer::Command::BindShaderResources:
+ bindShaderResources(cmd.args.bindShaderResources.ps,
+ cmd.args.bindShaderResources.srb,
+ cmd.args.bindShaderResources.dynamicOffsetPairs,
+ cmd.args.bindShaderResources.dynamicOffsetCount);
+ break;
+ case QGles2CommandBuffer::Command::BindFramebuffer:
+ if (cmd.args.bindFramebuffer.fbo) {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.bindFramebuffer.fbo);
+ if (caps.maxDrawBuffers > 1) {
+ const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount;
+ QVarLengthArray<GLenum, 8> bufs;
+ for (int i = 0; i < colorAttCount; ++i)
+ bufs.append(GL_COLOR_ATTACHMENT0 + i);
+ f->glDrawBuffers(colorAttCount, bufs.constData());
+ }
+ } else {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ if (caps.maxDrawBuffers > 1) {
+ GLenum bufs = GL_BACK;
+ f->glDrawBuffers(1, &bufs);
+ }
+ }
+ if (caps.srgbCapableDefaultFramebuffer) {
+ if (cmd.args.bindFramebuffer.srgb)
+ f->glEnable(GL_FRAMEBUFFER_SRGB);
+ else
+ f->glDisable(GL_FRAMEBUFFER_SRGB);
+ }
+ break;
+ case QGles2CommandBuffer::Command::Clear:
+ f->glDisable(GL_SCISSOR_TEST);
+ if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) {
+ f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ f->glClearColor(cmd.args.clear.c[0], cmd.args.clear.c[1], cmd.args.clear.c[2], cmd.args.clear.c[3]);
+ }
+ if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) {
+ f->glDepthMask(GL_TRUE);
+ f->glClearDepthf(cmd.args.clear.d);
+ }
+ if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT)
+ f->glClearStencil(cmd.args.clear.s);
+ f->glClear(cmd.args.clear.mask);
+ break;
+ case QGles2CommandBuffer::Command::BufferSubData:
+ f->glBindBuffer(cmd.args.bufferSubData.target, cmd.args.bufferSubData.buffer);
+ f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size,
+ cmd.args.bufferSubData.data);
+ break;
+ case QGles2CommandBuffer::Command::CopyTex:
+ {
+ GLuint fbo;
+ f->glGenFramebuffers(1, &fbo);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel);
+ f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture);
+ f->glCopyTexSubImage2D(cmd.args.copyTex.dstFaceTarget, cmd.args.copyTex.dstLevel,
+ cmd.args.copyTex.dstX, cmd.args.copyTex.dstY,
+ cmd.args.copyTex.srcX, cmd.args.copyTex.srcY,
+ cmd.args.copyTex.w, cmd.args.copyTex.h);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ f->glDeleteFramebuffers(1, &fbo);
+ }
+ break;
+ case QGles2CommandBuffer::Command::ReadPixels:
+ {
+ QRhiReadbackResult *result = cmd.args.readPixels.result;
+ GLuint tex = cmd.args.readPixels.texture;
+ GLuint fbo = 0;
+ if (tex) {
+ result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
+ result->format = cmd.args.readPixels.format;
+ f->glGenFramebuffers(1, &fbo);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level);
+ } else {
+ result->pixelSize = currentSwapChain->pixelSize;
+ result->format = QRhiTexture::RGBA8;
+ // readPixels handles multisample resolving implicitly
+ }
+ result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4);
+ // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
+ f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
+ GL_RGBA, GL_UNSIGNED_BYTE,
+ result->data.data());
+ if (fbo) {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ f->glDeleteFramebuffers(1, &fbo);
+ }
+ if (result->completed)
+ result->completed();
+ }
+ break;
+ case QGles2CommandBuffer::Command::SubImage:
+ f->glBindTexture(cmd.args.subImage.target, cmd.args.subImage.texture);
+ if (cmd.args.subImage.rowStartAlign != 4)
+ f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign);
+ f->glTexSubImage2D(cmd.args.subImage.faceTarget, cmd.args.subImage.level,
+ cmd.args.subImage.dx, cmd.args.subImage.dy,
+ cmd.args.subImage.w, cmd.args.subImage.h,
+ cmd.args.subImage.glformat, cmd.args.subImage.gltype,
+ cmd.args.subImage.data);
+ if (cmd.args.subImage.rowStartAlign != 4)
+ f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ break;
+ case QGles2CommandBuffer::Command::CompressedImage:
+ f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture);
+ f->glCompressedTexImage2D(cmd.args.compressedImage.faceTarget, cmd.args.compressedImage.level,
+ cmd.args.compressedImage.glintformat,
+ cmd.args.compressedImage.w, cmd.args.compressedImage.h, 0,
+ cmd.args.compressedImage.size, cmd.args.compressedImage.data);
+ break;
+ case QGles2CommandBuffer::Command::CompressedSubImage:
+ f->glBindTexture(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.texture);
+ f->glCompressedTexSubImage2D(cmd.args.compressedSubImage.faceTarget, cmd.args.compressedSubImage.level,
+ cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy,
+ cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h,
+ cmd.args.compressedSubImage.glintformat,
+ cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data);
+ break;
+ case QGles2CommandBuffer::Command::BlitFromRenderbuffer:
+ {
+ GLuint fbo[2];
+ f->glGenFramebuffers(2, fbo);
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer);
+ f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target,
+ cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel);
+ f->glBlitFramebuffer(0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
+ 0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
+ GL_COLOR_BUFFER_BIT,
+ GL_LINEAR);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ }
+ break;
+ case QGles2CommandBuffer::Command::GenMip:
+ f->glBindTexture(cmd.args.genMip.target, cmd.args.genMip.texture);
+ f->glGenerateMipmap(cmd.args.genMip.target);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void QRhiGles2::executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps)
+{
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+
+ // No state tracking logic as of now. Could introduce something to reduce
+ // the number of gl* calls (when using and changing between multiple
+ // pipelines), but then begin/endExternal() should invalidate the cached
+ // state as appropriate.
+
+ if (psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
+ f->glEnable(GL_SCISSOR_TEST);
+ else
+ f->glDisable(GL_SCISSOR_TEST);
+ if (psD->m_cullMode == QRhiGraphicsPipeline::None) {
+ f->glDisable(GL_CULL_FACE);
+ } else {
+ f->glEnable(GL_CULL_FACE);
+ f->glCullFace(toGlCullMode(psD->m_cullMode));
+ }
+ f->glFrontFace(toGlFrontFace(psD->m_frontFace));
+ if (!psD->m_targetBlends.isEmpty()) {
+ const QRhiGraphicsPipeline::TargetBlend &blend(psD->m_targetBlends.first()); // no MRT
+ GLboolean wr = blend.colorWrite.testFlag(QRhiGraphicsPipeline::R);
+ GLboolean wg = blend.colorWrite.testFlag(QRhiGraphicsPipeline::G);
+ GLboolean wb = blend.colorWrite.testFlag(QRhiGraphicsPipeline::B);
+ GLboolean wa = blend.colorWrite.testFlag(QRhiGraphicsPipeline::A);
+ f->glColorMask(wr, wg, wb, wa);
+ if (blend.enable) {
+ f->glEnable(GL_BLEND);
+ f->glBlendFuncSeparate(toGlBlendFactor(blend.srcColor),
+ toGlBlendFactor(blend.dstColor),
+ toGlBlendFactor(blend.srcAlpha),
+ toGlBlendFactor(blend.dstAlpha));
+ f->glBlendEquationSeparate(toGlBlendOp(blend.opColor), toGlBlendOp(blend.opAlpha));
+ } else {
+ f->glDisable(GL_BLEND);
+ }
+ } else {
+ f->glDisable(GL_BLEND);
+ }
+ if (psD->m_depthTest)
+ f->glEnable(GL_DEPTH_TEST);
+ else
+ f->glDisable(GL_DEPTH_TEST);
+ if (psD->m_depthWrite)
+ f->glDepthMask(GL_TRUE);
+ else
+ f->glDepthMask(GL_FALSE);
+ f->glDepthFunc(toGlCompareOp(psD->m_depthOp));
+ if (psD->m_stencilTest) {
+ f->glEnable(GL_STENCIL_TEST);
+ f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), 0, psD->m_stencilReadMask);
+ f->glStencilOpSeparate(GL_FRONT,
+ toGlStencilOp(psD->m_stencilFront.failOp),
+ toGlStencilOp(psD->m_stencilFront.depthFailOp),
+ toGlStencilOp(psD->m_stencilFront.passOp));
+ f->glStencilMaskSeparate(GL_FRONT, psD->m_stencilWriteMask);
+ f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), 0, psD->m_stencilReadMask);
+ f->glStencilOpSeparate(GL_BACK,
+ toGlStencilOp(psD->m_stencilBack.failOp),
+ toGlStencilOp(psD->m_stencilBack.depthFailOp),
+ toGlStencilOp(psD->m_stencilBack.passOp));
+ f->glStencilMaskSeparate(GL_BACK, psD->m_stencilWriteMask);
+ } else {
+ f->glDisable(GL_STENCIL_TEST);
+ }
+
+ f->glUseProgram(psD->program);
+}
+
+void QRhiGles2::bindShaderResources(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb,
+ const uint *dynOfsPairs, int dynOfsCount)
+{
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+ QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
+ int texUnit = 0;
+
+ for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->m_bindings[i]);
+
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ int viewOffset = b->u.ubuf.offset;
+ if (dynOfsCount) {
+ for (int j = 0; j < dynOfsCount; ++j) {
+ if (dynOfsPairs[2 * j] == uint(b->binding)) {
+ viewOffset = dynOfsPairs[2 * j + 1];
+ break;
+ }
+ }
+ }
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf);
+ const QByteArray bufView = QByteArray::fromRawData(bufD->ubuf.constData() + viewOffset,
+ b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size);
+ for (QGles2GraphicsPipeline::Uniform &uniform : psD->uniforms) {
+ if (uniform.binding == b->binding) {
+ // in a uniform buffer everything is at least 4 byte aligned
+ // so this should not cause unaligned reads
+ const void *src = bufView.constData() + uniform.offset;
+
+ switch (uniform.type) {
+ case QShaderDescription::Float:
+ f->glUniform1f(uniform.glslLocation, *reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec2:
+ f->glUniform2fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec3:
+ f->glUniform3fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec4:
+ f->glUniform4fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat2:
+ f->glUniformMatrix2fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat3:
+ f->glUniformMatrix3fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat4:
+ f->glUniformMatrix4fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Int:
+ f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int2:
+ f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int3:
+ f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int4:
+ f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ // ### more types
+ default:
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.tex);
+ QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.sampler);
+
+ for (QGles2GraphicsPipeline::Sampler &sampler : psD->samplers) {
+ if (sampler.binding == b->binding) {
+ f->glActiveTexture(GL_TEXTURE0 + texUnit);
+ f->glBindTexture(texD->target, texD->texture);
+
+ if (texD->samplerState != samplerD->d) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, samplerD->d.glminfilter);
+ f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, samplerD->d.glmagfilter);
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, samplerD->d.glwraps);
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, samplerD->d.glwrapt);
+ // 3D textures not supported by GLES 2.0 or by us atm...
+ //f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, samplerD->d.glwrapr);
+ if (samplerD->d.gltexcomparefunc != GL_NEVER) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, samplerD->d.gltexcomparefunc);
+ } else {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+ }
+ texD->samplerState = samplerD->d;
+ }
+
+ f->glUniform1i(sampler.glslLocation, texUnit);
+ ++texUnit;
+ }
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (texUnit > 1)
+ f->glActiveTexture(GL_TEXTURE0);
+}
+
+void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
+ bool *wantsColorClear, bool *wantsDsClear)
+{
+ QGles2RenderTargetData *rtD = nullptr;
+ QGles2CommandBuffer::Command fbCmd;
+ fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d;
+ if (wantsColorClear)
+ *wantsColorClear = true;
+ if (wantsDsClear)
+ *wantsDsClear = true;
+ fbCmd.args.bindFramebuffer.fbo = 0;
+ fbCmd.args.bindFramebuffer.colorAttCount = 1;
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
+ rtD = &rtTex->d;
+ if (wantsColorClear)
+ *wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+ if (wantsDsClear)
+ *wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+ fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer;
+ fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend;
+ cbD->commands.append(fbCmd);
+ return rtD;
+}
+
+void QRhiGles2::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ bool wantsColorClear, wantsDsClear;
+ QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, &wantsColorClear, &wantsDsClear);
+
+ QGles2CommandBuffer::Command clearCmd;
+ clearCmd.cmd = QGles2CommandBuffer::Command::Clear;
+ clearCmd.args.clear.mask = 0;
+ if (rtD->colorAttCount && wantsColorClear)
+ clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT;
+ if (rtD->dsAttCount && wantsDsClear)
+ clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ clearCmd.args.clear.c[0] = colorClearValue.redF();
+ clearCmd.args.clear.c[1] = colorClearValue.greenF();
+ clearCmd.args.clear.c[2] = colorClearValue.blueF();
+ clearCmd.args.clear.c[3] = colorClearValue.alphaF();
+ clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
+ clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
+ cbD->commands.append(clearCmd);
+
+ cbD->currentTarget = rt;
+
+ inPass = true;
+}
+
+void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inPass);
+ inPass = false;
+
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+ QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget);
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ if (!colorAttachments.isEmpty()) {
+ // handle only 1 color attachment and only (msaa) renderbuffer
+ const QRhiColorAttachment &colorAtt(colorAttachments[0]);
+ if (colorAtt.resolveTexture()) {
+ Q_ASSERT(colorAtt.renderBuffer());
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer());
+ const QSize size = colorAtt.resolveTexture()->pixelSize();
+ if (rbD->pixelSize() != size) {
+ qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match",
+ rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height());
+ }
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
+ cmd.args.blitFromRb.renderbuffer = rbD->renderbuffer;
+ cmd.args.blitFromRb.w = size.width();
+ cmd.args.blitFromRb.h = size.height();
+ QGles2Texture *colorTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
+ const GLenum faceTargetBase = colorTexD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X
+ : colorTexD->target;
+ cmd.args.blitFromRb.target = faceTargetBase + colorAtt.resolveLayer();
+ cmd.args.blitFromRb.texture = colorTexD->texture;
+ cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel();
+ cbD->commands.append(cmd);
+ }
+ }
+ }
+
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QGles2Buffer::~QGles2Buffer()
+{
+ release();
+}
+
+void QGles2Buffer::release()
+{
+ if (!buffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Buffer;
+
+ e.buffer.buffer = buffer;
+
+ buffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2Buffer::build()
+{
+ if (buffer)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+
+ if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ if (m_usage.testFlag(QRhiBuffer::VertexBuffer) || m_usage.testFlag(QRhiBuffer::IndexBuffer)) {
+ qWarning("Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend");
+ return false;
+ }
+ ubuf.resize(m_size);
+ QRHI_PROF_F(newBuffer(this, m_size, 0, 1));
+ return true;
+ }
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ if (m_usage.testFlag(QRhiBuffer::VertexBuffer)) {
+ if (m_usage.testFlag(QRhiBuffer::IndexBuffer)) {
+ qWarning("Vertex buffer: multiple usages specified, this is not supported by the OpenGL backend");
+ return false;
+ }
+ target = GL_ARRAY_BUFFER;
+ }
+ if (m_usage.testFlag(QRhiBuffer::IndexBuffer))
+ target = GL_ELEMENT_ARRAY_BUFFER;
+
+ rhiD->f->glGenBuffers(1, &buffer);
+ rhiD->f->glBindBuffer(target, buffer);
+ rhiD->f->glBufferData(target, m_size, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+
+ QRHI_PROF_F(newBuffer(this, m_size, 1, 0));
+ rhiD->registerResource(this);
+ return true;
+}
+
+QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QGles2RenderBuffer::~QGles2RenderBuffer()
+{
+ release();
+}
+
+void QGles2RenderBuffer::release()
+{
+ if (!renderbuffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer;
+
+ e.renderbuffer.renderbuffer = renderbuffer;
+ e.renderbuffer.renderbuffer2 = stencilRenderbuffer;
+
+ renderbuffer = 0;
+ stencilRenderbuffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2RenderBuffer::build()
+{
+ if (renderbuffer)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ if (m_flags.testFlag(UsedWithSwapChainOnly)) {
+ if (m_type == DepthStencil) {
+ QRHI_PROF_F(newRenderBuffer(this, false, true, samples));
+ return true;
+ }
+
+ qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color");
+ }
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ rhiD->f->glGenRenderbuffers(1, &renderbuffer);
+ rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+
+ switch (m_type) {
+ case QRhiRenderBuffer::DepthStencil:
+ if (rhiD->caps.msaaRenderBuffer && samples > 1) {
+ rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8,
+ m_pixelSize.width(), m_pixelSize.height());
+ } else {
+ if (rhiD->caps.packedDepthStencil) {
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
+ m_pixelSize.width(), m_pixelSize.height());
+ stencilRenderbuffer = 0;
+ } else {
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_ATTACHMENT,
+ m_pixelSize.width(), m_pixelSize.height());
+ rhiD->f->glGenRenderbuffers(1, &stencilRenderbuffer);
+ rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, stencilRenderbuffer);
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_ATTACHMENT,
+ m_pixelSize.width(), m_pixelSize.height());
+ }
+ }
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ break;
+ case QRhiRenderBuffer::Color:
+ if (rhiD->caps.msaaRenderBuffer && samples > 1)
+ rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8,
+ m_pixelSize.width(), m_pixelSize.height());
+ else
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8,
+ m_pixelSize.width(), m_pixelSize.height());
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QGles2RenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+}
+
+QGles2Texture::~QGles2Texture()
+{
+ release();
+}
+
+void QGles2Texture::release()
+{
+ if (!texture)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Texture;
+
+ e.texture.texture = texture;
+
+ texture = 0;
+ specified = false;
+ nativeHandlesStruct.texture = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ if (owns)
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline bool isPowerOfTwo(int x)
+{
+ // Assumption: x >= 1
+ return x == (x & -x);
+}
+
+bool QGles2Texture::prepareBuild(QSize *adjustedSize)
+{
+ if (texture)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ if (!rhiD->ensureContext())
+ return false;
+
+ QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ if (!rhiD->caps.npotTexture && (!isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height())))
+ size = QSize(qNextPowerOfTwo(size.width()), qNextPowerOfTwo(size.height()));
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ const bool isCompressed = rhiD->isCompressedFormat(m_format);
+
+ target = isCube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ gltype = GL_UNSIGNED_BYTE;
+
+ if (isCompressed) {
+ glintformat = toGlCompressedTextureFormat(m_format, m_flags);
+ if (!glintformat) {
+ qWarning("Compressed format %d not mappable to GL compressed format", m_format);
+ return false;
+ }
+ glformat = GL_RGBA;
+ } else {
+ switch (m_format) {
+ case QRhiTexture::RGBA8:
+ glintformat = GL_RGBA;
+ glformat = GL_RGBA;
+ break;
+ case QRhiTexture::BGRA8:
+ glintformat = rhiD->caps.bgraInternalFormat ? GL_BGRA : GL_RGBA;
+ glformat = GL_BGRA;
+ break;
+ case QRhiTexture::R16:
+ glintformat = GL_R16;
+ glformat = GL_RED;
+ gltype = GL_UNSIGNED_SHORT;
+ break;
+ case QRhiTexture::R8:
+ glintformat = GL_R8;
+ glformat = GL_RED;
+ break;
+ case QRhiTexture::RED_OR_ALPHA8:
+ glintformat = rhiD->caps.coreProfile ? GL_R8 : GL_ALPHA;
+ glformat = rhiD->caps.coreProfile ? GL_RED : GL_ALPHA;
+ break;
+ case QRhiTexture::RGBA16F:
+ glintformat = GL_RGBA16F;
+ glformat = GL_RGBA;
+ gltype = GL_HALF_FLOAT;
+ break;
+ case QRhiTexture::RGBA32F:
+ glintformat = GL_RGBA32F;
+ glformat = GL_RGBA;
+ gltype = GL_FLOAT;
+ break;
+ case QRhiTexture::D16:
+ glintformat = GL_DEPTH_COMPONENT16;
+ glformat = GL_DEPTH_COMPONENT;
+ gltype = GL_UNSIGNED_SHORT;
+ break;
+ case QRhiTexture::D32F:
+ glintformat = GL_DEPTH_COMPONENT32F;
+ glformat = GL_DEPTH_COMPONENT;
+ gltype = GL_FLOAT;
+ break;
+ default:
+ Q_UNREACHABLE();
+ glintformat = GL_RGBA;
+ glformat = GL_RGBA;
+ break;
+ }
+ }
+
+ samplerState = QGles2SamplerData();
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QGles2Texture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->f->glGenTextures(1, &texture);
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ const bool isCompressed = rhiD->isCompressedFormat(m_format);
+ if (!isCompressed) {
+ rhiD->f->glBindTexture(target, texture);
+ if (hasMipMaps || isCube) {
+ const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
+ for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) {
+ for (int level = 0; level != mipLevelCount; ++level) {
+ const QSize mipSize = rhiD->q->sizeForMipLevel(level, size);
+ rhiD->f->glTexImage2D(faceTargetBase + layer, level, glintformat,
+ mipSize.width(), mipSize.height(), 0,
+ glformat, gltype, nullptr);
+ }
+ }
+ } else {
+ rhiD->f->glTexImage2D(target, 0, glintformat, size.width(), size.height(), 0, glformat, gltype, nullptr);
+ }
+ specified = true;
+ } else {
+ // Cannot use glCompressedTexImage2D without valid data, so defer.
+ // Compressed textures will not be used as render targets so this is
+ // not an issue.
+ specified = false;
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
+
+ owns = true;
+ nativeHandlesStruct.texture = texture;
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QGles2Texture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiGles2TextureNativeHandles *h = static_cast<const QRhiGles2TextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ texture = h->texture;
+ specified = true;
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, 1));
+
+ owns = false;
+ nativeHandlesStruct.texture = texture;
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QGles2Texture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QGles2Sampler::~QGles2Sampler()
+{
+ release();
+}
+
+void QGles2Sampler::release()
+{
+ // nothing to do here
+}
+
+bool QGles2Sampler::build()
+{
+ d.glminfilter = toGlMinFilter(m_minFilter, m_mipmapMode);
+ d.glmagfilter = toGlMagFilter(m_magFilter);
+ d.glwraps = toGlWrapMode(m_addressU);
+ d.glwrapt = toGlWrapMode(m_addressV);
+ d.glwrapr = toGlWrapMode(m_addressW);
+ d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp);
+
+ generation += 1;
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here
+QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor()
+{
+ release();
+}
+
+void QGles2RenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QGles2ReferenceRenderTarget::~QGles2ReferenceRenderTarget()
+{
+ release();
+}
+
+void QGles2ReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QGles2ReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QGles2ReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QGles2ReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+}
+
+QGles2TextureRenderTarget::~QGles2TextureRenderTarget()
+{
+ release();
+}
+
+void QGles2TextureRenderTarget::release()
+{
+ if (!framebuffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget;
+
+ e.textureRenderTarget.framebuffer = framebuffer;
+
+ framebuffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QGles2RenderPassDescriptor(m_rhi);
+}
+
+bool QGles2TextureRenderTarget::build()
+{
+ QRHI_RES_RHI(QRhiGles2);
+
+ if (framebuffer)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ if (colorAttachments.count() > rhiD->caps.maxDrawBuffers)
+ qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)",
+ colorAttachments.count(), rhiD->caps.maxDrawBuffers);
+ if (m_desc.depthTexture() && !rhiD->caps.depthTexture)
+ qWarning("QGles2TextureRenderTarget: Depth texture is not supported and will be ignored");
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ rhiD->f->glGenFramebuffers(1, &framebuffer);
+ rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ const QRhiColorAttachment &colorAtt(colorAttachments[i]);
+ QRhiTexture *texture = colorAtt.texture();
+ QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer();
+ Q_ASSERT(texture || renderBuffer);
+ if (texture) {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
+ Q_ASSERT(texD->texture && texD->specified);
+ const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, faceTargetBase + colorAtt.layer(), texD->texture, colorAtt.level());
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = 1;
+ }
+ } else if (renderBuffer) {
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer);
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, rbD->renderbuffer);
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->samples;
+ }
+ }
+ }
+
+ if (hasDepthStencil) {
+ if (m_desc.depthStencilBuffer()) {
+ QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer());
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer);
+ if (depthRbD->stencilRenderbuffer)
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->stencilRenderbuffer);
+ else // packed
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthRbD->pixelSize();
+ d.sampleCount = depthRbD->samples;
+ }
+ } else {
+ QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexD->texture, 0);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = 1;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ d.dpr = 1;
+ d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
+
+ GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) {
+ qWarning("Framebuffer incomplete: 0x%x", status);
+ return false;
+ }
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QGles2TextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QGles2TextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QGles2TextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings()
+{
+ release();
+}
+
+void QGles2ShaderResourceBindings::release()
+{
+ // nothing to do here
+}
+
+bool QGles2ShaderResourceBindings::build()
+{
+ generation += 1;
+ return true;
+}
+
+QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QGles2GraphicsPipeline::~QGles2GraphicsPipeline()
+{
+ release();
+}
+
+void QGles2GraphicsPipeline::release()
+{
+ if (!program)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
+
+ e.pipeline.program = program;
+
+ program = 0;
+ uniforms.clear();
+ samplers.clear();
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2GraphicsPipeline::build()
+{
+ QRHI_RES_RHI(QRhiGles2);
+
+ if (program)
+ release();
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ drawMode = toGlTopology(m_topology);
+
+ program = rhiD->f->glCreateProgram();
+
+ int sourceVer = 0;
+ for (const QRhiGraphicsShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ const bool isVertex = shaderStage.type() == QRhiGraphicsShaderStage::Vertex;
+ const bool isFragment = shaderStage.type() == QRhiGraphicsShaderStage::Fragment;
+ if (!isVertex && !isFragment)
+ continue;
+
+ GLuint shader = rhiD->f->glCreateShader(isVertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER);
+ const QShader bakedShader = shaderStage.shader();
+ QVector<int> versionsToTry;
+ QByteArray source;
+ if (rhiD->caps.gles) {
+ if (rhiD->caps.ctxMajor > 3 || (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor >= 2)) {
+ versionsToTry << 320 << 310 << 300 << 100;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 1) {
+ versionsToTry << 310 << 300 << 100;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 0) {
+ versionsToTry << 300 << 100;
+ } else {
+ versionsToTry << 100;
+ }
+ for (int v : versionsToTry) {
+ QShaderVersion ver(v, QShaderVersion::GlslEs);
+ source = bakedShader.shader({ QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader();
+ if (!source.isEmpty()) {
+ sourceVer = v;
+ break;
+ }
+ }
+ } else {
+ if (rhiD->caps.ctxMajor > 4 || (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor >= 6)) {
+ versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 5) {
+ versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 4) {
+ versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 3) {
+ versionsToTry << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 2) {
+ versionsToTry << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 1) {
+ versionsToTry << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 0) {
+ versionsToTry << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 3) {
+ versionsToTry << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 2) {
+ versionsToTry << 150;
+ }
+ if (!rhiD->caps.coreProfile)
+ versionsToTry << 120;
+ for (int v : versionsToTry) {
+ source = bakedShader.shader({ QShader::GlslShader, v, shaderStage.shaderVariant() }).shader();
+ if (!source.isEmpty()) {
+ sourceVer = v;
+ break;
+ }
+ }
+ }
+ if (source.isEmpty()) {
+ qWarning() << "No GLSL shader code found (versions tried: " << versionsToTry
+ << ") in baked shader" << bakedShader;
+ return false;
+ }
+
+ const char *srcStr = source.constData();
+ const GLint srcLength = source.count();
+ rhiD->f->glShaderSource(shader, 1, &srcStr, &srcLength);
+ rhiD->f->glCompileShader(shader);
+ GLint compiled = 0;
+ rhiD->f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ GLint infoLogLength = 0;
+ rhiD->f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+ QByteArray log;
+ if (infoLogLength > 1) {
+ GLsizei length = 0;
+ log.resize(infoLogLength);
+ rhiD->f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data());
+ }
+ qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
+ return false;
+ }
+
+ rhiD->f->glAttachShader(program, shader);
+ rhiD->f->glDeleteShader(shader);
+
+ if (isVertex)
+ vsDesc = bakedShader.description();
+ else
+ fsDesc = bakedShader.description();
+ }
+
+ // not very useful atm since we assume the qsb-generated GLSL shaders never use uniform blocks
+ canUseUniformBuffers = rhiD->caps.uniformBuffers && sourceVer >= 140;
+
+ for (auto inVar : vsDesc.inputVariables()) {
+ const QByteArray name = inVar.name.toUtf8();
+ rhiD->f->glBindAttribLocation(program, inVar.location, name.constData());
+ }
+
+ rhiD->f->glLinkProgram(program);
+ GLint linked = 0;
+ rhiD->f->glGetProgramiv(program, GL_LINK_STATUS, &linked);
+ if (!linked) {
+ GLint infoLogLength = 0;
+ rhiD->f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+ QByteArray log;
+ if (infoLogLength > 1) {
+ GLsizei length = 0;
+ log.resize(infoLogLength);
+ rhiD->f->glGetProgramInfoLog(program, infoLogLength, &length, log.data());
+ }
+ qWarning("Failed to link shader program: %s", log.constData());
+ return false;
+ }
+
+ auto lookupUniforms = [this, rhiD](const QShaderDescription::UniformBlock &ub) {
+ const QByteArray prefix = ub.structName.toUtf8() + '.';
+ for (const QShaderDescription::BlockVariable &blockMember : ub.members) {
+ // ### no array support for now
+ Uniform uniform;
+ uniform.type = blockMember.type;
+ const QByteArray name = prefix + blockMember.name.toUtf8();
+ uniform.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData());
+ if (uniform.glslLocation >= 0) {
+ uniform.binding = ub.binding;
+ uniform.offset = blockMember.offset;
+ uniform.size = blockMember.size;
+ uniforms.append(uniform);
+ }
+ }
+ };
+
+ for (const QShaderDescription::UniformBlock &ub : vsDesc.uniformBlocks())
+ lookupUniforms(ub);
+
+ for (const QShaderDescription::UniformBlock &ub : fsDesc.uniformBlocks())
+ lookupUniforms(ub);
+
+ auto lookupSamplers = [this, rhiD](const QShaderDescription::InOutVariable &v) {
+ Sampler sampler;
+ const QByteArray name = v.name.toUtf8();
+ sampler.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData());
+ if (sampler.glslLocation >= 0) {
+ sampler.binding = v.binding;
+ samplers.append(sampler);
+ }
+ };
+
+ for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers())
+ lookupSamplers(v);
+
+ for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers())
+ lookupSamplers(v);
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QGles2CommandBuffer::~QGles2CommandBuffer()
+{
+ release();
+}
+
+void QGles2CommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+}
+
+QGles2SwapChain::~QGles2SwapChain()
+{
+ release();
+}
+
+void QGles2SwapChain::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+}
+
+QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QGles2SwapChain::surfacePixelSize()
+{
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
+}
+
+QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QGles2RenderPassDescriptor(m_rhi);
+}
+
+bool QGles2SwapChain::buildOrResize()
+{
+ surface = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ rt.d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
+ rt.d.pixelSize = pixelSize;
+ rt.d.dpr = m_window->devicePixelRatio();
+ rt.d.sampleCount = qBound(1, m_sampleCount, 64);
+ rt.d.colorAttCount = 1;
+ rt.d.dsAttCount = m_depthStencil ? 1 : 0;
+ rt.d.srgbUpdateAndBlend = m_flags.testFlag(QRhiSwapChain::sRGB);
+
+ frameCount = 0;
+
+ QRHI_PROF;
+ // make something up
+ QRHI_PROF_F(resizeSwapChain(this, 2, m_sampleCount > 1 ? 2 : 0, m_sampleCount));
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
new file mode 100644
index 0000000000..7f7c8b4c40
--- /dev/null
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIGLES2_H
+#define QRHIGLES2_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+#include <QtGui/qsurfaceformat.h>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLContext;
+class QOffscreenSurface;
+class QWindow;
+
+struct Q_GUI_EXPORT QRhiGles2InitParams : public QRhiInitParams
+{
+ QRhiGles2InitParams();
+
+ QSurfaceFormat format;
+ QOffscreenSurface *fallbackSurface = nullptr;
+ QWindow *window = nullptr;
+
+ static QOffscreenSurface *newFallbackSurface(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat());
+ static QSurfaceFormat adjustedFormat(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat());
+};
+
+struct Q_GUI_EXPORT QRhiGles2NativeHandles : public QRhiNativeHandles
+{
+ QOpenGLContext *context = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiGles2TextureNativeHandles : public QRhiNativeHandles
+{
+ uint texture = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h
new file mode 100644
index 0000000000..d1a8a1fadf
--- /dev/null
+++ b/src/gui/rhi/qrhigles2_p_p.h
@@ -0,0 +1,695 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIGLES2_P_H
+#define QRHIGLES2_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhigles2_p.h"
+#include "qrhi_p_p.h"
+#include "qshaderdescription_p.h"
+#include <qopengl.h>
+#include <QSurface>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLExtensions;
+
+struct QGles2Buffer : public QRhiBuffer
+{
+ QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QGles2Buffer();
+ void release() override;
+ bool build() override;
+
+ GLuint buffer = 0;
+ GLenum target;
+ QByteArray ubuf;
+ friend class QRhiGles2;
+};
+
+struct QGles2RenderBuffer : public QRhiRenderBuffer
+{
+ QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QGles2RenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ GLuint renderbuffer = 0;
+ GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported
+ int samples;
+ friend class QRhiGles2;
+};
+
+struct QGles2SamplerData
+{
+ GLenum glminfilter = 0;
+ GLenum glmagfilter = 0;
+ GLenum glwraps = 0;
+ GLenum glwrapt = 0;
+ GLenum glwrapr = 0;
+ GLenum gltexcomparefunc = 0;
+};
+
+inline bool operator==(const QGles2SamplerData &a, const QGles2SamplerData &b)
+{
+ return a.glminfilter == b.glminfilter
+ && a.glmagfilter == b.glmagfilter
+ && a.glwraps == b.glwraps
+ && a.glwrapt == b.glwrapt
+ && a.glwrapr == b.glwrapr
+ && a.gltexcomparefunc == b.gltexcomparefunc;
+}
+
+inline bool operator!=(const QGles2SamplerData &a, const QGles2SamplerData &b)
+{
+ return !(a == b);
+}
+
+struct QGles2Texture : public QRhiTexture
+{
+ QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QGles2Texture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+
+ GLuint texture = 0;
+ bool owns = true;
+ GLenum target;
+ GLenum glintformat;
+ GLenum glformat;
+ GLenum gltype;
+ QGles2SamplerData samplerState;
+ bool specified = false;
+ int mipLevelCount = 0;
+ QRhiGles2TextureNativeHandles nativeHandlesStruct;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2Sampler : public QRhiSampler
+{
+ QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QGles2Sampler();
+ void release() override;
+ bool build() override;
+
+ QGles2SamplerData d;
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2RenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QGles2RenderPassDescriptor(QRhiImplementation *rhi);
+ ~QGles2RenderPassDescriptor();
+ void release() override;
+};
+
+struct QGles2RenderTargetData
+{
+ QGles2RenderTargetData(QRhiImplementation *) { }
+
+ QGles2RenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+ bool srgbUpdateAndBlend = false;
+};
+
+struct QGles2ReferenceRenderTarget : public QRhiRenderTarget
+{
+ QGles2ReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QGles2ReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QGles2RenderTargetData d;
+};
+
+struct QGles2TextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QGles2TextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QGles2RenderTargetData d;
+ GLuint framebuffer = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2ShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QGles2ShaderResourceBindings(QRhiImplementation *rhi);
+ ~QGles2ShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2GraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QGles2GraphicsPipeline(QRhiImplementation *rhi);
+ ~QGles2GraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ GLuint program = 0;
+ GLenum drawMode = GL_TRIANGLES;
+ QShaderDescription vsDesc;
+ QShaderDescription fsDesc;
+ bool canUseUniformBuffers = false;
+
+ struct Uniform {
+ QShaderDescription::VariableType type;
+ int glslLocation;
+ int binding;
+ uint offset;
+ int size;
+ };
+ QVector<Uniform> uniforms;
+
+ struct Sampler {
+ int glslLocation;
+ int binding;
+ };
+ QVector<Sampler> samplers;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+Q_DECLARE_TYPEINFO(QGles2GraphicsPipeline::Uniform, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QGles2GraphicsPipeline::Sampler, Q_MOVABLE_TYPE);
+
+struct QGles2CommandBuffer : public QRhiCommandBuffer
+{
+ QGles2CommandBuffer(QRhiImplementation *rhi);
+ ~QGles2CommandBuffer();
+ void release() override;
+
+ struct Command {
+ enum Cmd {
+ BeginFrame,
+ EndFrame,
+ Viewport,
+ Scissor,
+ BlendConstants,
+ StencilRef,
+ BindVertexBuffer,
+ BindIndexBuffer,
+ Draw,
+ DrawIndexed,
+ BindGraphicsPipeline,
+ BindShaderResources,
+ BindFramebuffer,
+ Clear,
+ BufferData,
+ BufferSubData,
+ CopyTex,
+ ReadPixels,
+ SubImage,
+ CompressedImage,
+ CompressedSubImage,
+ BlitFromRenderbuffer,
+ GenMip
+ };
+ Cmd cmd;
+
+ static const int MAX_UBUF_BINDINGS = 32; // should be more than enough
+
+ // QRhi*/QGles2* references should be kept at minimum (so no
+ // QRhiTexture/Buffer/etc. pointers).
+ union {
+ struct {
+ float x, y, w, h;
+ float d0, d1;
+ } viewport;
+ struct {
+ int x, y, w, h;
+ } scissor;
+ struct {
+ float r, g, b, a;
+ } blendConstants;
+ struct {
+ quint32 ref;
+ QRhiGraphicsPipeline *ps;
+ } stencilRef;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ GLuint buffer;
+ quint32 offset;
+ int binding;
+ } bindVertexBuffer;
+ struct {
+ GLuint buffer;
+ quint32 offset;
+ GLenum type;
+ } bindIndexBuffer;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ quint32 vertexCount;
+ quint32 firstVertex;
+ } draw;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ quint32 indexCount;
+ quint32 firstIndex;
+ } drawIndexed;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ } bindGraphicsPipeline;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ QRhiShaderResourceBindings *srb;
+ int dynamicOffsetCount;
+ uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offsetInConstants
+ } bindShaderResources;
+ struct {
+ GLbitfield mask;
+ float c[4];
+ float d;
+ quint32 s;
+ } clear;
+ struct {
+ GLuint fbo;
+ bool srgb;
+ int colorAttCount;
+ } bindFramebuffer;
+ struct {
+ GLenum target;
+ GLuint buffer;
+ int offset;
+ int size;
+ const void *data; // must come from retainData()
+ } bufferSubData;
+ struct {
+ GLenum srcFaceTarget;
+ GLuint srcTexture;
+ int srcLevel;
+ int srcX;
+ int srcY;
+ GLenum dstTarget;
+ GLuint dstTexture;
+ GLenum dstFaceTarget;
+ int dstLevel;
+ int dstX;
+ int dstY;
+ int w;
+ int h;
+ } copyTex;
+ struct {
+ QRhiReadbackResult *result;
+ GLuint texture;
+ int w;
+ int h;
+ QRhiTexture::Format format;
+ GLenum readTarget;
+ int level;
+ } readPixels;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ int dx;
+ int dy;
+ int w;
+ int h;
+ GLenum glformat;
+ GLenum gltype;
+ int rowStartAlign;
+ const void *data; // must come from retainImage()
+ } subImage;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ GLenum glintformat;
+ int w;
+ int h;
+ int size;
+ const void *data; // must come from retainData()
+ } compressedImage;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ int dx;
+ int dy;
+ int w;
+ int h;
+ GLenum glintformat;
+ int size;
+ const void *data; // must come from retainData()
+ } compressedSubImage;
+ struct {
+ GLuint renderbuffer;
+ int w;
+ int h;
+ GLenum target;
+ GLuint texture;
+ int dstLevel;
+ } blitFromRb;
+ struct {
+ GLenum target;
+ GLuint texture;
+ } genMip;
+ } args;
+ };
+
+ QVector<Command> commands;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentPipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentSrb;
+ uint currentSrbGeneration;
+
+ QVector<QByteArray> dataRetainPool;
+ QVector<QImage> imageRetainPool;
+
+ // relies heavily on implicit sharing (no copies of the actual data will be made)
+ const void *retainData(const QByteArray &data) {
+ dataRetainPool.append(data);
+ return dataRetainPool.constLast().constData();
+ }
+ const void *retainImage(const QImage &image) {
+ imageRetainPool.append(image);
+ return imageRetainPool.constLast().constBits();
+ }
+ void resetCommands() {
+ commands.clear();
+ dataRetainPool.clear();
+ imageRetainPool.clear();
+ }
+ void resetState() {
+ resetCommands();
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+ void resetCachedState() {
+ currentPipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentSrb = nullptr;
+ currentSrbGeneration = 0;
+ }
+};
+
+Q_DECLARE_TYPEINFO(QGles2CommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QGles2SwapChain : public QRhiSwapChain
+{
+ QGles2SwapChain(QRhiImplementation *rhi);
+ ~QGles2SwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ QSurface *surface = nullptr;
+ QSize pixelSize;
+ QGles2ReferenceRenderTarget rt;
+ QGles2CommandBuffer cb;
+ int frameCount = 0;
+};
+
+class QRhiGles2 : public QRhiImplementation
+{
+public:
+ QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+
+ bool ensureContext(QSurface *surface = nullptr) const;
+ void executeDeferredReleases();
+ QRhi::FrameOpResult flushCommandBuffer();
+ void enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeCommandBuffer(QRhiCommandBuffer *cb);
+ void executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps);
+ void bindShaderResources(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb,
+ const uint *dynOfsPairs, int dynOfsCount);
+ QGles2RenderTargetData *enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
+ bool *wantsColorClear = nullptr, bool *wantsDsClear = nullptr);
+ int effectiveSampleCount(int sampleCount) const;
+
+ QOpenGLContext *ctx = nullptr;
+ bool importedContext = false;
+ QSurfaceFormat requestedFormat;
+ QSurface *fallbackSurface = nullptr;
+ QWindow *maybeWindow = nullptr;
+ mutable bool needsMakeCurrent = false;
+ QOpenGLExtensions *f = nullptr;
+ uint vao = 0;
+ struct Caps {
+ Caps()
+ : ctxMajor(2),
+ ctxMinor(0),
+ maxTextureSize(2048),
+ maxDrawBuffers(4),
+ msaaRenderBuffer(false),
+ npotTexture(true),
+ npotTextureRepeat(true),
+ gles(false),
+ fixedIndexPrimitiveRestart(false),
+ bgraExternalFormat(false),
+ bgraInternalFormat(false),
+ r8Format(false),
+ r16Format(false),
+ floatFormats(false),
+ depthTexture(false),
+ packedDepthStencil(false),
+ srgbCapableDefaultFramebuffer(false),
+ coreProfile(false),
+ uniformBuffers(false),
+ elementIndexUint(false)
+ { }
+ int ctxMajor;
+ int ctxMinor;
+ int maxTextureSize;
+ int maxDrawBuffers;
+ int maxSamples;
+ // Multisample fb and blit are supported (GLES 3.0 or OpenGL 3.x). Not
+ // the same as multisample textures!
+ uint msaaRenderBuffer : 1;
+ uint npotTexture : 1;
+ uint npotTextureRepeat : 1;
+ uint gles : 1;
+ uint fixedIndexPrimitiveRestart : 1;
+ uint bgraExternalFormat : 1;
+ uint bgraInternalFormat : 1;
+ uint r8Format : 1;
+ uint r16Format : 1;
+ uint floatFormats : 1;
+ uint depthTexture : 1;
+ uint packedDepthStencil : 1;
+ uint srgbCapableDefaultFramebuffer : 1;
+ uint coreProfile : 1;
+ uint uniformBuffers : 1;
+ uint elementIndexUint : 1;
+ } caps;
+ bool inFrame = false;
+ bool inPass = false;
+ QGles2SwapChain *currentSwapChain = nullptr;
+ QVector<GLint> supportedCompressedFormats;
+ mutable QVector<int> supportedSampleCountList;
+ QRhiGles2NativeHandles nativeHandlesStruct;
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Buffer,
+ Pipeline,
+ Texture,
+ RenderBuffer,
+ TextureRenderTarget
+ };
+ Type type;
+ union {
+ struct {
+ GLuint buffer;
+ } buffer;
+ struct {
+ GLuint program;
+ } pipeline;
+ struct {
+ GLuint texture;
+ } texture;
+ struct {
+ GLuint renderbuffer;
+ GLuint renderbuffer2;
+ } renderbuffer;
+ struct {
+ GLuint framebuffer;
+ } textureRenderTarget;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QGles2CommandBuffer cbWrapper;
+ } ofr;
+};
+
+Q_DECLARE_TYPEINFO(QRhiGles2::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
new file mode 100644
index 0000000000..8739a42738
--- /dev/null
+++ b/src/gui/rhi/qrhimetal.mm
@@ -0,0 +1,3249 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhimetal_p_p.h"
+#include "qshader_p.h"
+#include <QGuiApplication>
+#include <QWindow>
+#include <qmath.h>
+
+#ifdef Q_OS_MACOS
+#include <AppKit/AppKit.h>
+#endif
+
+#include <Metal/Metal.h>
+#include <QuartzCore/CAMetalLayer.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
+ Shared (host visible) and duplicated (due to 2 frames in flight), "static"
+ are Managed on macOS and Shared on iOS/tvOS, and still duplicated.
+ "Immutable" is like "static" but with only one native buffer underneath.
+ Textures are Private (device local) and a host visible staging buffer is
+ used to upload data to them. Does not rely on strong objects refs from
+ command buffers (hence uses commandBufferWithUnretainedReferences), but
+ does rely on automatic dependency tracking between encoders (hence no
+ MTLResourceHazardTrackingModeUntracked atm).
+*/
+
+#if __has_feature(objc_arc)
+#error ARC not supported
+#endif
+
+// Note: we expect everything here pass the Metal API validation when running
+// in Debug mode in XCode. Some of the issues that break validation are not
+// obvious and not visible when running outside XCode.
+//
+// An exception is the nextDrawable Called Early blah blah warning, which is
+// plain and simply false.
+
+/*!
+ \class QRhiMetalInitParams
+ \inmodule QtRhi
+ \brief Metal specific initialization parameters.
+
+ A Metal-based QRhi needs no special parameters for initialization.
+
+ \badcode
+ QRhiMetalInitParams params;
+ rhi = QRhi::create(QRhi::Metal, &params);
+ \endcode
+
+ \note Metal API validation cannot be enabled by the application. Instead,
+ run the debug build of the application in XCode. Generating a
+ \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
+ way to enable this.
+
+ \note QRhiSwapChain can only target QWindow instances that have their
+ surface type set to QSurface::MetalSurface.
+
+ \section2 Working with existing Metal devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Metal device. This can be achieved
+ by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
+ device must be set to a non-null value then. Optionally, a command queue
+ object can be specified as well.
+
+ The QRhi does not take ownership of any of the external objects.
+ */
+
+/*!
+ \class QRhiMetalNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal device used by the QRhi.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
+ \c{id<MTLCommandQueue>}.
+ */
+
+/*!
+ \class QRhiMetalTextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal texture object that is backing a QRhiTexture instance.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual type is \c{id<MTLTexture>}.
+ */
+
+/*!
+ \class QRhiMetalCommandBufferNativeHandles
+ \inmodule QtRhi
+ \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
+
+ \note The command buffer object is only guaranteed to be valid while
+ recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
+ - \l{QRhi::endFrame()}{endFrame()} or
+ \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+ \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
+
+ \note The command encoder is only valid while recording a pass, that is,
+ between \l{QRhiCommandBuffer::beginPass()} -
+ \l{QRhiCommandBuffer::endPass()}.
+ */
+
+struct QRhiMetalData
+{
+ QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
+
+ id<MTLDevice> dev = nil;
+ id<MTLCommandQueue> cmdQueue = nil;
+
+ MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount);
+ id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint);
+ id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Buffer,
+ RenderBuffer,
+ Texture,
+ Sampler,
+ StagingBuffer
+ };
+ Type type;
+ int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
+ union {
+ struct {
+ id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
+ } buffer;
+ struct {
+ id<MTLTexture> texture;
+ } renderbuffer;
+ struct {
+ id<MTLTexture> texture;
+ id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
+ } texture;
+ struct {
+ id<MTLSamplerState> samplerState;
+ } sampler;
+ struct {
+ id<MTLBuffer> buffer;
+ } stagingBuffer;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QMetalCommandBuffer cbWrapper;
+ } ofr;
+
+ struct ActiveReadback {
+ int activeFrameSlot = -1;
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ id<MTLBuffer> buf;
+ quint32 bufSize;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+
+ API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
+ API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
+
+ static const int TEXBUF_ALIGN = 256; // probably not accurate
+};
+
+Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
+
+struct QMetalBufferData
+{
+ bool managed;
+ id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
+};
+
+struct QMetalRenderBufferData
+{
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+};
+
+struct QMetalTextureData
+{
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+ id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
+ bool owns = true;
+};
+
+struct QMetalSamplerData
+{
+ id<MTLSamplerState> samplerState = nil;
+};
+
+struct QMetalCommandBufferData
+{
+ id<MTLCommandBuffer> cb;
+ id<MTLRenderCommandEncoder> currentPassEncoder;
+ MTLRenderPassDescriptor *currentPassRpDesc;
+ int currentFirstVertexBinding;
+ QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
+ QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
+};
+
+struct QMetalRenderTargetData
+{
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+
+ struct ColorAtt {
+ bool needsDrawableForTex = false;
+ id<MTLTexture> tex = nil;
+ int layer = 0;
+ int level = 0;
+ bool needsDrawableForResolveTex = false;
+ id<MTLTexture> resolveTex = nil;
+ int resolveLayer = 0;
+ int resolveLevel = 0;
+ };
+
+ struct {
+ ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
+ id<MTLTexture> dsTex = nil;
+ bool hasStencil = false;
+ bool depthNeedsStore = false;
+ } fb;
+};
+
+struct QMetalGraphicsPipelineData
+{
+ id<MTLRenderPipelineState> ps = nil;
+ id<MTLDepthStencilState> ds = nil;
+ MTLPrimitiveType primitiveType;
+ MTLWinding winding;
+ MTLCullMode cullMode;
+ id<MTLLibrary> vsLib = nil;
+ id<MTLFunction> vsFunc = nil;
+ id<MTLLibrary> fsLib = nil;
+ id<MTLFunction> fsFunc = nil;
+};
+
+struct QMetalSwapChainData
+{
+ CAMetalLayer *layer = nullptr;
+ id<CAMetalDrawable> curDrawable;
+ dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
+ MTLRenderPassDescriptor *rp = nullptr;
+ id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
+ QRhiTexture::Format rhiColorFormat;
+ MTLPixelFormat colorFormat;
+};
+
+QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
+{
+ Q_UNUSED(params);
+
+ d = new QRhiMetalData(this);
+
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ if (d->dev) {
+ d->dev = (id<MTLDevice>) importDevice->dev;
+ importedCmdQueue = importDevice->cmdQueue != nullptr;
+ if (importedCmdQueue)
+ d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
+ } else {
+ qWarning("No MTLDevice given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+QRhiMetal::~QRhiMetal()
+{
+ delete d;
+}
+
+static inline uint aligned(uint v, uint byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+bool QRhiMetal::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+
+ if (importedDevice)
+ [d->dev retain];
+ else
+ d->dev = MTLCreateSystemDefaultDevice();
+
+ qDebug("Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
+
+ if (importedCmdQueue)
+ [d->cmdQueue retain];
+ else
+ d->cmdQueue = [d->dev newCommandQueue];
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ d->captureMgr = [MTLCaptureManager sharedCaptureManager];
+ // Have a custom capture scope as well which then shows up in XCode as
+ // an option when capturing, and becomes especially useful when having
+ // multiple windows with multiple QRhis.
+ d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
+ const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
+ d->captureScope.label = label.toNSString();
+ }
+
+#if defined(Q_OS_MACOS)
+ caps.maxTextureSize = 16384;
+#elif defined(Q_OS_TVOS)
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
+ caps.maxTextureSize = 16384;
+ else
+ caps.maxTextureSize = 8192;
+#elif defined(Q_OS_IOS)
+ // welcome to feature set hell
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
+ {
+ caps.maxTextureSize = 16384;
+ } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
+ || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
+ {
+ caps.maxTextureSize = 8192;
+ } else {
+ caps.maxTextureSize = 4096;
+ }
+#endif
+
+ nativeHandlesStruct.dev = d->dev;
+ nativeHandlesStruct.cmdQueue = d->cmdQueue;
+
+ return true;
+}
+
+void QRhiMetal::destroy()
+{
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ [d->captureScope release];
+ d->captureScope = nil;
+ }
+
+ [d->cmdQueue release];
+ if (!importedCmdQueue)
+ d->cmdQueue = nil;
+
+ [d->dev release];
+ if (!importedDevice)
+ d->dev = nil;
+}
+
+QVector<int> QRhiMetal::supportedSampleCounts() const
+{
+ return { 1, 2, 4, 8 };
+}
+
+int QRhiMetal::effectiveSampleCount(int sampleCount) const
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ const int s = qBound(1, sampleCount, 64);
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return 1;
+ }
+ return s;
+}
+
+QRhiSwapChain *QRhiMetal::createSwapChain()
+{
+ return new QMetalSwapChain(this);
+}
+
+QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QMetalBuffer(this, type, usage, size);
+}
+
+int QRhiMetal::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiMetal::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiMetal::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiMetal::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
+{
+ // depth range 0..1
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(flags);
+
+#ifdef Q_OS_MACOS
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
+ return false;
+ if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
+ return false;
+#else
+ if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
+ return false;
+#endif
+
+ return true;
+}
+
+bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return true;
+ case QRhi::Timestamps:
+ return false;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return true;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return false;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return caps.maxTextureSize;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return QMTL_FRAMES_IN_FLIGHT;
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiMetal::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QMetalTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
+{
+ return new QMetalGraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
+{
+ return new QMetalShaderResourceBindings(this);
+}
+
+void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
+ bool offsetOnlyChange)
+{
+ static const int KNOWN_STAGES = 2;
+ struct {
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> bufferOffsets;
+ QRhiBatchedBindings<id<MTLTexture> > textures;
+ QRhiBatchedBindings<id<MTLSamplerState> > samplers;
+ } res[KNOWN_STAGES];
+
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
+ uint offset = b->u.ubuf.offset;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ if (dynOfs.first == b->binding) {
+ offset = dynOfs.second;
+ break;
+ }
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].buffers.feed(b->binding, mtlbuf);
+ res[0].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].buffers.feed(b->binding, mtlbuf);
+ res[1].bufferOffsets.feed(b->binding, offset);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].textures.feed(b->binding, texD->d->tex);
+ res[0].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].textures.feed(b->binding, texD->d->tex);
+ res[1].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
+ res[idx].buffers.finish();
+ res[idx].bufferOffsets.finish();
+
+ for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(res[idx].buffers.batches[i]);
+ const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentPassEncoder setVertexBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (offsetOnlyChange)
+ continue;
+
+ res[idx].textures.finish();
+ res[idx].samplers.finish();
+
+ for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].textures.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentPassEncoder setVertexTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentPassEncoder setFragmentTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].samplers.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentPassEncoder setVertexSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentPassEncoder setFragmentSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ }
+}
+
+void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_ASSERT(inPass);
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
+
+ if (cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ cbD->currentPipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ [cbD->d->currentPassEncoder setRenderPipelineState: psD->d->ps];
+ [cbD->d->currentPassEncoder setDepthStencilState: psD->d->ds];
+ [cbD->d->currentPassEncoder setCullMode: psD->d->cullMode];
+ [cbD->d->currentPassEncoder setFrontFacingWinding: psD->d->winding];
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_ASSERT(inPass);
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline);
+ if (!srb)
+ srb = QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_shaderResourceBindings;
+
+ QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
+ bool hasSlottedResourceInSrb = false;
+ bool hasDynamicOffsetInSrb = false;
+ bool resNeedsRebind = false;
+
+ // do buffer writes, figure out if we need to rebind, and mark as in-use
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+ executeBufferHostWritesForCurrentFrame(bufD);
+ if (bufD->m_type != QRhiBuffer::Immutable)
+ hasSlottedResourceInSrb = true;
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ resNeedsRebind = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ resNeedsRebind = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ // make sure the resources for the correct slot get bound
+ const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
+ if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
+ resNeedsRebind = true;
+
+ const bool srbChange = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation;
+
+ // dynamic uniform buffer offsets always trigger a rebind
+ if (hasDynamicOffsetInSrb || resNeedsRebind || srbChange) {
+ cbD->currentSrb = srb;
+ cbD->currentSrbGeneration = srbD->generation;
+ cbD->currentResSlot = resSlot;
+
+ const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChange;
+ enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange);
+ }
+}
+
+void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline);
+
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> offsets;
+ for (int i = 0; i < bindingCount; ++i) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
+ executeBufferHostWritesForCurrentFrame(bufD);
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
+ buffers.feed(startBinding + i, mtlbuf);
+ offsets.feed(startBinding + i, bindings[i].second);
+ }
+ buffers.finish();
+ offsets.finish();
+
+ // same binding space for vertex and constant buffers - work it around
+ QRhiShaderResourceBindings *srb = cbD->currentSrb;
+ // There's nothing guaranteeing setShaderResources() was called before
+ // setVertexInput()... but whatever srb will get bound will have to be
+ // layout-compatible anyways so maxBinding is the same.
+ if (!srb)
+ srb = cbD->currentPipeline->shaderResourceBindings();
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
+
+ if (firstVertexBinding != cbD->d->currentFirstVertexBinding
+ || buffers != cbD->d->currentVertexInputsBuffers
+ || offsets != cbD->d->currentVertexInputOffsets)
+ {
+ cbD->d->currentFirstVertexBinding = firstVertexBinding;
+ cbD->d->currentVertexInputsBuffers = buffers;
+ cbD->d->currentVertexInputOffsets = offsets;
+
+ for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(buffers.batches[i]);
+ const auto &offsetBatch(offsets.batches[i]);
+ [cbD->d->currentPassEncoder setVertexBuffers:
+ bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(firstVertexBinding + bufferBatch.startBinding, bufferBatch.resources.count())];
+ }
+ }
+
+ if (indexBuf) {
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
+ executeBufferHostWritesForCurrentFrame(ibufD);
+ ibufD->lastActiveFrameSlot = currentFrameSlot;
+ cbD->currentIndexBuffer = indexBuf;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = indexFormat;
+ } else {
+ cbD->currentIndexBuffer = nullptr;
+ }
+}
+
+void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ MTLViewport vp;
+ vp.originX = x;
+ vp.originY = y;
+ vp.width = w;
+ vp.height = h;
+ vp.znear = viewport.minDepth();
+ vp.zfar = viewport.maxDepth();
+
+ [cbD->d->currentPassEncoder setViewport: vp];
+
+ if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+ [cbD->d->currentPassEncoder setScissorRect: s];
+ }
+}
+
+void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
+ Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+
+ [cbD->d->currentPassEncoder setScissorRect: s];
+}
+
+void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ [cbD->d->currentPassEncoder setBlendColorRed: c.redF() green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
+}
+
+void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ [cbD->d->currentPassEncoder setStencilReferenceValue: refValue];
+}
+
+void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ [cbD->d->currentPassEncoder drawPrimitives:
+ QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
+ vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
+}
+
+void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (!cbD->currentIndexBuffer)
+ return;
+
+ const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
+ Q_ASSERT(indexOffset == aligned(indexOffset, 4));
+
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
+ id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot];
+
+ [cbD->d->currentPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentPipeline)->d->primitiveType
+ indexCount: indexCount
+ indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
+ indexBuffer: mtlbuf
+ indexBufferOffset: indexOffset
+ instanceCount: instanceCount
+ baseVertex: vertexOffset
+ baseInstance: firstInstance];
+}
+
+void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers)
+ return;
+
+ NSString *str = [NSString stringWithUTF8String: name.constData()];
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (inPass) {
+ [cbD->d->currentPassEncoder pushDebugGroup: str];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb pushDebugGroup: str];
+ }
+}
+
+void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers)
+ return;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (inPass) {
+ [cbD->d->currentPassEncoder popDebugGroup];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb popDebugGroup];
+ }
+}
+
+void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers)
+ return;
+
+ if (inPass) {
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ [cbD->d->currentPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
+ }
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
+{
+ return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ cbD->resetPerPassCachedState();
+}
+
+QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(!inFrame);
+ inFrame = true;
+
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+
+ // This is a bit messed up since for this swapchain we want to wait for the
+ // commands+present to complete, while for others just for the commands
+ // (for this same frame slot) but not sure how to do that in a sane way so
+ // wait for full cb completion for now.
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ if (sc != swapChainD)
+ dispatch_semaphore_signal(sem);
+ }
+
+ currentSwapChain = swapChainD;
+ currentFrameSlot = swapChainD->currentFrameSlot;
+ if (swapChainD->ds)
+ swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope beginScope];
+
+ // Do not let the command buffer mess with the refcount of objects. We do
+ // have a proper render loop and will manage lifetimes similarly to other
+ // backends (Vulkan).
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ if (swapChainD->samples > 1) {
+ colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
+ colorAtt.needsDrawableForResolveTex = true;
+ } else {
+ colorAtt.needsDrawableForTex = true;
+ }
+
+ swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
+ swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
+ swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
+ swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ executeDeferredReleases();
+ swapChainD->cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_ASSERT(inFrame);
+ inFrame = false;
+
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
+ if (needsPresent)
+ [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
+
+ __block int thisFrameSlot = currentFrameSlot;
+ [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
+ dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
+ }];
+
+ [swapChainD->cbWrapper.d->cb commit];
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope endScope];
+
+ if (needsPresent)
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ Q_ASSERT(!inFrame);
+ inFrame = true;
+
+ currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+ if (swapchains.count() > 1) {
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ // wait+signal is the general pattern to ensure the commands for a
+ // given frame slot have completed (if sem is 1, we go 0 then 1; if
+ // sem is 0 we go -1, block, completion increments to 0, then us to 1)
+ dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ d->ofr.active = true;
+ *cb = &d->ofr.cbWrapper;
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ executeDeferredReleases();
+ d->ofr.cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
+{
+ Q_ASSERT(d->ofr.active);
+ d->ofr.active = false;
+ Q_ASSERT(inFrame);
+ inFrame = false;
+
+ [d->ofr.cbWrapper.d->cb commit];
+
+ // offscreen frames wait for completion, unlike swapchain ones
+ [d->ofr.cbWrapper.d->cb waitUntilCompleted];
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::finish()
+{
+ Q_ASSERT(!inPass);
+
+ id<MTLCommandBuffer> cb = nil;
+ QMetalSwapChain *swapChainD = nullptr;
+ if (inFrame) {
+ if (d->ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ cb = d->ofr.cbWrapper.d->cb;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = currentSwapChain;
+ cb = swapChainD->cbWrapper.d->cb;
+ }
+ }
+
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
+ // no wait as this is the thing we're going to be commit below and
+ // beginFrame decremented sem already and going to be signaled by endFrame
+ continue;
+ }
+ dispatch_semaphore_t sem = sc->d->sem[i];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ if (cb) {
+ [cb commit];
+ [cb waitUntilCompleted];
+ }
+
+ if (inFrame) {
+ if (d->ofr.active)
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ else
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ }
+
+ executeDeferredReleases(true);
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount)
+{
+ MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
+ MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
+ colorClearValue.alphaF());
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ rp.colorAttachments[i].loadAction = MTLLoadActionClear;
+ rp.colorAttachments[i].storeAction = MTLStoreActionStore;
+ rp.colorAttachments[i].clearColor = c;
+ }
+
+ if (hasDepthStencil) {
+ rp.depthAttachment.loadAction = MTLLoadActionClear;
+ rp.depthAttachment.storeAction = MTLStoreActionDontCare;
+ rp.stencilAttachment.loadAction = MTLLoadActionClear;
+ rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
+ rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
+ rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
+ }
+
+ return rp;
+}
+
+qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
+{
+ qsizetype size = 0;
+ const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
+ subresDesc.data().size() : subresDesc.image().sizeInBytes();
+ if (imageSizeBytes > 0)
+ size += aligned(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ return size;
+}
+
+void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
+ qsizetype *curOfs)
+{
+ const QPoint dp = subresDesc.destinationTopLeft();
+ const QByteArray rawData = subresDesc.data();
+ QImage img = subresDesc.image();
+ id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
+
+ if (!img.isNull()) {
+ const qsizetype fullImageSizeBytes = img.sizeInBytes();
+ int w = img.width();
+ int h = img.height();
+ int bpl = img.bytesPerLine();
+ int srcOffset = 0;
+
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const int sx = subresDesc.sourceTopLeft().x();
+ const int sy = subresDesc.sourceTopLeft().y();
+ if (!subresDesc.sourceSize().isEmpty()) {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+ if (img.depth() == 32) {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ srcOffset = sy * bpl + sx * 4;
+ // bpl remains set to the original image's row stride
+ } else {
+ img = img.copy(sx, sy, w, h);
+ bpl = img.bytesPerLine();
+ Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), img.sizeInBytes());
+ }
+ } else {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ }
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs + srcOffset
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
+
+ const int dx = aligned(dp.x(), blockDim.width());
+ const int dy = aligned(dp.y(), blockDim.height());
+ if (dx + w != subresw)
+ w = aligned(w, blockDim.width());
+ if (dy + h != subresh)
+ h = aligned(h, blockDim.height());
+
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dx, dy, 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty()) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+}
+
+void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ bufD->d->pendingUpdates[i].append(u);
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ for (int i = 0, ie = bufD->m_type == QRhiBuffer::Immutable ? 1 : QMTL_FRAMES_IN_FLIGHT; i != ie; ++i)
+ bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
+ }
+
+ id<MTLBlitCommandEncoder> blitEnc = nil;
+ auto ensureBlit = [&blitEnc, cbD, this] {
+ if (!blitEnc) {
+ blitEnc = [cbD->d->cb blitCommandEncoder];
+ if (debugMarkers)
+ [blitEnc pushDebugGroup: @"Texture upload/copy"];
+ }
+ };
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex);
+ qsizetype stagingSize = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ stagingSize += subresUploadByteSize(subresDesc);
+ }
+ }
+
+ ensureBlit();
+ Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
+ utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: stagingSize
+ options: MTLResourceStorageModeShared];
+ QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
+
+ void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
+ qsizetype curOfs = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
+ }
+ }
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
+ utexD->d->stagingBuf[currentFrameSlot] = nil;
+ d->releaseQueue.append(e);
+ QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src);
+ QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst);
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+
+ ensureBlit();
+ [blitEnc copyFromTexture: srcD->d->tex
+ sourceSlice: u.copy.desc.sourceLayer()
+ sourceLevel: u.copy.desc.sourceLevel()
+ sourceOrigin: MTLOriginMake(sp.x(), sp.y(), 0)
+ sourceSize: MTLSizeMake(size.width(), size.height(), 1)
+ toTexture: dstD->d->tex
+ destinationSlice: u.copy.desc.destinationLayer()
+ destinationLevel: u.copy.desc.destinationLevel()
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)];
+
+ srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QRhiMetalData::ActiveReadback aRb;
+ aRb.activeFrameSlot = currentFrameSlot;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture());
+ QMetalSwapChain *swapChainD = nullptr;
+ id<MTLTexture> src;
+ QSize srcSize;
+ if (texD) {
+ if (texD->samples > 1) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
+ : texD->m_pixelSize;
+ aRb.format = texD->m_format;
+ src = texD->d->tex;
+ srcSize = texD->m_pixelSize;
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ aRb.pixelSize = swapChainD->pixelSize;
+ aRb.format = swapChainD->d->rhiColorFormat;
+ // Multisample swapchains need nothing special since resolving
+ // happens when ending a renderpass.
+ const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
+ src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
+ srcSize = swapChainD->rtWrapper.d->pixelSize;
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
+ aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
+
+ QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(aRb.buf)),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ aRb.bufSize));
+
+ ensureBlit();
+ [blitEnc copyFromTexture: src
+ sourceSlice: u.read.rb.layer()
+ sourceLevel: u.read.rb.level()
+ sourceOrigin: MTLOriginMake(0, 0, 0)
+ sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
+ toBuffer: aRb.buf
+ destinationOffset: 0
+ destinationBytesPerRow: bpl
+ destinationBytesPerImage: 0
+ options: MTLBlitOptionNone];
+
+ d->activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex);
+ ensureBlit();
+ [blitEnc generateMipmapsForTexture: utexD->d->tex];
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+
+ if (blitEnc) {
+ if (debugMarkers)
+ [blitEnc popDebugGroup];
+ [blitEnc endEncoding];
+ }
+
+ ud->free();
+}
+
+void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
+{
+ const int idx = bufD->m_type == QRhiBuffer::Immutable ? 0 : currentFrameSlot;
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
+ if (updates.isEmpty())
+ return;
+
+ void *p = [bufD->d->buf[idx] contents];
+ int changeBegin = -1;
+ int changeEnd = -1;
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
+ Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
+ memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
+ if (changeBegin == -1 || u.offset < changeBegin)
+ changeBegin = u.offset;
+ if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
+ changeEnd = u.offset + u.data.size();
+ }
+ if (changeBegin >= 0 && bufD->d->managed)
+ [bufD->d->buf[idx] didModifyRange: NSMakeRange(changeBegin, changeEnd - changeBegin)];
+
+ updates.clear();
+}
+
+void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+
+ QMetalRenderTargetData *rtD = nullptr;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = QRHI_RES(QMetalReferenceRenderTarget, rt)->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtD->colorAttCount) {
+ QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
+ if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) {
+ Q_ASSERT(currentSwapChain);
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable];
+ if (!swapChainD->d->curDrawable) {
+ qWarning("No drawable");
+ return;
+ }
+ id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
+ if (color0.needsDrawableForTex) {
+ color0.tex = scTex;
+ color0.needsDrawableForTex = false;
+ } else {
+ color0.resolveTex = scTex;
+ color0.needsDrawableForResolveTex = false;
+ }
+ }
+ }
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
+ rtD = rtTex->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
+ for (int i = 0; i < rtD->colorAttCount; ++i)
+ cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
+ }
+ if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
+ cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
+ cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
+ }
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
+ if (colorAttachment.texture())
+ QRHI_RES(QMetalTexture, colorAttachment.texture())->lastActiveFrameSlot = currentFrameSlot;
+ else if (colorAttachment.renderBuffer())
+ QRHI_RES(QMetalRenderBuffer, colorAttachment.renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (colorAttachment.resolveTexture())
+ QRHI_RES(QMetalTexture, colorAttachment.resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ if (rtTex->m_desc.depthStencilBuffer())
+ QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtTex->m_desc.depthTexture())
+ QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ for (int i = 0; i < rtD->colorAttCount; ++i) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].slice = rtD->fb.colorAtt[i].layer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].level = rtD->fb.colorAtt[i].level;
+ if (rtD->fb.colorAtt[i].resolveTex) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = rtD->fb.colorAtt[i].resolveLayer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = rtD->fb.colorAtt[i].resolveLevel;
+ }
+ }
+
+ if (rtD->dsAttCount) {
+ Q_ASSERT(rtD->fb.dsTex);
+ cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
+ cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
+ if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
+ cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
+ }
+
+ cbD->d->currentPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
+
+ cbD->resetPerPassState();
+
+ cbD->currentTarget = rt;
+ inPass = true;
+}
+
+void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inPass);
+ inPass = false;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ [cbD->d->currentPassEncoder endEncoding];
+
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.buffer.buffers[i] release];
+}
+
+static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.renderbuffer.texture release];
+}
+
+static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.texture.texture release];
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.texture.stagingBuffers[i] release];
+}
+
+static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.sampler.samplerState release];
+}
+
+void QRhiMetal::executeDeferredReleases(bool forced)
+{
+ for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
+ if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
+ switch (e.type) {
+ case QRhiMetalData::DeferredReleaseEntry::Buffer:
+ qrhimtl_releaseBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::RenderBuffer:
+ qrhimtl_releaseRenderBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Texture:
+ qrhimtl_releaseTexture(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Sampler:
+ qrhimtl_releaseSampler(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
+ [e.stagingBuffer.buffer release];
+ break;
+ default:
+ break;
+ }
+ d->releaseQueue.removeAt(i);
+ }
+ }
+}
+
+void QRhiMetal::finishActiveReadbacks(bool forced)
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]);
+ if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+ void *p = [aRb.buf contents];
+ memcpy(aRb.result->data.data(), p, aRb.bufSize);
+ [aRb.buf release];
+
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.buf))));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ d->activeReadbacks.removeAt(i);
+ }
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size),
+ d(new QMetalBufferData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->buf[i] = nil;
+}
+
+QMetalBuffer::~QMetalBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalBuffer::release()
+{
+ if (!d->buf[0])
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Buffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.buffer.buffers[i] = d->buf[i];
+ d->buf[i] = nil;
+ d->pendingUpdates[i].clear();
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalBuffer::build()
+{
+ if (d->buf[0])
+ release();
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+ const int roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256) : nonZeroSize;
+
+ d->managed = false;
+ MTLResourceOptions opts = MTLResourceStorageModeShared;
+#ifdef Q_OS_MACOS
+ if (m_type != Dynamic) {
+ opts = MTLResourceStorageModeManaged;
+ d->managed = true;
+ }
+#endif
+
+ QRHI_RES_RHI(QRhiMetal);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ // Immutable only has buf[0] and pendingUpdates[0] in use.
+ // Static and Dynamic use all.
+ if (i == 0 || m_type != Immutable) {
+ d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
+ d->pendingUpdates[i].reserve(16);
+ if (!m_objectName.isEmpty()) {
+ if (m_type == Immutable) {
+ d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
+ } else {
+ const QByteArray name = m_objectName + '/' + QByteArray::number(i);
+ d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
+ }
+ }
+ }
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, roundedSize, m_type == Immutable ? 1 : QMTL_FRAMES_IN_FLIGHT, 0));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),
+ d(new QMetalRenderBufferData)
+{
+}
+
+QMetalRenderBuffer::~QMetalRenderBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalRenderBuffer::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::RenderBuffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderbuffer.texture = d->tex;
+ d->tex = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalRenderBuffer::build()
+{
+ if (d->tex)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.width = m_pixelSize.width();
+ desc.height = m_pixelSize.height();
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+
+ bool transientBacking = false;
+ switch (m_type) {
+ case DepthStencil:
+#ifdef Q_OS_MACOS
+ desc.storageMode = MTLStorageModePrivate;
+#else
+ desc.storageMode = MTLResourceStorageModeMemoryless;
+ transientBacking = true;
+#endif
+ d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+ desc.pixelFormat = d->format;
+ break;
+ case Color:
+ desc.storageMode = MTLStorageModePrivate;
+ d->format = MTLPixelFormatRGBA8Unorm;
+ desc.pixelFormat = d->format;
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, transientBacking, false, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags),
+ d(new QMetalTextureData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->stagingBuf[i] = nil;
+}
+
+QMetalTexture::~QMetalTexture()
+{
+ release();
+ delete d;
+}
+
+void QMetalTexture::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Texture;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.texture.texture = d->owns ? d->tex : nil;
+ d->tex = nil;
+ nativeHandlesStruct.texture = nullptr;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.texture.stagingBuffers[i] = d->stagingBuf[i];
+ d->stagingBuf[i] = nil;
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BGRA8:
+ return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ case QRhiTexture::R8:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatR8Unorm;
+#else
+ return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
+#endif
+ case QRhiTexture::R16:
+ return MTLPixelFormatR16Unorm;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return MTLPixelFormatR8Unorm;
+
+ case QRhiTexture::RGBA16F:
+ return MTLPixelFormatRGBA16Float;
+ case QRhiTexture::RGBA32F:
+ return MTLPixelFormatRGBA32Float;
+
+ case QRhiTexture::D16:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatDepth16Unorm;
+#else
+ return MTLPixelFormatDepth32Float;
+#endif
+ case QRhiTexture::D32F:
+ return MTLPixelFormatDepth32Float;
+
+#ifdef Q_OS_MACOS
+ case QRhiTexture::BC1:
+ return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
+ case QRhiTexture::BC2:
+ return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
+ case QRhiTexture::BC3:
+ return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
+ case QRhiTexture::BC4:
+ return MTLPixelFormatBC4_RUnorm;
+ case QRhiTexture::BC5:
+ qWarning("QRhiMetal does not support BC5");
+ return MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BC6H:
+ return MTLPixelFormatBC6H_RGBUfloat;
+ case QRhiTexture::BC7:
+ return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
+#else
+ case QRhiTexture::BC1:
+ case QRhiTexture::BC2:
+ case QRhiTexture::BC3:
+ case QRhiTexture::BC4:
+ case QRhiTexture::BC5:
+ case QRhiTexture::BC6H:
+ case QRhiTexture::BC7:
+ qWarning("QRhiMetal: BCx compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+#ifndef Q_OS_MACOS
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
+#else
+ case QRhiTexture::ETC2_RGB8:
+ case QRhiTexture::ETC2_RGB8A1:
+ case QRhiTexture::ETC2_RGBA8:
+ qWarning("QRhiMetal: ETC2 compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+
+ case QRhiTexture::ASTC_4x4:
+ case QRhiTexture::ASTC_5x4:
+ case QRhiTexture::ASTC_5x5:
+ case QRhiTexture::ASTC_6x5:
+ case QRhiTexture::ASTC_6x6:
+ case QRhiTexture::ASTC_8x5:
+ case QRhiTexture::ASTC_8x6:
+ case QRhiTexture::ASTC_8x8:
+ case QRhiTexture::ASTC_10x5:
+ case QRhiTexture::ASTC_10x6:
+ case QRhiTexture::ASTC_10x8:
+ case QRhiTexture::ASTC_10x10:
+ case QRhiTexture::ASTC_12x10:
+ case QRhiTexture::ASTC_12x12:
+ qWarning("QRhiMetal: ASTC compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+ default:
+ Q_UNREACHABLE();
+ return MTLPixelFormatRGBA8Unorm;
+ }
+}
+
+bool QMetalTexture::prepareBuild(QSize *adjustedSize)
+{
+ if (d->tex)
+ release();
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->format = toMetalTextureFormat(m_format, m_flags);
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ if (samples > 1) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QMetalTexture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ if (isCube)
+ desc.textureType = MTLTextureTypeCube;
+ else
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.pixelFormat = d->format;
+ desc.width = size.width();
+ desc.height = size.height();
+ desc.mipmapLevelCount = mipLevelCount;
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageShaderRead;
+ if (m_flags.testFlag(RenderTarget))
+ desc.usage |= MTLTextureUsageRenderTarget;
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ d->owns = true;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QMetalTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiMetalTextureNativeHandles *h = static_cast<const QRhiMetalTextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ d->tex = (id<MTLTexture>) h->texture;
+
+ d->owns = false;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QMetalTexture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v),
+ d(new QMetalSamplerData)
+{
+}
+
+QMetalSampler::~QMetalSampler()
+{
+ release();
+ delete d;
+}
+
+void QMetalSampler::release()
+{
+ if (!d->samplerState)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Sampler;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.sampler.samplerState = d->samplerState;
+ d->samplerState = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return MTLSamplerMinMagFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMinMagFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMinMagFilterNearest;
+ }
+}
+
+static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::None:
+ return MTLSamplerMipFilterNotMipmapped;
+ case QRhiSampler::Nearest:
+ return MTLSamplerMipFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMipFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMipFilterNotMipmapped;
+ }
+}
+
+static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return MTLSamplerAddressModeRepeat;
+ case QRhiSampler::ClampToEdge:
+ return MTLSamplerAddressModeClampToEdge;
+ case QRhiSampler::Border:
+ return MTLSamplerAddressModeClampToBorderColor;
+ case QRhiSampler::Mirror:
+ return MTLSamplerAddressModeMirrorRepeat;
+ case QRhiSampler::MirrorOnce:
+ return MTLSamplerAddressModeMirrorClampToEdge;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerAddressModeClampToEdge;
+ }
+}
+
+static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return MTLCompareFunctionNever;
+ case QRhiSampler::Less:
+ return MTLCompareFunctionLess;
+ case QRhiSampler::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiSampler::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiSampler::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiSampler::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiSampler::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiSampler::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionNever;
+ }
+}
+
+bool QMetalSampler::build()
+{
+ if (d->samplerState)
+ release();
+
+ MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
+ desc.minFilter = toMetalFilter(m_minFilter);
+ desc.magFilter = toMetalFilter(m_magFilter);
+ desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
+ desc.sAddressMode = toMetalAddressMode(m_addressU);
+ desc.tAddressMode = toMetalAddressMode(m_addressV);
+ desc.rAddressMode = toMetalAddressMode(m_addressW);
+ desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
+ [desc release];
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
+// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
+QMetalRenderPassDescriptor::QMetalRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
+{
+ release();
+}
+
+void QMetalRenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalReferenceRenderTarget::~QMetalReferenceRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QMetalReferenceRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalReferenceRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalReferenceRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalTextureRenderTarget::QMetalTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalTextureRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = colorAttachments.count();
+ rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ for (int i = 0, ie = colorAttachments.count(); i != ie; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ rpD->colorFormat[i] = texD ? texD->d->format : rbD->d->format;
+ }
+
+ if (m_desc.depthTexture())
+ rpD->dsFormat = QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format;
+ else if (m_desc.depthStencilBuffer())
+ rpD->dsFormat = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format;
+
+ return rpD;
+}
+
+bool QMetalTextureRenderTarget::build()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ d->colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d->colorAttCount; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ id<MTLTexture> dst = nil;
+ if (texD) {
+ dst = texD->d->tex;
+ if (i == 0) {
+ d->pixelSize = texD->pixelSize();
+ d->sampleCount = texD->samples;
+ }
+ } else if (rbD) {
+ dst = rbD->d->tex;
+ if (i == 0) {
+ d->pixelSize = rbD->pixelSize();
+ d->sampleCount = rbD->samples;
+ }
+ }
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ colorAtt.tex = dst;
+ colorAtt.layer = colorAttachments[i].layer();
+ colorAtt.level = colorAttachments[i].level();
+ QMetalTexture *resTexD = QRHI_RES(QMetalTexture, colorAttachments[i].resolveTexture());
+ colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
+ colorAtt.resolveLayer = colorAttachments[i].resolveLayer();
+ colorAtt.resolveLevel = colorAttachments[i].resolveLevel();
+ d->fb.colorAtt[i] = colorAtt;
+ }
+ d->dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
+ d->fb.dsTex = depthTexD->d->tex;
+ d->fb.hasStencil = false;
+ d->fb.depthNeedsStore = true;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthTexD->pixelSize();
+ d->sampleCount = depthTexD->samples;
+ }
+ } else {
+ QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
+ d->fb.dsTex = depthRbD->d->tex;
+ d->fb.hasStencil = true;
+ d->fb.depthNeedsStore = false;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthRbD->pixelSize();
+ d->sampleCount = depthRbD->samples;
+ }
+ }
+ d->dsAttCount = 1;
+ } else {
+ d->dsAttCount = 0;
+ }
+
+ return true;
+}
+
+QSize QMetalTextureRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalTextureRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalTextureRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalShaderResourceBindings::QMetalShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QMetalShaderResourceBindings::~QMetalShaderResourceBindings()
+{
+ release();
+}
+
+void QMetalShaderResourceBindings::release()
+{
+ sortedBindings.clear();
+ maxBinding = -1;
+}
+
+bool QMetalShaderResourceBindings::build()
+{
+ if (!sortedBindings.isEmpty())
+ release();
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+ if (!sortedBindings.isEmpty())
+ maxBinding = QRhiShaderResourceBindingPrivate::get(&sortedBindings.last())->binding;
+ else
+ maxBinding = -1;
+
+ boundResourceData.resize(sortedBindings.count());
+
+ for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ generation += 1;
+ return true;
+}
+
+QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi),
+ d(new QMetalGraphicsPipelineData)
+{
+}
+
+QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
+{
+ release();
+ delete d;
+}
+
+void QMetalGraphicsPipeline::release()
+{
+ QRHI_RES_RHI(QRhiMetal);
+
+ if (!d->ps)
+ return;
+
+ if (d->ps) {
+ [d->ps release];
+ d->ps = nil;
+ }
+
+ if (d->ds) {
+ [d->ds release];
+ d->ds = nil;
+ }
+
+ if (d->vsFunc) {
+ [d->vsFunc release];
+ d->vsFunc = nil;
+ }
+ if (d->vsLib) {
+ [d->vsLib release];
+ d->vsLib = nil;
+ }
+
+ if (d->fsFunc) {
+ [d->fsFunc release];
+ d->fsFunc = nil;
+ }
+ if (d->fsLib) {
+ [d->fsLib release];
+ d->fsLib = nil;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return MTLVertexFormatFloat4;
+ case QRhiVertexInputAttribute::Float3:
+ return MTLVertexFormatFloat3;
+ case QRhiVertexInputAttribute::Float2:
+ return MTLVertexFormatFloat2;
+ case QRhiVertexInputAttribute::Float:
+ return MTLVertexFormatFloat;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return MTLVertexFormatUChar4Normalized;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return MTLVertexFormatUChar2Normalized;
+ case QRhiVertexInputAttribute::UNormByte:
+ if (@available(macOS 10.13, iOS 11.0, *))
+ return MTLVertexFormatUCharNormalized;
+ else
+ Q_UNREACHABLE();
+ default:
+ Q_UNREACHABLE();
+ return MTLVertexFormatFloat4;
+ }
+}
+
+static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return MTLBlendFactorZero;
+ case QRhiGraphicsPipeline::One:
+ return MTLBlendFactorOne;
+ case QRhiGraphicsPipeline::SrcColor:
+ return MTLBlendFactorSourceColor;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return MTLBlendFactorOneMinusSourceColor;
+ case QRhiGraphicsPipeline::DstColor:
+ return MTLBlendFactorDestinationColor;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return MTLBlendFactorOneMinusDestinationColor;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return MTLBlendFactorSourceAlpha;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return MTLBlendFactorOneMinusSourceAlpha;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return MTLBlendFactorDestinationAlpha;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return MTLBlendFactorOneMinusDestinationAlpha;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return MTLBlendFactorBlendColor;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return MTLBlendFactorBlendAlpha;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return MTLBlendFactorOneMinusBlendColor;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return MTLBlendFactorOneMinusBlendAlpha;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return MTLBlendFactorSourceAlphaSaturated;
+ case QRhiGraphicsPipeline::Src1Color:
+ return MTLBlendFactorSource1Color;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return MTLBlendFactorOneMinusSource1Color;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return MTLBlendFactorSource1Alpha;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return MTLBlendFactorOneMinusSource1Alpha;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendFactorZero;
+ }
+}
+
+static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return MTLBlendOperationAdd;
+ case QRhiGraphicsPipeline::Subtract:
+ return MTLBlendOperationSubtract;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return MTLBlendOperationReverseSubtract;
+ case QRhiGraphicsPipeline::Min:
+ return MTLBlendOperationMin;
+ case QRhiGraphicsPipeline::Max:
+ return MTLBlendOperationMax;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendOperationAdd;
+ }
+}
+
+static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+ uint f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= MTLColorWriteMaskRed;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= MTLColorWriteMaskGreen;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= MTLColorWriteMaskBlue;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= MTLColorWriteMaskAlpha;
+ return f;
+}
+
+static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return MTLCompareFunctionNever;
+ case QRhiGraphicsPipeline::Less:
+ return MTLCompareFunctionLess;
+ case QRhiGraphicsPipeline::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiGraphicsPipeline::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiGraphicsPipeline::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiGraphicsPipeline::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionAlways;
+ }
+}
+
+static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return MTLStencilOperationZero;
+ case QRhiGraphicsPipeline::Keep:
+ return MTLStencilOperationKeep;
+ case QRhiGraphicsPipeline::Replace:
+ return MTLStencilOperationReplace;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return MTLStencilOperationIncrementClamp;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return MTLStencilOperationDecrementClamp;
+ case QRhiGraphicsPipeline::Invert:
+ return MTLStencilOperationInvert;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return MTLStencilOperationIncrementWrap;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return MTLStencilOperationDecrementWrap;
+ default:
+ Q_UNREACHABLE();
+ return MTLStencilOperationKeep;
+ }
+}
+
+static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return MTLPrimitiveTypeTriangle;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return MTLPrimitiveTypeTriangleStrip;
+ case QRhiGraphicsPipeline::Lines:
+ return MTLPrimitiveTypeLine;
+ case QRhiGraphicsPipeline::LineStrip:
+ return MTLPrimitiveTypeLineStrip;
+ case QRhiGraphicsPipeline::Points:
+ return MTLPrimitiveTypePoint;
+ default:
+ Q_UNREACHABLE();
+ return MTLPrimitiveTypeTriangle;
+ }
+}
+
+static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return MTLCullModeNone;
+ case QRhiGraphicsPipeline::Front:
+ return MTLCullModeFront;
+ case QRhiGraphicsPipeline::Back:
+ return MTLCullModeBack;
+ default:
+ Q_UNREACHABLE();
+ return MTLCullModeNone;
+ }
+}
+
+id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint)
+{
+ QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant });
+ if (!mtllib.shader().isEmpty()) {
+ dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
+ mtllib.shader().size(),
+ dispatch_get_global_queue(0, 0),
+ DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
+ dispatch_release(data);
+ if (!err) {
+ *entryPoint = mtllib.entryPoint();
+ return lib;
+ } else {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
+ }
+ }
+
+ QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant });
+ if (mslSource.shader().isEmpty()) {
+ qWarning() << "No MSL 1.2 code found in baked shader" << shader;
+ return nil;
+ }
+
+ NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
+ MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
+ opts.languageVersion = MTLLanguageVersion1_2;
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
+ [opts release];
+ // src is autoreleased
+
+ if (err) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ *error = msg;
+ return nil;
+ }
+
+ *entryPoint = mslSource.entryPoint();
+ return lib;
+}
+
+id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
+{
+ NSString *name = [NSString stringWithUTF8String: entryPoint.constData()];
+ id<MTLFunction> f = [lib newFunctionWithName: name];
+ [name release];
+ return f;
+}
+
+bool QMetalGraphicsPipeline::build()
+{
+ if (d->ps)
+ release();
+
+ QRHI_RES_RHI(QRhiMetal);
+
+ // same binding space for vertex and constant buffers - work it around
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
+
+ MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ const int loc = attribute.location();
+ inputLayout.attributes[loc].format = toMetalAttributeFormat(attribute.format());
+ inputLayout.attributes[loc].offset = attribute.offset();
+ inputLayout.attributes[loc].bufferIndex = firstVertexBinding + attribute.binding();
+ }
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ for (int i = 0, ie = bindings.count(); i != ie; ++i) {
+ const QRhiVertexInputBinding &binding(bindings[i]);
+ const int layoutIdx = firstVertexBinding + i;
+ inputLayout.layouts[layoutIdx].stepFunction =
+ binding.classification() == QRhiVertexInputBinding::PerInstance
+ ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
+ inputLayout.layouts[layoutIdx].stepRate = binding.instanceStepRate();
+ inputLayout.layouts[layoutIdx].stride = binding.stride();
+ }
+
+ MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+
+ rpDesc.vertexDescriptor = inputLayout;
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ // Everything is immutable because we can guarantee that "neither the
+ // CPU nor the GPU will modify a buffer's contents between the time the
+ // buffer is set in a function's argument table and the time its
+ // associated command buffer completes execution" (as that's the point
+ // of our Vulkan-style buffer juggling in the first place).
+ const int vertexBufferCount = firstVertexBinding + bindings.count(); // cbuf + vbuf
+ const int fragmentBufferCount = firstVertexBinding; // cbuf
+ for (int i = 0; i < vertexBufferCount; ++i)
+ rpDesc.vertexBuffers[i].mutability = MTLMutabilityImmutable;
+ for (int i = 0; i < fragmentBufferCount; ++i)
+ rpDesc.fragmentBuffers[i].mutability = MTLMutabilityImmutable;
+ }
+
+ for (const QRhiGraphicsShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ QString error;
+ QByteArray entryPoint;
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint);
+ if (!lib) {
+ qWarning("MSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ return false;
+ }
+ switch (shaderStage.type()) {
+ case QRhiGraphicsShaderStage::Vertex:
+ rpDesc.vertexFunction = func;
+ d->vsLib = lib;
+ d->vsFunc = func;
+ break;
+ case QRhiGraphicsShaderStage::Fragment:
+ rpDesc.fragmentFunction = func;
+ d->fsLib = lib;
+ d->fsFunc = func;
+ break;
+ default:
+ [func release];
+ [lib release];
+ break;
+ }
+ }
+
+ QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
+
+ if (rpD->colorAttachmentCount) {
+ // defaults when no targetBlends are provided
+ rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
+ rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
+ rpDesc.colorAttachments[0].blendingEnabled = false;
+
+ Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
+ || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
+
+ for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+ rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
+ rpDesc.colorAttachments[i].blendingEnabled = b.enable;
+ rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
+ rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
+ rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
+ rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
+ rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
+ rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
+ rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
+ }
+ }
+
+ if (rpD->hasDepthStencil) {
+ // Must only be set when a depth-stencil buffer will actually be bound,
+ // validation blows up otherwise.
+ MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
+ rpDesc.depthAttachmentPixelFormat = fmt;
+ if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
+ rpDesc.stencilAttachmentPixelFormat = fmt;
+ }
+
+ rpDesc.sampleCount = rhiD->effectiveSampleCount(m_sampleCount);
+
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ [rpDesc release];
+ return false;
+ }
+ [rpDesc release];
+
+ MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
+ dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
+ dsDesc.depthWriteEnabled = m_depthWrite;
+ if (m_stencilTest) {
+ dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
+ dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
+ dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
+ dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
+
+ dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
+ dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
+ dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
+ dsDesc.backFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
+ }
+
+ d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
+ [dsDesc release];
+
+ d->primitiveType = toMetalPrimitiveType(m_topology);
+ d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
+ d->cullMode = toMetalCullMode(m_cullMode);
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalCommandBuffer::QMetalCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi),
+ d(new QMetalCommandBufferData)
+{
+ resetState();
+}
+
+QMetalCommandBuffer::~QMetalCommandBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalCommandBuffer::release()
+{
+ // nothing to do here, we do not own the MTL cb object
+}
+
+const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles()
+{
+ nativeHandlesStruct.commandBuffer = d->cb;
+ nativeHandlesStruct.encoder = d->currentPassEncoder;
+ return &nativeHandlesStruct;
+}
+
+void QMetalCommandBuffer::resetState()
+{
+ d->currentPassEncoder = nil;
+ d->currentPassRpDesc = nil;
+ resetPerPassState();
+}
+
+void QMetalCommandBuffer::resetPerPassState()
+{
+ currentTarget = nullptr;
+ resetPerPassCachedState();
+}
+
+void QMetalCommandBuffer::resetPerPassCachedState()
+{
+ currentPipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentResSlot = -1;
+ currentIndexBuffer = nullptr;
+
+ d->currentFirstVertexBinding = -1;
+ d->currentVertexInputsBuffers.clear();
+ d->currentVertexInputOffsets.clear();
+}
+
+QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rtWrapper(rhi),
+ cbWrapper(rhi),
+ d(new QMetalSwapChainData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ d->sem[i] = nullptr;
+ d->msaaTex[i] = nil;
+ }
+}
+
+QMetalSwapChain::~QMetalSwapChain()
+{
+ release();
+ delete d;
+}
+
+void QMetalSwapChain::release()
+{
+ if (!d->layer)
+ return;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (d->sem[i]) {
+ // the semaphores cannot be released if they do not have the initial value
+ dispatch_semaphore_wait(d->sem[i], DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(d->sem[i]);
+
+ dispatch_release(d->sem[i]);
+ d->sem[i] = nullptr;
+ }
+ }
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = nil;
+ }
+
+ d->layer = nullptr;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->swapchains.remove(this);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QMetalSwapChain::currentFrameCommandBuffer()
+{
+ return &cbWrapper;
+}
+
+QRhiRenderTarget *QMetalSwapChain::currentFrameRenderTarget()
+{
+ return &rtWrapper;
+}
+
+QSize QMetalSwapChain::surfacePixelSize()
+{
+ // may be called before build, must not access other than m_*
+
+ NSView *v = (NSView *) m_window->winId();
+ if (v) {
+ CAMetalLayer *layer = (CAMetalLayer *) [v layer];
+ if (layer) {
+ CGSize size = [layer drawableSize];
+ return QSize(size.width, size.height);
+ }
+ }
+ return QSize();
+}
+
+QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
+{
+ chooseFormats(); // ensure colorFormat and similar are filled out
+
+ QRHI_RES_RHI(QRhiMetal);
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = 1;
+ rpD->hasDepthStencil = m_depthStencil != nullptr;
+
+ rpD->colorFormat[0] = d->colorFormat;
+
+ // m_depthStencil may not be built yet so cannot rely on computed fields in it
+ rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+
+ return rpD;
+}
+
+void QMetalSwapChain::chooseFormats()
+{
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ // pick a format that is allowed for CAMetalLayer.pixelFormat
+ d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ d->rhiColorFormat = QRhiTexture::BGRA8;
+}
+
+bool QMetalSwapChain::buildOrResize()
+{
+ Q_ASSERT(m_window);
+
+ const bool needsRegistration = !window || window != m_window;
+
+ if (window && window != m_window)
+ release();
+ // else no release(), this is intentional
+
+ QRHI_RES_RHI(QRhiMetal);
+ if (needsRegistration)
+ rhiD->swapchains.insert(this);
+
+ window = m_window;
+
+ if (window->surfaceType() != QSurface::MetalSurface) {
+ qWarning("QMetalSwapChain only supports MetalSurface windows");
+ return false;
+ }
+
+ NSView *v = (NSView *) window->winId();
+ d->layer = (CAMetalLayer *) [v layer];
+ Q_ASSERT(d->layer);
+
+ chooseFormats();
+ if (d->colorFormat != d->layer.pixelFormat)
+ d->layer.pixelFormat = d->colorFormat;
+
+ if (m_flags.testFlag(UsedAsTransferSource))
+ d->layer.framebufferOnly = NO;
+
+#ifdef Q_OS_MACOS
+ if (m_flags.testFlag(NoVSync)) {
+ if (@available(macOS 10.13, *))
+ d->layer.displaySyncEnabled = NO;
+ }
+#endif
+
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ [d->layer setDevice: rhiD->d->dev];
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (!d->sem[i])
+ d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
+ }
+
+ currentFrameSlot = 0;
+ frameCount = 0;
+
+ ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ rtWrapper.d->pixelSize = pixelSize;
+ rtWrapper.d->dpr = window->devicePixelRatio();
+ rtWrapper.d->sampleCount = samples;
+ rtWrapper.d->colorAttCount = 1;
+ rtWrapper.d->dsAttCount = ds ? 1 : 0;
+
+ qDebug("got CAMetalLayer, size %dx%d", pixelSize.width(), pixelSize.height());
+
+ if (samples > 1) {
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = MTLTextureType2DMultisample;
+ desc.pixelFormat = d->colorFormat;
+ desc.width = pixelSize.width();
+ desc.height = pixelSize.height();
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
+ }
+ [desc release];
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, QMTL_FRAMES_IN_FLIGHT, samples > 1 ? QMTL_FRAMES_IN_FLIGHT : 0, samples));
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h
new file mode 100644
index 0000000000..094801c58c
--- /dev/null
+++ b/src/gui/rhi/qrhimetal_p.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIMETAL_H
+#define QRHIMETAL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+// no Metal includes here, the user code may be plain C++
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiMetalInitParams : public QRhiInitParams
+{
+};
+
+struct Q_GUI_EXPORT QRhiMetalNativeHandles : public QRhiNativeHandles
+{
+ void *dev = nullptr; // id<MTLDevice>
+ void *cmdQueue = nullptr; // id<MTLCommandQueue>
+};
+
+struct Q_GUI_EXPORT QRhiMetalTextureNativeHandles : public QRhiNativeHandles
+{
+ void *texture = nullptr; // id<MTLTexture>
+};
+
+struct Q_GUI_EXPORT QRhiMetalCommandBufferNativeHandles : public QRhiNativeHandles
+{
+ void *commandBuffer = nullptr; // id<MTLCommandBuffer>
+ void *encoder = nullptr; // id<MTLRenderCommandEncoder>
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h
new file mode 100644
index 0000000000..039a5bdbf6
--- /dev/null
+++ b/src/gui/rhi/qrhimetal_p_p.h
@@ -0,0 +1,410 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIMETAL_P_H
+#define QRHIMETAL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhimetal_p.h"
+#include "qrhi_p_p.h"
+#include <QWindow>
+
+QT_BEGIN_NAMESPACE
+
+static const int QMTL_FRAMES_IN_FLIGHT = 2;
+
+// have to hide the ObjC stuff, this header cannot contain MTL* at all
+struct QMetalBufferData;
+
+struct QMetalBuffer : public QRhiBuffer
+{
+ QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QMetalBuffer();
+ void release() override;
+ bool build() override;
+
+ QMetalBufferData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+};
+
+struct QMetalRenderBufferData;
+
+struct QMetalRenderBuffer : public QRhiRenderBuffer
+{
+ QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QMetalRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ QMetalRenderBufferData *d;
+ int samples = 1;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+};
+
+struct QMetalTextureData;
+
+struct QMetalTexture : public QRhiTexture
+{
+ QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QMetalTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+
+ QMetalTextureData *d;
+ QRhiMetalTextureNativeHandles nativeHandlesStruct;
+ int mipLevelCount = 0;
+ int samples = 1;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+};
+
+struct QMetalSamplerData;
+
+struct QMetalSampler : public QRhiSampler
+{
+ QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QMetalSampler();
+ void release() override;
+ bool build() override;
+
+ QMetalSamplerData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+};
+
+struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QMetalRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QMetalRenderPassDescriptor();
+ void release() override;
+
+ // there is no MTLRenderPassDescriptor here as one will be created for each pass in beginPass()
+
+ // but the things needed for the render pipeline descriptor have to be provided
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+ int colorAttachmentCount = 0;
+ bool hasDepthStencil = false;
+ int colorFormat[MAX_COLOR_ATTACHMENTS];
+ int dsFormat;
+};
+
+struct QMetalRenderTargetData;
+
+struct QMetalReferenceRenderTarget : public QRhiRenderTarget
+{
+ QMetalReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QMetalReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QMetalRenderTargetData *d;
+};
+
+struct QMetalTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QMetalTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QMetalTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QMetalRenderTargetData *d;
+ friend class QRhiMetal;
+};
+
+struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QMetalShaderResourceBindings(QRhiImplementation *rhi);
+ ~QMetalShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ int maxBinding = -1;
+
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData;
+
+ uint generation = 0;
+ friend class QRhiMetal;
+};
+
+struct QMetalGraphicsPipelineData;
+
+struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QMetalGraphicsPipeline(QRhiImplementation *rhi);
+ ~QMetalGraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ QMetalGraphicsPipelineData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+};
+
+struct QMetalCommandBufferData;
+struct QMetalSwapChain;
+
+struct QMetalCommandBuffer : public QRhiCommandBuffer
+{
+ QMetalCommandBuffer(QRhiImplementation *rhi);
+ ~QMetalCommandBuffer();
+ void release() override;
+
+ QMetalCommandBufferData *d = nullptr;
+ QRhiMetalCommandBufferNativeHandles nativeHandlesStruct;
+
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentPipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentSrb;
+ uint currentSrbGeneration;
+ int currentResSlot;
+ QRhiBuffer *currentIndexBuffer;
+ quint32 currentIndexOffset;
+ QRhiCommandBuffer::IndexFormat currentIndexFormat;
+
+ const QRhiNativeHandles *nativeHandles();
+ void resetState();
+ void resetPerPassState();
+ void resetPerPassCachedState();
+};
+
+struct QMetalSwapChainData;
+
+struct QMetalSwapChain : public QRhiSwapChain
+{
+ QMetalSwapChain(QRhiImplementation *rhi);
+ ~QMetalSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+
+ bool buildOrResize() override;
+
+ void chooseFormats();
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ int currentFrameSlot = 0; // 0..QMTL_FRAMES_IN_FLIGHT-1
+ int frameCount = 0;
+ int samples = 1;
+ QMetalReferenceRenderTarget rtWrapper;
+ QMetalCommandBuffer cbWrapper;
+ QMetalRenderBuffer *ds = nullptr;
+ QMetalSwapChainData *d = nullptr;
+};
+
+struct QRhiMetalData;
+
+class QRhiMetal : public QRhiImplementation
+{
+public:
+ QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice = nullptr);
+ ~QRhiMetal();
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+
+ void executeDeferredReleases(bool forced = false);
+ void finishActiveReadbacks(bool forced = false);
+ qsizetype subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const;
+ void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
+ qsizetype *curOfs);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD);
+ void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
+ bool offsetOnlyChange);
+ int effectiveSampleCount(int sampleCount) const;
+
+ bool importedDevice = false;
+ bool importedCmdQueue = false;
+ bool inFrame = false;
+ bool inPass = false;
+ QMetalSwapChain *currentSwapChain = nullptr;
+ QSet<QMetalSwapChain *> swapchains;
+ QRhiMetalNativeHandles nativeHandlesStruct;
+
+ struct {
+ int maxTextureSize = 4096;
+ } caps;
+
+ QRhiMetalData *d = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
new file mode 100644
index 0000000000..ad7b74a5c3
--- /dev/null
+++ b/src/gui/rhi/qrhinull.cpp
@@ -0,0 +1,709 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhinull_p_p.h"
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhiNullInitParams
+ \inmodule QtRhi
+ \brief Null backend specific initialization parameters.
+
+ A Null QRhi needs no special parameters for initialization.
+
+ \badcode
+ QRhiNullInitParams params;
+ rhi = QRhi::create(QRhi::Null, &params);
+ \endcode
+
+ The Null backend does not issue any graphics calls and creates no
+ resources. All QRhi operations will succeed as normal so applications can
+ still be run, albeit potentially at an unthrottled speed, depending on
+ their frame rendering strategy. The backend reports resources to
+ QRhiProfiler as usual.
+ */
+
+/*!
+ \class QRhiNullNativeHandles
+ \inmodule QtRhi
+ \brief Empty.
+ */
+
+/*!
+ \class QRhiNullTextureNativeHandles
+ \inmodule QtRhi
+ \brief Empty.
+ */
+
+QRhiNull::QRhiNull(QRhiNullInitParams *params)
+ : offscreenCommandBuffer(this)
+{
+ Q_UNUSED(params);
+}
+
+bool QRhiNull::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ return true;
+}
+
+void QRhiNull::destroy()
+{
+}
+
+QVector<int> QRhiNull::supportedSampleCounts() const
+{
+ return { 1 };
+}
+
+QRhiSwapChain *QRhiNull::createSwapChain()
+{
+ return new QNullSwapChain(this);
+}
+
+QRhiBuffer *QRhiNull::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QNullBuffer(this, type, usage, size);
+}
+
+int QRhiNull::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiNull::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiNull::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiNull::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiNull::clipSpaceCorrMatrix() const
+{
+ return QMatrix4x4(); // identity
+}
+
+bool QRhiNull::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(format);
+ Q_UNUSED(flags);
+ return true;
+}
+
+bool QRhiNull::isFeatureSupported(QRhi::Feature feature) const
+{
+ Q_UNUSED(feature);
+ return true;
+}
+
+int QRhiNull::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return 16384;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiNull::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiNull::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiNull::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QNullRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiNull::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QNullTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiNull::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QNullSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiNull::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QNullTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiNull::createGraphicsPipeline()
+{
+ return new QNullGraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiNull::createShaderResourceBindings()
+{
+ return new QNullShaderResourceBindings(this);
+}
+
+void QRhiNull::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(ps);
+}
+
+void QRhiNull::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(srb);
+ Q_UNUSED(dynamicOffsetCount);
+ Q_UNUSED(dynamicOffsets);
+}
+
+void QRhiNull::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(startBinding);
+ Q_UNUSED(bindingCount);
+ Q_UNUSED(bindings);
+ Q_UNUSED(indexBuf);
+ Q_UNUSED(indexOffset);
+ Q_UNUSED(indexFormat);
+}
+
+void QRhiNull::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(viewport);
+}
+
+void QRhiNull::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(scissor);
+}
+
+void QRhiNull::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(c);
+}
+
+void QRhiNull::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(refValue);
+}
+
+void QRhiNull::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(vertexCount);
+ Q_UNUSED(instanceCount);
+ Q_UNUSED(firstVertex);
+ Q_UNUSED(firstInstance);
+}
+
+void QRhiNull::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(indexCount);
+ Q_UNUSED(instanceCount);
+ Q_UNUSED(firstIndex);
+ Q_UNUSED(vertexOffset);
+ Q_UNUSED(firstInstance);
+}
+
+void QRhiNull::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(name);
+}
+
+void QRhiNull::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiNull::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(msg);
+}
+
+const QRhiNativeHandles *QRhiNull::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiNull::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiNull::endExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+QRhi::FrameOpResult QRhiNull::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ currentSwapChain = swapChain;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ QNullSwapChain *swapChainD = QRHI_RES(QNullSwapChain, swapChain);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, 0.000666f));
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ *cb = &offscreenCommandBuffer;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::endOffscreenFrame()
+{
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::finish()
+{
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_UNUSED(cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QRhiReadbackResult *result = u.read.result;
+ QRhiTexture *tex = u.read.rb.texture();
+ if (tex) {
+ result->format = tex->format();
+ result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize());
+ } else {
+ Q_ASSERT(currentSwapChain);
+ result->format = QRhiTexture::RGBA8;
+ result->pixelSize = currentSwapChain->currentPixelSize();
+ }
+ quint32 byteSize = 0;
+ textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize);
+ result->data.fill(0, byteSize);
+ if (result->completed)
+ result->completed();
+ }
+ }
+ ud->free();
+}
+
+void QRhiNull::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_UNUSED(rt);
+ Q_UNUSED(colorClearValue);
+ Q_UNUSED(depthStencilClearValue);
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+void QRhiNull::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+QNullBuffer::QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QNullBuffer::~QNullBuffer()
+{
+ release();
+}
+
+void QNullBuffer::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+}
+
+bool QNullBuffer::build()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, m_size, 1, 0));
+ return true;
+}
+
+QNullRenderBuffer::QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QNullRenderBuffer::~QNullRenderBuffer()
+{
+ release();
+}
+
+void QNullRenderBuffer::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+}
+
+bool QNullRenderBuffer::build()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, 1));
+ return true;
+}
+
+QRhiTexture::Format QNullRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QNullTexture::QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+}
+
+QNullTexture::~QNullTexture()
+{
+ release();
+}
+
+void QNullTexture::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+}
+
+bool QNullTexture::build()
+{
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
+ return true;
+}
+
+bool QNullTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ Q_UNUSED(src);
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1));
+ return true;
+}
+
+const QRhiNativeHandles *QNullTexture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QNullSampler::QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QNullSampler::~QNullSampler()
+{
+ release();
+}
+
+void QNullSampler::release()
+{
+}
+
+bool QNullSampler::build()
+{
+ return true;
+}
+
+QNullRenderPassDescriptor::QNullRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QNullRenderPassDescriptor::~QNullRenderPassDescriptor()
+{
+ release();
+}
+
+void QNullRenderPassDescriptor::release()
+{
+}
+
+QNullReferenceRenderTarget::QNullReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QNullReferenceRenderTarget::~QNullReferenceRenderTarget()
+{
+ release();
+}
+
+void QNullReferenceRenderTarget::release()
+{
+}
+
+QSize QNullReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QNullReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QNullReferenceRenderTarget::sampleCount() const
+{
+ return 1;
+}
+
+QNullTextureRenderTarget::QNullTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+}
+
+QNullTextureRenderTarget::~QNullTextureRenderTarget()
+{
+ release();
+}
+
+void QNullTextureRenderTarget::release()
+{
+}
+
+QRhiRenderPassDescriptor *QNullTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QNullRenderPassDescriptor(m_rhi);
+}
+
+bool QNullTextureRenderTarget::build()
+{
+ d.rp = QRHI_RES(QNullRenderPassDescriptor, m_renderPassDesc);
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ if (!colorAttachments.isEmpty()) {
+ QRhiTexture *tex = colorAttachments.first().texture();
+ QRhiRenderBuffer *rb = colorAttachments.first().renderBuffer();
+ d.pixelSize = tex ? tex->pixelSize() : rb->pixelSize();
+ } else if (m_desc.depthStencilBuffer()) {
+ d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+ } else if (m_desc.depthTexture()) {
+ d.pixelSize = m_desc.depthTexture()->pixelSize();
+ }
+ return true;
+}
+
+QSize QNullTextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QNullTextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QNullTextureRenderTarget::sampleCount() const
+{
+ return 1;
+}
+
+QNullShaderResourceBindings::QNullShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QNullShaderResourceBindings::~QNullShaderResourceBindings()
+{
+ release();
+}
+
+void QNullShaderResourceBindings::release()
+{
+}
+
+bool QNullShaderResourceBindings::build()
+{
+ return true;
+}
+
+QNullGraphicsPipeline::QNullGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QNullGraphicsPipeline::~QNullGraphicsPipeline()
+{
+ release();
+}
+
+void QNullGraphicsPipeline::release()
+{
+}
+
+bool QNullGraphicsPipeline::build()
+{
+ return true;
+}
+
+QNullCommandBuffer::QNullCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+}
+
+QNullCommandBuffer::~QNullCommandBuffer()
+{
+ release();
+}
+
+void QNullCommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QNullSwapChain::QNullSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+}
+
+QNullSwapChain::~QNullSwapChain()
+{
+ release();
+}
+
+void QNullSwapChain::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+}
+
+QRhiCommandBuffer *QNullSwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QNullSwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QNullSwapChain::surfacePixelSize()
+{
+ return QSize(1280, 720);
+}
+
+QRhiRenderPassDescriptor *QNullSwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QNullRenderPassDescriptor(m_rhi);
+}
+
+bool QNullSwapChain::buildOrResize()
+{
+ m_currentPixelSize = surfacePixelSize();
+ rt.d.rp = QRHI_RES(QNullRenderPassDescriptor, m_renderPassDesc);
+ rt.d.pixelSize = m_currentPixelSize;
+ frameCount = 0;
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, 1, 0, 1));
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhinull_p.h b/src/gui/rhi/qrhinull_p.h
new file mode 100644
index 0000000000..7d3ce5dbf1
--- /dev/null
+++ b/src/gui/rhi/qrhinull_p.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHINULL_H
+#define QRHINULL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiNullInitParams : public QRhiInitParams
+{
+};
+
+struct Q_GUI_EXPORT QRhiNullNativeHandles : public QRhiNativeHandles
+{
+};
+
+struct Q_GUI_EXPORT QRhiNullTextureNativeHandles : public QRhiNativeHandles
+{
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h
new file mode 100644
index 0000000000..da48b72656
--- /dev/null
+++ b/src/gui/rhi/qrhinull_p_p.h
@@ -0,0 +1,279 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHINULL_P_H
+#define QRHINULL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhinull_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QNullBuffer : public QRhiBuffer
+{
+ QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QNullBuffer();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullRenderBuffer : public QRhiRenderBuffer
+{
+ QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QNullRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+};
+
+struct QNullTexture : public QRhiTexture
+{
+ QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QNullTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ QRhiNullTextureNativeHandles nativeHandlesStruct;
+};
+
+struct QNullSampler : public QRhiSampler
+{
+ QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QNullSampler();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QNullRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QNullRenderPassDescriptor();
+ void release() override;
+};
+
+struct QNullRenderTargetData
+{
+ QNullRenderTargetData(QRhiImplementation *) { }
+
+ QNullRenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+};
+
+struct QNullReferenceRenderTarget : public QRhiRenderTarget
+{
+ QNullReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QNullReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QNullRenderTargetData d;
+};
+
+struct QNullTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QNullTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QNullTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QNullRenderTargetData d;
+};
+
+struct QNullShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QNullShaderResourceBindings(QRhiImplementation *rhi);
+ ~QNullShaderResourceBindings();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QNullGraphicsPipeline(QRhiImplementation *rhi);
+ ~QNullGraphicsPipeline();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullCommandBuffer : public QRhiCommandBuffer
+{
+ QNullCommandBuffer(QRhiImplementation *rhi);
+ ~QNullCommandBuffer();
+ void release() override;
+};
+
+struct QNullSwapChain : public QRhiSwapChain
+{
+ QNullSwapChain(QRhiImplementation *rhi);
+ ~QNullSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ QNullReferenceRenderTarget rt;
+ QNullCommandBuffer cb;
+ int frameCount = 0;
+};
+
+class QRhiNull : public QRhiImplementation
+{
+public:
+ QRhiNull(QRhiNullInitParams *params);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+
+ QRhiNullNativeHandles nativeHandlesStruct;
+ QRhiSwapChain *currentSwapChain = nullptr;
+ QNullCommandBuffer offscreenCommandBuffer;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhiprofiler.cpp b/src/gui/rhi/qrhiprofiler.cpp
new file mode 100644
index 0000000000..15e3007d49
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler.cpp
@@ -0,0 +1,603 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhiprofiler_p_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhiProfiler
+ \inmodule QtRhi
+
+ \brief Collects resource and timing information from an active QRhi.
+
+ A QRhiProfiler is present for each QRhi. Query it via QRhi::profiler(). The
+ profiler is active only when the QRhi was created with
+ QRhi::EnableProfiling. No data is collected otherwise.
+
+ \note GPU timings are only available when QRhi indicates that
+ QRhi::Timestamps is supported.
+
+ Besides collecting data from the QRhi implementations, some additional
+ values are calculated. For example, for textures and similar resources the
+ profiler gives an estimate of the complete amount of memory the resource
+ needs.
+
+ \section2 Output Format
+
+ The output is comma-separated text. Each line has a number of
+ comma-separated entries and each line ends with a comma.
+
+ For example:
+
+ \badcode
+ 1,0,140446057946208,Triangle vbuf,type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,0,140446057947376,Triangle ubuf,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 1,1,140446057950416,,type,0,usage,1,logical_size,112,effective_size,112,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057950544,,type,0,usage,2,logical_size,12,effective_size,12,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057947440,,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 1,1,140446057984784,Cube vbuf (textured),type,0,usage,1,logical_size,720,effective_size,720,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057982528,Cube ubuf (textured),type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 7,8,140446058913648,Qt texture,width,256,height,256,format,1,owns_native_resource,1,mip_count,9,layer_count,1,effective_sample_count,1,approx_byte_size,349524,
+ 1,8,140446058795856,Cube vbuf (textured with offscreen),type,0,usage,1,logical_size,720,effective_size,720,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,8,140446058947920,Cube ubuf (textured with offscreen),type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 7,8,140446058794928,Texture for offscreen content,width,512,height,512,format,1,owns_native_resource,1,mip_count,1,layer_count,1,effective_sample_count,1,approx_byte_size,1048576,
+ 1,8,140446058963904,Triangle vbuf,type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,8,140446058964560,Triangle ubuf,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 5,9,140446057945392,,type,0,width,1280,height,720,effective_sample_count,1,transient_backing,0,winsys_backing,0,approx_byte_size,3686400,
+ 11,9,140446057944592,,width,1280,height,720,buffer_count,2,msaa_buffer_count,0,effective_sample_count,1,approx_total_byte_size,7372800,
+ 9,9,140446058913648,Qt texture,slot,0,size,262144,
+ 10,9,140446058913648,Qt texture,slot,0,
+ 17,2019,140446057944592,,frames_since_resize,121,min_ms_frame_delta,9,max_ms_frame_delta,33,Favg_ms_frame_delta,16.1167,
+ 18,2019,140446057944592,,frames_since_resize,121,min_ms_frame_build,0,max_ms_frame_build,1,Favg_ms_frame_build,0.00833333,
+ 17,4019,140446057944592,,frames_since_resize,241,min_ms_frame_delta,15,max_ms_frame_delta,17,Favg_ms_frame_delta,16.0583,
+ 18,4019,140446057944592,,frames_since_resize,241,min_ms_frame_build,0,max_ms_frame_build,0,Favg_ms_frame_build,0,
+ 12,5070,140446057944592,,
+ 2,5079,140446057947376,Triangle ubuf,
+ 2,5079,140446057946208,Triangle vbuf,
+ 2,5079,140446057947440,,
+ 2,5079,140446057950544,,
+ 2,5079,140446057950416,,
+ 8,5079,140446058913648,Qt texture,
+ 2,5079,140446057982528,Cube ubuf (textured),
+ 2,5079,140446057984784,Cube vbuf (textured),
+ 2,5079,140446058964560,Triangle ubuf,
+ 2,5079,140446058963904,Triangle vbuf,
+ 8,5079,140446058794928,Texture for offscreen content,
+ 2,5079,140446058947920,Cube ubuf (textured with offscreen),
+ 2,5079,140446058795856,Cube vbuf (textured with offscreen),
+ 6,5079,140446057945392,,
+ \endcode
+
+ Each line starts with \c op, \c timestamp, \c res, \c name where op is a
+ value from StreamOp, timestamp is a recording timestamp in milliseconds
+ (qint64), res is a number (quint64) referring to the QRhiResource the entry
+ refers to, or 0 if not applicable. \c name is the value of
+ QRhiResource::name() and may be empty as well. The \c name will never
+ contain a comma.
+
+ This is followed by any number of \c{key, value} pairs where \c key is an
+ unspecified string and \c value is a number. If \c key starts with \c F, it
+ indicates the value is a float. Otherwise assume that the value is a
+ qint64.
+ */
+
+/*!
+ \enum QRhiProfiler::StreamOp
+ Describes an entry in the profiler's output stream.
+
+ \value NewBuffer A buffer is created
+ \value ReleaseBuffer A buffer is destroyed
+ \value NewBufferStagingArea A staging buffer for buffer upload is created
+ \value ReleaseBufferStagingArea A staging buffer for buffer upload is destroyed
+ \value NewRenderBuffer A renderbuffer is created
+ \value ReleaseRenderBuffer A renderbuffer is destroyed
+ \value NewTexture A texture is created
+ \value ReleaseTexture A texture is destroyed
+ \value NewTextureStagingArea A staging buffer for texture upload is created
+ \value ReleaseTextureStagingArea A staging buffer for texture upload is destroyed
+ \value ResizeSwapChain A swapchain is created or resized
+ \value ReleaseSwapChain A swapchain is destroyed
+ \value NewReadbackBuffer A staging buffer for readback is created
+ \value ReleaseReadbackBuffer A staging buffer for readback is destroyed
+ \value GpuMemAllocStats GPU memory allocator statistics
+ \value GpuFrameTime GPU frame times
+ \value FrameToFrameTime CPU frame-to-frame times
+ \value FrameBuildTime CPU beginFrame-endFrame times
+ */
+
+/*!
+ \class QRhiProfiler::CpuTime
+ \inmodule QtRhi
+ \brief Contains CPU-side frame timings.
+
+ Once sufficient number of frames have been rendered, the minimum, maximum,
+ and average values (in milliseconds) from various measurements are made
+ available in this struct queriable from QRhiProfiler::frameToFrameTimes()
+ and QRhiProfiler::frameBuildTimes().
+
+ \sa QRhiProfiler::setFrameTimingWriteInterval()
+ */
+
+/*!
+ \class QRhiProfiler::GpuTime
+ \inmodule QtRhi
+ \brief Contains GPU-side frame timings.
+
+ Once sufficient number of frames have been rendered, the minimum, maximum,
+ and average values (in milliseconds) calculated from GPU command buffer
+ timestamps are made available in this struct queriable from
+ QRhiProfiler::gpuFrameTimes().
+
+ \sa QRhiProfiler::setFrameTimingWriteInterval()
+ */
+
+/*!
+ \internal
+ */
+QRhiProfiler::QRhiProfiler()
+ : d(new QRhiProfilerPrivate)
+{
+ d->ts.start();
+}
+
+/*!
+ Destructor.
+ */
+QRhiProfiler::~QRhiProfiler()
+{
+ // Flush because there is a high chance we have writes that were made since
+ // the event loop last ran. (esp. relevant for network devices like QTcpSocket)
+ if (d->outputDevice)
+ d->outputDevice->waitForBytesWritten(1000);
+
+ delete d;
+}
+
+/*!
+ Sets the output \a device.
+
+ \note No output will be generated when QRhi::EnableProfiling was not set.
+ */
+void QRhiProfiler::setDevice(QIODevice *device)
+{
+ d->outputDevice = device;
+}
+
+/*!
+ Requests writing a GpuMemAllocStats entry into the output, when applicable.
+ Backends that do not support this will ignore the request. This is an
+ explicit request since getting the allocator status and statistics may be
+ an expensive operation.
+ */
+void QRhiProfiler::addVMemAllocatorStats()
+{
+ if (d->rhiDWhenEnabled)
+ d->rhiDWhenEnabled->sendVMemStatsToProfiler();
+}
+
+/*!
+ \return the currently set frame timing writeout interval.
+ */
+int QRhiProfiler::frameTimingWriteInterval() const
+{
+ return d->frameTimingWriteInterval;
+}
+
+/*!
+ Sets the number of frames that need to be rendered before the collected CPU
+ and GPU timings are processed (min, max, average are calculated) to \a
+ frameCount.
+
+ The default value is 120.
+ */
+void QRhiProfiler::setFrameTimingWriteInterval(int frameCount)
+{
+ if (frameCount > 0)
+ d->frameTimingWriteInterval = frameCount;
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the time that elapsed between two
+ QRhi::endFrame() calls.
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+ */
+QRhiProfiler::CpuTime QRhiProfiler::frameToFrameTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->frameToFrameTime;
+
+ return QRhiProfiler::CpuTime();
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the time that elapsed between
+ a QRhi::beginFrame() and QRhi::endFrame().
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+ */
+QRhiProfiler::CpuTime QRhiProfiler::frameBuildTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->beginToEndFrameTime;
+
+ return QRhiProfiler::CpuTime();
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the GPU time that is spent on
+ one frame.
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+
+ The GPU times should only be compared between runs on the same GPU of the
+ same system with the same backend. Comparing times for different graphics
+ cards or for different backends can give misleading results. The numbers are
+ not meant to be comparable that way.
+
+ \note Some backends have no support for this, and even for those that have,
+ it is not guaranteed that the driver will support it at run time. Support
+ can be checked via QRhi::Timestamps.
+ */
+QRhiProfiler::GpuTime QRhiProfiler::gpuFrameTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->gpuFrameTime;
+
+ return QRhiProfiler::GpuTime();
+}
+
+void QRhiProfilerPrivate::startEntry(QRhiProfiler::StreamOp op, qint64 timestamp, QRhiResource *res)
+{
+ buf.clear();
+ buf.append(QByteArray::number(op));
+ buf.append(',');
+ buf.append(QByteArray::number(timestamp));
+ buf.append(',');
+ buf.append(QByteArray::number(quint64(quintptr(res))));
+ buf.append(',');
+ if (res)
+ buf.append(res->name());
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::writeInt(const char *key, qint64 v)
+{
+ Q_ASSERT(key[0] != 'F');
+ buf.append(key);
+ buf.append(',');
+ buf.append(QByteArray::number(v));
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::writeFloat(const char *key, float f)
+{
+ Q_ASSERT(key[0] == 'F');
+ buf.append(key);
+ buf.append(',');
+ buf.append(QByteArray::number(f));
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::endEntry()
+{
+ buf.append('\n');
+ outputDevice->write(buf);
+}
+
+void QRhiProfilerPrivate::newBuffer(QRhiBuffer *buf, quint32 realSize, int backingGpuBufCount, int backingCpuBufCount)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewBuffer, ts.elapsed(), buf);
+ writeInt("type", buf->type());
+ writeInt("usage", buf->usage());
+ writeInt("logical_size", buf->size());
+ writeInt("effective_size", realSize);
+ writeInt("backing_gpu_buf_count", backingGpuBufCount);
+ writeInt("backing_cpu_buf_count", backingCpuBufCount);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseBuffer(QRhiBuffer *buf)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseBuffer, ts.elapsed(), buf);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newBufferStagingArea(QRhiBuffer *buf, int slot, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewBufferStagingArea, ts.elapsed(), buf);
+ writeInt("slot", slot);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseBufferStagingArea(QRhiBuffer *buf, int slot)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseBufferStagingArea, ts.elapsed(), buf);
+ writeInt("slot", slot);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newRenderBuffer(QRhiRenderBuffer *rb, bool transientBacking, bool winSysBacking, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QRhiRenderBuffer::Type type = rb->type();
+ const QSize sz = rb->pixelSize();
+ // just make up something, ds is likely D24S8 while color is RGBA8 or similar
+ const QRhiTexture::Format assumedFormat = type == QRhiRenderBuffer::DepthStencil ? QRhiTexture::D32F : QRhiTexture::RGBA8;
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(assumedFormat, sz, 1, 1);
+ if (sampleCount > 1)
+ byteSize *= sampleCount;
+
+ startEntry(QRhiProfiler::NewRenderBuffer, ts.elapsed(), rb);
+ writeInt("type", type);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("transient_backing", transientBacking);
+ writeInt("winsys_backing", winSysBacking);
+ writeInt("approx_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseRenderBuffer(QRhiRenderBuffer *rb)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseRenderBuffer, ts.elapsed(), rb);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newTexture(QRhiTexture *tex, bool owns, int mipCount, int layerCount, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QRhiTexture::Format format = tex->format();
+ const QSize sz = tex->pixelSize();
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(format, sz, mipCount, layerCount);
+ if (sampleCount > 1)
+ byteSize *= sampleCount;
+
+ startEntry(QRhiProfiler::NewTexture, ts.elapsed(), tex);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("format", format);
+ writeInt("owns_native_resource", owns);
+ writeInt("mip_count", mipCount);
+ writeInt("layer_count", layerCount);
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("approx_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseTexture(QRhiTexture *tex)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseTexture, ts.elapsed(), tex);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newTextureStagingArea(QRhiTexture *tex, int slot, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewTextureStagingArea, ts.elapsed(), tex);
+ writeInt("slot", slot);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseTextureStagingArea(QRhiTexture *tex, int slot)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseTextureStagingArea, ts.elapsed(), tex);
+ writeInt("slot", slot);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::resizeSwapChain(QRhiSwapChain *sc, int bufferCount, int msaaBufferCount, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QSize sz = sc->currentPixelSize();
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(QRhiTexture::BGRA8, sz, 1, 1);
+ byteSize = byteSize * bufferCount + byteSize * msaaBufferCount * sampleCount;
+
+ startEntry(QRhiProfiler::ResizeSwapChain, ts.elapsed(), sc);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("buffer_count", bufferCount);
+ writeInt("msaa_buffer_count", msaaBufferCount);
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("approx_total_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseSwapChain(QRhiSwapChain *sc)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseSwapChain, ts.elapsed(), sc);
+ endEntry();
+}
+
+template<typename T>
+void calcTiming(QVector<T> *vec, T *minDelta, T *maxDelta, float *avgDelta)
+{
+ if (vec->isEmpty())
+ return;
+
+ *minDelta = *maxDelta = 0;
+ float totalDelta = 0;
+ for (T delta : qAsConst(*vec)) {
+ totalDelta += float(delta);
+ if (*minDelta == 0 || delta < *minDelta)
+ *minDelta = delta;
+ if (*maxDelta == 0 || delta > *maxDelta)
+ *maxDelta = delta;
+ }
+ *avgDelta = totalDelta / vec->count();
+
+ vec->clear();
+}
+
+void QRhiProfilerPrivate::beginSwapChainFrame(QRhiSwapChain *sc)
+{
+ Sc &scd(swapchains[sc]);
+ scd.beginToEndTimer.start();
+}
+
+void QRhiProfilerPrivate::endSwapChainFrame(QRhiSwapChain *sc, int frameCount)
+{
+ Sc &scd(swapchains[sc]);
+ if (!scd.frameToFrameRunning) {
+ scd.frameToFrameTimer.start();
+ scd.frameToFrameRunning = true;
+ return;
+ }
+
+ scd.frameToFrameSamples.append(scd.frameToFrameTimer.restart());
+ if (scd.frameToFrameSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.frameToFrameSamples,
+ &scd.frameToFrameTime.minTime, &scd.frameToFrameTime.maxTime, &scd.frameToFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::FrameToFrameTime, ts.elapsed(), sc);
+ writeInt("frames_since_resize", frameCount);
+ writeInt("min_ms_frame_delta", scd.frameToFrameTime.minTime);
+ writeInt("max_ms_frame_delta", scd.frameToFrameTime.maxTime);
+ writeFloat("Favg_ms_frame_delta", scd.frameToFrameTime.avgTime);
+ endEntry();
+ }
+ }
+
+ scd.beginToEndSamples.append(scd.beginToEndTimer.elapsed());
+ if (scd.beginToEndSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.beginToEndSamples,
+ &scd.beginToEndFrameTime.minTime, &scd.beginToEndFrameTime.maxTime, &scd.beginToEndFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::FrameBuildTime, ts.elapsed(), sc);
+ writeInt("frames_since_resize", frameCount);
+ writeInt("min_ms_frame_build", scd.beginToEndFrameTime.minTime);
+ writeInt("max_ms_frame_build", scd.beginToEndFrameTime.maxTime);
+ writeFloat("Favg_ms_frame_build", scd.beginToEndFrameTime.avgTime);
+ endEntry();
+ }
+ }
+}
+
+void QRhiProfilerPrivate::swapChainFrameGpuTime(QRhiSwapChain *sc, float gpuTime)
+{
+ Sc &scd(swapchains[sc]);
+ scd.gpuFrameSamples.append(gpuTime);
+ if (scd.gpuFrameSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.gpuFrameSamples,
+ &scd.gpuFrameTime.minTime, &scd.gpuFrameTime.maxTime, &scd.gpuFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::GpuFrameTime, ts.elapsed(), sc);
+ writeFloat("Fmin_ms_gpu_frame_time", scd.gpuFrameTime.minTime);
+ writeFloat("Fmax_ms_gpu_frame_time", scd.gpuFrameTime.maxTime);
+ writeFloat("Favg_ms_gpu_frame_time", scd.gpuFrameTime.avgTime);
+ endEntry();
+ }
+ }
+}
+
+void QRhiProfilerPrivate::newReadbackBuffer(quint64 id, QRhiResource *src, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewReadbackBuffer, ts.elapsed(), src);
+ writeInt("id", id);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseReadbackBuffer(quint64 id)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseReadbackBuffer, ts.elapsed(), nullptr);
+ writeInt("id", id);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::vmemStat(int realAllocCount, int subAllocCount, quint32 totalSize, quint32 unusedSize)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::GpuMemAllocStats, ts.elapsed(), nullptr);
+ writeInt("real_alloc_count", realAllocCount);
+ writeInt("sub_alloc_count", subAllocCount);
+ writeInt("total_size", totalSize);
+ writeInt("unused_size", unusedSize);
+ endEntry();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhiprofiler_p.h b/src/gui/rhi/qrhiprofiler_p.h
new file mode 100644
index 0000000000..89fd0a8798
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIPROFILER_H
+#define QRHIPROFILER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiProfilerPrivate;
+class QIODevice;
+
+class Q_GUI_EXPORT QRhiProfiler
+{
+public:
+ enum StreamOp {
+ NewBuffer = 1,
+ ReleaseBuffer,
+ NewBufferStagingArea,
+ ReleaseBufferStagingArea,
+ NewRenderBuffer,
+ ReleaseRenderBuffer,
+ NewTexture,
+ ReleaseTexture,
+ NewTextureStagingArea,
+ ReleaseTextureStagingArea,
+ ResizeSwapChain,
+ ReleaseSwapChain,
+ NewReadbackBuffer,
+ ReleaseReadbackBuffer,
+ GpuMemAllocStats,
+ GpuFrameTime,
+ FrameToFrameTime,
+ FrameBuildTime
+ };
+
+ ~QRhiProfiler();
+
+ void setDevice(QIODevice *device);
+
+ void addVMemAllocatorStats();
+
+ int frameTimingWriteInterval() const;
+ void setFrameTimingWriteInterval(int frameCount);
+
+ struct CpuTime {
+ qint64 minTime = 0;
+ qint64 maxTime = 0;
+ float avgTime = 0;
+ };
+
+ struct GpuTime {
+ float minTime = 0;
+ float maxTime = 0;
+ float avgTime = 0;
+ };
+
+ CpuTime frameToFrameTimes(QRhiSwapChain *sc) const;
+ CpuTime frameBuildTimes(QRhiSwapChain *sc) const; // beginFrame - endFrame
+ GpuTime gpuFrameTimes(QRhiSwapChain *sc) const;
+
+private:
+ Q_DISABLE_COPY(QRhiProfiler)
+ QRhiProfiler();
+ QRhiProfilerPrivate *d;
+ friend class QRhiImplementation;
+ friend class QRhiProfilerPrivate;
+};
+
+Q_DECLARE_TYPEINFO(QRhiProfiler::CpuTime, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiProfiler::GpuTime, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhiprofiler_p_p.h b/src/gui/rhi/qrhiprofiler_p_p.h
new file mode 100644
index 0000000000..49c6bd78ed
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler_p_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIPROFILER_P_H
+#define QRHIPROFILER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhiprofiler_p.h"
+#include <QElapsedTimer>
+#include <QHash>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiProfilerPrivate
+{
+public:
+ static QRhiProfilerPrivate *get(QRhiProfiler *p) { return p->d; }
+
+ void newBuffer(QRhiBuffer *buf, quint32 realSize, int backingGpuBufCount, int backingCpuBufCount);
+ void releaseBuffer(QRhiBuffer *buf);
+ void newBufferStagingArea(QRhiBuffer *buf, int slot, quint32 size);
+ void releaseBufferStagingArea(QRhiBuffer *buf, int slot);
+
+ void newRenderBuffer(QRhiRenderBuffer *rb, bool transientBacking, bool winSysBacking, int sampleCount);
+ void releaseRenderBuffer(QRhiRenderBuffer *rb);
+
+ void newTexture(QRhiTexture *tex, bool owns, int mipCount, int layerCount, int sampleCount);
+ void releaseTexture(QRhiTexture *tex);
+ void newTextureStagingArea(QRhiTexture *tex, int slot, quint32 size);
+ void releaseTextureStagingArea(QRhiTexture *tex, int slot);
+
+ void resizeSwapChain(QRhiSwapChain *sc, int bufferCount, int msaaBufferCount, int sampleCount);
+ void releaseSwapChain(QRhiSwapChain *sc);
+
+ void beginSwapChainFrame(QRhiSwapChain *sc);
+ void endSwapChainFrame(QRhiSwapChain *sc, int frameCount);
+ void swapChainFrameGpuTime(QRhiSwapChain *sc, float gpuTimeMs);
+
+ void newReadbackBuffer(quint64 id, QRhiResource *src, quint32 size);
+ void releaseReadbackBuffer(quint64 id);
+
+ void vmemStat(int realAllocCount, int subAllocCount, quint32 totalSize, quint32 unusedSize);
+
+ void startEntry(QRhiProfiler::StreamOp op, qint64 timestamp, QRhiResource *res);
+ void writeInt(const char *key, qint64 v);
+ void writeFloat(const char *key, float f);
+ void endEntry();
+
+ QRhiImplementation *rhiDWhenEnabled = nullptr;
+ QIODevice *outputDevice = nullptr;
+ QElapsedTimer ts;
+ QByteArray buf;
+ static const int DEFAULT_FRAME_TIMING_WRITE_INTERVAL = 120; // frames
+ int frameTimingWriteInterval = DEFAULT_FRAME_TIMING_WRITE_INTERVAL;
+ struct Sc {
+ Sc() {
+ frameToFrameSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ beginToEndSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ gpuFrameSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ }
+ QElapsedTimer frameToFrameTimer;
+ bool frameToFrameRunning = false;
+ QElapsedTimer beginToEndTimer;
+ QVector<qint64> frameToFrameSamples;
+ QVector<qint64> beginToEndSamples;
+ QVector<float> gpuFrameSamples;
+ QRhiProfiler::CpuTime frameToFrameTime;
+ QRhiProfiler::CpuTime beginToEndFrameTime;
+ QRhiProfiler::GpuTime gpuFrameTime;
+ };
+ QHash<QRhiSwapChain *, Sc> swapchains;
+};
+
+Q_DECLARE_TYPEINFO(QRhiProfilerPrivate::Sc, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
new file mode 100644
index 0000000000..1f80df6d0d
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -0,0 +1,5681 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhivulkan_p_p.h"
+#include "qrhivulkanext_p.h"
+
+#define VMA_IMPLEMENTATION
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_RECORDING_ENABLED 0
+#define VMA_DEDICATED_ALLOCATION 0
+#ifdef QT_DEBUG
+#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1
+#endif
+#include "vk_mem_alloc.h"
+
+#include <qmath.h>
+#include <QVulkanFunctions>
+#include <QVulkanWindow>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the
+ rendering thread to vsync. Textures and "static" buffers are device local,
+ and a separate, host visible staging buffer is used to upload data to them.
+ "Dynamic" buffers are in host visible memory and are duplicated (since there
+ can be 2 frames in flight). This is handled transparently to the application.
+*/
+
+/*!
+ \class QRhiVulkanInitParams
+ \inmodule QtRhi
+ \brief Vulkan specific initialization parameters.
+
+ A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to
+ the user to ensure this is available and initialized. This is typically
+ done in main() similarly to the following:
+
+ \badcode
+ int main(int argc, char **argv)
+ {
+ ...
+
+ QVulkanInstance inst;
+ #ifndef Q_OS_ANDROID
+ inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
+ #else
+ inst.setLayers(QByteArrayList()
+ << "VK_LAYER_GOOGLE_threading"
+ << "VK_LAYER_LUNARG_parameter_validation"
+ << "VK_LAYER_LUNARG_object_tracker"
+ << "VK_LAYER_LUNARG_core_validation"
+ << "VK_LAYER_LUNARG_image"
+ << "VK_LAYER_LUNARG_swapchain"
+ << "VK_LAYER_GOOGLE_unique_objects");
+ #endif
+ inst.setExtensions(QByteArrayList()
+ << "VK_KHR_get_physical_device_properties2");
+ if (!inst.create())
+ qFatal("Vulkan not available");
+
+ ...
+ }
+ \endcode
+
+ The example here has two optional aspects: it enables the
+ \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan
+ validation layers}, when they are available, and also enables the
+ VK_KHR_get_physical_device_properties2 extension (part of Vulkan 1.1), when
+ available. The former is useful during the development phase (remember that
+ QVulkanInstance conveniently redirects messages and warnings to qDebug).
+ Avoid enabling it in production builds, however. The latter is important in
+ order to make QRhi::CustomInstanceStepRate available with Vulkan since
+ VK_EXT_vertex_attribute_divisor (part of Vulkan 1.1) depends on it. It can
+ be omitted when instanced drawing with a non-one step rate is not used.
+
+ Once this is done, a Vulkan-based QRhi can be created by passing the
+ instance and a QWindow with its surface type set to
+ QSurface::VulkanSurface:
+
+ \badcode
+ QRhiVulkanInitParams params;
+ params.inst = vulkanInstance;
+ params.window = window;
+ rhi = QRhi::create(QRhi::Vulkan, &params);
+ \endcode
+
+ The window is optional and can be omitted. This is not recommended however
+ because there is then no way to ensure presenting is supported while
+ choosing a graphics queue.
+
+ \note Even when a window is specified, QRhiSwapChain objects can be created
+ for other windows as well, as long as they all have their
+ QWindow::surfaceType() set to QSurface::VulkanSurface.
+
+ \section2 Working with existing Vulkan devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Vulkan device. This can be achieved
+ by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create().
+
+ The physical device and device object must then be set to a non-null value.
+ In addition, either the graphics queue family index or the graphics queue
+ object itself is required. Prefer the former, whenever possible since
+ deducing the index is not possible afterwards. Optionally, an existing
+ command pool object can be specified as well, and, also optionally,
+ vmemAllocator can be used to share the same
+ \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan
+ memory allocator} between two QRhi instances.
+
+ The QRhi does not take ownership of any of the external objects.
+ */
+
+/*!
+ \class QRhiVulkanNativeHandles
+ \inmodule QtRhi
+ \brief Collects device, queue, and other Vulkan objects that are used by the QRhi.
+
+ \note Ownership of the Vulkan objects is never transferred.
+ */
+
+/*!
+ \class QRhiVulkanTextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Vulkan image object that is backing a QRhiTexture.
+
+ Importing and exporting Vulkan image objects that back a QRhiTexture when
+ running with the Vulkan backend is supported via this class. Ownership of
+ the Vulkan object is never transferred.
+
+ \note Memory allocation details are not exposed. This is intentional since
+ memory is typically suballocated from a bigger chunk of VkDeviceMemory, and
+ exposing the allocator details is not desirable for now.
+ */
+
+/*!
+ \class QRhiVulkanCommandBufferNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer.
+
+ \note The Vulkan command buffer object is only guaranteed to be valid, and
+ in recording state, while recording a frame. That is, between a
+ \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
+ \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+ \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
+ */
+
+static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+static QVulkanInstance *globalVulkanInstance;
+
+static void VKAPI_PTR wrap_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties)
+{
+ globalVulkanInstance->functions()->vkGetPhysicalDeviceProperties(physicalDevice, pProperties);
+}
+
+static void VKAPI_PTR wrap_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties)
+{
+ globalVulkanInstance->functions()->vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
+}
+
+static VkResult VKAPI_PTR wrap_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
+}
+
+void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkFreeMemory(device, memory, pAllocator);
+}
+
+VkResult VKAPI_PTR wrap_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkMapMemory(device, memory, offset, size, flags, ppData);
+}
+
+void VKAPI_PTR wrap_vkUnmapMemory(VkDevice device, VkDeviceMemory memory)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory);
+}
+
+VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkFlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
+}
+
+VkResult VKAPI_PTR wrap_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkInvalidateMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
+}
+
+VkResult VKAPI_PTR wrap_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkBindBufferMemory(device, buffer, memory, memoryOffset);
+}
+
+VkResult VKAPI_PTR wrap_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkBindImageMemory(device, image, memory, memoryOffset);
+}
+
+void VKAPI_PTR wrap_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkGetBufferMemoryRequirements(device, buffer, pMemoryRequirements);
+}
+
+void VKAPI_PTR wrap_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkGetImageMemoryRequirements(device, image, pMemoryRequirements);
+}
+
+VkResult VKAPI_PTR wrap_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
+}
+
+void VKAPI_PTR wrap_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkDestroyBuffer(device, buffer, pAllocator);
+}
+
+VkResult VKAPI_PTR wrap_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkCreateImage(device, pCreateInfo, pAllocator, pImage);
+}
+
+void VKAPI_PTR wrap_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkDestroyImage(device, image, pAllocator);
+}
+
+static inline VmaAllocation toVmaAllocation(QVkAlloc a)
+{
+ return reinterpret_cast<VmaAllocation>(a);
+}
+
+static inline VmaAllocator toVmaAllocator(QVkAllocator a)
+{
+ return reinterpret_cast<VmaAllocator>(a);
+}
+
+QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice)
+ : ofr(this)
+{
+ inst = params->inst;
+ maybeWindow = params->window; // may be null
+
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ physDev = importDevice->physDev;
+ dev = importDevice->dev;
+ if (physDev && dev) {
+ gfxQueueFamilyIdx = importDevice->gfxQueueFamilyIdx;
+ gfxQueue = importDevice->gfxQueue;
+ if (importDevice->cmdPool) {
+ importedCmdPool = true;
+ cmdPool = importDevice->cmdPool;
+ }
+ if (importDevice->vmemAllocator) {
+ importedAllocator = true;
+ allocator = importDevice->vmemAllocator;
+ }
+ } else {
+ qWarning("No (physical) Vulkan device is given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object,
+ size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage)
+{
+ Q_UNUSED(flags);
+ Q_UNUSED(objectType);
+ Q_UNUSED(object);
+ Q_UNUSED(location);
+ Q_UNUSED(messageCode);
+ Q_UNUSED(pLayerPrefix);
+
+ // Filter out certain misleading validation layer messages, as per
+ // VulkanMemoryAllocator documentation.
+ if (strstr(pMessage, "Mapping an image with layout")
+ && strstr(pMessage, "can result in undefined behavior if this memory is used by the device"))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool QRhiVulkan::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(inst);
+
+ globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application
+
+ f = inst->functions();
+
+ QVector<VkQueueFamilyProperties> queueFamilyProps;
+ auto queryQueueFamilyProps = [this, &queueFamilyProps] {
+ uint32_t queueCount = 0;
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
+ queueFamilyProps.resize(queueCount);
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
+ };
+
+ if (!importedDevice) {
+ uint32_t physDevCount = 0;
+ f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr);
+ if (!physDevCount) {
+ qWarning("No physical devices");
+ return false;
+ }
+ QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
+ VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data());
+ if (err != VK_SUCCESS || !physDevCount) {
+ qWarning("Failed to enumerate physical devices: %d", err);
+ return false;
+ }
+ int physDevIndex = -1;
+ int requestedPhysDevIndex = -1;
+ if (qEnvironmentVariableIsSet("QT_VK_PHYSICAL_DEVICE_INDEX"))
+ requestedPhysDevIndex = qEnvironmentVariableIntValue("QT_VK_PHYSICAL_DEVICE_INDEX");
+ for (uint32_t i = 0; i < physDevCount; ++i) {
+ f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
+ qDebug("Physical device %d: '%s' %d.%d.%d", i,
+ physDevProperties.deviceName,
+ VK_VERSION_MAJOR(physDevProperties.driverVersion),
+ VK_VERSION_MINOR(physDevProperties.driverVersion),
+ VK_VERSION_PATCH(physDevProperties.driverVersion));
+ if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) {
+ physDevIndex = i;
+ qDebug(" using this physical device");
+ }
+ }
+ if (physDevIndex < 0) {
+ qWarning("No matching physical device");
+ return false;
+ }
+ physDev = physDevs[physDevIndex];
+
+ queryQueueFamilyProps();
+
+ gfxQueue = VK_NULL_HANDLE;
+ gfxQueueFamilyIdx = -1;
+ int presQueueFamilyIdx = -1;
+ for (int i = 0; i < queueFamilyProps.count(); ++i) {
+ qDebug("queue family %d: flags=0x%x count=%d", i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount);
+ if (gfxQueueFamilyIdx == -1
+ && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+ && (!maybeWindow || inst->supportsPresent(physDev, i, maybeWindow)))
+ {
+ gfxQueueFamilyIdx = i;
+ }
+ }
+ if (gfxQueueFamilyIdx != -1) {
+ presQueueFamilyIdx = gfxQueueFamilyIdx;
+ } else {
+ // ###
+ qWarning("No graphics queue that can present. This is not supported atm.");
+ }
+ if (gfxQueueFamilyIdx == -1) {
+ qWarning("No graphics queue family found");
+ return false;
+ }
+ if (presQueueFamilyIdx == -1) {
+ qWarning("No present queue family found");
+ return false;
+ }
+
+ VkDeviceQueueCreateInfo queueInfo[2];
+ const float prio[] = { 0 };
+ memset(queueInfo, 0, sizeof(queueInfo));
+ queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queueInfo[0].queueFamilyIndex = gfxQueueFamilyIdx;
+ queueInfo[0].queueCount = 1;
+ queueInfo[0].pQueuePriorities = prio;
+ if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
+ queueInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queueInfo[1].queueFamilyIndex = presQueueFamilyIdx;
+ queueInfo[1].queueCount = 1;
+ queueInfo[1].pQueuePriorities = prio;
+ }
+
+ QVector<const char *> devLayers;
+ if (inst->layers().contains("VK_LAYER_LUNARG_standard_validation"))
+ devLayers.append("VK_LAYER_LUNARG_standard_validation");
+
+ uint32_t devExtCount = 0;
+ f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr);
+ QVector<VkExtensionProperties> devExts(devExtCount);
+ f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, devExts.data());
+ qDebug("%d device extensions available", devExts.count());
+
+ QVector<const char *> requestedDevExts;
+ requestedDevExts.append("VK_KHR_swapchain");
+
+ debugMarkersAvailable = false;
+ vertexAttribDivisorAvailable = false;
+ for (const VkExtensionProperties &ext : devExts) {
+ if (!strcmp(ext.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
+ debugMarkersAvailable = true;
+ } else if (!strcmp(ext.extensionName, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
+ if (inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2"))) {
+ requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
+ vertexAttribDivisorAvailable = true;
+ }
+ }
+ }
+
+ VkDeviceCreateInfo devInfo;
+ memset(&devInfo, 0, sizeof(devInfo));
+ devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ devInfo.queueCreateInfoCount = gfxQueueFamilyIdx == presQueueFamilyIdx ? 1 : 2;
+ devInfo.pQueueCreateInfos = queueInfo;
+ devInfo.enabledLayerCount = devLayers.count();
+ devInfo.ppEnabledLayerNames = devLayers.constData();
+ devInfo.enabledExtensionCount = requestedDevExts.count();
+ devInfo.ppEnabledExtensionNames = requestedDevExts.constData();
+
+ err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create device: %d", err);
+ return false;
+ }
+ }
+
+ df = inst->deviceFunctions(dev);
+
+ if (!importedCmdPool) {
+ VkCommandPoolCreateInfo poolInfo;
+ memset(&poolInfo, 0, sizeof(poolInfo));
+ poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
+ VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command pool: %d", err);
+ return false;
+ }
+ }
+
+ if (gfxQueueFamilyIdx != -1) {
+ // Will use one queue always, including when multiple QRhis use the
+ // same device. This has significant consequences, and cannot easily be
+ // changed (e.g. think pipeline barriers which create a dependency
+ // between commands submitted to a queue - with multiple queues
+ // additional synchronization would be needed)
+
+ if (!gfxQueue)
+ df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue);
+
+ if (queueFamilyProps.isEmpty())
+ queryQueueFamilyProps();
+
+ timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits;
+ }
+
+ f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties);
+ ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment;
+ // helps little with an optimal offset of 1 (on some drivers) when the spec
+ // elsewhere states that the minimum bufferOffset is 4...
+ texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment);
+
+ if (!importedAllocator) {
+ VmaVulkanFunctions afuncs;
+ afuncs.vkGetPhysicalDeviceProperties = wrap_vkGetPhysicalDeviceProperties;
+ afuncs.vkGetPhysicalDeviceMemoryProperties = wrap_vkGetPhysicalDeviceMemoryProperties;
+ afuncs.vkAllocateMemory = wrap_vkAllocateMemory;
+ afuncs.vkFreeMemory = wrap_vkFreeMemory;
+ afuncs.vkMapMemory = wrap_vkMapMemory;
+ afuncs.vkUnmapMemory = wrap_vkUnmapMemory;
+ afuncs.vkFlushMappedMemoryRanges = wrap_vkFlushMappedMemoryRanges;
+ afuncs.vkInvalidateMappedMemoryRanges = wrap_vkInvalidateMappedMemoryRanges;
+ afuncs.vkBindBufferMemory = wrap_vkBindBufferMemory;
+ afuncs.vkBindImageMemory = wrap_vkBindImageMemory;
+ afuncs.vkGetBufferMemoryRequirements = wrap_vkGetBufferMemoryRequirements;
+ afuncs.vkGetImageMemoryRequirements = wrap_vkGetImageMemoryRequirements;
+ afuncs.vkCreateBuffer = wrap_vkCreateBuffer;
+ afuncs.vkDestroyBuffer = wrap_vkDestroyBuffer;
+ afuncs.vkCreateImage = wrap_vkCreateImage;
+ afuncs.vkDestroyImage = wrap_vkDestroyImage;
+
+ VmaAllocatorCreateInfo allocatorInfo;
+ memset(&allocatorInfo, 0, sizeof(allocatorInfo));
+ allocatorInfo.physicalDevice = physDev;
+ allocatorInfo.device = dev;
+ allocatorInfo.pVulkanFunctions = &afuncs;
+ VmaAllocator vmaallocator;
+ VkResult err = vmaCreateAllocator(&allocatorInfo, &vmaallocator);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create allocator: %d", err);
+ return false;
+ }
+ allocator = vmaallocator;
+ }
+
+ inst->installDebugOutputFilter(qvk_debug_filter);
+
+ VkDescriptorPool pool;
+ VkResult err = createDescriptorPool(&pool);
+ if (err == VK_SUCCESS)
+ descriptorPools.append(pool);
+ else
+ qWarning("Failed to create initial descriptor pool: %d", err);
+
+ VkQueryPoolCreateInfo timestampQueryPoolInfo;
+ memset(&timestampQueryPoolInfo, 0, sizeof(timestampQueryPoolInfo));
+ timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+ timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
+ timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2;
+ err = df->vkCreateQueryPool(dev, &timestampQueryPoolInfo, nullptr, &timestampQueryPool);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create timestamp query pool: %d", err);
+ return false;
+ }
+ timestampQueryPoolMap.resize(QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair
+ timestampQueryPoolMap.fill(false);
+
+ if (debugMarkersAvailable) {
+ vkCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerBeginEXT"));
+ vkCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerEndEXT"));
+ vkCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerInsertEXT"));
+ vkDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkDebugMarkerSetObjectNameEXT"));
+ }
+
+ nativeHandlesStruct.physDev = physDev;
+ nativeHandlesStruct.dev = dev;
+ nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx;
+ nativeHandlesStruct.gfxQueue = gfxQueue;
+ nativeHandlesStruct.cmdPool = cmdPool;
+ nativeHandlesStruct.vmemAllocator = allocator;
+
+ return true;
+}
+
+void QRhiVulkan::destroy()
+{
+ if (!df)
+ return;
+
+ df->vkDeviceWaitIdle(dev);
+
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ if (ofr.cmdFence) {
+ df->vkDestroyFence(dev, ofr.cmdFence, nullptr);
+ ofr.cmdFence = VK_NULL_HANDLE;
+ }
+
+ if (ofr.cbWrapper.cb) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, &ofr.cbWrapper.cb);
+ ofr.cbWrapper.cb = VK_NULL_HANDLE;
+ }
+
+ if (pipelineCache) {
+ df->vkDestroyPipelineCache(dev, pipelineCache, nullptr);
+ pipelineCache = VK_NULL_HANDLE;
+ }
+
+ for (const DescriptorPoolData &pool : descriptorPools)
+ df->vkDestroyDescriptorPool(dev, pool.pool, nullptr);
+
+ descriptorPools.clear();
+
+ if (timestampQueryPool) {
+ df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr);
+ timestampQueryPool = VK_NULL_HANDLE;
+ }
+
+ if (!importedAllocator && allocator) {
+ vmaDestroyAllocator(toVmaAllocator(allocator));
+ allocator = nullptr;
+ }
+
+ if (!importedCmdPool && cmdPool) {
+ df->vkDestroyCommandPool(dev, cmdPool, nullptr);
+ cmdPool = VK_NULL_HANDLE;
+ }
+
+ if (!importedDevice && dev) {
+ df->vkDestroyDevice(dev, nullptr);
+ inst->resetDeviceFunctions(dev);
+ dev = VK_NULL_HANDLE;
+ }
+
+ f = nullptr;
+ df = nullptr;
+}
+
+VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool)
+{
+ VkDescriptorPoolSize descPoolSizes[] = {
+ { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, QVK_UNIFORM_BUFFERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, QVK_UNIFORM_BUFFERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL }
+ };
+ VkDescriptorPoolCreateInfo descPoolInfo;
+ memset(&descPoolInfo, 0, sizeof(descPoolInfo));
+ descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ // Do not enable vkFreeDescriptorSets - sets are never freed on their own
+ // (good so no trouble with fragmentation), they just deref their pool
+ // which is then reset at some point (or not).
+ descPoolInfo.flags = 0;
+ descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL;
+ descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]);
+ descPoolInfo.pPoolSizes = descPoolSizes;
+ return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool);
+}
+
+bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex)
+{
+ auto tryAllocate = [this, allocInfo, result](int poolIndex) {
+ allocInfo->descriptorPool = descriptorPools[poolIndex].pool;
+ VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result);
+ if (r == VK_SUCCESS)
+ descriptorPools[poolIndex].refCount += 1;
+ return r;
+ };
+
+ int lastPoolIdx = descriptorPools.count() - 1;
+ for (int i = lastPoolIdx; i >= 0; --i) {
+ if (descriptorPools[i].refCount == 0) {
+ df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0);
+ descriptorPools[i].allocedDescSets = 0;
+ }
+ if (descriptorPools[i].allocedDescSets + allocInfo->descriptorSetCount <= QVK_DESC_SETS_PER_POOL) {
+ VkResult err = tryAllocate(i);
+ if (err == VK_SUCCESS) {
+ descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount;
+ *resultPoolIndex = i;
+ return true;
+ }
+ }
+ }
+
+ VkDescriptorPool newPool;
+ VkResult poolErr = createDescriptorPool(&newPool);
+ if (poolErr == VK_SUCCESS) {
+ descriptorPools.append(newPool);
+ lastPoolIdx = descriptorPools.count() - 1;
+ VkResult err = tryAllocate(lastPoolIdx);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to allocate descriptor set from new pool too, giving up: %d", err);
+ return false;
+ }
+ descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount;
+ *resultPoolIndex = lastPoolIdx;
+ return true;
+ } else {
+ qWarning("Failed to allocate new descriptor pool: %d", poolErr);
+ return false;
+ }
+}
+
+static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
+ case QRhiTexture::BGRA8:
+ return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
+ case QRhiTexture::R8:
+ return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
+ case QRhiTexture::R16:
+ return VK_FORMAT_R16_UNORM;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return VK_FORMAT_R8_UNORM;
+
+ case QRhiTexture::RGBA16F:
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case QRhiTexture::RGBA32F:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+
+ case QRhiTexture::D16:
+ return VK_FORMAT_D16_UNORM;
+ case QRhiTexture::D32F:
+ return VK_FORMAT_D32_SFLOAT;
+
+ case QRhiTexture::BC1:
+ return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK;
+ case QRhiTexture::BC2:
+ return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK;
+ case QRhiTexture::BC3:
+ return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
+ case QRhiTexture::BC4:
+ return VK_FORMAT_BC4_UNORM_BLOCK;
+ case QRhiTexture::BC5:
+ return VK_FORMAT_BC5_UNORM_BLOCK;
+ case QRhiTexture::BC6H:
+ return VK_FORMAT_BC6H_UFLOAT_BLOCK;
+ case QRhiTexture::BC7:
+ return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK;
+
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
+
+ default:
+ Q_UNREACHABLE();
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
+static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags)
+{
+ switch (format) {
+ case VK_FORMAT_R8G8B8A8_UNORM:
+ return QRhiTexture::RGBA8;
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::RGBA8;
+ case VK_FORMAT_B8G8R8A8_UNORM:
+ return QRhiTexture::BGRA8;
+ case VK_FORMAT_B8G8R8A8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::BGRA8;
+ case VK_FORMAT_R8_UNORM:
+ return QRhiTexture::R8;
+ case VK_FORMAT_R8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::R8;
+ case VK_FORMAT_R16_UNORM:
+ return QRhiTexture::R16;
+ default: // this cannot assert, must warn and return unknown
+ qWarning("VkFormat %d is not a recognized uncompressed color format", format);
+ break;
+ }
+ return QRhiTexture::UnknownFormat;
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::Format::D32F:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+// Transient images ("render buffers") backed by lazily allocated memory are
+// managed manually without going through vk_mem_alloc since it does not offer
+// any support for such images. This should be ok since in practice there
+// should be very few of such images.
+
+uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
+{
+ VkPhysicalDeviceMemoryProperties physDevMemProps;
+ f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
+
+ VkMemoryRequirements memReq;
+ df->vkGetImageMemoryRequirements(dev, img, &memReq);
+ uint32_t memTypeIndex = uint32_t(-1);
+
+ if (memReq.memoryTypeBits) {
+ // Find a device local + lazily allocated, or at least device local memtype.
+ const VkMemoryType *memType = physDevMemProps.memoryTypes;
+ bool foundDevLocal = false;
+ for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
+ if (memReq.memoryTypeBits & (1 << i)) {
+ if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ if (!foundDevLocal) {
+ foundDevLocal = true;
+ memTypeIndex = i;
+ }
+ if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
+ memTypeIndex = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return memTypeIndex;
+}
+
+bool QRhiVulkan::createTransientImage(VkFormat format,
+ const QSize &pixelSize,
+ VkImageUsageFlags usage,
+ VkImageAspectFlags aspectMask,
+ VkSampleCountFlagBits samples,
+ VkDeviceMemory *mem,
+ VkImage *images,
+ VkImageView *views,
+ int count)
+{
+ VkMemoryRequirements memReq;
+ VkResult err;
+
+ for (int i = 0; i < count; ++i) {
+ VkImageCreateInfo imgInfo;
+ memset(&imgInfo, 0, sizeof(imgInfo));
+ imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imgInfo.imageType = VK_IMAGE_TYPE_2D;
+ imgInfo.format = format;
+ imgInfo.extent.width = pixelSize.width();
+ imgInfo.extent.height = pixelSize.height();
+ imgInfo.extent.depth = 1;
+ imgInfo.mipLevels = imgInfo.arrayLayers = 1;
+ imgInfo.samples = samples;
+ imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
+ imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image: %d", err);
+ return false;
+ }
+
+ // Assume the reqs are the same since the images are same in every way.
+ // Still, call GetImageMemReq for every image, in order to prevent the
+ // validation layer from complaining.
+ df->vkGetImageMemoryRequirements(dev, images[i], &memReq);
+ }
+
+ VkMemoryAllocateInfo memInfo;
+ memset(&memInfo, 0, sizeof(memInfo));
+ memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * count;
+
+ uint32_t startIndex = 0;
+ do {
+ memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
+ if (memInfo.memoryTypeIndex == uint32_t(-1)) {
+ qWarning("No suitable memory type found");
+ return false;
+ }
+ startIndex = memInfo.memoryTypeIndex + 1;
+ err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem);
+ if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
+ qWarning("Failed to allocate image memory: %d", err);
+ return false;
+ }
+ } while (err != VK_SUCCESS);
+
+ VkDeviceSize ofs = 0;
+ for (int i = 0; i < count; ++i) {
+ err = df->vkBindImageMemory(dev, images[i], *mem, ofs);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to bind image memory: %d", err);
+ return false;
+ }
+ ofs += aligned(memReq.size, memReq.alignment);
+
+ VkImageViewCreateInfo imgViewInfo;
+ memset(&imgViewInfo, 0, sizeof(imgViewInfo));
+ imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ imgViewInfo.image = images[i];
+ imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ imgViewInfo.format = format;
+ imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ imgViewInfo.subresourceRange.aspectMask = aspectMask;
+ imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
+
+ err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image view: %d", err);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+VkFormat QRhiVulkan::optimalDepthStencilFormat()
+{
+ if (optimalDsFormat != VK_FORMAT_UNDEFINED)
+ return optimalDsFormat;
+
+ const VkFormat dsFormatCandidates[] = {
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D16_UNORM_S8_UINT
+ };
+ const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
+ int dsFormatIdx = 0;
+ while (dsFormatIdx < dsFormatCandidateCount) {
+ optimalDsFormat = dsFormatCandidates[dsFormatIdx];
+ VkFormatProperties fmtProp;
+ f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp);
+ if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+ break;
+ ++dsFormatIdx;
+ }
+ if (dsFormatIdx == dsFormatCandidateCount)
+ qWarning("Failed to find an optimal depth-stencil format");
+
+ return optimalDsFormat;
+}
+
+bool QRhiVulkan::createDefaultRenderPass(VkRenderPass *rp, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat)
+{
+ VkAttachmentDescription attDesc[3];
+ memset(attDesc, 0, sizeof(attDesc));
+
+ // attachment list layout is color (1), ds (0-1), resolve (0-1)
+
+ attDesc[0].format = colorFormat;
+ attDesc[0].samples = samples;
+ attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[0].storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[0].finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ // clear on load + no store + lazy alloc + transient image should play
+ // nicely with tiled GPUs (no physical backing necessary for ds buffer)
+ attDesc[1].format = optimalDepthStencilFormat();
+ attDesc[1].samples = samples;
+ attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ attDesc[2].format = colorFormat;
+ attDesc[2].samples = VK_SAMPLE_COUNT_1_BIT;
+ attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ }
+
+ VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+ VkAttachmentReference resolveRef = { 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+ VkSubpassDescription subpassDesc;
+ memset(&subpassDesc, 0, sizeof(subpassDesc));
+ subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpassDesc.colorAttachmentCount = 1;
+ subpassDesc.pColorAttachments = &colorRef;
+ subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
+
+ // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own.
+ VkSubpassDependency subpassDep;
+ memset(&subpassDep, 0, sizeof(subpassDep));
+ subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL;
+ subpassDep.dstSubpass = 0;
+ subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ subpassDep.srcAccessMask = 0;
+ subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+ VkRenderPassCreateInfo rpInfo;
+ memset(&rpInfo, 0, sizeof(rpInfo));
+ rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rpInfo.attachmentCount = 1;
+ rpInfo.pAttachments = attDesc;
+ rpInfo.subpassCount = 1;
+ rpInfo.pSubpasses = &subpassDesc;
+ rpInfo.dependencyCount = 1;
+ rpInfo.pDependencies = &subpassDep;
+
+ if (hasDepthStencil)
+ rpInfo.attachmentCount += 1;
+
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ rpInfo.attachmentCount += 1;
+ subpassDesc.pResolveAttachments = &resolveRef;
+ }
+
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
+ const QVector<QRhiColorAttachment> &colorAttachments,
+ bool preserveColor,
+ bool preserveDs,
+ QRhiRenderBuffer *depthStencilBuffer,
+ QRhiTexture *depthTexture)
+{
+ QVarLengthArray<VkAttachmentDescription, 8> attDescs;
+ QVarLengthArray<VkAttachmentReference, 8> colorRefs;
+ QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
+ const int colorAttCount = colorAttachments.count();
+
+ // attachment list layout is color (0-8), ds (0-1), resolve (0-8)
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachments[i].texture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat;
+ const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples;
+
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = vkformat;
+ attDesc.samples = samples;
+ attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc.storeOp = colorAttachments[i].resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT
+ attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+
+ const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ colorRefs.append(ref);
+ }
+
+ const bool hasDepthStencil = depthStencilBuffer || depthTexture;
+ if (hasDepthStencil) {
+ const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat
+ : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat;
+ const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples
+ : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples;
+ const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
+ const VkAttachmentStoreOp storeOp = depthTexture ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = dsFormat;
+ attDesc.samples = samples;
+ attDesc.loadOp = loadOp;
+ attDesc.storeOp = storeOp;
+ attDesc.stencilLoadOp = loadOp;
+ attDesc.stencilStoreOp = storeOp;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+ }
+ VkAttachmentReference dsRef = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ if (colorAttachments[i].resolveTexture()) {
+ QVkTexture *rtexD = QRHI_RES(QVkTexture, colorAttachments[i].resolveTexture());
+ if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
+ qWarning("Resolving into a multisample texture is not supported");
+
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = rtexD->vkformat;
+ attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+ attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
+ attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+
+ const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ resolveRefs.append(ref);
+ } else {
+ const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ resolveRefs.append(ref);
+ }
+ }
+
+ VkSubpassDescription subpassDesc;
+ memset(&subpassDesc, 0, sizeof(subpassDesc));
+ subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpassDesc.colorAttachmentCount = colorRefs.count();
+ Q_ASSERT(colorRefs.count() == resolveRefs.count());
+ subpassDesc.pColorAttachments = !colorRefs.isEmpty() ? colorRefs.constData() : nullptr;
+ subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
+ subpassDesc.pResolveAttachments = !resolveRefs.isEmpty() ? resolveRefs.constData() : nullptr;
+
+ VkRenderPassCreateInfo rpInfo;
+ memset(&rpInfo, 0, sizeof(rpInfo));
+ rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rpInfo.attachmentCount = attDescs.count();
+ rpInfo.pAttachments = attDescs.constData();
+ rpInfo.subpassCount = 1;
+ rpInfo.pSubpasses = &subpassDesc;
+ // don't yet know the correct initial/final access and stage stuff for the
+ // implicit deps at this point, so leave it to the resource tracking to
+ // generate barriers
+
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
+{
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ if (swapChainD->pixelSize.isEmpty()) {
+ qWarning("Surface size is 0, cannot create swapchain");
+ return false;
+ }
+
+ df->vkDeviceWaitIdle(dev);
+
+ if (!vkCreateSwapchainKHR) {
+ vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
+ vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
+ vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
+ vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
+ vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
+ if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) {
+ qWarning("Swapchain functions not available");
+ return false;
+ }
+ }
+
+ VkSurfaceCapabilitiesKHR surfaceCaps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps);
+ quint32 reqBufferCount;
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::MinimalBufferCount)) {
+ reqBufferCount = qMax<quint32>(2, surfaceCaps.minImageCount);
+ } else {
+ const quint32 maxBuffers = QVkSwapChain::MAX_BUFFER_COUNT;
+ if (surfaceCaps.maxImageCount)
+ reqBufferCount = qMax(qMin(surfaceCaps.maxImageCount, maxBuffers), surfaceCaps.minImageCount);
+ else
+ reqBufferCount = qMax<quint32>(2, surfaceCaps.minImageCount);
+ }
+
+ VkSurfaceTransformFlagBitsKHR preTransform =
+ (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
+ ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
+ : surfaceCaps.currentTransform;
+
+ VkCompositeAlphaFlagBitsKHR compositeAlpha =
+ (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
+ ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
+ : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasPreMulAlpha)
+ && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR))
+ {
+ compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
+ }
+
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasNonPreMulAlpha)
+ && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR))
+ {
+ compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
+ }
+
+ VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+ if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
+ usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+
+ VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) {
+ if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR))
+ presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+ else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
+ presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
+ }
+
+ // If the surface is different than before, then passing in the old
+ // swapchain associated with the old surface can fail the swapchain
+ // creation. (for example, Android loses the surface when backgrounding and
+ // restoring applications, and it also enforces failing swapchain creation
+ // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided)
+ const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface;
+
+ qDebug("Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d",
+ reuseExisting ? "recycled" : "new",
+ reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode);
+
+ VkSwapchainCreateInfoKHR swapChainInfo;
+ memset(&swapChainInfo, 0, sizeof(swapChainInfo));
+ swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ swapChainInfo.surface = swapChainD->surface;
+ swapChainInfo.minImageCount = reqBufferCount;
+ swapChainInfo.imageFormat = swapChainD->colorFormat;
+ swapChainInfo.imageColorSpace = swapChainD->colorSpace;
+ swapChainInfo.imageExtent = VkExtent2D { uint32_t(swapChainD->pixelSize.width()), uint32_t(swapChainD->pixelSize.height()) };
+ swapChainInfo.imageArrayLayers = 1;
+ swapChainInfo.imageUsage = usage;
+ swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ swapChainInfo.preTransform = preTransform;
+ swapChainInfo.compositeAlpha = compositeAlpha;
+ swapChainInfo.presentMode = presentMode;
+ swapChainInfo.clipped = true;
+ swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE;
+
+ VkSwapchainKHR newSwapChain;
+ VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create swapchain: %d", err);
+ return false;
+ }
+
+ if (swapChainD->sc)
+ releaseSwapChainResources(swapChain);
+
+ swapChainD->sc = newSwapChain;
+ swapChainD->lastConnectedSurface = swapChainD->surface;
+
+ quint32 actualSwapChainBufferCount = 0;
+ err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr);
+ if (err != VK_SUCCESS || actualSwapChainBufferCount < 2) {
+ qWarning("Failed to get swapchain images: %d (count=%u)", err, actualSwapChainBufferCount);
+ return false;
+ }
+
+ if (actualSwapChainBufferCount > QVkSwapChain::MAX_BUFFER_COUNT) {
+ qWarning("Too many swapchain buffers (%u)", actualSwapChainBufferCount);
+ return false;
+ }
+ if (actualSwapChainBufferCount != reqBufferCount)
+ qDebug("Actual swapchain buffer count is %u", actualSwapChainBufferCount);
+ swapChainD->bufferCount = actualSwapChainBufferCount;
+
+ VkImage swapChainImages[QVkSwapChain::MAX_BUFFER_COUNT];
+ err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to get swapchain images: %d", err);
+ return false;
+ }
+
+ VkImage msaaImages[QVkSwapChain::MAX_BUFFER_COUNT];
+ VkImageView msaaViews[QVkSwapChain::MAX_BUFFER_COUNT];
+ if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ if (!createTransientImage(swapChainD->colorFormat,
+ swapChainD->pixelSize,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ swapChainD->samples,
+ &swapChainD->msaaImageMem,
+ msaaImages,
+ msaaViews,
+ swapChainD->bufferCount))
+ {
+ qWarning("Failed to create transient image for MSAA color buffer");
+ return false;
+ }
+ }
+
+ VkFenceCreateInfo fenceInfo;
+ memset(&fenceInfo, 0, sizeof(fenceInfo));
+ fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+ for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
+ image.image = swapChainImages[i];
+ if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ image.msaaImage = msaaImages[i];
+ image.msaaImageView = msaaViews[i];
+ }
+
+ VkImageViewCreateInfo imgViewInfo;
+ memset(&imgViewInfo, 0, sizeof(imgViewInfo));
+ imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ imgViewInfo.image = swapChainImages[i];
+ imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ imgViewInfo.format = swapChainD->colorFormat;
+ imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
+ err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create swapchain image view %d: %d", i, err);
+ return false;
+ }
+ }
+
+ swapChainD->currentImageIndex = 0;
+
+ VkSemaphoreCreateInfo semInfo;
+ memset(&semInfo, 0, sizeof(semInfo));
+ semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
+
+ frame.imageAcquired = false;
+ frame.imageSemWaitable = false;
+
+ df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.imageFence);
+ frame.imageFenceWaitable = true; // fence was created in signaled state
+
+ df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
+ df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
+
+ err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command buffer fence: %d", err);
+ return false;
+ }
+ frame.cmdFenceWaitable = true; // fence was created in signaled state
+ }
+
+ swapChainD->currentFrameSlot = 0;
+
+ return true;
+}
+
+void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
+{
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+
+ if (swapChainD->sc == VK_NULL_HANDLE)
+ return;
+
+ df->vkDeviceWaitIdle(dev);
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
+ if (frame.cmdFence) {
+ if (frame.cmdFenceWaitable)
+ df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkDestroyFence(dev, frame.cmdFence, nullptr);
+ frame.cmdFence = VK_NULL_HANDLE;
+ frame.cmdFenceWaitable = false;
+ }
+ if (frame.imageFence) {
+ if (frame.imageFenceWaitable)
+ df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
+ df->vkDestroyFence(dev, frame.imageFence, nullptr);
+ frame.imageFence = VK_NULL_HANDLE;
+ frame.imageFenceWaitable = false;
+ }
+ if (frame.imageSem) {
+ df->vkDestroySemaphore(dev, frame.imageSem, nullptr);
+ frame.imageSem = VK_NULL_HANDLE;
+ }
+ if (frame.drawSem) {
+ df->vkDestroySemaphore(dev, frame.drawSem, nullptr);
+ frame.drawSem = VK_NULL_HANDLE;
+ }
+ if (frame.cmdBuf) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
+ frame.cmdBuf = VK_NULL_HANDLE;
+ }
+ }
+
+ for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
+ if (image.fb) {
+ df->vkDestroyFramebuffer(dev, image.fb, nullptr);
+ image.fb = VK_NULL_HANDLE;
+ }
+ if (image.imageView) {
+ df->vkDestroyImageView(dev, image.imageView, nullptr);
+ image.imageView = VK_NULL_HANDLE;
+ }
+ if (image.msaaImageView) {
+ df->vkDestroyImageView(dev, image.msaaImageView, nullptr);
+ image.msaaImageView = VK_NULL_HANDLE;
+ }
+ if (image.msaaImage) {
+ df->vkDestroyImage(dev, image.msaaImage, nullptr);
+ image.msaaImage = VK_NULL_HANDLE;
+ }
+ }
+
+ if (swapChainD->msaaImageMem) {
+ df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr);
+ swapChainD->msaaImageMem = VK_NULL_HANDLE;
+ }
+
+ vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr);
+ swapChainD->sc = VK_NULL_HANDLE;
+
+ // NB! surface and similar must remain intact
+}
+
+static inline bool checkDeviceLost(VkResult err)
+{
+ if (err == VK_ERROR_DEVICE_LOST) {
+ qWarning("Device lost");
+ return true;
+ }
+ return false;
+}
+
+QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ if (!frame.imageAcquired) {
+ // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
+ // (note that we are using FIFO mode -> vsync)
+ if (frame.imageFenceWaitable) {
+ df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &frame.imageFence);
+ frame.imageFenceWaitable = false;
+ }
+
+ // move on to next swapchain image
+ VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX,
+ frame.imageSem, frame.imageFence, &frame.imageIndex);
+ if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
+ swapChainD->currentImageIndex = frame.imageIndex;
+ frame.imageSemWaitable = true;
+ frame.imageAcquired = true;
+ frame.imageFenceWaitable = true;
+ } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ return QRhi::FrameOpSwapChainOutOfDate;
+ } else {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to acquire next swapchain image: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ // Make sure the previous commands for the same image have finished. (note
+ // that this is based on the fence from the command buffer submit, nothing
+ // to do with the Present)
+ //
+ // Do this also for any other swapchain's commands with the same frame slot
+ // While this reduces concurrency, it keeps resource usage safe: swapchain
+ // A starting its frame 0, followed by swapchain B starting its own frame 0
+ // will make B wait for A's frame 0 commands, so if a resource is written
+ // in B's frame or when B checks for pending resource releases, that won't
+ // mess up A's in-flight commands (as they are not in flight anymore).
+ waitCommandCompletion(swapChainD->currentFrameSlot);
+
+ // Now is the time to read the timestamps for the previous frame for this slot.
+ if (frame.timestampQueryIndex >= 0) {
+ quint64 timestamp[2] = { 0, 0 };
+ VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, frame.timestampQueryIndex, 2,
+ 2 * sizeof(quint64), timestamp, sizeof(quint64), VK_QUERY_RESULT_64_BIT);
+ timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2);
+ frame.timestampQueryIndex = -1;
+ if (err == VK_SUCCESS) {
+ quint64 mask = 0;
+ for (quint64 i = 0; i < timestampValidBits; i += 8)
+ mask |= 0xFFULL << i;
+ const quint64 ts0 = timestamp[0] & mask;
+ const quint64 ts1 = timestamp[1] & mask;
+ const float nsecsPerTick = physDevProperties.limits.timestampPeriod;
+ if (!qFuzzyIsNull(nsecsPerTick)) {
+ const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f;
+ // now we have the gpu time for the previous frame for this slot, report it
+ // (does not matter that it is not for this frame)
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs));
+ }
+ } else {
+ qWarning("Failed to query timestamp: %d", err);
+ }
+ }
+
+ // build new draw command buffer
+ QRhi::FrameOpResult cbres = startCommandBuffer(&frame.cmdBuf);
+ if (cbres != QRhi::FrameOpSuccess)
+ return cbres;
+
+ // when profiling is enabled, pick a free query (pair) from the pool
+ int timestampQueryIdx = -1;
+ if (profilerPrivateOrNull()) {
+ for (int i = 0; i < timestampQueryPoolMap.count(); ++i) {
+ if (!timestampQueryPoolMap.testBit(i)) {
+ timestampQueryPoolMap.setBit(i);
+ timestampQueryIdx = i * 2;
+ break;
+ }
+ }
+ }
+ if (timestampQueryIdx >= 0) {
+ df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, timestampQueryIdx, 2);
+ // record timestamp at the start of the command buffer
+ df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ timestampQueryPool, timestampQueryIdx);
+ frame.timestampQueryIndex = timestampQueryIdx;
+ }
+
+ swapChainD->cbWrapper.cb = frame.cmdBuf;
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
+ swapChainD->rtWrapper.d.fb = image.fb;
+
+ currentFrameSlot = swapChainD->currentFrameSlot;
+ currentSwapChain = swapChainD;
+ if (swapChainD->ds)
+ swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
+
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ prepareNewFrame(&swapChainD->cbWrapper);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_ASSERT(inFrame);
+ inFrame = false;
+
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ recordCommandBuffer(&swapChainD->cbWrapper);
+
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]);
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
+
+ if (image.transferSource) {
+ // was used in a readback as transfer source, go back to presentable layout
+ VkImageMemoryBarrier presTrans;
+ memset(&presTrans, 0, sizeof(presTrans));
+ presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ presTrans.image = image.image;
+ presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
+ df->vkCmdPipelineBarrier(frame.cmdBuf,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &presTrans);
+ image.transferSource = false;
+ }
+
+ // record another timestamp, when enabled
+ if (frame.timestampQueryIndex >= 0) {
+ df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+ timestampQueryPool, frame.timestampQueryIndex + 1);
+ }
+
+ // stop recording and submit to the queue
+ Q_ASSERT(!frame.cmdFenceWaitable);
+ const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(frame.cmdBuf,
+ frame.cmdFence,
+ frame.imageSemWaitable ? &frame.imageSem : nullptr,
+ needsPresent ? &frame.drawSem : nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+
+ frame.imageSemWaitable = false;
+ frame.cmdFenceWaitable = true;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the Present
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (needsPresent) {
+ // add the Present to the queue
+ VkPresentInfoKHR presInfo;
+ memset(&presInfo, 0, sizeof(presInfo));
+ presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ presInfo.swapchainCount = 1;
+ presInfo.pSwapchains = &swapChainD->sc;
+ presInfo.pImageIndices = &swapChainD->currentImageIndex;
+ presInfo.waitSemaphoreCount = 1;
+ presInfo.pWaitSemaphores = &frame.drawSem; // gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
+
+ VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo);
+ if (err != VK_SUCCESS) {
+ if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ return QRhi::FrameOpSwapChainOutOfDate;
+ } else if (err != VK_SUBOPTIMAL_KHR) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to present: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ // mark the current swapchain buffer as unused from our side
+ frame.imageAcquired = false;
+ // and move on to the next buffer
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
+ }
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb)
+{
+ Q_ASSERT(!inFrame);
+ inFrame = true;
+
+ // Now is the time to do things for frame N-F, where N is the current one,
+ // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that
+ // frame has completed on the GPU (due to the fence wait in beginFrame). To
+ // decide if something is safe to handle now a simple "lastActiveFrameSlot
+ // == currentFrameSlot" is sufficient (remember that e.g. with F==2
+ // currentFrameSlot goes 0, 1, 0, 1, 0, ...)
+ //
+ // With multiple swapchains on the same QRhi things get more convoluted
+ // (and currentFrameSlot strictly alternating is not true anymore) but
+ // beginNonWrapperFrame() solves that by blocking as necessary so the rest
+ // here is safe regardless.
+
+ executeDeferredReleases();
+
+ QRHI_RES(QVkCommandBuffer, cb)->resetState();
+
+ finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
+}
+
+QRhi::FrameOpResult QRhiVulkan::startCommandBuffer(VkCommandBuffer *cb)
+{
+ if (*cb) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, cb);
+ *cb = VK_NULL_HANDLE;
+ }
+
+ VkCommandBufferAllocateInfo cmdBufInfo;
+ memset(&cmdBufInfo, 0, sizeof(cmdBufInfo));
+ cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cmdBufInfo.commandPool = cmdPool;
+ cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cmdBufInfo.commandBufferCount = 1;
+
+ VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to allocate frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ VkCommandBufferBeginInfo cmdBufBeginInfo;
+ memset(&cmdBufBeginInfo, 0, sizeof(cmdBufBeginInfo));
+ cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+ err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to begin frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endAndSubmitCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
+ VkSemaphore *waitSem, VkSemaphore *signalSem)
+{
+ VkResult err = df->vkEndCommandBuffer(cb);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to end frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ VkSubmitInfo submitInfo;
+ memset(&submitInfo, 0, sizeof(submitInfo));
+ submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &cb;
+ if (waitSem) {
+ submitInfo.waitSemaphoreCount = 1;
+ submitInfo.pWaitSemaphores = waitSem;
+ }
+ if (signalSem) {
+ submitInfo.signalSemaphoreCount = 1;
+ submitInfo.pSignalSemaphores = signalSem;
+ }
+ VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ submitInfo.pWaitDstStageMask = &psf;
+
+ err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to submit to graphics queue: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiVulkan::waitCommandCompletion(int frameSlot)
+{
+ for (QVkSwapChain *sc : qAsConst(swapchains)) {
+ QVkSwapChain::FrameResources &frame(sc->frameRes[frameSlot]);
+ if (frame.cmdFenceWaitable) {
+ df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &frame.cmdFence);
+ frame.cmdFenceWaitable = false;
+ }
+ }
+}
+
+QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ QRhi::FrameOpResult cbres = startCommandBuffer(&ofr.cbWrapper.cb);
+ if (cbres != QRhi::FrameOpSuccess)
+ return cbres;
+
+ // Switch to the next slot manually. Swapchains do not know about this
+ // which is good. So for example a - unusual but possible - onscreen,
+ // onscreen, offscreen, onscreen, onscreen, onscreen sequence of
+ // begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the
+ // offscreen frame is synchronous in the sense that we wait for execution
+ // to complete in endFrame, and so no resources used in that frame are busy
+ // anymore in the next frame.
+ currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
+ // except that this gets complicated with multiple swapchains so make sure
+ // any pending commands have finished for the frame slot we are going to use
+ if (swapchains.count() > 1)
+ waitCommandCompletion(currentFrameSlot);
+
+ prepareNewFrame(&ofr.cbWrapper);
+ ofr.active = true;
+
+ *cb = &ofr.cbWrapper;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame()
+{
+ Q_ASSERT(inFrame);
+ inFrame = false;
+ Q_ASSERT(ofr.active);
+ ofr.active = false;
+
+ recordCommandBuffer(&ofr.cbWrapper);
+
+ if (!ofr.cmdFence) {
+ VkFenceCreateInfo fenceInfo;
+ memset(&fenceInfo, 0, sizeof(fenceInfo));
+ fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command buffer fence: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(ofr.cbWrapper.cb, ofr.cmdFence, nullptr, nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+
+ // wait for completion
+ df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &ofr.cmdFence);
+
+ // Here we know that executing the host-side reads for this (or any
+ // previous) frame is safe since we waited for completion above.
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::finish()
+{
+ Q_ASSERT(!inPass);
+
+ QVkSwapChain *swapChainD = nullptr;
+ if (inFrame) {
+ // There is either a swapchain or an offscreen frame on-going.
+ // End command recording and submit what we have.
+ VkCommandBuffer cb;
+ if (ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ recordCommandBuffer(&ofr.cbWrapper);
+ cb = ofr.cbWrapper.cb;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = currentSwapChain;
+ recordCommandBuffer(&swapChainD->cbWrapper);
+ cb = swapChainD->cbWrapper.cb;
+ }
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(cb, VK_NULL_HANDLE, nullptr, nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+ }
+
+ df->vkQueueWaitIdle(gfxQueue);
+
+ if (inFrame) {
+ // Allocate and begin recording on a new command buffer.
+ if (ofr.active)
+ startCommandBuffer(&ofr.cbWrapper.cb);
+ else
+ startCommandBuffer(&swapChainD->frameRes[swapChainD->currentFrameSlot].cmdBuf);
+ }
+
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage)
+{
+ QRhiPassResourceTracker::UsageState u;
+ u.layout = 0; // unused with buffers
+ u.access = bufUsage.access;
+ u.stage = bufUsage.stage;
+ return u;
+}
+
+static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage)
+{
+ QRhiPassResourceTracker::UsageState u;
+ u.layout = texUsage.layout;
+ u.access = texUsage.access;
+ u.stage = texUsage.stage;
+ return u;
+}
+
+void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD)
+{
+ rtD->lastActiveFrameSlot = currentFrameSlot;
+ rtD->d.rp->lastActiveFrameSlot = currentFrameSlot;
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+ const QVector<QRhiColorAttachment> colorAttachments = rtD->m_desc.colorAttachments();
+ for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachment.texture());
+ QVkTexture *resolveTexD = QRHI_RES(QVkTexture, colorAttachment.resolveTexture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachment.renderBuffer());
+ if (texD) {
+ trackedRegisterTexture(&passResTracker, texD,
+ QRhiPassResourceTracker::TexColorOutput,
+ QRhiPassResourceTracker::TexColorOutputStage);
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (rbD) {
+ // Won't register rbD->backingTexture because it cannot be used for
+ // anything in a renderpass, its use makes only sense in
+ // combination with a resolveTexture.
+ rbD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ if (resolveTexD) {
+ trackedRegisterTexture(&passResTracker, resolveTexD,
+ QRhiPassResourceTracker::TexColorOutput,
+ QRhiPassResourceTracker::TexColorOutputStage);
+ resolveTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+ if (rtD->m_desc.depthStencilBuffer())
+ QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtD->m_desc.depthTexture()) {
+ QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture());
+ trackedRegisterTexture(&passResTracker, depthTexD,
+ QRhiPassResourceTracker::TexDepthOutput,
+ QRhiPassResourceTracker::TexDepthOutputStage);
+ depthTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+}
+
+void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+
+ enqueueResourceUpdates(QRHI_RES(QVkCommandBuffer, cb), resourceUpdates);
+}
+
+void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inFrame && !inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+
+ // Insert a TransitionPassResources into the command stream, pointing to
+ // the tracker this pass is going to use. That's how we generate the
+ // barriers later during recording the real VkCommandBuffer, right before
+ // the vkCmdBeginRenderPass.
+ enqueueTransitionPassResources(cbD);
+
+ QVkRenderTargetData *rtD = nullptr;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = &QRHI_RES(QVkReferenceRenderTarget, rt)->d;
+ rtD->rp->lastActiveFrameSlot = currentFrameSlot;
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt);
+ rtD = &rtTex->d;
+ activateTextureRenderTarget(cbD, rtTex);
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ cbD->currentTarget = rt;
+
+ // No copy operations or image layout transitions allowed after this point
+ // (up until endPass) as we are going to begin the renderpass.
+
+ VkRenderPassBeginInfo rpBeginInfo;
+ memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
+ rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ rpBeginInfo.renderPass = rtD->rp->rp;
+ rpBeginInfo.framebuffer = rtD->fb;
+ rpBeginInfo.renderArea.extent.width = rtD->pixelSize.width();
+ rpBeginInfo.renderArea.extent.height = rtD->pixelSize.height();
+
+ QVarLengthArray<VkClearValue, 4> cvs;
+ for (int i = 0; i < rtD->colorAttCount; ++i) {
+ VkClearValue cv;
+ cv.color = { { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
+ float(colorClearValue.alphaF()) } };
+ cvs.append(cv);
+ }
+ for (int i = 0; i < rtD->dsAttCount; ++i) {
+ VkClearValue cv;
+ cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
+ cvs.append(cv);
+ }
+ for (int i = 0; i < rtD->resolveAttCount; ++i) {
+ VkClearValue cv;
+ cv.color = { { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
+ float(colorClearValue.alphaF()) } };
+ cvs.append(cv);
+ }
+ rpBeginInfo.clearValueCount = cvs.count();
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass;
+ cmd.args.beginRenderPass.desc = rpBeginInfo;
+ cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.count();
+ cbD->pools.clearValue.append(cvs.constData(), cvs.count());
+ cbD->commands.append(cmd);
+
+ inPass = true;
+}
+
+void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::EndRenderPass;
+ cbD->commands.append(cmd);
+
+ inPass = false;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv)
+{
+ VkShaderModuleCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ shaderInfo.codeSize = spirv.size();
+ shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData());
+ VkShaderModule shaderModule;
+ VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create shader module: %d", err);
+ return VK_NULL_HANDLE;
+ }
+ return shaderModule;
+}
+
+bool QRhiVulkan::ensurePipelineCache()
+{
+ if (pipelineCache)
+ return true;
+
+ VkPipelineCacheCreateInfo pipelineCacheInfo;
+ memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo));
+ pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+ VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create pipeline cache: %d", err);
+ return false;
+ }
+ return true;
+}
+
+void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx)
+{
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
+
+ QVarLengthArray<VkDescriptorBufferInfo, 4> bufferInfos;
+ QVarLengthArray<VkDescriptorImageInfo, 4> imageInfos;
+ QVarLengthArray<VkWriteDescriptorSet, 8> writeInfos;
+
+ const bool updateAll = descSetIdx < 0;
+ int frameSlot = updateAll ? 0 : descSetIdx;
+ while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) {
+ srbD->boundResourceData[frameSlot].resize(srbD->sortedBindings.count());
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]);
+
+ VkWriteDescriptorSet writeInfo;
+ memset(&writeInfo, 0, sizeof(writeInfo));
+ writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writeInfo.dstSet = srbD->descSets[frameSlot];
+ writeInfo.dstBinding = b->binding;
+ writeInfo.descriptorCount = 1;
+
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
+ : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ QRhiBuffer *buf = b->u.ubuf.buf;
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ VkDescriptorBufferInfo bufInfo;
+ bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0];
+ bufInfo.offset = b->u.ubuf.offset;
+ bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size;
+ // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads
+ Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset);
+ bufferInfos.append(bufInfo);
+ writeInfo.pBufferInfo = &bufferInfos.last();
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
+ QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ VkDescriptorImageInfo imageInfo;
+ imageInfo.sampler = samplerD->sampler;
+ imageInfo.imageView = texD->imageView;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ imageInfos.append(imageInfo);
+ writeInfo.pImageInfo = &imageInfos.last();
+ }
+ break;
+ default:
+ continue;
+ }
+
+ writeInfos.append(writeInfo);
+ }
+ ++frameSlot;
+ }
+
+ df->vkUpdateDescriptorSets(dev, writeInfos.count(), writeInfos.constData(), 0, nullptr);
+}
+
+static inline bool accessIsWrite(VkAccessFlags access)
+{
+ return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0
+ || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0
+ || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0
+ || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0
+ || (access & VK_ACCESS_HOST_WRITE_BIT) != 0
+ || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0;
+}
+
+void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot,
+ VkAccessFlags access, VkPipelineStageFlags stage)
+{
+ Q_ASSERT(!inPass);
+ Q_ASSERT(access && stage);
+ QVkBuffer::UsageState &s(bufD->usageState[slot]);
+ if (!s.stage) {
+ s.access = access;
+ s.stage = stage;
+ return;
+ }
+
+ if (s.access == access && s.stage == stage) {
+ // No need to flood with unnecessary read-after-read barriers.
+ // Write-after-write is a different matter, however.
+ if (!accessIsWrite(access))
+ return;
+ }
+
+ VkBufferMemoryBarrier bufMemBarrier;
+ memset(&bufMemBarrier, 0, sizeof(bufMemBarrier));
+ bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.srcAccessMask = s.access;
+ bufMemBarrier.dstAccessMask = access;
+ bufMemBarrier.buffer = bufD->buffers[slot];
+ bufMemBarrier.size = VK_WHOLE_SIZE;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BufferBarrier;
+ cmd.args.bufferBarrier.srcStageMask = s.stage;
+ cmd.args.bufferBarrier.dstStageMask = stage;
+ cmd.args.bufferBarrier.desc = bufMemBarrier;
+ cbD->commands.append(cmd);
+
+ s.access = access;
+ s.stage = stage;
+}
+
+void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD,
+ VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage)
+{
+ Q_ASSERT(!inPass);
+ Q_ASSERT(layout && access && stage);
+ QVkTexture::UsageState &s(texD->usageState);
+ if (s.access == access && s.stage == stage && s.layout == layout) {
+ if (!accessIsWrite(access))
+ return;
+ }
+
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = !isDepthTextureFormat(texD->m_format)
+ ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
+ barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
+ barrier.newLayout = layout;
+ barrier.srcAccessMask = s.access; // may be 0 but that's fine
+ barrier.dstAccessMask = access;
+ barrier.image = texD->image;
+
+ VkPipelineStageFlags srcStage = s.stage;
+ // stage mask cannot be 0
+ if (!srcStage)
+ srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
+ cmd.args.imageBarrier.srcStageMask = srcStage;
+ cmd.args.imageBarrier.dstStageMask = stage;
+ cmd.args.imageBarrier.desc = barrier;
+ cbD->commands.append(cmd);
+
+ s.layout = layout;
+ s.access = access;
+ s.stage = stage;
+}
+
+void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ VkAccessFlags srcAccess, VkAccessFlags dstAccess,
+ VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
+ int startLayer, int layerCount,
+ int startLevel, int levelCount)
+{
+ Q_ASSERT(!inPass);
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.baseMipLevel = startLevel;
+ barrier.subresourceRange.levelCount = levelCount;
+ barrier.subresourceRange.baseArrayLayer = startLayer;
+ barrier.subresourceRange.layerCount = layerCount;
+ barrier.oldLayout = oldLayout;
+ barrier.newLayout = newLayout;
+ barrier.srcAccessMask = srcAccess;
+ barrier.dstAccessMask = dstAccess;
+ barrier.image = image;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
+ cmd.args.imageBarrier.srcStageMask = srcStage;
+ cmd.args.imageBarrier.dstStageMask = dstStage;
+ cmd.args.imageBarrier.desc = barrier;
+ cbD->commands.append(cmd);
+}
+
+VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
+{
+ VkDeviceSize size = 0;
+ const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
+ subresDesc.data().size() : subresDesc.image().sizeInBytes();
+ if (imageSizeBytes > 0)
+ size += aligned(imageSizeBytes, texbufAlign);
+ return size;
+}
+
+void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &subresDesc,
+ size_t *curOfs, void *mp,
+ BufferImageCopyList *copyInfos)
+{
+ qsizetype copySizeBytes = 0;
+ qsizetype imageSizeBytes = 0;
+ const void *src = nullptr;
+
+ VkBufferImageCopy copyInfo;
+ memset(&copyInfo, 0, sizeof(copyInfo));
+ copyInfo.bufferOffset = *curOfs;
+ copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copyInfo.imageSubresource.mipLevel = level;
+ copyInfo.imageSubresource.baseArrayLayer = layer;
+ copyInfo.imageSubresource.layerCount = 1;
+ copyInfo.imageExtent.depth = 1;
+
+ const QByteArray rawData = subresDesc.data();
+ const QPoint dp = subresDesc.destinationTopLeft();
+ QImage image = subresDesc.image();
+ if (!image.isNull()) {
+ copySizeBytes = imageSizeBytes = image.sizeInBytes();
+ QSize size = image.size();
+ src = image.constBits();
+ // Scanlines in QImage are 4 byte aligned so bpl must
+ // be taken into account for bufferRowLength.
+ int bpc = qMax(1, image.depth() / 8);
+ // this is in pixels, not bytes, to make it more complicated...
+ copyInfo.bufferRowLength = image.bytesPerLine() / bpc;
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const int sx = subresDesc.sourceTopLeft().x();
+ const int sy = subresDesc.sourceTopLeft().y();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ if (image.depth() == 32) {
+ // The staging buffer will get the full image
+ // regardless, just adjust the vk
+ // buffer-to-image copy start offset.
+ copyInfo.bufferOffset += sy * image.bytesPerLine() + sx * 4;
+ // bufferRowLength remains set to the original image's width
+ } else {
+ image = image.copy(sx, sy, size.width(), size.height());
+ src = image.constBits();
+ // The staging buffer gets the slice only. The rest of the
+ // space reserved for this mip will be unused.
+ copySizeBytes = image.sizeInBytes();
+ bpc = qMax(1, image.depth() / 8);
+ copyInfo.bufferRowLength = image.bytesPerLine() / bpc;
+ }
+ }
+ copyInfo.imageOffset.x = dp.x();
+ copyInfo.imageOffset.y = dp.y();
+ copyInfo.imageExtent.width = size.width();
+ copyInfo.imageExtent.height = size.height();
+ copyInfos->append(copyInfo);
+ } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
+ copySizeBytes = imageSizeBytes = rawData.size();
+ src = rawData.constData();
+ QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = size.width();
+ const int subresh = size.height();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ const int w = size.width();
+ const int h = size.height();
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, QSize(w, h), nullptr, nullptr, &blockDim);
+ // x and y must be multiples of the block width and height
+ copyInfo.imageOffset.x = aligned(dp.x(), blockDim.width());
+ copyInfo.imageOffset.y = aligned(dp.y(), blockDim.height());
+ // width and height must be multiples of the block width and height
+ // or x + width and y + height must equal the subresource width and height
+ copyInfo.imageExtent.width = dp.x() + w == subresw ? w : aligned(w, blockDim.width());
+ copyInfo.imageExtent.height = dp.y() + h == subresh ? h : aligned(h, blockDim.height());
+ copyInfos->append(copyInfo);
+ } else if (!rawData.isEmpty()) {
+ copySizeBytes = imageSizeBytes = rawData.size();
+ src = rawData.constData();
+ QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ copyInfo.imageOffset.x = dp.x();
+ copyInfo.imageOffset.y = dp.y();
+ copyInfo.imageExtent.width = size.width();
+ copyInfo.imageExtent.height = size.height();
+ copyInfos->append(copyInfo);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, src, copySizeBytes);
+ *curOfs += aligned(imageSizeBytes, texbufAlign);
+}
+
+void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ bufD->pendingDynamicUpdates[i].append(u);
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+
+ if (!bufD->stagingBuffers[currentFrameSlot]) {
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ // must cover the entire buffer - this way multiple, partial updates per frame
+ // are supported even when the staging buffer is reused (Static)
+ bufferInfo.size = bufD->m_size;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
+ &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
+ if (err == VK_SUCCESS) {
+ bufD->stagingAllocations[currentFrameSlot] = allocation;
+ QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, bufD->m_size));
+ } else {
+ qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err);
+ continue;
+ }
+ }
+
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]);
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map buffer: %d", err);
+ continue;
+ }
+ memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), u.data.size());
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ vmaFlushAllocation(toVmaAllocator(allocator), a, u.offset, u.data.size());
+
+ trackedBufferBarrier(cbD, bufD, 0,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ VkBufferCopy copyInfo;
+ memset(&copyInfo, 0, sizeof(copyInfo));
+ copyInfo.srcOffset = u.offset;
+ copyInfo.dstOffset = u.offset;
+ copyInfo.size = u.data.size();
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
+ cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
+ cmd.args.copyBuffer.dst = bufD->buffers[0];
+ cmd.args.copyBuffer.desc = copyInfo;
+ cbD->commands.append(cmd);
+
+ // Where's the barrier for read-after-write? (assuming the common case
+ // of binding this buffer as vertex/index, or, less likely, as uniform
+ // buffer, in a renderpass later on) That is handled by the pass
+ // resource tracking: the appropriate pipeline barrier will be
+ // generated and recorded right before the renderpass, that binds this
+ // buffer in one of its commands, gets its BeginRenderPass recorded.
+
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+
+ if (bufD->m_type == QRhiBuffer::Immutable) {
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
+ e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
+ bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
+ bufD->stagingAllocations[currentFrameSlot] = nullptr;
+ releaseQueue.append(e);
+ QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot));
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QVkTexture *utexD = QRHI_RES(QVkTexture, u.upload.tex);
+ // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos
+ VkDeviceSize stagingSize = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ stagingSize += subresUploadByteSize(subresDesc);
+ }
+ }
+
+ Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]);
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = stagingSize;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
+ &utexD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image staging buffer of size %d: %d", int(stagingSize), err);
+ continue;
+ }
+ utexD->stagingAllocations[currentFrameSlot] = allocation;
+ QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
+
+ BufferImageCopyList copyInfos;
+ size_t curOfs = 0;
+ void *mp = nullptr;
+ VmaAllocation a = toVmaAllocation(utexD->stagingAllocations[currentFrameSlot]);
+ err = vmaMapMemory(toVmaAllocator(allocator), a, &mp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map image data: %d", err);
+ continue;
+ }
+
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.upload.subresDesc[layer][level]);
+ if (srd.isEmpty())
+ continue;
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) {
+ prepareUploadSubres(utexD, layer, level,
+ subresDesc, &curOfs, mp, &copyInfos);
+ }
+ }
+ }
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ vmaFlushAllocation(toVmaAllocator(allocator), a, 0, stagingSize);
+
+ trackedImageBarrier(cbD, utexD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyBufferToImage;
+ cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot];
+ cmd.args.copyBufferToImage.dst = utexD->image;
+ cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ cmd.args.copyBufferToImage.count = copyInfos.count();
+ cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.count();
+ cbD->pools.bufferImageCopy.append(copyInfos.constData(), copyInfos.count());
+ cbD->commands.append(cmd);
+
+ // no reuse of staging, this is intentional
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot];
+ e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot];
+ utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
+ utexD->stagingAllocations[currentFrameSlot] = nullptr;
+ releaseQueue.append(e);
+ QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
+
+ // Similarly to buffers, transitioning away from DST is done later,
+ // when a renderpass using the texture is encountered.
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ if (u.copy.src == u.copy.dst) {
+ qWarning("Texture copy with matching source and destination is not supported");
+ continue;
+ }
+ QVkTexture *srcD = QRHI_RES(QVkTexture, u.copy.src);
+ QVkTexture *dstD = QRHI_RES(QVkTexture, u.copy.dst);
+
+ VkImageCopy region;
+ memset(&region, 0, sizeof(region));
+
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.mipLevel = u.copy.desc.sourceLevel();
+ region.srcSubresource.baseArrayLayer = u.copy.desc.sourceLayer();
+ region.srcSubresource.layerCount = 1;
+
+ region.srcOffset.x = u.copy.desc.sourceTopLeft().x();
+ region.srcOffset.y = u.copy.desc.sourceTopLeft().y();
+
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.mipLevel = u.copy.desc.destinationLevel();
+ region.dstSubresource.baseArrayLayer = u.copy.desc.destinationLayer();
+ region.dstSubresource.layerCount = 1;
+
+ region.dstOffset.x = u.copy.desc.destinationTopLeft().x();
+ region.dstOffset.y = u.copy.desc.destinationTopLeft().y();
+
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ region.extent.width = size.width();
+ region.extent.height = size.height();
+ region.extent.depth = 1;
+
+ trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ trackedImageBarrier(cbD, dstD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImage;
+ cmd.args.copyImage.src = srcD->image;
+ cmd.args.copyImage.srcLayout = srcD->usageState.layout;
+ cmd.args.copyImage.dst = dstD->image;
+ cmd.args.copyImage.dstLayout = dstD->usageState.layout;
+ cmd.args.copyImage.desc = region;
+ cbD->commands.append(cmd);
+
+ srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ ActiveReadback aRb;
+ aRb.activeFrameSlot = currentFrameSlot;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ QVkTexture *texD = QRHI_RES(QVkTexture, u.read.rb.texture());
+ QVkSwapChain *swapChainD = nullptr;
+ if (texD) {
+ if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
+ : texD->m_pixelSize;
+ aRb.format = texD->m_format;
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain);
+ if (!swapChainD->supportsReadback) {
+ qWarning("Swapchain does not support readback");
+ continue;
+ }
+ aRb.pixelSize = swapChainD->pixelSize;
+ aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr);
+ if (aRb.format == QRhiTexture::UnknownFormat)
+ continue;
+
+ // Multisample swapchains need nothing special since resolving
+ // happens when ending a renderpass.
+ }
+ textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize);
+
+ // Create a host visible buffer.
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = aRb.bufSize;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &aRb.buf, &allocation, nullptr);
+ if (err == VK_SUCCESS) {
+ aRb.bufAlloc = allocation;
+ QRHI_PROF_F(newReadbackBuffer(quint64(aRb.buf),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ aRb.bufSize));
+ } else {
+ qWarning("Failed to create readback buffer of size %u: %d", aRb.bufSize, err);
+ continue;
+ }
+
+ // Copy from the (optimal and not host visible) image into the buffer.
+ VkBufferImageCopy copyDesc;
+ memset(&copyDesc, 0, sizeof(copyDesc));
+ copyDesc.bufferOffset = 0;
+ copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copyDesc.imageSubresource.mipLevel = u.read.rb.level();
+ copyDesc.imageSubresource.baseArrayLayer = u.read.rb.layer();
+ copyDesc.imageSubresource.layerCount = 1;
+ copyDesc.imageExtent.width = aRb.pixelSize.width();
+ copyDesc.imageExtent.height = aRb.pixelSize.height();
+ copyDesc.imageExtent.depth = 1;
+
+ if (texD) {
+ trackedImageBarrier(cbD, texD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
+ cmd.args.copyImageToBuffer.src = texD->image;
+ cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout;
+ cmd.args.copyImageToBuffer.dst = aRb.buf;
+ cmd.args.copyImageToBuffer.desc = copyDesc;
+ cbD->commands.append(cmd);
+ } else {
+ // use the swapchain image
+ VkImage image = swapChainD->imageRes[swapChainD->currentImageIndex].image;
+ if (!swapChainD->imageRes[swapChainD->currentImageIndex].transferSource) {
+ subresourceBarrier(cbD, image,
+ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 1,
+ 0, 1);
+ swapChainD->imageRes[swapChainD->currentImageIndex].transferSource = true;
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
+ cmd.args.copyImageToBuffer.src = image;
+ cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ cmd.args.copyImageToBuffer.dst = aRb.buf;
+ cmd.args.copyImageToBuffer.desc = copyDesc;
+ cbD->commands.append(cmd);
+ }
+
+ activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QVkTexture *utexD = QRHI_RES(QVkTexture, u.mipgen.tex);
+ Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
+ int w = utexD->m_pixelSize.width();
+ int h = utexD->m_pixelSize.height();
+
+ VkImageLayout origLayout = utexD->usageState.layout;
+ VkAccessFlags origAccess = utexD->usageState.access;
+ VkPipelineStageFlags origStage = utexD->usageState.stage;
+ if (!origStage)
+ origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+
+ for (uint level = 1; level < utexD->mipLevelCount; ++level) {
+ if (level == 1) {
+ subresourceBarrier(cbD, utexD->image,
+ origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ origAccess, VK_ACCESS_TRANSFER_READ_BIT,
+ origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level - 1, 1);
+ } else {
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level - 1, 1);
+ }
+
+ subresourceBarrier(cbD, utexD->image,
+ origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ origAccess, VK_ACCESS_TRANSFER_WRITE_BIT,
+ origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level, 1);
+
+ VkImageBlit region;
+ memset(&region, 0, sizeof(region));
+
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.mipLevel = level - 1;
+ region.srcSubresource.baseArrayLayer = u.mipgen.layer;
+ region.srcSubresource.layerCount = 1;
+
+ region.srcOffsets[1].x = qMax(1, w);
+ region.srcOffsets[1].y = qMax(1, h);
+ region.srcOffsets[1].z = 1;
+
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.mipLevel = level;
+ region.dstSubresource.baseArrayLayer = u.mipgen.layer;
+ region.dstSubresource.layerCount = 1;
+
+ region.dstOffsets[1].x = qMax(1, w >> 1);
+ region.dstOffsets[1].y = qMax(1, h >> 1);
+ region.dstOffsets[1].z = 1;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BlitImage;
+ cmd.args.blitImage.src = utexD->image;
+ cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ cmd.args.blitImage.dst = utexD->image;
+ cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ cmd.args.blitImage.filter = VK_FILTER_LINEAR;
+ cmd.args.blitImage.desc = region;
+ cbD->commands.append(cmd);
+
+ w >>= 1;
+ h >>= 1;
+ }
+
+ if (utexD->mipLevelCount > 1) {
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout,
+ VK_ACCESS_TRANSFER_READ_BIT, origAccess,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
+ u.mipgen.layer, 1,
+ 0, utexD->mipLevelCount - 1);
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout,
+ VK_ACCESS_TRANSFER_WRITE_BIT, origAccess,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
+ u.mipgen.layer, 1,
+ utexD->mipLevelCount - 1, 1);
+ }
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+
+ ud->free();
+}
+
+void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD)
+{
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->pendingDynamicUpdates[currentFrameSlot]);
+ if (updates.isEmpty())
+ return;
+
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(bufD->allocations[currentFrameSlot]);
+ // The vmaMap/Unmap are basically a no-op when persistently mapped since it
+ // refcounts; this is great because we don't need to care if the allocation
+ // was created as persistently mapped or not.
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map buffer: %d", err);
+ return;
+ }
+ int changeBegin = -1;
+ int changeEnd = -1;
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
+ Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf));
+ memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
+ if (changeBegin == -1 || u.offset < changeBegin)
+ changeBegin = u.offset;
+ if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
+ changeEnd = u.offset + u.data.size();
+ }
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ if (changeBegin >= 0)
+ vmaFlushAllocation(toVmaAllocator(allocator), a, changeBegin, changeEnd - changeBegin);
+
+ updates.clear();
+}
+
+static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.buffers[i], toVmaAllocation(e.buffer.allocations[i]));
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.stagingBuffers[i], toVmaAllocation(e.buffer.stagingAllocations[i]));
+ }
+}
+
+static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
+{
+ df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr);
+ df->vkDestroyImage(dev, e.renderBuffer.image, nullptr);
+ df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr);
+}
+
+static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator)
+{
+ df->vkDestroyImageView(dev, e.texture.imageView, nullptr);
+ vmaDestroyImage(toVmaAllocator(allocator), e.texture.image, toVmaAllocation(e.texture.allocation));
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.texture.stagingBuffers[i], toVmaAllocation(e.texture.stagingAllocations[i]));
+}
+
+static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
+{
+ df->vkDestroySampler(dev, e.sampler.sampler, nullptr);
+}
+
+void QRhiVulkan::executeDeferredReleases(bool forced)
+{
+ for (int i = releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]);
+ if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
+ switch (e.type) {
+ case QRhiVulkan::DeferredReleaseEntry::Pipeline:
+ df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr);
+ df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings:
+ df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr);
+ if (e.shaderResourceBindings.poolIndex >= 0) {
+ descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1;
+ Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0);
+ }
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Buffer:
+ qrhivk_releaseBuffer(e, allocator);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::RenderBuffer:
+ qrhivk_releaseRenderBuffer(e, dev, df);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Texture:
+ qrhivk_releaseTexture(e, dev, df, allocator);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Sampler:
+ qrhivk_releaseSampler(e, dev, df);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget:
+ df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr);
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr);
+ df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
+ }
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::RenderPass:
+ df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::StagingBuffer:
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.stagingBuffer.stagingBuffer, toVmaAllocation(e.stagingBuffer.stagingAllocation));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ releaseQueue.removeAt(i);
+ }
+ }
+}
+
+void QRhiVulkan::finishActiveReadbacks(bool forced)
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]);
+ if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(aRb.bufAlloc);
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map readback buffer: %d", err);
+ continue;
+ }
+ memcpy(aRb.result->data.data(), p, aRb.bufSize);
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+
+ vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a);
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(aRb.buf)));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ activeReadbacks.removeAt(i);
+ }
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+static struct {
+ VkSampleCountFlagBits mask;
+ int count;
+} qvk_sampleCounts[] = {
+ // keep this sorted by 'count'
+ { VK_SAMPLE_COUNT_1_BIT, 1 },
+ { VK_SAMPLE_COUNT_2_BIT, 2 },
+ { VK_SAMPLE_COUNT_4_BIT, 4 },
+ { VK_SAMPLE_COUNT_8_BIT, 8 },
+ { VK_SAMPLE_COUNT_16_BIT, 16 },
+ { VK_SAMPLE_COUNT_32_BIT, 32 },
+ { VK_SAMPLE_COUNT_64_BIT, 64 }
+};
+
+QVector<int> QRhiVulkan::supportedSampleCounts() const
+{
+ const VkPhysicalDeviceLimits *limits = &physDevProperties.limits;
+ VkSampleCountFlags color = limits->framebufferColorSampleCounts;
+ VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
+ VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
+ QVector<int> result;
+
+ for (size_t i = 0; i < sizeof(qvk_sampleCounts) / sizeof(qvk_sampleCounts[0]); ++i) {
+ if ((color & qvk_sampleCounts[i].mask)
+ && (depth & qvk_sampleCounts[i].mask)
+ && (stencil & qvk_sampleCounts[i].mask))
+ {
+ result.append(qvk_sampleCounts[i].count);
+ }
+ }
+
+ return result;
+}
+
+VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount)
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ sampleCount = qBound(1, sampleCount, 64);
+
+ if (!supportedSampleCounts().contains(sampleCount)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return VK_SAMPLE_COUNT_1_BIT;
+ }
+
+ for (size_t i = 0; i < sizeof(qvk_sampleCounts) / sizeof(qvk_sampleCounts[0]); ++i) {
+ if (qvk_sampleCounts[i].count == sampleCount)
+ return qvk_sampleCounts[i].mask;
+ }
+
+ Q_UNREACHABLE();
+ return VK_SAMPLE_COUNT_1_BIT;
+}
+
+void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD)
+{
+ cbD->passResTrackers.append(QRhiPassResourceTracker());
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources;
+ cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.count() - 1;
+ cbD->commands.append(cmd);
+ cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1;
+}
+
+void QRhiVulkan::recordCommandBuffer(QVkCommandBuffer *cbD)
+{
+ for (QVkCommandBuffer::Command &cmd : cbD->commands) {
+ switch (cmd.cmd) {
+ case QVkCommandBuffer::Command::CopyBuffer:
+ df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst,
+ 1, &cmd.args.copyBuffer.desc);
+ break;
+ case QVkCommandBuffer::Command::CopyBufferToImage:
+ df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst,
+ cmd.args.copyBufferToImage.dstLayout,
+ cmd.args.copyBufferToImage.count,
+ cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex);
+ break;
+ case QVkCommandBuffer::Command::CopyImage:
+ df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout,
+ cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout,
+ 1, &cmd.args.copyImage.desc);
+ break;
+ case QVkCommandBuffer::Command::CopyImageToBuffer:
+ df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout,
+ cmd.args.copyImageToBuffer.dst,
+ 1, &cmd.args.copyImageToBuffer.desc);
+ break;
+ case QVkCommandBuffer::Command::ImageBarrier:
+ df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &cmd.args.imageBarrier.desc);
+ break;
+ case QVkCommandBuffer::Command::BufferBarrier:
+ df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask,
+ 0, 0, nullptr,
+ 1, &cmd.args.bufferBarrier.desc,
+ 0, nullptr);
+ break;
+ case QVkCommandBuffer::Command::BlitImage:
+ df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout,
+ cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout,
+ 1, &cmd.args.blitImage.desc,
+ cmd.args.blitImage.filter);
+ break;
+ case QVkCommandBuffer::Command::BeginRenderPass:
+ cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex;
+ df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc, VK_SUBPASS_CONTENTS_INLINE);
+ break;
+ case QVkCommandBuffer::Command::EndRenderPass:
+ df->vkCmdEndRenderPass(cbD->cb);
+ break;
+ case QVkCommandBuffer::Command::BindPipeline:
+ df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline);
+ break;
+ case QVkCommandBuffer::Command::BindDescriptorSet:
+ {
+ const uint32_t *offsets = nullptr;
+ if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0)
+ offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex;
+ df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint,
+ cmd.args.bindDescriptorSet.pipelineLayout,
+ 0, 1, &cmd.args.bindDescriptorSet.descSet,
+ cmd.args.bindDescriptorSet.dynamicOffsetCount,
+ offsets);
+ }
+ break;
+ case QVkCommandBuffer::Command::BindVertexBuffer:
+ df->vkCmdBindVertexBuffers(cbD->cb, cmd.args.bindVertexBuffer.startBinding,
+ cmd.args.bindVertexBuffer.count,
+ cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex,
+ cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex);
+ break;
+ case QVkCommandBuffer::Command::BindIndexBuffer:
+ df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf,
+ cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type);
+ break;
+ case QVkCommandBuffer::Command::SetViewport:
+ df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport);
+ break;
+ case QVkCommandBuffer::Command::SetScissor:
+ df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor);
+ break;
+ case QVkCommandBuffer::Command::SetBlendConstants:
+ df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c);
+ break;
+ case QVkCommandBuffer::Command::SetStencilRef:
+ df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref);
+ break;
+ case QVkCommandBuffer::Command::Draw:
+ df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
+ cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
+ break;
+ case QVkCommandBuffer::Command::DrawIndexed:
+ df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
+ cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
+ cmd.args.drawIndexed.firstInstance);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerBegin:
+ cmd.args.debugMarkerBegin.marker.pMarkerName =
+ cbD->pools.debugMarkerName[cmd.args.debugMarkerBegin.markerNameIndex].constData();
+ vkCmdDebugMarkerBegin(cbD->cb, &cmd.args.debugMarkerBegin.marker);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerEnd:
+ vkCmdDebugMarkerEnd(cbD->cb);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerInsert:
+ vkCmdDebugMarkerInsert(cbD->cb, &cmd.args.debugMarkerInsert.marker);
+ break;
+ case QVkCommandBuffer::Command::TransitionPassResources:
+ recordTransitionPassResources(cbD, cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ cbD->resetCommands();
+}
+
+static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::BufVertexInput:
+ return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+ case QRhiPassResourceTracker::BufIndexRead:
+ return VK_ACCESS_INDEX_READ_BIT;
+ case QRhiPassResourceTracker::BufUniformRead:
+ return VK_ACCESS_UNIFORM_READ_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage)
+{
+ switch (stage) {
+ case QRhiPassResourceTracker::BufVertexInputStage:
+ return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
+ case QRhiPassResourceTracker::BufVertexStage:
+ return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
+ case QRhiPassResourceTracker::BufFragmentStage:
+ return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage)
+{
+ QVkBuffer::UsageState u;
+ u.access = usage.access;
+ u.stage = usage.stage;
+ return u;
+}
+
+static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::TexSample:
+ return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ case QRhiPassResourceTracker::TexColorOutput:
+ return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ case QRhiPassResourceTracker::TexDepthOutput:
+ return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return VK_IMAGE_LAYOUT_GENERAL;
+}
+
+static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::TexSample:
+ return VK_ACCESS_SHADER_READ_BIT;
+ case QRhiPassResourceTracker::TexColorOutput:
+ return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ case QRhiPassResourceTracker::TexDepthOutput:
+ return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage)
+{
+ switch (stage) {
+ case QRhiPassResourceTracker::TexVertexStage:
+ return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
+ case QRhiPassResourceTracker::TexFragmentStage:
+ return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ case QRhiPassResourceTracker::TexColorOutputStage:
+ return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ case QRhiPassResourceTracker::TexDepthOutputStage:
+ return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage)
+{
+ QVkTexture::UsageState u;
+ u.layout = VkImageLayout(usage.layout);
+ u.access = usage.access;
+ u.stage = usage.stage;
+ return u;
+}
+
+void QRhiVulkan::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
+ QVkBuffer *bufD,
+ int slot,
+ QRhiPassResourceTracker::BufferAccess access,
+ QRhiPassResourceTracker::BufferStage stage)
+{
+ QVkBuffer::UsageState &u(bufD->usageState[slot]);
+ // The last arg will get ignored if this buffer was already used in the
+ // same pass; that's good because u is not the state at pass start anymore
+ // at that point.
+ passResTracker->registerBufferOnce(bufD, slot, access, stage, toPassTrackerUsageState(u));
+ u.access = toVkAccess(access);
+ u.stage = toVkPipelineStage(stage);
+}
+
+void QRhiVulkan::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
+ QVkTexture *texD,
+ QRhiPassResourceTracker::TextureAccess access,
+ QRhiPassResourceTracker::TextureStage stage)
+{
+ QVkTexture::UsageState &u(texD->usageState);
+ // The last arg will get ignored if this buffer was already used in the
+ // same pass; that's good because u is not the state at pass start anymore
+ // at that point.
+ passResTracker->registerTextureOnce(texD, access, stage, toPassTrackerUsageState(u));
+ u.layout = toVkLayout(access);
+ u.access = toVkAccess(access);
+ u.stage = toVkPipelineStage(stage);
+}
+
+void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker)
+{
+ if (tracker.isEmpty())
+ return;
+
+ const QVector<QRhiPassResourceTracker::Buffer> *buffers = tracker.buffers();
+ for (const QRhiPassResourceTracker::Buffer &b : *buffers) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b.buf);
+ VkAccessFlags access = toVkAccess(b.access);
+ VkPipelineStageFlags stage = toVkPipelineStage(b.stage);
+ QVkBuffer::UsageState s = toVkBufferUsageState(b.stateAtPassBegin);
+ if (!s.stage)
+ continue;
+ if (s.access == access && s.stage == stage) {
+ if (!accessIsWrite(access))
+ continue;
+ }
+ VkBufferMemoryBarrier bufMemBarrier;
+ memset(&bufMemBarrier, 0, sizeof(bufMemBarrier));
+ bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.srcAccessMask = s.access;
+ bufMemBarrier.dstAccessMask = access;
+ bufMemBarrier.buffer = bufD->buffers[b.slot];
+ bufMemBarrier.size = VK_WHOLE_SIZE;
+ df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0,
+ 0, nullptr,
+ 1, &bufMemBarrier,
+ 0, nullptr);
+ }
+
+ const QVector<QRhiPassResourceTracker::Texture> *textures = tracker.textures();
+ for (const QRhiPassResourceTracker::Texture &t : *textures) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, t.tex);
+ VkImageLayout layout = toVkLayout(t.access);
+ VkAccessFlags access = toVkAccess(t.access);
+ VkPipelineStageFlags stage = toVkPipelineStage(t.stage);
+ QVkTexture::UsageState s = toVkTextureUsageState(t.stateAtPassBegin);
+ if (s.access == access && s.stage == stage && s.layout == layout) {
+ if (!accessIsWrite(access))
+ continue;
+ }
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = !isDepthTextureFormat(texD->m_format)
+ ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
+ barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
+ barrier.newLayout = layout;
+ barrier.srcAccessMask = s.access; // may be 0 but that's fine
+ barrier.dstAccessMask = access;
+ barrier.image = texD->image;
+ VkPipelineStageFlags srcStage = s.stage;
+ // stage mask cannot be 0
+ if (!srcStage)
+ srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0,
+ 0, nullptr,
+ 0, nullptr,
+ 1, &barrier);
+ }
+}
+
+QRhiSwapChain *QRhiVulkan::createSwapChain()
+{
+ return new QVkSwapChain(this);
+}
+
+QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QVkBuffer(this, type, usage, size);
+}
+
+int QRhiVulkan::ubufAlignment() const
+{
+ return ubufAlign; // typically 256 (bytes)
+}
+
+bool QRhiVulkan::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiVulkan::isYUpInNDC() const
+{
+ return false;
+}
+
+bool QRhiVulkan::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiVulkan::clipSpaceCorrMatrix() const
+{
+ // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
+
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ VkPhysicalDeviceFeatures features;
+ f->vkGetPhysicalDeviceFeatures(physDev, &features);
+
+ // Note that with some SDKs the validation layer gives an odd warning about
+ // BC not being supported, even when our check here succeeds. Not much we
+ // can do about that.
+ if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) {
+ if (!features.textureCompressionBC)
+ return false;
+ }
+
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) {
+ if (!features.textureCompressionETC2)
+ return false;
+ }
+
+ if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) {
+ if (!features.textureCompressionASTC_LDR)
+ return false;
+ }
+
+ VkFormat vkformat = toVkTextureFormat(format, flags);
+ VkFormatProperties props;
+ f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props);
+ return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
+}
+
+bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return debugMarkersAvailable;
+ case QRhi::Timestamps:
+ return timestampValidBits != 0;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return vertexAttribDivisorAvailable;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return physDevProperties.limits.maxImageDimension2D;
+ case QRhi::MaxColorAttachments:
+ return physDevProperties.limits.maxColorAttachments;
+ case QRhi::FramesInFlight:
+ return QVK_FRAMES_IN_FLIGHT;
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiVulkan::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiVulkan::sendVMemStatsToProfiler()
+{
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ if (!rhiP)
+ return;
+
+ VmaStats stats;
+ vmaCalculateStats(toVmaAllocator(allocator), &stats);
+ QRHI_PROF_F(vmemStat(stats.total.blockCount, stats.total.allocationCount,
+ stats.total.usedBytes, stats.total.unusedBytes));
+}
+
+QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QVkTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QVkTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiVulkan::createGraphicsPipeline()
+{
+ return new QVkGraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings()
+{
+ return new QVkShaderResourceBindings(this);
+}
+
+void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_ASSERT(inPass);
+ QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps);
+ Q_ASSERT(psD->pipeline);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+
+ if (cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindPipeline;
+ cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ cmd.args.bindPipeline.pipeline = psD->pipeline;
+ cbD->commands.append(cmd);
+
+ cbD->currentPipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+QRhiPassResourceTracker::BufferStage toPassTrackerBufferStage(QRhiShaderResourceBinding::StageFlags stages)
+{
+ // pick the earlier stage (as this is going to be dstAccessMask)
+ if (stages.testFlag(QRhiShaderResourceBinding::VertexStage))
+ return QRhiPassResourceTracker::BufVertexStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ return QRhiPassResourceTracker::BufFragmentStage;
+
+ Q_UNREACHABLE();
+ return QRhiPassResourceTracker::BufVertexStage;
+}
+
+QRhiPassResourceTracker::TextureStage toPassTrackerTextureStage(QRhiShaderResourceBinding::StageFlags stages)
+{
+ // pick the earlier stage (as this is going to be dstAccessMask)
+ if (stages.testFlag(QRhiShaderResourceBinding::VertexStage))
+ return QRhiPassResourceTracker::TexVertexStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ return QRhiPassResourceTracker::TexFragmentStage;
+
+ Q_UNREACHABLE();
+ return QRhiPassResourceTracker::TexVertexStage;
+}
+
+void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_ASSERT(inPass);
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline);
+ QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, cbD->currentPipeline);
+
+ if (!srb)
+ srb = psD->m_shaderResourceBindings;
+
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
+ bool hasSlottedResourceInSrb = false;
+ bool hasDynamicOffsetInSrb = false;
+
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->m_type == QRhiBuffer::Dynamic)
+ hasSlottedResourceInSrb = true;
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ const int descSetIdx = hasSlottedResourceInSrb ? currentFrameSlot : 0;
+ bool rewriteDescSet = false;
+
+ // Do host writes and mark referenced shader resources as in-use.
+ // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects.
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[descSetIdx][i]);
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ trackedRegisterBuffer(&passResTracker, bufD, currentFrameSlot,
+ QRhiPassResourceTracker::BufUniformRead,
+ toPassTrackerBufferStage(b->stage));
+
+ // Check both the "local" id (the generation counter) and the
+ // global id. The latter is relevant when a newly allocated
+ // QRhiResource ends up with the same pointer as a previous one.
+ // (and that previous one could have been in an srb...)
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ rewriteDescSet = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
+ QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ trackedRegisterTexture(&passResTracker, texD,
+ QRhiPassResourceTracker::TexSample,
+ toPassTrackerTextureStage(b->stage));
+
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ rewriteDescSet = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ // write descriptor sets, if needed
+ if (rewriteDescSet)
+ updateShaderResourceBindings(srb, descSetIdx);
+
+ // make sure the descriptors for the correct slot will get bound.
+ // also, dynamic offsets always need a bind.
+ const bool forceRebind = (hasSlottedResourceInSrb && cbD->currentDescSetSlot != descSetIdx) || hasDynamicOffsetInSrb;
+
+ if (forceRebind || rewriteDescSet || cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation) {
+ QVarLengthArray<uint32_t, 4> dynOfs;
+ if (hasDynamicOffsetInSrb) {
+ // Filling out dynOfs based on the sorted bindings is important
+ // because dynOfs has to be ordered based on the binding numbers,
+ // and neither srb nor dynamicOffsets has any such ordering
+ // requirement.
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) {
+ uint32_t offset = 0;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ if (dynOfs.first == b->binding) {
+ offset = dynOfs.second;
+ break;
+ }
+ }
+ dynOfs.append(offset); // use 0 if dynamicOffsets did not contain this binding
+ }
+ }
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet;
+ cmd.args.bindDescriptorSet.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ cmd.args.bindDescriptorSet.pipelineLayout = psD->layout;
+ cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx];
+ cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.count();
+ cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.count();
+ cbD->pools.dynamicOffset.append(dynOfs.constData(), dynOfs.count());
+ cbD->commands.append(cmd);
+
+ cbD->currentSrb = srb;
+ cbD->currentSrbGeneration = srbD->generation;
+ cbD->currentDescSetSlot = descSetIdx;
+ }
+
+ srbD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+
+ bool needsBindVBuf = false;
+ for (int i = 0; i < bindingCount; ++i) {
+ const int inputSlot = startBinding + i;
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
+ if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf
+ || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+ {
+ needsBindVBuf = true;
+ cbD->currentVertexBuffers[inputSlot] = vkvertexbuf;
+ cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+ }
+ }
+
+ if (needsBindVBuf) {
+ QVarLengthArray<VkBuffer, 4> bufs;
+ QVarLengthArray<VkDeviceSize, 4> ofs;
+ for (int i = 0; i < bindingCount; ++i) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
+ const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
+ bufs.append(bufD->buffers[slot]);
+ ofs.append(bindings[i].second);
+ trackedRegisterBuffer(&passResTracker, bufD, slot,
+ QRhiPassResourceTracker::BufVertexInput,
+ QRhiPassResourceTracker::BufVertexInputStage);
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer;
+ cmd.args.bindVertexBuffer.startBinding = startBinding;
+ cmd.args.bindVertexBuffer.count = bufs.count();
+ cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.count();
+ cbD->pools.vertexBuffer.append(bufs.constData(), bufs.count());
+ cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.count();
+ cbD->pools.vertexBufferOffset.append(ofs.constData(), ofs.count());
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ ibufD->lastActiveFrameSlot = currentFrameSlot;
+ if (ibufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(ibufD);
+
+ const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
+ const VkBuffer vkindexbuf = ibufD->buffers[slot];
+ const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16
+ : VK_INDEX_TYPE_UINT32;
+
+ if (cbD->currentIndexBuffer != vkindexbuf
+ || cbD->currentIndexOffset != indexOffset
+ || cbD->currentIndexFormat != type)
+ {
+ cbD->currentIndexBuffer = vkindexbuf;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = type;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buf = vkindexbuf;
+ cmd.args.bindIndexBuffer.ofs = indexOffset;
+ cmd.args.bindIndexBuffer.type = type;
+ cbD->commands.append(cmd);
+
+ trackedRegisterBuffer(&passResTracker, ibufD, slot,
+ QRhiPassResourceTracker::BufIndexRead,
+ QRhiPassResourceTracker::BufVertexInputStage);
+ }
+ }
+}
+
+void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in VkViewport but bottom-left in QRhiViewport
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetViewport;
+ VkViewport *vp = &cmd.args.setViewport.viewport;
+ vp->x = x;
+ vp->y = y;
+ vp->width = w;
+ vp->height = h;
+ vp->minDepth = viewport.minDepth();
+ vp->maxDepth = viewport.maxDepth();
+ cbD->commands.append(cmd);
+
+ if (!QRHI_RES(QVkGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ cmd.cmd = QVkCommandBuffer::Command::SetScissor;
+ VkRect2D *s = &cmd.args.setScissor.scissor;
+ s->offset.x = x;
+ s->offset.y = y;
+ s->extent.width = w;
+ s->extent.height = h;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->currentPipeline && cbD->currentTarget);
+ Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in VkRect2D but bottom-left in QRhiScissor
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetScissor;
+ VkRect2D *s = &cmd.args.setScissor.scissor;
+ s->offset.x = x;
+ s->offset.y = y;
+ s->extent.width = w;
+ s->extent.height = h;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetBlendConstants;
+ cmd.args.setBlendConstants.c[0] = c.redF();
+ cmd.args.setBlendConstants.c[1] = c.greenF();
+ cmd.args.setBlendConstants.c[2] = c.blueF();
+ cmd.args.setBlendConstants.c[3] = c.alphaF();
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetStencilRef;
+ cmd.args.setStencilRef.ref = refValue;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::Draw;
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.instanceCount = instanceCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cmd.args.draw.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_ASSERT(inPass);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.instanceCount = instanceCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cmd.args.drawIndexed.vertexOffset = vertexOffset;
+ cmd.args.drawIndexed.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ VkDebugMarkerMarkerInfoEXT marker;
+ memset(&marker, 0, sizeof(marker));
+ marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin;
+ cmd.args.debugMarkerBegin.marker = marker;
+ cmd.args.debugMarkerBegin.markerNameIndex = cbD->pools.debugMarkerName.count();
+ cbD->pools.debugMarkerName.append(name);
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ VkDebugMarkerMarkerInfoEXT marker;
+ memset(&marker, 0, sizeof(marker));
+ marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+ marker.pMarkerName = msg.constData();
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert;
+ cmd.args.debugMarkerInsert.marker = marker;
+ cbD->commands.append(cmd);
+}
+
+const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb)
+{
+ return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiVulkan::endExternal(QRhiCommandBuffer *cb)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ cbD->resetCachedState();
+}
+
+void QRhiVulkan::setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot)
+{
+ if (!debugMarkers || !debugMarkersAvailable || name.isEmpty())
+ return;
+
+ VkDebugMarkerObjectNameInfoEXT nameInfo;
+ memset(&nameInfo, 0, sizeof(nameInfo));
+ nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
+ nameInfo.objectType = type;
+ nameInfo.object = object;
+ QByteArray decoratedName = name;
+ if (slot >= 0) {
+ decoratedName += '/';
+ decoratedName += QByteArray::number(slot);
+ }
+ nameInfo.pObjectName = decoratedName.constData();
+ vkDebugMarkerSetObjectName(dev, &nameInfo);
+}
+
+static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage)
+{
+ int u = 0;
+ if (usage.testFlag(QRhiBuffer::VertexBuffer))
+ u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+ if (usage.testFlag(QRhiBuffer::IndexBuffer))
+ u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+ if (usage.testFlag(QRhiBuffer::UniformBuffer))
+ u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ return VkBufferUsageFlagBits(u);
+}
+
+static inline VkFilter toVkFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return VK_FILTER_NEAREST;
+ case QRhiSampler::Linear:
+ return VK_FILTER_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return VK_FILTER_NEAREST;
+ }
+}
+
+static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::None:
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ case QRhiSampler::Nearest:
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ case QRhiSampler::Linear:
+ return VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ }
+}
+
+static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ case QRhiSampler::ClampToEdge:
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ case QRhiSampler::Border:
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+ case QRhiSampler::Mirror:
+ return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+ case QRhiSampler::MirrorOnce:
+ return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
+ default:
+ Q_UNREACHABLE();
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ }
+}
+
+static inline VkShaderStageFlagBits toVkShaderStage(QRhiGraphicsShaderStage::Type type)
+{
+ switch (type) {
+ case QRhiGraphicsShaderStage::Vertex:
+ return VK_SHADER_STAGE_VERTEX_BIT;
+ case QRhiGraphicsShaderStage::Fragment:
+ return VK_SHADER_STAGE_FRAGMENT_BIT;
+ default:
+ Q_UNREACHABLE();
+ return VK_SHADER_STAGE_VERTEX_BIT;
+ }
+}
+
+static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ case QRhiVertexInputAttribute::Float3:
+ return VK_FORMAT_R32G32B32_SFLOAT;
+ case QRhiVertexInputAttribute::Float2:
+ return VK_FORMAT_R32G32_SFLOAT;
+ case QRhiVertexInputAttribute::Float:
+ return VK_FORMAT_R32_SFLOAT;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return VK_FORMAT_R8G8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte:
+ return VK_FORMAT_R8_UNORM;
+ default:
+ Q_UNREACHABLE();
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ }
+}
+
+static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
+ case QRhiGraphicsPipeline::LineStrip:
+ return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
+ case QRhiGraphicsPipeline::Points:
+ return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+ default:
+ Q_UNREACHABLE();
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ }
+}
+
+static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return VK_CULL_MODE_NONE;
+ case QRhiGraphicsPipeline::Front:
+ return VK_CULL_MODE_FRONT_BIT;
+ case QRhiGraphicsPipeline::Back:
+ return VK_CULL_MODE_BACK_BIT;
+ default:
+ Q_UNREACHABLE();
+ return VK_CULL_MODE_NONE;
+ }
+}
+
+static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::CCW:
+ return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ case QRhiGraphicsPipeline::CW:
+ return VK_FRONT_FACE_CLOCKWISE;
+ default:
+ Q_UNREACHABLE();
+ return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ }
+}
+
+static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c)
+{
+ int f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= VK_COLOR_COMPONENT_R_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= VK_COLOR_COMPONENT_G_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= VK_COLOR_COMPONENT_B_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= VK_COLOR_COMPONENT_A_BIT;
+ return VkColorComponentFlags(f);
+}
+
+static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return VK_BLEND_FACTOR_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return VK_BLEND_FACTOR_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return VK_BLEND_FACTOR_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return VK_BLEND_FACTOR_DST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return VK_BLEND_FACTOR_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return VK_BLEND_FACTOR_DST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return VK_BLEND_FACTOR_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return VK_BLEND_FACTOR_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
+ case QRhiGraphicsPipeline::Src1Color:
+ return VK_BLEND_FACTOR_SRC1_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return VK_BLEND_FACTOR_SRC1_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
+ default:
+ Q_UNREACHABLE();
+ return VK_BLEND_FACTOR_ZERO;
+ }
+}
+
+static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return VK_BLEND_OP_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return VK_BLEND_OP_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return VK_BLEND_OP_REVERSE_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return VK_BLEND_OP_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return VK_BLEND_OP_MAX;
+ default:
+ Q_UNREACHABLE();
+ return VK_BLEND_OP_ADD;
+ }
+}
+
+static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return VK_COMPARE_OP_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return VK_COMPARE_OP_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return VK_COMPARE_OP_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return VK_COMPARE_OP_LESS_OR_EQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return VK_COMPARE_OP_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return VK_COMPARE_OP_NOT_EQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return VK_COMPARE_OP_GREATER_OR_EQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return VK_COMPARE_OP_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return VK_COMPARE_OP_ALWAYS;
+ }
+}
+
+static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return VK_STENCIL_OP_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return VK_STENCIL_OP_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return VK_STENCIL_OP_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
+ case QRhiGraphicsPipeline::Invert:
+ return VK_STENCIL_OP_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return VK_STENCIL_OP_INCREMENT_AND_WRAP;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return VK_STENCIL_OP_DECREMENT_AND_WRAP;
+ default:
+ Q_UNREACHABLE();
+ return VK_STENCIL_OP_KEEP;
+ }
+}
+
+static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src)
+{
+ dst->failOp = toVkStencilOp(src.failOp);
+ dst->passOp = toVkStencilOp(src.passOp);
+ dst->depthFailOp = toVkStencilOp(src.depthFailOp);
+ dst->compareOp = toVkCompareOp(src.compareOp);
+}
+
+static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBindingPrivate *b)
+{
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
+ : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ case QRhiShaderResourceBinding::SampledTexture:
+ return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ default:
+ Q_UNREACHABLE();
+ return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ }
+}
+
+static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage)
+{
+ int s = 0;
+ if (stage.testFlag(QRhiShaderResourceBinding::VertexStage))
+ s |= VK_SHADER_STAGE_VERTEX_BIT;
+ if (stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ s |= VK_SHADER_STAGE_FRAGMENT_BIT;
+ return VkShaderStageFlags(s);
+}
+
+static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return VK_COMPARE_OP_NEVER;
+ case QRhiSampler::Less:
+ return VK_COMPARE_OP_LESS;
+ case QRhiSampler::Equal:
+ return VK_COMPARE_OP_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return VK_COMPARE_OP_LESS_OR_EQUAL;
+ case QRhiSampler::Greater:
+ return VK_COMPARE_OP_GREATER;
+ case QRhiSampler::NotEqual:
+ return VK_COMPARE_OP_NOT_EQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return VK_COMPARE_OP_GREATER_OR_EQUAL;
+ case QRhiSampler::Always:
+ return VK_COMPARE_OP_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return VK_COMPARE_OP_NEVER;
+ }
+}
+
+QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE;
+ allocations[i] = stagingAllocations[i] = nullptr;
+ }
+}
+
+QVkBuffer::~QVkBuffer()
+{
+ release();
+}
+
+void QVkBuffer::release()
+{
+ if (!buffers[0])
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Buffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ e.buffer.buffers[i] = buffers[i];
+ e.buffer.allocations[i] = allocations[i];
+ e.buffer.stagingBuffers[i] = stagingBuffers[i];
+ e.buffer.stagingAllocations[i] = stagingAllocations[i];
+
+ buffers[i] = VK_NULL_HANDLE;
+ allocations[i] = nullptr;
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ pendingDynamicUpdates[i].clear();
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkBuffer::build()
+{
+ if (buffers[0])
+ release();
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = nonZeroSize;
+ bufferInfo.usage = toVkBufferUsage(m_usage);
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+
+ if (m_type == Dynamic) {
+#ifndef Q_OS_DARWIN // not for MoltenVK
+ // Keep mapped all the time. Essential f.ex. with some mobile GPUs,
+ // where mapping and unmapping an entire allocation every time updating
+ // a suballocated buffer presents a significant perf. hit.
+ allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
+#endif
+ // host visible, frequent changes
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
+ } else {
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+ bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = VK_SUCCESS;
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ buffers[i] = VK_NULL_HANDLE;
+ allocations[i] = nullptr;
+ usageState[i].access = usageState[i].stage = 0;
+ if (i == 0 || m_type == Dynamic) {
+ VmaAllocation allocation;
+ err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr);
+ if (err != VK_SUCCESS)
+ break;
+
+ allocations[i] = allocation;
+ if (m_type == Dynamic)
+ pendingDynamicUpdates[i].reserve(16);
+
+ rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName,
+ m_type == Dynamic ? i : -1);
+ }
+ }
+
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create buffer: %d", err);
+ return false;
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, nonZeroSize, m_type != Dynamic ? 1 : QVK_FRAMES_IN_FLIGHT, 0));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QVkRenderBuffer::~QVkRenderBuffer()
+{
+ release();
+ delete backingTexture;
+}
+
+void QVkRenderBuffer::release()
+{
+ if (!memory && !backingTexture)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::RenderBuffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderBuffer.memory = memory;
+ e.renderBuffer.image = image;
+ e.renderBuffer.imageView = imageView;
+
+ memory = VK_NULL_HANDLE;
+ image = VK_NULL_HANDLE;
+ imageView = VK_NULL_HANDLE;
+
+ if (backingTexture) {
+ Q_ASSERT(backingTexture->lastActiveFrameSlot == -1);
+ backingTexture->lastActiveFrameSlot = e.lastActiveFrameSlot;
+ backingTexture->release();
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkRenderBuffer::build()
+{
+ if (memory || backingTexture)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QRHI_PROF;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ switch (m_type) {
+ case QRhiRenderBuffer::Color:
+ {
+ if (!backingTexture) {
+ backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(QRhiTexture::RGBA8,
+ m_pixelSize,
+ m_sampleCount,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ } else {
+ backingTexture->setPixelSize(m_pixelSize);
+ backingTexture->setSampleCount(m_sampleCount);
+ }
+ backingTexture->setName(m_objectName);
+ if (!backingTexture->build())
+ return false;
+ vkformat = backingTexture->vkformat;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ }
+ break;
+ case QRhiRenderBuffer::DepthStencil:
+ vkformat = rhiD->optimalDepthStencilFormat();
+ if (!rhiD->createTransientImage(vkformat,
+ m_pixelSize,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+ samples,
+ &memory,
+ &image,
+ &imageView,
+ 1))
+ {
+ return false;
+ }
+ rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName);
+ QRHI_PROF_F(newRenderBuffer(this, true, false, samples));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ lastActiveFrameSlot = -1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QVkRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ }
+}
+
+QVkTexture::~QVkTexture()
+{
+ release();
+}
+
+void QVkTexture::release()
+{
+ if (!image)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Texture;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.texture.image = owns ? image : VK_NULL_HANDLE;
+ e.texture.imageView = imageView;
+ e.texture.allocation = owns ? imageAlloc : nullptr;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ e.texture.stagingBuffers[i] = stagingBuffers[i];
+ e.texture.stagingAllocations[i] = stagingAllocations[i];
+
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ }
+
+ image = VK_NULL_HANDLE;
+ imageView = VK_NULL_HANDLE;
+ imageAlloc = nullptr;
+ nativeHandlesStruct.image = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkTexture::prepareBuild(QSize *adjustedSize)
+{
+ if (image)
+ release();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ vkformat = toVkTextureFormat(m_format, m_flags);
+ VkFormatProperties props;
+ rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props);
+ const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
+ if (!canSampleOptimal) {
+ qWarning("Texture sampling with optimal tiling for format %d not supported", vkformat);
+ return false;
+ }
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ const int maxLevels = QRhi::MAX_LEVELS;
+ if (mipLevelCount > maxLevels) {
+ qWarning("Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels);
+ mipLevelCount = maxLevels;
+ }
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+
+ usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+ usageState.access = 0;
+ usageState.stage = 0;
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QVkTexture::finishBuild()
+{
+ QRHI_RES_RHI(QRhiVulkan);
+
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = image;
+ viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.levelCount = mipLevelCount;
+ viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
+
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image view: %d", err);
+ return false;
+ }
+
+ nativeHandlesStruct.image = image;
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+
+ return true;
+}
+
+bool QVkTexture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ const bool isRenderTarget = m_flags.testFlag(QRhiTexture::RenderTarget);
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ VkImageCreateInfo imageInfo;
+ memset(&imageInfo, 0, sizeof(imageInfo));
+ imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imageInfo.flags = isCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0;
+ imageInfo.imageType = VK_IMAGE_TYPE_2D;
+ imageInfo.format = vkformat;
+ imageInfo.extent.width = size.width();
+ imageInfo.extent.height = size.height();
+ imageInfo.extent.depth = 1;
+ imageInfo.mipLevels = mipLevelCount;
+ imageInfo.arrayLayers = isCube ? 6 : 1;
+ imageInfo.samples = samples;
+ imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+
+ imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ if (isRenderTarget) {
+ if (isDepth)
+ imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ else
+ imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ }
+ if (m_flags.testFlag(QRhiTexture::UsedAsTransferSource))
+ imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (m_flags.testFlag(QRhiTexture::UsedWithGenerateMips))
+ imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VmaAllocation allocation;
+ VkResult err = vmaCreateImage(toVmaAllocator(rhiD->allocator), &imageInfo, &allocInfo, &image, &allocation, nullptr);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image: %d", err);
+ return false;
+ }
+ imageAlloc = allocation;
+
+ if (!finishBuild())
+ return false;
+
+ rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName);
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
+
+ owns = true;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QVkTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiVulkanTextureNativeHandles *h = static_cast<const QRhiVulkanTextureNativeHandles *>(src);
+ if (!h || !h->image)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ image = h->image;
+
+ if (!finishBuild())
+ return false;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
+
+ usageState.layout = h->layout;
+
+ owns = false;
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QVkTexture::nativeHandles()
+{
+ nativeHandlesStruct.layout = usageState.layout;
+ return &nativeHandlesStruct;
+}
+
+QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QVkSampler::~QVkSampler()
+{
+ release();
+}
+
+void QVkSampler::release()
+{
+ if (!sampler)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Sampler;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.sampler.sampler = sampler;
+ sampler = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+ rhiD->unregisterResource(this);
+}
+
+bool QVkSampler::build()
+{
+ if (sampler)
+ release();
+
+ VkSamplerCreateInfo samplerInfo;
+ memset(&samplerInfo, 0, sizeof(samplerInfo));
+ samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+ samplerInfo.magFilter = toVkFilter(m_magFilter);
+ samplerInfo.minFilter = toVkFilter(m_minFilter);
+ samplerInfo.mipmapMode = toVkMipmapMode(m_mipmapMode);
+ samplerInfo.addressModeU = toVkAddressMode(m_addressU);
+ samplerInfo.addressModeV = toVkAddressMode(m_addressV);
+ samplerInfo.addressModeW = toVkAddressMode(m_addressW);
+ samplerInfo.maxAnisotropy = 1.0f;
+ samplerInfo.compareEnable = m_compareOp != Never;
+ samplerInfo.compareOp = toVkTextureCompareOp(m_compareOp);
+ samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create sampler: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QVkRenderPassDescriptor::~QVkRenderPassDescriptor()
+{
+ release();
+}
+
+void QVkRenderPassDescriptor::release()
+{
+ if (!rp)
+ return;
+
+ if (!ownsRp) {
+ rp = VK_NULL_HANDLE;
+ return;
+ }
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::RenderPass;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderPass.rp = rp;
+
+ rp = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QVkReferenceRenderTarget::QVkReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi)
+{
+}
+
+QVkReferenceRenderTarget::~QVkReferenceRenderTarget()
+{
+ release();
+}
+
+void QVkReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QVkReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QVkReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QVkReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QVkTextureRenderTarget::QVkTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags)
+{
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ rtv[att] = VK_NULL_HANDLE;
+ resrtv[att] = VK_NULL_HANDLE;
+ }
+}
+
+QVkTextureRenderTarget::~QVkTextureRenderTarget()
+{
+ release();
+}
+
+void QVkTextureRenderTarget::release()
+{
+ if (!d.fb)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.textureRenderTarget.fb = d.fb;
+ d.fb = VK_NULL_HANDLE;
+
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ e.textureRenderTarget.rtv[att] = rtv[att];
+ e.textureRenderTarget.resrtv[att] = resrtv[att];
+ rtv[att] = VK_NULL_HANDLE;
+ resrtv[att] = VK_NULL_HANDLE;
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ // not yet built so cannot rely on data computed in build()
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
+ if (!rhiD->createOffscreenRenderPass(&rp->rp,
+ m_desc.colorAttachments(),
+ m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
+ m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents),
+ m_desc.depthStencilBuffer(),
+ m_desc.depthTexture()))
+ {
+ delete rp;
+ return nullptr;
+ }
+
+ rp->ownsRp = true;
+ rhiD->registerResource(rp);
+ return rp;
+}
+
+bool QVkTextureRenderTarget::build()
+{
+ if (d.fb)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVarLengthArray<VkImageView, 8> views;
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachments[i].texture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ if (texD) {
+ Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget));
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = texD->image;
+ viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = texD->vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = colorAttachments[i].level();
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = colorAttachments[i].layer();
+ viewInfo.subresourceRange.layerCount = 1;
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[i]);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target image view: %d", err);
+ return false;
+ }
+ views.append(rtv[i]);
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = texD->samples;
+ }
+ } else if (rbD) {
+ Q_ASSERT(rbD->backingTexture);
+ views.append(rbD->backingTexture->imageView);
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->samples;
+ }
+ }
+ }
+ d.dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture());
+ views.append(depthTexD->imageView);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = depthTexD->samples;
+ }
+ } else {
+ QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer());
+ views.append(depthRbD->imageView);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthRbD->pixelSize();
+ d.sampleCount = depthRbD->samples;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ d.resolveAttCount = 0;
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ if (colorAttachments[i].resolveTexture()) {
+ QVkTexture *resTexD = QRHI_RES(QVkTexture, colorAttachments[i].resolveTexture());
+ Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
+ d.resolveAttCount += 1;
+
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = resTexD->image;
+ viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = resTexD->vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = colorAttachments[i].resolveLevel();
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = colorAttachments[i].resolveLayer();
+ viewInfo.subresourceRange.layerCount = 1;
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[i]);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target resolve image view: %d", err);
+ return false;
+ }
+ views.append(resrtv[i]);
+ }
+ }
+
+ if (!m_renderPassDesc)
+ qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
+
+ d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
+ Q_ASSERT(d.rp && d.rp->rp);
+
+ VkFramebufferCreateInfo fbInfo;
+ memset(&fbInfo, 0, sizeof(fbInfo));
+ fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbInfo.renderPass = d.rp->rp;
+ fbInfo.attachmentCount = d.colorAttCount + d.dsAttCount + d.resolveAttCount;
+ fbInfo.pAttachments = views.constData();
+ fbInfo.width = d.pixelSize.width();
+ fbInfo.height = d.pixelSize.height();
+ fbInfo.layers = 1;
+
+ VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create framebuffer: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QVkTextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QVkTextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QVkTextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QVkShaderResourceBindings::QVkShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QVkShaderResourceBindings::~QVkShaderResourceBindings()
+{
+ release();
+}
+
+void QVkShaderResourceBindings::release()
+{
+ if (!layout)
+ return;
+
+ sortedBindings.clear();
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.shaderResourceBindings.poolIndex = poolIndex;
+ e.shaderResourceBindings.layout = layout;
+
+ poolIndex = -1;
+ layout = VK_NULL_HANDLE;
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ descSets[i] = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkShaderResourceBindings::build()
+{
+ if (layout)
+ release();
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ descSets[i] = VK_NULL_HANDLE;
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+
+ QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings;
+ for (const QRhiShaderResourceBinding &binding : qAsConst(sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ VkDescriptorSetLayoutBinding vkbinding;
+ memset(&vkbinding, 0, sizeof(vkbinding));
+ vkbinding.binding = b->binding;
+ vkbinding.descriptorType = toVkDescriptorType(b);
+ vkbinding.descriptorCount = 1; // no array support yet
+ vkbinding.stageFlags = toVkShaderStageFlags(b->stage);
+ vkbindings.append(vkbinding);
+ }
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo;
+ memset(&layoutInfo, 0, sizeof(layoutInfo));
+ layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ layoutInfo.bindingCount = uint32_t(vkbindings.count());
+ layoutInfo.pBindings = vkbindings.constData();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create descriptor set layout: %d", err);
+ return false;
+ }
+
+ VkDescriptorSetAllocateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT;
+ VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT];
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ layouts[i] = layout;
+ allocInfo.pSetLayouts = layouts;
+ if (!rhiD->allocateDescriptorSet(&allocInfo, descSets, &poolIndex))
+ return false;
+
+ rhiD->updateShaderResourceBindings(this);
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QVkGraphicsPipeline::~QVkGraphicsPipeline()
+{
+ release();
+}
+
+void QVkGraphicsPipeline::release()
+{
+ if (!pipeline && !layout)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.pipelineState.pipeline = pipeline;
+ e.pipelineState.layout = layout;
+
+ pipeline = VK_NULL_HANDLE;
+ layout = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkGraphicsPipeline::build()
+{
+ if (pipeline)
+ release();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ if (!rhiD->ensurePipelineCache())
+ return false;
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo;
+ memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo));
+ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipelineLayoutInfo.setLayoutCount = 1;
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
+ Q_ASSERT(m_shaderResourceBindings && srbD->layout);
+ pipelineLayoutInfo.pSetLayouts = &srbD->layout;
+ VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create pipeline layout: %d", err);
+ return false;
+ }
+
+ VkGraphicsPipelineCreateInfo pipelineInfo;
+ memset(&pipelineInfo, 0, sizeof(pipelineInfo));
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+
+ QVarLengthArray<VkShaderModule, 4> shaders;
+ QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos;
+ for (const QRhiGraphicsShaderStage &shaderStage : m_shaderStages) {
+ const QShader bakedShader = shaderStage.shader();
+ const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, shaderStage.shaderVariant() });
+ if (spirv.shader().isEmpty()) {
+ qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
+ return false;
+ }
+ VkShaderModule shader = rhiD->createShader(spirv.shader());
+ if (shader) {
+ shaders.append(shader);
+ VkPipelineShaderStageCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderInfo.stage = toVkShaderStage(shaderStage.type());
+ shaderInfo.module = shader;
+ shaderInfo.pName = spirv.entryPoint().constData();
+ shaderStageCreateInfos.append(shaderInfo);
+ }
+ }
+ pipelineInfo.stageCount = shaderStageCreateInfos.count();
+ pipelineInfo.pStages = shaderStageCreateInfos.constData();
+
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings;
+ QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates;
+ for (int i = 0, ie = bindings.count(); i != ie; ++i) {
+ const QRhiVertexInputBinding &binding(bindings[i]);
+ VkVertexInputBindingDescription bindingInfo = {
+ uint32_t(i),
+ binding.stride(),
+ binding.classification() == QRhiVertexInputBinding::PerVertex
+ ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE
+ };
+ if (binding.classification() == QRhiVertexInputBinding::PerInstance
+ && binding.instanceStepRate() != 1)
+ {
+ if (rhiD->vertexAttribDivisorAvailable) {
+ nonOneStepRates.append({ uint32_t(i), uint32_t(binding.instanceStepRate()) });
+ } else {
+ qWarning("QRhiVulkan: Instance step rates other than 1 not supported without "
+ "VK_EXT_vertex_attribute_divisor on the device and "
+ "VK_KHR_get_physical_device_properties2 on the instance");
+ }
+ }
+ vertexBindings.append(bindingInfo);
+ }
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes;
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ VkVertexInputAttributeDescription attributeInfo = {
+ uint32_t(attribute.location()),
+ uint32_t(attribute.binding()),
+ toVkAttributeFormat(attribute.format()),
+ attribute.offset()
+ };
+ vertexAttributes.append(attributeInfo);
+ }
+ VkPipelineVertexInputStateCreateInfo vertexInputInfo;
+ memset(&vertexInputInfo, 0, sizeof(vertexInputInfo));
+ vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ vertexInputInfo.vertexBindingDescriptionCount = vertexBindings.count();
+ vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData();
+ vertexInputInfo.vertexAttributeDescriptionCount = vertexAttributes.count();
+ vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData();
+ VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo;
+ if (!nonOneStepRates.isEmpty()) {
+ memset(&divisorInfo, 0, sizeof(divisorInfo));
+ divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
+ divisorInfo.vertexBindingDivisorCount = nonOneStepRates.count();
+ divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData();
+ vertexInputInfo.pNext = &divisorInfo;
+ }
+ pipelineInfo.pVertexInputState = &vertexInputInfo;
+
+ QVarLengthArray<VkDynamicState, 8> dynEnable;
+ dynEnable << VK_DYNAMIC_STATE_VIEWPORT;
+ dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always
+ if (m_flags.testFlag(QRhiGraphicsPipeline::UsesBlendConstants))
+ dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS;
+ if (m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef))
+ dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE;
+
+ VkPipelineDynamicStateCreateInfo dynamicInfo;
+ memset(&dynamicInfo, 0, sizeof(dynamicInfo));
+ dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+ dynamicInfo.dynamicStateCount = dynEnable.count();
+ dynamicInfo.pDynamicStates = dynEnable.constData();
+ pipelineInfo.pDynamicState = &dynamicInfo;
+
+ VkPipelineViewportStateCreateInfo viewportInfo;
+ memset(&viewportInfo, 0, sizeof(viewportInfo));
+ viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ viewportInfo.viewportCount = viewportInfo.scissorCount = 1;
+ pipelineInfo.pViewportState = &viewportInfo;
+
+ VkPipelineInputAssemblyStateCreateInfo inputAsmInfo;
+ memset(&inputAsmInfo, 0, sizeof(inputAsmInfo));
+ inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ inputAsmInfo.topology = toVkTopology(m_topology);
+ inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip);
+ pipelineInfo.pInputAssemblyState = &inputAsmInfo;
+
+ VkPipelineRasterizationStateCreateInfo rastInfo;
+ memset(&rastInfo, 0, sizeof(rastInfo));
+ rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ rastInfo.cullMode = toVkCullMode(m_cullMode);
+ rastInfo.frontFace = toVkFrontFace(m_frontFace);
+ rastInfo.lineWidth = 1.0f;
+ pipelineInfo.pRasterizationState = &rastInfo;
+
+ VkPipelineMultisampleStateCreateInfo msInfo;
+ memset(&msInfo, 0, sizeof(msInfo));
+ msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ msInfo.rasterizationSamples = rhiD->effectiveSampleCount(m_sampleCount);
+ pipelineInfo.pMultisampleState = &msInfo;
+
+ VkPipelineDepthStencilStateCreateInfo dsInfo;
+ memset(&dsInfo, 0, sizeof(dsInfo));
+ dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+ dsInfo.depthTestEnable = m_depthTest;
+ dsInfo.depthWriteEnable = m_depthWrite;
+ dsInfo.depthCompareOp = toVkCompareOp(m_depthOp);
+ dsInfo.stencilTestEnable = m_stencilTest;
+ if (m_stencilTest) {
+ fillVkStencilOpState(&dsInfo.front, m_stencilFront);
+ dsInfo.front.compareMask = m_stencilReadMask;
+ dsInfo.front.writeMask = m_stencilWriteMask;
+ fillVkStencilOpState(&dsInfo.back, m_stencilBack);
+ dsInfo.back.compareMask = m_stencilReadMask;
+ dsInfo.back.writeMask = m_stencilWriteMask;
+ }
+ pipelineInfo.pDepthStencilState = &dsInfo;
+
+ VkPipelineColorBlendStateCreateInfo blendInfo;
+ memset(&blendInfo, 0, sizeof(blendInfo));
+ blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends;
+ for (const QRhiGraphicsPipeline::TargetBlend &b : qAsConst(m_targetBlends)) {
+ VkPipelineColorBlendAttachmentState blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.blendEnable = b.enable;
+ blend.srcColorBlendFactor = toVkBlendFactor(b.srcColor);
+ blend.dstColorBlendFactor = toVkBlendFactor(b.dstColor);
+ blend.colorBlendOp = toVkBlendOp(b.opColor);
+ blend.srcAlphaBlendFactor = toVkBlendFactor(b.srcAlpha);
+ blend.dstAlphaBlendFactor = toVkBlendFactor(b.dstAlpha);
+ blend.alphaBlendOp = toVkBlendOp(b.opAlpha);
+ blend.colorWriteMask = toVkColorComponents(b.colorWrite);
+ vktargetBlends.append(blend);
+ }
+ if (vktargetBlends.isEmpty()) {
+ VkPipelineColorBlendAttachmentState blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
+ | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+ vktargetBlends.append(blend);
+ }
+ blendInfo.attachmentCount = vktargetBlends.count();
+ blendInfo.pAttachments = vktargetBlends.constData();
+ pipelineInfo.pColorBlendState = &blendInfo;
+
+ pipelineInfo.layout = layout;
+
+ Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp);
+ pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp;
+
+ err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
+
+ for (VkShaderModule shader : shaders)
+ rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
+
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create graphics pipeline: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QVkCommandBuffer::~QVkCommandBuffer()
+{
+ release();
+}
+
+void QVkCommandBuffer::release()
+{
+ // nothing to do here, cb is not owned by us
+}
+
+QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rtWrapper(rhi),
+ cbWrapper(rhi)
+{
+}
+
+QVkSwapChain::~QVkSwapChain()
+{
+ release();
+}
+
+void QVkSwapChain::release()
+{
+ if (sc == VK_NULL_HANDLE)
+ return;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->swapchains.remove(this);
+ rhiD->releaseSwapChainResources(this);
+ surface = lastConnectedSurface = VK_NULL_HANDLE;
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer()
+{
+ return &cbWrapper;
+}
+
+QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget()
+{
+ return &rtWrapper;
+}
+
+QSize QVkSwapChain::surfacePixelSize()
+{
+ if (!ensureSurface())
+ return QSize();
+
+ // The size from the QWindow may not exactly match the surface... so if a
+ // size is reported from the surface, use that.
+ VkSurfaceCapabilitiesKHR surfaceCaps;
+ memset(&surfaceCaps, 0, sizeof(surfaceCaps));
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps);
+ VkExtent2D bufferSize = surfaceCaps.currentExtent;
+ if (bufferSize.width == quint32(-1)) {
+ Q_ASSERT(bufferSize.height == quint32(-1));
+ return m_window->size() * m_window->devicePixelRatio();
+ }
+ return QSize(bufferSize.width, bufferSize.height);
+}
+
+QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor()
+{
+ // not yet built so cannot rely on data computed in buildOrResize()
+
+ if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested
+ return nullptr;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
+ if (!rhiD->createDefaultRenderPass(&rp->rp,
+ m_depthStencil != nullptr,
+ samples,
+ colorFormat))
+ {
+ delete rp;
+ return nullptr;
+ }
+
+ rp->ownsRp = true;
+ rhiD->registerResource(rp);
+ return rp;
+}
+
+static inline bool isSrgbFormat(VkFormat format)
+{
+ switch (format) {
+ case VK_FORMAT_R8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8B8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_B8G8R8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_B8G8R8A8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QVkSwapChain::ensureSurface()
+{
+ // Do nothing when already done, however window may change so check the
+ // surface is still the same. Some of the queries below are very expensive
+ // with some implementations so it is important to do the rest only once
+ // per surface.
+
+ Q_ASSERT(m_window);
+ VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(m_window);
+ if (!surf) {
+ qWarning("Failed to get surface for window");
+ return false;
+ }
+ if (surface == surf)
+ return true;
+
+ surface = surf;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ if (rhiD->gfxQueueFamilyIdx != -1) {
+ if (!rhiD->inst->supportsPresent(rhiD->physDev, rhiD->gfxQueueFamilyIdx, m_window)) {
+ qWarning("Presenting not supported on this window");
+ return false;
+ }
+ }
+
+ if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR) {
+ rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR"));
+ if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR
+ || !rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR
+ || !rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR)
+ {
+ qWarning("Physical device surface queries not available");
+ return false;
+ }
+ }
+
+ quint32 formatCount = 0;
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr);
+ QVector<VkSurfaceFormatKHR> formats(formatCount);
+ if (formatCount)
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data());
+
+ const bool srgbRequested = m_flags.testFlag(sRGB);
+ for (quint32 i = 0; i < formatCount; ++i) {
+ if (formats[i].format != VK_FORMAT_UNDEFINED && srgbRequested == isSrgbFormat(formats[i].format)) {
+ colorFormat = formats[i].format;
+ colorSpace = formats[i].colorSpace;
+ break;
+ }
+ }
+
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ quint32 presModeCount = 0;
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr);
+ QVector<VkPresentModeKHR> presModes(presModeCount);
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, presModes.data());
+ supportedPresentationModes = presModes;
+
+ return true;
+}
+
+bool QVkSwapChain::buildOrResize()
+{
+ QRHI_RES_RHI(QRhiVulkan);
+ const bool needsRegistration = !window || window != m_window;
+
+ // Can be called multiple times due to window resizes - that is not the
+ // same as a simple release+build (as with other resources). Thus no
+ // release() here. See recreateSwapChain().
+
+ // except if the window actually changes
+ if (window && window != m_window)
+ release();
+
+ window = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ if (!rhiD->recreateSwapChain(this)) {
+ qWarning("Failed to create new swapchain");
+ return false;
+ }
+
+ if (needsRegistration)
+ rhiD->swapchains.insert(this);
+
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ if (!m_renderPassDesc)
+ qWarning("QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
+
+ rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
+ Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp);
+
+ rtWrapper.d.pixelSize = pixelSize;
+ rtWrapper.d.dpr = window->devicePixelRatio();
+ rtWrapper.d.sampleCount = samples;
+ rtWrapper.d.colorAttCount = 1;
+ if (m_depthStencil) {
+ rtWrapper.d.dsAttCount = 1;
+ ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
+ } else {
+ rtWrapper.d.dsAttCount = 0;
+ ds = nullptr;
+ }
+ if (samples > VK_SAMPLE_COUNT_1_BIT)
+ rtWrapper.d.resolveAttCount = 1;
+ else
+ rtWrapper.d.resolveAttCount = 0;
+
+ for (int i = 0; i < bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(imageRes[i]);
+ VkImageView views[3] = { // color, ds, resolve
+ samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView,
+ ds ? ds->imageView : VK_NULL_HANDLE,
+ samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE
+ };
+
+ VkFramebufferCreateInfo fbInfo;
+ memset(&fbInfo, 0, sizeof(fbInfo));
+ fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbInfo.renderPass = rtWrapper.d.rp->rp;
+ fbInfo.attachmentCount = rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount;
+ fbInfo.pAttachments = views;
+ fbInfo.width = pixelSize.width();
+ fbInfo.height = pixelSize.height();
+ fbInfo.layers = 1;
+
+ VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create framebuffer: %d", err);
+ return false;
+ }
+ }
+
+ frameCount = 0;
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, QVK_FRAMES_IN_FLIGHT, samples > VK_SAMPLE_COUNT_1_BIT ? QVK_FRAMES_IN_FLIGHT : 0, samples));
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
new file mode 100644
index 0000000000..545ef5ad72
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKAN_H
+#define QRHIVULKAN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+#include <QtGui/qvulkaninstance.h> // this is where vulkan.h gets pulled in
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiVulkanInitParams : public QRhiInitParams
+{
+ QVulkanInstance *inst = nullptr;
+ QWindow *window = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanNativeHandles : public QRhiNativeHandles
+{
+ VkPhysicalDevice physDev = VK_NULL_HANDLE;
+ VkDevice dev = VK_NULL_HANDLE;
+ int gfxQueueFamilyIdx = -1;
+ VkQueue gfxQueue = VK_NULL_HANDLE;
+ VkCommandPool cmdPool = VK_NULL_HANDLE;
+ void *vmemAllocator = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanTextureNativeHandles : public QRhiNativeHandles
+{
+ VkImage image = VK_NULL_HANDLE;
+ VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanCommandBufferNativeHandles : public QRhiNativeHandles
+{
+ VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h
new file mode 100644
index 0000000000..01acc40d58
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan_p_p.h
@@ -0,0 +1,859 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKAN_P_H
+#define QRHIVULKAN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhivulkan_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QVulkanFunctions;
+class QVulkanDeviceFunctions;
+
+static const int QVK_FRAMES_IN_FLIGHT = 2;
+
+static const int QVK_DESC_SETS_PER_POOL = 128;
+static const int QVK_UNIFORM_BUFFERS_PER_POOL = 256;
+static const int QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL = 256;
+
+static const int QVK_MAX_ACTIVE_TIMESTAMP_PAIRS = 16;
+
+// no vk_mem_alloc.h available here, void* is good enough
+typedef void * QVkAlloc;
+typedef void * QVkAllocator;
+
+struct QVkBuffer : public QRhiBuffer
+{
+ QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QVkBuffer();
+ void release() override;
+ bool build() override;
+
+ VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT];
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ struct UsageState {
+ VkAccessFlags access = 0;
+ VkPipelineStageFlags stage = 0;
+ };
+ UsageState usageState[QVK_FRAMES_IN_FLIGHT];
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkTexture;
+
+struct QVkRenderBuffer : public QRhiRenderBuffer
+{
+ QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QVkRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ VkDeviceMemory memory = VK_NULL_HANDLE;
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ VkSampleCountFlagBits samples;
+ QVkTexture *backingTexture = nullptr;
+ VkFormat vkformat;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiVulkan;
+};
+
+struct QVkTexture : public QRhiTexture
+{
+ QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QVkTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+ bool finishBuild();
+
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ QVkAlloc imageAlloc = nullptr;
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ bool owns = true;
+ QRhiVulkanTextureNativeHandles nativeHandlesStruct;
+ struct UsageState {
+ // no tracking of subresource layouts (some operations can keep
+ // subresources in different layouts for some time, but that does not
+ // need to be kept track of)
+ VkImageLayout layout;
+ VkAccessFlags access;
+ VkPipelineStageFlags stage;
+ };
+ UsageState usageState;
+ VkFormat vkformat;
+ uint mipLevelCount = 0;
+ VkSampleCountFlagBits samples;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkSampler : public QRhiSampler
+{
+ QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QVkSampler();
+ void release() override;
+ bool build() override;
+
+ VkSampler sampler = VK_NULL_HANDLE;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QVkRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QVkRenderPassDescriptor();
+ void release() override;
+
+ VkRenderPass rp = VK_NULL_HANDLE;
+ bool ownsRp = false;
+ int lastActiveFrameSlot = -1;
+};
+
+struct QVkRenderTargetData
+{
+ VkFramebuffer fb = VK_NULL_HANDLE;
+ QVkRenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+ int resolveAttCount = 0;
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+};
+
+struct QVkReferenceRenderTarget : public QRhiRenderTarget
+{
+ QVkReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QVkReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QVkRenderTargetData d;
+};
+
+struct QVkTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QVkTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QVkTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QVkRenderTargetData d;
+ VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ int lastActiveFrameSlot = -1;
+ friend class QRhiVulkan;
+};
+
+struct QVkShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QVkShaderResourceBindings(QRhiImplementation *rhi);
+ ~QVkShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ int poolIndex = -1;
+ VkDescriptorSetLayout layout = VK_NULL_HANDLE;
+ VkDescriptorSet descSets[QVK_FRAMES_IN_FLIGHT]; // multiple sets to support dynamic buffers
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+
+ // Keep track of the generation number of each referenced QRhi* to be able
+ // to detect that the underlying descriptor set became out of date and they
+ // need to be written again with the up-to-date VkBuffer etc. objects.
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData[QVK_FRAMES_IN_FLIGHT];
+
+ friend class QRhiVulkan;
+};
+
+Q_DECLARE_TYPEINFO(QVkShaderResourceBindings::BoundResourceData, Q_MOVABLE_TYPE);
+
+struct QVkGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QVkGraphicsPipeline(QRhiImplementation *rhi);
+ ~QVkGraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ VkPipelineLayout layout = VK_NULL_HANDLE;
+ VkPipeline pipeline = VK_NULL_HANDLE;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkCommandBuffer : public QRhiCommandBuffer
+{
+ QVkCommandBuffer(QRhiImplementation *rhi);
+ ~QVkCommandBuffer();
+ void release() override;
+
+ VkCommandBuffer cb = VK_NULL_HANDLE;
+ QRhiVulkanCommandBufferNativeHandles nativeHandlesStruct;
+
+ const QRhiNativeHandles *nativeHandles() {
+ nativeHandlesStruct.commandBuffer = cb;
+ return &nativeHandlesStruct;
+ }
+
+ void resetState() {
+ resetCommands();
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+
+ void resetCachedState() {
+ currentPipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentDescSetSlot = -1;
+ currentIndexBuffer = VK_NULL_HANDLE;
+ currentIndexOffset = 0;
+ currentIndexFormat = VK_INDEX_TYPE_UINT16;
+ memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
+ memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
+ }
+
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentPipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentSrb;
+ uint currentSrbGeneration;
+ int currentDescSetSlot;
+ VkBuffer currentIndexBuffer;
+ quint32 currentIndexOffset;
+ VkIndexType currentIndexFormat;
+ static const int VERTEX_INPUT_RESOURCE_SLOT_COUNT = 32;
+ VkBuffer currentVertexBuffers[VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ quint32 currentVertexOffsets[VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+
+ struct Command {
+ enum Cmd {
+ CopyBuffer,
+ CopyBufferToImage,
+ CopyImage,
+ CopyImageToBuffer,
+ ImageBarrier,
+ BufferBarrier,
+ BlitImage,
+ BeginRenderPass,
+ EndRenderPass,
+ BindPipeline,
+ BindDescriptorSet,
+ BindVertexBuffer,
+ BindIndexBuffer,
+ SetViewport,
+ SetScissor,
+ SetBlendConstants,
+ SetStencilRef,
+ Draw,
+ DrawIndexed,
+ DebugMarkerBegin,
+ DebugMarkerEnd,
+ DebugMarkerInsert,
+ TransitionPassResources
+ };
+ Cmd cmd;
+
+ union Args {
+ struct {
+ VkBuffer src;
+ VkBuffer dst;
+ VkBufferCopy desc;
+ } copyBuffer;
+ struct {
+ VkBuffer src;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ int count;
+ int bufferImageCopyIndex;
+ } copyBufferToImage;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ VkImageCopy desc;
+ } copyImage;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkBuffer dst;
+ VkBufferImageCopy desc;
+ } copyImageToBuffer;
+ struct {
+ VkPipelineStageFlags srcStageMask;
+ VkPipelineStageFlags dstStageMask;
+ VkImageMemoryBarrier desc;
+ } imageBarrier;
+ struct {
+ VkPipelineStageFlags srcStageMask;
+ VkPipelineStageFlags dstStageMask;
+ VkBufferMemoryBarrier desc;
+ } bufferBarrier;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ VkFilter filter;
+ VkImageBlit desc;
+ } blitImage;
+ struct {
+ VkRenderPassBeginInfo desc;
+ int clearValueIndex;
+ } beginRenderPass;
+ struct {
+ } endRenderPass;
+ struct {
+ VkPipelineBindPoint bindPoint;
+ VkPipeline pipeline;
+ } bindPipeline;
+ struct {
+ VkPipelineBindPoint bindPoint;
+ VkPipelineLayout pipelineLayout;
+ VkDescriptorSet descSet;
+ int dynamicOffsetCount;
+ int dynamicOffsetIndex;
+ } bindDescriptorSet;
+ struct {
+ int startBinding;
+ int count;
+ int vertexBufferIndex;
+ int vertexBufferOffsetIndex;
+ } bindVertexBuffer;
+ struct {
+ VkBuffer buf;
+ VkDeviceSize ofs;
+ VkIndexType type;
+ } bindIndexBuffer;
+ struct {
+ VkViewport viewport;
+ } setViewport;
+ struct {
+ VkRect2D scissor;
+ } setScissor;
+ struct {
+ float c[4];
+ } setBlendConstants;
+ struct {
+ uint32_t ref;
+ } setStencilRef;
+ struct {
+ uint32_t vertexCount;
+ uint32_t instanceCount;
+ uint32_t firstVertex;
+ uint32_t firstInstance;
+ } draw;
+ struct {
+ uint32_t indexCount;
+ uint32_t instanceCount;
+ uint32_t firstIndex;
+ int32_t vertexOffset;
+ uint32_t firstInstance;
+ } drawIndexed;
+ struct {
+ VkDebugMarkerMarkerInfoEXT marker;
+ int markerNameIndex;
+ } debugMarkerBegin;
+ struct {
+ } debugMarkerEnd;
+ struct {
+ VkDebugMarkerMarkerInfoEXT marker;
+ } debugMarkerInsert;
+ struct {
+ int trackerIndex;
+ } transitionResources;
+ } args;
+ };
+ QVector<Command> commands;
+ QVarLengthArray<QRhiPassResourceTracker, 8> passResTrackers;
+ int currentPassResTrackerIndex;
+
+ void resetCommands() {
+ commands.clear();
+ passResTrackers.clear();
+ currentPassResTrackerIndex = -1;
+ resetPools();
+ }
+
+ void resetPools() {
+ pools.clearValue.clear();
+ pools.bufferImageCopy.clear();
+ pools.dynamicOffset.clear();
+ pools.vertexBuffer.clear();
+ pools.vertexBufferOffset.clear();
+ pools.debugMarkerName.clear();
+ }
+
+ struct {
+ QVarLengthArray<VkClearValue, 4> clearValue;
+ QVarLengthArray<VkBufferImageCopy, 16> bufferImageCopy;
+ QVarLengthArray<uint32_t, 4> dynamicOffset;
+ QVarLengthArray<VkBuffer, 4> vertexBuffer;
+ QVarLengthArray<VkDeviceSize, 4> vertexBufferOffset;
+ QVarLengthArray<QByteArray, 4> debugMarkerName;
+ } pools;
+
+ friend class QRhiVulkan;
+};
+
+Q_DECLARE_TYPEINFO(QVkCommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QVkSwapChain : public QRhiSwapChain
+{
+ QVkSwapChain(QRhiImplementation *rhi);
+ ~QVkSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ bool ensureSurface();
+
+ static const quint32 MAX_BUFFER_COUNT = 3;
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ bool supportsReadback = false;
+ VkSwapchainKHR sc = VK_NULL_HANDLE;
+ int bufferCount = 0;
+ VkSurfaceKHR surface = VK_NULL_HANDLE;
+ VkSurfaceKHR lastConnectedSurface = VK_NULL_HANDLE;
+ VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM;
+ VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+ QVkRenderBuffer *ds = nullptr;
+ VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
+ QVector<VkPresentModeKHR> supportedPresentationModes;
+ VkDeviceMemory msaaImageMem = VK_NULL_HANDLE;
+ QVkReferenceRenderTarget rtWrapper;
+ QVkCommandBuffer cbWrapper;
+
+ struct ImageResources {
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ VkFramebuffer fb = VK_NULL_HANDLE;
+ VkImage msaaImage = VK_NULL_HANDLE;
+ VkImageView msaaImageView = VK_NULL_HANDLE;
+ bool transferSource = false;
+ } imageRes[MAX_BUFFER_COUNT];
+
+ struct FrameResources {
+ VkFence imageFence = VK_NULL_HANDLE;
+ bool imageFenceWaitable = false;
+ VkSemaphore imageSem = VK_NULL_HANDLE;
+ VkSemaphore drawSem = VK_NULL_HANDLE;
+ bool imageAcquired = false;
+ bool imageSemWaitable = false;
+ quint32 imageIndex = 0;
+ VkCommandBuffer cmdBuf = VK_NULL_HANDLE;
+ VkFence cmdFence = VK_NULL_HANDLE;
+ bool cmdFenceWaitable = false;
+ int timestampQueryIndex = -1;
+ } frameRes[QVK_FRAMES_IN_FLIGHT];
+
+ quint32 currentImageIndex = 0; // index in imageRes
+ quint32 currentFrameSlot = 0; // index in frameRes
+ int frameCount = 0;
+
+ friend class QRhiVulkan;
+};
+
+class QRhiVulkan : public QRhiImplementation
+{
+public:
+ QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+
+ VkResult createDescriptorPool(VkDescriptorPool *pool);
+ bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex);
+ uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex);
+ bool createTransientImage(VkFormat format, const QSize &pixelSize, VkImageUsageFlags usage,
+ VkImageAspectFlags aspectMask, VkSampleCountFlagBits samples,
+ VkDeviceMemory *mem, VkImage *images, VkImageView *views, int count);
+
+ bool recreateSwapChain(QRhiSwapChain *swapChain);
+ void releaseSwapChainResources(QRhiSwapChain *swapChain);
+
+ VkFormat optimalDepthStencilFormat();
+ VkSampleCountFlagBits effectiveSampleCount(int sampleCount);
+ bool createDefaultRenderPass(VkRenderPass *rp,
+ bool hasDepthStencil,
+ VkSampleCountFlagBits samples,
+ VkFormat colorFormat);
+ bool createOffscreenRenderPass(VkRenderPass *rp,
+ const QVector<QRhiColorAttachment> &colorAttachments,
+ bool preserveColor,
+ bool preserveDs,
+ QRhiRenderBuffer *depthStencilBuffer,
+ QRhiTexture *depthTexture);
+ bool ensurePipelineCache();
+ VkShaderModule createShader(const QByteArray &spirv);
+
+ void prepareNewFrame(QRhiCommandBuffer *cb);
+ QRhi::FrameOpResult startCommandBuffer(VkCommandBuffer *cb);
+ QRhi::FrameOpResult endAndSubmitCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
+ VkSemaphore *waitSem, VkSemaphore *signalSem);
+ void waitCommandCompletion(int frameSlot);
+ VkDeviceSize subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const;
+ using BufferImageCopyList = QVarLengthArray<VkBufferImageCopy, 16>;
+ void prepareUploadSubres(QVkTexture *texD, int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &subresDesc,
+ size_t *curOfs, void *mp,
+ BufferImageCopyList *copyInfos);
+ void enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD);
+ void enqueueTransitionPassResources(QVkCommandBuffer *cbD);
+ void recordCommandBuffer(QVkCommandBuffer *cbD);
+ void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
+ QVkBuffer *bufD,
+ int slot,
+ QRhiPassResourceTracker::BufferAccess access,
+ QRhiPassResourceTracker::BufferStage stage);
+ void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
+ QVkTexture *texD,
+ QRhiPassResourceTracker::TextureAccess access,
+ QRhiPassResourceTracker::TextureStage stage);
+ void recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker);
+ void activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD);
+ void executeDeferredReleases(bool forced = false);
+ void finishActiveReadbacks(bool forced = false);
+
+ void setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot = -1);
+ void trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot,
+ VkAccessFlags access, VkPipelineStageFlags stage);
+ void trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD,
+ VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage);
+ void subresourceBarrier(QVkCommandBuffer *cbD, VkImage image,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ VkAccessFlags srcAccess, VkAccessFlags dstAccess,
+ VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
+ int startLayer, int layerCount,
+ int startLevel, int levelCount);
+ void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1);
+
+ QVulkanInstance *inst = nullptr;
+ QWindow *maybeWindow = nullptr;
+ bool importedDevice = false;
+ VkPhysicalDevice physDev = VK_NULL_HANDLE;
+ VkDevice dev = VK_NULL_HANDLE;
+ bool importedCmdPool = false;
+ VkCommandPool cmdPool = VK_NULL_HANDLE;
+ int gfxQueueFamilyIdx = -1;
+ VkQueue gfxQueue = VK_NULL_HANDLE;
+ quint32 timestampValidBits = 0;
+ bool importedAllocator = false;
+ QVkAllocator allocator = nullptr;
+ QVulkanFunctions *f = nullptr;
+ QVulkanDeviceFunctions *df = nullptr;
+ VkPhysicalDeviceProperties physDevProperties;
+ VkDeviceSize ubufAlign;
+ VkDeviceSize texbufAlign;
+
+ bool debugMarkersAvailable = false;
+ bool vertexAttribDivisorAvailable = false;
+ PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBegin = nullptr;
+ PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEnd = nullptr;
+ PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsert = nullptr;
+ PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectName = nullptr;
+
+ PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr;
+ PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
+ PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
+ PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
+ PFN_vkQueuePresentKHR vkQueuePresentKHR;
+ PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
+ PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
+ PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
+
+ VkPipelineCache pipelineCache = VK_NULL_HANDLE;
+ struct DescriptorPoolData {
+ DescriptorPoolData() { }
+ DescriptorPoolData(VkDescriptorPool pool_)
+ : pool(pool_)
+ { }
+ VkDescriptorPool pool = VK_NULL_HANDLE;
+ int refCount = 0;
+ int allocedDescSets = 0;
+ };
+ QVector<DescriptorPoolData> descriptorPools;
+
+ VkQueryPool timestampQueryPool = VK_NULL_HANDLE;
+ QBitArray timestampQueryPoolMap;
+
+ VkFormat optimalDsFormat = VK_FORMAT_UNDEFINED;
+ QMatrix4x4 clipCorrectMatrix;
+
+ bool inFrame = false;
+ bool inPass = false;
+ QVkSwapChain *currentSwapChain = nullptr;
+ QSet<QVkSwapChain *> swapchains;
+ QRhiVulkanNativeHandles nativeHandlesStruct;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QVkCommandBuffer cbWrapper;
+ VkFence cmdFence = VK_NULL_HANDLE;
+ } ofr;
+
+ struct ActiveReadback {
+ int activeFrameSlot = -1;
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ VkBuffer buf;
+ QVkAlloc bufAlloc;
+ quint32 bufSize;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Pipeline,
+ ShaderResourceBindings,
+ Buffer,
+ RenderBuffer,
+ Texture,
+ Sampler,
+ TextureRenderTarget,
+ RenderPass,
+ StagingBuffer
+ };
+ Type type;
+ int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
+ union {
+ struct {
+ VkPipeline pipeline;
+ VkPipelineLayout layout;
+ } pipelineState;
+ struct {
+ int poolIndex;
+ VkDescriptorSetLayout layout;
+ } shaderResourceBindings;
+ struct {
+ VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ } buffer;
+ struct {
+ VkDeviceMemory memory;
+ VkImage image;
+ VkImageView imageView;
+ } renderBuffer;
+ struct {
+ VkImage image;
+ VkImageView imageView;
+ QVkAlloc allocation;
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ } texture;
+ struct {
+ VkSampler sampler;
+ } sampler;
+ struct {
+ VkFramebuffer fb;
+ VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ } textureRenderTarget;
+ struct {
+ VkRenderPass rp;
+ } renderPass;
+ struct {
+ VkBuffer stagingBuffer;
+ QVkAlloc stagingAllocation;
+ } stagingBuffer;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiVulkan::ActiveReadback, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkanext_p.h b/src/gui/rhi/qrhivulkanext_p.h
new file mode 100644
index 0000000000..67a63e07e0
--- /dev/null
+++ b/src/gui/rhi/qrhivulkanext_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt RHI module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKANEXT_P_H
+#define QRHIVULKANEXT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhivulkan_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#ifndef VK_EXT_vertex_attribute_divisor
+#define VK_EXT_vertex_attribute_divisor 1
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_SPEC_VERSION 2
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME "VK_EXT_vertex_attribute_divisor"
+
+typedef struct VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT {
+ VkStructureType sType;
+ void* pNext;
+ uint32_t maxVertexAttribDivisor;
+} VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT;
+
+typedef struct VkVertexInputBindingDivisorDescriptionEXT {
+ uint32_t binding;
+ uint32_t divisor;
+} VkVertexInputBindingDivisorDescriptionEXT;
+
+typedef struct VkPipelineVertexInputDivisorStateCreateInfoEXT {
+ VkStructureType sType;
+ const void* pNext;
+ uint32_t vertexBindingDivisorCount;
+ const VkVertexInputBindingDivisorDescriptionEXT* pVertexBindingDivisors;
+} VkPipelineVertexInputDivisorStateCreateInfoEXT;
+#endif // VK_EXT_vertex_attribute_divisor
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp
new file mode 100644
index 0000000000..5569aad639
--- /dev/null
+++ b/src/gui/rhi/qshader.cpp
@@ -0,0 +1,586 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qshader_p_p.h"
+#include <QDataStream>
+#include <QBuffer>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QShader
+ \inmodule QtRhi
+ \since 5.14
+
+ \brief Contains multiple versions of a shader translated to multiple shading languages,
+ together with reflection metadata.
+
+ QShader is the entry point to shader code in the graphics API agnostic
+ Qt world. Instead of using GLSL shader sources, as was the custom with Qt
+ 5.x, new graphics systems with backends for multiple graphics APIs, such
+ as, Vulkan, Metal, Direct3D, and OpenGL, take QShader as their input
+ whenever a shader needs to be specified.
+
+ A QShader instance is empty and thus invalid by default. To get a useful
+ instance, the two typical methods are:
+
+ \list
+
+ \li Generate the contents offline, during build time or earlier, using the
+ \c qsb command line tool. The result is a binary file that is shipped with
+ the application, read via QIODevice::readAll(), and then deserialized via
+ fromSerialized(). For more information, see QShaderBaker.
+
+ \li Generate at run time via QShaderBaker. This is an expensive operation,
+ but allows applications to use user-provided or dynamically generated
+ shader source strings.
+
+ \endlist
+
+ When used together with the Qt Rendering Hardware Interface and its
+ classes, like QRhiGraphicsPipeline, no further action is needed from the
+ application's side as these classes are prepared to consume a QShader
+ whenever a shader needs to be specified for a given stage of the graphics
+ pipeline.
+
+ Alternatively, applications can access
+
+ \list
+
+ \li the source or byte code for any of the shading language versions that
+ are included in the QShader,
+
+ \li the name of the entry point for the shader,
+
+ \li the reflection metadata containing a description of the shader's
+ inputs, outputs and resources like uniform blocks. This is essential when
+ an application or framework needs to discover the inputs of a shader at
+ runtime due to not having advance knowledge of the vertex attributes or the
+ layout of the uniform buffers used by the shader.
+
+ \endlist
+
+ QShader makes no assumption about the shading language that was used
+ as the source for generating the various versions and variants that are
+ included in it.
+
+ QShader uses implicit sharing similarly to many core Qt types, and so
+ can be returned or passed by value. Detach happens implicitly when calling
+ a setter.
+
+ For reference, QRhi expects that a QShader suitable for all its
+ backends contains at least the following:
+
+ \list
+
+ \li SPIR-V 1.0 bytecode suitable for Vulkan 1.0 or newer
+
+ \li GLSL/ES 100 source code suitable for OpenGL ES 2.0 or newer
+
+ \li GLSL 120 source code suitable for OpenGL 2.1
+
+ \li HLSL Shader Model 5.0 source code or the corresponding DXBC bytecode suitable for Direct3D 11
+
+ \li Metal Shading Language 1.2 source code or the corresponding bytecode suitable for Metal
+
+ \endlist
+
+ \sa QShaderBaker
+ */
+
+/*!
+ \enum QShader::Stage
+ Describes the stage of the graphics pipeline the shader is suitable for.
+
+ \value VertexStage Vertex shader
+ \value TessellationControlStage Tessellation control (hull) shader
+ \value TessellationEvaluationStage Tessellation evaluation (domain) shader
+ \value GeometryStage Geometry shader
+ \value FragmentStage Fragment (pixel) shader
+ \value ComputeStage Compute shader
+ */
+
+/*!
+ \class QShaderVersion
+ \inmodule QtRhi
+
+ \brief Specifies the shading language version.
+
+ While languages like SPIR-V or the Metal Shading Language use traditional
+ version numbers, shaders for other APIs can use slightly different
+ versioning schemes. All those are mapped to a single version number in
+ here, however. For HLSL, the version refers to the Shader Model version,
+ like 5.0, 5.1, or 6.0. For GLSL an additional flag is needed to choose
+ between GLSL and GLSL/ES.
+
+ Below is a list with the most common examples of shader versions for
+ different graphics APIs:
+
+ \list
+
+ \li Vulkan (SPIR-V): 100
+ \li OpenGL: 120, 330, 440, etc.
+ \li OpenGL ES: 100 with GlslEs, 300 with GlslEs, etc.
+ \li Direct3D: 50, 51, 60
+ \li Metal: 12, 20
+ \endlist
+
+ A default constructed QShaderVersion contains a version of 100 and no
+ flags set.
+ */
+
+/*!
+ \enum QShaderVersion::Flag
+
+ Describes the flags that can be set.
+
+ \value GlslEs Indicates that GLSL/ES is meant in combination with GlslShader
+ */
+
+/*!
+ \class QShaderKey
+ \inmodule QtRhi
+
+ \brief Specifies the shading language, the version with flags, and the variant.
+
+ A default constructed QShaderKey has source set to SpirvShader and
+ sourceVersion set to 100. sourceVariant defaults to StandardShader.
+ */
+
+/*!
+ \enum QShader::Source
+ Describes what kind of shader code an entry contains.
+
+ \value SpirvShader SPIR-V
+ \value GlslShader GLSL
+ \value HlslShader HLSL
+ \value DxbcShader Direct3D bytecode (HLSL compiled by \c fxc)
+ \value MslShader Metal Shading Language
+ \value DxilShader Direct3D bytecode (HLSL compiled by \c dxc)
+ \value MetalLibShader Pre-compiled Metal bytecode
+ */
+
+/*!
+ \enum QShader::Variant
+ Describes what kind of shader code an entry contains.
+
+ \value StandardShader The normal, unmodified version of the shader code.
+ \value BatchableVertexShader Vertex shader rewritten to be suitable for Qt Quick scenegraph batching.
+ */
+
+/*!
+ \class QShaderCode
+ \inmodule QtRhi
+
+ \brief Contains source or binary code for a shader and additional metadata.
+
+ When shader() is empty after retrieving a QShaderCode instance from
+ QShader, it indicates no shader code was found for the requested key.
+ */
+
+static const int QSB_VERSION = 1;
+
+/*!
+ Constructs a new, empty (and thus invalid) QShader instance.
+ */
+QShader::QShader()
+ : d(new QShaderPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QShader::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QShader::QShader(const QShader &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QShader &QShader::operator=(const QShader &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QShader::~QShader()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return true if the QShader contains at least one shader version.
+ */
+bool QShader::isValid() const
+{
+ return !d->shaders.isEmpty();
+}
+
+/*!
+ \return the pipeline stage the shader is meant for.
+ */
+QShader::Stage QShader::stage() const
+{
+ return d->stage;
+}
+
+/*!
+ Sets the pipeline \a stage.
+ */
+void QShader::setStage(Stage stage)
+{
+ if (stage != d->stage) {
+ detach();
+ d->stage = stage;
+ }
+}
+
+/*!
+ \return the reflection metadata for the shader.
+ */
+QShaderDescription QShader::description() const
+{
+ return d->desc;
+}
+
+/*!
+ Sets the reflection metadata to \a desc.
+ */
+void QShader::setDescription(const QShaderDescription &desc)
+{
+ detach();
+ d->desc = desc;
+}
+
+/*!
+ \return the list of available shader versions
+ */
+QVector<QShaderKey> QShader::availableShaders() const
+{
+ return d->shaders.keys().toVector();
+}
+
+/*!
+ \return the source or binary code for a given shader version specified by \a key.
+ */
+QShaderCode QShader::shader(const QShaderKey &key) const
+{
+ return d->shaders.value(key);
+}
+
+/*!
+ Stores the source or binary \a shader code for a given shader version specified by \a key.
+ */
+void QShader::setShader(const QShaderKey &key, const QShaderCode &shader)
+{
+ if (d->shaders.value(key) == shader)
+ return;
+
+ detach();
+ d->shaders[key] = shader;
+}
+
+/*!
+ Removes the source or binary shader code for a given \a key.
+ Does nothing when not found.
+ */
+void QShader::removeShader(const QShaderKey &key)
+{
+ auto it = d->shaders.find(key);
+ if (it == d->shaders.end())
+ return;
+
+ detach();
+ d->shaders.erase(it);
+}
+
+/*!
+ \return a serialized binary version of all the data held by the
+ QShader, suitable for writing to files or other I/O devices.
+
+ \sa fromSerialized()
+ */
+QByteArray QShader::serialized() const
+{
+ QBuffer buf;
+ QDataStream ds(&buf);
+ ds.setVersion(QDataStream::Qt_5_10);
+ if (!buf.open(QIODevice::WriteOnly))
+ return QByteArray();
+
+ ds << QSB_VERSION;
+ ds << d->stage;
+ ds << d->desc.toBinaryJson();
+ ds << d->shaders.count();
+ for (auto it = d->shaders.cbegin(), itEnd = d->shaders.cend(); it != itEnd; ++it) {
+ const QShaderKey &k(it.key());
+ ds << k.source();
+ ds << k.sourceVersion().version();
+ ds << k.sourceVersion().flags();
+ ds << k.sourceVariant();
+ const QShaderCode &shader(d->shaders.value(k));
+ ds << shader.shader();
+ ds << shader.entryPoint();
+ }
+
+ return qCompress(buf.buffer());
+}
+
+/*!
+ Creates a new QShader instance from the given \a data.
+
+ \sa serialized()
+ */
+QShader QShader::fromSerialized(const QByteArray &data)
+{
+ QByteArray udata = qUncompress(data);
+ QBuffer buf(&udata);
+ QDataStream ds(&buf);
+ ds.setVersion(QDataStream::Qt_5_10);
+ if (!buf.open(QIODevice::ReadOnly))
+ return QShader();
+
+ QShader bs;
+ QShaderPrivate *d = QShaderPrivate::get(&bs);
+ Q_ASSERT(d->ref.load() == 1); // must be detached
+ int intVal;
+ ds >> intVal;
+ if (intVal != QSB_VERSION)
+ return QShader();
+
+ ds >> intVal;
+ d->stage = Stage(intVal);
+ QByteArray descBin;
+ ds >> descBin;
+ d->desc = QShaderDescription::fromBinaryJson(descBin);
+ int count;
+ ds >> count;
+ for (int i = 0; i < count; ++i) {
+ QShaderKey k;
+ ds >> intVal;
+ k.setSource(Source(intVal));
+ QShaderVersion ver;
+ ds >> intVal;
+ ver.setVersion(intVal);
+ ds >> intVal;
+ ver.setFlags(QShaderVersion::Flags(intVal));
+ k.setSourceVersion(ver);
+ ds >> intVal;
+ k.setSourceVariant(Variant(intVal));
+ QShaderCode shader;
+ QByteArray s;
+ ds >> s;
+ shader.setShader(s);
+ ds >> s;
+ shader.setEntryPoint(s);
+ d->shaders[k] = shader;
+ }
+
+ return bs;
+}
+
+QShaderVersion::QShaderVersion(int v, Flags f)
+ : m_version(v), m_flags(f)
+{
+}
+
+QShaderCode::QShaderCode(const QByteArray &code, const QByteArray &entry)
+ : m_shader(code), m_entryPoint(entry)
+{
+}
+
+QShaderKey::QShaderKey(QShader::Source s,
+ const QShaderVersion &sver,
+ QShader::Variant svar)
+ : m_source(s),
+ m_sourceVersion(sver),
+ m_sourceVariant(svar)
+{
+}
+
+/*!
+ Returns \c true if the two QShader objects \a a and \a b are equal,
+ meaning they are for the same stage with matching sets of shader source or
+ binary code.
+
+ \relates QShader
+ */
+bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW
+{
+ return lhs.d->stage == rhs.d->stage
+ && lhs.d->shaders == rhs.d->shaders;
+ // do not bother with desc, if the shader code is the same, the description must match too
+}
+
+/*!
+ \fn bool operator!=(const QShader &lhs, const QShader &rhs)
+
+ Returns \c false if the values in the two QShader objects \a a and \a b
+ are equal; otherwise returns \c true.
+
+ \relates QShader
+ */
+
+/*!
+ Returns the hash value for \a s, using \a seed to seed the calculation.
+
+ \relates QShader
+ */
+uint qHash(const QShader &s, uint seed) Q_DECL_NOTHROW
+{
+ uint h = s.stage();
+ for (auto it = s.d->shaders.constBegin(), itEnd = s.d->shaders.constEnd(); it != itEnd; ++it)
+ h += qHash(it.key(), seed) + qHash(it.value().shader(), seed);
+ return h;
+}
+
+/*!
+ Returns \c true if the two QShaderVersion objects \a a and \a b are
+ equal.
+
+ \relates QShaderVersion
+ */
+bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW
+{
+ return lhs.version() == rhs.version() && lhs.flags() == rhs.flags();
+}
+
+/*!
+ \fn bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs)
+
+ Returns \c false if the values in the two QShaderVersion objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderVersion
+ */
+
+/*!
+ Returns \c true if the two QShaderKey objects \a a and \a b are equal.
+
+ \relates QShaderKey
+ */
+bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW
+{
+ return lhs.source() == rhs.source() && lhs.sourceVersion() == rhs.sourceVersion()
+ && lhs.sourceVariant() == rhs.sourceVariant();
+}
+
+/*!
+ \fn bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs)
+
+ Returns \c false if the values in the two QShaderKey objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderKey
+ */
+
+/*!
+ Returns the hash value for \a k, using \a seed to seed the calculation.
+
+ \relates QShaderKey
+ */
+uint qHash(const QShaderKey &k, uint seed) Q_DECL_NOTHROW
+{
+ return seed + 10 * k.source() + k.sourceVersion().version() + k.sourceVersion().flags() + k.sourceVariant();
+}
+
+/*!
+ Returns \c true if the two QShaderCode objects \a a and \a b are equal.
+
+ \relates QShaderCode
+ */
+bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW
+{
+ return lhs.shader() == rhs.shader() && lhs.entryPoint() == rhs.entryPoint();
+}
+
+/*!
+ \fn bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs)
+
+ Returns \c false if the values in the two QShaderCode objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderCode
+ */
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QShader &bs)
+{
+ const QShaderPrivate *d = bs.d;
+ QDebugStateSaver saver(dbg);
+
+ dbg.nospace() << "QShader("
+ << "stage=" << d->stage
+ << " shaders=" << d->shaders.keys()
+ << " desc.isValid=" << d->desc.isValid()
+ << ')';
+
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderKey &k)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "ShaderKey(" << k.source()
+ << " " << k.sourceVersion()
+ << " " << k.sourceVariant() << ")";
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderVersion &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "Version(" << v.version() << " " << v.flags() << ")";
+ return dbg;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h
new file mode 100644
index 0000000000..12a0a26e12
--- /dev/null
+++ b/src/gui/rhi/qshader_p.h
@@ -0,0 +1,227 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADER_P_H
+#define QSHADER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <private/qshaderdescription_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QShaderPrivate;
+class QShaderKey;
+
+class Q_GUI_EXPORT QShaderVersion
+{
+public:
+ enum Flag {
+ GlslEs = 0x01
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QShaderVersion() = default;
+ QShaderVersion(int v, Flags f = Flags());
+
+ int version() const { return m_version; }
+ void setVersion(int v) { m_version = v; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+private:
+ int m_version = 100;
+ Flags m_flags;
+ Q_DECL_UNUSED_MEMBER quint64 m_reserved = 0;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderVersion::Flags)
+Q_DECLARE_TYPEINFO(QShaderVersion, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QShaderCode
+{
+public:
+ QShaderCode() = default;
+ QShaderCode(const QByteArray &code, const QByteArray &entry = QByteArray());
+
+ QByteArray shader() const { return m_shader; }
+ void setShader(const QByteArray &code) { m_shader = code; }
+
+ QByteArray entryPoint() const { return m_entryPoint; }
+ void setEntryPoint(const QByteArray &entry) { m_entryPoint = entry; }
+
+private:
+ QByteArray m_shader;
+ QByteArray m_entryPoint;
+ Q_DECL_UNUSED_MEMBER quint64 m_reserved = 0;
+};
+
+Q_DECLARE_TYPEINFO(QShaderCode, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QShader
+{
+public:
+ enum Stage {
+ VertexStage = 0,
+ TessellationControlStage,
+ TessellationEvaluationStage,
+ GeometryStage,
+ FragmentStage,
+ ComputeStage
+ };
+
+ enum Source {
+ SpirvShader = 0,
+ GlslShader,
+ HlslShader,
+ DxbcShader, // fxc
+ MslShader,
+ DxilShader, // dxc
+ MetalLibShader // xcrun metal + xcrun metallib
+ };
+
+ enum Variant {
+ StandardShader = 0,
+ BatchableVertexShader
+ };
+
+ QShader();
+ QShader(const QShader &other);
+ QShader &operator=(const QShader &other);
+ ~QShader();
+ void detach();
+
+ bool isValid() const;
+
+ Stage stage() const;
+ void setStage(Stage stage);
+
+ QShaderDescription description() const;
+ void setDescription(const QShaderDescription &desc);
+
+ QVector<QShaderKey> availableShaders() const;
+ QShaderCode shader(const QShaderKey &key) const;
+ void setShader(const QShaderKey &key, const QShaderCode &shader);
+ void removeShader(const QShaderKey &key);
+
+ QByteArray serialized() const;
+ static QShader fromSerialized(const QByteArray &data);
+
+private:
+ QShaderPrivate *d;
+ friend struct QShaderPrivate;
+ friend Q_GUI_EXPORT bool operator==(const QShader &, const QShader &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT uint qHash(const QShader &, uint) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &);
+#endif
+};
+
+class Q_GUI_EXPORT QShaderKey
+{
+public:
+ QShaderKey() = default;
+ QShaderKey(QShader::Source s,
+ const QShaderVersion &sver,
+ QShader::Variant svar = QShader::StandardShader);
+
+ QShader::Source source() const { return m_source; }
+ void setSource(QShader::Source s) { m_source = s; }
+
+ QShaderVersion sourceVersion() const { return m_sourceVersion; }
+ void setSourceVersion(const QShaderVersion &sver) { m_sourceVersion = sver; }
+
+ QShader::Variant sourceVariant() const { return m_sourceVariant; }
+ void setSourceVariant(QShader::Variant svar) { m_sourceVariant = svar; }
+
+private:
+ QShader::Source m_source = QShader::SpirvShader;
+ QShaderVersion m_sourceVersion;
+ QShader::Variant m_sourceVariant = QShader::StandardShader;
+ Q_DECL_UNUSED_MEMBER quint64 m_reserved = 0;
+};
+
+Q_DECLARE_TYPEINFO(QShaderKey, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QShader &s, uint seed = 0) Q_DECL_NOTHROW;
+
+inline bool operator!=(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+Q_GUI_EXPORT bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW;
+
+inline bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+Q_GUI_EXPORT uint qHash(const QShaderKey &k, uint seed = 0) Q_DECL_NOTHROW;
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &);
+Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderKey &k);
+Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderVersion &v);
+#endif
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h
new file mode 100644
index 0000000000..6473590e95
--- /dev/null
+++ b/src/gui/rhi/qshader_p_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADER_P_P_H
+#define QSHADER_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qshader_p.h"
+#include <QtCore/QAtomicInt>
+#include <QtCore/QHash>
+#include <QtCore/QDebug>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QShaderPrivate
+{
+ QShaderPrivate()
+ : ref(1)
+ {
+ }
+
+ QShaderPrivate(const QShaderPrivate *other)
+ : ref(1),
+ stage(other->stage),
+ desc(other->desc),
+ shaders(other->shaders)
+ {
+ }
+
+ static QShaderPrivate *get(QShader *s) { return s->d; }
+ static const QShaderPrivate *get(const QShader *s) { return s->d; }
+
+ QAtomicInt ref;
+ QShader::Stage stage = QShader::VertexStage;
+ QShaderDescription desc;
+ QHash<QShaderKey, QShaderCode> shaders;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp
new file mode 100644
index 0000000000..ed549b083f
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription.cpp
@@ -0,0 +1,1088 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qshaderdescription_p_p.h"
+#include <QDebug>
+#include <QJsonObject>
+#include <QJsonArray>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QShaderDescription
+ \inmodule QtRhi
+ \since 5.14
+
+ \brief Describes the interface of a shader.
+
+ A shader typically has a set of inputs and outputs. A vertex shader for
+ example has a number of input variables and may use one or more uniform
+ buffers to access data (e.g. a modelview matrix) provided by the
+ application. The shader for the fragment stage receives data from the
+ vertex stage (in a simple setup) and may also rely on data from uniform
+ buffers, images, and samplers.
+
+ When it comes to vertex inputs and the layout of the uniform buffers (what
+ are the names of the members? what is there size, offset, and so on),
+ applications and frameworks may need to discover this dynamically at run
+ time. This is typical when the shader is not built-in but provided by an
+ external entity, like the user.
+
+ Modern and lean graphics APIs may no longer provide a way to query shader
+ reflection information at run time. Therefore, such data is now
+ automatically generated by QShaderBaker and is provided as a
+ QShaderDescription object for each and every QShader.
+
+ \section2 Example
+
+ Take the following vertex shader:
+
+ \badcode
+ #version 440
+
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec3 color;
+ layout(location = 0) out vec3 v_color;
+
+ layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ float opacity;
+ } ubuf;
+
+ out gl_PerVertex { vec4 gl_Position; };
+
+ void main()
+ {
+ v_color = color;
+ gl_Position = ubuf.mvp * position;
+ }
+ \endcode
+
+ This shader has two inputs: \c position at location 0 with a type of \c
+ vec4, and \c color at location 1 with a type of \c vec3. It has one output:
+ \c v_color, although this is typically not interesting for applications.
+ What is more important, there is a uniform block at binding 0 with a size
+ of 68 bytes and two members, a 4x4 matrix named \c mvp at offset 0, and a
+ float \c opacity at offset 64.
+
+ All this is described by a QShaderDescription object. QShaderDescription
+ can also be serialized to JSON and binary JSON, and can be deserialized
+ from binary JSON. In practice this is rarely needed since QShader
+ takes care of the associated QShaderDescription automatically, but if the
+ QShaderDescription of the above shader would be written out as JSON, it
+ would look like the following:
+
+ \badcode
+ {
+ "inputs": [
+ {
+ "location": 1,
+ "name": "color",
+ "type": "vec3"
+ },
+ {
+ "location": 0,
+ "name": "position",
+ "type": "vec4"
+ }
+ ],
+ "outputs": [
+ {
+ "location": 0,
+ "name": "v_color",
+ "type": "vec3"
+ }
+ ],
+ "uniformBlocks": [
+ {
+ "binding": 0,
+ "blockName": "buf",
+ "members": [
+ {
+ "matrixStride": 16,
+ "name": "mvp",
+ "offset": 0,
+ "size": 64,
+ "type": "mat4"
+ },
+ {
+ "name": "opacity",
+ "offset": 64,
+ "size": 4,
+ "type": "float"
+ }
+ ],
+ "set": 0,
+ "size": 68,
+ "structName": "ubuf"
+ }
+ ]
+ }
+ \endcode
+
+ The C++ API allows accessing a data structure like the above. For
+ simplicity the inner structs only contain public data members, also
+ considering that their layout is unlikely to change in the future.
+
+ \sa QShaderBaker, QShader
+ */
+
+/*!
+ \enum QShaderDescription::VariableType
+ Represents the type of a variable or block member.
+
+ \value Unknown
+ \value Float
+ \value Vec2
+ \value Vec3
+ \value Vec4
+ \value Mat2
+ \value Mat2x3
+ \value Mat2x4
+ \value Mat3
+ \value Mat3x2
+ \value Mat3x4
+ \value Mat4
+ \value Mat4x2
+ \value Mat4x3
+ \value Int
+ \value Int2
+ \value Int3
+ \value Int4
+ \value Uint
+ \value Uint2
+ \value Uint3
+ \value Uint4
+ \value Bool
+ \value Bool2
+ \value Bool3
+ \value Bool4
+ \value Double
+ \value Double2
+ \value Double3
+ \value Double4
+ \value DMat2
+ \value DMat2x3
+ \value DMat2x4
+ \value DMat3
+ \value DMat3x2
+ \value DMat3x4
+ \value DMat4
+ \value DMat4x2
+ \value DMat4x3
+ \value Sampler1D
+ \value Sampler2D
+ \value Sampler2DMS
+ \value Sampler3D
+ \value SamplerCube
+ \value Sampler1DArray
+ \value Sampler2DArray
+ \value Sampler2DMSArray
+ \value Sampler3DArray
+ \value SamplerCubeArray
+ \value SamplerRect
+ \value SamplerBuffer
+ \value Image1D
+ \value Image2D
+ \value Image2DMS
+ \value Image3D
+ \value ImageCube
+ \value Image1DArray
+ \value Image2DArray
+ \value Image2DMSArray
+ \value Image3DArray
+ \value ImageCubeArray
+ \value ImageRect
+ \value ImageBuffer
+ \value Struct
+ */
+
+/*!
+ \class QShaderDescription::InOutVariable
+ \inmodule QtRhi
+
+ \brief Describes an input or output variable in the shader.
+ */
+
+/*!
+ \class QShaderDescription::BlockVariable
+ \inmodule QtRhi
+
+ \brief Describes a member of a uniform or push constant block.
+ */
+
+/*!
+ \class QShaderDescription::UniformBlock
+ \inmodule QtRhi
+
+ \brief Describes a uniform block.
+
+ \note When translating to shading languages without uniform block support
+ (like GLSL 120 or GLSL/ES 100), uniform blocks are replaced with ordinary
+ uniforms in a struct. The name of the struct, and so the prefix for the
+ uniforms generated from the block members, is given by structName.
+ */
+
+/*!
+ \class QShaderDescription::PushConstantBlock
+ \inmodule QtRhi
+
+ \brief Describes a push constant block.
+ */
+
+/*!
+ \class QShaderDescription::StorageBlock
+ \inmodule QtRhi
+
+ \brief Describes a shader storage block.
+ */
+
+/*!
+ Constructs a new, empty QShaderDescription.
+
+ \note Being empty implies that isValid() returns \c false for the
+ newly constructed instance.
+ */
+QShaderDescription::QShaderDescription()
+ : d(new QShaderDescriptionPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QShaderDescription::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QShaderDescription::QShaderDescription(const QShaderDescription &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QShaderDescription &QShaderDescription::operator=(const QShaderDescription &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QShaderDescription::~QShaderDescription()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return true if the QShaderDescription contains at least one entry in one of
+ the variable and block lists.
+ */
+bool QShaderDescription::isValid() const
+{
+ return !d->inVars.isEmpty() || !d->outVars.isEmpty()
+ || !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty()
+ || !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty();
+}
+
+/*!
+ \return a serialized binary version of the data.
+
+ \sa toJson()
+ */
+QByteArray QShaderDescription::toBinaryJson() const
+{
+ return d->makeDoc().toBinaryData();
+}
+
+/*!
+ \return a serialized JSON text version of the data.
+
+ \note There is no deserialization method provided for JSON text.
+
+ \sa toBinaryJson()
+ */
+QByteArray QShaderDescription::toJson() const
+{
+ return d->makeDoc().toJson();
+}
+
+/*!
+ Deserializes the given binary JSON \a data and returns a new
+ QShaderDescription.
+ */
+QShaderDescription QShaderDescription::fromBinaryJson(const QByteArray &data)
+{
+ QShaderDescription desc;
+ QShaderDescriptionPrivate::get(&desc)->loadDoc(QJsonDocument::fromBinaryData(data));
+ return desc;
+}
+
+/*!
+ \return the list of input variables. This includes vertex inputs (sometimes
+ called attributes) for the vertex stage, and inputs for other stages
+ (sometimes called varyings).
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::inputVariables() const
+{
+ return d->inVars;
+}
+
+/*!
+ \return the list of output variables.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::outputVariables() const
+{
+ return d->outVars;
+}
+
+/*!
+ \return the list of uniform blocks.
+ */
+QVector<QShaderDescription::UniformBlock> QShaderDescription::uniformBlocks() const
+{
+ return d->uniformBlocks;
+}
+
+/*!
+ \return the list of push constant blocks.
+
+ \note Avoid relying on push constant blocks for shaders that are to be used
+ in combination with the Qt Rendering Hardware Interface since that
+ currently has no support for them.
+ */
+QVector<QShaderDescription::PushConstantBlock> QShaderDescription::pushConstantBlocks() const
+{
+ return d->pushConstantBlocks;
+}
+
+/*!
+ \return the list of shader storage blocks.
+
+ For example, with GLSL/Vulkan shaders as source, the declaration
+
+ \badcode
+ struct Stuff {
+ vec2 a;
+ vec2 b;
+ };
+ layout(std140, binding = 0) buffer StuffSsbo {
+ vec4 whatever;
+ Stuff stuff[];
+ } buf;
+ \endcode
+
+ generates the following: (shown as textual JSON here)
+
+ \badcode
+ "storageBlocks": [ {
+ "binding": 0,
+ "blockName": "StuffSsbo",
+ "instanceName": "buf",
+ "knownSize": 16,
+ "members": [
+ {
+ "name": "whatever",
+ "offset": 0,
+ "size": 16,
+ "type": "vec4"
+ },
+ {
+ "arrayDims": [
+ 0
+ ],
+ "name": "stuff",
+ "offset": 16,
+ "size": 0,
+ "structMembers": [
+ {
+ "name": "a",
+ "offset": 0,
+ "size": 8,
+ "type": "vec2"
+ },
+ {
+ "name": "b",
+ "offset": 8,
+ "size": 8,
+ "type": "vec2"
+ }
+ ],
+ "type": "struct"
+ }
+ ],
+ "set": 0
+ } ]
+ \endcode
+
+ \note The size of the last member in the storage block is undefined. This shows
+ up as \c size 0 and an array dimension of \c{[0]}. The storage block's \c knownSize
+ excludes the size of the last member since that will only be known at run time.
+
+ \note SSBOs are not available with some graphics APIs, such as, OpenGL 2.x or
+ OpenGL ES older than 3.1.
+ */
+QVector<QShaderDescription::StorageBlock> QShaderDescription::storageBlocks() const
+{
+ return d->storageBlocks;
+}
+
+/*!
+ \return the list of combined image samplers
+
+ With GLSL/Vulkan shaders as source a \c{layout(binding = 1) uniform sampler2D tex;}
+ uniform generates the following: (shown as textual JSON here)
+
+ \badcode
+ "combinedImageSamplers": [
+ {
+ "binding": 1,
+ "name": "tex",
+ "set": 0,
+ "type": "sampler2D"
+ }
+ ]
+ \endcode
+
+ This does not mean that other language versions of the shader must also use
+ a combined image sampler, especially considering that the concept may not
+ exist everywhere. For instance, a HLSL version will likely just use a
+ Texture2D and SamplerState object with registers t1 and s1, respectively.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::combinedImageSamplers() const
+{
+ return d->combinedImageSamplers;
+}
+
+/*!
+ \return the list of image variables.
+
+ These will likely occur in compute shaders. For example,
+ \c{layout (binding = 0, rgba8) uniform readonly image2D inputImage;}
+ generates the following: (shown as textual JSON here)
+
+ \badcode
+ "storageImages": [
+ {
+ "binding": 0,
+ "imageFormat": "rgba8",
+ "name": "inputImage",
+ "set": 0,
+ "type": "image2D"
+ }
+ ]
+ \endcode
+
+ \note Separate image objects are not compatible with some graphics APIs,
+ such as, OpenGL 2.x or OpenGL ES older than 3.1.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::storageImages() const
+{
+ return d->storageImages;
+}
+
+static struct TypeTab {
+ QString k;
+ QShaderDescription::VariableType v;
+} typeTab[] = {
+ { QLatin1String("float"), QShaderDescription::Float },
+ { QLatin1String("vec2"), QShaderDescription::Vec2 },
+ { QLatin1String("vec3"), QShaderDescription::Vec3 },
+ { QLatin1String("vec4"), QShaderDescription::Vec4 },
+ { QLatin1String("mat2"), QShaderDescription::Mat2 },
+ { QLatin1String("mat3"), QShaderDescription::Mat3 },
+ { QLatin1String("mat4"), QShaderDescription::Mat4 },
+
+ { QLatin1String("struct"), QShaderDescription::Struct },
+
+ { QLatin1String("sampler1D"), QShaderDescription::Sampler1D },
+ { QLatin1String("sampler2D"), QShaderDescription::Sampler2D },
+ { QLatin1String("sampler2DMS"), QShaderDescription::Sampler2DMS },
+ { QLatin1String("sampler3D"), QShaderDescription::Sampler3D },
+ { QLatin1String("samplerCube"), QShaderDescription::SamplerCube },
+ { QLatin1String("sampler1DArray"), QShaderDescription::Sampler1DArray },
+ { QLatin1String("sampler2DArray"), QShaderDescription::Sampler2DArray },
+ { QLatin1String("sampler2DMSArray"), QShaderDescription::Sampler2DMSArray },
+ { QLatin1String("sampler3DArray"), QShaderDescription::Sampler3DArray },
+ { QLatin1String("samplerCubeArray"), QShaderDescription::SamplerCubeArray },
+ { QLatin1String("samplerRect"), QShaderDescription::SamplerRect },
+ { QLatin1String("samplerBuffer"), QShaderDescription::SamplerBuffer },
+
+ { QLatin1String("mat2x3"), QShaderDescription::Mat2x3 },
+ { QLatin1String("mat2x4"), QShaderDescription::Mat2x4 },
+ { QLatin1String("mat3x2"), QShaderDescription::Mat3x2 },
+ { QLatin1String("mat3x4"), QShaderDescription::Mat3x4 },
+ { QLatin1String("mat4x2"), QShaderDescription::Mat4x2 },
+ { QLatin1String("mat4x3"), QShaderDescription::Mat4x3 },
+
+ { QLatin1String("int"), QShaderDescription::Int },
+ { QLatin1String("ivec2"), QShaderDescription::Int2 },
+ { QLatin1String("ivec3"), QShaderDescription::Int3 },
+ { QLatin1String("ivec4"), QShaderDescription::Int4 },
+
+ { QLatin1String("uint"), QShaderDescription::Uint },
+ { QLatin1String("uvec2"), QShaderDescription::Uint2 },
+ { QLatin1String("uvec3"), QShaderDescription::Uint3 },
+ { QLatin1String("uvec4"), QShaderDescription::Uint4 },
+
+ { QLatin1String("bool"), QShaderDescription::Bool },
+ { QLatin1String("bvec2"), QShaderDescription::Bool2 },
+ { QLatin1String("bvec3"), QShaderDescription::Bool3 },
+ { QLatin1String("bvec4"), QShaderDescription::Bool4 },
+
+ { QLatin1String("double"), QShaderDescription::Double },
+ { QLatin1String("dvec2"), QShaderDescription::Double2 },
+ { QLatin1String("dvec3"), QShaderDescription::Double3 },
+ { QLatin1String("dvec4"), QShaderDescription::Double4 },
+ { QLatin1String("dmat2"), QShaderDescription::DMat2 },
+ { QLatin1String("dmat3"), QShaderDescription::DMat3 },
+ { QLatin1String("dmat4"), QShaderDescription::DMat4 },
+ { QLatin1String("dmat2x3"), QShaderDescription::DMat2x3 },
+ { QLatin1String("dmat2x4"), QShaderDescription::DMat2x4 },
+ { QLatin1String("dmat3x2"), QShaderDescription::DMat3x2 },
+ { QLatin1String("dmat3x4"), QShaderDescription::DMat3x4 },
+ { QLatin1String("dmat4x2"), QShaderDescription::DMat4x2 },
+ { QLatin1String("dmat4x3"), QShaderDescription::DMat4x3 },
+
+ { QLatin1String("image1D"), QShaderDescription::Image1D },
+ { QLatin1String("image2D"), QShaderDescription::Image2D },
+ { QLatin1String("image2DMS"), QShaderDescription::Image2DMS },
+ { QLatin1String("image3D"), QShaderDescription::Image3D },
+ { QLatin1String("imageCube"), QShaderDescription::ImageCube },
+ { QLatin1String("image1DArray"), QShaderDescription::Image1DArray },
+ { QLatin1String("image2DArray"), QShaderDescription::Image2DArray },
+ { QLatin1String("image2DMSArray"), QShaderDescription::Image2DMSArray },
+ { QLatin1String("image3DArray"), QShaderDescription::Image3DArray },
+ { QLatin1String("imageCubeArray"), QShaderDescription::ImageCubeArray },
+ { QLatin1String("imageRect"), QShaderDescription::ImageRect },
+ { QLatin1String("imageBuffer"), QShaderDescription::ImageBuffer }
+};
+
+static QString typeStr(const QShaderDescription::VariableType &t)
+{
+ for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
+ if (typeTab[i].v == t)
+ return typeTab[i].k;
+ }
+ return QString();
+}
+
+static QShaderDescription::VariableType mapType(const QString &t)
+{
+ for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
+ if (typeTab[i].k == t)
+ return typeTab[i].v;
+ }
+ return QShaderDescription::Unknown;
+}
+
+static struct ImageFormatTab {
+ QString k;
+ QShaderDescription::ImageFormat v;
+} imageFormatTab[] {
+ { QLatin1String("unknown"), QShaderDescription::ImageFormatUnknown },
+ { QLatin1String("rgba32f"), QShaderDescription::ImageFormatRgba32f },
+ { QLatin1String("rgba16"), QShaderDescription::ImageFormatRgba16f },
+ { QLatin1String("r32f"), QShaderDescription::ImageFormatR32f },
+ { QLatin1String("rgba8"), QShaderDescription::ImageFormatRgba8 },
+ { QLatin1String("rgba8_snorm"), QShaderDescription::ImageFormatRgba8Snorm },
+ { QLatin1String("rg32f"), QShaderDescription::ImageFormatRg32f },
+ { QLatin1String("rg16f"), QShaderDescription::ImageFormatRg16f },
+ { QLatin1String("r11f_g11f_b10f"), QShaderDescription::ImageFormatR11fG11fB10f },
+ { QLatin1String("r16f"), QShaderDescription::ImageFormatR16f },
+ { QLatin1String("rgba16"), QShaderDescription::ImageFormatRgba16 },
+ { QLatin1String("rgb10_a2"), QShaderDescription::ImageFormatRgb10A2 },
+ { QLatin1String("rg16"), QShaderDescription::ImageFormatRg16 },
+ { QLatin1String("rg8"), QShaderDescription::ImageFormatRg8 },
+ { QLatin1String("r16"), QShaderDescription::ImageFormatR16 },
+ { QLatin1String("r8"), QShaderDescription::ImageFormatR8 },
+ { QLatin1String("rgba16_snorm"), QShaderDescription::ImageFormatRgba16Snorm },
+ { QLatin1String("rg16_snorm"), QShaderDescription::ImageFormatRg16Snorm },
+ { QLatin1String("rg8_snorm"), QShaderDescription::ImageFormatRg8Snorm },
+ { QLatin1String("r16_snorm"), QShaderDescription::ImageFormatR16Snorm },
+ { QLatin1String("r8_snorm"), QShaderDescription::ImageFormatR8Snorm },
+ { QLatin1String("rgba32i"), QShaderDescription::ImageFormatRgba32i },
+ { QLatin1String("rgba16i"), QShaderDescription::ImageFormatRgba16i },
+ { QLatin1String("rgba8i"), QShaderDescription::ImageFormatRgba8i },
+ { QLatin1String("r32i"), QShaderDescription::ImageFormatR32i },
+ { QLatin1String("rg32i"), QShaderDescription::ImageFormatRg32i },
+ { QLatin1String("rg16i"), QShaderDescription::ImageFormatRg16i },
+ { QLatin1String("rg8i"), QShaderDescription::ImageFormatRg8i },
+ { QLatin1String("r16i"), QShaderDescription::ImageFormatR16i },
+ { QLatin1String("r8i"), QShaderDescription::ImageFormatR8i },
+ { QLatin1String("rgba32ui"), QShaderDescription::ImageFormatRgba32ui },
+ { QLatin1String("rgba16ui"), QShaderDescription::ImageFormatRgba16ui },
+ { QLatin1String("rgba8ui"), QShaderDescription::ImageFormatRgba8ui },
+ { QLatin1String("r32ui"), QShaderDescription::ImageFormatR32ui },
+ { QLatin1String("rgb10_a2ui"), QShaderDescription::ImageFormatRgb10a2ui },
+ { QLatin1String("rg32ui"), QShaderDescription::ImageFormatRg32ui },
+ { QLatin1String("rg16ui"), QShaderDescription::ImageFormatRg16ui },
+ { QLatin1String("rg8ui"), QShaderDescription::ImageFormatRg8ui },
+ { QLatin1String("r16ui"), QShaderDescription::ImageFormatR16ui },
+ { QLatin1String("r8ui"), QShaderDescription::ImageFormatR8ui }
+};
+
+static QString imageFormatStr(const QShaderDescription::ImageFormat &f)
+{
+ for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
+ if (imageFormatTab[i].v == f)
+ return imageFormatTab[i].k;
+ }
+ return QString();
+}
+
+static QShaderDescription::ImageFormat mapImageFormat(const QString &f)
+{
+ for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
+ if (imageFormatTab[i].k == f)
+ return imageFormatTab[i].v;
+ }
+ return QShaderDescription::ImageFormatUnknown;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
+{
+ const QShaderDescriptionPrivate *d = sd.d;
+ QDebugStateSaver saver(dbg);
+
+ if (sd.isValid()) {
+ dbg.nospace() << "QShaderDescription("
+ << "inVars " << d->inVars
+ << " outVars " << d->outVars
+ << " uniformBlocks " << d->uniformBlocks
+ << " pcBlocks " << d->pushConstantBlocks
+ << " storageBlocks " << d->storageBlocks
+ << " combinedSamplers " << d->combinedImageSamplers
+ << " images " << d->storageImages
+ << ')';
+ } else {
+ dbg.nospace() << "QShaderDescription(null)";
+ }
+
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "InOutVariable(" << typeStr(var.type) << ' ' << var.name;
+ if (var.location >= 0)
+ dbg.nospace() << " location=" << var.location;
+ if (var.binding >= 0)
+ dbg.nospace() << " binding=" << var.binding;
+ if (var.descriptorSet >= 0)
+ dbg.nospace() << " set=" << var.descriptorSet;
+ if (var.imageFormat != QShaderDescription::ImageFormatUnknown)
+ dbg.nospace() << " imageFormat=" << imageFormatStr(var.imageFormat);
+ if (var.imageFlags)
+ dbg.nospace() << " imageFlags=" << var.imageFlags;
+ dbg.nospace() << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::BlockVariable &var)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name
+ << " offset=" << var.offset << " size=" << var.size;
+ if (!var.arrayDims.isEmpty())
+ dbg.nospace() << " array=" << var.arrayDims;
+ if (var.arrayStride)
+ dbg.nospace() << " arrayStride=" << var.arrayStride;
+ if (var.matrixStride)
+ dbg.nospace() << " matrixStride=" << var.matrixStride;
+ if (var.matrixIsRowMajor)
+ dbg.nospace() << " [rowmaj]";
+ if (!var.structMembers.isEmpty())
+ dbg.nospace() << " structMembers=" << var.structMembers;
+ dbg.nospace() << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::UniformBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "UniformBlock(" << blk.blockName << ' ' << blk.structName << " size=" << blk.size;
+ if (blk.binding >= 0)
+ dbg.nospace() << " binding=" << blk.binding;
+ if (blk.descriptorSet >= 0)
+ dbg.nospace() << " set=" << blk.descriptorSet;
+ dbg.nospace() << ' ' << blk.members << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::PushConstantBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "PushConstantBlock(" << blk.name << " size=" << blk.size << ' ' << blk.members << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "StorageBlock(" << blk.blockName << ' ' << blk.instanceName << " knownSize=" << blk.knownSize;
+ if (blk.binding >= 0)
+ dbg.nospace() << " binding=" << blk.binding;
+ if (blk.descriptorSet >= 0)
+ dbg.nospace() << " set=" << blk.descriptorSet;
+ dbg.nospace() << ' ' << blk.members << ')';
+ return dbg;
+}
+#endif
+
+static const QString nameKey = QLatin1String("name");
+static const QString typeKey = QLatin1String("type");
+static const QString locationKey = QLatin1String("location");
+static const QString bindingKey = QLatin1String("binding");
+static const QString setKey = QLatin1String("set");
+static const QString imageFormatKey = QLatin1String("imageFormat");
+static const QString imageFlagsKey = QLatin1String("imageFlags");
+static const QString offsetKey = QLatin1String("offset");
+static const QString arrayDimsKey = QLatin1String("arrayDims");
+static const QString arrayStrideKey = QLatin1String("arrayStride");
+static const QString matrixStrideKey = QLatin1String("matrixStride");
+static const QString matrixRowMajorKey = QLatin1String("matrixRowMajor");
+static const QString structMembersKey = QLatin1String("structMembers");
+static const QString membersKey = QLatin1String("members");
+static const QString inputsKey = QLatin1String("inputs");
+static const QString outputsKey = QLatin1String("outputs");
+static const QString uniformBlocksKey = QLatin1String("uniformBlocks");
+static const QString blockNameKey = QLatin1String("blockName");
+static const QString structNameKey = QLatin1String("structName");
+static const QString instanceNameKey = QLatin1String("instanceName");
+static const QString sizeKey = QLatin1String("size");
+static const QString knownSizeKey = QLatin1String("knownSize");
+static const QString pushConstantBlocksKey = QLatin1String("pushConstantBlocks");
+static const QString storageBlocksKey = QLatin1String("storageBlocks");
+static const QString combinedImageSamplersKey = QLatin1String("combinedImageSamplers");
+static const QString storageImagesKey = QLatin1String("storageImages");
+
+static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v)
+{
+ if (v.location >= 0)
+ (*obj)[locationKey] = v.location;
+ if (v.binding >= 0)
+ (*obj)[bindingKey] = v.binding;
+ if (v.descriptorSet >= 0)
+ (*obj)[setKey] = v.descriptorSet;
+ if (v.imageFormat != QShaderDescription::ImageFormatUnknown)
+ (*obj)[imageFormatKey] = imageFormatStr(v.imageFormat);
+ if (v.imageFlags)
+ (*obj)[imageFlagsKey] = int(v.imageFlags);
+}
+
+static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
+{
+ QJsonObject obj;
+ obj[nameKey] = v.name;
+ obj[typeKey] = typeStr(v.type);
+ addDeco(&obj, v);
+ return obj;
+}
+
+static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
+{
+ QJsonObject obj;
+ obj[nameKey] = v.name;
+ obj[typeKey] = typeStr(v.type);
+ obj[offsetKey] = v.offset;
+ obj[sizeKey] = v.size;
+ if (!v.arrayDims.isEmpty()) {
+ QJsonArray dimArr;
+ for (int dim : v.arrayDims)
+ dimArr.append(dim);
+ obj[arrayDimsKey] = dimArr;
+ }
+ if (v.arrayStride)
+ obj[arrayStrideKey] = v.arrayStride;
+ if (v.matrixStride)
+ obj[matrixStrideKey] = v.matrixStride;
+ if (v.matrixIsRowMajor)
+ obj[matrixRowMajorKey] = true;
+ if (!v.structMembers.isEmpty()) {
+ QJsonArray arr;
+ for (const QShaderDescription::BlockVariable &sv : v.structMembers)
+ arr.append(blockMemberObject(sv));
+ obj[structMembersKey] = arr;
+ }
+ return obj;
+}
+
+QJsonDocument QShaderDescriptionPrivate::makeDoc()
+{
+ QJsonObject root;
+
+ QJsonArray jinputs;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(inVars))
+ jinputs.append(inOutObject(v));
+ if (!jinputs.isEmpty())
+ root[inputsKey] = jinputs;
+
+ QJsonArray joutputs;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(outVars))
+ joutputs.append(inOutObject(v));
+ if (!joutputs.isEmpty())
+ root[outputsKey] = joutputs;
+
+ QJsonArray juniformBlocks;
+ for (const QShaderDescription::UniformBlock &b : uniformBlocks) {
+ QJsonObject juniformBlock;
+ juniformBlock[blockNameKey] = b.blockName;
+ juniformBlock[structNameKey] = b.structName;
+ juniformBlock[sizeKey] = b.size;
+ if (b.binding >= 0)
+ juniformBlock[bindingKey] = b.binding;
+ if (b.descriptorSet >= 0)
+ juniformBlock[setKey] = b.descriptorSet;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ juniformBlock[membersKey] = members;
+ juniformBlocks.append(juniformBlock);
+ }
+ if (!juniformBlocks.isEmpty())
+ root[uniformBlocksKey] = juniformBlocks;
+
+ QJsonArray jpushConstantBlocks;
+ for (const QShaderDescription::PushConstantBlock &b : pushConstantBlocks) {
+ QJsonObject jpushConstantBlock;
+ jpushConstantBlock[nameKey] = b.name;
+ jpushConstantBlock[sizeKey] = b.size;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ jpushConstantBlock[membersKey] = members;
+ jpushConstantBlocks.append(jpushConstantBlock);
+ }
+ if (!jpushConstantBlocks.isEmpty())
+ root[pushConstantBlocksKey] = jpushConstantBlocks;
+
+ QJsonArray jstorageBlocks;
+ for (const QShaderDescription::StorageBlock &b : storageBlocks) {
+ QJsonObject jstorageBlock;
+ jstorageBlock[blockNameKey] = b.blockName;
+ jstorageBlock[instanceNameKey] = b.instanceName;
+ jstorageBlock[knownSizeKey] = b.knownSize;
+ if (b.binding >= 0)
+ jstorageBlock[bindingKey] = b.binding;
+ if (b.descriptorSet >= 0)
+ jstorageBlock[setKey] = b.descriptorSet;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ jstorageBlock[membersKey] = members;
+ jstorageBlocks.append(jstorageBlock);
+ }
+ if (!jstorageBlocks.isEmpty())
+ root[storageBlocksKey] = jstorageBlocks;
+
+ QJsonArray jcombinedSamplers;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(combinedImageSamplers)) {
+ QJsonObject sampler;
+ sampler[nameKey] = v.name;
+ sampler[typeKey] = typeStr(v.type);
+ addDeco(&sampler, v);
+ jcombinedSamplers.append(sampler);
+ }
+ if (!jcombinedSamplers.isEmpty())
+ root[combinedImageSamplersKey] = jcombinedSamplers;
+
+ QJsonArray jstorageImages;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(storageImages)) {
+ QJsonObject image;
+ image[nameKey] = v.name;
+ image[typeKey] = typeStr(v.type);
+ addDeco(&image, v);
+ jstorageImages.append(image);
+ }
+ if (!jstorageImages.isEmpty())
+ root[storageImagesKey] = jstorageImages;
+
+ return QJsonDocument(root);
+}
+
+static QShaderDescription::InOutVariable inOutVar(const QJsonObject &obj)
+{
+ QShaderDescription::InOutVariable var;
+ var.name = obj[nameKey].toString();
+ var.type = mapType(obj[typeKey].toString());
+ if (obj.contains(locationKey))
+ var.location = obj[locationKey].toInt();
+ if (obj.contains(bindingKey))
+ var.binding = obj[bindingKey].toInt();
+ if (obj.contains(setKey))
+ var.descriptorSet = obj[setKey].toInt();
+ if (obj.contains(imageFormatKey))
+ var.imageFormat = mapImageFormat(obj[imageFormatKey].toString());
+ if (obj.contains(imageFlagsKey))
+ var.imageFlags = QShaderDescription::ImageFlags(obj[imageFlagsKey].toInt());
+ return var;
+}
+
+static QShaderDescription::BlockVariable blockVar(const QJsonObject &obj)
+{
+ QShaderDescription::BlockVariable var;
+ var.name = obj[nameKey].toString();
+ var.type = mapType(obj[typeKey].toString());
+ var.offset = obj[offsetKey].toInt();
+ var.size = obj[sizeKey].toInt();
+ if (obj.contains(arrayDimsKey)) {
+ QJsonArray dimArr = obj[arrayDimsKey].toArray();
+ for (int i = 0; i < dimArr.count(); ++i)
+ var.arrayDims.append(dimArr.at(i).toInt());
+ }
+ if (obj.contains(arrayStrideKey))
+ var.arrayStride = obj[arrayStrideKey].toInt();
+ if (obj.contains(matrixStrideKey))
+ var.matrixStride = obj[matrixStrideKey].toInt();
+ if (obj.contains(matrixRowMajorKey))
+ var.matrixIsRowMajor = obj[matrixRowMajorKey].toBool();
+ if (obj.contains(structMembersKey)) {
+ QJsonArray arr = obj[structMembersKey].toArray();
+ for (int i = 0; i < arr.count(); ++i)
+ var.structMembers.append(blockVar(arr.at(i).toObject()));
+ }
+ return var;
+}
+
+void QShaderDescriptionPrivate::loadDoc(const QJsonDocument &doc)
+{
+ if (doc.isNull()) {
+ qWarning("QShaderDescription: JSON document is empty");
+ return;
+ }
+
+ Q_ASSERT(ref.load() == 1); // must be detached
+
+ inVars.clear();
+ outVars.clear();
+ uniformBlocks.clear();
+ pushConstantBlocks.clear();
+ storageBlocks.clear();
+ combinedImageSamplers.clear();
+ storageImages.clear();
+
+ QJsonObject root = doc.object();
+
+ if (root.contains(inputsKey)) {
+ QJsonArray inputs = root[inputsKey].toArray();
+ for (int i = 0; i < inputs.count(); ++i)
+ inVars.append(inOutVar(inputs[i].toObject()));
+ }
+
+ if (root.contains(outputsKey)) {
+ QJsonArray outputs = root[outputsKey].toArray();
+ for (int i = 0; i < outputs.count(); ++i)
+ outVars.append(inOutVar(outputs[i].toObject()));
+ }
+
+ if (root.contains(uniformBlocksKey)) {
+ QJsonArray ubs = root[uniformBlocksKey].toArray();
+ for (int i = 0; i < ubs.count(); ++i) {
+ QJsonObject ubObj = ubs[i].toObject();
+ QShaderDescription::UniformBlock ub;
+ ub.blockName = ubObj[blockNameKey].toString();
+ ub.structName = ubObj[structNameKey].toString();
+ ub.size = ubObj[sizeKey].toInt();
+ if (ubObj.contains(bindingKey))
+ ub.binding = ubObj[bindingKey].toInt();
+ if (ubObj.contains(setKey))
+ ub.descriptorSet = ubObj[setKey].toInt();
+ QJsonArray members = ubObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ ub.members.append(blockVar(member.toObject()));
+ uniformBlocks.append(ub);
+ }
+ }
+
+ if (root.contains(pushConstantBlocksKey)) {
+ QJsonArray pcs = root[pushConstantBlocksKey].toArray();
+ for (int i = 0; i < pcs.count(); ++i) {
+ QJsonObject pcObj = pcs[i].toObject();
+ QShaderDescription::PushConstantBlock pc;
+ pc.name = pcObj[nameKey].toString();
+ pc.size = pcObj[sizeKey].toInt();
+ QJsonArray members = pcObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ pc.members.append(blockVar(member.toObject()));
+ pushConstantBlocks.append(pc);
+ }
+ }
+
+ if (root.contains(storageBlocksKey)) {
+ QJsonArray ubs = root[storageBlocksKey].toArray();
+ for (int i = 0; i < ubs.count(); ++i) {
+ QJsonObject sbObj = ubs[i].toObject();
+ QShaderDescription::StorageBlock sb;
+ sb.blockName = sbObj[blockNameKey].toString();
+ sb.instanceName = sbObj[instanceNameKey].toString();
+ sb.knownSize = sbObj[knownSizeKey].toInt();
+ if (sbObj.contains(bindingKey))
+ sb.binding = sbObj[bindingKey].toInt();
+ if (sbObj.contains(setKey))
+ sb.descriptorSet = sbObj[setKey].toInt();
+ QJsonArray members = sbObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ sb.members.append(blockVar(member.toObject()));
+ storageBlocks.append(sb);
+ }
+ }
+
+ if (root.contains(combinedImageSamplersKey)) {
+ QJsonArray samplers = root[combinedImageSamplersKey].toArray();
+ for (int i = 0; i < samplers.count(); ++i)
+ combinedImageSamplers.append(inOutVar(samplers[i].toObject()));
+ }
+
+ if (root.contains(storageImagesKey)) {
+ QJsonArray images = root[storageImagesKey].toArray();
+ for (int i = 0; i < images.count(); ++i)
+ storageImages.append(inOutVar(images[i].toObject()));
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h
new file mode 100644
index 0000000000..43d4256a63
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription_p.h
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADERDESCRIPTION_H
+#define QSHADERDESCRIPTION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <QtCore/QString>
+#include <QtCore/QVector>
+
+QT_BEGIN_NAMESPACE
+
+struct QShaderDescriptionPrivate;
+
+class Q_GUI_EXPORT QShaderDescription
+{
+public:
+ QShaderDescription();
+ QShaderDescription(const QShaderDescription &other);
+ QShaderDescription &operator=(const QShaderDescription &other);
+ ~QShaderDescription();
+ void detach();
+
+ bool isValid() const;
+
+ QByteArray toBinaryJson() const;
+ QByteArray toJson() const;
+
+ static QShaderDescription fromBinaryJson(const QByteArray &data);
+
+ enum VariableType {
+ Unknown = 0,
+
+ // do not reorder
+ Float,
+ Vec2,
+ Vec3,
+ Vec4,
+ Mat2,
+ Mat2x3,
+ Mat2x4,
+ Mat3,
+ Mat3x2,
+ Mat3x4,
+ Mat4,
+ Mat4x2,
+ Mat4x3,
+
+ Int,
+ Int2,
+ Int3,
+ Int4,
+
+ Uint,
+ Uint2,
+ Uint3,
+ Uint4,
+
+ Bool,
+ Bool2,
+ Bool3,
+ Bool4,
+
+ Double,
+ Double2,
+ Double3,
+ Double4,
+ DMat2,
+ DMat2x3,
+ DMat2x4,
+ DMat3,
+ DMat3x2,
+ DMat3x4,
+ DMat4,
+ DMat4x2,
+ DMat4x3,
+
+ Sampler1D,
+ Sampler2D,
+ Sampler2DMS,
+ Sampler3D,
+ SamplerCube,
+ Sampler1DArray,
+ Sampler2DArray,
+ Sampler2DMSArray,
+ Sampler3DArray,
+ SamplerCubeArray,
+ SamplerRect,
+ SamplerBuffer,
+
+ Image1D,
+ Image2D,
+ Image2DMS,
+ Image3D,
+ ImageCube,
+ Image1DArray,
+ Image2DArray,
+ Image2DMSArray,
+ Image3DArray,
+ ImageCubeArray,
+ ImageRect,
+ ImageBuffer,
+
+ Struct
+ };
+
+ enum ImageFormat {
+ // must match SPIR-V's ImageFormat
+ ImageFormatUnknown = 0,
+ ImageFormatRgba32f = 1,
+ ImageFormatRgba16f = 2,
+ ImageFormatR32f = 3,
+ ImageFormatRgba8 = 4,
+ ImageFormatRgba8Snorm = 5,
+ ImageFormatRg32f = 6,
+ ImageFormatRg16f = 7,
+ ImageFormatR11fG11fB10f = 8,
+ ImageFormatR16f = 9,
+ ImageFormatRgba16 = 10,
+ ImageFormatRgb10A2 = 11,
+ ImageFormatRg16 = 12,
+ ImageFormatRg8 = 13,
+ ImageFormatR16 = 14,
+ ImageFormatR8 = 15,
+ ImageFormatRgba16Snorm = 16,
+ ImageFormatRg16Snorm = 17,
+ ImageFormatRg8Snorm = 18,
+ ImageFormatR16Snorm = 19,
+ ImageFormatR8Snorm = 20,
+ ImageFormatRgba32i = 21,
+ ImageFormatRgba16i = 22,
+ ImageFormatRgba8i = 23,
+ ImageFormatR32i = 24,
+ ImageFormatRg32i = 25,
+ ImageFormatRg16i = 26,
+ ImageFormatRg8i = 27,
+ ImageFormatR16i = 28,
+ ImageFormatR8i = 29,
+ ImageFormatRgba32ui = 30,
+ ImageFormatRgba16ui = 31,
+ ImageFormatRgba8ui = 32,
+ ImageFormatR32ui = 33,
+ ImageFormatRgb10a2ui = 34,
+ ImageFormatRg32ui = 35,
+ ImageFormatRg16ui = 36,
+ ImageFormatRg8ui = 37,
+ ImageFormatR16ui = 38,
+ ImageFormatR8ui = 39
+ };
+
+ enum ImageFlag {
+ ReadOnlyImage = 1 << 0,
+ WriteOnlyImage = 1 << 1
+ };
+ Q_DECLARE_FLAGS(ImageFlags, ImageFlag)
+
+ // Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional.
+
+ struct InOutVariable {
+ QString name;
+ VariableType type = Unknown;
+ int location = -1;
+ int binding = -1;
+ int descriptorSet = -1;
+ ImageFormat imageFormat = ImageFormatUnknown;
+ ImageFlags imageFlags;
+ };
+
+ struct BlockVariable {
+ QString name;
+ VariableType type = Unknown;
+ int offset = 0;
+ int size = 0;
+ QVector<int> arrayDims;
+ int arrayStride = 0;
+ int matrixStride = 0;
+ bool matrixIsRowMajor = false;
+ QVector<BlockVariable> structMembers;
+ };
+
+ struct UniformBlock {
+ QString blockName;
+ QString structName; // instanceName
+ int size = 0;
+ int binding = -1;
+ int descriptorSet = -1;
+ QVector<BlockVariable> members;
+ };
+
+ struct PushConstantBlock {
+ QString name;
+ int size = 0;
+ QVector<BlockVariable> members;
+ };
+
+ struct StorageBlock {
+ QString blockName;
+ QString instanceName;
+ int knownSize = 0;
+ int binding = -1;
+ int descriptorSet = -1;
+ QVector<BlockVariable> members;
+ };
+
+ QVector<InOutVariable> inputVariables() const;
+ QVector<InOutVariable> outputVariables() const;
+ QVector<UniformBlock> uniformBlocks() const;
+ QVector<PushConstantBlock> pushConstantBlocks() const;
+ QVector<StorageBlock> storageBlocks() const;
+ QVector<InOutVariable> combinedImageSamplers() const;
+ QVector<InOutVariable> storageImages() const;
+
+private:
+ QShaderDescriptionPrivate *d;
+ friend struct QShaderDescriptionPrivate;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &);
+#endif
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags)
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::InOutVariable &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &);
+#endif
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshaderdescription_p_p.h b/src/gui/rhi/qshaderdescription_p_p.h
new file mode 100644
index 0000000000..dbe68d1060
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription_p_p.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADERDESCRIPTION_P_H
+#define QSHADERDESCRIPTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qshaderdescription_p.h"
+#include <QtCore/QVector>
+#include <QtCore/QAtomicInt>
+#include <QtCore/QJsonDocument>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QShaderDescriptionPrivate
+{
+ QShaderDescriptionPrivate()
+ : ref(1)
+ {
+ }
+
+ QShaderDescriptionPrivate(const QShaderDescriptionPrivate *other)
+ : ref(1),
+ inVars(other->inVars),
+ outVars(other->outVars),
+ uniformBlocks(other->uniformBlocks),
+ pushConstantBlocks(other->pushConstantBlocks),
+ storageBlocks(other->storageBlocks),
+ combinedImageSamplers(other->combinedImageSamplers),
+ storageImages(other->storageImages)
+ {
+ }
+
+ static QShaderDescriptionPrivate *get(QShaderDescription *desc) { return desc->d; }
+ static const QShaderDescriptionPrivate *get(const QShaderDescription *desc) { return desc->d; }
+
+ QJsonDocument makeDoc();
+ void loadDoc(const QJsonDocument &doc);
+
+ QAtomicInt ref;
+ QVector<QShaderDescription::InOutVariable> inVars;
+ QVector<QShaderDescription::InOutVariable> outVars;
+ QVector<QShaderDescription::UniformBlock> uniformBlocks;
+ QVector<QShaderDescription::PushConstantBlock> pushConstantBlocks;
+ QVector<QShaderDescription::StorageBlock> storageBlocks;
+ QVector<QShaderDescription::InOutVariable> combinedImageSamplers;
+ QVector<QShaderDescription::InOutVariable> storageImages;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/rhi.pri b/src/gui/rhi/rhi.pri
new file mode 100644
index 0000000000..d8607f1024
--- /dev/null
+++ b/src/gui/rhi/rhi.pri
@@ -0,0 +1,57 @@
+HEADERS += \
+ rhi/qrhi_p.h \
+ rhi/qrhi_p_p.h \
+ rhi/qrhiprofiler_p.h \
+ rhi/qrhiprofiler_p_p.h \
+ rhi/qrhinull_p.h \
+ rhi/qrhinull_p_p.h \
+ rhi/qshader_p.h \
+ rhi/qshader_p_p.h \
+ rhi/qshaderdescription_p.h \
+ rhi/qshaderdescription_p_p.h
+
+SOURCES += \
+ rhi/qrhi.cpp \
+ rhi/qrhiprofiler.cpp \
+ rhi/qrhinull.cpp \
+ rhi/qshaderdescription.cpp \
+ rhi/qshader.cpp
+
+qtConfig(opengl) {
+ HEADERS += \
+ rhi/qrhigles2_p.h \
+ rhi/qrhigles2_p_p.h
+ SOURCES += \
+ rhi/qrhigles2.cpp
+}
+
+qtConfig(vulkan) {
+ HEADERS += \
+ rhi/qrhivulkan_p.h \
+ rhi/qrhivulkan_p_p.h
+ SOURCES += \
+ rhi/qrhivulkan.cpp
+}
+
+win32 {
+ HEADERS += \
+ rhi/qrhid3d11_p.h \
+ rhi/qrhid3d11_p_p.h
+ SOURCES += \
+ rhi/qrhid3d11.cpp
+
+ LIBS += -ld3d11 -ldxgi -ldxguid -ld3dcompiler
+}
+
+# darwin {
+macos {
+ HEADERS += \
+ rhi/qrhimetal_p.h \
+ rhi/qrhimetal_p_p.h
+ SOURCES += \
+ rhi/qrhimetal.mm
+
+ LIBS += -framework AppKit -framework Metal
+}
+
+include($$PWD/../../3rdparty/VulkanMemoryAllocator.pri)