diff --git a/src/gui/doc/src/qtgui-overview.qdoc b/src/gui/doc/src/qtgui-overview.qdoc index 446479c9be1..cdd41d68c89 100644 --- a/src/gui/doc/src/qtgui-overview.qdoc +++ b/src/gui/doc/src/qtgui-overview.qdoc @@ -79,6 +79,7 @@ \li QRhiRenderBuffer \li QRhiTexture \li QRhiSampler + \li QRhiShadingRateMap \li QRhiTextureRenderTarget \li QRhiShaderResourceBindings \li QRhiGraphicsPipeline diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 32f65431703..1f5be494525 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -1036,6 +1036,38 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") texture becomes necessary, for example when rendering into an OpenXR-provided depth texture (XR_KHR_composition_layer_depth). This enum value has been introduced in Qt 6.8. + + \value VariableRateShading Indicates that per-draw (per-pipeline) variable + rate shading is supported. When reported as supported, \l + QRhiCommandBuffer::setShadingRate() is functional and has an effect for + QRhiGraphicsPipeline objects that declared \l + QRhiGraphicsPipeline::UsesShadingRate in their flags. Call \l + QRhi::supportedShadingRates() to check which rates are supported. (1x1 is + always supported, other typical values are 2x2, 1x2, 2x1, 2x4, 4x2, 4x4). + This feature can be expected to be supported with Direct 3D 12 and Vulkan, + assuming the implementation and GPU used at run time supports VRS. This enum + value has been introduced in Qt 6.9. + + \value VariableRateShadingMap Indicates that image-based specification of + the shading rate is possible. The "image" is not necessarily a texture, it + may be a native 3D API object, depending on the underlying backend and + graphics API at run time. In practice this feature can be expected to be + supported with Direct 3D 12, Vulkan, and Metal, assuming the GPU is modern + enough to support VRS. To check if D3D12/Vulkan-style image-based VRS is + suspported, use VariableRateShadingMapWithTexture instead. When this feature + is reported as supported, there are two possibilities: when + VariableRateShadingMapWithTexture is also true, then QRhiShadingRateMap + consumes QRhiTexture objects via the createFrom() overload taking a + QRhiTexture argument. When VariableRateShadingMapWithTexture is false, then + QRhiShadingRateMap consumes some other type of native objects, for example + an MTLRasterizationRateMap in case of Metal. Use the createFrom() overload + taking a NativeShadingRateMap in this case. This enum value has been + introduced in Qt 6.9. + + \value VariableRateShadingMapWithTexture Indicates that image-based + specification of the shading rate is supported via regular textures. In + practice this may be supported with Direct 3D 12 and Vulkan. This enum value + has been introduced in Qt 6.9. */ /*! @@ -1143,6 +1175,12 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \c out variables) from the vertex shader. The value may be as low as 8 with OpenGL ES 2.0, and 15 with OpenGL ES 3.0 and some Metal devices. Elsewhere, a typical value is 32. + + \value ShadingRateImageTileSize The tile size for shading rate textures. 0 + if the QRhi::VariableRateShadingMapWithTexture feature is not supported. + Otherwise a value such as 16, indicating, for example, a tile size of 16x16. + Each byte in the (R8UI) shading rate texture defines then the shading rate + for a tile of 16x16 pixels. See \l QRhiShadingRateMap for details. */ /*! @@ -2739,6 +2777,37 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh \sa QRhiColorAttachment::setResolveTexture(), setDepthTexture() */ +/*! + \fn QRhiShadingRateMap *QRhiTextureRenderTargetDescription::shadingRateMap() const + \return the currently set QRhiShadingRateMap. By default this is \nullptr. + \since 6.9 + */ + +/*! + \fn void QRhiTextureRenderTargetDescription::setShadingRateMap(QRhiShadingRateMap *map) + + Associates with the specified QRhiShadingRateMap \a map. This is functional + only when the \l QRhi::VariableRateShadingMap feature is reported as + supported. + + When QRhiCommandBuffer::setShadingRate() is also called, the higher of two + the shading rates are used for each tile. There is currently no control + offered over the combiner behavior. + + \note When the render target had already been built (create() was called + successfully), setting a shading rate map implies that a different, new + QRhiRenderPassDescriptor is needed and thus a rebuild is needed. Call + setRenderPassDescriptor() again (outside of a render pass) and then rebuild + by calling create(). This has other rolling consequences as well, for + example for graphics pipelines: those also need to be associated with the + new QRhiRenderPassDescriptor and then rebuilt. See \l + QRhiRenderPassDescriptor::serializedFormat() for some suggestions on how to + deal with this. Remember to set the QRhiGraphicsPipeline::UsesShadingRate + flag as well. + + \since 6.9 + */ + /*! \class QRhiTextureSubresourceUploadDescription \inmodule QtGui @@ -3450,6 +3519,7 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture) \value SwapChain \value ComputePipeline \value CommandBuffer + \value ShadingRateMap */ /*! @@ -4324,6 +4394,8 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) mipmap-based filtering may be unsupported. This is indicated by the QRhi::OneDimensionalTextures and QRhi::OneDimensionalTextureMipmaps feature flags. + + \value UsedAsShadingRateMap */ /*! @@ -4403,6 +4475,8 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) \value ASTC_10x10 \value ASTC_12x10 \value ASTC_12x12 + + \value R8UI One component, unsigned 8 bit. */ /*! @@ -4853,6 +4927,114 @@ QRhiResource::Type QRhiSampler::resourceType() const Sets the texture comparison function \a op. */ +/*! + \class QRhiShadingRateMap + \inmodule QtGui + \since 6.9 + \brief An object that wraps a texture or another kind of native 3D API object. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + For an introduction to Variable Rate Shading (VRS), see + \l{https://learn.microsoft.com/en-us/windows/win32/direct3d12/vrs}. Qt + supports a subset of the VRS features offered by Direct 3D 12 and Vulkan. In + addition, Metal's somewhat different mechanism is supported by making it + possible to set up a QRhiShadingRateMap with an existing + MTLRasterizationRateMap object. + */ + +/*! + \struct QRhiShadingRateMap::NativeShadingRateMap + \inmodule QtGui + \since 6.9 + \brief Wraps a native shading rate map. + + An example is MTLRasterizationRateMap with Metal. Other 3D APIs that use + textures for image-based VRS do not use this struct since those can function + via the QRhiTexture-based overload of QRhiShadingRate::createFrom(). + */ + +/*! + \variable QRhiShadingRateMap::NativeShadingRateMap::object + \brief 64-bit integer containing the native object handle. + + Used with QRhiShadingRateMap::createFrom(). For example, with Metal, + \c object is expected to be an id. + */ + +/*! + \internal + */ +QRhiShadingRateMap::QRhiShadingRateMap(QRhiImplementation *rhi) + : QRhiResource(rhi) +{ +} + +/*! + \return the resource type. + */ +QRhiResource::Type QRhiShadingRateMap::resourceType() const +{ + return ShadingRateMap; +} + +/*! + Sets up the shading rate map to use a native 3D API shading rate object + \a src. + + \return \c true when successful, \c false when not supported. + + \note This is functional only when the QRhi::VariableRateShadingMap feature + is reported as supported, while QRhi::VariableShadingRateMapWithTexture + feature is not. Currently this is true for Metal, assuming variable rate + shading is supported by the GPU. + + \note With Metal, the \c object field of \a src is expected to contain an + id. Note that Qt does not perform anything else + apart from passing the MTLRasterizationRateMap on to the + MTLRenderPassDescriptor. If any special scaling is required, it is up to the + application (or the XR compositor) to perform that. + */ +bool QRhiShadingRateMap::createFrom(NativeShadingRateMap src) +{ + Q_UNUSED(src); + return false; +} + +/*! + Sets up the shading rate map to use the texture \a src as the + image containing the per-tile shading rates. + + \return \c true when successful, \c false when not supported. + + The QRhiShadingRateMap does not take ownership of \a src. + + \note This is functional only when the + QRhi::VariableRateShadingMapWithTexture feature is reported as supported. In + practice may be supported on Vulkan and Direct 3D 12 when using modern + graphics cards. It will never be supported on OpenGL or Metal, for example. + + \note \a src must have a format of QRhiTexture::R8UI. + + \note \a src must have a width of \c{ceil(render_target_pixel_width / + (float)tile_width)} and a height of \c{ceil(render_target_pixel_height / + (float)tile_height)}. It is up to the application to ensure the size of the + texture is as expected, using the above formula, at all times. The tile size + can be queried via \l QRhi::resourceLimit() and + QRhi::ShadingRateImageTileSize. + + Each byte (texel) in the texture corresponds to the shading rate value for + one tile. 0 indicates 1x1, while a value of 10 indicates 4x4. See + \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_shading_rate}{D3D12_SHADING_RATE} + for other possible values. + */ +bool QRhiShadingRateMap::createFrom(QRhiTexture *src) +{ + Q_UNUSED(src); + return false; +} + /*! \class QRhiRenderPassDescriptor \inmodule QtGui @@ -4954,6 +5136,34 @@ QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const meant for storing on disk, reusing between processes, or using with multiple QRhi instances with potentially different backends. + \note Calling this function is expected to be a cheap operation since the + backends are not supposed to calculate the data in this function, but rather + return an already calculated series of data. + + When creating reusable components as part of a library, where graphics + pipelines are created and maintained while targeting a QRhiRenderTarget (be + it a swapchain or a texture) managed by the client of the library, the + components must be able to deal with a changing QRhiRenderPassDescriptor. + For example, because the render target changes and so invalidates the + previously QRhiRenderPassDescriptor (with regards to the new render target + at least) due to having a potentially different color format and attachments + now. Or because \l{QRhiShadingRateMap}{variable rate shading} is taken into + use dynamically. A simple pattern that helps dealing with this is performing + the following check on every frame, to recognize the case when the pipeline + needs to be associated with a new QRhiRenderPassDescriptor, because + something is different about the render target now, compared to earlier + frames: + + \code + QRhiRenderPassDescriptor *rp = m_renderTarget->renderPassDescriptor(); + if (m_pipeline && rp->serializedFormat() != m_renderPassFormat) { + m_pipeline->setRenderPassDescriptor(rp); + m_renderPassFormat = rp->serializedFormat(); + m_pipeline->create(); + } + // remember to store m_renderPassFormat also when creating m_pipeline the first time + \endcode + \sa isCompatible() */ @@ -6507,6 +6717,11 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) into account. Debug information is relevant in particular with tools like RenderDoc since it allows seeing the original source code when investigating the pipeline and when performing vertex or fragment shader debugging. + + \value UsesShadingRate Indicates that a per-draw (per-pipeline) shading rate + value will be set via QRhiCommandBuffer::setShadingRate(). Not specifying + this flag and still calling setShadingRate() may lead to varying, unexpected + results depending on the underlying graphics API. */ /*! @@ -7608,6 +7823,37 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar \sa createOrResize() */ +/*! + \fn QRhiShadingRateMap *QRhiSwapChain::shadingRateMap() const + \return the currently set QRhiShadingRateMap. By default this is \nullptr. + \since 6.9 + */ + +/*! + \fn void QRhiSwapChain::setShadingRateMap(QRhiShadingRateMap *map) + + Associates with the specified QRhiShadingRateMap \a map. This is functional + only when the \l QRhi::VariableRateShadingMap feature is reported as + supported. + + When QRhiCommandBuffer::setShadingRate() is also called, the higher of two + the shading rates are used for each tile. There is currently no control + offered over the combiner behavior. + + \note Setting a shading rate map implies that a different, new + QRhiRenderPassDescriptor is needed and some of the native swapchain objects + must be rebuilt. Therefore, if the swapchain is already set up, call + newCompatibleRenderPassDescriptor() and setRenderPassDescriptor() right + after setShadingRateMap(). Then, createOrResize() must also be called again. + This has rolling consequences, for example for graphics pipelines: those + also need to be associated with the new QRhiRenderPassDescriptor and then + rebuilt. See \l QRhiRenderPassDescriptor::serializedFormat() for some + suggestions on how to deal with this. Remember to set the + QRhiGraphicsPipeline::UsesShadingRate flag for them as well. + + \since 6.9 + */ + /*! \struct QRhiSwapChainHdrInfo \inmodule QtGui @@ -8032,6 +8278,8 @@ static const char *resourceTypeStr(const QRhiResource *res) return "ComputePipeline"; case QRhiResource::CommandBuffer: return "CommandBuffer"; + case QRhiResource::ShadingRateMap: + return "ShadingRateMap"; } Q_UNREACHABLE_RETURN(""); @@ -8260,6 +8508,10 @@ void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSi bpc = 8; break; + case QRhiTexture::R8UI: + bpc = 1; + break; + default: Q_UNREACHABLE(); break; @@ -9692,6 +9944,29 @@ void QRhiCommandBuffer::setStencilRef(quint32 refValue) m_rhi->setStencilRef(this, refValue); } +/*! + Sets the shading rate for the following draw calls to \a coarsePixelSize. + + The default is 1x1. + + Functional only when the \l QRhi::VariableRateShading feature is reported as + supported and the QRhiGraphicsPipeline(s) bound on the command buffer were + declaring \l QRhiGraphicsPipeline::UsesShadingRate when creating them. + + Call \l QRhi::supportedShadingRates() to check what shading rates are + supported for a given sample count. + + When both a QRhiShadingRateMap and this function is in use, the higher of + two the shading rates are used for each tile. There is currently no control + offered over the combiner behavior. + + \since 6.9 + */ +void QRhiCommandBuffer::setShadingRate(const QSize &coarsePixelSize) +{ + m_rhi->setShadingRate(this, coarsePixelSize); +} + /*! Records a non-indexed draw. @@ -10694,6 +10969,16 @@ QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, return d->createSampler(magFilter, minFilter, mipmapMode, addressU, addressV, addressW); } +/*! + \return a new shading rate map object. + + \since 6.9 + */ +QRhiShadingRateMap *QRhi::newShadingRateMap() +{ + return d->createShadingRateMap(); +} + /*! \return a new texture render target with color and depth/stencil attachments given in \a desc, and with the specified \a flags. @@ -10992,6 +11277,18 @@ int QRhi::ubufAlignment() const return d->ubufAlignment(); } +/*! + \return The list of supported variable shading rates for the specified \a sampleCount. + + 1x1 is always supported. + + \since 6.9 + */ +QList QRhi::supportedShadingRates(int sampleCount) const +{ + return d->supportedShadingRates(sampleCount); +} + Q_CONSTINIT static QBasicAtomicInteger counter = Q_BASIC_ATOMIC_INITIALIZER(0); QRhiGlobalObjectIdGenerator::Type QRhiGlobalObjectIdGenerator::newId() diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h index f491cd3507d..6b0fe55c6f1 100644 --- a/src/gui/rhi/qrhi.h +++ b/src/gui/rhi/qrhi.h @@ -39,6 +39,7 @@ class QRhiCommandBuffer; class QRhiResourceUpdateBatch; class QRhiResourceUpdateBatchPrivate; class QRhiSwapChain; +class QRhiShadingRateMap; class Q_GUI_EXPORT QRhiDepthStencilClearValue { @@ -645,11 +646,15 @@ public: QRhiTexture *depthResolveTexture() const { return m_depthResolveTexture; } void setDepthResolveTexture(QRhiTexture *tex) { m_depthResolveTexture = tex; } + QRhiShadingRateMap *shadingRateMap() const { return m_shadingRateMap; } + void setShadingRateMap(QRhiShadingRateMap *map) { m_shadingRateMap = map; } + private: QVarLengthArray m_colorAttachments; QRhiRenderBuffer *m_depthStencilBuffer = nullptr; QRhiTexture *m_depthTexture = nullptr; QRhiTexture *m_depthResolveTexture = nullptr; + QRhiShadingRateMap *m_shadingRateMap = nullptr; }; class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription @@ -815,7 +820,8 @@ public: GraphicsPipeline, SwapChain, ComputePipeline, - CommandBuffer + CommandBuffer, + ShadingRateMap }; virtual ~QRhiResource(); @@ -908,7 +914,8 @@ public: ThreeDimensional = 1 << 10, TextureRectangleGL = 1 << 11, TextureArray = 1 << 12, - OneDimensional = 1 << 13 + OneDimensional = 1 << 13, + UsedAsShadingRateMap = 1 << 14 }; Q_DECLARE_FLAGS(Flags, Flag) @@ -930,6 +937,8 @@ public: RGB10A2, + R8UI, + D16, D24, D24S8, @@ -1140,6 +1149,22 @@ protected: Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiRenderBuffer::Flags) +class Q_GUI_EXPORT QRhiShadingRateMap : public QRhiResource +{ +public: + struct NativeShadingRateMap { + quint64 object; + }; + + QRhiResource::Type resourceType() const override; + + virtual bool createFrom(NativeShadingRateMap src); + virtual bool createFrom(QRhiTexture *src); + +protected: + QRhiShadingRateMap(QRhiImplementation *rhi); +}; + class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource { public: @@ -1275,7 +1300,8 @@ public: UsesBlendConstants = 1 << 0, UsesStencilRef = 1 << 1, UsesScissor = 1 << 2, - CompileShadersWithDebugInfo = 1 << 3 + CompileShadersWithDebugInfo = 1 << 3, + UsesShadingRate = 1 << 4 }; Q_DECLARE_FLAGS(Flags, Flag) @@ -1595,6 +1621,9 @@ public: QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } + QRhiShadingRateMap *shadingRateMap() const { return m_shadingRateMap; } + void setShadingRateMap(QRhiShadingRateMap *map) { m_shadingRateMap = map; } + QSize currentPixelSize() const { return m_currentPixelSize; } virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0; @@ -1616,6 +1645,7 @@ protected: QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; QSize m_currentPixelSize; QRhiSwapChainProxyData m_proxyData; + QRhiShadingRateMap *m_shadingRateMap = nullptr; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags) @@ -1688,6 +1718,7 @@ public: void setScissor(const QRhiScissor &scissor); void setBlendConstants(const QColor &c); void setStencilRef(quint32 refValue); + void setShadingRate(const QSize &coarsePixelSize); void draw(quint32 vertexCount, quint32 instanceCount = 1, @@ -1873,7 +1904,10 @@ public: ThreeDimensionalTextureMipmaps, MultiView, TextureViewFormat, - ResolveDepthStencil + ResolveDepthStencil, + VariableRateShading, + VariableRateShadingMap, + VariableRateShadingMapWithTexture }; enum BeginFrameFlag { @@ -1899,7 +1933,8 @@ public: TextureArraySizeMax, MaxUniformBufferRange, MaxVertexInputs, - MaxVertexOutputs + MaxVertexOutputs, + ShadingRateImageTileSize }; ~QRhi(); @@ -1959,6 +1994,8 @@ public: QRhiSampler::AddressMode addressV, QRhiSampler::AddressMode addressW = QRhiSampler::Repeat); + QRhiShadingRateMap *newShadingRateMap(); + QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags = {}); @@ -2009,6 +2046,8 @@ public: static QRhiSwapChainProxyData updateSwapChainProxyData(Implementation impl, QWindow *window); + QList supportedShadingRates(int sampleCount) const; + protected: QRhi(); diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index b5429372a84..471c58d0dcd 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -65,6 +65,8 @@ public: virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) = 0; + virtual QRhiShadingRateMap *createShadingRateMap() = 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; @@ -99,6 +101,7 @@ public: 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 setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) = 0; virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0; @@ -124,6 +127,7 @@ public: virtual QList supportedSampleCounts() const = 0; virtual int ubufAlignment() const = 0; + virtual QList supportedShadingRates(int sampleCount) const = 0; virtual bool isYUpInFramebuffer() const = 0; virtual bool isYUpInNDC() const = 0; virtual bool isClipDepthZeroToOne() const = 0; @@ -658,7 +662,8 @@ public: TexDepthOutput, TexStorageLoad, TexStorageStore, - TexStorageLoadStore + TexStorageLoadStore, + TexShadingRate }; void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index fd0ca51f10c..a4a81015d5a 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -478,6 +478,12 @@ QList QRhiD3D11::supportedSampleCounts() const return { 1, 2, 4, 8 }; } +QList QRhiD3D11::supportedShadingRates(int sampleCount) const +{ + Q_UNUSED(sampleCount); + return { QSize(1, 1) }; +} + DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleDesc(int sampleCount) const { DXGI_SAMPLE_DESC desc; @@ -639,6 +645,11 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return false; // because we use fully typed formats for textures and relaxed casting is a D3D12 thing case QRhi::ResolveDepthStencil: return false; + case QRhi::VariableRateShading: + return false; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return false; default: Q_UNREACHABLE(); return false; @@ -680,6 +691,8 @@ int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const return D3D11_VS_INPUT_REGISTER_COUNT; case QRhi::MaxVertexOutputs: return D3D11_VS_OUTPUT_REGISTER_COUNT; + case QRhi::ShadingRateImageTileSize: + return 0; default: Q_UNREACHABLE(); return 0; @@ -902,6 +915,11 @@ QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureR return new QD3D11TextureRenderTarget(this, desc, flags); } +QRhiShadingRateMap *QRhiD3D11::createShadingRateMap() +{ + return nullptr; +} + QRhiGraphicsPipeline *QRhiD3D11::createGraphicsPipeline() { return new QD3D11GraphicsPipeline(this); @@ -1237,6 +1255,12 @@ void QRhiD3D11::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) cmd.args.stencilRef.ref = refValue; } +void QRhiD3D11::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +} + void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -1542,6 +1566,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; case QRhiTexture::R8: return DXGI_FORMAT_R8_UNORM; + case QRhiTexture::R8UI: + return DXGI_FORMAT_R8_UINT; case QRhiTexture::RG8: return DXGI_FORMAT_R8G8_UNORM; case QRhiTexture::R16: diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h index 44ad7017d9f..1c19de5defd 100644 --- a/src/gui/rhi/qrhid3d11_p.h +++ b/src/gui/rhi/qrhid3d11_p.h @@ -663,6 +663,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -697,6 +699,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -722,6 +725,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp index c317539e014..d12331daf10 100644 --- a/src/gui/rhi/qrhid3d12.cpp +++ b/src/gui/rhi/qrhid3d12.cpp @@ -498,6 +498,20 @@ bool QRhiD3D12::create(QRhi::Flags flags) caps.textureViewFormat = options3.CastingFullyTypedFormatSupported; } +#ifdef QRHI_D3D12_CL5_AVAILABLE + D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = {}; + if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, sizeof(options6)))) { + caps.vrs = options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED; + caps.vrsMap = options6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2; + caps.vrsAdditionalRates = options6.AdditionalShadingRatesSupported; + shadingRateImageTileSize = options6.ShadingRateImageTileSize; + } +#else + caps.vrs = false; + caps.vrsMap = false; + caps.vrsAdditionalRates = false; +#endif + deviceLost = false; offscreenActive = false; @@ -597,6 +611,40 @@ QList QRhiD3D12::supportedSampleCounts() const return { 1, 2, 4, 8 }; } +QList QRhiD3D12::supportedShadingRates(int sampleCount) const +{ + QList sizes; + switch (sampleCount) { + case 0: + case 1: + if (caps.vrsAdditionalRates) { + sizes.append(QSize(4, 4)); + sizes.append(QSize(4, 2)); + sizes.append(QSize(2, 4)); + } + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + case 2: + if (caps.vrsAdditionalRates) + sizes.append(QSize(2, 4)); + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + case 4: + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + default: + break; + } + sizes.append(QSize(1, 1)); + return sizes; +} + QRhiSwapChain *QRhiD3D12::createSwapChain() { return new QD3D12SwapChain(this); @@ -747,6 +795,11 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const // there is no Multisample Resolve support for depth/stencil formats // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats return false; + case QRhi::VariableRateShading: + return caps.vrs; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return caps.vrsMap; } return false; } @@ -782,6 +835,8 @@ int QRhiD3D12::resourceLimit(QRhi::ResourceLimit limit) const return 32; case QRhi::MaxVertexOutputs: return 32; + case QRhi::ShadingRateImageTileSize: + return shadingRateImageTileSize; } return 0; } @@ -868,6 +923,11 @@ QRhiTextureRenderTarget *QRhiD3D12::createTextureRenderTarget(const QRhiTextureR return new QD3D12TextureRenderTarget(this, desc, flags); } +QRhiShadingRateMap *QRhiD3D12::createShadingRateMap() +{ + return new QD3D12ShadingRateMap(this); +} + QRhiGraphicsPipeline *QRhiD3D12::createGraphicsPipeline() { return new QD3D12GraphicsPipeline(this); @@ -1404,6 +1464,44 @@ void QRhiD3D12::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) cbD->cmdList->OMSetStencilRef(refValue); } +static inline D3D12_SHADING_RATE toD3DShadingRate(const QSize &coarsePixelSize) +{ + if (coarsePixelSize == QSize(1, 2)) + return D3D12_SHADING_RATE_1X2; + if (coarsePixelSize == QSize(2, 1)) + return D3D12_SHADING_RATE_2X1; + if (coarsePixelSize == QSize(2, 2)) + return D3D12_SHADING_RATE_2X2; + if (coarsePixelSize == QSize(2, 4)) + return D3D12_SHADING_RATE_2X4; + if (coarsePixelSize == QSize(4, 2)) + return D3D12_SHADING_RATE_4X2; + if (coarsePixelSize == QSize(4, 4)) + return D3D12_SHADING_RATE_4X4; + return D3D12_SHADING_RATE_1X1; +} + +void QRhiD3D12::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + cbD->hasShadingRateSet = false; + +#ifdef QRHI_D3D12_CL5_AVAILABLE + if (!caps.vrs) + return; + + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + const D3D12_SHADING_RATE_COMBINER combiners[] = { D3D12_SHADING_RATE_COMBINER_MAX, D3D12_SHADING_RATE_COMBINER_MAX }; + cbD->cmdList->RSSetShadingRate(toD3DShadingRate(coarsePixelSize), combiners); + if (coarsePixelSize.width() != 1 || coarsePixelSize.height() != 1) + cbD->hasShadingRateSet = true; +#else + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); + qWarning("Attempted to set ShadingRate without building Qt against a sufficiently new Windows SDK and d3d12.h. This cannot work."); +#endif +} + void QRhiD3D12::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -1633,7 +1731,7 @@ QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame timestampPairStartIndex * sizeof(quint64)); } - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; + D3D12GraphicsCommandList *cmdList = cbD->cmdList; HRESULT hr = cmdList->Close(); if (FAILED(hr)) { qWarning("Failed to close command list: %s", @@ -1753,7 +1851,7 @@ QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags) timestampPairStartIndex * sizeof(quint64)); } - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; + D3D12GraphicsCommandList *cmdList = cbD->cmdList; HRESULT hr = cmdList->Close(); if (FAILED(hr)) { qWarning("Failed to close command list: %s", @@ -1802,7 +1900,7 @@ QRhi::FrameOpResult QRhiD3D12::finish() Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; + D3D12GraphicsCommandList *cmdList = cbD->cmdList; HRESULT hr = cmdList->Close(); if (FAILED(hr)) { qWarning("Failed to close command list: %s", @@ -1928,7 +2026,31 @@ void QRhiD3D12::beginPass(QRhiCommandBuffer *cb, cbD->recordingPass = QD3D12CommandBuffer::RenderPass; cbD->currentTarget = rt; + bool hasShadingRateMapSet = false; +#ifdef QRHI_D3D12_CL5_AVAILABLE + if (rtD->rp->hasShadingRateMap) { + cbD->setShadingRate(QSize(1, 1)); + QD3D12ShadingRateMap *rateMapD = rt->resourceType() == QRhiRenderTarget::TextureRenderTarget + ? QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12TextureRenderTarget, rt)->m_desc.shadingRateMap()) + : QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12SwapChainRenderTarget, rt)->swapChain()->shadingRateMap()); + if (QD3D12Resource *res = resourcePool.lookupRef(rateMapD->handle)) { + barrierGen.addTransitionBarrier(rateMapD->handle, D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + cbD->cmdList->RSSetShadingRateImage(res->resource); + hasShadingRateMapSet = true; + } + } else if (cbD->hasShadingRateMapSet) { + cbD->cmdList->RSSetShadingRateImage(nullptr); + cbD->setShadingRate(QSize(1, 1)); + } else if (cbD->hasShadingRateSet) { + cbD->setShadingRate(QSize(1, 1)); + } +#endif + cbD->resetPerPassState(); + + // shading rate tracking is reset in resetPerPassState(), sync what we did just above + cbD->hasShadingRateMapSet = hasShadingRateMapSet; } void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -3140,7 +3262,7 @@ DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleDesc(int sampleCount, DXGI_FORMAT for return desc; } -bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList) +bool QRhiD3D12::startCommandListForCurrentFrameSlot(D3D12GraphicsCommandList **cmdList) { ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot]; if (!*cmdList) { @@ -3148,7 +3270,7 @@ bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 * D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, - __uuidof(ID3D12GraphicsCommandList1), + __uuidof(D3D12GraphicsCommandList), reinterpret_cast(cmdList)); if (FAILED(hr)) { qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr))); @@ -3873,6 +3995,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; case QRhiTexture::R8: return DXGI_FORMAT_R8_UNORM; + case QRhiTexture::R8UI: + return DXGI_FORMAT_R8_UINT; case QRhiTexture::RG8: return DXGI_FORMAT_R8G8_UNORM; case QRhiTexture::R16: @@ -4595,6 +4719,34 @@ QD3D12Descriptor QD3D12Sampler::lookupOrCreateShaderVisibleDescriptor() return shaderVisibleDescriptor; } +QD3D12ShadingRateMap::QD3D12ShadingRateMap(QRhiImplementation *rhi) + : QRhiShadingRateMap(rhi) +{ +} + +QD3D12ShadingRateMap::~QD3D12ShadingRateMap() +{ + destroy(); +} + +void QD3D12ShadingRateMap::destroy() +{ + if (handle.isNull()) + return; + + handle = {}; +} + +bool QD3D12ShadingRateMap::createFrom(QRhiTexture *src) +{ + if (!handle.isNull()) + destroy(); + + handle = QRHI_RES(QD3D12Texture, src)->handle; + + return true; +} + QD3D12TextureRenderTarget::QD3D12TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags) @@ -4659,6 +4811,8 @@ QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDesc rpD->dsFormat = toD3DDepthTextureDSVFormat(depthTexD->format()); // cannot be a typeless format } + rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr; + rpD->updateSerializedFormat(); QRHI_RES_RHI(QRhiD3D12); @@ -6003,6 +6157,9 @@ bool QD3D12RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *ot return false; } + if (hasShadingRateMap != o->hasShadingRateMap) + return false; + return true; } @@ -6025,6 +6182,7 @@ QRhiRenderPassDescriptor *QD3D12RenderPassDescriptor::newCompatibleRenderPassDes rpD->hasDepthStencil = hasDepthStencil; memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat)); rpD->dsFormat = dsFormat; + rpD->hasShadingRateMap = hasShadingRateMap; rpD->updateSerializedFormat(); @@ -6251,6 +6409,9 @@ QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor() rpD->hasDepthStencil = m_depthStencil != nullptr; rpD->colorFormat[0] = int(srgbAdjustedColorFormat); rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT; + + rpD->hasShadingRateMap = m_shadingRateMap != nullptr; + rpD->updateSerializedFormat(); QRHI_RES_RHI(QRhiD3D12); diff --git a/src/gui/rhi/qrhid3d12_p.h b/src/gui/rhi/qrhid3d12_p.h index 72c6c95faaf..6413aa5f64f 100644 --- a/src/gui/rhi/qrhid3d12_p.h +++ b/src/gui/rhi/qrhid3d12_p.h @@ -38,6 +38,15 @@ #ifdef __ID3D12Device2_INTERFACE_DEFINED__ #define QRHI_D3D12_AVAILABLE +// Will use ID3D12GraphicsCommandList5 as long as the d3d12.h is new enough. +// Otherwise, some features (VRS) will not be available. +#ifdef __ID3D12GraphicsCommandList5_INTERFACE_DEFINED__ +#define QRHI_D3D12_CL5_AVAILABLE +using D3D12GraphicsCommandList = ID3D12GraphicsCommandList5; +#else +using D3D12GraphicsCommandList = ID3D12GraphicsCommandList1; +#endif + QT_BEGIN_NAMESPACE static const int QD3D12_FRAMES_IN_FLIGHT = 2; @@ -754,6 +763,17 @@ struct QD3D12Sampler : public QRhiSampler QD3D12Descriptor shaderVisibleDescriptor; }; +struct QD3D12ShadingRateMap : public QRhiShadingRateMap +{ + QD3D12ShadingRateMap(QRhiImplementation *rhi); + ~QD3D12ShadingRateMap(); + void destroy() override; + bool createFrom(QRhiTexture *src) override; + + QD3D12ObjectHandle handle; // just copied from the texture + friend class QRhiD3D12; +}; + struct QD3D12RenderPassDescriptor : public QRhiRenderPassDescriptor { QD3D12RenderPassDescriptor(QRhiImplementation *rhi); @@ -770,6 +790,7 @@ struct QD3D12RenderPassDescriptor : public QRhiRenderPassDescriptor bool hasDepthStencil = false; int colorFormat[MAX_COLOR_ATTACHMENTS]; int dsFormat; + bool hasShadingRateMap = false; QVector serializedFormatData; }; @@ -916,7 +937,7 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer const QRhiNativeHandles *nativeHandles(); - ID3D12GraphicsCommandList1 *cmdList = nullptr; // not owned + D3D12GraphicsCommandList *cmdList = nullptr; // not owned QRhiD3D12CommandBufferNativeHandles nativeHandlesStruct; enum PassType { @@ -946,6 +967,8 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer currentIndexFormat = DXGI_FORMAT_R16_UINT; currentVertexBuffers = {}; currentVertexOffsets = {}; + hasShadingRateSet = false; + hasShadingRateMapSet = false; } // per-frame @@ -964,6 +987,8 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer DXGI_FORMAT currentIndexFormat; std::array currentVertexBuffers; std::array currentVertexOffsets; + bool hasShadingRateSet; + bool hasShadingRateMapSet; // global double lastGpuTime = 0; @@ -1050,7 +1075,7 @@ struct QD3D12SwapChain : public QRhiSwapChain ID3D12Fence *fence = nullptr; HANDLE fenceEvent = nullptr; UINT64 fenceCounter = 0; - ID3D12GraphicsCommandList1 *cmdList = nullptr; + D3D12GraphicsCommandList *cmdList = nullptr; } frameRes[QD3D12_FRAMES_IN_FLIGHT]; int currentFrameSlot = 0; // index in frameRes @@ -1104,6 +1129,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -1138,6 +1165,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -1163,6 +1191,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; @@ -1184,7 +1213,7 @@ public: void waitGpu(); DXGI_SAMPLE_DESC effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) const; bool ensureDirectCompositionDevice(); - bool startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList); + bool startCommandListForCurrentFrameSlot(D3D12GraphicsCommandList **cmdList); void enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates); void finishActiveReadbacks(bool forced = false); bool ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h, @@ -1236,10 +1265,14 @@ public: QVarLengthArray activeReadbacks; bool offscreenActive = false; QD3D12CommandBuffer *offscreenCb[QD3D12_FRAMES_IN_FLIGHT] = {}; + UINT shadingRateImageTileSize = 0; struct { bool multiView = false; bool textureViewFormat = false; + bool vrs = false; + bool vrsMap = false; + bool vrsAdditionalRates = false; } caps; }; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index b40383e9fc9..b17cebcaf74 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -160,6 +160,10 @@ QT_BEGIN_NAMESPACE #define GL_R8 0x8229 #endif +#ifndef GL_R8UI +#define GL_R8UI 0x8232 +#endif + #ifndef GL_RG8 #define GL_RG8 0x822B #endif @@ -1188,6 +1192,12 @@ QList QRhiGles2::supportedSampleCounts() const return supportedSampleCountList; } +QList QRhiGles2::supportedShadingRates(int sampleCount) const +{ + Q_UNUSED(sampleCount); + return { QSize(1, 1) }; +} + QRhiSwapChain *QRhiGles2::createSwapChain() { return new QGles2SwapChain(this); @@ -1260,6 +1270,12 @@ static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2 *glformat = GL_RED; *gltype = GL_UNSIGNED_BYTE; break; + case QRhiTexture::R8UI: + *glintformat = GL_R8UI; + *glsizedintformat = *glintformat; + *glformat = GL_RED; + *gltype = GL_UNSIGNED_BYTE; + break; case QRhiTexture::RG8: *glintformat = GL_RG8; *glsizedintformat = *glintformat; @@ -1363,6 +1379,7 @@ bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture return caps.bgraExternalFormat; case QRhiTexture::R8: + case QRhiTexture::R8UI: return caps.r8Format; case QRhiTexture::RG8: @@ -1481,6 +1498,11 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ResolveDepthStencil: return true; + case QRhi::VariableRateShading: + return false; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return false; default: Q_UNREACHABLE_RETURN(false); } @@ -1519,6 +1541,8 @@ int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const return caps.maxVertexInputs; case QRhi::MaxVertexOutputs: return caps.maxVertexOutputs; + case QRhi::ShadingRateImageTileSize: + return 0; default: Q_UNREACHABLE_RETURN(0); } @@ -1719,6 +1743,11 @@ QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); } +QRhiShadingRateMap *QRhiGles2::createShadingRateMap() +{ + return nullptr; +} + QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) { @@ -1992,6 +2021,12 @@ void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) cmd.args.stencilRef.ps = cbD->currentGraphicsPipeline; } +void QRhiGles2::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +} + void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h index 61ab99c260d..7d0f80b1c72 100644 --- a/src/gui/rhi/qrhigles2_p.h +++ b/src/gui/rhi/qrhigles2_p.h @@ -794,6 +794,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -828,6 +830,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -853,6 +856,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 36a71e5101e..e7f01965780 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -176,7 +176,8 @@ struct QRhiMetalData MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, - int colorAttCount); + int colorAttCount, + QRhiShadingRateMap *shadingRateMap); id createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey); id createMSLShaderFunction(id lib, const QByteArray &entryPoint); @@ -194,7 +195,8 @@ struct QRhiMetalData Sampler, StagingBuffer, GraphicsPipeline, - ComputePipeline + ComputePipeline, + ShadingRateMap }; Type type; int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1 @@ -225,6 +227,9 @@ struct QRhiMetalData struct { id pipelineState; } computePipeline; + struct { + id rateMap; + } shadingRateMap; }; }; QVector releaseQueue; @@ -306,6 +311,11 @@ struct QMetalSamplerData id samplerState = nil; }; +struct QMetalShadingRateMapData +{ + id rateMap = nil; +}; + struct QMetalShaderResourceBindingsData { struct Stage { struct Buffer { @@ -634,6 +644,10 @@ bool QRhiMetal::create(QRhi::Flags flags) caps.supportedSampleCounts.append(sampleCount); } + caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 1]; + if (caps.shadingRateMap && caps.multiView) + caps.shadingRateMap = [d->dev supportsRasterizationRateMapWithLayerCount: 2]; + if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) d->setupBinaryArchive(); @@ -672,6 +686,12 @@ QVector QRhiMetal::supportedSampleCounts() const return caps.supportedSampleCounts; } +QVector QRhiMetal::supportedShadingRates(int sampleCount) const +{ + Q_UNUSED(sampleCount); + return { QSize(1, 1) }; +} + QRhiSwapChain *QRhiMetal::createSwapChain() { return new QMetalSwapChain(this); @@ -838,6 +858,12 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ResolveDepthStencil: return true; + case QRhi::VariableRateShading: + return false; + case QRhi::VariableRateShadingMap: + return caps.shadingRateMap; + case QRhi::VariableRateShadingMapWithTexture: + return false; default: Q_UNREACHABLE(); return false; @@ -875,6 +901,8 @@ int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const return 31; case QRhi::MaxVertexOutputs: return 15; // use the minimum from MTLGPUFamily1/2/3 + case QRhi::ShadingRateImageTileSize: + return 0; default: Q_UNREACHABLE(); return 0; @@ -1063,6 +1091,11 @@ QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } +QRhiShadingRateMap *QRhiMetal::createShadingRateMap() +{ + return new QMetalShadingRateMap(this); +} + QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) { @@ -1886,6 +1919,12 @@ void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue]; } +void QRhiMetal::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +} + static id tessellationComputeEncoder(QMetalCommandBuffer *cbD) { if (cbD->d->currentRenderPassEncoder) { @@ -2557,7 +2596,8 @@ QRhi::FrameOpResult QRhiMetal::finish() MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, - int colorAttCount) + int colorAttCount, + QRhiShadingRateMap *shadingRateMap) { MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor]; MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(), @@ -2578,6 +2618,9 @@ MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthSte rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue(); } + if (shadingRateMap) + rp.rasterizationRateMap = QRHI_RES(QMetalShadingRateMap, shadingRateMap)->d->rateMap; + return rp; } @@ -2958,8 +3001,15 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, QMetalRenderTargetData *rtD = nullptr; switch (rt->resourceType()) { case QRhiResource::SwapChainRenderTarget: - rtD = QRHI_RES(QMetalSwapChainRenderTarget, rt)->d; - cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount); + { + QMetalSwapChainRenderTarget *rtSc = QRHI_RES(QMetalSwapChainRenderTarget, rt); + rtD = rtSc->d; + QRhiShadingRateMap *shadingRateMap = rtSc->swapChain()->shadingRateMap(); + cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, + colorClearValue, + depthStencilClearValue, + rtD->colorAttCount, + shadingRateMap); if (rtD->colorAttCount) { QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]); if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) { @@ -2983,6 +3033,9 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, } } } + if (shadingRateMap) + QRHI_RES(QMetalShadingRateMap, shadingRateMap)->lastActiveFrameSlot = currentFrameSlot; + } break; case QRhiResource::TextureRenderTarget: { @@ -2990,7 +3043,11 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, rtD = rtTex->d; if (!QRhiRenderTargetAttachmentTracker::isUpToDate(rtTex->description(), rtD->currentResIdList)) rtTex->create(); - cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount); + cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, + colorClearValue, + depthStencilClearValue, + rtD->colorAttCount, + rtTex->m_desc.shadingRateMap()); if (rtD->fb.preserveColor) { for (uint i = 0; i < uint(rtD->colorAttCount); ++i) cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad; @@ -3024,6 +3081,8 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb, } if (rtTex->m_desc.depthResolveTexture()) QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot; + if (rtTex->m_desc.shadingRateMap()) + QRHI_RES(QMetalShadingRateMap, rtTex->m_desc.shadingRateMap())->lastActiveFrameSlot = currentFrameSlot; } break; default: @@ -3195,6 +3254,9 @@ void QRhiMetal::executeDeferredReleases(bool forced) case QRhiMetalData::DeferredReleaseEntry::ComputePipeline: [e.computePipeline.pipelineState release]; break; + case QRhiMetalData::DeferredReleaseEntry::ShadingRateMap: + [e.shadingRateMap.rateMap release]; + break; default: break; } @@ -3393,6 +3455,8 @@ static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QR #else return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm; #endif + case QRhiTexture::R8UI: + return MTLPixelFormatR8Uint; case QRhiTexture::RG8: #ifdef Q_OS_MACOS return MTLPixelFormatRG8Unorm; @@ -4034,6 +4098,55 @@ bool QMetalSampler::create() return true; } +QMetalShadingRateMap::QMetalShadingRateMap(QRhiImplementation *rhi) + : QRhiShadingRateMap(rhi), + d(new QMetalShadingRateMapData) +{ +} + +QMetalShadingRateMap::~QMetalShadingRateMap() +{ + destroy(); + delete d; +} + +void QMetalShadingRateMap::destroy() +{ + if (!d->rateMap) + return; + + QRhiMetalData::DeferredReleaseEntry e; + e.type = QRhiMetalData::DeferredReleaseEntry::ShadingRateMap; + e.lastActiveFrameSlot = lastActiveFrameSlot; + + e.shadingRateMap.rateMap = d->rateMap; + d->rateMap = nil; + + QRHI_RES_RHI(QRhiMetal); + if (rhiD) { + rhiD->d->releaseQueue.append(e); + rhiD->unregisterResource(this); + } +} + +bool QMetalShadingRateMap::createFrom(NativeShadingRateMap src) +{ + if (d->rateMap) + destroy(); + + d->rateMap = (id) (quintptr(src.object)); + if (!d->rateMap) + return false; + + [d->rateMap retain]; + + lastActiveFrameSlot = -1; + generation += 1; + QRHI_RES_RHI(QRhiMetal); + 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) @@ -4077,6 +4190,9 @@ bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *ot return false; } + if (hasShadingRateMap != o->hasShadingRateMap) + return false; + return true; } @@ -4088,8 +4204,9 @@ void QMetalRenderPassDescriptor::updateSerializedFormat() *p++ = colorAttachmentCount; *p++ = hasDepthStencil; for (int i = 0; i < colorAttachmentCount; ++i) - *p++ = colorFormat[i]; + *p++ = colorFormat[i]; *p++ = hasDepthStencil ? dsFormat : 0; + *p++ = hasShadingRateMap; } QRhiRenderPassDescriptor *QMetalRenderPassDescriptor::newCompatibleRenderPassDescriptor() const @@ -4099,6 +4216,7 @@ QRhiRenderPassDescriptor *QMetalRenderPassDescriptor::newCompatibleRenderPassDes rpD->hasDepthStencil = hasDepthStencil; memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat)); rpD->dsFormat = dsFormat; + rpD->hasShadingRateMap = hasShadingRateMap; rpD->updateSerializedFormat(); @@ -4184,6 +4302,8 @@ QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDesc else if (m_desc.depthStencilBuffer()) rpD->dsFormat = int(QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format); + rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr; + rpD->updateSerializedFormat(); QRHI_RES_RHI(QRhiMetal); @@ -6223,6 +6343,8 @@ QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor() rpD->dsFormat = MTLPixelFormatDepth32Float_Stencil8; #endif + rpD->hasShadingRateMap = m_shadingRateMap != nullptr; + rpD->updateSerializedFormat(); rhiD->registerResource(rpD, false); diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h index f539148b2c0..bab2f8b663f 100644 --- a/src/gui/rhi/qrhimetal_p.h +++ b/src/gui/rhi/qrhimetal_p.h @@ -105,6 +105,21 @@ struct QMetalSampler : public QRhiSampler friend struct QMetalShaderResourceBindings; }; +struct QMetalShadingRateMapData; + +struct QMetalShadingRateMap : public QRhiShadingRateMap +{ + QMetalShadingRateMap(QRhiImplementation *rhi); + ~QMetalShadingRateMap(); + void destroy() override; + bool createFrom(NativeShadingRateMap src) override; + + QMetalShadingRateMapData *d; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; +}; + struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor { QMetalRenderPassDescriptor(QRhiImplementation *rhi); @@ -124,6 +139,7 @@ struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor bool hasDepthStencil = false; int colorFormat[MAX_COLOR_ATTACHMENTS]; int dsFormat; + bool hasShadingRateMap = false; QVector serializedFormatData; }; @@ -361,6 +377,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -395,6 +413,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -420,6 +439,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; @@ -500,6 +520,7 @@ public: bool isAppleGPU = false; int maxThreadGroupSize = 512; bool multiView = false; + bool shadingRateMap = false; } caps; QRhiMetalData *d = nullptr; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 566b922c1be..47e191020d0 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -60,6 +60,12 @@ QList QRhiNull::supportedSampleCounts() const return { 1 }; } +QList QRhiNull::supportedShadingRates(int sampleCount) const +{ + Q_UNUSED(sampleCount); + return { QSize(1, 1) }; +} + QRhiSwapChain *QRhiNull::createSwapChain() { return new QNullSwapChain(this); @@ -139,6 +145,8 @@ int QRhiNull::resourceLimit(QRhi::ResourceLimit limit) const return 32; case QRhi::MaxVertexOutputs: return 32; + case QRhi::ShadingRateImageTileSize: + return 0; } Q_UNREACHABLE_RETURN(0); @@ -214,6 +222,11 @@ QRhiTextureRenderTarget *QRhiNull::createTextureRenderTarget(const QRhiTextureRe return new QNullTextureRenderTarget(this, desc, flags); } +QRhiShadingRateMap *QRhiNull::createShadingRateMap() +{ + return nullptr; +} + QRhiGraphicsPipeline *QRhiNull::createGraphicsPipeline() { return new QNullGraphicsPipeline(this); @@ -282,6 +295,12 @@ void QRhiNull::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) Q_UNUSED(refValue); } +void QRhiNull::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +} + void QRhiNull::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { diff --git a/src/gui/rhi/qrhinull_p.h b/src/gui/rhi/qrhinull_p.h index fc266b4f385..5aa2fcc2622 100644 --- a/src/gui/rhi/qrhinull_p.h +++ b/src/gui/rhi/qrhinull_p.h @@ -204,6 +204,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -238,6 +240,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -263,6 +266,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 36bbd989cf4..e44202cfdbd 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -336,7 +336,8 @@ QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice() QByteArrayLiteral("VK_KHR_swapchain"), QByteArrayLiteral("VK_EXT_vertex_attribute_divisor"), QByteArrayLiteral("VK_KHR_create_renderpass2"), - QByteArrayLiteral("VK_KHR_depth_stencil_resolve") + QByteArrayLiteral("VK_KHR_depth_stencil_resolve"), + QByteArrayLiteral("VK_KHR_fragment_shading_rate") }; } @@ -412,6 +413,20 @@ static inline QRhiDriverInfo::DeviceType toRhiDeviceType(VkPhysicalDeviceType ty } } +template +static inline void addToChain(T *head, void *entry) +{ + VkBaseOutStructure *s = reinterpret_cast(head); + for ( ; ; ) { + VkBaseOutStructure *next = reinterpret_cast(s->pNext); + if (next) + s = next; + else + break; + } + s->pNext = reinterpret_cast(entry); +} + bool QRhiVulkan::create(QRhi::Flags flags) { Q_ASSERT(inst); @@ -533,10 +548,29 @@ bool QRhiVulkan::create(QRhi::Flags flags) driverInfoStruct.vendorId = physDevProperties.vendorID; driverInfoStruct.deviceType = toRhiDeviceType(physDevProperties.deviceType); + QVulkanInfoVector devExts; + uint32_t devExtCount = 0; + f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr); + if (devExtCount) { + QList extProps(devExtCount); + f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data()); + for (const VkExtensionProperties &p : std::as_const(extProps)) + devExts.append({ p.extensionName, p.specVersion }); + } + qCDebug(QRHI_LOG_INFO, "%d device extensions available", int(devExts.size())); + bool featuresQueried = false; #ifdef VK_VERSION_1_1 VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {}; physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + // Extensions (that are really extensions in 1.1-1.3, not core) +#ifdef VK_KHR_fragment_shading_rate + VkPhysicalDeviceFragmentShadingRateFeaturesKHR fragmentShadingRateFeatures = {}; + fragmentShadingRateFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR; + if (devExts.contains("VK_KHR_fragment_shading_rate")) + addToChain(&physDevFeaturesChainable, &fragmentShadingRateFeatures); +#endif #endif // Vulkan >=1.2 headers at build time, >=1.2 implementation at run time @@ -552,7 +586,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) physDevFeatures13 = {}; physDevFeatures13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; #endif - physDevFeaturesChainable.pNext = &physDevFeatures11IfApi12OrNewer; + addToChain(&physDevFeaturesChainable, &physDevFeatures11IfApi12OrNewer); physDevFeatures11IfApi12OrNewer.pNext = &physDevFeatures12; #ifdef VK_VERSION_1_3 if (caps.apiVersion >= QVersionNumber(1, 3)) @@ -578,7 +612,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) if (caps.apiVersion == QVersionNumber(1, 1)) { multiviewFeaturesIfApi11 = {}; multiviewFeaturesIfApi11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; - physDevFeaturesChainable.pNext = &multiviewFeaturesIfApi11; + addToChain(&physDevFeaturesChainable, &multiviewFeaturesIfApi11); f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable); memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures)); featuresQueried = true; @@ -637,17 +671,6 @@ bool QRhiVulkan::create(QRhi::Flags flags) if (inst->layers().contains("VK_LAYER_KHRONOS_validation")) devLayers.append("VK_LAYER_KHRONOS_validation"); - QVulkanInfoVector devExts; - uint32_t devExtCount = 0; - f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr); - if (devExtCount) { - QList extProps(devExtCount); - f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data()); - for (const VkExtensionProperties &p : std::as_const(extProps)) - devExts.append({ p.extensionName, p.specVersion }); - } - qCDebug(QRHI_LOG_INFO, "%d device extensions available", int(devExts.size())); - QList requestedDevExts; requestedDevExts.append("VK_KHR_swapchain"); @@ -686,6 +709,11 @@ bool QRhiVulkan::create(QRhi::Flags flags) } #endif +#ifdef VK_KHR_fragment_shading_rate + if (devExts.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) + requestedDevExts.append(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME); +#endif + for (const QByteArray &ext : requestedDeviceExtensions) { if (!ext.isEmpty() && !requestedDevExts.contains(ext)) { if (devExts.contains(ext)) { @@ -748,10 +776,10 @@ bool QRhiVulkan::create(QRhi::Flags flags) if (caps.apiVersion >= QVersionNumber(1, 1)) { // For a >=1.2 implementation at run time, this will enable all // (1.0-1.3) features reported as supported, except the ones we turn - // off explicitly above. For a 1.1 implementation at run time, this - // only enables the 1.0 and multiview features reported as - // supported. We will not be bothering with the Vulkan 1.1 - // individual feature struct nonsense. + // off explicitly above. (+extensions) For a 1.1 implementation at + // run time, this only enables the 1.0 and multiview features (+any + // extensions) reported as supported. We will not be bothering with + // the Vulkan 1.1 individual feature struct nonsense. devInfo.pNext = &physDevFeaturesChainable; } else #endif @@ -831,6 +859,41 @@ bool QRhiVulkan::create(QRhi::Flags flags) caps.multiView = multiviewFeaturesIfApi11.multiview; #endif +#ifdef VK_KHR_fragment_shading_rate + fragmentShadingRates.clear(); + if (caps.apiVersion >= QVersionNumber(1, 1)) { + caps.perDrawShadingRate = fragmentShadingRateFeatures.pipelineFragmentShadingRate; + caps.imageBasedShadingRate = fragmentShadingRateFeatures.attachmentFragmentShadingRate; + if (caps.imageBasedShadingRate) { + VkPhysicalDeviceFragmentShadingRatePropertiesKHR shadingRateProps = {}; + shadingRateProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR; + VkPhysicalDeviceProperties2 props2 = {}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &shadingRateProps; + f->vkGetPhysicalDeviceProperties2(physDev, &props2); + caps.imageBasedShadingRateTileSize = int(shadingRateProps.maxFragmentShadingRateAttachmentTexelSize.width); + // If it's non-square, there's nothing we can do since it is not compatible with other APIs (D3D12) then. + } + if (caps.perDrawShadingRate) { + PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR vkGetPhysicalDeviceFragmentShadingRatesKHR = + reinterpret_cast( + inst->getInstanceProcAddr("vkGetPhysicalDeviceFragmentShadingRatesKHR")); + if (vkGetPhysicalDeviceFragmentShadingRatesKHR) { + uint32_t count = 0; + vkGetPhysicalDeviceFragmentShadingRatesKHR(physDev, &count, nullptr); + fragmentShadingRates.resize(count); + for (VkPhysicalDeviceFragmentShadingRateKHR &s : fragmentShadingRates) { + s = {}; + s.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR; + } + vkGetPhysicalDeviceFragmentShadingRatesKHR(physDev, &count, fragmentShadingRates.data()); + } + vkCmdSetFragmentShadingRateKHR = reinterpret_cast( + f->vkGetDeviceProcAddr(dev, "vkCmdSetFragmentShadingRateKHR")); + } + } +#endif + // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we // have to support the case of 1.1 + extensions, in particular for the Quest // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on @@ -1064,6 +1127,9 @@ static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture // intentionally A2B10G10R10, not A2R10G10B10 return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case QRhiTexture::R8UI: + return VK_FORMAT_R8_UINT; + case QRhiTexture::D16: return VK_FORMAT_D16_UNORM; case QRhiTexture::D24: @@ -1331,116 +1397,6 @@ VkFormat QRhiVulkan::optimalDepthStencilFormat() return optimalDsFormat; } -static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo, - VkSubpassDescription *subpassDesc, - QVkRenderPassDescriptor *rpD) -{ - memset(subpassDesc, 0, sizeof(VkSubpassDescription)); - subpassDesc->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.size()); - subpassDesc->pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr; - subpassDesc->pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr; - subpassDesc->pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr; - - memset(rpInfo, 0, sizeof(VkRenderPassCreateInfo)); - rpInfo->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - rpInfo->attachmentCount = uint32_t(rpD->attDescs.size()); - rpInfo->pAttachments = rpD->attDescs.constData(); - rpInfo->subpassCount = 1; - rpInfo->pSubpasses = subpassDesc; - rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.size()); - rpInfo->pDependencies = !rpD->subpassDeps.isEmpty() ? rpD->subpassDeps.constData() : nullptr; -} - -bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat) -{ - // attachment list layout is color (1), ds (0-1), resolve (0-1) - - VkAttachmentDescription attDesc = {}; - attDesc.format = colorFormat; - attDesc.samples = samples; - attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? 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; - attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - rpD->attDescs.append(attDesc); - - rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); - - rpD->hasDepthStencil = hasDepthStencil; - rpD->hasDepthStencilResolve = false; - rpD->multiViewCount = 0; - - if (hasDepthStencil) { - // clear on load + no store + lazy alloc + transient image should play - // nicely with tiled GPUs (no physical backing necessary for ds buffer) - memset(&attDesc, 0, sizeof(attDesc)); - attDesc.format = optimalDepthStencilFormat(); - attDesc.samples = samples; - attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - rpD->attDescs.append(attDesc); - - rpD->dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; - } - - if (samples > VK_SAMPLE_COUNT_1_BIT) { - memset(&attDesc, 0, sizeof(attDesc)); - attDesc.format = colorFormat; - attDesc.samples = VK_SAMPLE_COUNT_1_BIT; - attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - 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_PRESENT_SRC_KHR; - rpD->attDescs.append(attDesc); - - rpD->resolveRefs.append({ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); - } - - // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own. - VkSubpassDependency 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; - rpD->subpassDeps.append(subpassDep); - if (hasDepthStencil) { - memset(&subpassDep, 0, sizeof(subpassDep)); - subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; - subpassDep.dstSubpass = 0; - subpassDep.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT - | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - subpassDep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT - | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - subpassDep.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - subpassDep.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT - | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - rpD->subpassDeps.append(subpassDep); - } - - VkRenderPassCreateInfo rpInfo; - VkSubpassDescription subpassDesc; - fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD); - - VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); - if (err != VK_SUCCESS) { - qWarning("Failed to create renderpass: %d", err); - return false; - } - - return true; -} - struct MultiViewRenderPassSetupHelper { bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap) @@ -1476,10 +1432,12 @@ struct MultiViewRenderPassSetupHelper #ifdef VK_KHR_create_renderpass2 // Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2, -// adding depth-stencil resolve support. Assumes a single subpass and no subpass -// dependencies. +// adding depth-stencil resolve and VRS support. Incorporates multiview into the +// info structs (no chaining needed). Assumes a single subpass. struct RenderPass2SetupHelper { + RenderPass2SetupHelper(QRhiVulkan *rhiD) : rhiD(rhiD) { } + bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) { *rpInfo2 = {}; @@ -1572,10 +1530,44 @@ struct RenderPass2SetupHelper dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex; - subpass2.pNext = &dsResolveDesc; + addToChain(&subpass2, &dsResolveDesc); } #endif +#ifdef VK_KHR_fragment_shading_rate + shadingRateAttInfo = {}; + if (rpD->hasShadingRateMap) { + startIndex = attRefs2.count(); + attRefs2.append({}); + VkAttachmentReference2KHR &attref2(attRefs2.last()); + attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; + attref2.attachment = rpD->shadingRateRef.attachment; + attref2.layout = rpD->shadingRateRef.layout; + shadingRateAttInfo.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR; + shadingRateAttInfo.pFragmentShadingRateAttachment = attRefs2.constData() + startIndex; + shadingRateAttInfo.shadingRateAttachmentTexelSize.width = rhiD->caps.imageBasedShadingRateTileSize; + shadingRateAttInfo.shadingRateAttachmentTexelSize.height = rhiD->caps.imageBasedShadingRateTileSize; + addToChain(&subpass2, &shadingRateAttInfo); + } +#endif + + // subpass dependencies, typically 0, 1, 2 of them, + // depending on targeting swapchain or texture + subpassDeps2.clear(); + for (uint32_t i = 0; i < rpInfo->dependencyCount; ++i) { + const VkSubpassDependency &dep(rpInfo->pDependencies[i]); + subpassDeps2.append({}); + VkSubpassDependency2 &dep2(subpassDeps2.last()); + dep2.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR; + dep2.srcSubpass = dep.srcSubpass; + dep2.dstSubpass = dep.dstSubpass; + dep2.srcStageMask = dep.srcStageMask; + dep2.dstStageMask = dep.dstStageMask; + dep2.srcAccessMask = dep.srcAccessMask; + dep2.dstAccessMask = dep.dstAccessMask; + dep2.dependencyFlags = dep.dependencyFlags; + } + rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR; rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs rpInfo2->flags = rpInfo->flags; @@ -1583,6 +1575,8 @@ struct RenderPass2SetupHelper rpInfo2->pAttachments = attDescs2.constData(); rpInfo2->subpassCount = 1; rpInfo2->pSubpasses = &subpass2; + rpInfo2->dependencyCount = subpassDeps2.count(); + rpInfo2->pDependencies = !subpassDeps2.isEmpty() ? subpassDeps2.constData() : nullptr; if (multiViewCount >= 2) { rpInfo2->correlatedViewMaskCount = 1; rpInfo2->pCorrelatedViewMasks = &viewMask; @@ -1590,16 +1584,176 @@ struct RenderPass2SetupHelper return true; } + QRhiVulkan *rhiD; QVarLengthArray attDescs2; QVarLengthArray attRefs2; VkSubpassDescription2KHR subpass2; + QVarLengthArray subpassDeps2; #ifdef VK_KHR_depth_stencil_resolve VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc; +#endif +#ifdef VK_KHR_fragment_shading_rate + VkFragmentShadingRateAttachmentInfoKHR shadingRateAttInfo; #endif uint32_t viewMask; }; #endif // VK_KHR_create_renderpass2 +static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo, + VkSubpassDescription *subpassDesc, + QVkRenderPassDescriptor *rpD) +{ + memset(subpassDesc, 0, sizeof(VkSubpassDescription)); + subpassDesc->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.size()); + subpassDesc->pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr; + subpassDesc->pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr; + subpassDesc->pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr; + + memset(rpInfo, 0, sizeof(VkRenderPassCreateInfo)); + rpInfo->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + rpInfo->attachmentCount = uint32_t(rpD->attDescs.size()); + rpInfo->pAttachments = rpD->attDescs.constData(); + rpInfo->subpassCount = 1; + rpInfo->pSubpasses = subpassDesc; + rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.size()); + rpInfo->pDependencies = !rpD->subpassDeps.isEmpty() ? rpD->subpassDeps.constData() : nullptr; +} + +bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, + bool hasDepthStencil, + VkSampleCountFlagBits samples, + VkFormat colorFormat, + QRhiShadingRateMap *shadingRateMap) +{ + // attachment list layout is color (1), ds (0-1), resolve (0-1), shading rate (0-1) + + VkAttachmentDescription attDesc = {}; + attDesc.format = colorFormat; + attDesc.samples = samples; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? 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; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + rpD->attDescs.append(attDesc); + + rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); + + rpD->hasDepthStencil = hasDepthStencil; + rpD->hasDepthStencilResolve = false; + rpD->hasShadingRateMap = shadingRateMap != nullptr; + rpD->multiViewCount = 0; + + if (hasDepthStencil) { + // clear on load + no store + lazy alloc + transient image should play + // nicely with tiled GPUs (no physical backing necessary for ds buffer) + attDesc = {}; + attDesc.format = optimalDepthStencilFormat(); + attDesc.samples = samples; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + rpD->attDescs.append(attDesc); + + rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + } else { + rpD->dsRef = {}; + } + + if (samples > VK_SAMPLE_COUNT_1_BIT) { + attDesc = {}; + attDesc.format = colorFormat; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + 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_PRESENT_SRC_KHR; + rpD->attDescs.append(attDesc); + + rpD->resolveRefs.append({ uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); + } + + rpD->dsResolveRef = {}; + + rpD->shadingRateRef = {}; +#ifdef VK_KHR_fragment_shading_rate + if (shadingRateMap) { + attDesc = {}; + attDesc.format = VK_FORMAT_R8_UINT; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + attDesc.finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + rpD->attDescs.append(attDesc); + + rpD->shadingRateRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR }; + } +#endif + + // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own. + VkSubpassDependency 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; + rpD->subpassDeps.append(subpassDep); + if (hasDepthStencil) { + memset(&subpassDep, 0, sizeof(subpassDep)); + subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; + subpassDep.dstSubpass = 0; + subpassDep.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT + | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + subpassDep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT + | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + subpassDep.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + subpassDep.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT + | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + rpD->subpassDeps.append(subpassDep); + } + + VkRenderPassCreateInfo rpInfo; + VkSubpassDescription subpassDesc; + fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD); + +#ifdef VK_KHR_create_renderpass2 + if (caps.renderPass2KHR) { + // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1. + VkRenderPassCreateInfo2KHR rpInfo2; + RenderPass2SetupHelper rp2Helper(this); + if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, 0)) + return false; + VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err); + return false; + } + } else +#endif + { + if (rpD->hasShadingRateMap) + qWarning("Variable rate shading with image is not supported without VK_KHR_create_renderpass2"); + VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass: %d", err); + return false; + } + } + + return true; +} + bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, const QRhiColorAttachment *colorAttachmentsBegin, const QRhiColorAttachment *colorAttachmentsEnd, @@ -1608,7 +1762,8 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, bool storeDs, QRhiRenderBuffer *depthStencilBuffer, QRhiTexture *depthTexture, - QRhiTexture *depthResolveTexture) + QRhiTexture *depthResolveTexture, + QRhiShadingRateMap *shadingRateMap) { // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1) @@ -1669,8 +1824,10 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, multiViewCount = depthTexture->arraySize(); rpD->multiViewCount = multiViewCount; } + rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + } else { + rpD->dsRef = {}; } - rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) { if (it->resolveTexture()) { @@ -1732,8 +1889,28 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; rpD->attDescs.append(attDesc); + rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + } else { + rpD->dsResolveRef = {}; } - rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + + rpD->hasShadingRateMap = shadingRateMap != nullptr; + rpD->shadingRateRef = {}; +#ifdef VK_KHR_fragment_shading_rate + if (shadingRateMap) { + VkAttachmentDescription attDesc = {}; + attDesc.format = VK_FORMAT_R8_UINT; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + attDesc.finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + rpD->attDescs.append(attDesc); + rpD->shadingRateRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR }; + } +#endif // rpD->subpassDeps stays empty: don't yet know the correct initial/final // access and stage stuff for the implicit deps at this point, so leave it @@ -1749,10 +1926,10 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, return false; #ifdef VK_KHR_create_renderpass2 - if (rpD->hasDepthStencilResolve && caps.renderPass2KHR) { + if (caps.renderPass2KHR) { // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1. VkRenderPassCreateInfo2KHR rpInfo2; - RenderPass2SetupHelper rp2Helper; + RenderPass2SetupHelper rp2Helper(this); if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount)) return false; @@ -1768,6 +1945,8 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, qWarning("Resolving multisample depth-stencil buffers is not supported without " "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2"); } + if (rpD->hasShadingRateMap) + qWarning("Variable rate shading with image is not supported without VK_KHR_create_renderpass2"); VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); if (err != VK_SUCCESS) { qWarning("Failed to create renderpass: %d", err); @@ -2017,6 +2196,30 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) swapChainD->currentImageIndex = 0; + if (swapChainD->shadingRateMap() && caps.renderPass2KHR && caps.imageBasedShadingRate) { + QVkTexture *texD = QRHI_RES(QVkShadingRateMap, swapChainD->shadingRateMap())->texture; + Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedAsShadingRateMap)); + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = texD->image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = texD->viewFormat; + 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 = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VkResult err = df->vkCreateImageView(dev, &viewInfo, nullptr, &swapChainD->shadingRateMapView); + if (err != VK_SUCCESS) { + qWarning("Failed to create swapchain shading rate map view: %d", err); + return false; + } + } + VkSemaphoreCreateInfo semInfo = {}; semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -2106,6 +2309,11 @@ void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain) swapChainD->msaaImageMem = VK_NULL_HANDLE; } + if (swapChainD->shadingRateMapView) { + df->vkDestroyImageView(dev, swapChainD->shadingRateMapView, nullptr); + swapChainD->shadingRateMapView = VK_NULL_HANDLE; + } + vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr); swapChainD->sc = VK_NULL_HANDLE; @@ -2711,6 +2919,13 @@ void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRe QRhiPassResourceTracker::TexDepthOutputStage); depthResolveTexD->lastActiveFrameSlot = currentFrameSlot; } + if (rtD->m_desc.shadingRateMap()) { + QVkTexture *texD = QRHI_RES(QVkShadingRateMap, rtD->m_desc.shadingRateMap())->texture; + trackedRegisterTexture(&passResTracker, texD, + QRhiPassResourceTracker::TexShadingRate, + QRhiPassResourceTracker::TexColorOutputStage); + texD->lastActiveFrameSlot = currentFrameSlot; + } } void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2807,6 +3022,14 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, Q_ASSERT(currentSwapChain); currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse = QVkSwapChain::ImageResources::ScImageUseRender; + if (currentSwapChain->shadingRateMapView) { + QVkTexture *texD = QRHI_RES(QVkShadingRateMap, currentSwapChain->shadingRateMap())->texture; + QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); + trackedRegisterTexture(&passResTracker, texD, + QRhiPassResourceTracker::TexShadingRate, + QRhiPassResourceTracker::TexColorOutputStage); + texD->lastActiveFrameSlot = currentFrameSlot; + } break; case QRhiResource::TextureRenderTarget: { @@ -2857,6 +3080,11 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() }; cvs.append(cv); } + for (int i = 0; i < rtD->shadingRateAttCount; ++i) { + VkClearValue cv; + cv.color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; + cvs.append(cv); + } rpBeginInfo.clearValueCount = uint32_t(cvs.size()); QVkCommandBuffer::Command &cmd(cbD->commands.get()); @@ -2869,7 +3097,14 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, if (cbD->passUsesSecondaryCb) cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer(rtD)); - cbD->resetCachedState(); + if (cbD->hasShadingRateSet) { + QVkCommandBuffer::Command &rateCmd(cbD->commands.get()); + rateCmd.cmd = QVkCommandBuffer::Command::SetShadingRate; + rateCmd.args.setShadingRate.w = 1; + rateCmd.args.setShadingRate.h = 1; + } + + cbD->resetPerPassState(); } void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2913,7 +3148,7 @@ void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, if (cbD->passUsesSecondaryCb) cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer()); - cbD->resetCachedState(); + cbD->resetPerPassState(); } void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -4161,6 +4396,7 @@ void QRhiVulkan::executeDeferredReleases(bool forced) } df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr); df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr); + df->vkDestroyImageView(dev, e.textureRenderTarget.shadingRateMapView, nullptr); break; case QRhiVulkan::DeferredReleaseEntry::RenderPass: df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr); @@ -4282,6 +4518,28 @@ VkSampleCountFlagBits QRhiVulkan::effectiveSampleCountBits(int sampleCount) Q_UNREACHABLE_RETURN(VK_SAMPLE_COUNT_1_BIT); } +QList QRhiVulkan::supportedShadingRates(int sampleCount) const +{ + QList result; +#ifdef VK_KHR_fragment_shading_rate + sampleCount = qMax(1, sampleCount); + VkSampleCountFlagBits mask = VK_SAMPLE_COUNT_1_BIT; + for (const auto &qvk_sampleCount : qvk_sampleCounts) { + if (qvk_sampleCount.count == sampleCount) { + mask = qvk_sampleCount.mask; + break; + } + } + for (const VkPhysicalDeviceFragmentShadingRateKHR &s : fragmentShadingRates) { + if (s.sampleCounts & mask) + result.append(QSize(int(s.fragmentSize.width), int(s.fragmentSize.height))); + } +#else + result.append(QSize(1, 1)); +#endif + return result; +} + void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD) { cbD->passResTrackers.append(QRhiPassResourceTracker()); @@ -4419,6 +4677,18 @@ void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD) case QVkCommandBuffer::Command::ExecuteSecondary: df->vkCmdExecuteCommands(cbD->cb, 1, &cmd.args.executeSecondary.cb); break; + case QVkCommandBuffer::Command::SetShadingRate: + { +#ifdef VK_KHR_fragment_shading_rate + VkFragmentShadingRateCombinerOpKHR op[2] = { + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR, + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR + }; + VkExtent2D size = { cmd.args.setShadingRate.w, cmd.args.setShadingRate.h }; + vkCmdSetFragmentShadingRateKHR(cbD->cb, &size, op); +#endif + } + break; default: break; } @@ -4492,6 +4762,12 @@ static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess ac case QRhiPassResourceTracker::TexStorageStore: case QRhiPassResourceTracker::TexStorageLoadStore: return VK_IMAGE_LAYOUT_GENERAL; + case QRhiPassResourceTracker::TexShadingRate: +#ifdef VK_KHR_fragment_shading_rate + return VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; +#else + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +#endif default: Q_UNREACHABLE(); break; @@ -4514,6 +4790,8 @@ static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess ac return VK_ACCESS_SHADER_WRITE_BIT; case QRhiPassResourceTracker::TexStorageLoadStore: return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + case QRhiPassResourceTracker::TexShadingRate: + return 0; default: Q_UNREACHABLE(); break; @@ -4824,6 +5102,11 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ResolveDepthStencil: return caps.renderPass2KHR && caps.depthStencilResolveKHR; + case QRhi::VariableRateShading: + return caps.renderPass2KHR && caps.perDrawShadingRate; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return caps.renderPass2KHR && caps.imageBasedShadingRate; default: Q_UNREACHABLE_RETURN(false); } @@ -4862,6 +5145,8 @@ int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const return physDevProperties.limits.maxVertexInputAttributes; case QRhi::MaxVertexOutputs: return physDevProperties.limits.maxVertexOutputComponents / 4; + case QRhi::ShadingRateImageTileSize: + return caps.imageBasedShadingRateTileSize; default: Q_UNREACHABLE_RETURN(0); } @@ -5058,6 +5343,11 @@ QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSample return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } +QRhiShadingRateMap *QRhiVulkan::createShadingRateMap() +{ + return new QVkShadingRateMap(this); +} + QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) { @@ -5444,8 +5734,8 @@ void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport } if (cbD->currentGraphicsPipeline - && !QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline) - ->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) { + && !QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) + { QVkCommandBuffer::Command &cmd(cbD->commands.get()); VkRect2D *s = &cmd.args.setScissor.scissor; qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h); @@ -5521,6 +5811,37 @@ void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) } } +void QRhiVulkan::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ +#ifdef VK_KHR_fragment_shading_rate + if (!vkCmdSetFragmentShadingRateKHR) + return; + + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); + Q_ASSERT(!cbD->currentGraphicsPipeline || QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesShadingRate)); + + VkFragmentShadingRateCombinerOpKHR ops[2] = { + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR, + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR + }; + VkExtent2D size = { uint32_t(coarsePixelSize.width()), uint32_t(coarsePixelSize.height()) }; + if (cbD->passUsesSecondaryCb) { + vkCmdSetFragmentShadingRateKHR(cbD->activeSecondaryCbStack.last(), &size, ops); + } else { + QVkCommandBuffer::Command &cmd(cbD->commands.get()); + cmd.cmd = QVkCommandBuffer::Command::SetShadingRate; + cmd.args.setShadingRate.w = size.width; + cmd.args.setShadingRate.h = size.height; + } + if (coarsePixelSize.width() != 1 || coarsePixelSize.height() != 1) + cbD->hasShadingRateSet = true; +#else + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +#endif +} + void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -5704,7 +6025,7 @@ void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer(maybeRenderTargetData(cbD))); } - cbD->resetCachedState(); + cbD->resetPerPassState(); } double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb) @@ -6654,6 +6975,10 @@ bool QVkTexture::create() imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; if (m_flags.testFlag(QRhiTexture::UsedWithLoadStore)) imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; +#ifdef VK_KHR_fragment_shading_rate + if (m_flags.testFlag(QRhiTexture::UsedAsShadingRateMap) && rhiD->caps.imageBasedShadingRate) + imageInfo.usage |= VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR; +#endif VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; @@ -6889,6 +7214,8 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other return false; if (multiViewCount != o->multiViewCount) return false; + if (hasShadingRateMap != o->hasShadingRateMap) + return false; for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { const uint32_t attIdx = colorRefs[i].attachment; @@ -6922,6 +7249,14 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other return false; } + if (hasShadingRateMap) { + const uint32_t attIdx = shadingRateRef.attachment; + if (attIdx != o->shadingRateRef.attachment) + return false; + if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx])) + return false; + } + // subpassDeps is not included return true; @@ -6937,6 +7272,7 @@ void QVkRenderPassDescriptor::updateSerializedFormat() *p++ = resolveRefs.size(); *p++ = hasDepthStencil; *p++ = hasDepthStencilResolve; + *p++ = hasShadingRateMap; *p++ = multiViewCount; auto serializeAttachmentData = [this, &p](uint32_t attIdx) { @@ -6975,6 +7311,12 @@ void QVkRenderPassDescriptor::updateSerializedFormat() *p++ = attIdx; serializeAttachmentData(attIdx); } + + if (hasShadingRateMap) { + const uint32_t attIdx = shadingRateRef.attachment; + *p++ = attIdx; + serializeAttachmentData(attIdx); + } } QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const @@ -6988,9 +7330,11 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri rpD->subpassDeps = subpassDeps; rpD->hasDepthStencil = hasDepthStencil; rpD->hasDepthStencilResolve = hasDepthStencilResolve; + rpD->hasShadingRateMap = hasShadingRateMap; rpD->multiViewCount = multiViewCount; rpD->dsRef = dsRef; rpD->dsResolveRef = dsResolveRef; + rpD->shadingRateRef = shadingRateRef; VkRenderPassCreateInfo rpInfo; VkSubpassDescription subpassDesc; @@ -7003,11 +7347,30 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri return nullptr; } - VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp); - if (err != VK_SUCCESS) { - qWarning("Failed to create renderpass: %d", err); - delete rpD; - return nullptr; +#ifdef VK_KHR_create_renderpass2 + if (rhiD->caps.renderPass2KHR) { + // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1. + VkRenderPassCreateInfo2KHR rpInfo2; + RenderPass2SetupHelper rp2Helper(rhiD); + if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount)) { + delete rpD; + return nullptr; + } + VkResult err = rhiD->vkCreateRenderPass2KHR(rhiD->dev, &rpInfo2, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err); + delete rpD; + return nullptr; + } + } else +#endif + { + VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass: %d", err); + delete rpD; + return nullptr; + } } rpD->updateSerializedFormat(); @@ -7026,6 +7389,34 @@ const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles() return &nativeHandlesStruct; } +QVkShadingRateMap::QVkShadingRateMap(QRhiImplementation *rhi) + : QRhiShadingRateMap(rhi) +{ +} + +QVkShadingRateMap::~QVkShadingRateMap() +{ + destroy(); +} + +void QVkShadingRateMap::destroy() +{ + if (!texture) + return; + + texture = nullptr; +} + +bool QVkShadingRateMap::createFrom(QRhiTexture *src) +{ + if (texture) + destroy(); + + texture = QRHI_RES(QVkTexture, src); + + return true; +} + QVkSwapChainRenderTarget::QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) : QRhiSwapChainRenderTarget(rhi, swapchain) { @@ -7096,6 +7487,9 @@ void QVkTextureRenderTarget::destroy() e.textureRenderTarget.resdsv = resdsv; resdsv = VK_NULL_HANDLE; + e.textureRenderTarget.shadingRateMapView = shadingRateMapView; + shadingRateMapView = VK_NULL_HANDLE; + QRHI_RES_RHI(QRhiVulkan); if (rhiD) { rhiD->releaseQueue.append(e); @@ -7117,7 +7511,8 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(), m_desc.depthStencilBuffer(), m_desc.depthTexture(), - m_desc.depthResolveTexture())) + m_desc.depthResolveTexture(), + m_desc.shadingRateMap())) { delete rp; return nullptr; @@ -7294,6 +7689,36 @@ bool QVkTextureRenderTarget::create() d.dsResolveAttCount = 0; } + if (m_desc.shadingRateMap() && rhiD->caps.renderPass2KHR && rhiD->caps.imageBasedShadingRate) { + QVkTexture *texD = QRHI_RES(QVkShadingRateMap, m_desc.shadingRateMap())->texture; + Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedAsShadingRateMap)); + + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = texD->image; + viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY + : VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = texD->viewFormat; + 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 = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = qMax(1, d.multiViewCount); + VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &shadingRateMapView); + if (err != VK_SUCCESS) { + qWarning("Failed to create render target shading rate map view: %d", err); + return false; + } + views.append(shadingRateMapView); + d.shadingRateAttCount = 1; + } else { + d.shadingRateAttCount = 0; + } + if (!m_renderPassDesc) qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()."); @@ -7303,7 +7728,7 @@ bool QVkTextureRenderTarget::create() VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = d.rp->rp; - fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount + d.dsResolveAttCount); + fbInfo.attachmentCount = uint32_t(views.count()); fbInfo.pAttachments = views.constData(); fbInfo.width = uint32_t(d.pixelSize.width()); fbInfo.height = uint32_t(d.pixelSize.height()); @@ -7625,6 +8050,10 @@ bool QVkGraphicsPipeline::create() dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS; if (m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef)) dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE; +#ifdef VK_KHR_fragment_shading_rate + if (m_flags.testFlag(QRhiGraphicsPipeline::UsesShadingRate) && rhiD->caps.perDrawShadingRate) + dynEnable << VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR; +#endif VkPipelineDynamicStateCreateInfo dynamicInfo = {}; dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; @@ -8009,7 +8438,8 @@ QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor() if (!rhiD->createDefaultRenderPass(rp, m_depthStencil != nullptr, samples, - colorFormat)) + colorFormat, + m_shadingRateMap)) { delete rp; return nullptr; @@ -8160,19 +8590,28 @@ bool QVkSwapChain::createOrResize() else rtWrapper.d.resolveAttCount = 0; + if (shadingRateMapView) + rtWrapper.d.shadingRateAttCount = 1; + else + rtWrapper.d.shadingRateAttCount = 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 - }; + // color, ds, resolve, shading rate + QVarLengthArray views; + views.append(samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView); + if (ds) + views.append(ds->imageView); + if (samples > VK_SAMPLE_COUNT_1_BIT) + views.append(image.imageView); + if (shadingRateMapView) + views.append(shadingRateMapView); VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = rtWrapper.d.rp->rp; - fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount + rtWrapper.d.dsResolveAttCount); - fbInfo.pAttachments = views; + fbInfo.attachmentCount = uint32_t(views.count()); + fbInfo.pAttachments = views.constData(); fbInfo.width = uint32_t(pixelSize.width()); fbInfo.height = uint32_t(pixelSize.height()); fbInfo.layers = 1; @@ -8209,19 +8648,21 @@ bool QVkSwapChain::createOrResize() for (int i = 0; i < bufferCount; ++i) { QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]); - 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 - }; + // color, ds, resolve, shading rate + QVarLengthArray views; + views.append(samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView); + if (ds) + views.append(ds->imageView); + if (samples > VK_SAMPLE_COUNT_1_BIT) + views.append(image.imageView); + if (shadingRateMapView) + views.append(shadingRateMapView); VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = rtWrapperRight.d.rp->rp; - fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount - + rtWrapperRight.d.resolveAttCount + rtWrapperRight.d.dsResolveAttCount); - fbInfo.pAttachments = views; + fbInfo.attachmentCount = uint32_t(views.count()); + fbInfo.pAttachments = views.constData(); fbInfo.width = uint32_t(pixelSize.width()); fbInfo.height = uint32_t(pixelSize.height()); fbInfo.layers = 1; diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h index f23d8550f04..e0c7ec7a89a 100644 --- a/src/gui/rhi/qrhivulkan_p.h +++ b/src/gui/rhi/qrhivulkan_p.h @@ -145,6 +145,17 @@ struct QVkSampler : public QRhiSampler friend class QRhiVulkan; }; +struct QVkShadingRateMap : public QRhiShadingRateMap +{ + QVkShadingRateMap(QRhiImplementation *rhi); + ~QVkShadingRateMap(); + void destroy() override; + bool createFrom(QRhiTexture *src) override; + + QVkTexture *texture = nullptr; // not owned + friend class QRhiVulkan; +}; + struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor { QVkRenderPassDescriptor(QRhiImplementation *rhi); @@ -165,9 +176,11 @@ struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor QVarLengthArray subpassDeps; bool hasDepthStencil = false; bool hasDepthStencilResolve = false; + bool hasShadingRateMap = false; uint32_t multiViewCount = 0; VkAttachmentReference dsRef; VkAttachmentReference dsResolveRef; + VkAttachmentReference shadingRateRef; QVector serializedFormatData; QRhiVulkanRenderPassNativeHandles nativeHandlesStruct; int lastActiveFrameSlot = -1; @@ -184,6 +197,7 @@ struct QVkRenderTargetData int dsAttCount = 0; int resolveAttCount = 0; int dsResolveAttCount = 0; + int shadingRateAttCount = 0; int multiViewCount = 0; QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; static const int MAX_COLOR_ATTACHMENTS = 8; @@ -220,6 +234,7 @@ struct QVkTextureRenderTarget : public QRhiTextureRenderTarget VkImageView dsv = VK_NULL_HANDLE; VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; VkImageView resdsv = VK_NULL_HANDLE; + VkImageView shadingRateMapView = VK_NULL_HANDLE; int lastActiveFrameSlot = -1; friend class QRhiVulkan; }; @@ -332,10 +347,10 @@ struct QVkCommandBuffer : public QRhiCommandBuffer currentTarget = nullptr; activeSecondaryCbStack.clear(); resetCommands(); - resetCachedState(); + resetPerPassState(); } - void resetCachedState() { + void resetPerPassState() { currentGraphicsPipeline = nullptr; currentComputePipeline = nullptr; currentPipelineGeneration = 0; @@ -349,6 +364,7 @@ struct QVkCommandBuffer : public QRhiCommandBuffer memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers)); memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets)); inExternal = false; + hasShadingRateSet = false; } PassType recordingPass; @@ -370,6 +386,7 @@ struct QVkCommandBuffer : public QRhiCommandBuffer quint32 currentVertexOffsets[VERTEX_INPUT_RESOURCE_SLOT_COUNT]; QVarLengthArray activeSecondaryCbStack; bool inExternal; + bool hasShadingRateSet; struct { QHash > writtenResources; @@ -404,7 +421,8 @@ struct QVkCommandBuffer : public QRhiCommandBuffer DebugMarkerInsert, TransitionPassResources, Dispatch, - ExecuteSecondary + ExecuteSecondary, + SetShadingRate }; Cmd cmd; @@ -531,6 +549,10 @@ struct QVkCommandBuffer : public QRhiCommandBuffer struct { VkCommandBuffer cb; } executeSecondary; + struct { + uint32_t w; + uint32_t h; + } setShadingRate; } args; }; @@ -608,6 +630,7 @@ struct QVkSwapChain : public QRhiSwapChain QVkSwapChainRenderTarget rtWrapper; QVkSwapChainRenderTarget rtWrapperRight; QVkCommandBuffer cbWrapper; + VkImageView shadingRateMapView = VK_NULL_HANDLE; struct ImageResources { VkImage image = VK_NULL_HANDLE; @@ -679,6 +702,8 @@ public: QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -713,6 +738,7 @@ public: void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -738,6 +764,7 @@ public: double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; @@ -771,7 +798,8 @@ public: bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, - VkFormat colorFormat); + VkFormat colorFormat, + QRhiShadingRateMap *shadingRateMap); bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, const QRhiColorAttachment *colorAttachmentsBegin, const QRhiColorAttachment *colorAttachmentsEnd, @@ -780,7 +808,8 @@ public: bool storeDs, QRhiRenderBuffer *depthStencilBuffer, QRhiTexture *depthTexture, - QRhiTexture *depthResolveTexture); + QRhiTexture *depthResolveTexture, + QRhiShadingRateMap *shadingRateMap); bool ensurePipelineCache(const void *initialData = nullptr, size_t initialDataSize = 0); VkShaderModule createShader(const QByteArray &spirv); @@ -897,7 +926,10 @@ public: bool multiView = false; bool renderPass2KHR = false; bool depthStencilResolveKHR = false; + bool perDrawShadingRate = false; + bool imageBasedShadingRate = false; QVersionNumber apiVersion; + int imageBasedShadingRateTileSize = 0; } caps; VkPipelineCache pipelineCache = VK_NULL_HANDLE; @@ -1013,6 +1045,7 @@ public: VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; VkImageView dsv; VkImageView resdsv; + VkImageView shadingRateMapView; } textureRenderTarget; struct { VkRenderPass rp; @@ -1027,6 +1060,11 @@ public: }; }; QList releaseQueue; + +#ifdef VK_KHR_fragment_shading_rate + QVarLengthArray fragmentShadingRates; + PFN_vkCmdSetFragmentShadingRateKHR vkCmdSetFragmentShadingRateKHR = nullptr; +#endif }; Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_RELOCATABLE_TYPE); diff --git a/tests/manual/rhi/CMakeLists.txt b/tests/manual/rhi/CMakeLists.txt index 8f48bf219dc..4582e5240dd 100644 --- a/tests/manual/rhi/CMakeLists.txt +++ b/tests/manual/rhi/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(displacement) add_subdirectory(imguirenderer) add_subdirectory(multiview) add_subdirectory(msaatextureresolve) +add_subdirectory(vrs) if(QT_FEATURE_widgets) add_subdirectory(rhiwidgetproto) endif() diff --git a/tests/manual/rhi/shared/examplefw.h b/tests/manual/rhi/shared/examplefw.h index f2ca57341c6..7f662c443ea 100644 --- a/tests/manual/rhi/shared/examplefw.h +++ b/tests/manual/rhi/shared/examplefw.h @@ -18,7 +18,7 @@ #include #ifdef EXAMPLEFW_IMGUI -#include "qrhiimgui_p.h" +#include "qrhiimgui.h" #include "imgui.h" #endif @@ -98,6 +98,9 @@ protected: void customInit(); void customRelease(); +#ifdef EXAMPLEFW_BEFORE_FRAME + void customBeforeFrame(); +#endif void customRender(); #ifdef EXAMPLEFW_IMGUI void customGui(); @@ -360,6 +363,10 @@ void Window::render() if (!m_hasSwapChain || m_notExposed) return; +#ifdef EXAMPLEFW_BEFORE_FRAME + customBeforeFrame(); +#endif + // If the window got resized or got newly exposed, resize the swapchain. // (the newly-exposed case is not actually required by some // platforms/backends, but f.ex. Vulkan on Windows seems to need it) diff --git a/tests/manual/rhi/shared/imgui/imgui.cmakeinc b/tests/manual/rhi/shared/imgui/imgui.cmakeinc index 3e7420409d3..af99eb06a8c 100644 --- a/tests/manual/rhi/shared/imgui/imgui.cmakeinc +++ b/tests/manual/rhi/shared/imgui/imgui.cmakeinc @@ -5,7 +5,7 @@ set(imgui_sources ${imgui_base}/imgui/imgui_widgets.cpp ${imgui_base}/imgui/imgui_demo.cpp ${imgui_base}/qrhiimgui.cpp - ${imgui_base}/qrhiimgui_p.h + ${imgui_base}/qrhiimgui.h ) target_sources(${imgui_target} PRIVATE diff --git a/tests/manual/rhi/shared/imgui/qrhiimgui.cpp b/tests/manual/rhi/shared/imgui/qrhiimgui.cpp index 88b0a5d8977..d65889925ff 100644 --- a/tests/manual/rhi/shared/imgui/qrhiimgui.cpp +++ b/tests/manual/rhi/shared/imgui/qrhiimgui.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include "qrhiimgui_p.h" +#include "qrhiimgui.h" #include #include #include @@ -34,7 +34,8 @@ QRhiImguiRenderer::~QRhiImguiRenderer() void QRhiImguiRenderer::releaseResources() { for (Texture &t : m_textures) { - delete t.tex; + if (t.ownTex) + delete t.tex; delete t.srb; } m_textures.clear(); @@ -43,7 +44,8 @@ void QRhiImguiRenderer::releaseResources() m_ibuf.reset(); m_ubuf.reset(); m_ps.reset(); - m_sampler.reset(); + m_linearSampler.reset(); + m_nearestSampler.reset(); m_rhi = nullptr; } @@ -100,41 +102,53 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, return; } - if (!m_sampler) { - m_sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, - QRhiSampler::Repeat, QRhiSampler::Repeat)); - m_sampler->setName(QByteArrayLiteral("imgui sampler")); - if (!m_sampler->create()) + if (!m_linearSampler) { + m_linearSampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::Repeat, QRhiSampler::Repeat)); + m_linearSampler->setName(QByteArrayLiteral("imgui linear sampler")); + if (!m_linearSampler->create()) + return; + } + + if (!m_nearestSampler) { + m_nearestSampler.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::Repeat, QRhiSampler::Repeat)); + m_nearestSampler->setName(QByteArrayLiteral("imgui nearest sampler")); + if (!m_nearestSampler->create()) return; } if (m_textures.isEmpty()) { Texture fontTex; fontTex.image = sf.fontTextureData; - m_textures.append(fontTex); - } else if (!sf.fontTextureData.isNull()) { + m_textures.insert(nullptr, fontTex); + sf.reset(); + } else if (sf.isValid()) { Texture fontTex; fontTex.image = sf.fontTextureData; - delete m_textures[0].tex; - delete m_textures[0].srb; - m_textures[0] = fontTex; + Texture &fontTexEntry(m_textures[nullptr]); + delete fontTexEntry.tex; + delete fontTexEntry.srb; + fontTexEntry = fontTex; + sf.reset(); } - QVarLengthArray texturesNeedUpdate; - for (int i = 0; i < m_textures.count(); ++i) { - Texture &t(m_textures[i]); + QVarLengthArray texturesNeedUpdate; + for (auto it = m_textures.begin(), end = m_textures.end(); it != end; ++it) { + Texture &t(*it); if (!t.tex) { t.tex = m_rhi->newTexture(QRhiTexture::RGBA8, t.image.size()); - t.tex->setName(QByteArrayLiteral("imgui texture ") + QByteArray::number(i)); + t.tex->setName(QByteArrayLiteral("imgui texture ") + QByteArray::number(qintptr(it.key()))); if (!t.tex->create()) return; - texturesNeedUpdate.append(i); + texturesNeedUpdate.append(it.key()); } if (!t.srb) { + QRhiSampler *sampler = t.filter == QRhiSampler::Nearest ? m_nearestSampler.get() : m_linearSampler.get(); t.srb = m_rhi->newShaderResourceBindings(); t.srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf.get()), - QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, t.tex, m_sampler.get()) + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, t.tex, sampler) }); if (!t.srb->create()) return; @@ -146,6 +160,9 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, if (m_ps && m_rt->renderPassDescriptor()->serializedFormat() != m_renderPassFormat) m_ps.reset(); + if (m_ps && m_rt->sampleCount() != m_ps->sampleCount()) + m_ps.reset(); + if (!m_ps) { QShader vs = getShader(QLatin1String(":/imgui.vert.qsb")); QShader fs = getShader(QLatin1String(":/imgui.frag.qsb")); @@ -170,7 +187,7 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, m_ps->setDepthTest(true); m_ps->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); m_ps->setDepthWrite(false); - m_ps->setFlags(QRhiGraphicsPipeline::UsesScissor); + m_ps->setFlags(QRhiGraphicsPipeline::UsesScissor | QRhiGraphicsPipeline::UsesShadingRate); m_ps->setShaderStages({ { QRhiShaderStage::Vertex, vs }, @@ -186,8 +203,8 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }, { 0, 2, QRhiVertexInputAttribute::UNormByte4, 4 * sizeof(float) } }); - m_ps->setVertexInputLayout(inputLayout); + m_ps->setSampleCount(rt->sampleCount()); m_ps->setShaderResourceBindings(m_textures[0].srb); m_ps->setRenderPassDescriptor(m_rt->renderPassDescriptor()); m_renderPassFormat = m_rt->renderPassDescriptor()->serializedFormat(); @@ -210,8 +227,10 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, for (int i = 0; i < texturesNeedUpdate.count(); ++i) { Texture &t(m_textures[texturesNeedUpdate[i]]); - u->uploadTexture(t.tex, t.image); - t.image = QImage(); + if (!t.image.isNull()) { + u->uploadTexture(t.tex, t.image); + t.image = QImage(); + } } m_cb->resourceUpdate(u); @@ -244,12 +263,31 @@ void QRhiImguiRenderer::render() scissorSize.setWidth(qMin(viewportSize.width(), scissorSize.width())); scissorSize.setHeight(qMin(viewportSize.height(), scissorSize.height())); m_cb->setScissor({ scissorPos.x(), scissorPos.y(), scissorSize.width(), scissorSize.height() }); - m_cb->setShaderResources(m_textures[c.textureIndex].srb); + m_cb->setShaderResources(m_textures[c.textureId].srb); m_cb->setVertexInput(0, 1, &vbufBinding, m_ibuf.get(), c.indexOffset, QRhiCommandBuffer::IndexUInt32); m_cb->drawIndexed(c.elemCount); } } +void QRhiImguiRenderer::registerCustomTexture(void *id, + QRhiTexture *texture, + QRhiSampler::Filter filter, + CustomTextureOwnership ownership) +{ + Q_ASSERT(id); + auto it = m_textures.constFind(id); + if (it != m_textures.cend()) { + if (it->ownTex) + delete it->tex; + delete it->srb; + } + Texture t; + t.tex = texture; + t.filter = filter; + t.ownTex = ownership == TakeCustomTextureOwnership; + m_textures[id] = t; +} + static const char *getClipboardText(void *) { static QByteArray contents; @@ -264,7 +302,8 @@ static void setClipboardText(void *, const char *text) QRhiImgui::QRhiImgui() { - ImGui::CreateContext(); + context = ImGui::CreateContext(); + ImGui::SetCurrentContext(static_cast(context)); rebuildFontAtlas(); ImGuiIO &io(ImGui::GetIO()); io.GetClipboardTextFn = getClipboardText; @@ -273,22 +312,40 @@ QRhiImgui::QRhiImgui() QRhiImgui::~QRhiImgui() { - ImGui::DestroyContext(); + ImGui::DestroyContext(static_cast(context)); } void QRhiImgui::rebuildFontAtlas() { + ImGui::SetCurrentContext(static_cast(context)); + ImGuiIO &io(ImGui::GetIO()); unsigned char *pixels; int w, h; - ImGuiIO &io(ImGui::GetIO()); io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h); const QImage wrapperImg(const_cast(pixels), w, h, QImage::Format_RGBA8888); sf.fontTextureData = wrapperImg.copy(); - io.Fonts->SetTexID(reinterpret_cast(quintptr(0))); + io.Fonts->SetTexID(nullptr); +} + +void QRhiImgui::rebuildFontAtlasWithFont(const QString &filename) +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Failed to open %s", qPrintable(filename)); + return; + } + QByteArray font = f.readAll(); + ImGui::SetCurrentContext(static_cast(context)); + ImFontConfig fontCfg; + fontCfg.FontDataOwnedByAtlas = false; + ImGui::GetIO().Fonts->Clear(); + ImGui::GetIO().Fonts->AddFontFromMemoryTTF(font.data(), font.size(), 20.0f, &fontCfg); + rebuildFontAtlas(); } void QRhiImgui::nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPointF &logicalOffset, FrameFunc frameFunc) { + ImGui::SetCurrentContext(static_cast(context)); ImGuiIO &io(ImGui::GetIO()); const QPointF itemPixelOffset = logicalOffset * dpr; @@ -332,7 +389,7 @@ void QRhiImgui::nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPoi if (!cmd->UserCallback) { QRhiImguiRenderer::DrawCmd dc; dc.cmdListBufferIdx = n; - dc.textureIndex = int(reinterpret_cast(cmd->TextureId)); + dc.textureId = cmd->TextureId; dc.indexOffset = indexOffset; dc.elemCount = cmd->ElemCount; dc.itemPixelOffset = itemPixelOffset; @@ -348,8 +405,10 @@ void QRhiImgui::nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPoi void QRhiImgui::syncRenderer(QRhiImguiRenderer *renderer) { - renderer->sf = sf; - sf.fontTextureData = QImage(); + if (sf.isValid()) { + renderer->sf = sf; + sf.reset(); + } renderer->f = std::move(f); } @@ -540,6 +599,7 @@ static ImGuiKey mapKey(int k) bool QRhiImgui::processEvent(QEvent *event) { + ImGui::SetCurrentContext(static_cast(context)); ImGuiIO &io(ImGui::GetIO()); switch (event->type()) { diff --git a/tests/manual/rhi/shared/imgui/qrhiimgui_p.h b/tests/manual/rhi/shared/imgui/qrhiimgui.h similarity index 70% rename from tests/manual/rhi/shared/imgui/qrhiimgui_p.h rename to tests/manual/rhi/shared/imgui/qrhiimgui.h index ec86c2af7f4..bbb834bafac 100644 --- a/tests/manual/rhi/shared/imgui/qrhiimgui_p.h +++ b/tests/manual/rhi/shared/imgui/qrhiimgui.h @@ -1,8 +1,8 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#ifndef QRHIIMGUI_P_H -#define QRHIIMGUI_P_H +#ifndef QRHIIMGUI_H +#define QRHIIMGUI_H #include @@ -22,7 +22,7 @@ public: struct DrawCmd { int cmdListBufferIdx; - int textureIndex; + void *textureId; quint32 indexOffset; quint32 elemCount; QPointF itemPixelOffset; @@ -31,6 +31,8 @@ public: struct StaticRenderData { QImage fontTextureData; + bool isValid() const { return !fontTextureData.isNull(); } + void reset() { fontTextureData = QImage(); } }; struct FrameRenderData { @@ -54,6 +56,15 @@ public: void render(); void releaseResources(); + enum CustomTextureOwnership { + TakeCustomTextureOwnership, + NoCustomTextureOwnership + }; + void registerCustomTexture(void *id, + QRhiTexture *texture, + QRhiSampler::Filter filter, + CustomTextureOwnership ownership); + private: QRhi *m_rhi = nullptr; QRhiRenderTarget *m_rt = nullptr; @@ -64,14 +75,17 @@ private: std::unique_ptr m_ubuf; std::unique_ptr m_ps; QVector m_renderPassFormat; - std::unique_ptr m_sampler; + std::unique_ptr m_linearSampler; + std::unique_ptr m_nearestSampler; struct Texture { QImage image; QRhiTexture *tex = nullptr; QRhiShaderResourceBindings *srb = nullptr; + QRhiSampler::Filter filter = QRhiSampler::Linear; + bool ownTex = true; }; - QVector m_textures; + QHash m_textures; }; class QRhiImgui @@ -86,8 +100,10 @@ public: bool processEvent(QEvent *e); void rebuildFontAtlas(); + void rebuildFontAtlasWithFont(const QString &filename); private: + void *context; QRhiImguiRenderer::StaticRenderData sf; QRhiImguiRenderer::FrameRenderData f; Qt::MouseButtons pressedMouseButtons; diff --git a/tests/manual/rhi/vrs/CMakeLists.txt b/tests/manual/rhi/vrs/CMakeLists.txt new file mode 100644 index 00000000000..b802cd19700 --- /dev/null +++ b/tests/manual/rhi/vrs/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(vrs LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_manual_test(vrs + GUI + SOURCES + vrs.cpp + LIBRARIES + Qt::Gui + Qt::GuiPrivate +) + +set_source_files_properties("../shared/texture.vert.qsb" + PROPERTIES QT_RESOURCE_ALIAS "texture.vert.qsb" +) +set_source_files_properties("../shared/texture.frag.qsb" + PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb" +) +set_source_files_properties("../shared/qt256.png" + PROPERTIES QT_RESOURCE_ALIAS "qt256.png" +) +qt_internal_add_resource(vrs "vrs" + PREFIX + "/" + FILES + "../shared/texture.vert.qsb" + "../shared/texture.frag.qsb" + "../shared/qt256.png" +) + +qt_internal_extend_target(vrs CONDITION QT_FEATURE_metal + SOURCES + vrs_metaltest.mm +) + +set(imgui_base ../shared/imgui) +set(imgui_target vrs) +include(${imgui_base}/imgui.cmakeinc) diff --git a/tests/manual/rhi/vrs/vrs.cpp b/tests/manual/rhi/vrs/vrs.cpp new file mode 100644 index 00000000000..cda827238ab --- /dev/null +++ b/tests/manual/rhi/vrs/vrs.cpp @@ -0,0 +1,428 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#define EXAMPLEFW_IMGUI +#define EXAMPLEFW_BEFORE_FRAME +#include "../shared/examplefw.h" + +#include "../shared/cube.h" + +#include +#include + +#if QT_CONFIG(metal) +void *makeRateMap(QRhi *rhi, const QSize &outputSizeInPixels); +void releaseRateMap(void *map); +#endif + +const int CUBE_COUNT = 10; + +struct { + QMatrix4x4 winProj; + QList releasePool; + QRhiResourceUpdateBatch *initialUpdates = nullptr; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiTexture *tex = nullptr; + QRhiSampler *sampler = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + bool showDemoWindow = true; + float rotation = 35.0f; + bool vrsSupported = false; + bool vrsMapSupported = false; + bool vrsMapImageSupported = false; + QMap> supportedShadingRates; + int cps[2] = {}; + bool applyRateMapWithImage = false; + bool applyRateMapNative = false; + bool applyRateMapPending = false; + QRhiShadingRateMap *rateMap = nullptr; + QRhiRenderPassDescriptor *scRpWithRateMap = nullptr; + QRhiTexture *rateMapTexture = nullptr; + QRhiTexture *rateMapTextureForVisualization = nullptr; + void *nativeRateMap = nullptr; + QSize nativeRateMapSize; + QVector tx; + QVector ty; + QVector scale; + quint32 ubufAlignedSize; + + bool textureBased = false; + QRhiTexture *outTexture = nullptr; + QRhiTextureRenderTarget *texRt = nullptr; + QRhiRenderPassDescriptor *texRtRp = nullptr; + QRhiRenderPassDescriptor *texRtRpWithRateMap = nullptr; +} d; + +void Window::customInit() +{ + d.vrsSupported = m_r->isFeatureSupported(QRhi::VariableRateShading); + d.vrsMapSupported = m_r->isFeatureSupported(QRhi::VariableRateShadingMap); + d.vrsMapImageSupported = m_r->isFeatureSupported(QRhi::VariableRateShadingMapWithTexture); + for (int sampleCount : { 1, 2, 4, 8, 16 }) + d.supportedShadingRates.insert(sampleCount, m_r->supportedShadingRates(sampleCount)); + + d.initialUpdates = m_r->nextResourceUpdateBatch(); + + d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + d.vbuf->create(); + d.releasePool << d.vbuf; + + d.initialUpdates->uploadStaticBuffer(d.vbuf, cube); + + d.ubufAlignedSize = m_r->ubufAligned(68); + const quint32 ubufSize = d.ubufAlignedSize * CUBE_COUNT; + d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize); + d.ubuf->create(); + d.releasePool << d.ubuf; + + QImage image = QImage(QLatin1String(":/qt256.png")).convertToFormat(QImage::Format_RGBA8888).mirrored(); + d.tex = m_r->newTexture(QRhiTexture::RGBA8, QSize(image.width(), image.height()), 1, {}); + d.releasePool << d.tex; + d.tex->create(); + d.initialUpdates->uploadTexture(d.tex, image); + + d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + d.releasePool << d.sampler; + d.sampler->create(); + + d.srb = m_r->newShaderResourceBindings(); + d.releasePool << d.srb; + d.srb->setBindings({ + QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 68), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler) + }); + d.srb->create(); + + d.ps = m_r->newGraphicsPipeline(); + d.releasePool << d.ps; + + d.ps->setFlags(QRhiGraphicsPipeline::UsesShadingRate); + + d.ps->setCullMode(QRhiGraphicsPipeline::Back); + const QRhiShaderStage stages[] = { + { QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }; + d.ps->setShaderStages(stages, stages + 2); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + d.ps->setVertexInputLayout(inputLayout); + d.ps->setShaderResourceBindings(d.srb); + d.ps->setRenderPassDescriptor(m_rp); + d.ps->create(); + + // resources for trying out rendering into a texture + d.outTexture = m_r->newTexture(QRhiTexture::RGBA8, QSize(1024, 1024), 1, QRhiTexture::RenderTarget); + d.releasePool << d.outTexture; + d.outTexture->create(); + + d.texRt = m_r->newTextureRenderTarget({ d.outTexture }); + d.releasePool << d.texRt; + d.texRtRp = d.texRt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.texRtRp; + d.texRt->setRenderPassDescriptor(d.texRtRp); + d.texRt->create(); + + QRandomGenerator *rg = QRandomGenerator::global(); + for (int i = 0; i < CUBE_COUNT; i++) { + d.tx.append(rg->bounded(-20, 20) / 10.0f); + d.ty.append(rg->bounded(-20, 20) / 10.0f); + d.scale.append(rg->bounded(0, 10) / 10.0f); + } +} + +void Window::customRelease() +{ + qDeleteAll(d.releasePool); + d.releasePool.clear(); + +#if QT_CONFIG(metal) + if (d.nativeRateMap) + releaseRateMap(d.nativeRateMap); +#endif +} + +void Window::customBeforeFrame() +{ + // This function is invoked before calling rhi->beginFrame(). + // Thus it is suitable to do things that involve rebuilding render target related things. + + if (d.applyRateMapPending) { + d.applyRateMapPending = false; + if (d.applyRateMapWithImage || d.applyRateMapNative) { + if (d.textureBased) { + QRhiTextureRenderTargetDescription desc = d.texRt->description(); + desc.setShadingRateMap(d.rateMap); + d.texRt->setDescription(desc); + if (!d.texRtRpWithRateMap) { + d.texRtRpWithRateMap = d.texRt->newCompatibleRenderPassDescriptor(); + d.releasePool << d.texRtRpWithRateMap; + } + d.texRt->setRenderPassDescriptor(d.texRtRpWithRateMap); + d.texRt->create(); + d.ps->setRenderPassDescriptor(d.texRtRpWithRateMap); + d.ps->create(); + } else { + m_sc->setShadingRateMap(d.rateMap); + if (!d.scRpWithRateMap) { + d.scRpWithRateMap = m_sc->newCompatibleRenderPassDescriptor(); + d.releasePool << d.scRpWithRateMap; + } + m_sc->setRenderPassDescriptor(d.scRpWithRateMap); + m_sc->createOrResize(); + d.ps->setRenderPassDescriptor(d.scRpWithRateMap); + d.ps->create(); + } + } else { + if (d.textureBased) { + QRhiTextureRenderTargetDescription desc = d.texRt->description(); + desc.setShadingRateMap(nullptr); + d.texRt->setDescription(desc); + d.texRt->setRenderPassDescriptor(d.texRtRp); + d.texRt->create(); + d.ps->setRenderPassDescriptor(d.texRtRp); + d.ps->create(); + } else { + m_sc->setShadingRateMap(nullptr); + m_sc->setRenderPassDescriptor(m_rp); + m_sc->createOrResize(); + d.ps->setRenderPassDescriptor(m_rp); + d.ps->create(); + } + } + } +} + +static void renderCube(QRhiCommandBuffer *cb, const QSize &outputSizeInPixels, quint32 ubufAlignedSize) +{ + cb->setGraphicsPipeline(d.ps); + cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); + if (d.vrsSupported) { + int coarsePixelWidth = 1; + if (d.cps[0] == 1) + coarsePixelWidth = 2; + if (d.cps[0] == 2) + coarsePixelWidth = 4; + int coarsePixelHeight = 1; + if (d.cps[1] == 1) + coarsePixelHeight = 2; + if (d.cps[1] == 2) + coarsePixelHeight = 4; + const QSize shadingRate(coarsePixelWidth, coarsePixelHeight); + cb->setShadingRate(shadingRate); + } + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { d.vbuf, 0 }, + { d.vbuf, quint32(36 * 3 * sizeof(float)) } + }; + cb->setVertexInput(0, 2, vbufBindings); + for (int i = 0; i < CUBE_COUNT; ++i) { + QRhiCommandBuffer::DynamicOffset dynOfs(0, i * ubufAlignedSize); + cb->setShaderResources(d.srb, 1, &dynOfs); + cb->draw(36); + } +} + +void Window::customRender() +{ + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + if (d.initialUpdates) { + u->merge(d.initialUpdates); + d.initialUpdates->release(); + d.initialUpdates = nullptr; + } + + float opacity = 1.0f; + quint32 uoffset = 0; + for (int i = 0; i < CUBE_COUNT; ++i) { + QMatrix4x4 mvp = m_proj; + mvp.translate(d.tx[i], d.ty[i], 0.0f); + mvp.rotate(d.rotation, 0, 1, 0); + mvp.scale(d.scale[i], d.scale[i], d.scale[i]); + u->updateDynamicBuffer(d.ubuf, uoffset, 64, mvp.constData()); + u->updateDynamicBuffer(d.ubuf, uoffset + 64, 4, &opacity); + uoffset += d.ubufAlignedSize; + } + cb->resourceUpdate(u); + + if (d.textureBased) { + cb->beginPass(d.texRt, Qt::black, { 1.0f, 0 }, nullptr); + renderCube(cb, d.texRt->pixelSize(), d.ubufAlignedSize); + cb->endPass(); + } + + cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }); + if (!d.textureBased) + renderCube(cb, m_sc->currentPixelSize(), d.ubufAlignedSize); + + m_imguiRenderer->render(); + + cb->endPass(); + + d.rotation += 0.1f; +} + +void Window::customGui() +{ + ImGui::ShowDemoWindow(&d.showDemoWindow); + + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(620, 500), ImGuiCond_FirstUseEver); + ImGui::Begin("Variable Rate Shading Test"); + ImGui::Text("Per-draw VRS supported = %s", d.vrsSupported ? "true" : "false"); + ImGui::Text("Map-based VRS supported = %s", d.vrsMapSupported ? "true" : "false"); + ImGui::Text("Map/Image-based VRS supported = %s", d.vrsMapImageSupported ? "true" : "false"); + const int tileSize = m_r->resourceLimit(QRhi::ShadingRateImageTileSize); + ImGui::Text("VRS image tile size: %dx%d", tileSize, tileSize); + + if (ImGui::TreeNodeEx("Supported rates", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Columns(2, "ratecols"); + ImGui::Separator(); + ImGui::Text("Sample count"); ImGui::NextColumn(); + ImGui::Text("Rates"); ImGui::NextColumn(); + ImGui::Separator(); + for (int sampleCount : { 1, 2, 4, 8, 16 }) { + ImGui::Text("%d", sampleCount); + ImGui::NextColumn(); + QString rateStr; + for (QSize coarsePixelSize : d.supportedShadingRates[sampleCount]) + rateStr += QString::asprintf(" %dx%d", coarsePixelSize.width(), coarsePixelSize.height()); + ImGui::Text("%s", qPrintable(rateStr)); + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + ImGui::Text("Sample count: %d", sampleCount); + + const bool wasThisFrameTextureBased = d.textureBased; + if (ImGui::Checkbox("Render cubes to texture and apply VRS to that", &d.textureBased)) { + d.applyRateMapPending = true; + // this imgui callback is made before customRender(), ensure the pipeline + // and renderpasses are valid; customBeforeFrame() comes only before the next frame. + if (d.textureBased) { + d.ps->setRenderPassDescriptor(d.texRtRp); + d.ps->create(); + } else { + d.ps->setRenderPassDescriptor(m_rp); + d.ps->create(); + } + m_imguiRenderer->registerCustomTexture(d.outTexture, d.outTexture, QRhiSampler::Nearest, QRhiImguiRenderer::NoCustomTextureOwnership); + } + + if (d.vrsSupported) { + ImGui::Text("Coarse pixel size"); + ImGui::PushID("cps_width"); + ImGui::Text("Width"); ImGui::SameLine(); ImGui::RadioButton("1", &d.cps[0], 0); ImGui::SameLine(); ImGui::RadioButton("2", &d.cps[0], 1); ImGui::SameLine(); ImGui::RadioButton("4", &d.cps[0], 2); + ImGui::PopID(); + ImGui::PushID("cps_height"); + ImGui::Text("Height"); ImGui::SameLine(); ImGui::RadioButton("1", &d.cps[1], 0); ImGui::SameLine(); ImGui::RadioButton("2", &d.cps[1], 1); ImGui::SameLine(); ImGui::RadioButton("4", &d.cps[1], 2); + ImGui::PopID(); + } + + if (d.vrsMapImageSupported) { + if (ImGui::Checkbox("Apply R8_UINT texture as shading rate image", &d.applyRateMapWithImage)) { + // We are recording a frame already (between beginFrame..endFrame), it is too + // late to attempt to change settings that involve recreating the render + // targets, because the swapchain is involved here. It can only apply from the + // next frame (the one after this one). + d.applyRateMapPending = true; + if (d.applyRateMapWithImage && tileSize > 0) { + const QSize outputSizeInPixels = d.textureBased ? d.texRt->pixelSize() : m_sc->currentPixelSize(); + if (d.rateMap && d.rateMapTexture->pixelSize() != outputSizeInPixels) + d.rateMap = nullptr; + if (!d.rateMap) { + const QSize rateImageSize(qCeil(outputSizeInPixels.width() / (float)tileSize), + qCeil(outputSizeInPixels.height() / (float)tileSize)); + qDebug() << "Tile size" << tileSize << "Shading rate texture size" << rateImageSize; + d.rateMapTexture = m_r->newTexture(QRhiTexture::R8UI, rateImageSize, 1, QRhiTexture::UsedAsShadingRateMap); + d.releasePool << d.rateMapTexture; + d.rateMapTexture->create(); + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); + // 1x1 in a certain area, but use 4x4 outside + QImage img(rateImageSize, QImage::Format_Grayscale8); + img.fill(0xA); // 4x4 + QPainter pnt(&img); + // pnt.setPen(QColor::fromRgb(0, 0, 0)); // 1x1 + // pnt.setBrush(QColor::fromRgb(0, 0, 0)); + // pnt.drawEllipse(20, 20, rateImageSize.width() - 40, rateImageSize.height() - 40); + pnt.fillRect(20, 20, rateImageSize.width() - 40, rateImageSize.height() - 40, QColor::fromRgb(0, 0, 0)); + pnt.end(); + u->uploadTexture(d.rateMapTexture, img); + cb->resourceUpdate(u); + + d.rateMap = m_r->newShadingRateMap(); + d.releasePool << d.rateMap; + d.rateMap->createFrom(d.rateMapTexture); + + d.rateMapTextureForVisualization = m_r->newTexture(QRhiTexture::RGBA8, rateImageSize, 1); + d.releasePool << d.rateMapTextureForVisualization; + d.rateMapTextureForVisualization->create(); + QImage rgbaImg = img.convertToFormat(QImage::Format_RGBA8888); + u = m_r->nextResourceUpdateBatch(); + u->uploadTexture(d.rateMapTextureForVisualization, rgbaImg); + cb->resourceUpdate(u); + m_imguiRenderer->registerCustomTexture(d.rateMapTextureForVisualization, d.rateMapTextureForVisualization, QRhiSampler::Nearest, QRhiImguiRenderer::NoCustomTextureOwnership); + } + } + } + } else if (d.vrsMapSupported) { +#if QT_CONFIG(metal) + if (ImGui::Checkbox("Apply a MTLRasterizationRateMap (no scaling, incomplete!)", &d.applyRateMapNative)) { + d.applyRateMapPending = true; + const QSize outputSizeInPixels = d.textureBased ? d.texRt->pixelSize() : m_sc->currentPixelSize(); + if (d.applyRateMapNative && d.nativeRateMap && d.nativeRateMapSize != outputSizeInPixels) { + releaseRateMap(d.nativeRateMap); + d.nativeRateMap = nullptr; + } + if (d.applyRateMapNative && !d.nativeRateMap) { + d.nativeRateMap = makeRateMap(m_r, outputSizeInPixels); + d.nativeRateMapSize = outputSizeInPixels; + d.rateMap = m_r->newShadingRateMap(); + d.releasePool << d.rateMap; + // rateMap will not own nativeRateMap as per cross-platform docs, + // but it does actually do a retain/release in the Metal backend. + // Regardless, we make sure nativeRateMap lives until the end. + d.rateMap->createFrom({ quint64(d.nativeRateMap) }); + } + } +#endif + } + + ImGui::End(); + + if (wasThisFrameTextureBased) { + QSize s = d.outTexture->pixelSize(); + ImGui::SetNextWindowPos(ImVec2(500, 50), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(s.width() / 2, s.height() / 2), ImGuiCond_FirstUseEver); + ImGui::Begin("Texture", nullptr, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Image(d.outTexture, ImVec2(s.width(), s.height())); + ImGui::End(); + + if (d.applyRateMapWithImage && !d.applyRateMapPending) { + ImGui::SetNextWindowPos(ImVec2(500, 250), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(s.width() / 2, s.height() / 2), ImGuiCond_FirstUseEver); + ImGui::Begin("Shading rate image", nullptr, ImGuiWindowFlags_HorizontalScrollbar); + s = d.rateMapTextureForVisualization->pixelSize(); + const int tileSize = m_r->resourceLimit(QRhi::ShadingRateImageTileSize); + const float alpha = 0.4f; + ImGui::Image(d.rateMapTextureForVisualization, ImVec2(s.width() * tileSize, s.height() * tileSize), ImVec2(0, 0), ImVec2(1, 1), ImVec4(1, 1, 1, alpha)); + ImGui::End(); + } + } +} diff --git a/tests/manual/rhi/vrs/vrs_metaltest.mm b/tests/manual/rhi/vrs/vrs_metaltest.mm new file mode 100644 index 00000000000..820939f8076 --- /dev/null +++ b/tests/manual/rhi/vrs/vrs_metaltest.mm @@ -0,0 +1,33 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include + +void *makeRateMap(QRhi *rhi, const QSize &outputSizeInPixels) +{ + // note that multiview needs two layers, this example only uses one + + MTLDevice *dev = static_cast(rhi->nativeHandles())->dev; + MTLRasterizationRateMapDescriptor *descriptor = [[MTLRasterizationRateMapDescriptor alloc] init]; + descriptor.screenSize = MTLSizeMake(outputSizeInPixels.width(), outputSizeInPixels.height(), 1); + MTLSize zoneCounts = MTLSizeMake(8, 8, 1); + MTLRasterizationRateLayerDescriptor *layerDescriptor = [[MTLRasterizationRateLayerDescriptor alloc] initWithSampleCount:zoneCounts]; + for (uint row = 0; row < zoneCounts.height; row++) + layerDescriptor.verticalSampleStorage[row] = 1.0; + for (uint column = 0; column < zoneCounts.width; column++) + layerDescriptor.horizontalSampleStorage[column] = 1.0; + layerDescriptor.horizontalSampleStorage[0] = 0.25; + layerDescriptor.horizontalSampleStorage[7] = 0.25; + layerDescriptor.verticalSampleStorage[0] = 0.25; + layerDescriptor.verticalSampleStorage[7] = 0.25; + [descriptor setLayer:layerDescriptor atIndex:0]; + id rateMap = [dev newRasterizationRateMapWithDescriptor: descriptor]; + return rateMap; +} + +void releaseRateMap(void *map) +{ + id rateMap = (id) map; + [rateMap release]; +}