Qt 6.x
The Qt SDK
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 <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.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
520{
521}
522
524{
526 nullptr,
531}
532
533// Default vertex and fragment shader code that is used when no corresponding
534// Shader is present in the Effect. These go through the usual processing so
535// should use the user-facing builtins.
536
538 "void MAIN()\n"
539 "{\n"
540 "}\n";
541
543 "void MAIN()\n"
544 "{\n"
545 " FRAGCOLOR = texture(INPUT, INPUT_UV);\n"
546 "}\n";
547
548static inline void insertVertexMainArgs(QByteArray &snippet)
549{
550 static const char *argKey = "/*%QT_ARGS_MAIN%*/";
551 const int argKeyLen = int(strlen(argKey));
552 const int argKeyPos = snippet.indexOf(argKey);
553 if (argKeyPos >= 0)
554 snippet = snippet.left(argKeyPos) + QByteArrayLiteral("inout vec3 VERTEX") + snippet.mid(argKeyPos + argKeyLen);
555}
556
558{
559 using namespace QSSGShaderUtils;
560
561 const auto &renderContext = QQuick3DObjectPrivate::get(this)->sceneManager->wattached->rci();
562 if (!renderContext) {
563 qWarning("QQuick3DEffect: No render context interface?");
564 return nullptr;
565 }
566
567 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(node);
568 bool newBackendNode = false;
569 if (!effectNode) {
570 effectNode = new QSSGRenderEffect;
571 newBackendNode = true;
572 }
573
574 bool shadersMayChange = false;
575 if (m_dirtyAttributes & Dirty::EffectChainDirty)
576 shadersMayChange = true;
577
578 const bool fullUpdate = newBackendNode || effectNode->incompleteBuildTimeObject;
579
580 if (fullUpdate || shadersMayChange) {
581 markAllDirty();
582
583 QMetaMethod propertyDirtyMethod;
584 const int idx = metaObject()->indexOfSlot("onPropertyDirty()");
585 if (idx != -1)
586 propertyDirtyMethod = metaObject()->method(idx);
587
588 // Properties -> uniforms
590 const int propCount = metaObject()->propertyCount();
591 int propOffset = metaObject()->propertyOffset();
592
593 // Effect can have multilayered inheritance structure, so find the actual propOffset
594 const QMetaObject *superClass = metaObject()->superClass();
595 while (superClass && qstrcmp(superClass->className(), "QQuick3DEffect") != 0) {
596 propOffset = superClass->propertyOffset();
597 superClass = superClass->superClass();
598 }
599
601
602 QVector<TextureInputProperty> textureProperties; // We'll deal with these later
603 for (int i = propOffset; i != propCount; ++i) {
604 const QMetaProperty property = metaObject()->property(i);
605 if (Q_UNLIKELY(!property.isValid()))
606 continue;
607
608 const auto name = property.name();
609 QMetaType propType = property.metaType();
610 QVariant propValue = property.read(this);
611 if (propType == QMetaType(QMetaType::QVariant))
612 propType = propValue.metaType();
613
614 if (propType.id() >= QMetaType::User) {
615 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
617 textureProperties.push_back({texture, name});
618 }
619 } else if (propType == QMetaType(QMetaType::QObjectStar)) {
620 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
621 textureProperties.push_back({texture, name});
622 } else {
623 const auto type = uniformType(propType);
625 uniforms.append({ uniformTypeName(propType), name });
626 effectNode->properties.push_back({ name, uniformTypeName(propType),
627 propValue, uniformType(propType), i});
628 // Track the property changes
629 if (fullUpdate) {
630 if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
631 connect(this, property.notifySignal(), this, propertyDirtyMethod);
632 } // else already connected
633 } else {
634 // ### figure out how _not_ to warn when there are no dynamic
635 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
636 //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name());
637 }
638 }
639 }
640
641 const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) {
643 QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set
644 if (fullUpdate) {
645 connect(&texture, &QQuick3DShaderUtilsTextureInput::enabledChanged, this, &QQuick3DEffect::onTextureDirty);
646 connect(&texture, &QQuick3DShaderUtilsTextureInput::textureChanged, this, &QQuick3DEffect::onTextureDirty);
647 } // else already connected
648 texProp.name = name;
649 if (texture.enabled && tex)
650 texProp.texImage = tex->getRenderImage();
651
653
654 if (tex) {
668 }
669
670 if (tex && QQuick3DObjectPrivate::get(tex)->type == QQuick3DObjectPrivate::Type::ImageCube)
671 uniforms.append({ QByteArrayLiteral("samplerCube"), name });
672 else if (tex && tex->textureData() && tex->textureData()->depth() > 0)
673 uniforms.append({ QByteArrayLiteral("sampler3D"), name });
674 else
675 uniforms.append({ QByteArrayLiteral("sampler2D"), name });
676
677 effectNode->textureProperties.push_back(texProp);
678 };
679
680 // Textures
681 for (const auto &property : std::as_const(textureProperties))
682 processTextureProperty(*property.first, property.second);
683
684 if (effectNode->incompleteBuildTimeObject) { // This object came from the shadergen tool
685 const auto names = dynamicPropertyNames();
686 for (const auto &name : names) {
687 QVariant propValue = property(name.constData());
688 QMetaType propType = propValue.metaType();
689 if (propType == QMetaType(QMetaType::QVariant))
690 propType = propValue.metaType();
691
692 if (propType.id() >= QMetaType::User) {
693 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
695 textureProperties.push_back({texture, name});
696 }
697 } else if (propType.id() == QMetaType::QObjectStar) {
698 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
699 textureProperties.push_back({texture, name});
700 } else {
701 const auto type = uniformType(propType);
703 uniforms.append({ uniformTypeName(propType), name });
704 effectNode->properties.push_back({ name, uniformTypeName(propType),
705 propValue, uniformType(propType), -1 /* aka. dynamic property */});
706 // We don't need to track property changes
707 } else {
708 // ### figure out how _not_ to warn when there are no dynamic
709 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
710 qWarning("No known uniform conversion found for effect property %s. Skipping", name.constData());
711 }
712 }
713 }
714
715 for (const auto &property : std::as_const(textureProperties))
716 processTextureProperty(*property.first, property.second);
717 }
718
719 // built-ins
720 uniforms.append({ "mat4", "qt_modelViewProjection" });
721 uniforms.append({ "sampler2D", "qt_inputTexture" });
722 uniforms.append({ "vec2", "qt_inputSize" });
723 uniforms.append({ "vec2", "qt_outputSize" });
724 uniforms.append({ "float", "qt_frame_num" });
725 uniforms.append({ "float", "qt_fps" });
726 uniforms.append({ "vec2", "qt_cameraProperties" });
727 uniforms.append({ "float", "qt_normalAdjustViewportFactor" });
728 uniforms.append({ "float", "qt_nearClipValue" });
729
731 builtinVertexInputs.append({ "vec3", "attr_pos" });
732 builtinVertexInputs.append({ "vec2", "attr_uv" });
733
735 builtinVertexOutputs.append({ "vec2", "qt_inputUV" });
736 builtinVertexOutputs.append({ "vec2", "qt_textureUV" });
737
738 // fragOutput is added automatically by the program generator
739
740 if (!m_passes.isEmpty()) {
741 const QQmlContext *context = qmlContext(this);
742 effectNode->resetCommands();
743 for (QQuick3DShaderUtilsRenderPass *pass : std::as_const(m_passes)) {
744 // Have a key composed more or less of the vertex and fragment filenames.
745 // The shaderLibraryManager uses stage+shaderPathKey as the key.
746 // Thus shaderPathKey is then sufficient to look up both the vertex and fragment shaders later on.
747 // Note that this key is not suitable as a unique key for the graphics resources because the same
748 // set of shader files can be used in multiple different passes, or in multiple active effects.
749 // But that's the effect system's problem.
750 QByteArray shaderPathKey("effect pipeline--");
754 for (QQuick3DShaderUtilsShader *s : pass->m_shaders) {
755 if (s->stage == stage) {
756 shader = s;
757 break;
758 }
759 }
760
761 // just how many enums does one need for the exact same thing...
765
766 // Will just use the custom material infrastructure. Some
767 // substitutions are common between custom materials and effects.
768 //
769 // Substitutions relevant to us here:
770 // MAIN -> qt_customMain
771 // FRAGCOLOR -> fragOutput
772 // POSITION -> gl_Position
773 // MODELVIEWPROJECTION_MATRIX -> qt_modelViewProjection
774 // DEPTH_TEXTURE -> qt_depthTexture
775 // ... other things shared with custom material
776 //
777 // INPUT -> qt_inputTexture
778 // INPUT_UV -> qt_inputUV
779 // ... other effect specifics
780 //
781 // Built-in uniforms, inputs and outputs will be baked into
782 // metadata comment blocks in the resulting source code.
783 // Same goes for inputs/outputs declared with VARYING.
784
786 if (shader) {
787 code = QSSGShaderUtils::resolveShader(shader->shader, context, shaderPathKey); // appends to shaderPathKey
788 } else {
789 if (!shaderPathKey.isEmpty())
790 shaderPathKey.append('>');
791 shaderPathKey += "DEFAULT";
794 else
796 }
797
798 QByteArray shaderCodeMeta;
802 uniforms, builtinVertexInputs, builtinVertexOutputs);
803 } else {
805 uniforms, builtinVertexOutputs);
806 }
807
808 if (result.second.flags.testFlag(QSSGCustomShaderMetaData::UsesDepthTexture))
809 effectNode->requiresDepthTexture = true;
810
811 code = result.first + shaderCodeMeta;
812
814 // qt_customMain() has an argument list which gets injected here
816 passData.vertexShaderCode = code;
817 passData.vertexMetaData = result.second;
818 } else {
819 passData.fragmentShaderCode = code;
820 passData.fragmentMetaData = result.second;
821 }
822 }
823
824 effectNode->commands.push_back({ nullptr, true }); // will be changed to QSSGBindShader in finalizeShaders
825 passData.bindShaderCmdIndex = effectNode->commands.size() - 1;
826
827 // finalizing the shader code happens in a separate step later on by the backend node
828 passData.shaderPathKeyPrefix = shaderPathKey;
829 effectNode->shaderPrepData.passes.append(passData);
830 effectNode->shaderPrepData.valid = true; // trigger reprocessing the shader code later on
831
832 effectNode->commands.push_back({ new QSSGApplyInstanceValue, true });
833
834 // Buffers
835 QQuick3DShaderUtilsBuffer *outputBuffer = pass->outputBuffer;
836 if (outputBuffer) {
837 const QByteArray &outBufferName = outputBuffer->name;
838 if (outBufferName.isEmpty()) {
839 // default output buffer (with settings)
840 auto outputFormat = QQuick3DShaderUtilsBuffer::mapTextureFormat(outputBuffer->format());
841 effectNode->commands.push_back({ new QSSGBindTarget(outputFormat), true });
842 effectNode->outputFormat = outputFormat;
843 } else {
844 // Allocate buffer command
845 effectNode->commands.push_back({ outputBuffer->getCommand(), false });
846 // bind buffer
847 effectNode->commands.push_back({ new QSSGBindBuffer(outBufferName), true });
848 }
849 } else {
850 // Use the default output buffer, same format as the source buffer
851 effectNode->commands.push_back({ new QSSGBindTarget(QSSGRenderTextureFormat::Unknown), true });
852 effectNode->outputFormat = QSSGRenderTextureFormat::Unknown;
853 }
854
855 // Other commands (BufferInput, Blending ... )
856 const auto &extraCommands = pass->m_commands;
857 for (const auto &command : extraCommands) {
858 const int bufferCount = command->bufferCount();
859 for (int i = 0; i != bufferCount; ++i)
860 effectNode->commands.push_back({ command->bufferAt(i)->getCommand(), false });
861 effectNode->commands.push_back({ command->getCommand(), false });
862 }
863
864 effectNode->commands.push_back({ new QSSGRender, true });
865 }
866 }
867 }
868
869 if (m_dirtyAttributes & Dirty::PropertyDirty) {
870 for (const auto &prop : std::as_const(effectNode->properties)) {
871 auto p = metaObject()->property(prop.pid);
872 if (Q_LIKELY(p.isValid()))
873 prop.value = p.read(this);
874 }
875 }
876
877 m_dirtyAttributes = 0;
878
880
881 return effectNode;
882}
883
884void QQuick3DEffect::onPropertyDirty()
885{
886 markDirty(Dirty::PropertyDirty);
887}
888
889void QQuick3DEffect::onTextureDirty()
890{
891 markDirty(Dirty::TextureDirty);
892}
893
894void QQuick3DEffect::onPassDirty()
895{
896 markDirty(Dirty::EffectChainDirty);
897}
898
900{
901 markDirty(Dirty::EffectChainDirty);
902}
903
904void QQuick3DEffect::markDirty(QQuick3DEffect::Dirty type)
905{
906 if (!(m_dirtyAttributes & quint32(type))) {
907 m_dirtyAttributes |= quint32(type);
908 update();
909 }
910}
911
912void QQuick3DEffect::updateSceneManager(QQuick3DSceneManager *sceneManager)
913{
914 if (sceneManager) {
915 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
916 if (auto tex = it->texture())
917 QQuick3DObjectPrivate::refSceneManager(tex, *sceneManager);
918 }
919 } else {
920 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
921 if (auto tex = it->texture())
923 }
924 }
925}
926
927void QQuick3DEffect::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
928{
929 if (change == QQuick3DObject::ItemSceneChange)
930 updateSceneManager(value.sceneManager);
931}
932
934{
935 if (!pass)
936 return;
937
938 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
939 that->m_passes.push_back(pass);
940
941 connect(pass, &QQuick3DShaderUtilsRenderPass::changed, that, &QQuick3DEffect::onPassDirty);
942 that->effectChainDirty();
943}
944
946{
947 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
948 return that->m_passes.at(index);
949}
950
952{
953 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
954 return that->m_passes.size();
955}
956
958{
959 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(list->object);
960
961 for (QQuick3DShaderUtilsRenderPass *pass : that->m_passes)
962 pass->disconnect(that);
963
964 that->m_passes.clear();
965 that->effectChainDirty();
966}
967
968void QQuick3DEffect::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap)
969{
970 // There can only be one texture input per property, as the texture input is a combination
971 // of the texture used and the uniform name!
972 auto it = m_dynamicTextureMaps.constFind(textureMap);
973
974 if (it == m_dynamicTextureMaps.constEnd()) {
975 // Track the object, if it's destroyed we need to remove it from our table.
976 connect(textureMap, &QQuick3DShaderUtilsTextureInput::destroyed, this, [this, textureMap]() {
977 auto it = m_dynamicTextureMaps.constFind(textureMap);
978 if (it != m_dynamicTextureMaps.constEnd())
979 m_dynamicTextureMaps.erase(it);
980 });
981 m_dynamicTextureMaps.insert(textureMap);
982
983 update();
984 }
985}
986
\inmodule QtCore
Definition qbytearray.h:57
qsizetype indexOf(char c, qsizetype from=0) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
void push_back(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:451
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
QByteArray left(qsizetype len) const
Returns a byte array that contains the first len bytes of this byte array.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray mid(qsizetype index, qsizetype len=-1) const
Returns a byte array containing len bytes from this byte array, starting at position pos.
Definition qlist.h:74
void push_back(parameter_type t)
Definition qlist.h:672
\inmodule QtCore
Definition qmetaobject.h:18
bool isValid() const
\inmodule QtCore
\inmodule QtCore
Definition qmetatype.h:320
int id(int=0) const
Definition qmetatype.h:454
friend class QVariant
Definition qmetatype.h:775
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
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
The QQmlListProperty class allows applications to expose list-like properties of QObject-derived clas...
Definition qqmllist.h:24
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)
QPointer< QQuick3DSceneManager > sceneManager
void refSceneManager(QQuick3DSceneManager &)
static QQuick3DObjectPrivate * get(QQuick3DObject *item)
\qmltype Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DObject \inherits QtObject
virtual void markAllDirty()
QPointer< QQuick3DWindowAttachment > wattached
static QSSGRenderTextureFormat::Format mapTextureFormat(QQuick3DShaderUtilsBuffer::TextureFormat fmt)
QSSGRenderImage * getRenderImage()
TilingMode verticalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeVertical
TilingMode horizontalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeHorizontal
const std::shared_ptr< QSSGRenderContextInterface > & rci() const
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
QString first(qsizetype n) const
Definition qstring.h:337
void append(const T &t)
\inmodule QtCore
Definition qvariant.h:64
T value() const &
Definition qvariant.h:511
QMetaType metaType() const
QSet< QString >::iterator it
void ensureDebugObjectName(T *node, QObject *src)
\qmltype Shader \inherits Object \inqmlmodule QtQuick3D
QSSGRenderShaderDataType uniformType(QMetaType type)
QByteArray resolveShader(const QUrl &fileUrl, const QQmlContext *context, QByteArray &shaderPathKey)
QByteArray uniformTypeName(QMetaType type)
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)
std::pair< T1, T2 > QPair
static const QCssKnownValue properties[NumProperties - 1]
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:162
GLuint index
[2]
GLenum type
GLenum GLuint texture
GLenum GLuint GLsizei propCount
GLuint name
GLint first
GLuint GLuint * names
GLuint shader
Definition qopenglext.h:665
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:71
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:45
ptrdiff_t qsizetype
Definition qtypes.h:70
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 fragmentMetaData
QSSGRenderTextureCoordOp horizontalClampType
QSSGRenderTextureCoordOp verticalClampType
QSSGRenderShaderDataType shaderDataType
QSSGRenderTextureFilterOp magFilterType
QSSGRenderTextureFilterOp minFilterType
QSSGRenderTextureFilterOp mipFilterType
QVector< Property > properties
QVector< TextureProperty > textureProperties
static ShaderCodeAndMetaData prepareCustomShader(QByteArray &dst, const QByteArray &shaderCode, QSSGShaderCache::ShaderType type, const StringPairList &baseUniforms, const StringPairList &baseInputs=StringPairList(), const StringPairList &baseOutputs=StringPairList())
QPair< QByteArray, QSSGCustomShaderMetaData > ShaderCodeAndMetaData
Definition moc.h:24
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent