rhi: metal: Add support for tessellation

Change-Id: Ie8d226a6a959aa5e78284ea72505fd26aec1e671
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2022-08-18 12:43:23 +02:00
parent 855a9ca217
commit c681c7c23f
37 changed files with 2268 additions and 258 deletions

View File

@ -677,16 +677,17 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
can be set via
\l{QRhiGraphicsPipeline::setPatchControlPointCount()}{setPatchControlPointCount()},
and shaders for tessellation control and evaluation can be specified in the
QRhiShaderStage list. Tessellation is considered an experimental feature in
QRhi and can only be expected to be supported with Vulkan, OpenGL (ES), and
Direct 3D, assuming the implementation reports it as supported at run time.
Tessellation shaders have portability issues between APIs (for example,
translating GLSL/SPIR-V to HLSL is problematic due to the way hull shaders
are structured, whereas Metal uses a somewhat different tessellation
pipeline than others), and therefore no guarantees can be given for a
universal solution for now. (for Direct 3D in particular, handwritten HLSL
hull and domain shaders must be injected into each QShader since qsb cannot
generate these from SPIR-V)
QRhiShaderStage list. Tessellation shaders have portability issues between
APIs (for example, translating GLSL/SPIR-V to HLSL is problematic due to
the way hull shaders are structured, whereas Metal uses a somewhat
different tessellation pipeline than others), and therefore unexpected
issues may still arise, even though basic functionality is implemented
across all the underlying APIs. For Direct 3D in particular, handwritten
HLSL hull and domain shaders must be injected into each QShader for the
tessellation control and evaluation stages, respectively, since qsb cannot
generate these from SPIR-V. Note that isoline tessellation should be
avoided as it will not be supported by all backends. The maximum patch
control point count portable between backends is 32.
\value GeometryShader Indicates that the geometry shader stage is
supported. When supported, a geometry shader can be specified in the
@ -695,9 +696,9 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
Direct 3D, OpenGL (3.2+) and OpenGL ES (3.2+), assuming the implementation
reports it as supported at run time. Geometry shaders have portability
issues between APIs, and therefore no guarantees can be given for a
universal solution for now. (for Direct 3D in particular, a handwritten
HLSL geometry shader must be injected into each QShader since qsb cannot
generate this from SPIR-V)
universal solution. They will never be supported with Metal. Whereas with
Direct 3D a handwritten HLSL geometry shader must be injected into each
QShader for the geometry stage since qsb cannot generate this from SPIR-V.
\value TextureArrayRange Indicates that for
\l{QRhi::newTextureArray()}{texture arrays} it is possible to specify a
@ -1403,6 +1404,85 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputAttribute &a)
}
#endif
QRhiVertexInputAttribute::Format QRhiImplementation::shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const
{
switch (type) {
case QShaderDescription::Vec4:
return QRhiVertexInputAttribute::Float4;
case QShaderDescription::Vec3:
return QRhiVertexInputAttribute::Float3;
case QShaderDescription::Vec2:
return QRhiVertexInputAttribute::Float2;
case QShaderDescription::Float:
return QRhiVertexInputAttribute::Float;
case QShaderDescription::Int4:
return QRhiVertexInputAttribute::SInt4;
case QShaderDescription::Int3:
return QRhiVertexInputAttribute::SInt3;
case QShaderDescription::Int2:
return QRhiVertexInputAttribute::SInt2;
case QShaderDescription::Int:
return QRhiVertexInputAttribute::SInt;
case QShaderDescription::Uint4:
return QRhiVertexInputAttribute::UInt4;
case QShaderDescription::Uint3:
return QRhiVertexInputAttribute::UInt3;
case QShaderDescription::Uint2:
return QRhiVertexInputAttribute::UInt2;
case QShaderDescription::Uint:
return QRhiVertexInputAttribute::UInt;
default:
Q_UNREACHABLE();
return QRhiVertexInputAttribute::Float;
}
}
quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const
{
switch (format) {
case QRhiVertexInputAttribute::Float4:
return 4 * sizeof(float);
case QRhiVertexInputAttribute::Float3:
return 4 * sizeof(float); // vec3 still takes 16 bytes
case QRhiVertexInputAttribute::Float2:
return 2 * sizeof(float);
case QRhiVertexInputAttribute::Float:
return sizeof(float);
case QRhiVertexInputAttribute::UNormByte4:
return 4 * sizeof(quint8);
case QRhiVertexInputAttribute::UNormByte2:
return 2 * sizeof(quint8);
case QRhiVertexInputAttribute::UNormByte:
return sizeof(quint8);
case QRhiVertexInputAttribute::UInt4:
return 4 * sizeof(quint32);
case QRhiVertexInputAttribute::UInt3:
return 4 * sizeof(quint32); // ivec3 still takes 16 bytes
case QRhiVertexInputAttribute::UInt2:
return 2 * sizeof(quint32);
case QRhiVertexInputAttribute::UInt:
return sizeof(quint32);
case QRhiVertexInputAttribute::SInt4:
return 4 * sizeof(qint32);
case QRhiVertexInputAttribute::SInt3:
return 4 * sizeof(qint32); // uvec3 still takes 16 bytes
case QRhiVertexInputAttribute::SInt2:
return 2 * sizeof(qint32);
case QRhiVertexInputAttribute::SInt:
return sizeof(qint32);
default:
Q_UNREACHABLE();
return 1;
}
}
/*!
\class QRhiVertexInputLayout
\internal

View File

@ -217,6 +217,9 @@ public:
return accumulatedPipelineCreationTime;
}
QRhiVertexInputAttribute::Format shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const;
quint32 byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const;
QRhi *q;
static const int MAX_SHADER_CACHE_ENTRIES = 128;

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,9 @@ struct QMetalBuffer : public QRhiBuffer
int lastActiveFrameSlot = -1;
friend class QRhiMetal;
friend struct QMetalShaderResourceBindings;
static constexpr int WorkBufPoolUsage = 1 << 8;
static_assert(WorkBufPoolUsage > QRhiBuffer::StorageBuffer);
};
struct QMetalRenderBufferData;
@ -204,6 +207,7 @@ struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings
};
struct QMetalGraphicsPipelineData;
struct QMetalCommandBuffer;
struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
{
@ -212,6 +216,13 @@ struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
void destroy() override;
bool create() override;
void makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD);
void setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD);
void setupMetalDepthStencilDescriptor(void *metalDsDesc);
void mapStates();
bool createVertexFragmentPipeline();
bool createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag);
QMetalGraphicsPipelineData *d;
uint generation = 0;
int lastActiveFrameSlot = -1;
@ -256,14 +267,14 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer
QRhiRenderTarget *currentTarget;
// per-pass (render or compute command encoder) volatile (cached) state
QRhiGraphicsPipeline *currentGraphicsPipeline;
QRhiComputePipeline *currentComputePipeline;
QMetalGraphicsPipeline *currentGraphicsPipeline;
QMetalComputePipeline *currentComputePipeline;
uint currentPipelineGeneration;
QRhiShaderResourceBindings *currentGraphicsSrb;
QRhiShaderResourceBindings *currentComputeSrb;
QMetalShaderResourceBindings *currentGraphicsSrb;
QMetalShaderResourceBindings *currentComputeSrb;
uint currentSrbGeneration;
int currentResSlot;
QRhiBuffer *currentIndexBuffer;
QMetalBuffer *currentIndexBuffer;
quint32 currentIndexOffset;
QRhiCommandBuffer::IndexFormat currentIndexFormat;
int currentCullMode;
@ -442,6 +453,33 @@ public:
bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]);
int effectiveSampleCount(int sampleCount) const;
struct TessDrawArgs {
QMetalCommandBuffer *cbD;
enum {
NonIndexed,
U16Indexed,
U32Indexed
} type;
struct NonIndexedArgs {
quint32 vertexCount;
quint32 instanceCount;
quint32 firstVertex;
quint32 firstInstance;
};
struct IndexedArgs {
quint32 indexCount;
quint32 instanceCount;
quint32 firstIndex;
qint32 vertexOffset;
quint32 firstInstance;
void *indexBuffer;
};
union {
NonIndexedArgs draw;
IndexedArgs drawIndexed;
};
};
void tessellatedDraw(const TessDrawArgs &args);
bool importedDevice = false;
bool importedCmdQueue = false;

View File

@ -167,7 +167,31 @@ QT_BEGIN_NAMESPACE
Describes what kind of shader code an entry contains.
\value StandardShader The normal, unmodified version of the shader code.
\value BatchableVertexShader Vertex shader rewritten to be suitable for Qt Quick scenegraph batching.
\value UInt16IndexedVertexAsComputeShader A vertex shader meant to be used
in a Metal pipeline with tessellation in combination with indexed draw
calls sourcing index data from a uint16 index buffer. To support the Metal
tessellation pipeline, the vertex shader is translated to a compute shader
that may be dependent on the index buffer usage in the draw calls (e.g. if
the shader is using gl_VertexIndex), hence the need for three dedicated
variants.
\value UInt32IndexedVertexAsComputeShader A vertex shader meant to be used
in a Metal pipeline with tessellation in combination with indexed draw
calls sourcing index data from a uint32 index buffer. To support the Metal
tessellation pipeline, the vertex shader is translated to a compute shader
that may be dependent on the index buffer usage in the draw calls (e.g. if
the shader is using gl_VertexIndex), hence the need for three dedicated
variants.
\value NonIndexedVertexAsComputeShader A vertex shader meant to be used in
a Metal pipeline with tessellation in combination with non-indexed draw
calls. To support the Metal tessellation pipeline, the vertex shader is
translated to a compute shader that may be dependent on the index buffer
usage in the draw calls (e.g. if the shader is using gl_VertexIndex), hence
the need for three dedicated variants.
*/
/*!
@ -367,6 +391,19 @@ QByteArray QShader::serialized() const
ds << listIt->samplerBinding;
}
}
ds << int(d->nativeShaderInfoMap.count());
for (auto it = d->nativeShaderInfoMap.cbegin(), itEnd = d->nativeShaderInfoMap.cend(); it != itEnd; ++it) {
const QShaderKey &k(it.key());
writeShaderKey(&ds, k);
ds << it->flags;
ds << int(it->extraBufferBindings.count());
for (auto mapIt = it->extraBufferBindings.cbegin(), mapItEnd = it->extraBufferBindings.cend();
mapIt != mapItEnd; ++mapIt)
{
ds << mapIt.key();
ds << mapIt.value();
}
}
return qCompress(buf.buffer());
}
@ -407,6 +444,7 @@ QShader QShader::fromSerialized(const QByteArray &data)
ds >> intVal;
d->qsbVersion = intVal;
if (d->qsbVersion != QShaderPrivate::QSB_VERSION
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITH_CBOR
@ -484,6 +522,26 @@ QShader QShader::fromSerialized(const QByteArray &data)
}
}
if (d->qsbVersion > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
ds >> count;
for (int i = 0; i < count; ++i) {
QShaderKey k;
readShaderKey(&ds, &k);
int flags;
ds >> flags;
QMap<int, int> extraBufferBindings;
int mapSize;
ds >> mapSize;
for (int b = 0; b < mapSize; ++b) {
int k, v;
ds >> k;
ds >> v;
extraBufferBindings.insert(k, v);
}
d->nativeShaderInfoMap.insert(k, { flags, extraBufferBindings });
}
}
return bs;
}
@ -711,7 +769,7 @@ QDebug operator<<(QDebug dbg, const QShaderVersion &v)
/*!
\typedef QShader::NativeResourceBindingMap
Synonym for QHash<int, QPair<int, int>>.
Synonym for QMap<int, QPair<int, int>>.
The resource binding model QRhi assumes is based on SPIR-V. This means that
uniform buffers, storage buffers, combined image samplers, and storage
@ -839,4 +897,62 @@ void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &
d->combinedImageMap.erase(it);
}
/*!
\struct QShader::NativeShaderInfo
Describes information about the native shader code, if applicable. This
becomes relevant with certain shader languages for certain shader stages,
in case the translation from SPIR-V involves the introduction of
additional, "magic" inputs, outputs, or resources in the generated shader.
Such additions may be dependent on the original source code (i.e. the usage
of various GLSL language constructs or built-ins), and therefore it needs
to be indicated in a dynamic manner if certain features got added to the
generated shader code.
As an example, consider a tessellation control shader with a per-patch (not
per-vertex) output variable. This is translated to a Metal compute shader
outputting (among others) into an spvPatchOut buffer. But this buffer would
not be present at all if per-patch output variables were not used. The fact
that the shader code relies on such a buffer present can be indicated by
the data in this struct.
*/
/*!
\return the native shader info struct for \a key, or an empty object if
there is no data available for \a key, for example because such a mapping
is not applicable for the shading language or the shader stage.
*/
QShader::NativeShaderInfo QShader::nativeShaderInfo(const QShaderKey &key) const
{
auto it = d->nativeShaderInfoMap.constFind(key);
if (it == d->nativeShaderInfoMap.cend())
return {};
return it.value();
}
/*!
Stores the given native shader \a info associated with \a key.
\sa nativeShaderInfo()
*/
void QShader::setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info)
{
detach();
d->nativeShaderInfoMap[key] = info;
}
/*!
Removes the native shader information for \a key.
*/
void QShader::removeNativeShaderInfo(const QShaderKey &key)
{
auto it = d->nativeShaderInfoMap.find(key);
if (it == d->nativeShaderInfoMap.end())
return;
detach();
d->nativeShaderInfoMap.erase(it);
}
QT_END_NAMESPACE

