| 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/qquickgenericshadereffect_p.h> |
| 41 | #include <private/qquickwindow_p.h> |
| 42 | #include <private/qquickitem_p.h> |
| 43 | |
| 44 | QT_BEGIN_NAMESPACE |
| 45 | |
| 46 | namespace { |
| 47 | class IntSignalMapper : public QObject |
| 48 | { |
| 49 | Q_OBJECT |
| 50 | |
| 51 | int value; |
| 52 | public: |
| 53 | explicit IntSignalMapper(int v) |
| 54 | : QObject(nullptr), value(v) {} |
| 55 | |
| 56 | public Q_SLOTS: |
| 57 | void map() { emit mapped(value); } |
| 58 | |
| 59 | Q_SIGNALS: |
| 60 | void mapped(int); |
| 61 | }; |
| 62 | } // unnamed namespace |
| 63 | |
| 64 | // The generic shader effect is used whenever on the RHI code path, or when the |
| 65 | // scenegraph backend indicates SupportsShaderEffectNode. This, unlike the |
| 66 | // monolithic and interconnected (e.g. with particles) OpenGL variant, passes |
| 67 | // most of the work to a scenegraph node created via the adaptation layer, thus |
| 68 | // allowing different implementation in the backends. |
| 69 | |
| 70 | QQuickGenericShaderEffect::QQuickGenericShaderEffect(QQuickShaderEffect *item, QObject *parent) |
| 71 | : QObject(parent) |
| 72 | , m_item(item) |
| 73 | , m_meshResolution(1, 1) |
| 74 | , m_mesh(nullptr) |
| 75 | , m_cullMode(QQuickShaderEffect::NoCulling) |
| 76 | , m_blending(true) |
| 77 | , m_supportsAtlasTextures(false) |
| 78 | , m_mgr(nullptr) |
| 79 | , m_fragNeedsUpdate(true) |
| 80 | , m_vertNeedsUpdate(true) |
| 81 | { |
| 82 | qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>(typeName: "ShaderInfo::Type" ); |
| 83 | for (int i = 0; i < NShader; ++i) |
| 84 | m_inProgress[i] = nullptr; |
| 85 | } |
| 86 | |
| 87 | QQuickGenericShaderEffect::~QQuickGenericShaderEffect() |
| 88 | { |
| 89 | for (int i = 0; i < NShader; ++i) { |
| 90 | disconnectSignals(shaderType: Shader(i)); |
| 91 | for (const auto &sm : qAsConst(t&: m_signalMappers[i])) |
| 92 | delete sm.mapper; |
| 93 | } |
| 94 | |
| 95 | delete m_mgr; |
| 96 | } |
| 97 | |
| 98 | void QQuickGenericShaderEffect::setFragmentShader(const QByteArray &src) |
| 99 | { |
| 100 | // Compare the actual values since they are often just filenames. |
| 101 | // Optimizing by comparing constData() is a bad idea since seemingly static |
| 102 | // strings in QML may in fact have different addresses when a binding |
| 103 | // triggers assigning the "same" value to the property. |
| 104 | if (m_fragShader == src) |
| 105 | return; |
| 106 | |
| 107 | m_fragShader = src; |
| 108 | |
| 109 | m_fragNeedsUpdate = true; |
| 110 | if (m_item->isComponentComplete()) |
| 111 | maybeUpdateShaders(); |
| 112 | |
| 113 | emit m_item->fragmentShaderChanged(); |
| 114 | } |
| 115 | |
| 116 | void QQuickGenericShaderEffect::setVertexShader(const QByteArray &src) |
| 117 | { |
| 118 | if (m_vertShader == src) |
| 119 | return; |
| 120 | |
| 121 | m_vertShader = src; |
| 122 | |
| 123 | m_vertNeedsUpdate = true; |
| 124 | if (m_item->isComponentComplete()) |
| 125 | maybeUpdateShaders(); |
| 126 | |
| 127 | emit m_item->vertexShaderChanged(); |
| 128 | } |
| 129 | |
| 130 | void QQuickGenericShaderEffect::setBlending(bool enable) |
| 131 | { |
| 132 | if (m_blending == enable) |
| 133 | return; |
| 134 | |
| 135 | m_blending = enable; |
| 136 | m_item->update(); |
| 137 | emit m_item->blendingChanged(); |
| 138 | } |
| 139 | |
| 140 | QVariant QQuickGenericShaderEffect::mesh() const |
| 141 | { |
| 142 | return m_mesh ? QVariant::fromValue(value: static_cast<QObject *>(m_mesh)) |
| 143 | : QVariant::fromValue(value: m_meshResolution); |
| 144 | } |
| 145 | |
| 146 | void QQuickGenericShaderEffect::setMesh(const QVariant &mesh) |
| 147 | { |
| 148 | QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(object: qvariant_cast<QObject *>(v: mesh)); |
| 149 | if (newMesh && newMesh == m_mesh) |
| 150 | return; |
| 151 | |
| 152 | if (m_mesh) |
| 153 | disconnect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, member: nullptr); |
| 154 | |
| 155 | m_mesh = newMesh; |
| 156 | |
| 157 | if (m_mesh) { |
| 158 | connect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, SLOT(markGeometryDirtyAndUpdate())); |
| 159 | } else { |
| 160 | if (mesh.canConvert<QSize>()) { |
| 161 | m_meshResolution = mesh.toSize(); |
| 162 | } else { |
| 163 | QList<QByteArray> res = mesh.toByteArray().split(sep: 'x'); |
| 164 | bool ok = res.size() == 2; |
| 165 | if (ok) { |
| 166 | int w = res.at(i: 0).toInt(ok: &ok); |
| 167 | if (ok) { |
| 168 | int h = res.at(i: 1).toInt(ok: &ok); |
| 169 | if (ok) |
| 170 | m_meshResolution = QSize(w, h); |
| 171 | } |
| 172 | } |
| 173 | if (!ok) |
| 174 | qWarning(msg: "ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh" ); |
| 175 | } |
| 176 | m_defaultMesh.setResolution(m_meshResolution); |
| 177 | } |
| 178 | |
| 179 | m_dirty |= QSGShaderEffectNode::DirtyShaderMesh; |
| 180 | m_item->update(); |
| 181 | |
| 182 | emit m_item->meshChanged(); |
| 183 | } |
| 184 | |
| 185 | void QQuickGenericShaderEffect::setCullMode(QQuickShaderEffect::CullMode face) |
| 186 | { |
| 187 | if (m_cullMode == face) |
| 188 | return; |
| 189 | |
| 190 | m_cullMode = face; |
| 191 | m_item->update(); |
| 192 | emit m_item->cullModeChanged(); |
| 193 | } |
| 194 | |
| 195 | void QQuickGenericShaderEffect::setSupportsAtlasTextures(bool supports) |
| 196 | { |
| 197 | if (m_supportsAtlasTextures == supports) |
| 198 | return; |
| 199 | |
| 200 | m_supportsAtlasTextures = supports; |
| 201 | markGeometryDirtyAndUpdate(); |
| 202 | emit m_item->supportsAtlasTexturesChanged(); |
| 203 | } |
| 204 | |
| 205 | QString QQuickGenericShaderEffect::parseLog() |
| 206 | { |
| 207 | maybeUpdateShaders(); |
| 208 | return log(); |
| 209 | } |
| 210 | |
| 211 | QString QQuickGenericShaderEffect::log() const |
| 212 | { |
| 213 | QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); |
| 214 | if (!mgr) |
| 215 | return QString(); |
| 216 | |
| 217 | return mgr->log(); |
| 218 | } |
| 219 | |
| 220 | QQuickShaderEffect::Status QQuickGenericShaderEffect::status() const |
| 221 | { |
| 222 | QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); |
| 223 | if (!mgr) |
| 224 | return QQuickShaderEffect::Uncompiled; |
| 225 | |
| 226 | return QQuickShaderEffect::Status(mgr->status()); |
| 227 | } |
| 228 | |
| 229 | void QQuickGenericShaderEffect::handleEvent(QEvent *event) |
| 230 | { |
| 231 | if (event->type() == QEvent::DynamicPropertyChange) { |
| 232 | QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event); |
| 233 | for (int shaderType = 0; shaderType < NShader; ++shaderType) { |
| 234 | const auto &vars(m_shaders[shaderType].shaderInfo.variables); |
| 235 | for (int idx = 0; idx < vars.count(); ++idx) { |
| 236 | if (vars[idx].name == e->propertyName()) { |
| 237 | propertyChanged(mappedId: (shaderType << 16) | idx); |
| 238 | break; |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | void QQuickGenericShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &) |
| 246 | { |
| 247 | m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; |
| 248 | } |
| 249 | |
| 250 | QSGNode *QQuickGenericShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) |
| 251 | { |
| 252 | QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode); |
| 253 | |
| 254 | if (m_item->width() <= 0 || m_item->height() <= 0) { |
| 255 | delete node; |
| 256 | return nullptr; |
| 257 | } |
| 258 | |
| 259 | // Do not change anything while a new shader is being reflected or compiled. |
| 260 | if (m_inProgress[Vertex] || m_inProgress[Fragment]) |
| 261 | return node; |
| 262 | |
| 263 | // The manager should be already created on the gui thread. Just take that instance. |
| 264 | QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); |
| 265 | if (!mgr) { |
| 266 | delete node; |
| 267 | return nullptr; |
| 268 | } |
| 269 | |
| 270 | if (!node) { |
| 271 | QSGRenderContext *rc = QQuickWindowPrivate::get(c: m_item->window())->context; |
| 272 | node = rc->sceneGraphContext()->createShaderEffectNode(renderContext: rc, mgr); |
| 273 | if (!node) { |
| 274 | qWarning(msg: "No shader effect node" ); |
| 275 | return nullptr; |
| 276 | } |
| 277 | m_dirty = QSGShaderEffectNode::DirtyShaderAll; |
| 278 | } |
| 279 | |
| 280 | QSGShaderEffectNode::SyncData sd; |
| 281 | sd.dirty = m_dirty; |
| 282 | sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode); |
| 283 | sd.blending = m_blending; |
| 284 | sd.vertex.shader = &m_shaders[Vertex]; |
| 285 | sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex]; |
| 286 | sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex]; |
| 287 | sd.fragment.shader = &m_shaders[Fragment]; |
| 288 | sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment]; |
| 289 | sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment]; |
| 290 | node->syncMaterial(syncData: &sd); |
| 291 | |
| 292 | if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) { |
| 293 | node->setGeometry(nullptr); |
| 294 | m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh; |
| 295 | m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; |
| 296 | } |
| 297 | |
| 298 | if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) { |
| 299 | const QRectF rect(0, 0, m_item->width(), m_item->height()); |
| 300 | QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh; |
| 301 | QSGGeometry *geometry = node->geometry(); |
| 302 | |
| 303 | const QRectF srcRect = node->updateNormalizedTextureSubRect(supportsAtlasTextures: m_supportsAtlasTextures); |
| 304 | geometry = mesh->updateGeometry(geometry, attrCount: 2, posIndex: 0, srcRect, rect); |
| 305 | |
| 306 | node->setFlag(QSGNode::OwnsGeometry, false); |
| 307 | node->setGeometry(geometry); |
| 308 | node->setFlag(QSGNode::OwnsGeometry, true); |
| 309 | |
| 310 | m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry; |
| 311 | } |
| 312 | |
| 313 | m_dirty = {}; |
| 314 | for (int i = 0; i < NShader; ++i) { |
| 315 | m_dirtyConstants[i].clear(); |
| 316 | m_dirtyTextures[i].clear(); |
| 317 | } |
| 318 | |
| 319 | return node; |
| 320 | } |
| 321 | |
| 322 | void QQuickGenericShaderEffect::maybeUpdateShaders() |
| 323 | { |
| 324 | if (m_vertNeedsUpdate) |
| 325 | m_vertNeedsUpdate = !updateShader(shaderType: Vertex, src: m_vertShader); |
| 326 | if (m_fragNeedsUpdate) |
| 327 | m_fragNeedsUpdate = !updateShader(shaderType: Fragment, src: m_fragShader); |
| 328 | if (m_vertNeedsUpdate || m_fragNeedsUpdate) { |
| 329 | // This function is invoked either from componentComplete or in a |
| 330 | // response to a previous invocation's polish() request. If this is |
| 331 | // case #1 then updateShader can fail due to not having a window or |
| 332 | // scenegraph ready. Schedule the polish to try again later. In case #2 |
| 333 | // the backend probably does not have shadereffect support so there is |
| 334 | // nothing to do for us here. |
| 335 | if (!m_item->window() || !m_item->window()->isSceneGraphInitialized()) |
| 336 | m_item->polish(); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | void QQuickGenericShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
| 341 | { |
| 342 | // Move the window ref. |
| 343 | if (change == QQuickItem::ItemSceneChange) { |
| 344 | for (int shaderType = 0; shaderType < NShader; ++shaderType) { |
| 345 | for (const auto &vd : qAsConst(t&: m_shaders[shaderType].varData)) { |
| 346 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { |
| 347 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: vd.value)); |
| 348 | if (source) { |
| 349 | if (value.window) |
| 350 | QQuickItemPrivate::get(item: source)->refWindow(value.window); |
| 351 | else |
| 352 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 353 | } |
| 354 | } |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | QSGGuiThreadShaderEffectManager *QQuickGenericShaderEffect::shaderEffectManager() const |
| 361 | { |
| 362 | if (!m_mgr) { |
| 363 | // return null if this is not the gui thread and not already created |
| 364 | if (QThread::currentThread() != m_item->thread()) |
| 365 | return m_mgr; |
| 366 | QQuickWindow *w = m_item->window(); |
| 367 | if (w) { // note: just the window, don't care about isSceneGraphInitialized() here |
| 368 | m_mgr = QQuickWindowPrivate::get(c: w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager(); |
| 369 | if (m_mgr) { |
| 370 | connect(sender: m_mgr, SIGNAL(logAndStatusChanged()), receiver: m_item, SIGNAL(logChanged())); |
| 371 | connect(sender: m_mgr, SIGNAL(logAndStatusChanged()), receiver: m_item, SIGNAL(statusChanged())); |
| 372 | connect(sender: m_mgr, SIGNAL(textureChanged()), receiver: this, SLOT(markGeometryDirtyAndUpdateIfSupportsAtlas())); |
| 373 | connect(sender: m_mgr, signal: &QSGGuiThreadShaderEffectManager::shaderCodePrepared, receiver: this, slot: &QQuickGenericShaderEffect::shaderCodePrepared); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | return m_mgr; |
| 379 | } |
| 380 | |
| 381 | void QQuickGenericShaderEffect::disconnectSignals(Shader shaderType) |
| 382 | { |
| 383 | for (auto &sm : m_signalMappers[shaderType]) { |
| 384 | if (sm.active) { |
| 385 | sm.active = false; |
| 386 | QObject::disconnect(sender: m_item, signal: nullptr, receiver: sm.mapper, SLOT(map())); |
| 387 | QObject::disconnect(sender: sm.mapper, SIGNAL(mapped(int)), receiver: this, SLOT(propertyChanged(int))); |
| 388 | } |
| 389 | } |
| 390 | for (const auto &vd : qAsConst(t&: m_shaders[shaderType].varData)) { |
| 391 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { |
| 392 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: vd.value)); |
| 393 | if (source) { |
| 394 | if (m_item->window()) |
| 395 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 396 | QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceDestroyed(QObject*))); |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | struct ShaderInfoCache |
| 403 | { |
| 404 | bool contains(const QByteArray &key) const |
| 405 | { |
| 406 | return m_shaderInfoCache.contains(akey: key); |
| 407 | } |
| 408 | |
| 409 | QSGGuiThreadShaderEffectManager::ShaderInfo value(const QByteArray &key) const |
| 410 | { |
| 411 | return m_shaderInfoCache.value(akey: key); |
| 412 | } |
| 413 | |
| 414 | void insert(const QByteArray &key, const QSGGuiThreadShaderEffectManager::ShaderInfo &value) |
| 415 | { |
| 416 | m_shaderInfoCache.insert(akey: key, avalue: value); |
| 417 | } |
| 418 | |
| 419 | QHash<QByteArray, QSGGuiThreadShaderEffectManager::ShaderInfo> m_shaderInfoCache; |
| 420 | }; |
| 421 | |
| 422 | Q_GLOBAL_STATIC(ShaderInfoCache, shaderInfoCache) |
| 423 | |
| 424 | bool QQuickGenericShaderEffect::updateShader(Shader shaderType, const QByteArray &src) |
| 425 | { |
| 426 | QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); |
| 427 | if (!mgr) |
| 428 | return false; |
| 429 | |
| 430 | const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); |
| 431 | |
| 432 | disconnectSignals(shaderType); |
| 433 | |
| 434 | m_shaders[shaderType].shaderInfo = QSGGuiThreadShaderEffectManager::ShaderInfo(); |
| 435 | m_shaders[shaderType].varData.clear(); |
| 436 | |
| 437 | if (!src.isEmpty()) { |
| 438 | if (shaderInfoCache()->contains(key: src)) { |
| 439 | m_shaders[shaderType].shaderInfo = shaderInfoCache()->value(key: src); |
| 440 | m_shaders[shaderType].hasShaderCode = true; |
| 441 | } else { |
| 442 | // Each prepareShaderCode call needs its own work area, hence the |
| 443 | // dynamic alloc. If there are calls in progress, let those run to |
| 444 | // finish, their results can then simply be ignored because |
| 445 | // m_inProgress indicates what we care about. |
| 446 | m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo; |
| 447 | const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint = |
| 448 | shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex |
| 449 | : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment; |
| 450 | // Figure out what input parameters and variables are used in the |
| 451 | // shader. For file-based shader source/bytecode this is where the data |
| 452 | // is pulled in from the file. Some backends may choose to do |
| 453 | // source->bytecode compilation as well in this step. |
| 454 | mgr->prepareShaderCode(typeHint, src, result: m_inProgress[shaderType]); |
| 455 | // the rest is handled in shaderCodePrepared() |
| 456 | return true; |
| 457 | } |
| 458 | } else { |
| 459 | m_shaders[shaderType].hasShaderCode = false; |
| 460 | if (shaderType == Fragment) { |
| 461 | // With built-in shaders hasShaderCode is set to false and all |
| 462 | // metadata is empty, as it is left up to the node to provide a |
| 463 | // built-in default shader and its metadata. However, in case of |
| 464 | // the built-in fragment shader the value for 'source' has to be |
| 465 | // provided and monitored like with an application-provided shader. |
| 466 | QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v; |
| 467 | v.name = QByteArrayLiteral("source" ); |
| 468 | v.bindPoint = 0; // fake |
| 469 | v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture |
| 470 | : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler; |
| 471 | m_shaders[shaderType].shaderInfo.variables.append(t: v); |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | updateShaderVars(shaderType); |
| 476 | m_dirty |= QSGShaderEffectNode::DirtyShaders; |
| 477 | m_item->update(); |
| 478 | return true; |
| 479 | } |
| 480 | |
| 481 | void QQuickGenericShaderEffect::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint, |
| 482 | const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result) |
| 483 | { |
| 484 | const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment; |
| 485 | |
| 486 | // If another call was made to updateShader() for the same shader type in |
| 487 | // the meantime then our results are useless, just drop them. |
| 488 | if (result != m_inProgress[shaderType]) { |
| 489 | delete result; |
| 490 | return; |
| 491 | } |
| 492 | |
| 493 | m_shaders[shaderType].shaderInfo = *result; |
| 494 | delete result; |
| 495 | m_inProgress[shaderType] = nullptr; |
| 496 | |
| 497 | if (!ok) { |
| 498 | qWarning(msg: "ShaderEffect: shader preparation failed for %s\n%s\n" , src.constData(), qPrintable(log())); |
| 499 | m_shaders[shaderType].hasShaderCode = false; |
| 500 | return; |
| 501 | } |
| 502 | |
| 503 | m_shaders[shaderType].hasShaderCode = true; |
| 504 | shaderInfoCache()->insert(key: src, value: m_shaders[shaderType].shaderInfo); |
| 505 | updateShaderVars(shaderType); |
| 506 | m_dirty |= QSGShaderEffectNode::DirtyShaders; |
| 507 | m_item->update(); |
| 508 | } |
| 509 | |
| 510 | void QQuickGenericShaderEffect::updateShaderVars(Shader shaderType) |
| 511 | { |
| 512 | QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); |
| 513 | if (!mgr) |
| 514 | return; |
| 515 | |
| 516 | const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); |
| 517 | |
| 518 | const int varCount = m_shaders[shaderType].shaderInfo.variables.count(); |
| 519 | m_shaders[shaderType].varData.resize(asize: varCount); |
| 520 | |
| 521 | // Reuse signal mappers as much as possible since the mapping is based on |
| 522 | // the index and shader type which are both constant. |
| 523 | if (m_signalMappers[shaderType].count() < varCount) |
| 524 | m_signalMappers[shaderType].resize(asize: varCount); |
| 525 | |
| 526 | // Hook up the signals to get notified about changes for properties that |
| 527 | // correspond to variables in the shader. Store also the values. |
| 528 | for (int i = 0; i < varCount; ++i) { |
| 529 | const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i)); |
| 530 | QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]); |
| 531 | const bool isSpecial = v.name.startsWith(c: "qt_" ); // special names not mapped to properties |
| 532 | if (isSpecial) { |
| 533 | if (v.name == "qt_Opacity" ) |
| 534 | vd.specialType = QSGShaderEffectNode::VariableData::Opacity; |
| 535 | else if (v.name == "qt_Matrix" ) |
| 536 | vd.specialType = QSGShaderEffectNode::VariableData::Matrix; |
| 537 | else if (v.name.startsWith(c: "qt_SubRect_" )) |
| 538 | vd.specialType = QSGShaderEffectNode::VariableData::SubRect; |
| 539 | continue; |
| 540 | } |
| 541 | |
| 542 | // The value of a property corresponding to a sampler is the source |
| 543 | // item ref, unless there are separate texture objects in which case |
| 544 | // the sampler is ignored (here). |
| 545 | if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) { |
| 546 | if (texturesSeparate) { |
| 547 | vd.specialType = QSGShaderEffectNode::VariableData::Unused; |
| 548 | continue; |
| 549 | } else { |
| 550 | vd.specialType = QSGShaderEffectNode::VariableData::Source; |
| 551 | } |
| 552 | } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) { |
| 553 | Q_ASSERT(texturesSeparate); |
| 554 | vd.specialType = QSGShaderEffectNode::VariableData::Source; |
| 555 | } else { |
| 556 | vd.specialType = QSGShaderEffectNode::VariableData::None; |
| 557 | } |
| 558 | |
| 559 | // Find the property on the ShaderEffect item. |
| 560 | const int propIdx = m_item->metaObject()->indexOfProperty(name: v.name.constData()); |
| 561 | if (propIdx >= 0) { |
| 562 | QMetaProperty mp = m_item->metaObject()->property(index: propIdx); |
| 563 | if (!mp.hasNotifySignal()) |
| 564 | qWarning(msg: "ShaderEffect: property '%s' does not have notification method" , v.name.constData()); |
| 565 | |
| 566 | // Have a IntSignalMapper that emits mapped() with an index+type on each property change notify signal. |
| 567 | auto &sm(m_signalMappers[shaderType][i]); |
| 568 | if (!sm.mapper) |
| 569 | sm.mapper = new IntSignalMapper(i | (shaderType << 16)); |
| 570 | sm.active = true; |
| 571 | const QByteArray signalName = '2' + mp.notifySignal().methodSignature(); |
| 572 | QObject::connect(sender: m_item, signal: signalName, receiver: sm.mapper, SLOT(map())); |
| 573 | QObject::connect(sender: sm.mapper, SIGNAL(mapped(int)), receiver: this, SLOT(propertyChanged(int))); |
| 574 | } else { |
| 575 | // Do not warn for dynamic properties. |
| 576 | if (!m_item->property(name: v.name.constData()).isValid()) |
| 577 | qWarning(msg: "ShaderEffect: '%s' does not have a matching property" , v.name.constData()); |
| 578 | } |
| 579 | |
| 580 | vd.value = m_item->property(name: v.name.constData()); |
| 581 | |
| 582 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { |
| 583 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: vd.value)); |
| 584 | if (source) { |
| 585 | if (m_item->window()) |
| 586 | QQuickItemPrivate::get(item: source)->refWindow(m_item->window()); |
| 587 | QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceDestroyed(QObject*))); |
| 588 | } |
| 589 | } |
| 590 | } |
| 591 | } |
| 592 | |
| 593 | bool QQuickGenericShaderEffect::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const |
| 594 | { |
| 595 | for (int shaderType = 0; shaderType < NShader; ++shaderType) { |
| 596 | for (int idx = 0; idx < m_shaders[shaderType].varData.count(); ++idx) { |
| 597 | if (shaderType != typeToSkip || idx != indexToSkip) { |
| 598 | const auto &vd(m_shaders[shaderType].varData[idx]); |
| 599 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(v: vd.value) == source) |
| 600 | return false; |
| 601 | } |
| 602 | } |
| 603 | } |
| 604 | return true; |
| 605 | } |
| 606 | |
| 607 | void QQuickGenericShaderEffect::propertyChanged(int mappedId) |
| 608 | { |
| 609 | const Shader type = Shader(mappedId >> 16); |
| 610 | const int idx = mappedId & 0xFFFF; |
| 611 | const auto &v(m_shaders[type].shaderInfo.variables[idx]); |
| 612 | auto &vd(m_shaders[type].varData[idx]); |
| 613 | |
| 614 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { |
| 615 | QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: vd.value)); |
| 616 | if (source) { |
| 617 | if (m_item->window()) |
| 618 | QQuickItemPrivate::get(item: source)->derefWindow(); |
| 619 | // QObject::disconnect() will disconnect all matching connections. |
| 620 | // If the same source has been attached to two separate |
| 621 | // textures/samplers, then changing one of them would trigger both |
| 622 | // to be disconnected. So check first. |
| 623 | if (sourceIsUnique(source, typeToSkip: type, indexToSkip: idx)) |
| 624 | QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceDestroyed(QObject*))); |
| 625 | } |
| 626 | |
| 627 | vd.value = m_item->property(name: v.name.constData()); |
| 628 | |
| 629 | source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: vd.value)); |
| 630 | if (source) { |
| 631 | // 'source' needs a window to get a scene graph node. It usually gets one through its |
| 632 | // parent, but if the source item is "inline" rather than a reference -- i.e. |
| 633 | // "property variant source: Image { }" instead of "property variant source: foo" -- it |
| 634 | // will not get a parent. In those cases, 'source' should get the window from 'item'. |
| 635 | if (m_item->window()) |
| 636 | QQuickItemPrivate::get(item: source)->refWindow(m_item->window()); |
| 637 | QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceDestroyed(QObject*))); |
| 638 | } |
| 639 | |
| 640 | m_dirty |= QSGShaderEffectNode::DirtyShaderTexture; |
| 641 | m_dirtyTextures[type].insert(value: idx); |
| 642 | |
| 643 | } else { |
| 644 | vd.value = m_item->property(name: v.name.constData()); |
| 645 | m_dirty |= QSGShaderEffectNode::DirtyShaderConstant; |
| 646 | m_dirtyConstants[type].insert(value: idx); |
| 647 | } |
| 648 | |
| 649 | m_item->update(); |
| 650 | } |
| 651 | |
| 652 | void QQuickGenericShaderEffect::sourceDestroyed(QObject *object) |
| 653 | { |
| 654 | for (int shaderType = 0; shaderType < NShader; ++shaderType) { |
| 655 | for (auto &vd : m_shaders[shaderType].varData) { |
| 656 | if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) { |
| 657 | if (qvariant_cast<QObject *>(v: vd.value) == object) |
| 658 | vd.value = QVariant(); |
| 659 | } |
| 660 | } |
| 661 | } |
| 662 | } |
| 663 | |
| 664 | void QQuickGenericShaderEffect::markGeometryDirtyAndUpdate() |
| 665 | { |
| 666 | m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; |
| 667 | m_item->update(); |
| 668 | } |
| 669 | |
| 670 | void QQuickGenericShaderEffect::markGeometryDirtyAndUpdateIfSupportsAtlas() |
| 671 | { |
| 672 | if (m_supportsAtlasTextures) |
| 673 | markGeometryDirtyAndUpdate(); |
| 674 | } |
| 675 | |
| 676 | QT_END_NAMESPACE |
| 677 | |
| 678 | #include "moc_qquickgenericshadereffect_p.cpp" |
| 679 | #include "qquickgenericshadereffect.moc" |
| 680 | |