Add support for VRS

The basic Metal plumbing for letting an MTLRasterizationRateMap through
is important for Vision Pro, that seems to rely heavily on VRS to reduce
the GPU load via eye-tracked, dynamic foveation.

Metal's approach is kind of different than everyone else. For a more
complete, forward looking story, introduce support for some of the D3D12
and Vulkan VRS features. (these seem to be identical feature-wise, with
Vulkan essentially copying D3D, just with a lot more convoluted API) A
certain fragmentation is however inevitable.

This way we support:

- setting a per-draw rate (say I want to draw this mesh with 4x4 rate,
  i.e. 4x reduced resolution both horizontally and vertically). This is
  supported with D3D12/Vulkan, and could also be supported with the
  Qualcomm OpenGL ES extension, although that's not currently
  implemented.

- setting a texture-based rate map, D3D12/Vulkan style. This implies
  starting to support R8_UINT textures since R8_UNORM, while the
  difference isn't necessarily relevant here, will be rejected by some
  APIs. This is done by setting a QRhiTexture on a QRhiShadingRateMap (a
  new lightweight, wrapper-style object that does not do much on its
  own, just contains either a texture or some other native object)

- setting an MTLRasterizationRateMap on a QRhiShadingRateMap. This is
  only supported on Metal, naturally, and is realized by an overload of
  createFrom(). Conversely, setting a QRhiTexture (or attempting to set
  a per-draw rate) will not work with Metal.

We do not offer ways to create a rate map directly via Qt, due to the
underlying API differences. So for the user this involves either
uploading a Grayscale8 or similar QImage into an R8UI QRhiTexture, or by
setting up an MTLRasterizationRateMap directly with Metal and passing
that in to the QRhiShadingRateMap instead of a QRhiTexture.

We also do not care about the bizarre 'render to a texture, get a buffer
with scale rates, and then do a scaling pass using the buffer and MSL
intrinsics in the shader' approach of Metal. For now we assume that in
case of the Vision Pro this is taken care of by the XR compositor, and
is not something the applications (Qt) implements.

Some D3D12/Vulkan VRS features are left as potential future
enhancements. For example, when it comes to combining the two rates, we
always do MAX, there is no API to control the combiners.

The third mode of D3D12/Vulkan VRS, per-triangle (writing to a special
variable in the vertex shader) is not planned to be supported now or in
the future.

The Vulkan backend gets quite a lot of changes, mainly due to the
difficulty of dealing with extensions in a portable and robust manner in
that API, and the API versioning design mistakes in Vulkan 1.1 and
beyond. Now both swapchain and texture render pass objects go through
vkCreateRenderPass2, when supported, but the KHR variant still, not the
1.2+ core functions, in order to play nice with Vulkan 1.1 and the
Android ecosystem.

Some QRhi docs are enhanced, e.g. around QRhiRenderPassDescriptor, since
as the manual test demonstrates, dynamically taking VRS into use in a
scene involves certain steps (new/compatible QRhiRenderPassDescriptor is
needed, rebuilding the render target) that are not trivial. (these are
just the usual consequences of Vulkan's VkRenderPass permeating
everything in the rendering engines)

The manual test can be used to exercise both per-draw and image-based
VRS with D3D12 and Vulkan. It also includes a limited example of setting
up a rate map with Metal (but the test app does not implement the
special scaling) A number of things, such as operating with MSAA
enabled or using VRS combined with multiview are not currently
exercised by the test app, and are left as a future exercise to
verify.

Task-number: QTBUG-126297
Change-Id: I5210f4cff6b8360b6bb47cdbe8d9caee1c29b8a5
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2024-06-14 10:07:05 +02:00
parent e7ac6667f2
commit 07c0037c7e
24 changed files with 2070 additions and 231 deletions

View File