View File

@ -17,6 +17,7 @@
#include <QtGui/qtguiglobal.h>
#include <QtCore/qhash.h>
#include <QtCore/qmap.h>
#include <private/qshaderdescription_p.h>
QT_BEGIN_NAMESPACE
@ -102,7 +103,10 @@ public:
enum Variant {
StandardShader = 0,
BatchableVertexShader
BatchableVertexShader,
UInt16IndexedVertexAsComputeShader,
UInt32IndexedVertexAsComputeShader,
NonIndexedVertexAsComputeShader
};
QShader();
@ -127,7 +131,7 @@ public:
QByteArray serialized() const;
static QShader fromSerialized(const QByteArray &data);
using NativeResourceBindingMap = QHash<int, QPair<int, int> >; // binding -> native_binding[, native_binding]
using NativeResourceBindingMap = QMap<int, QPair<int, int> >; // binding -> native_binding[, native_binding]
NativeResourceBindingMap nativeResourceBindingMap(const QShaderKey &key) const;
void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map);
void removeResourceBindingMap(const QShaderKey &key);
@ -143,6 +147,14 @@ public:
const SeparateToCombinedImageSamplerMappingList &list);
void removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key);
struct NativeShaderInfo {
int flags = 0;
QMap<int, int> extraBufferBindings;
};
NativeShaderInfo nativeShaderInfo(const QShaderKey &key) const;
void setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info);
void removeNativeShaderInfo(const QShaderKey &key);
private:
QShaderPrivate *d;
friend struct QShaderPrivate;

