Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquick3deffect.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3deffect_p.h"
5
6#include <ssg/qssgrendercontextcore.h>
7#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
9#include <QtQuick3DUtils/private/qssgutils_p.h>
10#include <QtQuick/qquickwindow.h>
11#include <QtQuick3D/private/qquick3dobject_p.h>
12#include <QtQuick3D/private/qquick3dscenemanager_p.h>
13#include <QtCore/qfile.h>
14#include <QtCore/qurl.h>
15
16
18
576
577QQmlListProperty<QQuick3DShaderUtilsRenderPass> QQuick3DEffect::passes()
578{
579 return QQmlListProperty<QQuick3DShaderUtilsRenderPass>(this,
580 nullptr,
585}
586
587// Default vertex and fragment shader code that is used when no corresponding
588// Shader is present in the Effect. These go through the usual processing so
589// should use the user-facing builtins.
590
592 "void MAIN()\n"
593 "{\n"
594 "}\n";
595
597 "void MAIN()\n"
598 "{\n"
599 "#if QSHADER_VIEW_COUNT >= 2\n"
600 " FRAGCOLOR = texture(INPUT, vec3(INPUT_UV, VIEW_INDEX));\n"
601 "#else\n"
602 " FRAGCOLOR = texture(INPUT, INPUT_UV);\n"
603 "#endif\n"
604 "}\n";
605
606static inline void insertVertexMainArgs(QByteArray &snippet)
607{
608 static const char *argKey = "/*%QT_ARGS_MAIN%*/";
609 const int argKeyLen = int(strlen(argKey));
610 const int argKeyPos = snippet.indexOf(argKey);
611 if (argKeyPos >= 0)
612 snippet = snippet.left(argKeyPos) + QByteArrayLiteral("inout vec3 VERTEX") + snippet.mid(argKeyPos + argKeyLen);
613}
614
616{
617 using namespace QSSGShaderUtils;
618
619 const auto &renderContext = QQuick3DObjectPrivate::get(this)->sceneManager->wattached->rci();
620 if (!renderContext) {
621 qWarning("QQuick3DEffect: No render context interface?");
622 return nullptr;
623 }
624
625 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(node);
626 bool newBackendNode = false;
627 if (!effectNode) {
628 effectNode = new QSSGRenderEffect;
629 newBackendNode = true;
630 }
631
632 bool shadersMayChange = false;
633 if (m_dirtyAttributes & Dirty::EffectChainDirty)
634 shadersMayChange = true;
635
636 const bool fullUpdate = newBackendNode || effectNode->incompleteBuildTimeObject || (m_dirtyAttributes & Dirty::TextureDirty);
637
638 if (fullUpdate || shadersMayChange) {
639 markAllDirty();
640
641 // Need to clear the old list with properties and textures first.
642 effectNode->properties.clear();
643 effectNode->textureProperties.clear();
644
645 QMetaMethod propertyDirtyMethod;
646 const int idx = metaObject()->indexOfSlot("onPropertyDirty()");
647 if (idx != -1)
648 propertyDirtyMethod = metaObject()->method(idx);
649
650 // Properties -> uniforms
652 const int propCount = metaObject()->propertyCount();
653 int propOffset = metaObject()->propertyOffset();
654
655 // Effect can have multilayered inheritance structure, so find the actual propOffset
656 const QMetaObject *superClass = metaObject()->superClass();
657 while (superClass && qstrcmp(superClass->className(), "QQuick3DEffect") != 0) {
658 propOffset = superClass->propertyOffset();
659 superClass = superClass->superClass();
660 }
661
662 using TextureInputProperty = QPair<QQuick3DShaderUtilsTextureInput *, const char *>;
663
664 QVector<TextureInputProperty> textureProperties; // We'll deal with these later
665 for (int i = propOffset; i != propCount; ++i) {
666 const QMetaProperty property = metaObject()->property(i);
667 if (Q_UNLIKELY(!property.isValid()))
668 continue;
669
670 const auto name = property.name();
671 QMetaType propType = property.metaType();
672 QVariant propValue = property.read(this);
673 if (propType == QMetaType(QMetaType::QVariant))
674 propType = propValue.metaType();
675
676 if (propType.id() >= QMetaType::User) {
677 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
679 textureProperties.push_back({texture, name});
680 }
681 } else if (propType == QMetaType(QMetaType::QObjectStar)) {
682 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
683 textureProperties.push_back({texture, name});
684 } else {
685 const auto type = uniformType(propType);
687 uniforms.append({ uniformTypeName(propType), name });
688 effectNode->properties.push_back({ name, uniformTypeName(propType),
689 propValue, uniformType(propType), i});
690 // Track the property changes
691 if (fullUpdate) {
692 if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
693 connect(this, property.notifySignal(), this, propertyDirtyMethod);
694 } // else already connected
695 } else {
696 // ### figure out how _not_ to warn when there are no dynamic
697 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
698 //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name());
699 }
700 }
701 }
702
703 const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) {
705 QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set
706 if (fullUpdate) {
707 connect(&texture, &QQuick3DShaderUtilsTextureInput::enabledChanged, this, &QQuick3DEffect::onTextureDirty);
708 connect(&texture, &QQuick3DShaderUtilsTextureInput::textureChanged, this, &QQuick3DEffect::onTextureDirty);
709 } // else already connected
710 texProp.name = name;
711 if (texture.enabled && tex)
712 texProp.texImage = tex->getRenderImage();
713
714 texProp.shaderDataType = QSSGRenderShaderValue::Texture;
715
716 if (tex) {
724 texProp.horizontalClampType = tex->horizontalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
727 texProp.verticalClampType = tex->verticalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
730 texProp.zClampType = tex->depthTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
733 }
734
735 if (tex && QQuick3DObjectPrivate::get(tex)->type == QQuick3DObjectPrivate::Type::ImageCube)
736 uniforms.append({ QByteArrayLiteral("samplerCube"), name });
737 else if (tex && tex->textureData() && tex->textureData()->depth() > 0)
738 uniforms.append({ QByteArrayLiteral("sampler3D"), name });
739 else
740 uniforms.append({ QByteArrayLiteral("sampler2D"), name });
741
742 effectNode->textureProperties.push_back(texProp);
743 };
744
745 // Textures
746 for (const auto &property : std::as_const(textureProperties))
747 processTextureProperty(*property.first, property.second);
748
749 if (effectNode->incompleteBuildTimeObject) { // This object came from the shadergen tool
750 const auto names = dynamicPropertyNames();
751 for (const auto &name : names) {
752 QVariant propValue = property(name.constData());
753 QMetaType propType = propValue.metaType();
754 if (propType == QMetaType(QMetaType::QVariant))
755 propType = propValue.metaType();
756
757 if (propType.id() >= QMetaType::User) {
758 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
760 textureProperties.push_back({texture, name});
761 }
762 } else if (propType.id() == QMetaType::QObjectStar) {
763 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
764 textureProperties.push_back({texture, name});
765 } else {
766 const auto type = uniformType(propType);
768 uniforms.append({ uniformTypeName(propType), name });
769 effectNode->properties.push_back({ name, uniformTypeName(propType),
770 propValue, uniformType(propType), -1 /* aka. dynamic property */});
771 // We don't need to track property changes
772 } else {
773 // ### figure out how _not_ to warn when there are no dynamic
774 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
775 qWarning("No known uniform conversion found for effect property %s. Skipping", name.constData());
776 }
777 }
778 }
779
780 for (const auto &property : std::as_const(textureProperties))
781 processTextureProperty(*property.first, property.second);
782 }
783
784 // built-ins
785 uniforms.append({ "mat4", "qt_modelViewProjection" });
786 uniforms.append({ "vec2", "qt_inputSize" });
787 uniforms.append({ "vec2", "qt_outputSize" });
788 uniforms.append({ "float", "qt_frame_num" });
789 uniforms.append({ "float", "qt_fps" });
790 uniforms.append({ "vec2", "qt_cameraProperties" });
791 uniforms.append({ "float", "qt_normalAdjustViewportFactor" });
792 uniforms.append({ "float", "qt_nearClipValue" });
793
794 // qt_inputTexture is not listed in uniforms, will be added by prepareCustomShader()
795 // since the name and type varies between non-multiview and multiview mode
796
798 builtinVertexInputs.append({ "vec3", "attr_pos" });
799 builtinVertexInputs.append({ "vec2", "attr_uv" });
800
802 builtinVertexOutputs.append({ "vec2", "qt_inputUV" });
803 builtinVertexOutputs.append({ "vec2", "qt_textureUV" });
804 builtinVertexOutputs.append({ "flat uint", "qt_viewIndex" });
805
806 // fragOutput is added automatically by the program generator
807
808 if (!m_passes.isEmpty()) {
809 const QQmlContext *context = qmlContext(this);
810 effectNode->resetCommands();
811 for (QQuick3DShaderUtilsRenderPass *pass : std::as_const(m_passes)) {
812 // Have a key composed more or less of the vertex and fragment filenames.
813 // The shaderLibraryManager uses stage+shaderPathKey as the key.
814 // Thus shaderPathKey is then sufficient to look up both the vertex and fragment shaders later on.
815 // Note that this key is not suitable as a unique key for the graphics resources because the same
816 // set of shader files can be used in multiple different passes, or in multiple active effects.
817 // But that's the effect system's problem.
818 QByteArray shaderPathKey("effect pipeline--");
822 for (QQuick3DShaderUtilsShader *s : pass->m_shaders) {
823 if (s->stage == stage) {
824 shader = s;
825 break;
826 }
827 }
828
829 // just how many enums does one need for the exact same thing...
833
834 // Will just use the custom material infrastructure. Some
835 // substitutions are common between custom materials and effects.
836 //
837 // Substitutions relevant to us here:
838 // MAIN -> qt_customMain
839 // FRAGCOLOR -> fragOutput
840 // POSITION -> gl_Position
841 // MODELVIEWPROJECTION_MATRIX -> qt_modelViewProjection
842 // DEPTH_TEXTURE -> qt_depthTexture
843 // ... other things shared with custom material
844 //
845 // INPUT -> qt_inputTexture
846 // INPUT_UV -> qt_inputUV
847 // ... other effect specifics
848 //
849 // Built-in uniforms, inputs and outputs will be baked into
850 // metadata comment blocks in the resulting source code.
851 // Same goes for inputs/outputs declared with VARYING.
852
853 QByteArray code;
854 if (shader) {
855 code = QSSGShaderUtils::resolveShader(shader->shader, context, shaderPathKey); // appends to shaderPathKey
856 } else {
857 if (!shaderPathKey.isEmpty())
858 shaderPathKey.append('>');
859 shaderPathKey += "DEFAULT";
862 else
864 }
865
871 uniforms, builtinVertexInputs, builtinVertexOutputs, false);
873 buf.clear();
876 uniforms, builtinVertexInputs, builtinVertexOutputs, true);
878 } else {
882 uniforms, builtinVertexOutputs, {}, false);
884 buf.clear();
887 uniforms, builtinVertexOutputs, {}, true);
889 }
890
892 effectNode->requiresDepthTexture = true;
893
896 // qt_customMain() has an argument list which gets injected here
898 passData.vertexShaderCode[i] = result[i].first;
899 passData.vertexMetaData[i] = result[i].second;
900 } else {
901 passData.fragmentShaderCode[i] = result[i].first;
902 passData.fragmentMetaData[i] = result[i].second;
903 }
904 }
905 }
906
907 effectNode->commands.push_back({ nullptr, true }); // will be changed to QSSGBindShader in finalizeShaders
908 passData.bindShaderCmdIndex = effectNode->commands.size() - 1;
909
910 // finalizing the shader code happens in a separate step later on by the backend node
911 passData.shaderPathKeyPrefix = shaderPathKey;
912 effectNode->shaderPrepData.passes.append(passData);
913 effectNode->shaderPrepData.valid = true; // trigger reprocessing the shader code later on
914
915 effectNode->commands.push_back({ new QSSGApplyInstanceValue, true });
916
917 // Buffers
918 QQuick3DShaderUtilsBuffer *outputBuffer = pass->outputBuffer;
919 if (outputBuffer) {
920 const QByteArray &outBufferName = outputBuffer->name;
921 if (outBufferName.isEmpty()) {
922 // default output buffer (with settings)
923 auto outputFormat = QQuick3DShaderUtilsBuffer::mapTextureFormat(outputBuffer->format());
924 effectNode->commands.push_back({ new QSSGBindTarget(outputFormat), true });
925 effectNode->outputFormat = outputFormat;
926 } else {
927 // Allocate buffer command
928 effectNode->commands.push_back({ outputBuffer->getCommand(), false });
929 // bind buffer
930 effectNode->commands.push_back({ new QSSGBindBuffer(outBufferName), true });
931 }
932 } else {
933 // Use the default output buffer, same format as the source buffer
934 effectNode->commands.push_back({ new QSSGBindTarget(QSSGRenderTextureFormat::Unknown), true });
935 effectNode->outputFormat = QSSGRenderTextureFormat::Unknown;
936 }
937
938 // Other commands (BufferInput, Blending ... )
939 const auto &extraCommands = pass->m_commands;
940 for (const auto &command : extraCommands) {
941 const int bufferCount = command->bufferCount();
942 for (int i = 0; i != bufferCount; ++i)
943 effectNode->commands.push_back({ command->bufferAt(i)->getCommand(), false });
944 effectNode->commands.push_back({ command->getCommand(), false });
945 }
946
947 effectNode->commands.push_back({ new QSSGRender, true });
948 }
949 }
950 }
951
952 if (m_dirtyAttributes & Dirty::PropertyDirty) {
953 for (const auto &prop : std::as_const(effectNode->properties)) {
954 auto p = metaObject()->property(prop.pid);
955 if (Q_LIKELY(p.isValid()))
956 prop.value = p.read(this);
957 }
958 }
959
960 m_dirtyAttributes = 0;
961
963
964 return effectNode;
965}
966
967void QQuick3DEffect::onPropertyDirty()
968{
969 markDirty(Dirty::PropertyDirty);
970}
971
972void QQuick3DEffect::onTextureDirty()
973{
974 markDirty(Dirty::TextureDirty);
975}
976
977void QQuick3DEffect::onPassDirty()
978{
979 markDirty(Dirty::EffectChainDirty);
980}
981
983{
984 markDirty(Dirty::EffectChainDirty);
985}
986
987void QQuick3DEffect::markDirty(QQuick3DEffect::Dirty type)
988{
989 if (!(m_dirtyAttributes & quint32(type))) {
990 m_dirtyAttributes |= quint32(type);
991 update();
992 }
993}
994
995void QQuick3DEffect::updateSceneManager(QQuick3DSceneManager *sceneManager)
996{
997 if (sceneManager) {
998 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
999 if (auto tex = it->texture())
1000 QQuick3DObjectPrivate::refSceneManager(tex, *sceneManager);
1001 }
1002 } else {
1003 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
1004 if (auto tex = it->texture())
1006 }
1007 }
1008}
1009
1010void QQuick3DEffect::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
1011{
1012 if (change == QQuick3DObject::ItemSceneChange)
1013 updateSceneManager(value.sceneManager);
1014}
1015
1016void QQuick3DEffect::qmlAppendPass(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, QQuick3DShaderUtilsRenderPass *pass)
1017{
1018 if (!pass)
1019 return;
1020
1021 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
1022 that->m_passes.push_back(pass);
1023
1024 connect(pass, &QQuick3DShaderUtilsRenderPass::changed, that, &QQuick3DEffect::onPassDirty);
1025 that->effectChainDirty();
1026}
1027
1028QQuick3DShaderUtilsRenderPass *QQuick3DEffect::qmlPassAt(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, qsizetype index)
1029{
1030 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
1031 return that->m_passes.at(index);
1032}
1033
1034qsizetype QQuick3DEffect::qmlPassCount(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
1035{
1036 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
1037 return that->m_passes.size();
1038}
1039
1040void QQuick3DEffect::qmlPassClear(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
1041{
1042 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
1043
1044 for (QQuick3DShaderUtilsRenderPass *pass : that->m_passes)
1045 pass->disconnect(that);
1046
1047 that->m_passes.clear();
1048 that->effectChainDirty();
1049}
1050
1051void QQuick3DEffect::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap)
1052{
1053 // There can only be one texture input per property, as the texture input is a combination
1054 // of the texture used and the uniform name!
1055 auto it = m_dynamicTextureMaps.constFind(textureMap);
1056
1057 if (it == m_dynamicTextureMaps.constEnd()) {
1058 // Track the object, if it's destroyed we need to remove it from our table.
1059 connect(textureMap, &QQuick3DShaderUtilsTextureInput::destroyed, this, [this, textureMap]() {
1060 auto it = m_dynamicTextureMaps.constFind(textureMap);
1061 if (it != m_dynamicTextureMaps.constEnd())
1062 m_dynamicTextureMaps.erase(it);
1063 });
1064 m_dynamicTextureMaps.insert(textureMap);
1065
1066 update();
1067 }
1068}
1069
\inmodule QtCore
Definition qbytearray.h:57
void push_back(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:466
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray first(qsizetype n) const &
Definition qbytearray.h:196
\inmodule QtCore
Definition qmetaobject.h:19
\inmodule QtCore
const char * name() const
Returns this property's name.
\inmodule QtCore
Definition qmetatype.h:341
int id(int=0) const
Definition qmetatype.h:475
friend class QVariant
Definition qmetatype.h:796
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QList< QByteArray > dynamicPropertyNames() const
Definition qobject.cpp:4352
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
QQmlListProperty< QQuick3DShaderUtilsRenderPass > passes
static void qmlPassClear(QQmlListProperty< QQuick3DShaderUtilsRenderPass > *list)
static qsizetype qmlPassCount(QQmlListProperty< QQuick3DShaderUtilsRenderPass > *list)
QQuick3DEffect(QQuick3DObject *parent=nullptr)
\qmlproperty list Effect::passes Contains a list of render \l {Pass}{passes} implemented by the effec...
void itemChange(QQuick3DObject::ItemChange, const QQuick3DObject::ItemChangeData &) override
static void qmlAppendPass(QQmlListProperty< QQuick3DShaderUtilsRenderPass > *list, QQuick3DShaderUtilsRenderPass *pass)
QSSGRenderGraphObject * updateSpatialNode(QSSGRenderGraphObject *node) override
static QQuick3DShaderUtilsRenderPass * qmlPassAt(QQmlListProperty< QQuick3DShaderUtilsRenderPass > *list, qsizetype index)
void refSceneManager(QQuick3DSceneManager &)
static QQuick3DObjectPrivate * get(QQuick3DObject *item)
\qmltype Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DObject \inherits QtObject
virtual void markAllDirty()
static QSSGRenderTextureFormat::Format mapTextureFormat(QQuick3DShaderUtilsBuffer::TextureFormat fmt)
int depth() const
Returns the depth of the texture data in pixels.
QQuick3DTextureData * textureData
QSSGRenderImage * getRenderImage()
TilingMode verticalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeVertical
TilingMode horizontalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeHorizontal
const_iterator constEnd() const noexcept
Definition qset.h:143
iterator erase(const_iterator i)
Definition qset.h:145
const_iterator constFind(const T &value) const
Definition qset.h:161
iterator insert(const T &value)
Definition qset.h:155
void append(const T &t)
\inmodule QtCore
Definition qvariant.h:65
QSet< QString >::iterator it
void ensureDebugObjectName(T *node, QObject *src)
\qmltype Shader \inherits Object \inqmlmodule QtQuick3D
QByteArray resolveShader(const QUrl &fileUrl, const QQmlContext *context, QByteArray &shaderPathKey)
Combined button and popup list for selecting options.
static void * context
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
static const QCssKnownValue properties[NumProperties - 1]
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
GLuint index
[2]
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum GLuint texture
GLenum GLuint GLsizei propCount
GLuint name
GLint first
GLdouble s
[6]
Definition qopenglext.h:235
GLuint GLuint * names
GLuint shader
Definition qopenglext.h:665
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
static const char * default_effect_fragment_shader
static void insertVertexMainArgs(QByteArray &snippet)
static const char * default_effect_vertex_shader
unsigned int quint32
Definition qtypes.h:50
ptrdiff_t qsizetype
Definition qtypes.h:165
const char property[13]
Definition qwizard.cpp:101
QList< int > list
[14]
obj metaObject() -> className()
\inmodule QtCore
const char * className() const
Returns the class name.
const QMetaObject * superClass() const
Returns the meta-object of the superclass, or \nullptr if there is no such object.
int propertyOffset() const
Returns the property offset for this class; i.e.
QSSGCustomShaderMetaData vertexMetaData[2]
QSSGCustomShaderMetaData fragmentMetaData[2]
static ShaderCodeAndMetaData prepareCustomShader(QByteArray &dst, const QByteArray &shaderCode, QSSGShaderCache::ShaderType type, const StringPairList &baseUniforms, const StringPairList &baseInputs=StringPairList(), const StringPairList &baseOutputs=StringPairList(), bool multiViewCompatible=false)
QPair< QByteArray, QSSGCustomShaderMetaData > ShaderCodeAndMetaData
Definition moc.h:23