| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include <private/qquickopenglshadereffect_p.h> |
| 41 | |
| 42 | #include <QtQuick/qsgmaterial.h> |
| 43 | #include <QtQuick/private/qsgshadersourcebuilder_p.h> |
| 44 | #include "qquickitem_p.h" |
| 45 | |
| 46 | #include <QtQuick/private/qsgcontext_p.h> |
| 47 | #include <QtQuick/qsgtextureprovider.h> |
| 48 | #include "qquickwindow.h" |
| 49 | |
| 50 | #include "qquickimage_p.h" |
| 51 | #include "qquickshadereffectsource_p.h" |
| 52 | #include "qquickshadereffectmesh_p.h" |
| 53 | |
| 54 | #include <QtQml/qqmlfile.h> |
| 55 | #include <QtCore/qsignalmapper.h> |
| 56 | #include <QtCore/qfileselector.h> |
| 57 | |
| 58 | QT_BEGIN_NAMESPACE |
| 59 | |
| 60 | // Note: this legacy ShaderEffect implementation is used only when running |
| 61 | // directly with OpenGL. This is going to go away in the future (Qt 6?), since |
| 62 | // the RHI path uses QQuickGenericShaderEffect always. |
| 63 | |
| 64 | namespace { |
| 65 | |
| 66 | enum VariableQualifier { |
| 67 | AttributeQualifier, |
| 68 | UniformQualifier |
| 69 | }; |
| 70 | |
| 71 | inline bool qt_isalpha(char c) |
| 72 | { |
| 73 | char ch = c | 0x20; |
| 74 | return (ch >= 'a' && ch <= 'z') || c == '_'; |
| 75 | } |
| 76 | |
| 77 | inline bool qt_isalnum(char c) |
| 78 | { |
| 79 | return qt_isalpha(c) || (c >= '0' && c <= '9'); |
| 80 | } |
| 81 | |
| 82 | inline bool qt_isspace(char c) |
| 83 | { |
| 84 | return c == ' ' || (c >= 0x09 && c <= 0x0d); |
| 85 | } |
| 86 | |
| 87 | // Returns -1 if not found, returns index to first character after the name if found. |
| 88 | int qt_search_for_variable(const char *s, int length, int index, VariableQualifier &decl, |
| 89 | int &typeIndex, int &typeLength, |
| 90 | int &nameIndex, int &nameLength, |
| 91 | QQuickOpenGLShaderEffectCommon::Key::ShaderType shaderType) |
| 92 | { |
| 93 | enum Identifier { |
| 94 | QualifierIdentifier, // Base state |
| 95 | PrecisionIdentifier, |
| 96 | TypeIdentifier, |
| 97 | NameIdentifier |
| 98 | }; |
| 99 | Identifier expected = QualifierIdentifier; |
| 100 | bool compilerDirectiveExpected = index == 0; |
| 101 | |
| 102 | while (index < length) { |
| 103 | // Skip whitespace. |
| 104 | while (qt_isspace(c: s[index])) { |
| 105 | compilerDirectiveExpected |= s[index] == '\n'; |
| 106 | ++index; |
| 107 | } |
| 108 | |
| 109 | if (qt_isalpha(c: s[index])) { |
| 110 | // Read identifier. |
| 111 | int idIndex = index; |
| 112 | ++index; |
| 113 | while (qt_isalnum(c: s[index])) |
| 114 | ++index; |
| 115 | int idLength = index - idIndex; |
| 116 | |
| 117 | const int attrLen = sizeof("attribute" ) - 1; |
| 118 | const int inLen = sizeof("in" ) - 1; |
| 119 | const int uniLen = sizeof("uniform" ) - 1; |
| 120 | const int loLen = sizeof("lowp" ) - 1; |
| 121 | const int medLen = sizeof("mediump" ) - 1; |
| 122 | const int hiLen = sizeof("highp" ) - 1; |
| 123 | |
| 124 | switch (expected) { |
| 125 | case QualifierIdentifier: |
| 126 | if (idLength == attrLen && qstrncmp(str1: "attribute" , str2: s + idIndex, len: attrLen) == 0) { |
| 127 | decl = AttributeQualifier; |
| 128 | expected = PrecisionIdentifier; |
| 129 | } else if (shaderType == QQuickOpenGLShaderEffectCommon::Key::VertexShader |
| 130 | && idLength == inLen && qstrncmp(str1: "in" , str2: s + idIndex, len: inLen) == 0) { |
| 131 | decl = AttributeQualifier; |
| 132 | expected = PrecisionIdentifier; |
| 133 | } else if (idLength == uniLen && qstrncmp(str1: "uniform" , str2: s + idIndex, len: uniLen) == 0) { |
| 134 | decl = UniformQualifier; |
| 135 | expected = PrecisionIdentifier; |
| 136 | } |
| 137 | break; |
| 138 | case PrecisionIdentifier: |
| 139 | if ((idLength == loLen && qstrncmp(str1: "lowp" , str2: s + idIndex, len: loLen) == 0) |
| 140 | || (idLength == medLen && qstrncmp(str1: "mediump" , str2: s + idIndex, len: medLen) == 0) |
| 141 | || (idLength == hiLen && qstrncmp(str1: "highp" , str2: s + idIndex, len: hiLen) == 0)) |
| 142 | { |
| 143 | expected = TypeIdentifier; |
| 144 | break; |
| 145 | } |
| 146 | Q_FALLTHROUGH(); |
| 147 | case TypeIdentifier: |
| 148 | typeIndex = idIndex; |
| 149 | typeLength = idLength; |
| 150 | expected = NameIdentifier; |
| 151 | break; |
| 152 | case NameIdentifier: |
| 153 | nameIndex = idIndex; |
| 154 | nameLength = idLength; |
| 155 | return index; // Attribute or uniform declaration found. Return result. |
| 156 | default: |
| 157 | break; |
| 158 | } |
| 159 | } else if (s[index] == '#' && compilerDirectiveExpected) { |
| 160 | // Skip compiler directives. |
| 161 | ++index; |
| 162 | while (index < length && (s[index] != '\n' || s[index - 1] == '\\')) |
| 163 | ++index; |
| 164 | } else if (s[index] == '/' && s[index + 1] == '/') { |
| 165 | // Skip comments. |
| 166 | index += 2; |
| 167 | while (index < length && s[index] != '\n') |
| 168 | ++index; |
| 169 | } else if (s[index] == '/' && s[index + 1] == '*') { |
| 170 | // Skip comments. |
| 171 | index += 2; |
| 172 | while (index < length && (s[index] != '*' || s[index + 1] != '/')) |
| 173 | ++index; |
| 174 | if (index < length) |
| 175 | index += 2; // Skip star-slash. |
| 176 | } else { |
| 177 | expected = QualifierIdentifier; |
| 178 | ++index; |
| 179 | } |
| 180 | compilerDirectiveExpected = false; |
| 181 | } |
| 182 | return -1; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | namespace QtPrivate { |
| 187 | class MappedSlotObject: public QtPrivate::QSlotObjectBase |
| 188 | { |
| 189 | public: |
| 190 | typedef std::function<void()> PropChangedFunc; |
| 191 | |
| 192 | explicit MappedSlotObject(PropChangedFunc f) |
| 193 | : QSlotObjectBase(&impl), _signalIndex(-1), func(std::move(f)) |
| 194 | { ref(); } |
| 195 | |
| 196 | void setSignalIndex(int idx) { _signalIndex = idx; } |
| 197 | int signalIndex() const { return _signalIndex; } |
| 198 | |
| 199 | private: |
| 200 | int _signalIndex; |
| 201 | PropChangedFunc func; |
| 202 | |
| 203 | static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret) |
| 204 | { |
| 205 | auto thiz = static_cast<MappedSlotObject*>(this_); |
| 206 | switch (which) { |
| 207 | case Destroy: |
| 208 | delete thiz; |
| 209 | break; |
| 210 | case Call: |
| 211 | thiz->func(); |
| 212 | break; |
| 213 | case Compare: |
| 214 | *ret = thiz == reinterpret_cast<MappedSlotObject *>(a[0]); |
| 215 | break; |
| 216 | case NumOperations: ; |
| 217 | } |
| 218 | } |
| 219 | }; |
| 220 | } |
| 221 | |
| 222 | QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon() |
| 223 | { |
| 224 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) |
| 225 | clearSignalMappers(shader: shaderType); |
| 226 | } |
| 227 | |
| 228 | void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, Key::ShaderType shaderType) |
| 229 | { |
| 230 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 231 | if (signalMappers[shaderType].at(i) == 0) |
| 232 | continue; |
| 233 | const UniformData &d = uniformData[shaderType].at(i); |
| 234 | auto mapper = signalMappers[shaderType].at(i); |
| 235 | void *a = mapper; |
| 236 | QObjectPrivate::disconnect(sender: item, signal_index: mapper->signalIndex(), slot: &a); |
| 237 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
| 238 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 239 | if (source) { |
| 240 | if (item->window()) |
| 241 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 242 | QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*))); |
| 243 | } |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | void QQuickOpenGLShaderEffectCommon::connectPropertySignals(QQuickItem *item, |
| 249 | const QMetaObject *itemMetaObject, |
| 250 | Key::ShaderType shaderType) |
| 251 | { |
| 252 | auto engine = qmlEngine(item); |
| 253 | if (!engine) |
| 254 | return; |
| 255 | |
| 256 | QQmlPropertyCache *propCache = QQmlData::ensurePropertyCache(engine, object: item); |
| 257 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 258 | if (signalMappers[shaderType].at(i) == 0) |
| 259 | continue; |
| 260 | const UniformData &d = uniformData[shaderType].at(i); |
| 261 | QQmlPropertyData *pd = propCache->property(key: QString::fromUtf8(str: d.name), object: nullptr, context: nullptr); |
| 262 | if (pd && !pd->isFunction()) { |
| 263 | if (pd->notifyIndex() == -1) { |
| 264 | qWarning(msg: "QQuickOpenGLShaderEffect: property '%s' does not have notification method!" , d.name.constData()); |
| 265 | } else { |
| 266 | auto *mapper = signalMappers[shaderType].at(i); |
| 267 | mapper->setSignalIndex(itemMetaObject->property(index: d.propertyIndex).notifySignal().methodIndex()); |
| 268 | Q_ASSERT(item->metaObject() == itemMetaObject); |
| 269 | bool ok = QObjectPrivate::connectImpl(sender: item, signal_index: pd->notifyIndex(), receiver: item, slot: nullptr, slotObj: mapper, |
| 270 | type: Qt::AutoConnection, types: nullptr, senderMetaObject: itemMetaObject); |
| 271 | if (!ok) |
| 272 | qWarning() << "Failed to connect to property" << itemMetaObject->property(index: d.propertyIndex).name() |
| 273 | << "(" << d.propertyIndex << ", signal index" << pd->notifyIndex() |
| 274 | << ") of item" << item; |
| 275 | } |
| 276 | } else { |
| 277 | // If the source is set via a dynamic property, like the layer is, then we need this |
| 278 | // check to disable the warning. |
| 279 | if (!item->property(name: d.name).isValid()) |
| 280 | qWarning(msg: "QQuickOpenGLShaderEffect: '%s' does not have a matching property!" , d.name.constData()); |
| 281 | } |
| 282 | |
| 283 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
| 284 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 285 | if (source) { |
| 286 | if (item->window()) |
| 287 | QQuickItemPrivate::get(item: source)->refWindow(item->window()); |
| 288 | QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*))); |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | void QQuickOpenGLShaderEffectCommon::updateParseLog(bool ignoreAttributes) |
| 295 | { |
| 296 | parseLog.clear(); |
| 297 | if (!ignoreAttributes) { |
| 298 | if (!attributes.contains(t: qtPositionAttributeName())) { |
| 299 | parseLog += QLatin1String("Warning: Missing reference to \'" ) |
| 300 | + QLatin1String(qtPositionAttributeName()) |
| 301 | + QLatin1String("\'.\n" ); |
| 302 | } |
| 303 | if (!attributes.contains(t: qtTexCoordAttributeName())) { |
| 304 | parseLog += QLatin1String("Warning: Missing reference to \'" ) |
| 305 | + QLatin1String(qtTexCoordAttributeName()) |
| 306 | + QLatin1String("\'.\n" ); |
| 307 | } |
| 308 | } |
| 309 | bool respectsMatrix = false; |
| 310 | bool respectsOpacity = false; |
| 311 | for (int i = 0; i < uniformData[Key::VertexShader].size(); ++i) |
| 312 | respectsMatrix |= uniformData[Key::VertexShader].at(i).specialType == UniformData::Matrix; |
| 313 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 314 | for (int i = 0; i < uniformData[shaderType].size(); ++i) |
| 315 | respectsOpacity |= uniformData[shaderType].at(i).specialType == UniformData::Opacity; |
| 316 | } |
| 317 | if (!respectsMatrix) |
| 318 | parseLog += QLatin1String("Warning: Vertex shader is missing reference to \'qt_Matrix\'.\n" ); |
| 319 | if (!respectsOpacity) |
| 320 | parseLog += QLatin1String("Warning: Shaders are missing reference to \'qt_Opacity\'.\n" ); |
| 321 | } |
| 322 | |
| 323 | void QQuickOpenGLShaderEffectCommon::lookThroughShaderCode(QQuickItem *item, |
| 324 | const QMetaObject *itemMetaObject, |
| 325 | Key::ShaderType shaderType, |
| 326 | const QByteArray &code) |
| 327 | { |
| 328 | auto engine = qmlEngine(item); |
| 329 | QQmlPropertyCache *propCache = (engine) ? QQmlData::ensurePropertyCache(engine, object: item) : nullptr; |
| 330 | int index = 0; |
| 331 | int typeIndex = -1; |
| 332 | int typeLength = 0; |
| 333 | int nameIndex = -1; |
| 334 | int nameLength = 0; |
| 335 | const char *s = code.constData(); |
| 336 | VariableQualifier decl = AttributeQualifier; |
| 337 | while ((index = qt_search_for_variable(s, length: code.size(), index, decl, typeIndex, typeLength, |
| 338 | nameIndex, nameLength, shaderType)) != -1) |
| 339 | { |
| 340 | if (decl == AttributeQualifier) { |
| 341 | if (shaderType == Key::VertexShader) |
| 342 | attributes.append(t: QByteArray(s + nameIndex, nameLength)); |
| 343 | } else { |
| 344 | Q_ASSERT(decl == UniformQualifier); |
| 345 | |
| 346 | const int sampLen = sizeof("sampler2D" ) - 1; |
| 347 | const int sampExtLen = sizeof("samplerExternalOES" ) - 1; |
| 348 | const int opLen = sizeof("qt_Opacity" ) - 1; |
| 349 | const int matLen = sizeof("qt_Matrix" ) - 1; |
| 350 | const int srLen = sizeof("qt_SubRect_" ) - 1; |
| 351 | |
| 352 | UniformData d; |
| 353 | QtPrivate::MappedSlotObject *mapper = nullptr; |
| 354 | d.name = QByteArray(s + nameIndex, nameLength); |
| 355 | if (nameLength == opLen && qstrncmp(str1: "qt_Opacity" , str2: s + nameIndex, len: opLen) == 0) { |
| 356 | d.specialType = UniformData::Opacity; |
| 357 | } else if (nameLength == matLen && qstrncmp(str1: "qt_Matrix" , str2: s + nameIndex, len: matLen) == 0) { |
| 358 | d.specialType = UniformData::Matrix; |
| 359 | } else if (nameLength > srLen && qstrncmp(str1: "qt_SubRect_" , str2: s + nameIndex, len: srLen) == 0) { |
| 360 | d.specialType = UniformData::SubRect; |
| 361 | } else { |
| 362 | if (propCache) { |
| 363 | if (QQmlPropertyData *pd = propCache->property(key: QString::fromUtf8(str: d.name), object: nullptr, context: nullptr)) { |
| 364 | if (!pd->isFunction()) |
| 365 | d.propertyIndex = pd->coreIndex(); |
| 366 | } |
| 367 | } |
| 368 | const int mappedId = uniformData[shaderType].size() | (shaderType << 16); |
| 369 | mapper = new QtPrivate::MappedSlotObject([this, mappedId](){ |
| 370 | this->mappedPropertyChanged(mappedId); |
| 371 | }); |
| 372 | if (typeLength == sampLen && qstrncmp(str1: "sampler2D" , str2: s + typeIndex, len: sampLen) == 0) |
| 373 | d.specialType = UniformData::Sampler; |
| 374 | else if (typeLength == sampExtLen && qstrncmp(str1: "samplerExternalOES" , str2: s + typeIndex, len: sampExtLen) == 0) |
| 375 | d.specialType = UniformData::SamplerExternal; |
| 376 | else |
| 377 | d.specialType = UniformData::None; |
| 378 | d.setValueFromProperty(item, itemMetaObject); |
| 379 | } |
| 380 | uniformData[shaderType].append(t: d); |
| 381 | signalMappers[shaderType].append(t: mapper); |
| 382 | } |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | void QQuickOpenGLShaderEffectCommon::updateShader(QQuickItem *item, |
| 387 | const QMetaObject *itemMetaObject, |
| 388 | Key::ShaderType shaderType) |
| 389 | { |
| 390 | disconnectPropertySignals(item, shaderType); |
| 391 | uniformData[shaderType].clear(); |
| 392 | clearSignalMappers(shader: shaderType); |
| 393 | if (shaderType == Key::VertexShader) |
| 394 | attributes.clear(); |
| 395 | |
| 396 | // A qrc or file URL means the shader source is to be read from the specified file. |
| 397 | QUrl srcUrl(QString::fromUtf8(str: source.sourceCode[shaderType])); |
| 398 | if (!srcUrl.scheme().compare(other: QLatin1String("qrc" ), cs: Qt::CaseInsensitive) || srcUrl.isLocalFile()) { |
| 399 | if (!fileSelector) { |
| 400 | fileSelector = new QFileSelector(item); |
| 401 | // There may not be an OpenGL context accessible here. So rely on |
| 402 | // the window's requestedFormat(). |
| 403 | if (item->window() |
| 404 | && item->window()->requestedFormat().profile() == QSurfaceFormat::CoreProfile) { |
| 405 | fileSelector->setExtraSelectors(QStringList() << QStringLiteral("glslcore" )); |
| 406 | } |
| 407 | } |
| 408 | const QString fn = fileSelector->select(filePath: QQmlFile::urlToLocalFileOrQrc(srcUrl)); |
| 409 | QFile f(fn); |
| 410 | if (f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) { |
| 411 | source.sourceCode[shaderType] = f.readAll(); |
| 412 | f.close(); |
| 413 | } else { |
| 414 | qWarning(msg: "ShaderEffect: Failed to read %s" , qPrintable(fn)); |
| 415 | source.sourceCode[shaderType] = QByteArray(); |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | const QByteArray &code = source.sourceCode[shaderType]; |
| 420 | if (code.isEmpty()) { |
| 421 | // Optimize for default code. |
| 422 | if (shaderType == Key::VertexShader) { |
| 423 | attributes.append(t: QByteArray(qtPositionAttributeName())); |
| 424 | attributes.append(t: QByteArray(qtTexCoordAttributeName())); |
| 425 | UniformData d; |
| 426 | d.name = "qt_Matrix" ; |
| 427 | d.specialType = UniformData::Matrix; |
| 428 | uniformData[Key::VertexShader].append(t: d); |
| 429 | signalMappers[Key::VertexShader].append(t: 0); |
| 430 | } else if (shaderType == Key::FragmentShader) { |
| 431 | UniformData d; |
| 432 | d.name = "qt_Opacity" ; |
| 433 | d.specialType = UniformData::Opacity; |
| 434 | uniformData[Key::FragmentShader].append(t: d); |
| 435 | signalMappers[Key::FragmentShader].append(t: 0); |
| 436 | auto mapper = new QtPrivate::MappedSlotObject([this](){ |
| 437 | mappedPropertyChanged(1 | (Key::FragmentShader << 16)); |
| 438 | }); |
| 439 | const char *sourceName = "source" ; |
| 440 | d.name = sourceName; |
| 441 | d.setValueFromProperty(item, itemMetaObject); |
| 442 | d.specialType = UniformData::Sampler; |
| 443 | uniformData[Key::FragmentShader].append(t: d); |
| 444 | signalMappers[Key::FragmentShader].append(t: mapper); |
| 445 | } |
| 446 | } else { |
| 447 | lookThroughShaderCode(item, itemMetaObject, shaderType, code); |
| 448 | } |
| 449 | |
| 450 | connectPropertySignals(item, itemMetaObject, shaderType); |
| 451 | } |
| 452 | |
| 453 | void QQuickOpenGLShaderEffectCommon::updateMaterial(QQuickOpenGLShaderEffectNode *node, |
| 454 | QQuickOpenGLShaderEffectMaterial *material, |
| 455 | bool updateUniforms, bool updateUniformValues, |
| 456 | bool updateTextureProviders) |
| 457 | { |
| 458 | if (updateUniforms) { |
| 459 | for (int i = 0; i < material->textureProviders.size(); ++i) { |
| 460 | QSGTextureProvider *t = material->textureProviders.at(i); |
| 461 | if (t) { |
| 462 | QObject::disconnect(sender: t, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture())); |
| 463 | QObject::disconnect(sender: t, SIGNAL(destroyed(QObject*)), receiver: node, SLOT(textureProviderDestroyed(QObject*))); |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | // First make room in the textureProviders array. Set to proper value further down. |
| 468 | int textureProviderCount = 0; |
| 469 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 470 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 471 | if (uniformData[shaderType].at(i).specialType == UniformData::Sampler || |
| 472 | uniformData[shaderType].at(i).specialType == UniformData::SamplerExternal) |
| 473 | ++textureProviderCount; |
| 474 | } |
| 475 | material->uniforms[shaderType] = uniformData[shaderType]; |
| 476 | } |
| 477 | material->textureProviders.fill(t: 0, size: textureProviderCount); |
| 478 | updateUniformValues = false; |
| 479 | updateTextureProviders = true; |
| 480 | } |
| 481 | |
| 482 | if (updateUniformValues) { |
| 483 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 484 | Q_ASSERT(uniformData[shaderType].size() == material->uniforms[shaderType].size()); |
| 485 | for (int i = 0; i < uniformData[shaderType].size(); ++i) |
| 486 | material->uniforms[shaderType][i].value = uniformData[shaderType].at(i).value; |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | if (updateTextureProviders) { |
| 491 | int index = 0; |
| 492 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 493 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 494 | const UniformData &d = uniformData[shaderType].at(i); |
| 495 | if (d.specialType != UniformData::Sampler && d.specialType != UniformData::SamplerExternal) |
| 496 | continue; |
| 497 | QSGTextureProvider *oldProvider = material->textureProviders.at(i: index); |
| 498 | QSGTextureProvider *newProvider = nullptr; |
| 499 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 500 | if (source && source->isTextureProvider()) |
| 501 | newProvider = source->textureProvider(); |
| 502 | if (newProvider != oldProvider) { |
| 503 | if (oldProvider) { |
| 504 | QObject::disconnect(sender: oldProvider, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture())); |
| 505 | QObject::disconnect(sender: oldProvider, SIGNAL(destroyed(QObject*)), receiver: node, SLOT(textureProviderDestroyed(QObject*))); |
| 506 | } |
| 507 | if (newProvider) { |
| 508 | Q_ASSERT_X(newProvider->thread() == QThread::currentThread(), |
| 509 | "QQuickOpenGLShaderEffect::updatePaintNode" , |
| 510 | "Texture provider must belong to the rendering thread" ); |
| 511 | QObject::connect(sender: newProvider, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture())); |
| 512 | QObject::connect(sender: newProvider, SIGNAL(destroyed(QObject*)), receiver: node, SLOT(textureProviderDestroyed(QObject*))); |
| 513 | } else { |
| 514 | const char *typeName = source ? source->metaObject()->className() : d.value.typeName(); |
| 515 | qWarning(msg: "ShaderEffect: Property '%s' is not assigned a valid texture provider (%s)." , |
| 516 | d.name.constData(), typeName); |
| 517 | } |
| 518 | material->textureProviders[index] = newProvider; |
| 519 | } |
| 520 | ++index; |
| 521 | } |
| 522 | } |
| 523 | Q_ASSERT(index == material->textureProviders.size()); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | void QQuickOpenGLShaderEffectCommon::updateWindow(QQuickWindow *window) |
| 528 | { |
| 529 | // See comment in QQuickOpenGLShaderEffectCommon::propertyChanged(). |
| 530 | if (window) { |
| 531 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 532 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 533 | const UniformData &d = uniformData[shaderType].at(i); |
| 534 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
| 535 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 536 | if (source) |
| 537 | QQuickItemPrivate::get(item: source)->refWindow(window); |
| 538 | } |
| 539 | } |
| 540 | } |
| 541 | } else { |
| 542 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 543 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 544 | const UniformData &d = uniformData[shaderType].at(i); |
| 545 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
| 546 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 547 | if (source) |
| 548 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 549 | } |
| 550 | } |
| 551 | } |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | void QQuickOpenGLShaderEffectCommon::sourceDestroyed(QObject *object) |
| 556 | { |
| 557 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 558 | for (int i = 0; i < uniformData[shaderType].size(); ++i) { |
| 559 | UniformData &d = uniformData[shaderType][i]; |
| 560 | if ((d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) && d.value.canConvert<QObject *>()) { |
| 561 | if (qvariant_cast<QObject *>(v: d.value) == object) |
| 562 | d.value = QVariant(); |
| 563 | } |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | static bool qquick_uniqueInUniformData(QQuickItem *source, const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> *uniformData, int typeToSkip, int indexToSkip) |
| 569 | { |
| 570 | for (int s=0; s<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++s) { |
| 571 | for (int i=0; i<uniformData[s].size(); ++i) { |
| 572 | if (s == typeToSkip && i == indexToSkip) |
| 573 | continue; |
| 574 | const QQuickOpenGLShaderEffectMaterial::UniformData &d = uniformData[s][i]; |
| 575 | if ((d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::Sampler || d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::SamplerExternal) && qvariant_cast<QObject *>(v: d.value) == source) |
| 576 | return false; |
| 577 | } |
| 578 | } |
| 579 | return true; |
| 580 | } |
| 581 | |
| 582 | void QQuickOpenGLShaderEffectCommon::propertyChanged(QQuickItem *item, |
| 583 | const QMetaObject *itemMetaObject, |
| 584 | int mappedId, bool *textureProviderChanged) |
| 585 | { |
| 586 | Key::ShaderType shaderType = Key::ShaderType(mappedId >> 16); |
| 587 | int index = mappedId & 0xffff; |
| 588 | UniformData &d = uniformData[shaderType][index]; |
| 589 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
| 590 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 591 | if (source) { |
| 592 | if (item->window()) |
| 593 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 594 | |
| 595 | // QObject::disconnect() will disconnect all matching connections. If the same |
| 596 | // source has been attached to two separate samplers, then changing one of them |
| 597 | // would trigger both to be disconnected. Without the connection we'll end up |
| 598 | // with a dangling pointer in the uniformData. |
| 599 | if (qquick_uniqueInUniformData(source, uniformData, typeToSkip: shaderType, indexToSkip: index)) |
| 600 | QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*))); |
| 601 | } |
| 602 | |
| 603 | d.setValueFromProperty(item, itemMetaObject); |
| 604 | |
| 605 | source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value)); |
| 606 | if (source) { |
| 607 | // 'source' needs a window to get a scene graph node. It usually gets one through its |
| 608 | // parent, but if the source item is "inline" rather than a reference -- i.e. |
| 609 | // "property variant source: Image { }" instead of "property variant source: foo" -- it |
| 610 | // will not get a parent. In those cases, 'source' should get the window from 'item'. |
| 611 | if (item->window()) |
| 612 | QQuickItemPrivate::get(item: source)->refWindow(item->window()); |
| 613 | QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*))); |
| 614 | } |
| 615 | if (textureProviderChanged) |
| 616 | *textureProviderChanged = true; |
| 617 | } else { |
| 618 | d.setValueFromProperty(item, itemMetaObject); |
| 619 | if (textureProviderChanged) |
| 620 | *textureProviderChanged = false; |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | void QQuickOpenGLShaderEffectCommon::clearSignalMappers(int shader) |
| 625 | { |
| 626 | for (auto mapper : qAsConst(t&: signalMappers[shader])) { |
| 627 | if (mapper) |
| 628 | mapper->destroyIfLastRef(); |
| 629 | } |
| 630 | signalMappers[shader].clear(); |
| 631 | } |
| 632 | |
| 633 | QQuickOpenGLShaderEffect::QQuickOpenGLShaderEffect(QQuickShaderEffect *item, QObject *parent) |
| 634 | : QObject(parent) |
| 635 | , m_item(item) |
| 636 | , m_itemMetaObject(nullptr) |
| 637 | , m_meshResolution(1, 1) |
| 638 | , m_mesh(nullptr) |
| 639 | , m_cullMode(QQuickShaderEffect::NoCulling) |
| 640 | , m_status(QQuickShaderEffect::Uncompiled) |
| 641 | , m_common(this, [this](int mappedId){this->propertyChanged(mappedId);}) |
| 642 | , m_blending(true) |
| 643 | , m_dirtyUniforms(true) |
| 644 | , m_dirtyUniformValues(true) |
| 645 | , m_dirtyTextureProviders(true) |
| 646 | , m_dirtyProgram(true) |
| 647 | , m_dirtyParseLog(true) |
| 648 | , m_dirtyMesh(true) |
| 649 | , m_dirtyGeometry(true) |
| 650 | , m_customVertexShader(false) |
| 651 | , m_supportsAtlasTextures(false) |
| 652 | , m_vertNeedsUpdate(true) |
| 653 | , m_fragNeedsUpdate(true) |
| 654 | { |
| 655 | } |
| 656 | |
| 657 | QQuickOpenGLShaderEffect::~QQuickOpenGLShaderEffect() |
| 658 | { |
| 659 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) |
| 660 | m_common.disconnectPropertySignals(item: m_item, shaderType: Key::ShaderType(shaderType)); |
| 661 | } |
| 662 | |
| 663 | void QQuickOpenGLShaderEffect::setFragmentShader(const QByteArray &code) |
| 664 | { |
| 665 | if (m_common.source.sourceCode[Key::FragmentShader].constData() == code.constData()) |
| 666 | return; |
| 667 | m_common.source.sourceCode[Key::FragmentShader] = code; |
| 668 | m_dirtyProgram = true; |
| 669 | m_dirtyParseLog = true; |
| 670 | |
| 671 | m_fragNeedsUpdate = true; |
| 672 | if (m_item->isComponentComplete()) |
| 673 | maybeUpdateShaders(); |
| 674 | |
| 675 | m_item->update(); |
| 676 | if (m_status != QQuickShaderEffect::Uncompiled) { |
| 677 | m_status = QQuickShaderEffect::Uncompiled; |
| 678 | emit m_item->statusChanged(); |
| 679 | } |
| 680 | emit m_item->fragmentShaderChanged(); |
| 681 | } |
| 682 | |
| 683 | void QQuickOpenGLShaderEffect::setVertexShader(const QByteArray &code) |
| 684 | { |
| 685 | if (m_common.source.sourceCode[Key::VertexShader].constData() == code.constData()) |
| 686 | return; |
| 687 | m_common.source.sourceCode[Key::VertexShader] = code; |
| 688 | m_dirtyProgram = true; |
| 689 | m_dirtyParseLog = true; |
| 690 | m_customVertexShader = true; |
| 691 | |
| 692 | m_vertNeedsUpdate = true; |
| 693 | if (m_item->isComponentComplete()) |
| 694 | maybeUpdateShaders(); |
| 695 | |
| 696 | m_item->update(); |
| 697 | if (m_status != QQuickShaderEffect::Uncompiled) { |
| 698 | m_status = QQuickShaderEffect::Uncompiled; |
| 699 | emit m_item->statusChanged(); |
| 700 | } |
| 701 | emit m_item->vertexShaderChanged(); |
| 702 | } |
| 703 | |
| 704 | void QQuickOpenGLShaderEffect::setBlending(bool enable) |
| 705 | { |
| 706 | if (blending() == enable) |
| 707 | return; |
| 708 | |
| 709 | m_blending = enable; |
| 710 | m_item->update(); |
| 711 | |
| 712 | emit m_item->blendingChanged(); |
| 713 | } |
| 714 | |
| 715 | QVariant QQuickOpenGLShaderEffect::mesh() const |
| 716 | { |
| 717 | return m_mesh ? QVariant::fromValue(value: static_cast<QObject *>(m_mesh)) |
| 718 | : QVariant::fromValue(value: m_meshResolution); |
| 719 | } |
| 720 | |
| 721 | void QQuickOpenGLShaderEffect::setMesh(const QVariant &mesh) |
| 722 | { |
| 723 | QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(object: qvariant_cast<QObject *>(v: mesh)); |
| 724 | if (newMesh && newMesh == m_mesh) |
| 725 | return; |
| 726 | if (m_mesh) |
| 727 | disconnect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, member: nullptr); |
| 728 | m_mesh = newMesh; |
| 729 | if (m_mesh) { |
| 730 | connect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, SLOT(updateGeometry())); |
| 731 | } else { |
| 732 | if (mesh.canConvert<QSize>()) { |
| 733 | m_meshResolution = mesh.toSize(); |
| 734 | } else { |
| 735 | QList<QByteArray> res = mesh.toByteArray().split(sep: 'x'); |
| 736 | bool ok = res.size() == 2; |
| 737 | if (ok) { |
| 738 | int w = res.at(i: 0).toInt(ok: &ok); |
| 739 | if (ok) { |
| 740 | int h = res.at(i: 1).toInt(ok: &ok); |
| 741 | if (ok) |
| 742 | m_meshResolution = QSize(w, h); |
| 743 | } |
| 744 | } |
| 745 | if (!ok) |
| 746 | qWarning(msg: "ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh." ); |
| 747 | } |
| 748 | m_defaultMesh.setResolution(m_meshResolution); |
| 749 | } |
| 750 | |
| 751 | m_dirtyMesh = true; |
| 752 | m_dirtyParseLog = true; |
| 753 | m_item->update(); |
| 754 | emit m_item->meshChanged(); |
| 755 | } |
| 756 | |
| 757 | void QQuickOpenGLShaderEffect::setCullMode(QQuickShaderEffect::CullMode face) |
| 758 | { |
| 759 | if (face == m_cullMode) |
| 760 | return; |
| 761 | m_cullMode = face; |
| 762 | m_item->update(); |
| 763 | emit m_item->cullModeChanged(); |
| 764 | } |
| 765 | |
| 766 | void QQuickOpenGLShaderEffect::setSupportsAtlasTextures(bool supports) |
| 767 | { |
| 768 | if (supports == m_supportsAtlasTextures) |
| 769 | return; |
| 770 | m_supportsAtlasTextures = supports; |
| 771 | updateGeometry(); |
| 772 | emit m_item->supportsAtlasTexturesChanged(); |
| 773 | } |
| 774 | |
| 775 | QString QQuickOpenGLShaderEffect::parseLog() |
| 776 | { |
| 777 | maybeUpdateShaders(force: true); |
| 778 | |
| 779 | if (m_dirtyParseLog) { |
| 780 | m_common.updateParseLog(ignoreAttributes: m_mesh != nullptr); |
| 781 | m_dirtyParseLog = false; |
| 782 | } |
| 783 | return m_common.parseLog; |
| 784 | } |
| 785 | |
| 786 | void QQuickOpenGLShaderEffect::handleEvent(QEvent *event) |
| 787 | { |
| 788 | if (event->type() == QEvent::DynamicPropertyChange) { |
| 789 | QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event); |
| 790 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { |
| 791 | for (int i = 0; i < m_common.uniformData[shaderType].size(); ++i) { |
| 792 | if (m_common.uniformData[shaderType].at(i).name == e->propertyName()) { |
| 793 | bool textureProviderChanged; |
| 794 | m_common.propertyChanged(item: m_item, itemMetaObject: m_itemMetaObject, |
| 795 | mappedId: (shaderType << 16) | i, textureProviderChanged: &textureProviderChanged); |
| 796 | m_dirtyTextureProviders |= textureProviderChanged; |
| 797 | m_dirtyUniformValues = true; |
| 798 | m_item->update(); |
| 799 | } |
| 800 | } |
| 801 | } |
| 802 | } |
| 803 | } |
| 804 | |
| 805 | void QQuickOpenGLShaderEffect::updateGeometry() |
| 806 | { |
| 807 | m_dirtyGeometry = true; |
| 808 | m_item->update(); |
| 809 | } |
| 810 | |
| 811 | void QQuickOpenGLShaderEffect::updateGeometryIfAtlased() |
| 812 | { |
| 813 | if (m_supportsAtlasTextures) |
| 814 | updateGeometry(); |
| 815 | } |
| 816 | |
| 817 | void QQuickOpenGLShaderEffect::updateLogAndStatus(const QString &log, int status) |
| 818 | { |
| 819 | m_log = parseLog() + log; |
| 820 | m_status = QQuickShaderEffect::Status(status); |
| 821 | emit m_item->logChanged(); |
| 822 | emit m_item->statusChanged(); |
| 823 | } |
| 824 | |
| 825 | void QQuickOpenGLShaderEffect::sourceDestroyed(QObject *object) |
| 826 | { |
| 827 | m_common.sourceDestroyed(object); |
| 828 | } |
| 829 | |
| 830 | void QQuickOpenGLShaderEffect::propertyChanged(int mappedId) |
| 831 | { |
| 832 | bool textureProviderChanged; |
| 833 | m_common.propertyChanged(item: m_item, itemMetaObject: m_itemMetaObject, mappedId, textureProviderChanged: &textureProviderChanged); |
| 834 | m_dirtyTextureProviders |= textureProviderChanged; |
| 835 | m_dirtyUniformValues = true; |
| 836 | m_item->update(); |
| 837 | } |
| 838 | |
| 839 | void QQuickOpenGLShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &) |
| 840 | { |
| 841 | m_dirtyGeometry = true; |
| 842 | } |
| 843 | |
| 844 | QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) |
| 845 | { |
| 846 | QQuickOpenGLShaderEffectNode *node = static_cast<QQuickOpenGLShaderEffectNode *>(oldNode); |
| 847 | |
| 848 | // In the case of zero-size or a bad vertex shader, don't try to create a node... |
| 849 | if (m_common.attributes.isEmpty() || m_item->width() <= 0 || m_item->height() <= 0) { |
| 850 | if (node) |
| 851 | delete node; |
| 852 | return nullptr; |
| 853 | } |
| 854 | |
| 855 | if (!node) { |
| 856 | node = new QQuickOpenGLShaderEffectNode; |
| 857 | node->setMaterial(new QQuickOpenGLShaderEffectMaterial(node)); |
| 858 | node->setFlag(QSGNode::OwnsMaterial, true); |
| 859 | m_dirtyProgram = true; |
| 860 | m_dirtyUniforms = true; |
| 861 | m_dirtyGeometry = true; |
| 862 | connect(sender: node, SIGNAL(logAndStatusChanged(QString,int)), receiver: this, SLOT(updateLogAndStatus(QString,int))); |
| 863 | connect(sender: node, signal: &QQuickOpenGLShaderEffectNode::dirtyTexture, |
| 864 | receiver: this, slot: &QQuickOpenGLShaderEffect::updateGeometryIfAtlased); |
| 865 | } |
| 866 | |
| 867 | QQuickOpenGLShaderEffectMaterial *material = static_cast<QQuickOpenGLShaderEffectMaterial *>(node->material()); |
| 868 | |
| 869 | // Update blending |
| 870 | if (bool(material->flags() & QSGMaterial::Blending) != m_blending) { |
| 871 | material->setFlag(flags: QSGMaterial::Blending, on: m_blending); |
| 872 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 873 | } |
| 874 | |
| 875 | if (int(material->cullMode) != int(m_cullMode)) { |
| 876 | material->cullMode = QQuickShaderEffect::CullMode(m_cullMode); |
| 877 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 878 | } |
| 879 | |
| 880 | if (m_dirtyProgram) { |
| 881 | Key s = m_common.source; |
| 882 | QSGShaderSourceBuilder builder; |
| 883 | if (s.sourceCode[Key::FragmentShader].isEmpty()) { |
| 884 | builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.frag" )); |
| 885 | s.sourceCode[Key::FragmentShader] = builder.source(); |
| 886 | builder.clear(); |
| 887 | } |
| 888 | if (s.sourceCode[Key::VertexShader].isEmpty()) { |
| 889 | builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.vert" )); |
| 890 | s.sourceCode[Key::VertexShader] = builder.source(); |
| 891 | } |
| 892 | |
| 893 | material->setProgramSource(s); |
| 894 | material->attributes = m_common.attributes; |
| 895 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 896 | m_dirtyProgram = false; |
| 897 | m_dirtyUniforms = true; |
| 898 | } |
| 899 | |
| 900 | if (m_dirtyUniforms || m_dirtyUniformValues || m_dirtyTextureProviders) { |
| 901 | m_common.updateMaterial(node, material, updateUniforms: m_dirtyUniforms, updateUniformValues: m_dirtyUniformValues, |
| 902 | updateTextureProviders: m_dirtyTextureProviders); |
| 903 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 904 | m_dirtyUniforms = m_dirtyUniformValues = m_dirtyTextureProviders = false; |
| 905 | } |
| 906 | |
| 907 | QRectF srcRect(0, 0, 1, 1); |
| 908 | bool geometryUsesTextureSubRect = false; |
| 909 | if (m_supportsAtlasTextures && material->textureProviders.size() == 1) { |
| 910 | QSGTextureProvider *provider = material->textureProviders.at(i: 0); |
| 911 | if (provider && provider->texture()) { |
| 912 | srcRect = provider->texture()->normalizedTextureSubRect(); |
| 913 | geometryUsesTextureSubRect = true; |
| 914 | } |
| 915 | } |
| 916 | |
| 917 | if (bool(material->flags() & QSGMaterial::RequiresFullMatrix) != m_customVertexShader) { |
| 918 | material->setFlag(flags: QSGMaterial::RequiresFullMatrix, on: m_customVertexShader); |
| 919 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 920 | } |
| 921 | |
| 922 | if (material->geometryUsesTextureSubRect != geometryUsesTextureSubRect) { |
| 923 | material->geometryUsesTextureSubRect = geometryUsesTextureSubRect; |
| 924 | node->markDirty(bits: QSGNode::DirtyMaterial); |
| 925 | } |
| 926 | |
| 927 | if (m_dirtyMesh) { |
| 928 | node->setGeometry(nullptr); |
| 929 | m_dirtyMesh = false; |
| 930 | m_dirtyGeometry = true; |
| 931 | } |
| 932 | |
| 933 | if (m_dirtyGeometry) { |
| 934 | node->setFlag(QSGNode::OwnsGeometry, false); |
| 935 | QSGGeometry *geometry = node->geometry(); |
| 936 | QRectF rect(0, 0, m_item->width(), m_item->height()); |
| 937 | QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh; |
| 938 | |
| 939 | int posIndex = 0; |
| 940 | if (!mesh->validateAttributes(attributes: m_common.attributes, posIndex: &posIndex)) { |
| 941 | QString log = mesh->log(); |
| 942 | if (!log.isNull()) { |
| 943 | m_log = parseLog() + QLatin1String("*** Mesh ***\n" ) + log; |
| 944 | m_status = QQuickShaderEffect::Error; |
| 945 | emit m_item->logChanged(); |
| 946 | emit m_item->statusChanged(); |
| 947 | } |
| 948 | delete node; |
| 949 | return nullptr; |
| 950 | } |
| 951 | |
| 952 | geometry = mesh->updateGeometry(geometry, attrCount: m_common.attributes.count(), posIndex, srcRect, rect); |
| 953 | |
| 954 | node->setGeometry(geometry); |
| 955 | node->setFlag(QSGNode::OwnsGeometry, true); |
| 956 | |
| 957 | m_dirtyGeometry = false; |
| 958 | } |
| 959 | |
| 960 | return node; |
| 961 | } |
| 962 | |
| 963 | void QQuickOpenGLShaderEffect::maybeUpdateShaders(bool force) |
| 964 | { |
| 965 | if (!m_itemMetaObject) |
| 966 | m_itemMetaObject = m_item->metaObject(); |
| 967 | |
| 968 | // Defer processing if a window is not yet associated with the item. This |
| 969 | // is because the actual scenegraph backend is not known so conditions |
| 970 | // based on GraphicsInfo.shaderType and similar evaluate to wrong results. |
| 971 | if (!m_item->window() && !force) { |
| 972 | m_item->polish(); |
| 973 | return; |
| 974 | } |
| 975 | |
| 976 | if (m_vertNeedsUpdate) { |
| 977 | m_vertNeedsUpdate = false; |
| 978 | m_common.updateShader(item: m_item, itemMetaObject: m_itemMetaObject, shaderType: Key::VertexShader); |
| 979 | } |
| 980 | |
| 981 | if (m_fragNeedsUpdate) { |
| 982 | m_fragNeedsUpdate = false; |
| 983 | m_common.updateShader(item: m_item, itemMetaObject: m_itemMetaObject, shaderType: Key::FragmentShader); |
| 984 | } |
| 985 | } |
| 986 | |
| 987 | void QQuickOpenGLShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
| 988 | { |
| 989 | if (change == QQuickItem::ItemSceneChange) |
| 990 | m_common.updateWindow(window: value.window); |
| 991 | } |
| 992 | |
| 993 | QT_END_NAMESPACE |
| 994 | |
| 995 | #include "moc_qquickopenglshadereffect_p.cpp" |
| 996 | |