View File

@ -24,13 +24,23 @@ QT_BEGIN_NAMESPACE
struct Q_GUI_EXPORT QShaderPrivate
{
static const int QSB_VERSION = 6;
static const int QSB_VERSION = 7;
static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6;
static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5;
static const int QSB_VERSION_WITHOUT_VAR_ARRAYDIMS = 4;
static const int QSB_VERSION_WITH_CBOR = 3;
static const int QSB_VERSION_WITH_BINARY_JSON = 2;
static const int QSB_VERSION_WITHOUT_BINDINGS = 1;
enum MslNativeShaderInfoExtraBufferBindings {
MslTessVertIndicesBufferBinding = 0,
MslTessVertTescOutputBufferBinding,
MslTessTescTessLevelBufferBinding,
MslTessTescPatchOutputBufferBinding,
MslTessTescParamsBufferBinding,
MslTessTescInputBufferBinding
};
QShaderPrivate()
: ref(1)
{
@ -43,7 +53,8 @@ struct Q_GUI_EXPORT QShaderPrivate
desc(other.desc),
shaders(other.shaders),
bindings(other.bindings),
combinedImageMap(other.combinedImageMap)
combinedImageMap(other.combinedImageMap),
nativeShaderInfoMap(other.nativeShaderInfoMap)
{
}
@ -58,6 +69,7 @@ struct Q_GUI_EXPORT QShaderPrivate
QMap<QShaderKey, QShaderCode> shaders;
QMap<QShaderKey, QShader::NativeResourceBindingMap> bindings;
QMap<QShaderKey, QShader::SeparateToCombinedImageSamplerMappingList> combinedImageMap;
QMap<QShaderKey, QShader::NativeShaderInfo> nativeShaderInfoMap;
};
QT_END_NAMESPACE

View File

@ -302,7 +302,8 @@ bool QShaderDescription::isValid() const
return !d->inVars.isEmpty() || !d->outVars.isEmpty()
|| !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty()
|| !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty()
|| !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty();
|| !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty()
|| !d->inBuiltins.isEmpty() || !d->outBuiltins.isEmpty();
}
/*!
@ -514,7 +515,26 @@ QList<QShaderDescription::InOutVariable> QShaderDescription::storageImages() con
}
/*!
Returns the local size of a compute shader.
\return the list of active builtins used as input. For example, a
tessellation evaluation shader reading the value of gl_TessCoord and
gl_Position will have TessCoordBuiltin and PositionBuiltin listed here.
*/
QVector<QShaderDescription::BuiltinVariable> QShaderDescription::inputBuiltinVariables() const
{
return d->inBuiltins;
}
/*!
\return the list of active built-in variables used as input. For example, a
vertex shader will very often have PositionBuiltin as an output built-in.
*/
QVector<QShaderDescription::BuiltinVariable> QShaderDescription::outputBuiltinVariables() const
{
return d->outBuiltins;
}
/*!
\return the local size of a compute shader.
For example, for a compute shader with the following declaration the
function returns { 256, 16, 1}.
@ -528,6 +548,101 @@ std::array<uint, 3> QShaderDescription::computeShaderLocalSize() const
return d->localSize;
}
/*!
\return the number of output vertices.
For example, for a tessellation control shader with the following
declaration the function returns 3.
\badcode
layout(vertices = 3) out;
\endcode
*/
uint QShaderDescription::tessellationOutputVertexCount() const
{
return d->tessOutVertCount;
}
/*!
\enum QShaderDescription::TessellationMode
\value UnknownTessellationMode
\value TrianglesTessellationMode
\value QuadTessellationMode
\value IsolinesTessellationMode
*/
/*!
\return the tessellation execution mode for a tessellation control or
evaluation shader.
When not set, the returned value is UnknownTessellationMode.
For example, for a tessellation evaluation shader with the following
declaration the function returns TrianglesTessellationMode.
\badcode
layout(triangles) in;
\endcode
*/
QShaderDescription::TessellationMode QShaderDescription::tessellationMode() const
{
return d->tessMode;
}
/*!
\enum QShaderDescription::TessellationWindingOrder
\value UnknownTessellationWindingOrder
\value CwTessellationWindingOrder
\value CcwTessellationWindingOrder
*/
/*!
\return the tessellation winding order for a tessellation control or
evaluation shader.
When not set, the returned value is UnknownTessellationWindingOrder.
For example, for a tessellation evaluation shader with the following
declaration the function returns CcwTessellationWindingOrder.
\badcode
layout(triangles, fractional_odd_spacing, ccw) in;
\endcode
*/
QShaderDescription::TessellationWindingOrder QShaderDescription::tessellationWindingOrder() const
{
return d->tessWind;
}
/*!
\enum QShaderDescription::TessellationPartitioning
\value UnknownTessellationPartitioning
\value EqualTessellationPartitioning
\value FractionalEvenTessellationPartitioning
\value FractionalOddTessellationPartitioning
*/
/*!
\return the tessellation partitioning mode for a tessellation control or
evaluation shader.
When not set, the returned value is UnknownTessellationPartitioning.
For example, for a tessellation evaluation shader with the following
declaration the function returns FractionalOddTessellationPartitioning.
\badcode
layout(triangles, fractional_odd_spacing, ccw) in;
\endcode
*/
QShaderDescription::TessellationPartitioning QShaderDescription::tessellationPartitioning() const
{
return d->tessPart;
}
static const struct TypeTab {
const char k[20];
QShaderDescription::VariableType v;
@ -607,7 +722,7 @@ static const struct TypeTab {
{ "imageBuffer", QShaderDescription::ImageBuffer }
};
static QLatin1StringView typeStr(const QShaderDescription::VariableType &t)
static QLatin1StringView typeStr(QShaderDescription::VariableType t)
{
for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
if (typeTab[i].v == t)
@ -662,7 +777,7 @@ static const struct ImageFormatTab {
{ "r8ui", QShaderDescription::ImageFormatR8ui }
};
static QLatin1StringView imageFormatStr(const QShaderDescription::ImageFormat &f)
static QLatin1StringView imageFormatStr(QShaderDescription::ImageFormat f)
{
for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
if (imageFormatTab[i].v == f)
@ -671,6 +786,106 @@ static QLatin1StringView imageFormatStr(const QShaderDescription::ImageFormat &f
return {};
}
static const struct BuiltinTypeTab {
const char k[21];
QShaderDescription::BuiltinType v;
} builtinTypeTab[] = {
{ "Position", QShaderDescription::PositionBuiltin },
{ "PointSize", QShaderDescription::PointSizeBuiltin },
{ "ClipDistance", QShaderDescription::ClipDistanceBuiltin },
{ "CullDistance", QShaderDescription::CullDistanceBuiltin },
{ "VertexId", QShaderDescription::VertexIdBuiltin },
{ "InstanceId", QShaderDescription::InstanceIdBuiltin },
{ "PrimitiveId", QShaderDescription::PrimitiveIdBuiltin },
{ "InvocationId", QShaderDescription::InvocationIdBuiltin },
{ "Layer", QShaderDescription::LayerBuiltin },
{ "ViewportIndex", QShaderDescription::ViewportIndexBuiltin },
{ "TessLevelOuter", QShaderDescription::TessLevelOuterBuiltin },
{ "TessLevelInner", QShaderDescription::TessLevelInnerBuiltin },
{ "TessCoord", QShaderDescription::TessCoordBuiltin },
{ "PatchVertices", QShaderDescription::PatchVerticesBuiltin },
{ "FragCoord", QShaderDescription::FragCoordBuiltin },
{ "PointCoord", QShaderDescription::PointCoordBuiltin },
{ "FrontFacing", QShaderDescription::FrontFacingBuiltin },
{ "SampleId", QShaderDescription::SampleIdBuiltin },
{ "SamplePosition", QShaderDescription::SamplePositionBuiltin },
{ "SampleMask", QShaderDescription::SampleMaskBuiltin },
{ "FragDepth", QShaderDescription::FragDepthBuiltin },
{ "NumWorkGroups", QShaderDescription::NumWorkGroupsBuiltin },
{ "WorkgroupSize", QShaderDescription::WorkgroupSizeBuiltin },
{ "WorkgroupId", QShaderDescription::WorkgroupIdBuiltin },
{ "LocalInvocationId", QShaderDescription::LocalInvocationIdBuiltin },
{ "GlobalInvocationId", QShaderDescription::GlobalInvocationIdBuiltin },
{ "LocalInvocationIndex", QShaderDescription::LocalInvocationIndexBuiltin },
{ "VertexIndex", QShaderDescription::VertexIndexBuiltin },
{ "InstanceIndex", QShaderDescription::InstanceIndexBuiltin }
};
static QLatin1StringView builtinTypeStr(QShaderDescription::BuiltinType t)
{
for (size_t i = 0; i < sizeof(builtinTypeTab) / sizeof(BuiltinTypeTab); ++i) {
if (builtinTypeTab[i].v == t)
return QLatin1StringView(builtinTypeTab[i].k);
}
return {};
}
static const struct TessellationModeTab {
const char k[10];
QShaderDescription::TessellationMode v;
} tessellationModeTab[] {
{ "unknown", QShaderDescription::UnknownTessellationMode },
{ "triangles", QShaderDescription::TrianglesTessellationMode },
{ "quad", QShaderDescription::QuadTessellationMode },
{ "isoline", QShaderDescription::IsolineTessellationMode }
};
static QLatin1StringView tessModeStr(QShaderDescription::TessellationMode mode)
{
for (size_t i = 0; i < sizeof(tessellationModeTab) / sizeof(TessellationModeTab); ++i) {
if (tessellationModeTab[i].v == mode)
return QLatin1StringView(tessellationModeTab[i].k);
}
return {};
}
static const struct TessellationWindingOrderTab {
const char k[8];
QShaderDescription::TessellationWindingOrder v;
} tessellationWindingOrderTab[] {
{ "unknown", QShaderDescription::UnknownTessellationWindingOrder },
{ "cw", QShaderDescription::CwTessellationWindingOrder },
{ "ccw", QShaderDescription::CcwTessellationWindingOrder }
};
static QLatin1StringView tessWindStr(QShaderDescription::TessellationWindingOrder w)
{
for (size_t i = 0; i < sizeof(tessellationWindingOrderTab) / sizeof(TessellationWindingOrderTab); ++i) {
if (tessellationWindingOrderTab[i].v == w)
return QLatin1StringView(tessellationWindingOrderTab[i].k);
}
return {};
}
static const struct TessellationPartitioningTab {
const char k[24];
QShaderDescription::TessellationPartitioning v;
} tessellationPartitioningTab[] {
{ "unknown", QShaderDescription::UnknownTessellationPartitioning },
{ "equal_spacing", QShaderDescription::EqualTessellationPartitioning },
{ "fractional_even_spacing", QShaderDescription::FractionalEvenTessellationPartitioning },
{ "fractional_odd_spacing", QShaderDescription::FractionalOddTessellationPartitioning }
};
static QLatin1StringView tessPartStr(QShaderDescription::TessellationPartitioning p)
{
for (size_t i = 0; i < sizeof(tessellationPartitioningTab) / sizeof(TessellationPartitioningTab); ++i) {
if (tessellationPartitioningTab[i].v == p)
return QLatin1StringView(tessellationPartitioningTab[i].k);
}
return {};
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
{
@ -688,6 +903,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
<< " storageImages " << d->storageImages
<< " separateImages " << d->separateImages
<< " separateSamplers " << d->separateSamplers
<< " inBuiltins " << d->inBuiltins
<< " outBuiltins " << d->outBuiltins
<< ')';
} else {
dbg.nospace() << "QShaderDescription(null)";
@ -700,6 +917,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "InOutVariable(" << typeStr(var.type) << ' ' << var.name;
if (var.perPatch)
dbg.nospace() << " per-patch";
if (var.location >= 0)
dbg.nospace() << " location=" << var.location;
if (var.binding >= 0)
@ -768,6 +987,13 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
dbg.nospace() << ' ' << blk.members << ')';
return dbg;
}
QDebug operator<<(QDebug dbg, const QShaderDescription::BuiltinVariable &builtin)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type) << ")";
return dbg;
}
#endif
#define JSON_KEY(key) static constexpr QLatin1StringView key ## Key() noexcept { return QLatin1StringView( #key ); }
@ -776,6 +1002,7 @@ JSON_KEY(type)
JSON_KEY(location)
JSON_KEY(binding)
JSON_KEY(set)
JSON_KEY(perPatch)
JSON_KEY(imageFormat)
JSON_KEY(imageFlags)
JSON_KEY(offset)
@ -797,7 +1024,13 @@ JSON_KEY(pushConstantBlocks)
JSON_KEY(storageBlocks)
JSON_KEY(combinedImageSamplers)
JSON_KEY(storageImages)
JSON_KEY(localSize)
JSON_KEY(inBuiltins)
JSON_KEY(outBuiltins)
JSON_KEY(computeLocalSize)
JSON_KEY(tessellationOutputVertexCount)
JSON_KEY(tessellationMode)
JSON_KEY(tessellationWindingOrder)
JSON_KEY(tessellationPartitioning)
JSON_KEY(separateImages)
JSON_KEY(separateSamplers)
#undef JSON_KEY
@ -810,6 +1043,8 @@ static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v
(*obj)[bindingKey()] = v.binding;
if (v.descriptorSet >= 0)
(*obj)[setKey()] = v.descriptorSet;
if (v.perPatch)
(*obj)[perPatchKey()] = v.perPatch;
if (v.imageFormat != QShaderDescription::ImageFormatUnknown)
(*obj)[imageFormatKey()] = imageFormatStr(v.imageFormat);
if (v.imageFlags)
@ -832,6 +1067,7 @@ static void serializeDecorations(QDataStream *stream, const QShaderDescription::
(*stream) << int(v.arrayDims.count());
for (int dim : v.arrayDims)
(*stream) << dim;
(*stream) << quint8(v.perPatch);
}
static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
@ -985,10 +1221,42 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc()
if (!jstorageImages.isEmpty())
root[storageImagesKey()] = jstorageImages;
QJsonArray jlocalSize;
for (int i = 0; i < 3; ++i)
jlocalSize.append(QJsonValue(int(localSize[i])));
root[localSizeKey()] = jlocalSize;
QJsonArray jinBuiltins;
for (const QShaderDescription::BuiltinVariable &v : qAsConst(inBuiltins)) {
QJsonObject builtin;
builtin[typeKey()] = builtinTypeStr(v.type);
jinBuiltins.append(builtin);
}
if (!jinBuiltins.isEmpty())
root[inBuiltinsKey()] = jinBuiltins;
QJsonArray joutBuiltins;
for (const QShaderDescription::BuiltinVariable &v : qAsConst(outBuiltins)) {
QJsonObject builtin;
builtin[typeKey()] = builtinTypeStr(v.type);
joutBuiltins.append(builtin);
}
if (!joutBuiltins.isEmpty())
root[outBuiltinsKey()] = joutBuiltins;
if (localSize[0] || localSize[1] || localSize[2]) {
QJsonArray jlocalSize;
for (size_t i = 0; i < 3; ++i)
jlocalSize.append(QJsonValue(int(localSize[i])));
root[computeLocalSizeKey()] = jlocalSize;
}
if (tessOutVertCount)
root[tessellationOutputVertexCountKey()] = int(tessOutVertCount);
if (tessMode != QShaderDescription::UnknownTessellationMode)
root[tessellationModeKey()] = tessModeStr(tessMode);
if (tessWind != QShaderDescription::UnknownTessellationWindingOrder)
root[tessellationWindingOrderKey()] = tessWindStr(tessWind);
if (tessPart != QShaderDescription::UnknownTessellationPartitioning)
root[tessellationPartitioningKey()] = tessPartStr(tessPart);
QJsonArray jseparateImages;
for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) {
@ -1073,7 +1341,7 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream)
}
for (size_t i = 0; i < 3; ++i)
(*stream) << localSize[i];
(*stream) << quint32(localSize[i]);
(*stream) << int(separateImages.count());
for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) {
@ -1088,6 +1356,19 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream)
(*stream) << int(v.type);
serializeDecorations(stream, v);
}
(*stream) << quint32(tessOutVertCount);
(*stream) << quint32(tessMode);
(*stream) << quint32(tessWind);
(*stream) << quint32(tessPart);
(*stream) << int(inBuiltins.count());
for (const QShaderDescription::BuiltinVariable &v : qAsConst(inBuiltins))
(*stream) << int(v.type);
(*stream) << int(outBuiltins.count());
for (const QShaderDescription::BuiltinVariable &v : qAsConst(outBuiltins))
(*stream) << int(v.type);
}
static void deserializeDecorations(QDataStream *stream, int version, QShaderDescription::InOutVariable *v)
@ -1107,6 +1388,12 @@ static void deserializeDecorations(QDataStream *stream, int version, QShaderDesc
for (int i = 0; i < f; ++i)
(*stream) >> v->arrayDims[i];
}
if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
quint8 b;
(*stream) >> b;
v->perPatch = b;
}
}
static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version)
@ -1237,8 +1524,11 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
deserializeDecorations(stream, version, &storageImages[i]);
}
for (size_t i = 0; i < 3; ++i)
(*stream) >> localSize[i];
for (size_t i = 0; i < 3; ++i) {
quint32 v;
(*stream) >> v;
localSize[i] = v;
}
if (version > QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS) {
(*stream) >> count;
@ -1265,6 +1555,34 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
deserializeDecorations(stream, version, &separateSamplers[i]);
}
}
if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
quint32 v;
(*stream) >> v;
tessOutVertCount = v;
(*stream) >> v;
tessMode = QShaderDescription::TessellationMode(v);
(*stream) >> v;
tessWind = QShaderDescription::TessellationWindingOrder(v);
(*stream) >> v;
tessPart = QShaderDescription::TessellationPartitioning(v);
(*stream) >> count;
inBuiltins.resize(count);
for (int i = 0; i < count; ++i) {
int t;
(*stream) >> t;
inBuiltins[i].type = QShaderDescription::BuiltinType(t);
}
(*stream) >> count;
outBuiltins.resize(count);
for (int i = 0; i < count; ++i) {
int t;
(*stream) >> t;
outBuiltins[i].type = QShaderDescription::BuiltinType(t);
}
}
}
/*!
@ -1287,7 +1605,13 @@ bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) no
&& lhs.d->separateImages == rhs.d->separateImages
&& lhs.d->separateSamplers == rhs.d->separateSamplers
&& lhs.d->storageImages == rhs.d->storageImages
&& lhs.d->localSize == rhs.d->localSize;
&& lhs.d->inBuiltins == rhs.d->inBuiltins
&& lhs.d->outBuiltins == rhs.d->outBuiltins
&& lhs.d->localSize == rhs.d->localSize
&& lhs.d->tessOutVertCount == rhs.d->tessOutVertCount
&& lhs.d->tessMode == rhs.d->tessMode
&& lhs.d->tessWind == rhs.d->tessWind
&& lhs.d->tessPart == rhs.d->tessPart;
}
/*!
@ -1305,7 +1629,8 @@ bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescr
&& lhs.descriptorSet == rhs.descriptorSet
&& lhs.imageFormat == rhs.imageFormat
&& lhs.imageFlags == rhs.imageFlags
&& lhs.arrayDims == rhs.arrayDims;
&& lhs.arrayDims == rhs.arrayDims
&& lhs.perPatch == rhs.perPatch;
}
/*!
@ -1372,4 +1697,15 @@ bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescri
&& lhs.members == rhs.members;
}
/*!
Returns \c true if the two BuiltinVariable objects \a lhs and \a rhs are
equal.
\relates QShaderDescription::BuiltinVariable
*/
bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
{
return lhs.type == rhs.type;
}
QT_END_NAMESPACE