@ -79,6 +79,7 @@
\li QRhiRenderBuffer
\li QRhiTexture
\li QRhiSampler
\li QRhiShadingRateMap
\li QRhiTextureRenderTarget
\li QRhiShaderResourceBindings
\li QRhiGraphicsPipeline

View File

@ -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<MTLRasterizationRateMap>.
*/
/*!
\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<MTLRasterizationRateMap>. 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<QSize> QRhi::supportedShadingRates(int sampleCount) const
{
return d->supportedShadingRates(sampleCount);
}
Q_CONSTINIT static QBasicAtomicInteger<QRhiGlobalObjectIdGenerator::Type> counter = Q_BASIC_ATOMIC_INITIALIZER(0);
QRhiGlobalObjectIdGenerator::Type QRhiGlobalObjectIdGenerator::newId()

View File

@ -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<QRhiColorAttachment, 8> 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<QSize> supportedShadingRates(int sampleCount) const;
protected:
QRhi();

View File

@ -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<int> supportedSampleCounts() const = 0;
virtual int ubufAlignment() const = 0;
virtual QList<QSize> 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,

View File

@ -478,6 +478,12 @@ QList<int> QRhiD3D11::supportedSampleCounts() const
return { 1, 2, 4, 8 };
}
QList<QSize> 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:

View File

@ -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<int> supportedSampleCounts() const override;
QList<QSize> supportedShadingRates(int sampleCount) const override;
int ubufAlignment() const override;
bool isYUpInFramebuffer() const override;
bool isYUpInNDC() const override;

View File

@ -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<int> QRhiD3D12::supportedSampleCounts() const
return { 1, 2, 4, 8 };
}
QList<QSize> QRhiD3D12::supportedShadingRates(int sampleCount) const
{
QList<QSize> 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<void **>(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);

View File

@ -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<quint32> 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<QD3D12ObjectHandle, D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> currentVertexBuffers;
std::array<quint32, D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> 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<int> supportedSampleCounts() const override;
QList<QSize> 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<QD3D12Readback, 4> 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;
};

View File

@ -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<int> QRhiGles2::supportedSampleCounts() const
return supportedSampleCountList;
}
QList<QSize> 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)
{

View File

@ -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<int> supportedSampleCounts() const override;
QList<QSize> supportedShadingRates(int sampleCount) const override;
int ubufAlignment() const override;
bool isYUpInFramebuffer() const override;
bool isYUpInNDC() const override;

View File

@ -176,7 +176,8 @@ struct QRhiMetalData
MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
const QColor &colorClearValue,
const QRhiDepthStencilClearValue &depthStencilClearValue,
int colorAttCount);
int colorAttCount,
QRhiShadingRateMap *shadingRateMap);
id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
QString *error, QByteArray *entryPoint, QShaderKey *activeKey);
id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> 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<MTLComputePipelineState> pipelineState;
} computePipeline;
struct {
id<MTLRasterizationRateMap> rateMap;
} shadingRateMap;
};
};
QVector<DeferredReleaseEntry> releaseQueue;
@ -306,6 +311,11 @@ struct QMetalSamplerData
id<MTLSamplerState> samplerState = nil;
};
struct QMetalShadingRateMapData
{
id<MTLRasterizationRateMap> 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<int> QRhiMetal::supportedSampleCounts() const
return caps.supportedSampleCounts;
}
QVector<QSize> 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<MTLComputeCommandEncoder> 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<QMetalTexture, QMetalRenderBuffer>(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<MTLRasterizationRateMap>) (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);

View File

@ -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<quint32> 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<int> supportedSampleCounts() const override;
QList<QSize> 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;

View File

@ -60,6 +60,12 @@ QList<int> QRhiNull::supportedSampleCounts() const
return { 1 };
}
QList<QSize> 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)
{

View File

@ -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<int> supportedSampleCounts() const override;
QList<QSize> supportedShadingRates(int sampleCount) const override;
int ubufAlignment() const override;
bool isYUpInFramebuffer() const override;
bool isYUpInNDC() const override;

File diff suppressed because it is too large Load Diff

View File

@ -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<VkSubpassDependency, 2> subpassDeps;
bool hasDepthStencil = false;
bool hasDepthStencilResolve = false;
bool hasShadingRateMap = false;
uint32_t multiViewCount = 0;
VkAttachmentReference dsRef;
VkAttachmentReference dsResolveRef;
VkAttachmentReference shadingRateRef;
QVector<quint32> 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<VkCommandBuffer, 4> activeSecondaryCbStack;
bool inExternal;
bool hasShadingRateSet;
struct {
QHash<QRhiResource *, QPair<VkAccessFlags, bool> > 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<int> supportedSampleCounts() const override;
QList<QSize> 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<DeferredReleaseEntry> releaseQueue;
#ifdef VK_KHR_fragment_shading_rate
QVarLengthArray<VkPhysicalDeviceFragmentShadingRateKHR, 8> fragmentShadingRates;
PFN_vkCmdSetFragmentShadingRateKHR vkCmdSetFragmentShadingRateKHR = nullptr;
#endif
};
Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_RELOCATABLE_TYPE);

View File

@ -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()

View File

@ -18,7 +18,7 @@
#include <rhi/qrhi.h>
#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)

View File

@ -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

View File

@ -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 <QtCore/qfile.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qevent.h>
@ -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<int, 8> texturesNeedUpdate;
for (int i = 0; i < m_textures.count(); ++i) {
Texture &t(m_textures[i]);
QVarLengthArray<void *, 8> 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<ImGuiContext *>(context));
rebuildFontAtlas();
ImGuiIO &io(ImGui::GetIO());
io.GetClipboardTextFn = getClipboardText;
@ -273,22 +312,40 @@ QRhiImgui::QRhiImgui()
QRhiImgui::~QRhiImgui()
{
ImGui::DestroyContext();
ImGui::DestroyContext(static_cast<ImGuiContext *>(context));
}
void QRhiImgui::rebuildFontAtlas()
{
ImGui::SetCurrentContext(static_cast<ImGuiContext *>(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<const uchar *>(pixels), w, h, QImage::Format_RGBA8888);
sf.fontTextureData = wrapperImg.copy();
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(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<ImGuiContext *>(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<ImGuiContext *>(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<qintptr>(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<ImGuiContext *>(context));
ImGuiIO &io(ImGui::GetIO());
switch (event->type()) {

View File

@ -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 <rhi/qrhi.h>
@ -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<QRhiBuffer> m_ubuf;
std::unique_ptr<QRhiGraphicsPipeline> m_ps;
QVector<quint32> m_renderPassFormat;
std::unique_ptr<QRhiSampler> m_sampler;
std::unique_ptr<QRhiSampler> m_linearSampler;
std::unique_ptr<QRhiSampler> m_nearestSampler;
struct Texture {
QImage image;
QRhiTexture *tex = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiSampler::Filter filter = QRhiSampler::Linear;
bool ownTex = true;
};
QVector<Texture> m_textures;
QHash<void *, Texture> 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;

View File

@ -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)

View File

@ -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 <QPainter>
#include <QRandomGenerator>
#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<QRhiResource *> 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<int, QList<QSize>> 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<float> tx;
QVector<float> ty;
QVector<float> 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();
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <Metal/Metal.h>
#include <rhi/qrhi.h>
void *makeRateMap(QRhi *rhi, const QSize &outputSizeInPixels)
{
// note that multiview needs two layers, this example only uses one
MTLDevice *dev = static_cast<const QRhiMetalNativeHandles *>(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<MTLRasterizationRateMap> rateMap = [dev newRasterizationRateMapWithDescriptor: descriptor];
return rateMap;
}
void releaseRateMap(void *map)
{
id<MTLRasterizationRateMap> rateMap = (id<MTLRasterizationRateMap>) map;
[rateMap release];
}