| /**************************************************************************** |
| ** |
| ** Copyright (C) 2008-2012 NVIDIA Corporation. |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of Qt Quick 3D. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** 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 or (at your option) 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.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-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtQuick3DRender/private/qssgrendercontext_p.h> |
| #include <QtQuick3DRender/private/qssgrenderbasetypes_p.h> |
| #include <QtQuick3DRender/private/qssgrendershaderprogram_p.h> |
| #include <QtQuick3DRender/private/qssgrenderimagetexture_p.h> |
| #include <QtQuick3DUtils/private/qssgutils_p.h> |
| |
| #include <QtGui/qcolor.h> |
| |
| #include <limits> |
| |
| QT_BEGIN_NAMESPACE |
| |
| template<typename TDataType> |
| struct ShaderConstantApplier |
| { |
| bool force_compile_error; |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<qint32> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const qint32 &inValue, |
| qint32 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<qint32_2> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const qint32_2 &inValue, |
| qint32_2 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<qint32_3> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const qint32_3 &inValue, |
| qint32_3 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<qint32_4> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const qint32_4 &inValue, |
| qint32_4 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<bool> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const bool inValue, |
| bool &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<bool_2> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const bool_2 &inValue, |
| bool_2 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<bool_3> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const bool_3 &inValue, |
| bool_3 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<bool_4> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const bool_4 &inValue, |
| bool_4 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<float> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const float &inValue, |
| float &oldValue) |
| { |
| if (count > 1 || !(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QVector2D> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const QVector2D &inValue, |
| QVector2D &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QVector3D> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const QVector3D &inValue, |
| QVector3D &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QVector4D> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const QVector4D &inValue, |
| QVector4D &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<quint32> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const quint32 &inValue, |
| quint32 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<quint32_2> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const quint32_2 &inValue, |
| quint32_2 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<quint32_3> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const quint32_3 &inValue, |
| quint32_3 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<quint32_4> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const quint32_4 &inValue, |
| quint32_4 &oldValue) |
| { |
| if (!(inValue == oldValue)) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &inValue.x); |
| oldValue = inValue; |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QMatrix3x3> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const QMatrix3x3 inValue, |
| QMatrix3x3 &, |
| bool inTranspose) |
| { |
| program->backend()->setConstantValue(program->handle(), location, type, count, inValue.constData(), inTranspose); |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QMatrix4x4> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| const QMatrix4x4 inValue, |
| QMatrix4x4 &, |
| bool inTranspose) |
| { |
| program->backend()->setConstantValue(program->handle(), location, type, count, inValue.constData(), inTranspose); |
| } |
| |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGDataView<QMatrix4x4> inValue, |
| QMatrix4x4 &, |
| bool inTranspose) |
| { |
| program->backend()->setConstantValue(program->handle(), |
| location, |
| type, |
| count, |
| reinterpret_cast<const GLfloat *>(inValue.begin()), |
| inTranspose); |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QSSGRenderTexture2D *> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGRenderTexture2D *inValue, |
| quint32 &oldValue) |
| { |
| if (inValue) { |
| QSSGRenderTexture2D *texObj = reinterpret_cast<QSSGRenderTexture2D *>(inValue); |
| texObj->bind(); |
| quint32 texUnit = texObj->textureUnit(); |
| if (texUnit != oldValue) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &texUnit); |
| oldValue = texUnit; |
| } |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QSSGRenderTexture2D **> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGRenderTexture2D **inValue, |
| QVector<quint32> &oldValue) |
| { |
| Q_UNUSED(type) |
| if (inValue) { |
| bool update = false; |
| for (int i = 0; i < count; i++) { |
| QSSGRenderTexture2D *texObj = reinterpret_cast<QSSGRenderTexture2D *>(inValue[i]); |
| quint32 texUnit = std::numeric_limits<quint32>::max(); |
| if (texObj) { |
| texObj->bind(); |
| texUnit = texObj->textureUnit(); |
| } |
| if (texUnit != oldValue[i]) { |
| update = true; |
| oldValue[i] = texUnit; |
| } |
| } |
| if (update) |
| program->backend()->setConstantValue(program->handle(), |
| location, |
| QSSGRenderShaderDataType::Texture2D, |
| count, |
| oldValue.data()); |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QSSGRenderTextureCube *> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGRenderTextureCube *inValue, |
| quint32 &oldValue) |
| { |
| if (inValue) { |
| QSSGRenderTextureCube *texObj = reinterpret_cast<QSSGRenderTextureCube *>(inValue); |
| texObj->bind(); |
| quint32 texUnit = texObj->textureUnit(); |
| if (texUnit != oldValue) { |
| program->backend()->setConstantValue(program->handle(), location, type, count, &texUnit); |
| oldValue = texUnit; |
| } |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QSSGRenderTextureCube **> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGRenderTextureCube **inValue, |
| QVector<quint32> &oldValue) |
| { |
| Q_UNUSED(type) |
| if (inValue) { |
| bool update = false; |
| for (int i = 0; i < count; i++) { |
| QSSGRenderTextureCube *texObj = reinterpret_cast<QSSGRenderTextureCube *>(inValue[i]); |
| quint32 texUnit = std::numeric_limits<quint32>::max(); |
| if (texObj) { |
| texObj->bind(); |
| texUnit = texObj->textureUnit(); |
| } |
| if (texUnit != oldValue[i]) { |
| update = true; |
| oldValue[i] = texUnit; |
| } |
| } |
| if (update) |
| program->backend()->setConstantValue(program->handle(), |
| location, |
| QSSGRenderShaderDataType::TextureCube, |
| count, |
| oldValue.data()); |
| } |
| } |
| }; |
| |
| template<> |
| struct ShaderConstantApplier<QSSGRenderImage2D *> |
| { |
| void applyConstant(const QSSGRenderShaderProgram *program, |
| qint32 location, |
| qint32 count, |
| QSSGRenderShaderDataType type, |
| QSSGRenderImage2D *image, |
| quint32 &oldValue, |
| qint32 binding) |
| { |
| if (image) { |
| image->bind(binding); |
| quint32 texUnit = image->textureUnit(); |
| if (texUnit != oldValue) { |
| // on ES we need a explicit binding value |
| Q_ASSERT(program->backend()->getRenderContextType() != QSSGRenderContextType::GLES3PLUS || binding != -1); |
| // this is not allowed on ES 3+ for image types |
| if (program->backend()->getRenderContextType() != QSSGRenderContextType::GLES3PLUS) |
| program->backend()->setConstantValue(program->handle(), location, type, count, &texUnit); |
| |
| oldValue = texUnit; |
| } |
| } |
| } |
| }; |
| |
| QSSGRenderShaderProgram::QSSGRenderShaderProgram(const QSSGRef<QSSGRenderContext> &context, const char *programName, bool separableProgram) |
| : m_context(context) |
| , m_backend(context->backend()) |
| , m_programName(programName) |
| , m_handle(nullptr) |
| , m_programType(ProgramType::Graphics) |
| { |
| m_handle = m_backend->createShaderProgram(separableProgram); |
| |
| Q_UNUSED(m_programName) |
| Q_ASSERT(m_handle); |
| } |
| |
| QSSGRenderShaderProgram::~QSSGRenderShaderProgram() |
| { |
| m_context->shaderDestroyed(this); |
| |
| if (m_handle) |
| m_backend->releaseShaderProgram(m_handle); |
| |
| m_handle = nullptr; |
| } |
| |
| template<typename TShaderObject> |
| void QSSGRenderShaderProgram::attach(TShaderObject *pShader) |
| { |
| m_backend->attachShader(m_handle, pShader); |
| } |
| |
| template<typename TShaderObject> |
| void QSSGRenderShaderProgram::detach(TShaderObject *pShader) |
| { |
| m_backend->detachShader(m_handle, pShader); |
| } |
| |
| static QSSGRef<QSSGRenderShaderConstantBase> shaderConstantFactory(const QByteArray &inName, |
| qint32 uniLoc, |
| qint32 elementCount, |
| QSSGRenderShaderDataType inConstantType, |
| qint32 binding) |
| { |
| switch (inConstantType) { |
| case QSSGRenderShaderDataType::Integer: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<qint32>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::IntegerVec2: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<qint32_2>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::IntegerVec3: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<qint32_3>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::IntegerVec4: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<qint32_4>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Boolean: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<bool>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::BooleanVec2: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<bool_2>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::BooleanVec3: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<bool_3>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::BooleanVec4: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<bool_4>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Float: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<float>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Vec2: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QVector2D>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Vec3: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QVector3D>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Vec4: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QVector4D>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::UnsignedInteger: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<quint32>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::UnsignedIntegerVec2: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<quint32_2>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::UnsignedIntegerVec3: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<quint32_3>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::UnsignedIntegerVec4: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<quint32_4>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Matrix3x3: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QMatrix3x3>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Matrix4x4: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QMatrix4x4>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Texture2D: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderTexture2D *>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Texture2DHandle: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderTexture2D **>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::TextureCube: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderTextureCube *>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::TextureCubeHandle: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderTextureCube **>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::Image2D: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderImage2D *>(inName, uniLoc, elementCount, inConstantType, binding)); |
| case QSSGRenderShaderDataType::DataBuffer: |
| return QSSGRef<QSSGRenderShaderConstantBase>( |
| new QSSGRenderShaderConstant<QSSGRenderDataBuffer *>(inName, uniLoc, elementCount, inConstantType, binding)); |
| default: |
| break; |
| } |
| Q_ASSERT(false); |
| return nullptr; |
| } |
| |
| template<typename TShaderBufferType, typename TBufferDataType> |
| static QSSGRef<QSSGRenderShaderBufferBase> shaderBufferFactory(const QSSGRef<QSSGRenderContext> &context, |
| const QByteArray &inName, |
| qint32 cbLoc, |
| qint32 cbBinding, |
| qint32 cbSize, |
| qint32 cbCount, |
| const QSSGRef<TBufferDataType> &pBuffer) |
| { |
| return QSSGRef<QSSGRenderShaderBufferBase>(new TShaderBufferType(context, inName, cbLoc, cbBinding, cbSize, cbCount, pBuffer)); |
| } |
| |
| void QSSGRenderShaderProgram::getShaderParameters() |
| { |
| char nameBuf[512]; |
| qint32 location, elementCount, binding; |
| QSSGRenderShaderDataType type; |
| |
| qint32 constantCount = m_backend->getConstantCount(m_handle); |
| |
| for (int idx = 0; idx != constantCount; ++idx) { |
| location = m_backend->getConstantInfoByID(m_handle, idx, 512, &elementCount, &type, &binding, nameBuf); |
| |
| // sampler arrays have different type |
| if (type == QSSGRenderShaderDataType::Texture2D && elementCount > 1) { |
| type = QSSGRenderShaderDataType::Texture2DHandle; |
| } else if (type == QSSGRenderShaderDataType::TextureCube && elementCount > 1) { |
| type = QSSGRenderShaderDataType::TextureCubeHandle; |
| } |
| if (location != -1) |
| m_constants.insert(nameBuf, shaderConstantFactory(nameBuf, location, elementCount, type, binding)); |
| } |
| |
| // next query constant buffers info |
| qint32 length, bufferSize, paramCount; |
| qint32 constantBufferCount = m_backend->getConstantBufferCount(m_handle); |
| for (int idx = 0; idx != constantBufferCount; ++idx) { |
| location = m_backend->getConstantBufferInfoByID(m_handle, idx, 512, ¶mCount, &bufferSize, &length, nameBuf); |
| |
| if (location != -1) { |
| // find constant buffer in our DB |
| const QSSGRef<QSSGRenderConstantBuffer> &cb = m_context->getConstantBuffer(nameBuf); |
| if (cb) { |
| cb->setupBuffer(this, location, bufferSize, paramCount); |
| } |
| |
| m_shaderBuffers.insert(nameBuf, |
| shaderBufferFactory<QSSGRenderShaderConstantBuffer, |
| QSSGRenderConstantBuffer>(m_context, nameBuf, location, -1, bufferSize, paramCount, cb)); |
| } |
| } |
| |
| // next query storage buffers |
| qint32 storageBufferCount = m_backend->getStorageBufferCount(m_handle); |
| for (int idx = 0; idx != storageBufferCount; ++idx) { |
| location = m_backend->getStorageBufferInfoByID(m_handle, idx, 512, ¶mCount, &bufferSize, &length, nameBuf); |
| |
| if (location != -1) { |
| // find constant buffer in our DB |
| const QSSGRef<QSSGRenderStorageBuffer> &sb = m_context->getStorageBuffer(nameBuf); |
| m_shaderBuffers.insert(nameBuf, |
| shaderBufferFactory<QSSGRenderShaderStorageBuffer, |
| QSSGRenderStorageBuffer>(m_context, nameBuf, location, -1, bufferSize, paramCount, sb)); |
| } |
| } |
| } |
| |
| bool QSSGRenderShaderProgram::link() |
| { |
| bool success = m_backend->linkProgram(m_handle, m_errorMessage); |
| if (success) |
| getShaderParameters(); |
| return success; |
| } |
| |
| bool QSSGRenderShaderProgram::link(quint32 format, const QByteArray &binary) |
| { |
| bool success = m_backend->linkProgram(m_handle, m_errorMessage, format, binary); |
| if (success) |
| getShaderParameters(); |
| return success; |
| } |
| |
| QByteArray QSSGRenderShaderProgram::errorMessage() |
| { |
| return m_errorMessage; |
| } |
| |
| QSSGRef<QSSGRenderShaderConstantBase> QSSGRenderShaderProgram::shaderConstant(const QByteArray &constantName) const |
| { |
| const auto foundIt = m_constants.constFind(constantName); |
| return (foundIt != m_constants.cend()) ? foundIt.value() : nullptr; |
| } |
| |
| QSSGRef<QSSGRenderShaderBufferBase> QSSGRenderShaderProgram::shaderBuffer(const QByteArray &bufferName) const |
| { |
| const auto foundIt = m_shaderBuffers.constFind(bufferName); |
| return (foundIt != m_shaderBuffers.cend()) ? foundIt.value() : nullptr; |
| } |
| |
| const QSSGRef<QSSGRenderContext> &QSSGRenderShaderProgram::renderContext() |
| { |
| return m_context; |
| } |
| |
| template<typename TDataType> |
| void setConstantValueOfType(const QSSGRenderShaderProgram *program, |
| QSSGRenderShaderConstantBase *inConstantBase, |
| const TDataType &inValue, |
| const qint32 inCount) |
| { |
| if (inConstantBase == nullptr) { |
| Q_ASSERT(false); |
| return; |
| } |
| |
| Q_ASSERT(inConstantBase->m_elementCount >= inCount); |
| |
| if (inConstantBase->getShaderConstantType() == QSSGDataTypeToShaderDataTypeMap<TDataType>::getType()) { |
| QSSGRenderShaderConstant<TDataType> *inConstant = static_cast<QSSGRenderShaderConstant<TDataType> *>(inConstantBase); |
| ShaderConstantApplier<TDataType>().applyConstant(program, |
| inConstant->m_location, |
| inCount, |
| inConstant->m_type, |
| inValue, |
| inConstant->m_value); |
| } else { |
| Q_ASSERT(false); |
| } |
| } |
| |
| template<typename TDataType> |
| void setSamplerConstantValueOfType(const QSSGRenderShaderProgram *program, |
| QSSGRenderShaderConstantBase *inConstantBase, |
| const TDataType &inValue, |
| const qint32 inCount) |
| { |
| if (inConstantBase == nullptr) { |
| Q_ASSERT(false); |
| return; |
| } |
| |
| Q_ASSERT(inConstantBase->m_elementCount >= inCount); |
| |
| if (inConstantBase->getShaderConstantType() == QSSGDataTypeToShaderDataTypeMap<TDataType>::getType()) { |
| QSSGRenderShaderConstant<TDataType> *inConstant = static_cast<QSSGRenderShaderConstant<TDataType> *>(inConstantBase); |
| ShaderConstantApplier<TDataType>().applyConstant(program, |
| inConstant->m_location, |
| inCount, |
| inConstant->m_type, |
| inValue, |
| inConstant->m_value, |
| inConstant->m_binding); |
| } else { |
| Q_ASSERT(false); |
| } |
| } |
| |
| template<typename TDataType> |
| void setMatrixConstantValueOfType(const QSSGRenderShaderProgram *program, |
| QSSGRenderShaderConstantBase *inConstantBase, |
| const TDataType &inValue, |
| const qint32 inCount, |
| bool inTranspose) |
| { |
| if (inConstantBase == nullptr) { |
| Q_ASSERT(false); |
| return; |
| } |
| |
| Q_ASSERT(inConstantBase->m_elementCount >= inCount); |
| |
| if (inConstantBase->getShaderConstantType() == QSSGDataTypeToShaderDataTypeMap<TDataType>::getType()) { |
| QSSGRenderShaderConstant<TDataType> *inConstant = static_cast<QSSGRenderShaderConstant<TDataType> *>(inConstantBase); |
| ShaderConstantApplier<TDataType>().applyConstant(program, |
| inConstant->m_location, |
| inCount, |
| inConstant->m_type, |
| inValue, |
| inConstant->m_value, |
| inTranspose); |
| } else { |
| Q_ASSERT(false); |
| } |
| } |
| |
| template<typename TDataType> |
| void setMatrixConstantValueOfType(const QSSGRenderShaderProgram *program, |
| QSSGRenderShaderConstantBase *inConstantBase, |
| const QSSGDataView<TDataType> inValue, |
| const qint32 /*inCount*/, |
| bool inTranspose) |
| { |
| if (inConstantBase == nullptr) { |
| Q_ASSERT(false); |
| return; |
| } |
| |
| Q_ASSERT(inConstantBase->m_elementCount >= (qint32)inValue.size()); |
| |
| if (inConstantBase->getShaderConstantType() == QSSGDataTypeToShaderDataTypeMap<TDataType>::getType()) { |
| QSSGRenderShaderConstant<TDataType> *inConstant = static_cast<QSSGRenderShaderConstant<TDataType> *>(inConstantBase); |
| ShaderConstantApplier<TDataType>().applyConstant(program, |
| inConstant->m_location, |
| inValue.size(), |
| inConstant->m_type, |
| inValue, |
| inConstant->m_value, |
| inTranspose); |
| } else { |
| Q_ASSERT(false); |
| } |
| } |
| |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, qint32 inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const qint32_2 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const qint32_3 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const qint32_4 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, bool inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const bool_2 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const bool_3 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const bool_4 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const float &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const QVector2D &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const QVector3D &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const QVector4D &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const QColor &inValue, const qint32 inCount) |
| { |
| QVector4D value(float(inValue.redF()), float(inValue.greenF()), float(inValue.blueF()), float(inValue.alphaF())); |
| setConstantValueOfType(this, inConstant, value, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const quint32 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const quint32_2 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const quint32_3 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, const quint32_4 &inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, |
| const QMatrix3x3 &inValue, |
| const qint32 inCount, |
| bool inTranspose) |
| { |
| setMatrixConstantValueOfType(this, inConstant, inValue, inCount, inTranspose); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, |
| const QMatrix4x4 &inValue, |
| const qint32 inCount, |
| bool inTranspose) |
| { |
| setMatrixConstantValueOfType(this, inConstant, inValue, inCount, inTranspose); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, |
| const QSSGDataView<QMatrix4x4> inValue, |
| const qint32 inCount) |
| { |
| setMatrixConstantValueOfType(this, inConstant, inValue, inCount, false); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, QSSGRenderTexture2D *inValue, const qint32 inCount) |
| { |
| Q_UNUSED(inCount) |
| setConstantValueOfType(this, inConstant, inValue, 1); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, QSSGRenderTexture2D **inValue, const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, QSSGRenderTextureCube *inValue, const qint32 inCount) |
| { |
| Q_UNUSED(inCount) |
| setConstantValueOfType(this, inConstant, inValue, 1); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, |
| QSSGRenderTextureCube **inValue, |
| const qint32 inCount) |
| { |
| setConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *inConstant, QSSGRenderImage2D *inValue, const qint32 inCount) |
| { |
| setSamplerConstantValueOfType(this, inConstant, inValue, inCount); |
| } |
| void QSSGRenderShaderProgram::setConstantValue(QSSGRenderShaderConstantBase *, QSSGRenderDataBuffer *, const qint32) |
| { |
| // this is merely a dummy right now |
| } |
| |
| void QSSGRenderShaderProgram::bindComputeInput(QSSGRenderDataBuffer *inBuffer, quint32 inIndex) |
| { |
| QSSGRenderBackend::QSSGRenderBackendBufferObject obj(nullptr); |
| if (inBuffer) |
| obj = inBuffer->handle(); |
| m_backend->programSetStorageBuffer(inIndex, obj); |
| } |
| |
| namespace { |
| void writeErrorMessage(const char *tag, const QByteArray &message) |
| { |
| const auto lines = message.split('\n'); |
| for (const auto &line : lines) |
| qCCritical(RENDER_INVALID_OPERATION, "%s: %s", tag, line.constData()); |
| } |
| } |
| |
| QSSGRenderVertFragCompilationResult QSSGRenderShaderProgram::create(const QSSGRef<QSSGRenderContext> &context, |
| const char *programName, |
| QSSGByteView vertShaderSource, |
| QSSGByteView fragShaderSource, |
| QSSGByteView tessControlShaderSource, |
| QSSGByteView tessEvaluationShaderSource, |
| QSSGByteView geometryShaderSource, |
| bool separateProgram, |
| QSSGRenderShaderProgramBinaryType type, |
| bool binaryProgram) |
| { |
| QSSGRenderVertFragCompilationResult result; |
| result.m_shaderName = programName; |
| |
| // our minimum requirement is a vertex and a fragment shader or geometry shader |
| // if we should treat it as a separate program we don't care |
| if (!separateProgram && (vertShaderSource.size() == 0 || (fragShaderSource.size() == 0 && geometryShaderSource.size() == 0))) { |
| qCCritical(RENDER_INVALID_PARAMETER, "Vertex or fragment (geometry) source have 0 length"); |
| Q_ASSERT(false); |
| return result; |
| } |
| |
| if (binaryProgram && type != QSSGRenderShaderProgramBinaryType::NVBinary) { |
| qCCritical(RENDER_INVALID_PARAMETER, "Unrecoginzed binary format"); |
| Q_ASSERT(false); |
| return result; |
| } |
| |
| const QSSGRef<QSSGRenderBackend> &backend = context->backend(); |
| |
| // first create and compile shaders |
| QSSGRenderBackend::QSSGRenderBackendVertexShaderObject vtxShader = nullptr; |
| if (vertShaderSource.size()) { |
| QByteArray errorMessage; |
| vtxShader = backend->createVertexShader(vertShaderSource, errorMessage, binaryProgram); |
| if (!vtxShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate vertex shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, "Vertex source:\n%s", nonNull((const char *)vertShaderSource.begin())); |
| writeErrorMessage("Vertex compilation output:", errorMessage); |
| return result; |
| } |
| } |
| QSSGRenderBackend::QSSGRenderBackendFragmentShaderObject fragShader = nullptr; |
| if (fragShaderSource.size()) { |
| QByteArray errorMessage; |
| fragShader = backend->createFragmentShader(fragShaderSource, errorMessage, binaryProgram); |
| if (!fragShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate fragment shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, "Fragment source:\n%s", nonNull((const char *)fragShaderSource.begin())); |
| writeErrorMessage("Fragment compilation output:", errorMessage); |
| return result; |
| } |
| } |
| QSSGRenderBackend::QSSGRenderBackendTessControlShaderObject tcShader = nullptr; |
| if (tessControlShaderSource.size()) { |
| QByteArray errorMessage; |
| tcShader = backend->createTessControlShader(tessControlShaderSource, errorMessage, binaryProgram); |
| if (!tcShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate tessellation control shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, "Tessellation control source:\n%s", nonNull((const char *)tessControlShaderSource.begin())); |
| writeErrorMessage("Tessellation control compilation output:", errorMessage); |
| return result; |
| } |
| } |
| QSSGRenderBackend::QSSGRenderBackendTessEvaluationShaderObject teShader = nullptr; |
| if (tessEvaluationShaderSource.size()) { |
| QByteArray errorMessage; |
| teShader = backend->createTessEvaluationShader(tessEvaluationShaderSource, errorMessage, binaryProgram); |
| if (!teShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate tessellation evaluation shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, |
| "Tessellation evaluation source:\n%s", |
| nonNull((const char *)tessEvaluationShaderSource.begin())); |
| writeErrorMessage("Tessellation evaluation compilation output:", errorMessage); |
| return result; |
| } |
| } |
| QSSGRenderBackend::QSSGRenderBackendGeometryShaderObject geShader = nullptr; |
| if (geometryShaderSource.size()) { |
| QByteArray errorMessage; |
| geShader = backend->createGeometryShader(geometryShaderSource, errorMessage, binaryProgram); |
| if (!geShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate geometry shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, "Geometry source:\n%s", nonNull((const char *)geometryShaderSource.begin())); |
| writeErrorMessage("Geometry compilation output:", errorMessage); |
| return result; |
| } |
| } |
| |
| // shaders were succesfully created |
| result.m_shader = new QSSGRenderShaderProgram(context, programName, separateProgram); |
| |
| static const bool dumpShader = (qEnvironmentVariableIntValue("QT_QUICK3D_DUMP_SHADERS") > 0); |
| if (dumpShader) { |
| qCInfo(RENDER_SHADER_INFO, "Vertex source:\n%s", nonNull((const char *)vertShaderSource.begin())); |
| qCInfo(RENDER_SHADER_INFO, "Fragment source:\n%s", nonNull((const char *)fragShaderSource.begin())); |
| } |
| |
| // attach programs |
| if (vtxShader) |
| result.m_shader->attach(vtxShader); |
| if (fragShader) |
| result.m_shader->attach(fragShader); |
| if (tcShader) |
| result.m_shader->attach(tcShader); |
| if (teShader) |
| result.m_shader->attach(teShader); |
| if (geShader) |
| result.m_shader->attach(geShader); |
| |
| // link program |
| if (!result.m_shader->link()) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to link program!!"); |
| writeErrorMessage("Program link output:", result.m_shader->errorMessage()); |
| |
| // delete program |
| result.m_shader = nullptr; |
| } else { |
| // clean up |
| if (vtxShader) |
| result.m_shader->detach(vtxShader); |
| if (fragShader) |
| result.m_shader->detach(fragShader); |
| if (tcShader) |
| result.m_shader->detach(tcShader); |
| if (teShader) |
| result.m_shader->detach(teShader); |
| if (geShader) |
| result.m_shader->detach(geShader); |
| } |
| |
| backend->releaseVertexShader(vtxShader); |
| backend->releaseFragmentShader(fragShader); |
| backend->releaseTessControlShader(tcShader); |
| backend->releaseTessEvaluationShader(teShader); |
| backend->releaseGeometryShader(geShader); |
| |
| return result; |
| } |
| |
| QSSGRenderVertFragCompilationResult QSSGRenderShaderProgram::create( |
| const QSSGRef<QSSGRenderContext> &context, const char *programName, |
| quint32 format, const QByteArray &binary) |
| { |
| QSSGRenderVertFragCompilationResult result; |
| result.m_shaderName = programName; |
| result.m_shader = new QSSGRenderShaderProgram(context, programName, false); |
| result.m_shader->link(format, binary); |
| return result; |
| } |
| |
| QSSGRenderVertFragCompilationResult QSSGRenderShaderProgram::createCompute(const QSSGRef<QSSGRenderContext> &context, |
| const char *programName, |
| QSSGByteView computeShaderSource) |
| { |
| QSSGRenderVertFragCompilationResult result; |
| QSSGRef<QSSGRenderShaderProgram> pProgram; |
| bool bProgramIsValid = true; |
| |
| result.m_shaderName = programName; |
| |
| // check source |
| if (computeShaderSource.size() == 0) { |
| qCCritical(RENDER_INVALID_PARAMETER, "compute source has 0 length"); |
| Q_ASSERT(false); |
| return result; |
| } |
| |
| const auto &backend = context->backend(); |
| QByteArray errorMessage; |
| QSSGRenderBackend::QSSGRenderBackendComputeShaderObject computeShader = |
| backend->createComputeShader(computeShaderSource, errorMessage, false); |
| |
| if (computeShader) { |
| // shaders were successfully created |
| pProgram = new QSSGRenderShaderProgram(context, programName, false); |
| |
| if (pProgram) { |
| // attach programs |
| pProgram->attach(computeShader); |
| |
| // link program |
| bProgramIsValid = pProgram->link(); |
| |
| // set program type |
| pProgram->m_programType = ProgramType::Compute; |
| } |
| } |
| |
| // if anything went wrong print out |
| if (!computeShader || !bProgramIsValid) { |
| if (!computeShader) { |
| qCCritical(RENDER_INTERNAL_ERROR, "Failed to generate compute shader!!"); |
| qCCritical(RENDER_INTERNAL_ERROR, "Shader source:\n%s", nonNull((const char *)computeShaderSource.begin())); |
| writeErrorMessage("Compute shader compilation output:", errorMessage); |
| } |
| } |
| |
| // set program |
| result.m_shader = pProgram; |
| |
| return result; |
| } |
| |
| QSSGRenderVertFragCompilationResult::QSSGRenderVertFragCompilationResult() = default; |
| |
| QSSGRenderVertFragCompilationResult::~QSSGRenderVertFragCompilationResult() = default; |
| |
| QSSGRenderVertFragCompilationResult::QSSGRenderVertFragCompilationResult(const QSSGRenderVertFragCompilationResult &other) = default; |
| |
| QSSGRenderVertFragCompilationResult &QSSGRenderVertFragCompilationResult::operator=(const QSSGRenderVertFragCompilationResult &other) = default; |
| |
| QT_END_NAMESPACE |