View File

@ -181,6 +181,7 @@ public:
ImageFormat imageFormat = ImageFormatUnknown;
ImageFlags imageFlags;
QList<int> arrayDims;
bool perPatch = false;
};
struct BlockVariable {
@ -229,8 +230,76 @@ public:
QList<InOutVariable> separateSamplers() const;
QList<InOutVariable> storageImages() const;
enum BuiltinType {
// must match SpvBuiltIn
PositionBuiltin = 0,
PointSizeBuiltin = 1,
ClipDistanceBuiltin = 3,
CullDistanceBuiltin = 4,
VertexIdBuiltin = 5,
InstanceIdBuiltin = 6,
PrimitiveIdBuiltin = 7,
InvocationIdBuiltin = 8,
LayerBuiltin = 9,
ViewportIndexBuiltin = 10,
TessLevelOuterBuiltin = 11,
TessLevelInnerBuiltin = 12,
TessCoordBuiltin = 13,
PatchVerticesBuiltin = 14,
FragCoordBuiltin = 15,
PointCoordBuiltin = 16,
FrontFacingBuiltin = 17,
SampleIdBuiltin = 18,
SamplePositionBuiltin = 19,
SampleMaskBuiltin = 20,
FragDepthBuiltin = 22,
NumWorkGroupsBuiltin = 24,
WorkgroupSizeBuiltin = 25,
WorkgroupIdBuiltin = 26,
LocalInvocationIdBuiltin = 27,
GlobalInvocationIdBuiltin = 28,
LocalInvocationIndexBuiltin = 29,
VertexIndexBuiltin = 42,
InstanceIndexBuiltin = 43
};
struct BuiltinVariable {
BuiltinType type;
};
QList<BuiltinVariable> inputBuiltinVariables() const;
QList<BuiltinVariable> outputBuiltinVariables() const;
std::array<uint, 3> computeShaderLocalSize() const;
uint tessellationOutputVertexCount() const;
enum TessellationMode {
UnknownTessellationMode,
TrianglesTessellationMode,
QuadTessellationMode,
IsolineTessellationMode
};
TessellationMode tessellationMode() const;
enum TessellationWindingOrder {
UnknownTessellationWindingOrder,
CwTessellationWindingOrder,
CcwTessellationWindingOrder
};
TessellationWindingOrder tessellationWindingOrder() const;
enum TessellationPartitioning {
UnknownTessellationPartitioning,
EqualTessellationPartitioning,
FractionalEvenTessellationPartitioning,
FractionalOddTessellationPartitioning
};
TessellationPartitioning tessellationPartitioning() const;
private:
QShaderDescriptionPrivate *d;
friend struct QShaderDescriptionPrivate;
@ -249,6 +318,7 @@ Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BuiltinVariable &);
#endif
Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept;
@ -257,6 +327,7 @@ Q_GUI_EXPORT bool operator==(const QShaderDescription::BlockVariable &lhs, const
Q_GUI_EXPORT bool operator==(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept;
Q_GUI_EXPORT bool operator==(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept;
Q_GUI_EXPORT bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept;
Q_GUI_EXPORT bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept;
inline bool operator!=(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept
{
@ -288,6 +359,11 @@ inline bool operator!=(const QShaderDescription::StorageBlock &lhs, const QShade
return !(lhs == rhs);
}
inline bool operator!=(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
{
return !(lhs == rhs);
}
QT_END_NAMESPACE
#endif

View File

@ -27,7 +27,6 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
QShaderDescriptionPrivate()
: ref(1)
{
localSize[0] = localSize[1] = localSize[2] = 0;
}
QShaderDescriptionPrivate(const QShaderDescriptionPrivate &other)
@ -41,7 +40,13 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
separateImages(other.separateImages),
separateSamplers(other.separateSamplers),
storageImages(other.storageImages),
localSize(other.localSize)
inBuiltins(other.inBuiltins),
outBuiltins(other.outBuiltins),
localSize(other.localSize),
tessOutVertCount(other.tessOutVertCount),
tessMode(other.tessMode),
tessWind(other.tessWind),
tessPart(other.tessPart)
{
}
@ -62,7 +67,13 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate
QList<QShaderDescription::InOutVariable> separateImages;
QList<QShaderDescription::InOutVariable> separateSamplers;
QList<QShaderDescription::InOutVariable> storageImages;
std::array<uint, 3> localSize;
QList<QShaderDescription::BuiltinVariable> inBuiltins;
QList<QShaderDescription::BuiltinVariable> outBuiltins;
std::array<uint, 3> localSize = {};
uint tessOutVertCount = 0;
QShaderDescription::TessellationMode tessMode = QShaderDescription::UnknownTessellationMode;
QShaderDescription::TessellationWindingOrder tessWind = QShaderDescription::UnknownTessellationWindingOrder;
QShaderDescription::TessellationPartitioning tessPart = QShaderDescription::UnknownTessellationPartitioning;
};
QT_END_NAMESPACE

View File

@ -11,3 +11,7 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.frag.qsb textured_multiubuf.frag
qsb --glsl 320es,410 --msl 12 --msltess simpletess.vert -o simpletess.vert.qsb
qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletess.tesc.qsb
qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 simpletess.tese -o simpletess.tese.qsb
qsb --glsl 320es,410 --msl 12 simpletess.frag -o simpletess.frag.qsb

View File

@ -0,0 +1,10 @@
#version 440
layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 fragColor;
void main()
{
fragColor = vec4(v_color, 1.0);
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
#version 440
layout(vertices = 3) out;
layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor[];
layout(location = 1) patch out float a_per_patch_output_variable;
void main()
{
if (gl_InvocationID == 0) {
gl_TessLevelOuter[0] = 4.0;
gl_TessLevelOuter[1] = 4.0;
gl_TessLevelOuter[2] = 4.0;
gl_TessLevelInner[0] = 4.0;
}
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
outColor[gl_InvocationID] = inColor[gl_InvocationID];
a_per_patch_output_variable = 1.0;
}

Binary file not shown.

View File

@ -0,0 +1,17 @@
#version 440
layout(triangles, fractional_odd_spacing, ccw) in;
layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor;
layout(location = 1) patch in float a_per_patch_output_variable;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
};
void main()
{
gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2] * a_per_patch_output_variable;
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
#version 440
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 v_color;
void main()
{
gl_Position = vec4(position, 1.0);
v_color = color;
}

Binary file not shown.

View File

@ -136,6 +136,9 @@ private slots:
void renderToRgb10Texture_data();
void renderToRgb10Texture();
void tessellation_data();
void tessellation();
private:
void setWindowType(QWindow *window, QRhi::Implementation impl);
@ -4899,5 +4902,165 @@ void tst_QRhi::renderToRgb10Texture()
QVERIFY(redCount > blueCount); // 1742 > 178
}
void tst_QRhi::tessellation_data()
{
rhiTestData();
}
void tst_QRhi::tessellation()
{
QFETCH(QRhi::Implementation, impl);
QFETCH(QRhiInitParams *, initParams);
QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
if (!rhi)
QSKIP("QRhi could not be created, skipping testing rendering");
if (!rhi->isFeatureSupported(QRhi::Tessellation)) {
// From a Vulkan or Metal implementation we expect tessellation to work,
// even though it is optional (as per spec) for Vulkan.
QVERIFY(rhi->backend() != QRhi::Vulkan);
QVERIFY(rhi->backend() != QRhi::Metal);
QSKIP("Tessellation is not supported with this graphics API, skipping test");
}
if (rhi->backend() == QRhi::D3D11)
QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
QVERIFY(texture->create());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->create());
static const float triangleVertices[] = {
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
};
QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices)));
QVERIFY(vbuf->create());
u->uploadStaticBuffer(vbuf.data(), triangleVertices);
QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
QVERIFY(ubuf->create());
// Use the 3D API specific correction matrix that flips Y, so we can use
// the OpenGL-targeted vertex data and the tessellation winding order of
// counter-clockwise to get uniform results.
QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
});
QVERIFY(srb->create());
QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
pipeline->setTopology(QRhiGraphicsPipeline::Patches);
pipeline->setPatchControlPointCount(3);
pipeline->setShaderStages({
{ QRhiShaderStage::Vertex, loadShader(":/data/simpletess.vert.qsb") },
{ QRhiShaderStage::TessellationControl, loadShader(":/data/simpletess.tesc.qsb") },
{ QRhiShaderStage::TessellationEvaluation, loadShader(":/data/simpletess.tese.qsb") },
{ QRhiShaderStage::Fragment, loadShader(":/data/simpletess.frag.qsb") }
});
pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct
// won't get the wireframe with OpenGL ES
if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode))
pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 6 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) }
});
pipeline->setVertexInputLayout(inputLayout);
pipeline->setShaderResourceBindings(srb.data());
pipeline->setRenderPassDescriptor(rpDesc.data());
QVERIFY(pipeline->create());
QRhiCommandBuffer *cb = nullptr;
QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(pipeline.data());
cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
cb->setShaderResources();
QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(3);
QRhiReadbackResult readResult;
QImage result;
readResult.completed = [&readResult, &result] {
result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888);
};
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture({ texture.data() }, &readResult);
cb->endPass(readbackBatch);
rhi->endOffscreenFrame();
if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many other tests
result = std::move(result).mirrored();
QCOMPARE(result.size(), rt->pixelSize());
// cannot check rendering results with Null, because there is no rendering there
if (impl == QRhi::Null)
return;
int redCount = 0, greenCount = 0, blueCount = 0;
for (int y = 0; y < result.height(); ++y) {
const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
int x = result.width() - 1;
while (x-- >= 0) {
const QRgb c(*p++);
const int red = qRed(c);
const int green = qGreen(c);
const int blue = qBlue(c);
// just count the color components that are above a certain threshold
if (red > 240)
++redCount;
if (green > 240)
++greenCount;
if (blue > 240)
++blueCount;
}
}
// Line drawing can be different between the 3D APIs. What we will check if
// the number of strong-enough r/g/b components above a certain threshold.
// That is good enough to ensure that something got rendered, i.e. that
// tessellation is not completely broken.
//
// For the record the actual values are something like:
// OpenGL (NVIDIA, Windows) 59 82 82
// Metal (Intel, macOS 12.5) 59 79 79
// Vulkan (NVIDIA, Windows) 71 85 85
QVERIFY(redCount > 50);
QVERIFY(blueCount > 50);
QVERIFY(greenCount > 50);
}
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)

