ShaderEffect: Make uniform setting safer

...to better match the Qt 5 behavior. For example, trying to assign a
Qt.point() to a vec3 works in Qt 5, setting the 3rd component to 0,
because it just does a blind call to glUniform2fv. In Qt 6 this was not
done initially, and the data copy is based on the value's size, so
one either gets an assert in debug builds, and perhaps a crash or some
strange behavior in the shader in release. Make this safer.

The handling of QTransform->mat3 was broken as well, although it is
unlikely anyone ever used that in practice. Fix it so that we properly
map the 9 floats to the 4-float-per-column layout.

Pick-to: 6.3
Change-Id: Ia4a24bff0e54d94ddb2f5db276f0ed0a2cde0efd
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Laszlo Agocs 2022-01-12 10:03:49 +01:00
parent 720ffaeb60
commit c4f49a9e1f
7 changed files with 160 additions and 39 deletions

View File

@ -233,6 +233,20 @@ static inline QColor qsg_premultiply_color(const QColor &c)
return QColor::fromRgbF(r * a, g * a, b * a, a);
}
template<typename T>
static inline void fillUniformBlockMember(char *dst, const T *value, int valueCount, int fieldSizeBytes)
{
const size_t valueBytes = sizeof(T) * valueCount;
const size_t fieldBytes = fieldSizeBytes;
if (valueBytes <= fieldBytes) {
memcpy(dst, value, valueBytes);
if (valueBytes < fieldBytes)
memset(dst + valueBytes, 0, fieldBytes - valueBytes);
} else {
memcpy(dst, value, fieldBytes);
}
}
bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
Q_UNUSED(oldMaterial);
@ -248,15 +262,13 @@ bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSG
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));
fillUniformBlockMember<float>(dst, &f, 1, c.size);
changed = true;
}
} 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);
const QMatrix4x4 m = state.combinedMatrix();
fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
changed = true;
}
} else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
@ -271,40 +283,34 @@ bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSG
}
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));
fillUniformBlockMember<float>(dst, f, 4, c.size);
} else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
changed = true;
switch (int(c.value.userType())) {
case QMetaType::QColor: {
const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value)).toRgb();
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));
fillUniformBlockMember<float>(dst, f, 4, c.size);
break;
}
case QMetaType::Float: {
const float f = qvariant_cast<float>(c.value);
Q_ASSERT(sizeof(f) == c.size);
memcpy(dst, &f, sizeof(f));
fillUniformBlockMember<float>(dst, &f, 1, c.size);
break;
}
case QMetaType::Double: {
const float f = float(qvariant_cast<double>(c.value));
Q_ASSERT(sizeof(f) == c.size);
memcpy(dst, &f, sizeof(f));
fillUniformBlockMember<float>(dst, &f, 1, c.size);
break;
}
case QMetaType::Int: {
const int i = c.value.toInt();
Q_ASSERT(sizeof(i) == c.size);
memcpy(dst, &i, sizeof(i));
const qint32 i = c.value.toInt();
fillUniformBlockMember<qint32>(dst, &i, 1, c.size);
break;
}
case QMetaType::Bool: {
const bool b = c.value.toBool();
Q_ASSERT(sizeof(b) == c.size);
memcpy(dst, &b, sizeof(b));
const qint32 b = c.value.toBool();
fillUniformBlockMember<qint32>(dst, &b, 1, c.size);
break;
}
case QMetaType::QTransform: { // mat3
@ -314,67 +320,65 @@ bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSG
{ 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));
// stored as 4 floats per column, 1 unused
memset(dst, 0, c.size);
const size_t bytesPerColumn = 4 * sizeof(float);
if (c.size >= bytesPerColumn)
fillUniformBlockMember<float>(dst, m[0], 3, 3 * sizeof(float));
if (c.size >= 2 * bytesPerColumn)
fillUniformBlockMember<float>(dst + bytesPerColumn, m[1], 3, 3 * sizeof(float));
if (c.size >= 3 * bytesPerColumn)
fillUniformBlockMember<float>(dst + 2 * bytesPerColumn, m[2], 3, 3 * sizeof(float));
break;
}
case QMetaType::QSize:
case QMetaType::QSizeF: { // vec2
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));
fillUniformBlockMember<float>(dst, f, 2, c.size);
break;
}
case QMetaType::QPoint:
case QMetaType::QPointF: { // vec2
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));
fillUniformBlockMember<float>(dst, f, 2, c.size);
break;
}
case QMetaType::QRect:
case QMetaType::QRectF: { // vec4
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));
fillUniformBlockMember<float>(dst, f, 4, c.size);
break;
}
case QMetaType::QVector2D: { // vec2
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));
fillUniformBlockMember<float>(dst, f, 2, c.size);
break;
}
case QMetaType::QVector3D: { // vec3
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));
fillUniformBlockMember<float>(dst, f, 3, c.size);
break;
}
case QMetaType::QVector4D: { // vec4
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));
fillUniformBlockMember<float>(dst, f, 4, c.size);
break;
}
case QMetaType::QQuaternion: { // vec4
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));
fillUniformBlockMember<float>(dst, f, 4, c.size);
break;
}
case QMetaType::QMatrix4x4: { // mat4
const QMatrix4x4 v = qvariant_cast<QMatrix4x4>(c.value);
const int sz = 16 * sizeof(float);
Q_ASSERT(sz == c.size);
memcpy(dst, v.constData(), sz);
const QMatrix4x4 m = qvariant_cast<QMatrix4x4>(c.value);
fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
break;
}
default:

