963 lines
38 KiB
C++
963 lines
38 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 <QtCore/qfile.h>
|
|
#include <QtQml/qqmlfile.h>
|
|
#include <qsgtextureprovider.h>
|
|
|
|
#include <d3d12shader.h>
|
|
#include <d3dcompiler.h>
|
|
|
|
#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(render)
|
|
|
|
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<int> *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) {
|
|
const auto &vd(shader.varData.at(i));
|
|
Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Unused);
|
|
samplers.insert(var.bindPoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QSGD3D12ShaderLinker::feedTextures(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *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 <name> 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<Key, QSGMaterialType *> 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<QSGTextureProvider *> &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<const QSGD3D12ShaderEffectMaterial *>(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<const quint8 *>(linker.vs.constData());
|
|
pipelineState->shaders.vsSize = linker.vs.size();
|
|
pipelineState->shaders.ps = reinterpret_cast<const quint8 *>(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<QColor>(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<float>(c.value);
|
|
Q_ASSERT(sizeof(f) == c.size);
|
|
memcpy(dst, &f, sizeof(f));
|
|
break;
|
|
}
|
|
case QMetaType::Double: {
|
|
const float f = float(qvariant_cast<double>(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<QTransform>(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<QVector2D>(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<QVector3D>(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<QVector4D>(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<QQuaternion>(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<QMatrix4x4>(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<QQuickItem *>(qvariant_cast<QObject *>(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_render()))
|
|
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<const char *>(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<const char *>(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_render()))
|
|
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_render()))
|
|
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_render()))
|
|
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<QSGDynamicTexture *>(tp->texture()))
|
|
texture->updateTexture();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QSGD3D12GuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QString QSGD3D12GuiThreadShaderEffectManager::log() const
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
QSGGuiThreadShaderEffectManager::Status QSGD3D12GuiThreadShaderEffectManager::status() const
|
|
{
|
|
return Compiled;
|
|
}
|
|
|
|
struct RefGuard {
|
|
RefGuard(IUnknown *p) : p(p) { }
|
|
~RefGuard() { p->Release(); }
|
|
IUnknown *p;
|
|
};
|
|
|
|
bool QSGD3D12GuiThreadShaderEffectManager::reflect(const QByteArray &src, ShaderInfo *result)
|
|
{
|
|
const QString fn = QQmlFile::urlToLocalFileOrQrc(src);
|
|
QFile f(fn);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
|
|
return false;
|
|
}
|
|
result->blob = f.readAll();
|
|
f.close();
|
|
|
|
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_render()))
|
|
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_render())) {
|
|
qDebug() << "Input:" << result->inputParameters;
|
|
qDebug() << "Variables:" << result->variables << "cbuffer size" << result->constantDataSize;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|