Commit 2ec06299 authored by Laszlo Agocs's avatar Laszlo Agocs
Browse files

QBakedShader inner class refactor and docs

parent 208074dd
......@@ -2467,12 +2467,12 @@ static inline D3D11_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
static QByteArray compileHlslShaderSource(const QBakedShader &shader, QString *error)
{
QBakedShader::Shader dxbc = shader.shader({ QBakedShader::DxbcShader, 50 });
if (!dxbc.shader.isEmpty())
QBakedShaderCode dxbc = shader.shader({ QBakedShaderKey::DxbcShader, 50 });
if (!dxbc.shader().isEmpty())
return dxbc.shader;
QBakedShader::Shader hlslSource = shader.shader({ QBakedShader::HlslShader, 50 });
if (hlslSource.shader.isEmpty()) {
QBakedShaderCode hlslSource = shader.shader({ QBakedShaderKey::HlslShader, 50 });
if (hlslSource.shader().isEmpty()) {
qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader;
return QByteArray();
}
......@@ -2504,9 +2504,9 @@ static QByteArray compileHlslShaderSource(const QBakedShader &shader, QString *e
ID3DBlob *bytecode = nullptr;
ID3DBlob *errors = nullptr;
HRESULT hr = D3DCompile(hlslSource.shader.constData(), hlslSource.shader.size(),
HRESULT hr = D3DCompile(hlslSource.shader().constData(), hlslSource.shader().size(),
nullptr, nullptr, nullptr,
hlslSource.entryPoint.constData(), target, 0, 0, &bytecode, &errors);
hlslSource.entryPoint().constData(), target, 0, 0, &bytecode, &errors);
if (FAILED(hr) || !bytecode) {
qWarning("HLSL shader compilation failed: 0x%x", hr);
if (errors) {
......
......@@ -2075,14 +2075,14 @@ bool QGles2GraphicsPipeline::build()
continue;
GLuint shader = rhiD->f->glCreateShader(isVertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER);
QBakedShader::ShaderSourceVersion ver;
QBakedShaderVersion ver;
if (rhiD->ctx->isOpenGLES())
ver = { 100, QBakedShader::ShaderSourceVersion::GlslEs };
ver = { 100, QBakedShaderVersion::GlslEs };
else
ver = { 120 };
const QByteArray source = shaderStage.shader.shader({ QBakedShader::GlslShader, ver }).shader;
const QByteArray source = shaderStage.shader.shader({ QBakedShaderKey::GlslShader, ver }).shader();
if (source.isEmpty()) {
qWarning() << "No GLSL" << ver.version << "shader code found in baked shader" << shaderStage.shader;
qWarning() << "No GLSL" << ver.version() << "shader code found in baked shader" << shaderStage.shader;
return false;
}
const char *srcStr = source.constData();
......
......@@ -2441,17 +2441,17 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
id<MTLLibrary> QRhiMetalData::createMetalLib(const QBakedShader &shader, QString *error, QByteArray *entryPoint)
{
QBakedShader::Shader mtllib = shader.shader({ QBakedShader::MetalLibShader, 12 });
if (!mtllib.shader.isEmpty()) {
dispatch_data_t data = dispatch_data_create(mtllib.shader.constData(),
mtllib.shader.size(),
QBakedShaderCode mtllib = shader.shader({ QBakedShaderKey::MetalLibShader, 12 });
if (!mtllib.shader().isEmpty()) {
dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
mtllib.shader().size(),
dispatch_get_global_queue(0, 0),
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
NSError *err = nil;
id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
dispatch_release(data);
if (!err) {
*entryPoint = mtllib.entryPoint;
*entryPoint = mtllib.entryPoint();
return lib;
} else {
const QString msg = QString::fromNSString(err.localizedDescription);
......@@ -2459,13 +2459,13 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QBakedShader &shader, QString
}
}
QBakedShader::Shader mslSource = shader.shader({ QBakedShader::MslShader, 12 });
if (mslSource.shader.isEmpty()) {
QBakedShaderCode mslSource = shader.shader({ QBakedShaderKey::MslShader, 12 });
if (mslSource.shader().isEmpty()) {
qWarning() << "No MSL 1.2 code found in baked shader" << shader;
return nil;
}
NSString *src = [NSString stringWithUTF8String: mslSource.shader.constData()];
NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
opts.languageVersion = MTLLanguageVersion1_2;
NSError *err = nil;
......@@ -2479,7 +2479,7 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QBakedShader &shader, QString
return nil;
}
*entryPoint = mslSource.entryPoint;
*entryPoint = mslSource.entryPoint();
return lib;
}
......
......@@ -4278,12 +4278,12 @@ bool QVkGraphicsPipeline::build()
QVarLengthArray<VkShaderModule, 4> shaders;
QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos;
for (const QRhiGraphicsShaderStage &shaderStage : m_shaderStages) {
const QBakedShader::Shader spirv = shaderStage.shader.shader(QBakedShader::SpirvShader);
if (spirv.shader.isEmpty()) {
qWarning() << "No SPIR-V shader code found in baked shader" << shaderStage.shader;
const QBakedShaderCode spirv = shaderStage.shader.shader({ QBakedShaderKey::SpirvShader, 100 });
if (spirv.shader().isEmpty()) {
qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << shaderStage.shader;
return false;
}
VkShaderModule shader = rhiD->createShader(spirv.shader);
VkShaderModule shader = rhiD->createShader(spirv.shader());
if (shader) {
shaders.append(shader);
VkPipelineShaderStageCreateInfo shaderInfo;
......@@ -4291,7 +4291,7 @@ bool QVkGraphicsPipeline::build()
shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderInfo.stage = toVkShaderStage(shaderStage.type);
shaderInfo.module = shader;
shaderInfo.pName = spirv.entryPoint.constData();
shaderInfo.pName = spirv.entryPoint().constData();
shaderStageCreateInfos.append(shaderInfo);
}
}
......
......@@ -43,30 +43,185 @@ QT_BEGIN_NAMESPACE
/*!
\class QBakedShader
\inmodule QtShaderTools
\brief Contains multiple versions of a shader translated to multiple shading languages,
together with reflection metadata.
QBakedShader is the entry point to shader code in the graphics API agnostic
Qt world. Instead of using GLSL shader sources, as was the custom with Qt
5.x, new graphics systems with backends for multiple graphics APIs, such
as, Vulkan, Metal, Direct3D, and OpenGL, take QBakedShader as their input
whenever a shader needs to be specified.
A QBakedShader instance is empty and thus invalid by default. To get a useful
instance, the two typical methods are:
\list
\li Generate the contents offline, during build time or earlier, using the
\c qsb command line tool. The result is a binary file that is shipped with
the application, read via QIODevice::readAll(), and then deserialized via
fromSerialized(). For more information, see QShaderBaker.
\li Generate at run time via QShaderBaker. This is an expensive operation,
but allows applications to use user-provided or dynamically generated
shader source strings.
\endlist
When used together with the Qt Rendering Hardware Interface and its
classes, like QRhiGraphicsPipeline, no further action is needed from the
application's side as these classes are prepared to consume a QBakedShader
whenever a shader needs to be specified for a given stage of the graphics
pipeline.
Alternatively, applications can access
\list
\li the source or byte code for any of the shading language versions that
are included in the QBakedShader,
\li the name of the entry point for the shader,
\li the reflection metadata containing a description of the shader's
inputs, outputs and resources like uniform blocks. This is essential when
an application or framework needs to discover the inputs of a shader at
runtime due to not having advance knowledge of the vertex attributes or the
layout of the uniform buffers used by the shader.
\endlist
QBakedShader makes no assumption about the shading language that was used
as the source for generating the various versions and variants that are
included in it.
QBakedShader uses implicit sharing similarly to many core Qt types, and so
can be returned or passed by value.
For reference, QRhi expects that a QBakedShader suitable for all its
backends contains at least the following:
\list
\li SPIR-V 1.0 bytecode suitable for Vulkan 1.0 or newer
\li GLSL/ES 100 source code suitable for OpenGL ES 2.0 or newer
\li GLSL 120 source code suitable for OpenGL 2.1
\li HLSL Shader Model 5.0 source code or the corresponding DXBC bytecode suitable for Direct3D 11
\li Metal Shading Language 1.2 source code or the corresponding bytecode suitable for Metal
\endlist
\sa QShaderBaker
*/
/*!
\class QBakedShader::ShaderSourceVersion
\enum QBakedShader::ShaderStage
Describes the stage of the graphics pipeline the shader is suitable for.
\value VertexStage Vertex shader
\value TessControlStage Tessellation control (hull) shader
\value TessEvaluationStage Tessellation evaluation (domain) shader
\value GeometryStage Geometry shader
\value FragmentStage Fragment (pixel) shader
\value ComputeStage Compute shader
*/
/*!
\class QBakedShaderVersion
\inmodule QtShaderTools
\brief Specifies the shading language version.
While languages like SPIR-V or the Metal Shading Language use traditional
version numbers, shaders for other APIs can use slightly different
versioning schemes. All those are mapped to a single version number in
here, however. For HLSL, the version refers to the Shader Model version,
like 5.0, 5.1, or 6.0. For GLSL an additional flag is needed to choose
between GLSL and GLSL/ES.
Below is a list with the most common examples of shader versions for
different graphics APIs:
\list
\li Vulkan (SPIR-V): 100
\li OpenGL: 120, 330, 440, etc.
\li OpenGL ES: 100 with GlslEs, 300 with GlslEs, etc.
\li Direct3D: 50, 51, 60
\li Metal: 12, 20
\endlist
A default constructed QBakedShaderVersion contains a version of 100 and no
flags set.
*/
/*!
\enum QBakedShaderVersion::Flag
Describes the flags that can be set.
\value GlslEs Indicates that GLSL/ES is meant in combination with GlslShader
*/
/*!
\class QBakedShader::ShaderKey
\class QBakedShaderKey
\inmodule QtShaderTools
\brief Specifies the shading language, the version with flags, and the variant.
A default constructed QBakedShaderKey has source set to SpirvShader and
sourceVersion set to 100. sourceVariant defaults to StandardShader.
*/
/*!
\class QBakedShader::Shader
\enum QBakedShaderKey::ShaderSource
Describes what kind of shader code an entry contains.
\value SpirvShader SPIR-V
\value GlslShader GLSL
\value HlslShader HLSL
\value DxbcShader Direct3D bytecode (HLSL compiled by \c fxc)
\value MslShader Metal Shading Language
\value DxilShader Direct3D bytecode (HLSL compiled by \c dxc)
\value MetalLibShader Pre-compiled Metal bytecode
*/
/*!
\enum QBakedShaderKey::ShaderVariant
Describes what kind of shader code an entry contains.
\value StandardShader The normal, unmodified version of the shader code.
\value BatchableVertexShader Vertex shader rewritten to be suitable for Qt Quick scenegraph batching.
*/
/*!
\class QBakedShaderCode
\inmodule QtShaderTools
\brief Contains source or binary code for a shader and additional metadata.
When shader() is empty after retrieving a QBakedShaderCode instance from
QBakedShader, it indicates no shader code was found for the requested key.
*/
static const int QSB_VERSION = 1;
/*!
Constructs a new, empty (and thus invalid) QBakedShader instance.
*/
QBakedShader::QBakedShader()
: d(new QBakedShaderPrivate)
{
}
/*!
\internal
*/
void QBakedShader::detach()
{
if (d->ref.load() != 1) {
......@@ -77,12 +232,18 @@ void QBakedShader::detach()
}
}
/*!
\internal
*/
QBakedShader::QBakedShader(const QBakedShader &other)
{
d = other.d;
d->ref.ref();
}
/*!
\internal
*/
QBakedShader &QBakedShader::operator=(const QBakedShader &other)
{
if (d != other.d) {
......@@ -94,22 +255,34 @@ QBakedShader &QBakedShader::operator=(const QBakedShader &other)
return *this;
}
/*!
Destructor.
*/
QBakedShader::~QBakedShader()
{
if (!d->ref.deref())
delete d;
}
/*!
\return true if the QBakedShader contains at least one shader version.
*/
bool QBakedShader::isValid() const
{
return !d->shaders.isEmpty();
}
/*!
\return the pipeline stage the shader is meant for.
*/
QBakedShader::ShaderStage QBakedShader::stage() const
{
return d->stage;
}
/*!
Sets the pipeline \a stage.
*/
void QBakedShader::setStage(ShaderStage stage)
{
if (stage != d->stage) {
......@@ -118,28 +291,43 @@ void QBakedShader::setStage(ShaderStage stage)
}
}
/*!
\return the reflection metadata for the shader.
*/
QShaderDescription QBakedShader::description() const
{
return d->desc;
}
/*!
Sets the reflection metadata to \a desc.
*/
void QBakedShader::setDescription(const QShaderDescription &desc)
{
detach();
d->desc = desc;
}
QList<QBakedShader::ShaderKey> QBakedShader::availableShaders() const
/*!
\return the list of available shader versions
*/
QList<QBakedShaderKey> QBakedShader::availableShaders() const
{
return d->shaders.keys();
}
QBakedShader::Shader QBakedShader::shader(const ShaderKey &key) const
/*!
\return the source or binary code for a given shader version specified by \a key.
*/
QBakedShaderCode QBakedShader::shader(const QBakedShaderKey &key) const
{
return d->shaders.value(key);
}
void QBakedShader::setShader(const ShaderKey &key, const Shader &shader)
/*!
Stores the source or binary \a shader code for a given shader version specified by \a key.
*/
void QBakedShader::setShader(const QBakedShaderKey &key, const QBakedShaderCode &shader)
{
if (d->shaders.value(key) == shader)
return;
......@@ -148,7 +336,11 @@ void QBakedShader::setShader(const ShaderKey &key, const Shader &shader)
d->shaders[key] = shader;
}
void QBakedShader::removeShader(const ShaderKey &key)
/*!
Removes the source or binary shader code for a given \a key.
Does nothing when not found.
*/
void QBakedShader::removeShader(const QBakedShaderKey &key)
{
auto it = d->shaders.find(key);
if (it == d->shaders.end())
......@@ -158,6 +350,12 @@ void QBakedShader::removeShader(const ShaderKey &key)
d->shaders.erase(it);
}
/*!
\return a serialized binary version of all the data held by the
QBakedShader, suitable for writing to files or other I/O devices.
\sa fromSerialized()
*/
QByteArray QBakedShader::serialized() const
{
QBuffer buf;
......@@ -171,19 +369,24 @@ QByteArray QBakedShader::serialized() const
ds << d->desc.toBinaryJson();
ds << d->shaders.count();
for (auto it = d->shaders.cbegin(), itEnd = d->shaders.cend(); it != itEnd; ++it) {
const ShaderKey &k(it.key());
ds << k.source;
ds << k.sourceVersion.version;
ds << k.sourceVersion.flags;
ds << k.variant;
const Shader &shader(d->shaders.value(k));
ds << shader.shader;
ds << shader.entryPoint;
const QBakedShaderKey &k(it.key());
ds << k.source();
ds << k.sourceVersion().version();
ds << k.sourceVersion().flags();
ds << k.sourceVariant();
const QBakedShaderCode &shader(d->shaders.value(k));
ds << shader.shader();
ds << shader.entryPoint();
}
return qCompress(buf.buffer());
}
/*!
Creates a new QBakedShader instance from the given \a data.
\sa serialized()
*/
QBakedShader QBakedShader::fromSerialized(const QByteArray &data)
{
QByteArray udata = qUncompress(data);
......@@ -209,41 +412,43 @@ QBakedShader QBakedShader::fromSerialized(const QByteArray &data)
int count;
ds >> count;
for (int i = 0; i < count; ++i) {
ShaderKey k;
QBakedShaderKey k;
ds >> intVal;
k.source = ShaderSource(intVal);
k.setSource(QBakedShaderKey::ShaderSource(intVal));
QBakedShaderVersion ver;
ds >> intVal;
k.sourceVersion.version = intVal;
ver.setVersion(intVal);
ds >> intVal;
k.sourceVersion.flags = ShaderSourceVersion::Flags(intVal);
ver.setFlags(QBakedShaderVersion::Flags(intVal));
k.setSourceVersion(ver);
ds >> intVal;
k.variant = ShaderVariant(intVal);
Shader shader;
k.setSourceVariant(QBakedShaderKey::ShaderVariant(intVal));
QBakedShaderCode shader;
QByteArray s;
ds >> s;
shader.shader = s;
shader.setShader(s);
ds >> s;
shader.entryPoint = s;
shader.setEntryPoint(s);
d->shaders[k] = shader;
}
return bs;
}
bool operator==(const QBakedShader::ShaderSourceVersion &lhs, const QBakedShader::ShaderSourceVersion &rhs) Q_DECL_NOTHROW
bool operator==(const QBakedShaderVersion &lhs, const QBakedShaderVersion &rhs) Q_DECL_NOTHROW
{
return lhs.version == rhs.version && lhs.flags == rhs.flags;
return lhs.version() == rhs.version() && lhs.flags() == rhs.flags();
}
bool operator==(const QBakedShader::ShaderKey &lhs, const QBakedShader::ShaderKey &rhs) Q_DECL_NOTHROW
bool operator==(const QBakedShaderKey &lhs, const QBakedShaderKey &rhs) Q_DECL_NOTHROW
{
return lhs.source == rhs.source && lhs.sourceVersion == rhs.sourceVersion
&& lhs.variant == rhs.variant;
return lhs.source() == rhs.source() && lhs.sourceVersion() == rhs.sourceVersion()
&& lhs.sourceVariant() == rhs.sourceVariant();
}
bool operator==(const QBakedShader::Shader &lhs, const QBakedShader::Shader &rhs) Q_DECL_NOTHROW
bool operator==(const QBakedShaderCode &lhs, const QBakedShaderCode &rhs) Q_DECL_NOTHROW
{
return lhs.shader == rhs.shader && lhs.entryPoint == rhs.entryPoint;
return lhs.shader() == rhs.shader() && lhs.entryPoint() == rhs.entryPoint();
}
#ifndef QT_NO_DEBUG_STREAM
......@@ -262,24 +467,24 @@ QDebug operator<<(QDebug dbg, const QBakedShader &bs)
return dbg;
}
uint qHash(const QBakedShader::ShaderKey &k, uint seed)
uint qHash(const QBakedShaderKey &k, uint seed)
{
return seed + 10 * k.source + k.sourceVersion.version + k.sourceVersion.flags + k.variant;
return seed + 10 * k.source() + k.sourceVersion().version() + k.sourceVersion().flags() + k.sourceVariant();
}
QDebug operator<<(QDebug dbg, const QBakedShader::ShaderKey &k)
QDebug operator<<(QDebug dbg, const QBakedShaderKey &k)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "ShaderKey(" << k.source
<< " " << k.sourceVersion
<< " " << k.variant << ")";
dbg.nospace() << "ShaderKey(" << k.source()
<< " " << k.sourceVersion()
<< " " << k.sourceVariant() << ")";
return dbg;
}
QDebug operator<<(QDebug dbg, const QBakedShader::ShaderSourceVersion &v)
QDebug operator<<(QDebug dbg, const QBakedShaderVersion &v)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "Version(" << v.version << " " << v.flags << ")";
dbg.nospace() << "Version(" << v.version() << " " << v.flags() << ")";
return dbg;
}
......
......@@ -44,18 +44,38 @@ QT_BEGIN_NAMESPACE
struct QBakedShaderPrivate;
class Q_SHADERTOOLS_EXPORT QBakedShader
class Q_SHADERTOOLS_EXPORT QBakedShaderVersion
{
public:
enum ShaderStage {
VertexStage = 0,
TessControlStage,
TessEvaluationStage,
GeometryStage,
FragmentStage,
ComputeStage
enum Flag {
GlslEs = 0x01
};
#ifndef Q_CLANG_QDOC
Q_DECLARE_FLAGS(Flags, Flag)
#endif
QBakedShaderVersion() { }
QBakedShaderVersion(int v, Flags f = Flags())
: m_version(v), m_flags(f)
{ }
int version() const { return m_version; }
void setVersion(int v) { m_version = v; }
Flags flags() const { return m_flags; }
void setFlags(Flags f) { m_flags = f; }
private:
int m_version = 100;
Flags m_flags;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QBakedShaderVersion::Flags)
Q_DECLARE_TYPEINFO(QBakedShaderVersion, Q_MOVABLE_TYPE);
class Q_SHADERTOOLS_EXPORT QBakedShaderKey
{
public:
enum ShaderSource {
SpirvShader = 0,
GlslShader,
......@@ -66,49 +86,68 @@ public:
MetalLibShader // xcrun metal + xcrun metallib
};
struct ShaderSourceVersion {
enum Flag {
GlslEs = 0x01
};
Q_DECLARE_FLAGS(Flags, Flag)