View File

@ -21,6 +21,7 @@ set(resources_resource_files
"data/+qsb/red.frag"
"data/+qsb/test.frag"
"data/+qsb/test.vert"
"data/+qsb/testprop.frag"
"data/MyIcon.qml"
"data/connections.qml"
"data/deleteShaderEffectSource.qml"
@ -32,6 +33,8 @@ set(resources_resource_files
"data/test.frag"
"data/test.vert"
"data/twoImagesOneShaderEffect.qml"
"data/testprop.frag"
"data/testProperties.qml"
)
qt_internal_add_resource(tst_qquickshadereffect "resources"

View File

@ -2,3 +2,5 @@ qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/test.vert test_rhi.ver
qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/red.frag red_rhi.frag
qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/test.frag test_rhi.frag
qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/testprop.frag testprop.frag

View File

@ -0,0 +1,73 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick
Item {
id: root
width: 640
height: 480
property bool finished
Rectangle {
id: rect
color: "red"
width: 300
height: 300
Text {
text: "Hello world"
anchors.centerIn: parent
color: "#00FF00"
}
layer.enabled: true
layer.effect: ShaderEffect {
objectName: "shaderEffect"
fragmentShader: "qrc:/data/testprop.frag"
// This is intended to exercise the uniform data copy logic in
// QSGRhiShaderEffectMaterialShader::updateUniformData() to see if
// the undocumented combinations for passing in a vector with more
// components than the shader variable works, and no assertions or
// crashes occur.
property variant aVec4: Qt.point(0.1, 0.0) // third and fourth are 0
property variant aVec3: Qt.point(0.1, 0.5) // third will be 0
property variant aVec2: Qt.vector4d(0.1, 0.5, 1.0, 1.0) // only first two are used
property real f: 0.0
property variant aFloat: Qt.size(0.1 + f, 0.1) // only first is used
NumberAnimation on f {
from: 0.0
to: 1.0
duration: 1000
onFinished: root.finished = true
}
}
}
}

View File

@ -0,0 +1,28 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec4 aVec4;
vec3 aVec3;
vec2 aVec2;
float aFloat;
};
void main()
{
vec4 c = texture(source, qt_TexCoord0);
vec4 v = aVec4;
v.rgb += aVec3;
v.rg += aVec2;
v.r += aFloat;
c *= v;
if (c.a == 0.0)
c.a = 1.0;
fragColor = c * qt_Opacity;
}

View File

@ -90,6 +90,7 @@ private slots:
void withoutQmlEngine();
void hideParent();
void testPropertyMappings();
private:
enum PresenceFlags {
@ -199,6 +200,16 @@ void tst_qquickshadereffect::hideParent()
QTRY_VERIFY(view->rootObject()->property("finished").toBool());
}
void tst_qquickshadereffect::testPropertyMappings()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->setSource(QUrl(QStringLiteral("qrc:/data/testProperties.qml")));
QCOMPARE(view->status(), QQuickView::Ready);
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view.data()));
QTRY_VERIFY(view->rootObject()->property("finished").toBool());
}
QTEST_MAIN(tst_qquickshadereffect)
#include "tst_qquickshadereffect.moc"