View File

@ -25,6 +25,7 @@ private slots:
void loadV4();
void manualShaderPackCreation();
void loadV6WithSeparateImagesAndSamplers();
void loadV7();
};
static QShader getShader(const QString &name)
@ -590,5 +591,87 @@ void tst_QShader::loadV6WithSeparateImagesAndSamplers()
}
}
void tst_QShader::loadV7()
{
QShader vert = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.vert.qsb"));
QVERIFY(vert.isValid());
QCOMPARE(QShaderPrivate::get(&vert)->qsbVersion, 7);
QCOMPARE(vert.availableShaders().count(), 8);
QCOMPARE(vert.description().inputVariables().count(), 2);
QCOMPARE(vert.description().outputBuiltinVariables().count(), 1);
QCOMPARE(vert.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(vert.description().outputVariables().count(), 1);
QCOMPARE(vert.description().outputVariables()[0].name, QByteArrayLiteral("v_color"));
QVERIFY(vert.availableShaders().contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader)).shader().isEmpty());
QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt16IndexedVertexAsComputeShader)).shader().isEmpty());
QVERIFY(!vert.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader)).shader().isEmpty());
QShader tesc = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tesc.qsb"));
QVERIFY(tesc.isValid());
QCOMPARE(QShaderPrivate::get(&tesc)->qsbVersion, 7);
QCOMPARE(tesc.availableShaders().count(), 5);
QCOMPARE(tesc.description().tessellationOutputVertexCount(), 3);
QCOMPARE(tesc.description().inputBuiltinVariables().count(), 2);
QCOMPARE(tesc.description().outputBuiltinVariables().count(), 3);
// builtins must be sorted based on the type
QCOMPARE(tesc.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(tesc.description().inputBuiltinVariables()[1].type, QShaderDescription::InvocationIdBuiltin);
QCOMPARE(tesc.description().outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(tesc.description().outputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
QCOMPARE(tesc.description().outputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
QCOMPARE(tesc.description().outputVariables().count(), 3);
for (const QShaderDescription::InOutVariable &v : tesc.description().outputVariables()) {
switch (v.location) {
case 0:
QCOMPARE(v.name, QByteArrayLiteral("outColor"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, false);
break;
case 1:
QCOMPARE(v.name, QByteArrayLiteral("stuff"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, true);
break;
case 2:
QCOMPARE(v.name, QByteArrayLiteral("more_stuff"));
QCOMPARE(v.type, QShaderDescription::Float);
QCOMPARE(v.perPatch, true);
break;
default:
QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location)));
break;
}
}
QVERIFY(!tesc.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().isEmpty());
QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::SpirvShader, QShaderVersion(100))).extraBufferBindings.count(), 0);
QCOMPARE(tesc.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12))).extraBufferBindings.count(), 5);
QShader tese = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.tese.qsb"));
QVERIFY(tese.isValid());
QCOMPARE(QShaderPrivate::get(&tese)->qsbVersion, 7);
QCOMPARE(tese.availableShaders().count(), 5);
QCOMPARE(tese.description().tessellationMode(), QShaderDescription::TrianglesTessellationMode);
QCOMPARE(tese.description().tessellationWindingOrder(), QShaderDescription::CcwTessellationWindingOrder);
QCOMPARE(tese.description().tessellationPartitioning(), QShaderDescription::FractionalOddTessellationPartitioning);
QCOMPARE(tese.description().inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(tese.description().inputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
QCOMPARE(tese.description().inputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
QCOMPARE(tese.description().inputBuiltinVariables()[3].type, QShaderDescription::TessCoordBuiltin);
QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).count(), 1);
QCOMPARE(tese.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).value(0), qMakePair(0, -1));
QShader frag = getShader(QLatin1String(":/data/metal_enabled_tessellation_v7.frag.qsb"));
QVERIFY(frag.isValid());
QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7);
}
#include <tst_qshader.moc>
QTEST_MAIN(tst_QShader)

