/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsgd3d12shadereffectnode_p.h" #include "qsgd3d12rendercontext_p.h" #include "qsgd3d12texture_p.h" #include "qsgd3d12engine_p.h" #include #include #include #include #include #include #include #include "vs_shadereffectdefault.hlslh" #include "ps_shadereffectdefault.hlslh" QT_BEGIN_NAMESPACE // NOTE: Avoid categorized logging. It is slow. #define DECLARE_DEBUG_VAR(variable) \ static bool debug_ ## variable() \ { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; } DECLARE_DEBUG_VAR(shader) void QSGD3D12ShaderLinker::reset(const QByteArray &vertBlob, const QByteArray &fragBlob) { Q_ASSERT(!vertBlob.isEmpty() && !fragBlob.isEmpty()); vs = vertBlob; fs = fragBlob; error = false; constantBufferSize = 0; constants.clear(); samplers.clear(); textures.clear(); textureNameMap.clear(); } void QSGD3D12ShaderLinker::feedVertexInput(const QSGShaderEffectNode::ShaderData &shader) { bool foundPos = false, foundTexCoord = false; for (const auto &ip : qAsConst(shader.shaderInfo.inputParameters)) { if (ip.semanticName == QByteArrayLiteral("POSITION")) foundPos = true; else if (ip.semanticName == QByteArrayLiteral("TEXCOORD")) foundTexCoord = true; } if (!foundPos) { qWarning("ShaderEffect: No POSITION input found."); error = true; } if (!foundTexCoord) { qWarning("ShaderEffect: No TEXCOORD input found."); error = true; } // Nothing else to do here, the QSGGeometry::AttributeSet decides anyway // and that is already generated by QQuickShaderEffectMesh via // QSGGeometry::defaultAttributes_TexturedPoint2D() and has the semantics // so it will just work. } void QSGD3D12ShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet *dirtyIndices) { Q_ASSERT(shader.shaderInfo.variables.count() == shader.varData.count()); if (!dirtyIndices) { constantBufferSize = qMax(constantBufferSize, shader.shaderInfo.constantDataSize); for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) { const auto &var(shader.shaderInfo.variables.at(i)); if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) { const auto &vd(shader.varData.at(i)); Constant c; c.size = var.size; c.specialType = vd.specialType; if (c.specialType != QSGShaderEffectNode::VariableData::SubRect) { c.value = vd.value; } else { Q_ASSERT(var.name.startsWith(QByteArrayLiteral("qt_SubRect_"))); c.value = var.name.mid(11); } constants[var.offset] = c; } } } else { for (int idx : *dirtyIndices) constants[shader.shaderInfo.variables.at(idx).offset].value = shader.varData.at(idx).value; } } void QSGD3D12ShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &shader) { for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) { const auto &var(shader.shaderInfo.variables.at(i)); if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) { Q_ASSERT(shader.varData.at(i).specialType == QSGShaderEffectNode::VariableData::Unused); samplers.insert(var.bindPoint); } } } void QSGD3D12ShaderLinker::feedTextures(const QSGShaderEffectNode::ShaderData &shader, const QSet *dirtyIndices) { if (!dirtyIndices) { for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) { const auto &var(shader.shaderInfo.variables.at(i)); const auto &vd(shader.varData.at(i)); if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) { Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source); textures.insert(var.bindPoint, vd.value); textureNameMap.insert(var.name, var.bindPoint); } } } else { for (int idx : *dirtyIndices) { const auto &var(shader.shaderInfo.variables.at(idx)); const auto &vd(shader.varData.at(idx)); textures.insert(var.bindPoint, vd.value); textureNameMap.insert(var.name, var.bindPoint); } } } void QSGD3D12ShaderLinker::linkTextureSubRects() { // feedConstants stores in Constant::value for subrect entries. Now // that both constants and textures are known, replace the name with the // texture bind point. for (Constant &c : constants) { if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) { if (c.value.type() == QMetaType::QByteArray) { const QByteArray name = c.value.toByteArray(); if (!textureNameMap.contains(name)) qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", qPrintable(name)); c.value = textureNameMap[name]; } } } } void QSGD3D12ShaderLinker::dump() { if (error) { qDebug() << "Failed to generate program data"; return; } qDebug() << "Combined shader data" << vs.size() << fs.size() << "cbuffer size" << constantBufferSize; qDebug() << " - constants" << constants; qDebug() << " - samplers" << samplers; qDebug() << " - textures" << textures; } QDebug operator<<(QDebug debug, const QSGD3D12ShaderLinker::Constant &c) { QDebugStateSaver saver(debug); debug.space(); debug << "size" << c.size; if (c.specialType != QSGShaderEffectNode::VariableData::None) debug << "special" << c.specialType; else debug << "value" << c.value; return debug; } QSGD3D12ShaderEffectMaterial::QSGD3D12ShaderEffectMaterial(QSGD3D12ShaderEffectNode *node) : node(node) { setFlag(Blending | RequiresFullMatrix, true); // may be changed in sync() } QSGD3D12ShaderEffectMaterial::~QSGD3D12ShaderEffectMaterial() { delete dummy; } struct QSGD3D12ShaderMaterialTypeCache { QSGMaterialType *get(const QByteArray &vs, const QByteArray &fs); void reset() { qDeleteAll(m_types); m_types.clear(); } struct Key { QByteArray blob[2]; Key() { } Key(const QByteArray &vs, const QByteArray &fs) { blob[0] = vs; blob[1] = fs; } bool operator==(const Key &other) const { return blob[0] == other.blob[0] && blob[1] == other.blob[1]; } }; QHash m_types; }; uint qHash(const QSGD3D12ShaderMaterialTypeCache::Key &key, uint seed = 0) { uint hash = seed; for (int i = 0; i < 2; ++i) hash = hash * 31337 + qHash(key.blob[i]); return hash; } QSGMaterialType *QSGD3D12ShaderMaterialTypeCache::get(const QByteArray &vs, const QByteArray &fs) { const Key k(vs, fs); if (m_types.contains(k)) return m_types.value(k); QSGMaterialType *t = new QSGMaterialType; m_types.insert(k, t); return t; } Q_GLOBAL_STATIC(QSGD3D12ShaderMaterialTypeCache, shaderMaterialTypeCache) void QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache() { shaderMaterialTypeCache()->reset(); } QSGMaterialType *QSGD3D12ShaderEffectMaterial::type() const { return mtype; } static bool hasAtlasTexture(const QVector &textureProviders) { for (int i = 0; i < textureProviders.count(); ++i) { QSGTextureProvider *t = textureProviders.at(i); if (t && t->texture() && t->texture()->isAtlasTexture()) return true; } return false; } int QSGD3D12ShaderEffectMaterial::compare(const QSGMaterial *other) const { Q_ASSERT(other && type() == other->type()); const QSGD3D12ShaderEffectMaterial *o = static_cast(other); if (int diff = cullMode - o->cullMode) return diff; if (int diff = textureProviders.count() - o->textureProviders.count()) return diff; if (linker.constants != o->linker.constants) return 1; if ((hasAtlasTexture(textureProviders) && !geometryUsesTextureSubRect) || (hasAtlasTexture(o->textureProviders) && !o->geometryUsesTextureSubRect)) return 1; for (int i = 0; i < textureProviders.count(); ++i) { QSGTextureProvider *tp1 = textureProviders.at(i); QSGTextureProvider *tp2 = o->textureProviders.at(i); if (!tp1 || !tp2) return tp1 == tp2 ? 0 : 1; QSGTexture *t1 = tp1->texture(); QSGTexture *t2 = tp2->texture(); if (!t1 || !t2) return t1 == t2 ? 0 : 1; if (int diff = t1->textureId() - t2->textureId()) return diff; } return 0; } int QSGD3D12ShaderEffectMaterial::constantBufferSize() const { return QSGD3D12Engine::alignedConstantBufferSize(linker.constantBufferSize); } void QSGD3D12ShaderEffectMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState) { pipelineState->shaders.vs = reinterpret_cast(linker.vs.constData()); pipelineState->shaders.vsSize = linker.vs.size(); pipelineState->shaders.ps = reinterpret_cast(linker.fs.constData()); pipelineState->shaders.psSize = linker.fs.size(); pipelineState->shaders.rootSig.textureViewCount = textureProviders.count(); } static inline QColor qsg_premultiply_color(const QColor &c) { return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF()); } QSGD3D12Material::UpdateResults QSGD3D12ShaderEffectMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state, QSGD3D12PipelineState *pipelineState, ExtraState *, quint8 *constantBuffer) { QSGD3D12Material::UpdateResults r = 0; quint8 *p = constantBuffer; for (auto it = linker.constants.constBegin(), itEnd = linker.constants.constEnd(); it != itEnd; ++it) { quint8 *dst = p + it.key(); const QSGD3D12ShaderLinker::Constant &c(it.value()); if (c.specialType == QSGShaderEffectNode::VariableData::Opacity) { if (state.isOpacityDirty()) { const float f = state.opacity(); Q_ASSERT(sizeof(f) == c.size); memcpy(dst, &f, sizeof(f)); r |= UpdatedConstantBuffer; } } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) { if (state.isMatrixDirty()) { const int sz = 16 * sizeof(float); Q_ASSERT(sz == c.size); memcpy(dst, state.combinedMatrix().constData(), sz); r |= UpdatedConstantBuffer; } } else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) { // float4 QRectF subRect(0, 0, 1, 1); int srcBindPoint = c.value.toInt(); // filled in by linkTextureSubRects if (QSGTexture *t = textureProviders.at(srcBindPoint)->texture()) subRect = t->normalizedTextureSubRect(); const float f[4] = { float(subRect.x()), float(subRect.y()), float(subRect.width()), float(subRect.height()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); } else if (c.specialType == QSGShaderEffectNode::VariableData::None) { r |= UpdatedConstantBuffer; switch (c.value.type()) { case QMetaType::QColor: { const QColor v = qsg_premultiply_color(qvariant_cast(c.value)); const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::Float: { const float f = qvariant_cast(c.value); Q_ASSERT(sizeof(f) == c.size); memcpy(dst, &f, sizeof(f)); break; } case QMetaType::Double: { const float f = float(qvariant_cast(c.value)); Q_ASSERT(sizeof(f) == c.size); memcpy(dst, &f, sizeof(f)); break; } case QMetaType::Int: { const int i = c.value.toInt(); Q_ASSERT(sizeof(i) == c.size); memcpy(dst, &i, sizeof(i)); break; } case QMetaType::Bool: { const bool b = c.value.toBool(); Q_ASSERT(sizeof(b) == c.size); memcpy(dst, &b, sizeof(b)); break; } case QMetaType::QTransform: { // float3x3 const QTransform v = qvariant_cast(c.value); const float m[3][3] = { { float(v.m11()), float(v.m12()), float(v.m13()) }, { float(v.m21()), float(v.m22()), float(v.m23()) }, { float(v.m31()), float(v.m32()), float(v.m33()) } }; Q_ASSERT(sizeof(m) == c.size); memcpy(dst, m[0], sizeof(m)); break; } case QMetaType::QSize: case QMetaType::QSizeF: { // float2 const QSizeF v = c.value.toSizeF(); const float f[2] = { float(v.width()), float(v.height()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QPoint: case QMetaType::QPointF: { // float2 const QPointF v = c.value.toPointF(); const float f[2] = { float(v.x()), float(v.y()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QRect: case QMetaType::QRectF: { // float4 const QRectF v = c.value.toRectF(); const float f[4] = { float(v.x()), float(v.y()), float(v.width()), float(v.height()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QVector2D: { // float2 const QVector2D v = qvariant_cast(c.value); const float f[2] = { float(v.x()), float(v.y()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QVector3D: { // float3 const QVector3D v = qvariant_cast(c.value); const float f[3] = { float(v.x()), float(v.y()), float(v.z()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QVector4D: { // float4 const QVector4D v = qvariant_cast(c.value); const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QQuaternion: { // float4 const QQuaternion v = qvariant_cast(c.value); const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) }; Q_ASSERT(sizeof(f) == c.size); memcpy(dst, f, sizeof(f)); break; } case QMetaType::QMatrix4x4: { // float4x4 const QMatrix4x4 v = qvariant_cast(c.value); const int sz = 16 * sizeof(float); Q_ASSERT(sz == c.size); memcpy(dst, v.constData(), sz); break; } default: break; } } } for (int i = 0; i < textureProviders.count(); ++i) { QSGTextureProvider *tp = textureProviders[i]; QSGD3D12TextureView &tv(pipelineState->shaders.rootSig.textureViews[i]); if (tp) { if (QSGTexture *t = tp->texture()) { if (t->isAtlasTexture() && !geometryUsesTextureSubRect) { QSGTexture *newTexture = t->removedFromAtlas(); if (newTexture) t = newTexture; } tv.filter = t->filtering() == QSGTexture::Linear ? QSGD3D12TextureView::FilterLinear : QSGD3D12TextureView::FilterNearest; tv.addressModeHoriz = t->horizontalWrapMode() == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap; tv.addressModeVert = t->verticalWrapMode() == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap; t->bind(); continue; } } if (!dummy) { dummy = new QSGD3D12Texture(node->renderContext()->engine()); QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); img.fill(0); dummy->create(img, QSGRenderContext::CreateTexture_Alpha); } tv.filter = QSGD3D12TextureView::FilterNearest; tv.addressModeHoriz = QSGD3D12TextureView::AddressWrap; tv.addressModeVert = QSGD3D12TextureView::AddressWrap; dummy->bind(); } switch (cullMode) { case QSGShaderEffectNode::FrontFaceCulling: pipelineState->cullMode = QSGD3D12PipelineState::CullFront; break; case QSGShaderEffectNode::BackFaceCulling: pipelineState->cullMode = QSGD3D12PipelineState::CullBack; break; default: pipelineState->cullMode = QSGD3D12PipelineState::CullNone; break; } return r; } void QSGD3D12ShaderEffectMaterial::updateTextureProviders(bool layoutChange) { if (layoutChange) { for (QSGTextureProvider *tp : textureProviders) { if (tp) { QObject::disconnect(tp, SIGNAL(textureChanged()), node, SLOT(handleTextureChange())); QObject::disconnect(tp, SIGNAL(destroyed(QObject*)), node, SLOT(handleTextureProviderDestroyed(QObject*))); } } textureProviders.fill(nullptr, linker.textures.count()); } for (auto it = linker.textures.constBegin(), itEnd = linker.textures.constEnd(); it != itEnd; ++it) { const int bindPoint = it.key(); // Now that the linker has merged the textures, we can switch over to a // simple vector indexed by the binding point for textureProviders. Q_ASSERT(bindPoint >= 0 && bindPoint < textureProviders.count()); QQuickItem *source = qobject_cast(qvariant_cast(it.value())); QSGTextureProvider *newProvider = source && source->isTextureProvider() ? source->textureProvider() : nullptr; QSGTextureProvider *&activeProvider(textureProviders[bindPoint]); if (newProvider != activeProvider) { if (activeProvider) { QObject::disconnect(activeProvider, SIGNAL(textureChanged()), node, SLOT(handleTextureChange())); QObject::disconnect(activeProvider, SIGNAL(destroyed(QObject*)), node, SLOT(handleTextureProviderDestroyed(QObject*))); } if (newProvider) { Q_ASSERT_X(newProvider->thread() == QThread::currentThread(), "QSGD3D12ShaderEffectMaterial::updateTextureProviders", "Texture provider must belong to the rendering thread"); QObject::connect(newProvider, SIGNAL(textureChanged()), node, SLOT(handleTextureChange())); QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), node, SLOT(handleTextureProviderDestroyed(QObject*))); } else { const char *typeName = source ? source->metaObject()->className() : it.value().typeName(); qWarning("ShaderEffect: Texture t%d is not assigned a valid texture provider (%s).", bindPoint, typeName); } activeProvider = newProvider; } } } QSGD3D12ShaderEffectNode::QSGD3D12ShaderEffectNode(QSGD3D12RenderContext *rc, QSGD3D12GuiThreadShaderEffectManager *mgr) : QSGShaderEffectNode(mgr), m_rc(rc), m_mgr(mgr), m_material(this) { setFlag(UsePreprocess, true); setMaterial(&m_material); } QRectF QSGD3D12ShaderEffectNode::updateNormalizedTextureSubRect(bool supportsAtlasTextures) { QRectF srcRect(0, 0, 1, 1); bool geometryUsesTextureSubRect = false; if (supportsAtlasTextures && m_material.textureProviders.count() == 1) { QSGTextureProvider *provider = m_material.textureProviders.at(0); if (provider->texture()) { srcRect = provider->texture()->normalizedTextureSubRect(); geometryUsesTextureSubRect = true; } } if (m_material.geometryUsesTextureSubRect != geometryUsesTextureSubRect) { m_material.geometryUsesTextureSubRect = geometryUsesTextureSubRect; markDirty(QSGNode::DirtyMaterial); } return srcRect; } void QSGD3D12ShaderEffectNode::syncMaterial(SyncData *syncData) { if (Q_UNLIKELY(debug_shader())) qDebug() << "shadereffect node sync" << syncData->dirty; if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) { m_material.setFlag(QSGMaterial::Blending, syncData->blending); markDirty(QSGNode::DirtyMaterial); } if (m_material.cullMode != syncData->cullMode) { m_material.cullMode = syncData->cullMode; markDirty(QSGNode::DirtyMaterial); } if (syncData->dirty & QSGShaderEffectNode::DirtyShaders) { QByteArray vertBlob, fragBlob; m_material.hasCustomVertexShader = syncData->vertex.shader->hasShaderCode; if (m_material.hasCustomVertexShader) { vertBlob = syncData->vertex.shader->shaderInfo.blob; } else { vertBlob = QByteArray::fromRawData(reinterpret_cast(g_VS_DefaultShaderEffect), sizeof(g_VS_DefaultShaderEffect)); } m_material.hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode; if (m_material.hasCustomFragmentShader) { fragBlob = syncData->fragment.shader->shaderInfo.blob; } else { fragBlob = QByteArray::fromRawData(reinterpret_cast(g_PS_DefaultShaderEffect), sizeof(g_PS_DefaultShaderEffect)); } m_material.mtype = shaderMaterialTypeCache()->get(vertBlob, fragBlob); m_material.linker.reset(vertBlob, fragBlob); if (m_material.hasCustomVertexShader) { m_material.linker.feedVertexInput(*syncData->vertex.shader); m_material.linker.feedConstants(*syncData->vertex.shader); } else { QSGShaderEffectNode::ShaderData defaultSD; defaultSD.shaderInfo.blob = vertBlob; defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex; QSGGuiThreadShaderEffectManager::ShaderInfo::InputParameter ip; ip.semanticName = QByteArrayLiteral("POSITION"); defaultSD.shaderInfo.inputParameters.append(ip); ip.semanticName = QByteArrayLiteral("TEXCOORD"); defaultSD.shaderInfo.inputParameters.append(ip); // { float4x4 qt_Matrix; float qt_Opacity; } where only the matrix is used QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v; v.name = QByteArrayLiteral("qt_Matrix"); v.offset = 0; v.size = 16 * sizeof(float); defaultSD.shaderInfo.variables.append(v); QSGShaderEffectNode::VariableData vd; vd.specialType = QSGShaderEffectNode::VariableData::Matrix; defaultSD.varData.append(vd); defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float); m_material.linker.feedVertexInput(defaultSD); m_material.linker.feedConstants(defaultSD); } m_material.linker.feedSamplers(*syncData->vertex.shader); m_material.linker.feedTextures(*syncData->vertex.shader); if (m_material.hasCustomFragmentShader) { m_material.linker.feedConstants(*syncData->fragment.shader); } else { QSGShaderEffectNode::ShaderData defaultSD; defaultSD.shaderInfo.blob = fragBlob; defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment; // { float4x4 qt_Matrix; float qt_Opacity; } where only the opacity is used QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v; v.name = QByteArrayLiteral("qt_Opacity"); v.offset = 16 * sizeof(float); v.size = sizeof(float); defaultSD.shaderInfo.variables.append(v); QSGShaderEffectNode::VariableData vd; vd.specialType = QSGShaderEffectNode::VariableData::Opacity; defaultSD.varData.append(vd); v.name = QByteArrayLiteral("source"); v.bindPoint = 0; v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Texture; defaultSD.shaderInfo.variables.append(v); vd.specialType = QSGShaderEffectNode::VariableData::Source; defaultSD.varData.append(vd); v.name = QByteArrayLiteral("sourceSampler"); v.bindPoint = 0; v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler; defaultSD.shaderInfo.variables.append(v); vd.specialType = QSGShaderEffectNode::VariableData::Unused; defaultSD.varData.append(vd); defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float); m_material.linker.feedConstants(defaultSD); m_material.linker.feedSamplers(defaultSD); m_material.linker.feedTextures(defaultSD); } // While this may seem unnecessary for the built-in shaders, the value // of 'source' is still in there and we have to process it. m_material.linker.feedSamplers(*syncData->fragment.shader); m_material.linker.feedTextures(*syncData->fragment.shader); m_material.linker.linkTextureSubRects(); m_material.updateTextureProviders(true); markDirty(QSGNode::DirtyMaterial); if (Q_UNLIKELY(debug_shader())) m_material.linker.dump(); } else { if (syncData->dirty & QSGShaderEffectNode::DirtyShaderConstant) { if (!syncData->vertex.dirtyConstants->isEmpty()) m_material.linker.feedConstants(*syncData->vertex.shader, syncData->vertex.dirtyConstants); if (!syncData->fragment.dirtyConstants->isEmpty()) m_material.linker.feedConstants(*syncData->fragment.shader, syncData->fragment.dirtyConstants); markDirty(QSGNode::DirtyMaterial); if (Q_UNLIKELY(debug_shader())) m_material.linker.dump(); } if (syncData->dirty & QSGShaderEffectNode::DirtyShaderTexture) { if (!syncData->vertex.dirtyTextures->isEmpty()) m_material.linker.feedTextures(*syncData->vertex.shader, syncData->vertex.dirtyTextures); if (!syncData->fragment.dirtyTextures->isEmpty()) m_material.linker.feedTextures(*syncData->fragment.shader, syncData->fragment.dirtyTextures); m_material.linker.linkTextureSubRects(); m_material.updateTextureProviders(false); markDirty(QSGNode::DirtyMaterial); if (Q_UNLIKELY(debug_shader())) m_material.linker.dump(); } } if (bool(m_material.flags() & QSGMaterial::RequiresFullMatrix) != m_material.hasCustomVertexShader) { m_material.setFlag(QSGMaterial::RequiresFullMatrix, m_material.hasCustomVertexShader); markDirty(QSGNode::DirtyMaterial); } } void QSGD3D12ShaderEffectNode::handleTextureChange() { markDirty(QSGNode::DirtyMaterial); emit m_mgr->textureChanged(); } void QSGD3D12ShaderEffectNode::handleTextureProviderDestroyed(QObject *object) { for (QSGTextureProvider *&tp : m_material.textureProviders) { if (tp == object) tp = nullptr; } } void QSGD3D12ShaderEffectNode::preprocess() { for (QSGTextureProvider *tp : m_material.textureProviders) { if (tp) { if (QSGDynamicTexture *texture = qobject_cast(tp->texture())) texture->updateTexture(); } } } bool QSGD3D12GuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const { return true; } QString QSGD3D12GuiThreadShaderEffectManager::log() const { return m_log; } QSGGuiThreadShaderEffectManager::Status QSGD3D12GuiThreadShaderEffectManager::status() const { return m_status; } struct RefGuard { RefGuard(IUnknown *p) : p(p) { } ~RefGuard() { p->Release(); } IUnknown *p; }; class QSGD3D12ShaderCompileTask : public QRunnable { public: QSGD3D12ShaderCompileTask(QSGD3D12GuiThreadShaderEffectManager *mgr, QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::Type typeHint, const QByteArray &src, QSGD3D12GuiThreadShaderEffectManager::ShaderInfo *result) : mgr(mgr), typeHint(typeHint), src(src), result(result) { } void run() override; private: QSGD3D12GuiThreadShaderEffectManager *mgr; QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::Type typeHint; QByteArray src; QSGD3D12GuiThreadShaderEffectManager::ShaderInfo *result; }; void QSGD3D12ShaderCompileTask::run() { const char *target = typeHint == QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? "vs_5_0" : "ps_5_0"; ID3DBlob *bytecode = nullptr; ID3DBlob *errors = nullptr; HRESULT hr = D3DCompile(src.constData(), src.size(), nullptr, nullptr, nullptr, "main", target, 0, 0, &bytecode, &errors); if (FAILED(hr) || !bytecode) { qWarning("HLSL shader compilation failed: 0x%x", hr); if (errors) { mgr->m_log += QString::fromUtf8(static_cast(errors->GetBufferPointer()), errors->GetBufferSize()); errors->Release(); } mgr->m_status = QSGGuiThreadShaderEffectManager::Error; emit mgr->shaderCodePrepared(false, typeHint, src, result); emit mgr->logAndStatusChanged(); return; } result->blob.resize(bytecode->GetBufferSize()); memcpy(result->blob.data(), bytecode->GetBufferPointer(), result->blob.size()); bytecode->Release(); const bool ok = mgr->reflect(result); mgr->m_status = ok ? QSGGuiThreadShaderEffectManager::Compiled : QSGGuiThreadShaderEffectManager::Error; emit mgr->shaderCodePrepared(ok, typeHint, src, result); emit mgr->logAndStatusChanged(); } static const int BYTECODE_MAGIC = 0x43425844; // 'DXBC' void QSGD3D12GuiThreadShaderEffectManager::prepareShaderCode(ShaderInfo::Type typeHint, const QByteArray &src, ShaderInfo *result) { // The D3D12 backend's ShaderEffect implementation supports both HLSL // source strings and bytecode or source in files as input. Bytecode is // strongly recommended, but in order to make ShaderEffect users' (and // anything that stiches shader strings together dynamically, e.g. // qtgraphicaleffects) life easier, and since we link to d3dcompiler // anyways, compiling from source is also supported. QByteArray shaderSourceCode = src; QUrl srcUrl(QString::fromUtf8(src)); if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) { if (!m_fileSelector) { m_fileSelector = new QFileSelector(this); m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("hlsl")); } const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl)); QFile f(fn); if (!f.open(QIODevice::ReadOnly)) { qWarning("ShaderEffect: Failed to read %s", qPrintable(fn)); emit shaderCodePrepared(false, typeHint, src, result); return; } QByteArray blob = f.readAll(); f.close(); if (blob.size() > 4) { const quint32 *p = reinterpret_cast(blob.constData()); if (*p == BYTECODE_MAGIC) { // already compiled D3D bytecode, skip straight to reflection result->blob = blob; const bool ok = reflect(result); m_status = ok ? Compiled : Error; emit shaderCodePrepared(ok, typeHint, src, result); emit logAndStatusChanged(); return; } // assume the file contained HLSL source code shaderSourceCode = blob; } } QThreadPool::globalInstance()->start(new QSGD3D12ShaderCompileTask(this, typeHint, shaderSourceCode, result)); } bool QSGD3D12GuiThreadShaderEffectManager::reflect(ShaderInfo *result) { ID3D12ShaderReflection *reflector; HRESULT hr = D3DReflect(result->blob.constData(), result->blob.size(), IID_PPV_ARGS(&reflector)); if (FAILED(hr)) { qWarning("D3D shader reflection failed: 0x%x", hr); return false; } RefGuard rg(reflector); D3D12_SHADER_DESC shaderDesc; reflector->GetDesc(&shaderDesc); const uint progType = (shaderDesc.Version & 0xFFFF0000) >> 16; const uint major = (shaderDesc.Version & 0x000000F0) >> 4; const uint minor = (shaderDesc.Version & 0x0000000F); switch (progType) { case D3D12_SHVER_VERTEX_SHADER: result->type = ShaderInfo::TypeVertex; break; case D3D12_SHVER_PIXEL_SHADER: result->type = ShaderInfo::TypeFragment; break; default: result->type = ShaderInfo::TypeOther; qWarning("D3D shader is of unknown type 0x%x", shaderDesc.Version); return false; } if (major < 5) { qWarning("D3D shader model version %u.%u is too low", major, minor); return false; } const int ieCount = shaderDesc.InputParameters; const int cbufferCount = shaderDesc.ConstantBuffers; const int boundResCount = shaderDesc.BoundResources; result->constantDataSize = 0; if (ieCount < 1) { qWarning("Invalid shader: Not enough input parameters (%d)", ieCount); return false; } if (cbufferCount < 1) { qWarning("Invalid shader: Shader has no constant buffers"); return false; } if (boundResCount < 1) { qWarning("Invalid shader: No resources bound. Expected to have at least a constant buffer bound."); return false; } if (Q_UNLIKELY(debug_shader())) qDebug("Shader reflection size %d type %d v%u.%u input elems %d cbuffers %d boundres %d", result->blob.size(), result->type, major, minor, ieCount, cbufferCount, boundResCount); for (int i = 0; i < ieCount; ++i) { D3D12_SIGNATURE_PARAMETER_DESC desc; if (FAILED(reflector->GetInputParameterDesc(i, &desc))) { qWarning("D3D reflection: Failed to query input parameter %d", i); return false; } if (desc.SystemValueType != D3D_NAME_UNDEFINED) continue; ShaderInfo::InputParameter param; param.semanticName = QByteArray(desc.SemanticName); param.semanticIndex = desc.SemanticIndex; result->inputParameters.append(param); } for (int i = 0; i < boundResCount; ++i) { D3D12_SHADER_INPUT_BIND_DESC desc; if (FAILED(reflector->GetResourceBindingDesc(i, &desc))) { qWarning("D3D reflection: Failed to query resource binding %d", i); continue; } bool gotCBuffer = false; if (desc.Type == D3D_SIT_CBUFFER) { ID3D12ShaderReflectionConstantBuffer *cbuf = reflector->GetConstantBufferByName(desc.Name); D3D12_SHADER_BUFFER_DESC bufDesc; if (FAILED(cbuf->GetDesc(&bufDesc))) { qWarning("D3D reflection: Failed to query constant buffer description"); continue; } if (gotCBuffer) { qWarning("D3D reflection: Found more than one constant buffers. Only the first one is used."); continue; } gotCBuffer = true; result->constantDataSize = bufDesc.Size; for (uint cbIdx = 0; cbIdx < bufDesc.Variables; ++cbIdx) { ID3D12ShaderReflectionVariable *cvar = cbuf->GetVariableByIndex(cbIdx); D3D12_SHADER_VARIABLE_DESC varDesc; if (FAILED(cvar->GetDesc(&varDesc))) { qWarning("D3D reflection: Failed to query constant buffer variable %d", cbIdx); return false; } // we report the full size of the buffer but only return variables that are actually used by this shader if (!(varDesc.uFlags & D3D_SVF_USED)) continue; ShaderInfo::Variable v; v.type = ShaderInfo::Constant; v.name = QByteArray(varDesc.Name); v.offset = varDesc.StartOffset; v.size = varDesc.Size; result->variables.append(v); } } else if (desc.Type == D3D_SIT_TEXTURE) { if (desc.Dimension != D3D_SRV_DIMENSION_TEXTURE2D) { qWarning("D3D reflection: Texture %s is not a 2D texture, ignoring.", qPrintable(desc.Name)); continue; } if (desc.NumSamples != (UINT) -1) { qWarning("D3D reflection: Texture %s is multisample (%u), ignoring.", qPrintable(desc.Name), desc.NumSamples); continue; } if (desc.BindCount != 1) { qWarning("D3D reflection: Texture %s is an array, ignoring.", qPrintable(desc.Name)); continue; } if (desc.Space != 0) { qWarning("D3D reflection: Texture %s is not using register space 0, ignoring.", qPrintable(desc.Name)); continue; } ShaderInfo::Variable v; v.type = ShaderInfo::Texture; v.name = QByteArray(desc.Name); v.bindPoint = desc.BindPoint; result->variables.append(v); } else if (desc.Type == D3D_SIT_SAMPLER) { if (desc.BindCount != 1) { qWarning("D3D reflection: Sampler %s is an array, ignoring.", qPrintable(desc.Name)); continue; } if (desc.Space != 0) { qWarning("D3D reflection: Sampler %s is not using register space 0, ignoring.", qPrintable(desc.Name)); continue; } ShaderInfo::Variable v; v.type = ShaderInfo::Sampler; v.name = QByteArray(desc.Name); v.bindPoint = desc.BindPoint; result->variables.append(v); } else { qWarning("D3D reflection: Resource binding %d has an unknown type of %d and will be ignored.", i, desc.Type); continue; } } if (Q_UNLIKELY(debug_shader())) { qDebug() << "Input:" << result->inputParameters; qDebug() << "Variables:" << result->variables << "cbuffer size" << result->constantDataSize; } return true; } QT_END_NAMESPACE