diff options
Diffstat (limited to 'src/gui/rhi')
29 files changed, 31309 insertions, 0 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp new file mode 100644 index 0000000000..dbad63c6d1 --- /dev/null +++ b/src/gui/rhi/qrhi.cpp @@ -0,0 +1,5292 @@ +/**************************************************************************** +** +** 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. + + \note Resources within a render or compute pass are expected to be bound to + a single usage during that pass. For example, a buffer can be used as + vertex, index, uniform, or storage buffer, but not a combination of them + within a single pass. However, it is perfectly fine to use a buffer as a + storage buffer in a compute pass, and then as a vertex buffer in a render + pass, for example, assuming the buffer declared both usages upon creation. + + \note Textures have this rule relaxed in certain cases, because using two + subresources (typically two different mip levels) of the same texture for + different access (one for load, one for store) is supported even within the + same pass. + + \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. + + \value Compute Indicates that compute shaders are supported. + */ + +/*! + \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 QRhiShaderStage + \inmodule QtRhi + \brief Specifies the type and the shader code for a shader stage in the pipeline. + */ + +/*! + \enum QRhiShaderStage::Type + Specifies the type of the shader stage. + + \value Vertex Vertex stage + \value Fragment Fragment (pixel) stage + \value Compute Compute stage (this may not always be supported at run time) + */ + +/*! + \fn QRhiShaderStage::QRhiShaderStage() + + 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. + */ +QRhiShaderStage::QRhiShaderStage(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 QRhiShaderStage objects + \a a and \a b are equal. + + \relates QRhiShaderStage + */ +bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &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 QRhiShaderStage + objects \a a and \a b are equal; otherwise returns \c true. + + \relates QRhiShaderStage +*/ +bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW +{ + return !(a == b); +} + +/*! + \return the hash value for \a v, using \a seed to seed the calculation. + + \relates QRhiShaderStage + */ +uint qHash(const QRhiShaderStage &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 QRhiShaderStage &s) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "QRhiShaderStage(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. This allows the QRhiBuffer to be used in + \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}. + + \value IndexBuffer Index buffer. This allows the QRhiBuffer to be used in + \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}. + + \value UniformBuffer Uniform buffer (also called constant buffer). This + allows the QRhiBuffer to be used in combination with + \l{UniformBuffer}{QRhiShaderResourceBinding::UniformBuffer}. When + \l{QRhi::NonDynamicUniformBuffers}{NonDynamicUniformBuffers} is reported as + not supported, this usage can only be combined with the type Dynamic. + + \value StorageBuffer Storage buffer. This allows the QRhiBuffer to be used + in combination with \l{BufferLoad}{QRhiShaderResourceBinding::BufferLoad}, + \l{BufferStore}{QRhiShaderResourceBinding::BufferStore}, or + \l{BufferLoadStore}{QRhiShaderResourceBinding::BufferLoadStore}. This usage + can only be combined with the types Immutable or Static, and is only + available when the \l{QRhi::Compute}{Compute feature} is reported as + supported. + */ + +/*! + \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(). + + \value UsedWithLoadStore The texture is going to be used with image + load/store operations, for example, in a compute shader. + */ + +/*! + \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 + + \value ImageLoad Image load (with GLSL this maps to doing imageLoad() on a + single level - and either one or all layers - of a texture exposed to the + shader as an image object) + + \value ImageStore Image store (with GLSL this maps to doing imageStore() or + imageAtomic*() on a single level - and either one or all layers - of a + texture exposed to the shader as an image object) + + \value ImageLoadStore Image load and store + + \value BufferLoad Storage buffer store (with GLSL this maps to reading from + a shader storage buffer) + + \value BufferStore Storage buffer store (with GLSL this maps to writing to + a shader storage buffer) + + \value BufferLoadStore Storage buffer load and store + */ + +/*! + \enum QRhiShaderResourceBinding::StageFlag + Flag values to indicate which stages the shader resource is visible in + + \value VertexStage Vertex stage + \value FragmentStage Fragment (pixel) stage + \value ComputeStage Compute 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. + + \note \a buf must have been created with QRhiBuffer::UniformBuffer. + */ +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. + + \note \a buf must have been created with QRhiBuffer::UniformBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer( + int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) +{ + Q_ASSERT(size > 0); + QRhiShaderResourceBinding b = uniformBuffer(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->u.ubuf.offset = offset; + d->u.ubuf.maybeSize = size; + 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. + + \note \a buf must have been created with QRhiBuffer::UniformBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOffset( + int binding, StageFlags stage, QRhiBuffer *buf, int size) +{ + QRhiShaderResourceBinding b = uniformBuffer(binding, stage, buf, 0, size); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + 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 a shader resource binding for a read-only storage image with the + given \a binding number and pipeline \a stage. The image load operations + will have access to all layers of the specified \a level. (so if the texture + is a cubemap, the shader must use imageCube instead of image2D) + + \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoad( + int binding, StageFlags stage, QRhiTexture *tex, int level) +{ + QRhiShaderResourceBinding b; + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + Q_ASSERT(d->ref.load() == 1); + d->binding = binding; + d->stage = stage; + d->type = ImageLoad; + d->u.simage.tex = tex; + d->u.simage.level = level; + return b; +} + +/*! + \return a shader resource binding for a write-only storage image with the + given \a binding number and pipeline \a stage. The image store operations + will have access to all layers of the specified \a level. (so if the texture + is a cubemap, the shader must use imageCube instead of image2D) + + \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::imageStore( + int binding, StageFlags stage, QRhiTexture *tex, int level) +{ + QRhiShaderResourceBinding b = imageLoad(binding, stage, tex, level); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->type = ImageStore; + return b; +} + +/*! + \return a shader resource binding for a read/write storage image with the + given \a binding number and pipeline \a stage. The image load/store operations + will have access to all layers of the specified \a level. (so if the texture + is a cubemap, the shader must use imageCube instead of image2D) + + \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoadStore( + int binding, StageFlags stage, QRhiTexture *tex, int level) +{ + QRhiShaderResourceBinding b = imageLoad(binding, stage, tex, level); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->type = ImageLoadStore; + return b; +} + +/*! + \return a shader resource binding for a read-only storage buffer with the + given \a binding number and pipeline \a stage. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( + 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 = BufferLoad; + d->u.sbuf.buf = buf; + d->u.sbuf.offset = 0; + d->u.sbuf.maybeSize = 0; // entire buffer + return b; +} + +/*! + \return a shader resource binding for a read-only storage buffer with the + given \a binding number and pipeline \a stage. This overload binds a region + only, as specified by \a offset and \a size. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( + int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) +{ + Q_ASSERT(size > 0); + QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->u.sbuf.offset = offset; + d->u.sbuf.maybeSize = size; + return b; +} + +/*! + \return a shader resource binding for a write-only storage buffer with the + given \a binding number and pipeline \a stage. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( + int binding, StageFlags stage, QRhiBuffer *buf) +{ + QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->type = BufferStore; + return b; +} + +/*! + \return a shader resource binding for a write-only storage buffer with the + given \a binding number and pipeline \a stage. This overload binds a region + only, as specified by \a offset and \a size. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( + int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) +{ + Q_ASSERT(size > 0); + QRhiShaderResourceBinding b = bufferStore(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->u.sbuf.offset = offset; + d->u.sbuf.maybeSize = size; + return b; +} + +/*! + \return a shader resource binding for a read-write storage buffer with the + given \a binding number and pipeline \a stage. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( + int binding, StageFlags stage, QRhiBuffer *buf) +{ + QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->type = BufferLoadStore; + return b; +} + +/*! + \return a shader resource binding for a read-write storage buffer with the + given \a binding number and pipeline \a stage. This overload binds a region + only, as specified by \a offset and \a size. + + \note \a buf must have been created with QRhiBuffer::StorageBuffer. + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( + int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) +{ + Q_ASSERT(size > 0); + QRhiShaderResourceBinding b = bufferLoadStore(binding, stage, buf); + QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b); + d->u.sbuf.offset = offset; + d->u.sbuf.maybeSize = size; + 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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + if (a.d->u.simage.tex != b.d->u.simage.tex + || a.d->u.simage.level != b.d->u.simage.level) + { + return false; + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + if (a.d->u.sbuf.buf != b.d->u.sbuf.buf + || a.d->u.sbuf.offset != b.d->u.sbuf.offset + || a.d->u.sbuf.maybeSize != b.d->u.sbuf.maybeSize) + { + 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; + case QRhiShaderResourceBinding::ImageLoad: + dbg.nospace() << " ImageLoad(" + << "texture=" << d->u.simage.tex + << " level=" << d->u.simage.level + << ')'; + break; + case QRhiShaderResourceBinding::ImageStore: + dbg.nospace() << " ImageStore(" + << "texture=" << d->u.simage.tex + << " level=" << d->u.simage.level + << ')'; + break; + case QRhiShaderResourceBinding::ImageLoadStore: + dbg.nospace() << " ImageLoadStore(" + << "texture=" << d->u.simage.tex + << " level=" << d->u.simage.level + << ')'; + break; + case QRhiShaderResourceBinding::BufferLoad: + dbg.nospace() << " BufferLoad(" + << "buffer=" << d->u.sbuf.buf + << " offset=" << d->u.sbuf.offset + << " maybeSize=" << d->u.sbuf.maybeSize + << ')'; + break; + case QRhiShaderResourceBinding::BufferStore: + dbg.nospace() << " BufferStore(" + << "buffer=" << d->u.sbuf.buf + << " offset=" << d->u.sbuf.offset + << " maybeSize=" << d->u.sbuf.maybeSize + << ')'; + break; + case QRhiShaderResourceBinding::BufferLoadStore: + dbg.nospace() << " BufferLoadStore(" + << "buffer=" << d->u.sbuf.buf + << " offset=" << d->u.sbuf.offset + << " maybeSize=" << d->u.sbuf.maybeSize + << ')'; + 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 QRhiComputePipeline + \inmodule QtRhi + \brief Compute 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 shader is mandatory. + */ + +/*! + \return the resource type. + */ +QRhiResource::Type QRhiComputePipeline::resourceType() const +{ + return ComputePipeline; +} + +/*! + \internal + */ +QRhiComputePipeline::QRhiComputePipeline(QRhiImplementation *rhi) + : QRhiResource(rhi) +{ +} + +/*! + \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 render pass, meaning + between a beginPass() and 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 or compute pipeline's + associated QRhiShaderResourceBindings 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 the pipeline's 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 render or compute pass, + meaning between a beginPass() and endPass(), or beginComputePass() and + endComputePass(). + */ +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 render pass, meaning + between a beginPass() and 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 render pass, meaning + between a beginPass() and 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 render pass, meaning + between a beginPass() and 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 render pass, meaning + between a beginPass() and 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 render pass, meaning between + a beginPass() and 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 render pass, meaning + between a beginPass() and 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 render pass, meaning + between a beginPass() and 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); +} + +/*! + Records starting a new compute pass. + + \a resourceUpdates, when not null, specifies a resource update batch that + is to be committed and then released. + + \note Do not assume that any state or resource bindings persist between + passes. + + \note A compute pass can record setComputePipeline(), setShaderResources(), + and dispatch() calls, not graphics ones. General functionality, such as, + debug markers and beginExternal() is available both in render and compute + passes. + + \note Compute is only available when the \l{QRhi::Compute}{Compute} feature + is reported as supported. + */ +void QRhiCommandBuffer::beginComputePass(QRhiResourceUpdateBatch *resourceUpdates) +{ + m_rhi->beginComputePass(this, resourceUpdates); +} + +/*! + Records ending the current compute pass. + + \a resourceUpdates, when not null, specifies a resource update batch that + is to be committed and then released. + */ +void QRhiCommandBuffer::endComputePass(QRhiResourceUpdateBatch *resourceUpdates) +{ + m_rhi->endComputePass(this, resourceUpdates); +} + +/*! + Records setting a new compute pipeline \a ps. + + \note This function must be called before recording setShaderResources() or + dispatch() 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 compute pass, meaning + between a beginComputePass() and endComputePass() call. + */ +void QRhiCommandBuffer::setComputePipeline(QRhiComputePipeline *ps) +{ + m_rhi->setComputePipeline(this, ps); +} + +/*! + Records dispatching compute work items, with \a x, \a y, and \a z + specifying the number of local workgroups in the corresponding dimension. + + \note This function can only be called inside a compute pass, meaning + between a beginComputePass() and endComputePass() call. + */ +void QRhiCommandBuffer::dispatch(int x, int y, int z) +{ + m_rhi->dispatch(this, x, y, z); +} + +/*! + \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(); +} + +/*! + With OpenGL this makes the OpenGL context current on the current thread. + The function has no effect with other backends. + + Calling this function is relevant typically in Qt framework code, when one + has to ensure external OpenGL code provided by the application can still + run like it did before with direct usage of OpenGL, as long as the QRhi is + using the OpenGL backend. + */ +void QRhi::makeThreadLocalNativeContextCurrent() +{ + d->makeThreadLocalNativeContextCurrent(); +} + +/*! + \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 compute pipeline resource. + + \note Compute is only available when the \l{QRhi::Compute}{Compute} feature + is reported as supported. + + \sa QRhiResource::release() + */ +QRhiComputePipeline *QRhi::newComputePipeline() +{ + return d->createComputePipeline(); +} + +/*! + \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{QRhiBuffer::UsageFlag}{UsageFlags} and + \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::registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, + const UsageState &state) +{ + 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); + *stage = it->stage; + } + return; + } + + Buffer b; + b.buf = buf; + b.slot = slot; + b.access = *access; + b.stage = *stage; + b.stateAtPassBegin = state; // first use -> initial state + m_buffers.append(b); +} + +static inline QRhiPassResourceTracker::TextureStage earlierStage(QRhiPassResourceTracker::TextureStage a, + QRhiPassResourceTracker::TextureStage b) +{ + return QRhiPassResourceTracker::TextureStage(qMin(int(a), int(b))); +} + +static inline bool isImageLoadStore(QRhiPassResourceTracker::TextureAccess access) +{ + return access == QRhiPassResourceTracker::TexStorageLoad + || access == QRhiPassResourceTracker::TexStorageStore + || access == QRhiPassResourceTracker::TexStorageLoadStore; +} + +void QRhiPassResourceTracker::registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, + const UsageState &state) +{ + 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) { + // Different subresources of a texture may be used for both load + // and store in the same pass. (think reading from one mip level + // and writing to another one in a compute shader) This we can + // handle by treating the entire resource as read-write. + if (isImageLoadStore(it->access) && isImageLoadStore(*access)) { + it->access = QRhiPassResourceTracker::TexStorageLoadStore; + *access = it->access; + } else { + 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); + *stage = it->stage; + } + return; + } + + Texture t; + t.tex = tex; + t.access = *access; + t.stage = *stage; + t.stateAtPassBegin = state; // first use -> initial state + 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..0d296d370c --- /dev/null +++ b/src/gui/rhi/qrhi_p.h @@ -0,0 +1,1426 @@ +/**************************************************************************** +** +** 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 QRhiShaderStage +{ +public: + enum Type { + Vertex, + Fragment, + Compute + }; + + QRhiShaderStage() = default; + QRhiShaderStage(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(QRhiShaderStage, Q_MOVABLE_TYPE); + +Q_GUI_EXPORT bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW; +Q_GUI_EXPORT bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW; +Q_GUI_EXPORT uint qHash(const QRhiShaderStage &s, uint seed = 0) Q_DECL_NOTHROW; +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderStage &); +#endif + +using QRhiGraphicsShaderStage = QRhiShaderStage; + +class Q_GUI_EXPORT QRhiShaderResourceBinding +{ +public: + enum Type { + UniformBuffer, + SampledTexture, + ImageLoad, + ImageStore, + ImageLoadStore, + BufferLoad, + BufferStore, + BufferLoadStore + }; + + enum StageFlag { + VertexStage = 1 << 0, + FragmentStage = 1 << 1, + ComputeStage = 1 << 2 + }; + 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); + + static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level); + static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level); + static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level); + + static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size); + static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size); + static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size); + +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, + ComputePipeline, + 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, + StorageBuffer = 1 << 3 + }; + 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, + UsedWithLoadStore = 1 << 7 + }; + 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<QRhiShaderStage> shaderStages() const { return m_shaderStages; } + void setShaderStages(const QVector<QRhiShaderStage> &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<QRhiShaderStage> 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 QRhiComputePipeline : public QRhiResource +{ +public: + QRhiResource::Type resourceType() const override; + virtual bool build() = 0; + + QRhiShaderStage shaderStage() const { return m_shaderStage; } + void setShaderStage(const QRhiShaderStage &stage) { m_shaderStage = stage; } + + QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } + void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; } + +protected: + QRhiComputePipeline(QRhiImplementation *rhi); + QRhiShaderStage m_shaderStage; + QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; +}; + +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); + + void beginComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); + void endComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); + void setComputePipeline(QRhiComputePipeline *ps); + void dispatch(int x, int y, int z); + + 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, + Compute + }; + + 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(); + QRhiComputePipeline *newComputePipeline(); + 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(); + void makeThreadLocalNativeContextCurrent(); + + 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..4fd01d3ef2 --- /dev/null +++ b/src/gui/rhi/qrhi_p_p.h @@ -0,0 +1,570 @@ +/**************************************************************************** +** +** 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 QRhiComputePipeline *createComputePipeline() = 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 void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; + virtual void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; + virtual void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) = 0; + virtual void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) = 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; + virtual void makeThreadLocalNativeContextCurrent() = 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. + bool inFrame = false; + +private: + QRhi::Implementation implType; + QThread *implThread; + QRhiProfiler profiler; + QVector<QRhiResourceUpdateBatch *> resUpdPool; + QBitArray resUpdPoolMap; + QSet<QRhiResource *> resources; + QSet<QRhiResource *> pendingReleaseAndDestroyResources; + QVector<QRhi::CleanupCallback> cleanupCallbacks; + + 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; + }; + struct StorageImageData { + QRhiTexture *tex; + int level; + }; + struct StorageBufferData { + QRhiBuffer *buf; + int offset; + int maybeSize; + }; + union { + UniformBufferData ubuf; + SampledTextureData stex; + StorageImageData simage; + StorageBufferData sbuf; + } 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, + BufComputeStage + }; + + enum BufferAccess { + BufVertexInput, + BufIndexRead, + BufUniformRead, + BufStorageLoad, + BufStorageStore, + BufStorageLoadStore + }; + + void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, + const UsageState &state); + + enum TextureStage { + TexVertexStage, + TexFragmentStage, + TexColorOutputStage, + TexDepthOutputStage, + TexComputeStage + }; + + enum TextureAccess { + TexSample, + TexColorOutput, + TexDepthOutput, + TexStorageLoad, + TexStorageStore, + TexStorageLoadStore + }; + + void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, + const UsageState &state); + + 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..7d9c934c18 --- /dev/null +++ b/src/gui/rhi/qrhid3d11.cpp @@ -0,0 +1,3771 @@ +/**************************************************************************** +** +** 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, ¶ms); + \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; + case QRhi::Compute: + 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 +} + +void QRhiD3D11::makeThreadLocalNativeContextCurrent() +{ + // 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); +} + +QRhiComputePipeline *QRhiD3D11::createComputePipeline() +{ + return new QD3D11ComputePipeline(this); +} + +QRhiShaderResourceBindings *QRhiD3D11::createShaderResourceBindings() +{ + return new QD3D11ShaderResourceBindings(this); +} + +void QRhiD3D11::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + QD3D11GraphicsPipeline *psD = QRHI_RES(QD3D11GraphicsPipeline, ps); + const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation; + + if (pipelineChanged) { + cbD->currentGraphicsPipeline = ps; + cbD->currentComputePipeline = nullptr; + 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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass != QD3D11CommandBuffer::NoPass); + QD3D11GraphicsPipeline *gfxPsD = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline); + QD3D11ComputePipeline *compPsD = QRHI_RES(QD3D11ComputePipeline, cbD->currentComputePipeline); + + if (!srb) { + if (gfxPsD) + srb = gfxPsD->m_shaderResourceBindings; + else + srb = compPsD->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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex); + if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) { + srbUpdate = true; + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + } + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf); + if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { + srbUpdate = true; + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->generation; + } + } + break; + default: + Q_UNREACHABLE(); + break; + } + } + + if (srbUpdate) + updateShaderResourceBindings(srbD); + + const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); + const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation; + + if (srbChanged || srbRebuilt || srbUpdate || hasDynamicOffsetInSrb) { + if (gfxPsD) { + cbD->currentGraphicsSrb = srb; + cbD->currentComputeSrb = nullptr; + } else { + cbD->currentGraphicsSrb = nullptr; + cbD->currentComputeSrb = 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 && !srbRebuilt && !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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + 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->currentGraphicsPipeline)->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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + 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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + 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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::BlendConstants; + cmd.args.blendConstants.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline); + 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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::StencilRef; + cmd.args.stencilRef.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline); + cmd.args.stencilRef.ref = refValue; + cbD->commands.append(cmd); +} + +void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::Draw; + cmd.args.draw.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline); + 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) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::DrawIndexed; + cmd.args.drawIndexed.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline); + 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_UNUSED(cb); + flushCommandBuffer(); +} + +void QRhiD3D11::endExternal(QRhiCommandBuffer *cb) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->commands.isEmpty()); + cbD->resetCachedState(); + if (cbD->currentTarget) { // could be compute, no rendertarget then + QD3D11CommandBuffer::Command fbCmd; + fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget; + fbCmd.args.setRenderTarget.rt = cbD->currentTarget; + cbD->commands.append(fbCmd); + } +} + +QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) +{ + Q_UNUSED(flags); + + 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, ×tamps[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, ×tamps[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) +{ + 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) +{ + ofr.active = true; + + ofr.cbWrapper.resetState(); + *cb = &ofr.cbWrapper; + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame() +{ + 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() +{ + 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(QRHI_RES(QD3D11CommandBuffer, cb)->recordingPass == QD3D11CommandBuffer::NoPass); + + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiD3D11::beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + 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); + } + + QD3D11CommandBuffer::Command fbCmd; + fbCmd.cmd = QD3D11CommandBuffer::Command::ResetShaderResources; + cbD->commands.append(fbCmd); + fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget; + fbCmd.args.setRenderTarget.rt = rt; + cbD->commands.append(fbCmd); + + 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->recordingPass = QD3D11CommandBuffer::RenderPass; + cbD->currentTarget = rt; +} + +void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass); + + 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->recordingPass = QD3D11CommandBuffer::NoPass; + cbD->currentTarget = nullptr; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiD3D11::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::ResetShaderResources; + cbD->commands.append(cmd); + + cbD->recordingPass = QD3D11CommandBuffer::ComputePass; +} + +void QRhiD3D11::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass); + + cbD->recordingPass = QD3D11CommandBuffer::NoPass; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiD3D11::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass); + QD3D11ComputePipeline *psD = QRHI_RES(QD3D11ComputePipeline, ps); + const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation; + + if (pipelineChanged) { + cbD->currentGraphicsPipeline = nullptr; + cbD->currentComputePipeline = psD; + cbD->currentPipelineGeneration = psD->generation; + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::BindComputePipeline; + cmd.args.bindComputePipeline.ps = psD; + cbD->commands.append(cmd); + } +} + +void QRhiD3D11::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::Dispatch; + cmd.args.dispatch.x = x; + cmd.args.dispatch.y = y; + cmd.args.dispatch.z = z; + cbD->commands.append(cmd); +} + +void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD) +{ + srbD->vsubufs.clear(); + srbD->vsubufoffsets.clear(); + srbD->vsubufsizes.clear(); + + srbD->fsubufs.clear(); + srbD->fsubufoffsets.clear(); + srbD->fsubufsizes.clear(); + + srbD->csubufs.clear(); + srbD->csubufoffsets.clear(); + srbD->csubufsizes.clear(); + + srbD->vssamplers.clear(); + srbD->vsshaderresources.clear(); + + srbD->fssamplers.clear(); + srbD->fsshaderresources.clear(); + + srbD->cssamplers.clear(); + srbD->csshaderresources.clear(); + + srbD->csUAVs.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); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + srbD->csubufs.feed(b->binding, bufD->buffer); + srbD->csubufoffsets.feed(b->binding, offsetInConstants); + srbD->csubufsizes.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); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + srbD->cssamplers.feed(b->binding, samplerD->samplerState); + srbD->csshaderresources.feed(b->binding, texD->srv); + } + } + break; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex); + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + ID3D11UnorderedAccessView *uav = texD->unorderedAccessViewForLevel(b->u.simage.level); + if (uav) + srbD->csUAVs.feed(b->binding, uav); + } else { + qWarning("Unordered access only supported at compute stage"); + } + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf); + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->generation; + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + ID3D11UnorderedAccessView *uav = bufD->unorderedAccessView(); + if (uav) + srbD->csUAVs.feed(b->binding, uav); + } else { + qWarning("Unordered access only supported at compute stage"); + } + } + break; + default: + Q_UNREACHABLE(); + break; + } + } + + srbD->vsubufs.finish(); + srbD->vsubufoffsets.finish(); + srbD->vsubufsizes.finish(); + + srbD->fsubufs.finish(); + srbD->fsubufoffsets.finish(); + srbD->fsubufsizes.finish(); + + srbD->csubufs.finish(); + srbD->csubufoffsets.finish(); + srbD->csubufsizes.finish(); + + srbD->vssamplers.finish(); + srbD->vsshaderresources.finish(); + + srbD->fssamplers.finish(); + srbD->fsshaderresources.finish(); + + srbD->cssamplers.finish(); + srbD->csshaderresources.finish(); + + srbD->csUAVs.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))); + } +} + +static void applyDynamicOffsets(QVarLengthArray<UINT, 4> *offsets, + int batchIndex, + QRhiBatchedBindings<ID3D11Buffer *> *ubufs, + QRhiBatchedBindings<UINT> *ubufoffsets, + const uint *dynOfsPairs, int dynOfsPairCount) +{ + const UINT count = ubufs->batches[batchIndex].resources.count(); + const UINT startBinding = ubufs->batches[batchIndex].startBinding; + *offsets = ubufoffsets->batches[batchIndex].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; + } + } + } +} + +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 (const auto &batch : srbD->cssamplers.batches) + context->CSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData()); + + for (const auto &batch : srbD->csshaderresources.batches) { + context->CSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData()); + contextState.csHighestActiveSrvBinding = qMax<int>(contextState.csHighestActiveSrvBinding, + 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 { + QVarLengthArray<UINT, 4> offsets; + applyDynamicOffsets(&offsets, i, &srbD->vsubufs, &srbD->vsubufoffsets, dynOfsPairs, dynOfsPairCount); + context->VSSetConstantBuffers1(srbD->vsubufs.batches[i].startBinding, + srbD->vsubufs.batches[i].resources.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 { + QVarLengthArray<UINT, 4> offsets; + applyDynamicOffsets(&offsets, i, &srbD->fsubufs, &srbD->fsubufoffsets, dynOfsPairs, dynOfsPairCount); + context->PSSetConstantBuffers1(srbD->fsubufs.batches[i].startBinding, + srbD->fsubufs.batches[i].resources.count(), + srbD->fsubufs.batches[i].resources.constData(), + offsets.constData(), + srbD->fsubufsizes.batches[i].resources.constData()); + } + } + + for (int i = 0, ie = srbD->csubufs.batches.count(); i != ie; ++i) { + if (!dynOfsPairCount) { + context->CSSetConstantBuffers1(srbD->csubufs.batches[i].startBinding, + srbD->csubufs.batches[i].resources.count(), + srbD->csubufs.batches[i].resources.constData(), + srbD->csubufoffsets.batches[i].resources.constData(), + srbD->csubufsizes.batches[i].resources.constData()); + } else { + QVarLengthArray<UINT, 4> offsets; + applyDynamicOffsets(&offsets, i, &srbD->csubufs, &srbD->csubufoffsets, dynOfsPairs, dynOfsPairCount); + context->CSSetConstantBuffers1(srbD->csubufs.batches[i].startBinding, + srbD->csubufs.batches[i].resources.count(), + srbD->csubufs.batches[i].resources.constData(), + offsets.constData(), + srbD->csubufsizes.batches[i].resources.constData()); + } + } + + for (int i = 0, ie = srbD->csUAVs.batches.count(); i != ie; ++i) { + const uint startBinding = srbD->csUAVs.batches[i].startBinding; + const uint count = srbD->csUAVs.batches[i].resources.count(); + context->CSSetUnorderedAccessViews(startBinding, + count, + srbD->csUAVs.batches[i].resources.constData(), + nullptr); + contextState.csHighestActiveUavBinding = qMax<int>(contextState.csHighestActiveUavBinding, + startBinding + count - 1); + } +} + +void QRhiD3D11::resetShaderResources() +{ + // Output cannot be bound on input etc. + + if (contextState.vsHasIndexBufferBound) { + context->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0); + contextState.vsHasIndexBufferBound = false; + } + + if (contextState.vsHighestActiveVertexBufferBinding >= 0) { + const int count = contextState.vsHighestActiveVertexBufferBinding + 1; + QVarLengthArray<ID3D11Buffer *, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullbufs(count); + for (int i = 0; i < count; ++i) + nullbufs[i] = nullptr; + QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullstrides(count); + for (int i = 0; i < count; ++i) + nullstrides[i] = 0; + QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nulloffsets(count); + for (int i = 0; i < count; ++i) + nulloffsets[i] = 0; + context->IASetVertexBuffers(0, count, nullbufs.constData(), nullstrides.constData(), nulloffsets.constData()); + contextState.vsHighestActiveVertexBufferBinding = -1; + } + + int nullsrvCount = qMax(contextState.vsHighestActiveSrvBinding, contextState.fsHighestActiveSrvBinding); + nullsrvCount = qMax(nullsrvCount, contextState.csHighestActiveSrvBinding); + nullsrvCount += 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; + } + if (contextState.csHighestActiveSrvBinding >= 0) { + context->CSSetShaderResources(0, contextState.csHighestActiveSrvBinding + 1, nullsrvs.constData()); + contextState.csHighestActiveSrvBinding = -1; + } + } + + if (contextState.csHighestActiveUavBinding >= 0) { + const int nulluavCount = contextState.csHighestActiveUavBinding + 1; + QVarLengthArray<ID3D11UnorderedAccessView *, + D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nulluavs(nulluavCount); + for (int i = 0; i < nulluavCount; ++i) + nulluavs[i] = nullptr; + context->CSSetUnorderedAccessViews(0, nulluavCount, nulluavs.constData(), nullptr); + contextState.csHighestActiveUavBinding = -1; + } +} + +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); + QD3D11RenderTargetData *rtD = rtData(×tampSwapChain->rt); + context->OMSetRenderTargets(rtD->colorAttCount, rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv); + context->End(tsStart); // just record a timestamp, no Begin needed + } + } + + for (const QD3D11CommandBuffer::Command &cmd : qAsConst(cbD->commands)) { + switch (cmd.cmd) { + case QD3D11CommandBuffer::Command::ResetShaderResources: + resetShaderResources(); + break; + case QD3D11CommandBuffer::Command::SetRenderTarget: + { + QD3D11RenderTargetData *rtD = rtData(cmd.args.setRenderTarget.rt); + context->OMSetRenderTargets(rtD->colorAttCount, rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv); + } + 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: + contextState.vsHighestActiveVertexBufferBinding = qMax<int>( + contextState.vsHighestActiveVertexBufferBinding, + cmd.args.bindVertexBuffers.startSlot + cmd.args.bindVertexBuffers.slotCount - 1); + 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: + contextState.vsHasIndexBufferBound = true; + 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; + case QD3D11CommandBuffer::Command::BindComputePipeline: + context->CSSetShader(cmd.args.bindComputePipeline.ps->cs, nullptr, 0); + break; + case QD3D11CommandBuffer::Command::Dispatch: + context->Dispatch(cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z); + 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; + + if (uav) { + uav->Release(); + uav = 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; + if (usage.testFlag(QRhiBuffer::StorageBuffer)) + u |= D3D11_BIND_UNORDERED_ACCESS; + return u; +} + +bool QD3D11Buffer::build() +{ + if (buffer) + release(); + + if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) { + qWarning("UniformBuffer must always be combined with Dynamic on D3D11"); + return false; + } + + if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) { + qWarning("StorageBuffer cannot be combined with Dynamic"); + return false; + } + + const int nonZeroSize = m_size <= 0 ? 256 : m_size; + const int roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256 : 4); + + 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; + desc.MiscFlags = m_usage.testFlag(QRhiBuffer::StorageBuffer) ? D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS : 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; +} + +ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView() +{ + if (uav) + return uav; + + // SPIRV-Cross generated HLSL uses RWByteAddressBuffer + D3D11_UNORDERED_ACCESS_VIEW_DESC desc; + memset(&desc, 0, sizeof(desc)); + desc.Format = DXGI_FORMAT_R32_TYPELESS; + desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + desc.Buffer.FirstElement = 0; + desc.Buffer.NumElements = aligned(m_size, 4) / 4; + desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; + + QRHI_RES_RHI(QRhiD3D11); + HRESULT hr = rhiD->dev->CreateUnorderedAccessView(buffer, &desc, &uav); + if (FAILED(hr)) { + qWarning("Failed to create UAV: %s", qPrintable(comErrorMessage(hr))); + return nullptr; + } + + return uav; +} + +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) +{ + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) + perLevelViews[i] = nullptr; +} + +QD3D11Texture::~QD3D11Texture() +{ + release(); +} + +void QD3D11Texture::release() +{ + if (!tex) + return; + + if (srv) { + srv->Release(); + srv = nullptr; + } + + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + if (perLevelViews[i]) { + perLevelViews[i]->Release(); + perLevelViews[i] = 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; + } + if (m_flags.testFlag(UsedWithLoadStore)) + bindFlags |= D3D11_BIND_UNORDERED_ACCESS; + + 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; +} + +ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level) +{ + if (perLevelViews[level]) + return perLevelViews[level]; + + const bool isCube = m_flags.testFlag(CubeMap); + D3D11_UNORDERED_ACCESS_VIEW_DESC desc; + memset(&desc, 0, sizeof(desc)); + desc.Format = dxgiFormat; + if (isCube) { + desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; + desc.Texture2DArray.MipSlice = level; + desc.Texture2DArray.FirstArraySlice = 0; + desc.Texture2DArray.ArraySize = 6; + } else { + desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + desc.Texture2D.MipSlice = level; + } + + QRHI_RES_RHI(QRhiD3D11); + ID3D11UnorderedAccessView *uav = nullptr; + HRESULT hr = rhiD->dev->CreateUnorderedAccessView(tex, &desc, &uav); + if (FAILED(hr)) { + qWarning("Failed to create UAV: %s", qPrintable(comErrorMessage(hr))); + return nullptr; + } + + perLevelViews[level] = uav; + return uav; +} + +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 QRhiShaderStage &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 QRhiShaderStage::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 QRhiShaderStage::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; +} + +QD3D11ComputePipeline::QD3D11ComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi) +{ +} + +QD3D11ComputePipeline::~QD3D11ComputePipeline() +{ + release(); +} + +void QD3D11ComputePipeline::release() +{ + QRHI_RES_RHI(QRhiD3D11); + + if (!cs) + return; + + cs->Release(); + cs = nullptr; + + rhiD->unregisterResource(this); +} + +bool QD3D11ComputePipeline::build() +{ + if (cs) + release(); + + QRHI_RES_RHI(QRhiD3D11); + + QString error; + QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(), m_shaderStage.shaderVariant(), &error); + if (bytecode.isEmpty()) { + qWarning("HLSL compute shader compilation failed: %s", qPrintable(error)); + return false; + } + + HRESULT hr = rhiD->dev->CreateComputeShader(bytecode.constData(), bytecode.size(), nullptr, &cs); + if (FAILED(hr)) { + qWarning("Failed to create compute shader: %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, ×tampDisjointQuery[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, ×tampQuery[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..688f79b3b7 --- /dev/null +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** 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; + + ID3D11UnorderedAccessView *unorderedAccessView(); + + ID3D11Buffer *buffer = nullptr; + QByteArray dynBuf; + bool hasPendingDynamicUpdates = false; + ID3D11UnorderedAccessView *uav = nullptr; + 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(); + ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level); + + ID3D11Texture2D *tex = nullptr; + bool owns = true; + ID3D11ShaderResourceView *srv = nullptr; + DXGI_FORMAT dxgiFormat; + uint mipLevelCount = 0; + DXGI_SAMPLE_DESC sampleDesc; + QRhiD3D11TextureNativeHandles nativeHandlesStruct; + ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_LEVELS]; + 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 BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + QVector<BoundResourceData> boundResourceData; + + QRhiBatchedBindings<ID3D11Buffer *> vsubufs; + QRhiBatchedBindings<UINT> vsubufoffsets; + QRhiBatchedBindings<UINT> vsubufsizes; + + QRhiBatchedBindings<ID3D11Buffer *> fsubufs; + QRhiBatchedBindings<UINT> fsubufoffsets; + QRhiBatchedBindings<UINT> fsubufsizes; + + QRhiBatchedBindings<ID3D11Buffer *> csubufs; + QRhiBatchedBindings<UINT> csubufoffsets; + QRhiBatchedBindings<UINT> csubufsizes; + + QRhiBatchedBindings<ID3D11SamplerState *> vssamplers; + QRhiBatchedBindings<ID3D11ShaderResourceView *> vsshaderresources; + + QRhiBatchedBindings<ID3D11SamplerState *> fssamplers; + QRhiBatchedBindings<ID3D11ShaderResourceView *> fsshaderresources; + + QRhiBatchedBindings<ID3D11SamplerState *> cssamplers; + QRhiBatchedBindings<ID3D11ShaderResourceView *> csshaderresources; + + QRhiBatchedBindings<ID3D11UnorderedAccessView *> csUAVs; + + 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 QD3D11ComputePipeline : public QRhiComputePipeline +{ + QD3D11ComputePipeline(QRhiImplementation *rhi); + ~QD3D11ComputePipeline(); + void release() override; + bool build() override; + + ID3D11ComputeShader *cs = nullptr; + uint generation = 0; + friend class QRhiD3D11; +}; + +struct QD3D11SwapChain; + +struct QD3D11CommandBuffer : public QRhiCommandBuffer +{ + QD3D11CommandBuffer(QRhiImplementation *rhi); + ~QD3D11CommandBuffer(); + void release() override; + + struct Command { + enum Cmd { + ResetShaderResources, + SetRenderTarget, + Clear, + Viewport, + Scissor, + BindVertexBuffers, + BindIndexBuffer, + BindGraphicsPipeline, + BindShaderResources, + StencilRef, + BlendConstants, + Draw, + DrawIndexed, + UpdateSubRes, + CopySubRes, + ResolveSubRes, + GenMip, + DebugMarkBegin, + DebugMarkEnd, + DebugMarkMsg, + BindComputePipeline, + Dispatch + }; + 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; + struct { + QD3D11ComputePipeline *ps; + } bindComputePipeline; + struct { + UINT x; + UINT y; + UINT z; + } dispatch; + } args; + }; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + QVector<Command> commands; + PassType recordingPass; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + 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(); + recordingPass = NoPass; + currentTarget = nullptr; + resetCachedState(); + } + void resetCachedState() { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = 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; + QRhiComputePipeline *createComputePipeline() 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; + + void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) 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 makeThreadLocalNativeContextCurrent() 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 resetShaderResources(); + void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr); + DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const; + void finishActiveReadbacks(); + 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; + + struct { + int vsHighestActiveVertexBufferBinding = -1; + bool vsHasIndexBufferBound = false; + int vsHighestActiveSrvBinding = -1; + int fsHighestActiveSrvBinding = -1; + int csHighestActiveSrvBinding = -1; + int csHighestActiveUavBinding = -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..7c40a36701 --- /dev/null +++ b/src/gui/rhi/qrhigles2.cpp @@ -0,0 +1,3036 @@ +/**************************************************************************** +** +** 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, ¶ms); + \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; + case QRhi::Compute: + return false; + 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 +} + +void QRhiGles2::makeThreadLocalNativeContextCurrent() +{ + if (inFrame && !ofr.active) + ensureContext(currentSwapChain->surface); + else + ensureContext(); +} + +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); +} + +QRhiComputePipeline *QRhiGles2::createComputePipeline() +{ + return new QGles2ComputePipeline(this); +} + +void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + 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) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + 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) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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(); + cbD->commands.append(cmd); +} + +void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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]; + cbD->commands.append(cmd); +} + +void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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(); + cbD->commands.append(cmd); +} + +void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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_UNUSED(instanceCount); // no instancing + Q_UNUSED(firstInstance); + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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_UNUSED(instanceCount); // no instancing + Q_UNUSED(firstInstance); + Q_UNUSED(vertexOffset); // no glDrawElementsBaseVertex + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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_UNUSED(cb); + flushCommandBuffer(); // also ensures the context is current +} + +void QRhiGles2::endExternal(QRhiCommandBuffer *cb) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->commands.isEmpty()); + cbD->resetCachedState(); + if (cbD->currentTarget) + 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); + + QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); + if (!ensureContext(swapChainD->surface)) + return QRhi::FrameOpError; + + 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) +{ + 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) +{ + if (!ensureContext()) + return QRhi::FrameOpError; + + 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(ofr.active); + ofr.active = false; + + addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame); + + if (!ensureContext()) + return QRhi::FrameOpError; + + executeCommandBuffer(&ofr.cbWrapper); + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiGles2::finish() +{ + 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(¤tSwapChain->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(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass); + + 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) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + 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->recordingPass = QGles2CommandBuffer::RenderPass; + cbD->currentTarget = rt; +} + +void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); + + 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->recordingPass = QGles2CommandBuffer::NoPass; + cbD->currentTarget = nullptr; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + cbD->recordingPass = QGles2CommandBuffer::ComputePass; +} + +void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); + + cbD->recordingPass = QGles2CommandBuffer::NoPass; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + Q_UNUSED(cb); + Q_UNUSED(ps); +} + +void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + Q_UNUSED(cb); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(z); +} + +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 QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { + const bool isVertex = shaderStage.type() == QRhiShaderStage::Vertex; + const bool isFragment = shaderStage.type() == QRhiShaderStage::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; +} + +QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi) +{ +} + +QGles2ComputePipeline::~QGles2ComputePipeline() +{ + release(); +} + +void QGles2ComputePipeline::release() +{ +} + +bool QGles2ComputePipeline::build() +{ + return false; +} + +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..fe74e2e75b --- /dev/null +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -0,0 +1,716 @@ +/**************************************************************************** +** +** 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 QGles2ComputePipeline : public QRhiComputePipeline +{ + QGles2ComputePipeline(QRhiImplementation *rhi); + ~QGles2ComputePipeline(); + void release() override; + bool build() override; +}; + +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; + }; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + QVector<Command> commands; + PassType recordingPass; + 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(); + recordingPass = NoPass; + 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; + QRhiComputePipeline *createComputePipeline() 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; + + void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) 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 makeThreadLocalNativeContextCurrent() 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; + 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..214374e0c6 --- /dev/null +++ b/src/gui/rhi/qrhimetal.mm @@ -0,0 +1,3558 @@ +/**************************************************************************** +** +** 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 "qshaderdescription_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 (to help having 2 frames in flight), + "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS. + 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 but does rely on the automatic resource tracking of the + command encoders. +*/ + +#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, ¶ms); + \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]; + id<MTLTexture> views[QRhi::MAX_LEVELS]; + } 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; + bool slotted; + 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 +{ + QMetalTextureData(QMetalTexture *t) : q(t) { } + + QMetalTexture *q; + MTLPixelFormat format; + id<MTLTexture> tex = nil; + id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT]; + bool owns = true; + id<MTLTexture> perLevelViews[QRhi::MAX_LEVELS]; + + id<MTLTexture> viewForLevel(int level); +}; + +struct QMetalSamplerData +{ + id<MTLSamplerState> samplerState = nil; +}; + +struct QMetalCommandBufferData +{ + id<MTLCommandBuffer> cb; + id<MTLRenderCommandEncoder> currentRenderPassEncoder; + id<MTLComputeCommandEncoder> currentComputePassEncoder; + 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 QMetalComputePipelineData +{ + id<MTLComputePipelineState> ps = nil; + id<MTLLibrary> csLib = nil; + id<MTLFunction> csFunc = nil; + MTLSize localSize; +}; + +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; + case QRhi::Compute: + 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 +} + +void QRhiMetal::makeThreadLocalNativeContextCurrent() +{ + // 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); +} + +QRhiComputePipeline *QRhiMetal::createComputePipeline() +{ + return new QMetalComputePipeline(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 = 3; + 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->d->slotted ? currentFrameSlot : 0]; + 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); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + res[2].buffers.feed(b->binding, mtlbuf); + res[2].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); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + res[2].textures.feed(b->binding, texD->d->tex); + res[2].samplers.feed(b->binding, samplerD->d->samplerState); + } + } + break; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); + id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level); + if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) + res[0].textures.feed(b->binding, t); + if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) + res[1].textures.feed(b->binding, t); + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) + res[2].textures.feed(b->binding, t); + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); + id<MTLBuffer> mtlbuf = bufD->d->buf[0]; + uint offset = b->u.sbuf.offset; + 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); + } + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + res[2].buffers.feed(b->binding, mtlbuf); + res[2].bufferOffsets.feed(b->binding, offset); + } + } + 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->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() + offsets: offsetBatch.resources.constData() + withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())]; + break; + case 1: + [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData() + offsets: offsetBatch.resources.constData() + withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())]; + break; + case 2: + [cbD->d->currentComputePassEncoder setBuffers: 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->currentRenderPassEncoder setVertexTextures: batch.resources.constData() + withRange: NSMakeRange(batch.startBinding, batch.resources.count())]; + break; + case 1: + [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData() + withRange: NSMakeRange(batch.startBinding, batch.resources.count())]; + break; + case 2: + [cbD->d->currentComputePassEncoder setTextures: 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->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() + withRange: NSMakeRange(batch.startBinding, batch.resources.count())]; + break; + case 1: + [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData() + withRange: NSMakeRange(batch.startBinding, batch.resources.count())]; + break; + case 2: + [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData() + withRange: NSMakeRange(batch.startBinding, batch.resources.count())]; + break; + default: + Q_UNREACHABLE(); + break; + } + } + } +} + +void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps); + + if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) { + cbD->currentGraphicsPipeline = ps; + cbD->currentComputePipeline = nullptr; + cbD->currentPipelineGeneration = psD->generation; + + [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps]; + [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds]; + [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode]; + [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding]; + } + + psD->lastActiveFrameSlot = currentFrameSlot; +} + +void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass); + QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline); + QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline); + + if (!srb) { + if (gfxPsD) + srb = gfxPsD->m_shaderResourceBindings; + else + srb = compPsD->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->d->slotted) + 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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); + if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) { + resNeedsRebind = true; + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + } + texD->lastActiveFrameSlot = currentFrameSlot; + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); + Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); + executeBufferHostWritesForCurrentFrame(bufD); + if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { + resNeedsRebind = true; + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->generation; + } + bufD->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 srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); + const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation; + + // dynamic uniform buffer offsets always trigger a rebind + if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) { + if (gfxPsD) { + cbD->currentGraphicsSrb = srb; + cbD->currentComputeSrb = nullptr; + } else { + cbD->currentGraphicsSrb = nullptr; + cbD->currentComputeSrb = srb; + } + cbD->currentSrbGeneration = srbD->generation; + cbD->currentResSlot = resSlot; + + const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt; + 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) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + 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->d->slotted ? currentFrameSlot : 0]; + 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->currentGraphicsSrb; + // 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->currentGraphicsPipeline->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->currentRenderPassEncoder 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) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + 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->currentRenderPassEncoder setViewport: vp]; + + if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) { + MTLScissorRect s; + s.x = x; + s.y = y; + s.width = w; + s.height = h; + [cbD->d->currentRenderPassEncoder setScissorRect: s]; + } +} + +void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->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->currentRenderPassEncoder setScissorRect: s]; +} + +void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF() green: c.greenF() blue: c.blueF() alpha: c.alphaF()]; +} + +void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue]; +} + +void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + [cbD->d->currentRenderPassEncoder drawPrimitives: + QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->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) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + 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->d->slotted ? currentFrameSlot : 0]; + + [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->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 (cbD->recordingPass != QMetalCommandBuffer::NoPass) { + [cbD->d->currentRenderPassEncoder 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 (cbD->recordingPass != QMetalCommandBuffer::NoPass) { + [cbD->d->currentRenderPassEncoder 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; + + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + if (cbD->recordingPass != QMetalCommandBuffer::NoPass) + [cbD->d->currentRenderPassEncoder 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); + + 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) +{ + 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]; + swapChainD->d->curDrawable = nil; + } + + __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) +{ + 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; + + [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() +{ + id<MTLCommandBuffer> cb = nil; + QMetalSwapChain *swapChainD = nullptr; + if (inFrame) { + if (d->ofr.active) { + Q_ASSERT(!currentSwapChain); + Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass); + cb = d->ofr.cbWrapper.d->cb; + } else { + Q_ASSERT(currentSwapChain); + swapChainD = currentSwapChain; + Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass); + 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); + } + + // Due to the Metal API the handling of static and dynamic buffers is + // basically the same. So go through the same pendingUpdates machinery. + 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->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; 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(); +} + +// this handles all types of buffers, not just Dynamic +void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) +{ + const int idx = bufD->d->slotted ? currentFrameSlot : 0; + 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(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass); + + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiMetal::beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + 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); + if (!swapChainD->d->curDrawable) + 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->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc]; + + cbD->resetPerPassState(); + + cbD->recordingPass = QMetalCommandBuffer::RenderPass; + cbD->currentTarget = rt; +} + +void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass); + + [cbD->d->currentRenderPassEncoder endEncoding]; + + cbD->recordingPass = QMetalCommandBuffer::NoPass; + cbD->currentTarget = nullptr; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); + + cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder]; + cbD->resetPerPassState(); + cbD->recordingPass = QMetalCommandBuffer::ComputePass; +} + +void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass); + + [cbD->d->currentComputePassEncoder endEncoding]; + cbD->recordingPass = QMetalCommandBuffer::NoPass; + + if (resourceUpdates) + enqueueResourceUpdates(cb, resourceUpdates); +} + +void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass); + QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps); + + if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) { + cbD->currentGraphicsPipeline = nullptr; + cbD->currentComputePipeline = ps; + cbD->currentPipelineGeneration = psD->generation; + + [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps]; + } + + psD->lastActiveFrameSlot = currentFrameSlot; +} + +void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass); + QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline); + + [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(x, y, z) + threadsPerThreadgroup: psD->d->localSize]; +} + +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]; + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) + [e.texture.views[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(); + + if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) { + qWarning("StorageBuffer cannot be combined with Dynamic"); + return false; + } + + 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 + + // Immutable and Static only has buf[0] and pendingUpdates[0] in use. + // Dynamic uses all. + d->slotted = m_type == Dynamic; + + QRHI_RES_RHI(QRhiMetal); + for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { + if (i == 0 || d->slotted) { + d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts]; + d->pendingUpdates[i].reserve(16); + if (!m_objectName.isEmpty()) { + if (!d->slotted) { + 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, d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1, 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(this)) +{ + for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) + d->stagingBuf[i] = nil; + + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) + d->perLevelViews[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; + } + + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + e.texture.views[i] = d->perLevelViews[i]; + d->perLevelViews[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; + if (m_flags.testFlag(UsedWithLoadStore)) + desc.usage |= MTLTextureUsageShaderWrite; + + 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; +} + +id<MTLTexture> QMetalTextureData::viewForLevel(int level) +{ + Q_ASSERT(level >= 0 && level < int(q->mipLevelCount)); + if (perLevelViews[level]) + return perLevelViews[level]; + + const MTLTextureType type = [tex textureType]; + const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap); + id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type + levels: NSMakeRange(level, 1) slices: NSMakeRange(0, isCube ? 6 : 1)]; + + perLevelViews[level] = view; + return view; +} + +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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->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; + + // mutability cannot be determined (slotted buffers could be set as + // MTLMutabilityImmutable, but then we potentially need a different + // descriptor for each buffer combination as this depends on the actual + // buffers not just the resource binding layout) so leave it at the default + + for (const QRhiShaderStage &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 QRhiShaderStage::Vertex: + rpDesc.vertexFunction = func; + d->vsLib = lib; + d->vsFunc = func; + break; + case QRhiShaderStage::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; +} + +QMetalComputePipeline::QMetalComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi), + d(new QMetalComputePipelineData) +{ +} + +QMetalComputePipeline::~QMetalComputePipeline() +{ + release(); + delete d; +} + +void QMetalComputePipeline::release() +{ + QRHI_RES_RHI(QRhiMetal); + + if (d->csFunc) { + [d->csFunc release]; + d->csFunc = nil; + } + if (d->csLib) { + [d->csLib release]; + d->csLib = nil; + } + + if (!d->ps) + return; + + if (d->ps) { + [d->ps release]; + d->ps = nil; + } + + rhiD->unregisterResource(this); +} + +bool QMetalComputePipeline::build() +{ + if (d->ps) + release(); + + QRHI_RES_RHI(QRhiMetal); + + const QShader shader = m_shaderStage.shader(); + QString error; + QByteArray entryPoint; + id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_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; + } + d->csLib = lib; + d->csFunc = func; + std::array<uint, 3> localSize = shader.description().computeShaderLocalSize(); + d->localSize = MTLSizeMake(localSize[0], localSize[1], localSize[2]); + + NSError *err = nil; + d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->csFunc error: &err]; + if (!d->ps) { + const QString msg = QString::fromNSString(err.localizedDescription); + qWarning("Failed to create render pipeline state: %s", qPrintable(msg)); + return false; + } + + 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->currentRenderPassEncoder; + return &nativeHandlesStruct; +} + +void QMetalCommandBuffer::resetState() +{ + d->currentRenderPassEncoder = nil; + d->currentComputePassEncoder = nil; + d->currentPassRpDesc = nil; + resetPerPassState(); +} + +void QMetalCommandBuffer::resetPerPassState() +{ + recordingPass = NoPass; + currentTarget = nullptr; + resetPerPassCachedState(); +} + +void QMetalCommandBuffer::resetPerPassCachedState() +{ + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = 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]; + + d->curDrawable = nil; + + 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..8b0256991d --- /dev/null +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -0,0 +1,450 @@ +/**************************************************************************** +** +** 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; + friend struct QMetalTextureData; +}; + +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 BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + 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 QMetalComputePipelineData; + +struct QMetalComputePipeline : public QRhiComputePipeline +{ + QMetalComputePipeline(QRhiImplementation *rhi); + ~QMetalComputePipeline(); + void release() override; + bool build() override; + + QMetalComputePipelineData *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; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + PassType recordingPass; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + 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; + QRhiComputePipeline *createComputePipeline() 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; + + void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) 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 makeThreadLocalNativeContextCurrent() 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; + 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..1314e53893 --- /dev/null +++ b/src/gui/rhi/qrhinull.cpp @@ -0,0 +1,764 @@ +/**************************************************************************** +** +** 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, ¶ms); + \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 +} + +void QRhiNull::makeThreadLocalNativeContextCurrent() +{ + // 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); +} + +QRhiComputePipeline *QRhiNull::createComputePipeline() +{ + return new QNullComputePipeline(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); +} + +void QRhiNull::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + Q_UNUSED(cb); + Q_UNUSED(ps); +} + +void QRhiNull::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + Q_UNUSED(cb); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(z); +} + +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); +} + +void QRhiNull::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + if (resourceUpdates) + resourceUpdate(cb, resourceUpdates); +} + +void QRhiNull::endComputePass(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; +} + +QNullComputePipeline::QNullComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi) +{ +} + +QNullComputePipeline::~QNullComputePipeline() +{ + release(); +} + +void QNullComputePipeline::release() +{ +} + +bool QNullComputePipeline::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..b0227bc110 --- /dev/null +++ b/src/gui/rhi/qrhinull_p_p.h @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** 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 QNullComputePipeline : public QRhiComputePipeline +{ + QNullComputePipeline(QRhiImplementation *rhi); + ~QNullComputePipeline(); + 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; + QRhiComputePipeline *createComputePipeline() 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; + + void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) 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 makeThreadLocalNativeContextCurrent() 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..f6ecd7c00e --- /dev/null +++ b/src/gui/rhi/qrhivulkan.cpp @@ -0,0 +1,6063 @@ +/**************************************************************************** +** +** 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, ¶ms); + \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; + + // We only support combined graphics+present queues. When it comes to + // compute, only combined graphics+compute queue is used, compute gets + // disabled otherwise. + gfxQueueFamilyIdx = -1; + int computelessGfxQueueCandidateIdx = -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))) + { + if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) + gfxQueueFamilyIdx = i; + else if (computelessGfxQueueCandidateIdx == -1) + computelessGfxQueueCandidateIdx = i; + } + } + if (gfxQueueFamilyIdx == -1) { + if (computelessGfxQueueCandidateIdx != -1) { + gfxQueueFamilyIdx = computelessGfxQueueCandidateIdx; + } else { + qWarning("No graphics (or no graphics+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; + + 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 = 1; + 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) { + if (!gfxQueue) + df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue); + + if (queueFamilyProps.isEmpty()) + queryQueueFamilyProps(); + + hasCompute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; + 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(×tampQueryPoolInfo, 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, ×tampQueryPoolInfo, nullptr, ×tampQueryPool); + 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 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, QVK_STORAGE_BUFFERS_PER_POOL }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, QVK_STORAGE_IMAGES_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; + } + + image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; + } + + 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) +{ + 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.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { + VkImageMemoryBarrier presTrans; + memset(&presTrans, 0, sizeof(presTrans)); + presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + 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; + + if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseNone) { + // was not used at all (no render pass), just transition from undefined to presentable + presTrans.srcAccessMask = 0; + presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + df->vkCmdPipelineBarrier(frame.cmdBuf, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, nullptr, 0, nullptr, + 1, &presTrans); + } else if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseTransferSource) { + // was used in a readback as transfer source, go back to presentable layout + presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + df->vkCmdPipelineBarrier(frame.cmdBuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, nullptr, 0, nullptr, + 1, &presTrans); + } + image.lastUse = QVkSwapChain::ImageResources::ScImageUseRender; + } + + // 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) +{ + // 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(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() +{ + 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); + + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); + + 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; + Q_ASSERT(currentSwapChain); + currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse = + QVkSwapChain::ImageResources::ScImageUseRender; + break; + case QRhiResource::TextureRenderTarget: + { + QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt); + rtD = &rtTex->d; + activateTextureRenderTarget(cbD, rtTex); + } + break; + default: + Q_UNREACHABLE(); + break; + } + + cbD->recordingPass = QVkCommandBuffer::RenderPass; + 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); +} + +void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::EndRenderPass; + cbD->commands.append(cmd); + + cbD->recordingPass = QVkCommandBuffer::NoPass; + cbD->currentTarget = nullptr; + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); + + enqueueTransitionPassResources(cbD); + + cbD->recordingPass = QVkCommandBuffer::ComputePass; +} + +void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); + + cbD->recordingPass = QVkCommandBuffer::NoPass; + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + QVkComputePipeline *psD = QRHI_RES(QVkComputePipeline, ps); + Q_ASSERT(psD->pipeline); + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); + + if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) { + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::BindPipeline; + cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; + cmd.args.bindPipeline.pipeline = psD->pipeline; + cbD->commands.append(cmd); + + cbD->currentGraphicsPipeline = nullptr; + cbD->currentComputePipeline = ps; + cbD->currentPipelineGeneration = psD->generation; + } + + psD->lastActiveFrameSlot = currentFrameSlot; +} + +void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); + + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::Dispatch; + cmd.args.dispatch.x = x; + cmd.args.dispatch.y = y; + cmd.args.dispatch.z = z; + cbD->commands.append(cmd); +} + +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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); + VkImageView view = texD->imageViewForLevel(b->u.simage.level); + if (view) { + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + VkDescriptorImageInfo imageInfo; + imageInfo.sampler = VK_NULL_HANDLE; + imageInfo.imageView = view; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfos.append(imageInfo); + writeInfo.pImageInfo = &imageInfos.last(); + } + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bd.sbuf.id = bufD->m_id; + bd.sbuf.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; + bufferInfos.append(bufInfo); + writeInfo.pBufferInfo = &bufferInfos.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(cbD->recordingPass == QVkCommandBuffer::NoPass); + 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(cbD->recordingPass == QVkCommandBuffer::NoPass); + 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(cbD->recordingPass == QVkCommandBuffer::NoPass); + 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(©Info, 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(©Info, 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, ©Infos); + } + } + } + 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(®ion, 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(©Desc, 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 + QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]); + VkImage image = imageRes.image; + if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseTransferSource) { + if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { + qWarning("Attempted to read back undefined swapchain image content, " + "results are undefined. (do a render pass first)"); + } + 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); + imageRes.lastUse = QVkSwapChain::ImageResources::ScImageUseTransferSource; + } + + 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(®ion, 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])); + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + if (e.texture.extraImageViews[i]) + df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr); + } +} + +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) +{ + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); + + 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; + case QVkCommandBuffer::Command::Dispatch: + df->vkCmdDispatch(cbD->cb, cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z); + 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; + case QRhiPassResourceTracker::BufStorageLoad: + return VK_ACCESS_SHADER_READ_BIT; + case QRhiPassResourceTracker::BufStorageStore: + return VK_ACCESS_SHADER_WRITE_BIT; + case QRhiPassResourceTracker::BufStorageLoadStore: + return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_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; + case QRhiPassResourceTracker::BufComputeStage: + return VK_PIPELINE_STAGE_COMPUTE_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; + case QRhiPassResourceTracker::TexStorageLoad: + Q_FALLTHROUGH(); + case QRhiPassResourceTracker::TexStorageStore: + Q_FALLTHROUGH(); + case QRhiPassResourceTracker::TexStorageLoadStore: + return VK_IMAGE_LAYOUT_GENERAL; + 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; + case QRhiPassResourceTracker::TexStorageLoad: + return VK_ACCESS_SHADER_READ_BIT; + case QRhiPassResourceTracker::TexStorageStore: + return VK_ACCESS_SHADER_WRITE_BIT; + case QRhiPassResourceTracker::TexStorageLoadStore: + return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_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; + case QRhiPassResourceTracker::TexComputeStage: + return VK_PIPELINE_STAGE_COMPUTE_SHADER_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]); + passResTracker->registerBuffer(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); + passResTracker->registerTexture(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; + case QRhi::Compute: + return hasCompute; + 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)); +} + +void QRhiVulkan::makeThreadLocalNativeContextCurrent() +{ + // nothing to do here +} + +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); +} + +QRhiComputePipeline *QRhiVulkan::createComputePipeline() +{ + return new QVkComputePipeline(this); +} + +QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings() +{ + return new QVkShaderResourceBindings(this); +} + +void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) +{ + QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps); + Q_ASSERT(psD->pipeline); + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + if (cbD->currentGraphicsPipeline != 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->currentGraphicsPipeline = ps; + cbD->currentComputePipeline = nullptr; + 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; + if (stages.testFlag(QRhiShaderResourceBinding::ComputeStage)) + return QRhiPassResourceTracker::BufComputeStage; + + 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; + if (stages.testFlag(QRhiShaderResourceBinding::ComputeStage)) + return QRhiPassResourceTracker::TexComputeStage; + + Q_UNREACHABLE(); + return QRhiPassResourceTracker::TexVertexStage; +} + +void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass != QVkCommandBuffer::NoPass); + QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline); + QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline); + + if (!srb) { + if (gfxPsD) + srb = gfxPsD->m_shaderResourceBindings; + else + srb = compPsD->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, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, + 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; + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + { + QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); + Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore)); + texD->lastActiveFrameSlot = currentFrameSlot; + QRhiPassResourceTracker::TextureAccess access; + if (b->type == QRhiShaderResourceBinding::ImageLoad) + access = QRhiPassResourceTracker::TexStorageLoad; + else if (b->type == QRhiShaderResourceBinding::ImageStore) + access = QRhiPassResourceTracker::TexStorageStore; + else + access = QRhiPassResourceTracker::TexStorageLoadStore; + trackedRegisterTexture(&passResTracker, texD, + access, + toPassTrackerTextureStage(b->stage)); + + if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) { + rewriteDescSet = true; + bd.simage.id = texD->m_id; + bd.simage.generation = texD->generation; + } + } + break; + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); + Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); + + if (bufD->m_type == QRhiBuffer::Dynamic) + executeBufferHostWritesForCurrentFrame(bufD); + + bufD->lastActiveFrameSlot = currentFrameSlot; + QRhiPassResourceTracker::BufferAccess access; + if (b->type == QRhiShaderResourceBinding::BufferLoad) + access = QRhiPassResourceTracker::BufStorageLoad; + else if (b->type == QRhiShaderResourceBinding::BufferStore) + access = QRhiPassResourceTracker::BufStorageStore; + else + access = QRhiPassResourceTracker::BufStorageLoadStore; + trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, + access, + toPassTrackerBufferStage(b->stage)); + + if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { + rewriteDescSet = true; + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->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; + + const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); + + if (forceRebind || rewriteDescSet || srbChanged || 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 = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS + : VK_PIPELINE_BIND_POINT_COMPUTE; + cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->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); + + if (gfxPsD) { + cbD->currentGraphicsSrb = srb; + cbD->currentComputeSrb = nullptr; + } else { + cbD->currentGraphicsSrb = nullptr; + cbD->currentComputeSrb = 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + 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->currentGraphicsPipeline)->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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + 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) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + + 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; + if (usage.testFlag(QRhiBuffer::StorageBuffer)) + u |= VK_BUFFER_USAGE_STORAGE_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(QRhiShaderStage::Type type) +{ + switch (type) { + case QRhiShaderStage::Vertex: + return VK_SHADER_STAGE_VERTEX_BIT; + case QRhiShaderStage::Fragment: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case QRhiShaderStage::Compute: + return VK_SHADER_STAGE_COMPUTE_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; + + case QRhiShaderResourceBinding::ImageLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::ImageLoadStore: + return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + + case QRhiShaderResourceBinding::BufferLoad: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferStore: + Q_FALLTHROUGH(); + case QRhiShaderResourceBinding::BufferLoadStore: + return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + 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; + if (stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) + s |= VK_SHADER_STAGE_COMPUTE_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(); + + if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) { + qWarning("StorageBuffer cannot be combined with Dynamic"); + return false; + } + + 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; + } + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) + perLevelImageViews[i] = VK_NULL_HANDLE; +} + +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; + } + + for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + e.texture.extraImageViews[i] = perLevelImageViews[i]; + perLevelImageViews[i] = VK_NULL_HANDLE; + } + + 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; + if (m_flags.testFlag(QRhiTexture::UsedWithLoadStore)) + imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_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; +} + +VkImageView QVkTexture::imageViewForLevel(int level) +{ + Q_ASSERT(level >= 0 && level < int(mipLevelCount)); + if (perLevelImageViews[level] != VK_NULL_HANDLE) + return perLevelImageViews[level]; + + 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.baseMipLevel = level; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = isCube ? 6 : 1; + + VkImageView v = VK_NULL_HANDLE; + QRHI_RES_RHI(QRhiVulkan); + VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v); + if (err != VK_SUCCESS) { + qWarning("Failed to create image view: %d", err); + return VK_NULL_HANDLE; + } + + perLevelImageViews[level] = v; + return v; +} + +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 QRhiShaderStage &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; +} + +QVkComputePipeline::QVkComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi) +{ +} + +QVkComputePipeline::~QVkComputePipeline() +{ + release(); +} + +void QVkComputePipeline::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 QVkComputePipeline::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; + } + + VkComputePipelineCreateInfo pipelineInfo; + memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipelineInfo.layout = layout; + + if (m_shaderStage.type() != QRhiShaderStage::Compute) { + qWarning("Compute pipeline requires a compute shader stage"); + return false; + } + const QShader bakedShader = m_shaderStage.shader(); + const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, m_shaderStage.shaderVariant() }); + if (spirv.shader().isEmpty()) { + qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader; + return false; + } + if (bakedShader.stage() != QShader::ComputeStage) { + qWarning() << bakedShader << "is not a compute shader"; + return false; + } + VkShaderModule shader = rhiD->createShader(spirv.shader()); + VkPipelineShaderStageCreateInfo shaderInfo; + memset(&shaderInfo, 0, sizeof(shaderInfo)); + shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; + shaderInfo.module = shader; + shaderInfo.pName = spirv.entryPoint().constData(); + pipelineInfo.stage = shaderInfo; + + err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); + 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..cec9016603 --- /dev/null +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -0,0 +1,915 @@ +/**************************************************************************** +** +** 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_STORAGE_BUFFERS_PER_POOL = 128; +static const int QVK_STORAGE_IMAGES_PER_POOL = 128; + +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(); + VkImageView imageViewForLevel(int level); + + 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]; + VkImageView perLevelImageViews[QRhi::MAX_LEVELS]; + 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 BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + 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 QVkComputePipeline : public QRhiComputePipeline +{ + QVkComputePipeline(QRhiImplementation *rhi); + ~QVkComputePipeline(); + 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; + } + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + void resetState() { + resetCommands(); + recordingPass = NoPass; + currentTarget = nullptr; + resetCachedState(); + } + + void resetCachedState() { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = 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)); + } + + PassType recordingPass; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + 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, + Dispatch + }; + 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; + struct { + int x, y, z; + } dispatch; + } 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; + enum LastUse { + ScImageUseNone, + ScImageUseRender, + ScImageUseTransferSource + }; + LastUse lastUse = ScImageUseNone; + } 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; + QRhiComputePipeline *createComputePipeline() 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; + + void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) 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 makeThreadLocalNativeContextCurrent() 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; + bool hasCompute = false; + 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; + + 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]; + VkImageView extraImageViews[QRhi::MAX_LEVELS]; + } 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..4676ec3f5b --- /dev/null +++ b/src/gui/rhi/qshader.cpp @@ -0,0 +1,585 @@ +/**************************************************************************** +** +** 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 + + \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..243842a95a --- /dev/null +++ b/src/gui/rhi/qshader_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** 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_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_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_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..77aceaddba --- /dev/null +++ b/src/gui/rhi/qshaderdescription.cpp @@ -0,0 +1,1116 @@ +/**************************************************************************** +** +** 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 + + \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; +} + +/*! + Returns the local size of a compute shader. + + For example, for a compute shader with the following declaration the + function returns { 256, 16, 1}. + + \badcode + layout(local_size_x = 256, local_size_y = 16, local_size_z = 1) in; + \endcode + */ +std::array<uint, 3> QShaderDescription::computeShaderLocalSize() const +{ + return d->localSize; +} + +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 const QString localSizeKey = QLatin1String("localSize"); + +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; + + QJsonArray jlocalSize; + for (int i = 0; i < 3; ++i) + jlocalSize.append(QJsonValue(int(localSize[i]))); + root[localSizeKey] = jlocalSize; + + 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())); + } + + if (root.contains(localSizeKey)) { + QJsonArray localSizeArr = root[localSizeKey].toArray(); + if (localSizeArr.count() == 3) { + for (int i = 0; i < 3; ++i) + localSize[i] = localSizeArr[i].toInt(); + } + } +} + +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..5a63b998cd --- /dev/null +++ b/src/gui/rhi/qshaderdescription_p.h @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** 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> +#include <array> + +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; + + std::array<uint, 3> computeShaderLocalSize() 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..1caee24984 --- /dev/null +++ b/src/gui/rhi/qshaderdescription_p_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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) + { + localSize[0] = localSize[1] = localSize[2] = 0; + } + + 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), + localSize(other->localSize) + { + } + + 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; + std::array<uint, 3> localSize; +}; + +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) |