View File

@ -1,6 +1,6 @@
qsb --glsl 320es,410 --hlsl 50 test.vert -o test.vert.qsb
qsb --glsl 320es,410 test.tesc -o test.tesc.qsb
qsb --glsl 320es,410 --hlsl 50 --msl 12 --msltess test.vert -o test.vert.qsb
qsb --glsl 320es,410 --msl 12 --tess-mode triangles test.tesc -o test.tesc.qsb
qsb -r hlsl,50,test_hull.hlsl test.tesc.qsb
qsb --glsl 320es,410 test.tese -o test.tese.qsb
qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 test.tese -o test.tese.qsb
qsb -r hlsl,50,test_domain.hlsl test.tese.qsb
qsb --glsl 320es,410 --hlsl 50 test.frag -o test.frag.qsb
qsb --glsl 320es,410 --hlsl 50 --msl 12 test.frag -o test.frag.qsb

View File

@ -9,9 +9,13 @@ static const float tri[] = {
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
};
static const bool INDEXED = false;
static const quint32 indices[] = { 0, 1, 2 };
struct {
QVector<QRhiResource *> releasePool;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ibuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
@ -29,6 +33,12 @@ void Window::customInit()
d.vbuf->create();
d.releasePool << d.vbuf;
if (INDEXED) {
d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indices));
d.ibuf->create();
d.releasePool << d.ibuf;
}
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4 + 4);
d.ubuf->create();
d.releasePool << d.ubuf;
@ -71,8 +81,12 @@ void Window::customInit()
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.initialUpdates->uploadStaticBuffer(d.vbuf, tri);
const float amplitude = 0.5f;
d.initialUpdates->updateDynamicBuffer(d.ubuf, 68, 4, &amplitude);
if (INDEXED)
d.initialUpdates->uploadStaticBuffer(d.ibuf, indices);
}
void Window::customRelease()
@ -96,14 +110,20 @@ void Window::customRender()
u->updateDynamicBuffer(d.ubuf, 0, 64, d.winProj.constData());
}
u->updateDynamicBuffer(d.ubuf, 64, 4, &d.time);
d.time += 0.1f;
d.time += 0.01f;
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.ps);
cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
cb->setShaderResources();
QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0);
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(3);
if (INDEXED) {
cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt32);
cb->drawIndexed(3);
} else {
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(3);
}
cb->endPass();
}

View File

@ -6,6 +6,10 @@ layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor[];
// these serve no purpose, just exist to test per-patch outputs
layout(location = 1) patch out vec3 stuff;
layout(location = 2) patch out float more_stuff;
void main()
{
if (gl_InvocationID == 0) {
@ -18,4 +22,6 @@ void main()
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
outColor[gl_InvocationID] = inColor[gl_InvocationID];
stuff = vec3(1.0);
more_stuff = 1.0;
}

View File

@ -6,6 +6,10 @@ layout(location = 0) in vec3 inColor[];
layout(location = 0) out vec3 outColor;
// these serve no purpose, just exist to test per-patch outputs
layout(location = 1) patch in vec3 stuff;
layout(location = 2) patch in float more_stuff;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float time;
@ -14,7 +18,10 @@ layout(std140, binding = 0) uniform buf {
void main()
{
gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
gl_Position.x += sin(time + gl_Position.y) * amplitude;
outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2];
vec4 pos = (gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position);
gl_Position = mvp * pos;
gl_Position.x += sin(time + pos.y) * amplitude;
outColor = gl_TessCoord.x * inColor[0] + gl_TessCoord.y * inColor[1] + gl_TessCoord.z * inColor[2]
// these are all 1.0, just here to exercise the shader generation and the runtime pipeline setup
* stuff.x * more_stuff * (gl_TessLevelOuter[0] / 4.0) * (gl_TessLevelInner[0] / 4.0);
}

View File

@ -30,7 +30,7 @@ PixelInput main(Input input, float3 uvwCoord : SV_DomainLocation, const OutputPa
float3 vertexPosition = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
output.position = mul(float4(vertexPosition, 1.0f), mvp);
output.position.x += sin(time + output.position.y) * amplitude;
output.position.x += sin(time + vertexPosition.y) * amplitude;
output.color = uvwCoord.x * patch[0].color + uvwCoord.y * patch[1].color + uvwCoord.z * patch[2].color;