| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part 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 "qsgvideonode_yuv_p.h" |
| #include <QtCore/qmutex.h> |
| #include <QtQuick/qsgtexturematerial.h> |
| #include <QtQuick/qsgmaterial.h> |
| #include <QtGui/QOpenGLContext> |
| #include <QtGui/QOpenGLFunctions> |
| #include <QtGui/QOpenGLShaderProgram> |
| |
| #ifndef GL_RED |
| #define GL_RED 0x1903 |
| #endif |
| #ifndef GL_GREEN |
| #define GL_GREEN 0x1904 |
| #endif |
| #ifndef GL_RG |
| #define GL_RG 0x8227 |
| #endif |
| #ifndef GL_TEXTURE_SWIZZLE_R |
| #define GL_TEXTURE_SWIZZLE_R 0x8E42 |
| #endif |
| #ifndef GL_TEXTURE_SWIZZLE_G |
| #define GL_TEXTURE_SWIZZLE_G 0x8E43 |
| #endif |
| #ifndef GL_TEXTURE_SWIZZLE_B |
| #define GL_TEXTURE_SWIZZLE_B 0x8E44 |
| #endif |
| #ifndef GL_TEXTURE_SWIZZLE_A |
| #define GL_TEXTURE_SWIZZLE_A 0x8E45 |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| QList<QVideoFrame::PixelFormat> QSGVideoNodeFactory_YUV::supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const |
| { |
| QList<QVideoFrame::PixelFormat> formats; |
| |
| if (handleType == QAbstractVideoBuffer::NoHandle) { |
| formats << QVideoFrame::Format_YUV420P << QVideoFrame::Format_YV12 << QVideoFrame::Format_YUV422P |
| << QVideoFrame::Format_NV12 << QVideoFrame::Format_NV21 |
| << QVideoFrame::Format_UYVY << QVideoFrame::Format_YUYV; |
| } |
| |
| return formats; |
| } |
| |
| QSGVideoNode *QSGVideoNodeFactory_YUV::createNode(const QVideoSurfaceFormat &format) |
| { |
| if (supportedPixelFormats(format.handleType()).contains(format.pixelFormat())) |
| return new QSGVideoNode_YUV(format); |
| |
| return 0; |
| } |
| |
| |
| class QSGVideoMaterialShader_YUV_BiPlanar : public QSGMaterialShader |
| { |
| public: |
| QSGVideoMaterialShader_YUV_BiPlanar() |
| : QSGMaterialShader() |
| { |
| setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/qtmultimediaquicktools/shaders/biplanaryuvvideo.vert")); |
| setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtmultimediaquicktools/shaders/biplanaryuvvideo.frag")); |
| } |
| |
| void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; |
| |
| char const *const *attributeNames() const override { |
| static const char *names[] = { |
| "qt_VertexPosition", |
| "qt_VertexTexCoord", |
| 0 |
| }; |
| return names; |
| } |
| |
| protected: |
| void initialize() override { |
| m_id_matrix = program()->uniformLocation("qt_Matrix"); |
| m_id_plane1Width = program()->uniformLocation("plane1Width"); |
| m_id_plane2Width = program()->uniformLocation("plane2Width"); |
| m_id_plane1Texture = program()->uniformLocation("plane1Texture"); |
| m_id_plane2Texture = program()->uniformLocation("plane2Texture"); |
| m_id_colorMatrix = program()->uniformLocation("colorMatrix"); |
| m_id_opacity = program()->uniformLocation("opacity"); |
| } |
| |
| int m_id_matrix; |
| int m_id_plane1Width; |
| int m_id_plane2Width; |
| int m_id_plane1Texture; |
| int m_id_plane2Texture; |
| int m_id_colorMatrix; |
| int m_id_opacity; |
| }; |
| |
| class QSGVideoMaterialShader_UYVY : public QSGMaterialShader |
| { |
| public: |
| QSGVideoMaterialShader_UYVY() |
| : QSGMaterialShader() |
| { |
| setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/qtmultimediaquicktools/shaders/monoplanarvideo.vert")); |
| setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtmultimediaquicktools/shaders/uyvyvideo.frag")); |
| } |
| |
| void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; |
| |
| char const *const *attributeNames() const override { |
| static const char *names[] = { |
| "qt_VertexPosition", |
| "qt_VertexTexCoord", |
| 0 |
| }; |
| return names; |
| } |
| |
| protected: |
| void initialize() override { |
| m_id_matrix = program()->uniformLocation("qt_Matrix"); |
| m_id_yTexture = program()->uniformLocation("yTexture"); |
| m_id_uvTexture = program()->uniformLocation("uvTexture"); |
| m_id_colorMatrix = program()->uniformLocation("colorMatrix"); |
| m_id_opacity = program()->uniformLocation("opacity"); |
| QSGMaterialShader::initialize(); |
| } |
| |
| int m_id_matrix; |
| int m_id_yTexture; |
| int m_id_uvTexture; |
| int m_id_colorMatrix; |
| int m_id_opacity; |
| }; |
| |
| |
| class QSGVideoMaterialShader_YUYV : public QSGVideoMaterialShader_UYVY |
| { |
| public: |
| QSGVideoMaterialShader_YUYV() |
| : QSGVideoMaterialShader_UYVY() |
| { |
| setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtmultimediaquicktools/shaders/yuyvvideo.frag")); |
| } |
| }; |
| |
| |
| class QSGVideoMaterialShader_YUV_BiPlanar_swizzle : public QSGVideoMaterialShader_YUV_BiPlanar |
| { |
| public: |
| QSGVideoMaterialShader_YUV_BiPlanar_swizzle() |
| : QSGVideoMaterialShader_YUV_BiPlanar() |
| { |
| setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtmultimediaquicktools/shaders/biplanaryuvvideo_swizzle.frag")); |
| } |
| }; |
| |
| |
| class QSGVideoMaterialShader_YUV_TriPlanar : public QSGVideoMaterialShader_YUV_BiPlanar |
| { |
| public: |
| QSGVideoMaterialShader_YUV_TriPlanar() |
| : QSGVideoMaterialShader_YUV_BiPlanar() |
| { |
| setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/qtmultimediaquicktools/shaders/triplanaryuvvideo.vert")); |
| setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qtmultimediaquicktools/shaders/triplanaryuvvideo.frag")); |
| } |
| |
| void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; |
| |
| protected: |
| void initialize() override { |
| m_id_plane3Width = program()->uniformLocation("plane3Width"); |
| m_id_plane3Texture = program()->uniformLocation("plane3Texture"); |
| QSGVideoMaterialShader_YUV_BiPlanar::initialize(); |
| } |
| |
| int m_id_plane3Width; |
| int m_id_plane3Texture; |
| }; |
| |
| |
| class QSGVideoMaterial_YUV : public QSGMaterial |
| { |
| public: |
| QSGVideoMaterial_YUV(const QVideoSurfaceFormat &format); |
| ~QSGVideoMaterial_YUV(); |
| |
| QSGMaterialType *type() const override { |
| static QSGMaterialType biPlanarType, biPlanarSwizzleType, triPlanarType, uyvyType, yuyvType; |
| |
| switch (m_format.pixelFormat()) { |
| case QVideoFrame::Format_NV12: |
| return &biPlanarType; |
| case QVideoFrame::Format_NV21: |
| return &biPlanarSwizzleType; |
| case QVideoFrame::Format_UYVY: |
| return &uyvyType; |
| case QVideoFrame::Format_YUYV: |
| return &yuyvType; |
| default: // Currently: YUV420P, YUV422P and YV12 |
| return &triPlanarType; |
| } |
| } |
| |
| QSGMaterialShader *createShader() const override { |
| switch (m_format.pixelFormat()) { |
| case QVideoFrame::Format_NV12: |
| return new QSGVideoMaterialShader_YUV_BiPlanar; |
| case QVideoFrame::Format_NV21: |
| return new QSGVideoMaterialShader_YUV_BiPlanar_swizzle; |
| case QVideoFrame::Format_UYVY: |
| return new QSGVideoMaterialShader_UYVY; |
| case QVideoFrame::Format_YUYV: |
| return new QSGVideoMaterialShader_YUYV; |
| default: // Currently: YUV420P, YUV422P and YV12 |
| return new QSGVideoMaterialShader_YUV_TriPlanar; |
| } |
| } |
| |
| int compare(const QSGMaterial *other) const override { |
| const QSGVideoMaterial_YUV *m = static_cast<const QSGVideoMaterial_YUV *>(other); |
| if (!m_textureIds[0]) |
| return 1; |
| |
| int d = m_textureIds[0] - m->m_textureIds[0]; |
| if (d) |
| return d; |
| else if ((d = m_textureIds[1] - m->m_textureIds[1]) != 0) |
| return d; |
| else |
| return m_textureIds[2] - m->m_textureIds[2]; |
| } |
| |
| void updateBlending() { |
| setFlag(Blending, qFuzzyCompare(m_opacity, qreal(1.0)) ? false : true); |
| } |
| |
| void setCurrentFrame(const QVideoFrame &frame) { |
| QMutexLocker lock(&m_frameMutex); |
| m_frame = frame; |
| } |
| |
| void bind(); |
| void bindTexture(int id, int w, int h, const uchar *bits, GLenum format); |
| |
| QVideoSurfaceFormat m_format; |
| QSize m_textureSize; |
| int m_planeCount; |
| |
| GLuint m_textureIds[3]; |
| GLfloat m_planeWidth[3]; |
| |
| qreal m_opacity; |
| QMatrix4x4 m_colorMatrix; |
| |
| QVideoFrame m_frame; |
| QMutex m_frameMutex; |
| }; |
| |
| QSGVideoMaterial_YUV::QSGVideoMaterial_YUV(const QVideoSurfaceFormat &format) : |
| m_format(format), |
| m_opacity(1.0) |
| { |
| memset(m_textureIds, 0, sizeof(m_textureIds)); |
| |
| switch (format.pixelFormat()) { |
| case QVideoFrame::Format_NV12: |
| case QVideoFrame::Format_NV21: |
| m_planeCount = 2; |
| break; |
| case QVideoFrame::Format_YUV420P: |
| case QVideoFrame::Format_YV12: |
| case QVideoFrame::Format_YUV422P: |
| m_planeCount = 3; |
| break; |
| case QVideoFrame::Format_UYVY: |
| case QVideoFrame::Format_YUYV: |
| default: |
| m_planeCount = 2; |
| break; |
| } |
| |
| switch (format.yCbCrColorSpace()) { |
| case QVideoSurfaceFormat::YCbCr_JPEG: |
| m_colorMatrix = QMatrix4x4( |
| 1.0f, 0.000f, 1.402f, -0.701f, |
| 1.0f, -0.344f, -0.714f, 0.529f, |
| 1.0f, 1.772f, 0.000f, -0.886f, |
| 0.0f, 0.000f, 0.000f, 1.0000f); |
| break; |
| case QVideoSurfaceFormat::YCbCr_BT709: |
| case QVideoSurfaceFormat::YCbCr_xvYCC709: |
| m_colorMatrix = QMatrix4x4( |
| 1.164f, 0.000f, 1.793f, -0.5727f, |
| 1.164f, -0.534f, -0.213f, 0.3007f, |
| 1.164f, 2.115f, 0.000f, -1.1302f, |
| 0.0f, 0.000f, 0.000f, 1.0000f); |
| break; |
| default: //BT 601: |
| m_colorMatrix = QMatrix4x4( |
| 1.164f, 0.000f, 1.596f, -0.8708f, |
| 1.164f, -0.392f, -0.813f, 0.5296f, |
| 1.164f, 2.017f, 0.000f, -1.081f, |
| 0.0f, 0.000f, 0.000f, 1.0000f); |
| } |
| |
| setFlag(Blending, false); |
| } |
| |
| QSGVideoMaterial_YUV::~QSGVideoMaterial_YUV() |
| { |
| if (!m_textureSize.isEmpty()) { |
| if (QOpenGLContext *current = QOpenGLContext::currentContext()) |
| current->functions()->glDeleteTextures(m_planeCount, m_textureIds); |
| else |
| qWarning() << "QSGVideoMaterial_YUV: Cannot obtain GL context, unable to delete textures"; |
| } |
| } |
| |
| void QSGVideoMaterial_YUV::bind() |
| { |
| QOpenGLFunctions *functions = QOpenGLContext::currentContext()->functions(); |
| QSurfaceFormat::OpenGLContextProfile profile = QOpenGLContext::currentContext()->format().profile(); |
| |
| QMutexLocker lock(&m_frameMutex); |
| if (m_frame.isValid()) { |
| if (m_frame.map(QAbstractVideoBuffer::ReadOnly)) { |
| int fw = m_frame.width(); |
| int fh = m_frame.height(); |
| |
| // Frame has changed size, recreate textures... |
| if (m_textureSize != m_frame.size()) { |
| if (!m_textureSize.isEmpty()) |
| functions->glDeleteTextures(m_planeCount, m_textureIds); |
| functions->glGenTextures(m_planeCount, m_textureIds); |
| m_textureSize = m_frame.size(); |
| } |
| |
| GLint previousAlignment; |
| const GLenum texFormat1 = (profile == QSurfaceFormat::CoreProfile) ? GL_RED : GL_LUMINANCE; |
| const GLenum texFormat2 = (profile == QSurfaceFormat::CoreProfile) ? GL_RG : GL_LUMINANCE_ALPHA; |
| |
| functions->glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousAlignment); |
| functions->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| |
| if (m_format.pixelFormat() == QVideoFrame::Format_UYVY |
| || m_format.pixelFormat() == QVideoFrame::Format_YUYV) { |
| int fw = m_frame.width(); |
| |
| m_planeWidth[0] = fw; |
| // In YUYV texture the UV plane appears with the 1/2 of image and Y width. |
| m_planeWidth[1] = fw / 2; |
| functions->glActiveTexture(GL_TEXTURE1); |
| // Either r,b (YUYV) or g,a (UYVY) values are used as source of UV. |
| // Additionally U and V are set per 2 pixels hence only 1/2 of image width is used. |
| // Interpreting this properly in shaders allows to not copy or not make conditionals inside shaders, |
| // only interpretation of data changes. |
| bindTexture(m_textureIds[1], m_planeWidth[1], m_frame.height(), m_frame.bits(), GL_RGBA); |
| functions->glActiveTexture(GL_TEXTURE0); // Finish with 0 as default texture unit |
| // Either red (YUYV) or alpha (UYVY) values are used as source of Y |
| bindTexture(m_textureIds[0], m_planeWidth[0], m_frame.height(), m_frame.bits(), texFormat2); |
| } else if (m_format.pixelFormat() == QVideoFrame::Format_NV12 |
| || m_format.pixelFormat() == QVideoFrame::Format_NV21) { |
| const int y = 0; |
| const int uv = 1; |
| |
| m_planeWidth[0] = m_planeWidth[1] = qreal(fw) / m_frame.bytesPerLine(y); |
| |
| functions->glActiveTexture(GL_TEXTURE1); |
| bindTexture(m_textureIds[1], m_frame.bytesPerLine(uv) / 2, fh / 2, m_frame.bits(uv), texFormat2); |
| functions->glActiveTexture(GL_TEXTURE0); // Finish with 0 as default texture unit |
| bindTexture(m_textureIds[0], m_frame.bytesPerLine(y), fh, m_frame.bits(y), texFormat1); |
| |
| } else { // YUV420P || YV12 || YUV422P |
| const int y = 0; |
| const int u = m_frame.pixelFormat() == QVideoFrame::Format_YV12 ? 2 : 1; |
| const int v = m_frame.pixelFormat() == QVideoFrame::Format_YV12 ? 1 : 2; |
| |
| m_planeWidth[0] = qreal(fw) / m_frame.bytesPerLine(y); |
| m_planeWidth[1] = m_planeWidth[2] = qreal(fw) / (2 * m_frame.bytesPerLine(u)); |
| |
| const int uvHeight = m_frame.pixelFormat() == QVideoFrame::Format_YUV422P ? fh : fh / 2; |
| |
| functions->glActiveTexture(GL_TEXTURE1); |
| bindTexture(m_textureIds[1], m_frame.bytesPerLine(u), uvHeight, m_frame.bits(u), texFormat1); |
| functions->glActiveTexture(GL_TEXTURE2); |
| bindTexture(m_textureIds[2], m_frame.bytesPerLine(v), uvHeight, m_frame.bits(v), texFormat1); |
| functions->glActiveTexture(GL_TEXTURE0); // Finish with 0 as default texture unit |
| bindTexture(m_textureIds[0], m_frame.bytesPerLine(y), fh, m_frame.bits(y), texFormat1); |
| } |
| |
| functions->glPixelStorei(GL_UNPACK_ALIGNMENT, previousAlignment); |
| m_frame.unmap(); |
| } |
| |
| m_frame = QVideoFrame(); |
| } else { |
| // Go backwards to finish with GL_TEXTURE0 |
| for (int i = m_planeCount - 1; i >= 0; --i) { |
| functions->glActiveTexture(GL_TEXTURE0 + i); |
| functions->glBindTexture(GL_TEXTURE_2D, m_textureIds[i]); |
| } |
| } |
| } |
| |
| void QSGVideoMaterial_YUV::bindTexture(int id, int w, int h, const uchar *bits, GLenum format) |
| { |
| QOpenGLFunctions *functions = QOpenGLContext::currentContext()->functions(); |
| functions->glBindTexture(GL_TEXTURE_2D, id); |
| functions->glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, bits); |
| // replacement for GL_LUMINANCE_ALPHA in core profile |
| if (format == GL_RG) { |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_GREEN); |
| } |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| functions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| } |
| |
| QSGVideoNode_YUV::QSGVideoNode_YUV(const QVideoSurfaceFormat &format) : |
| m_format(format) |
| { |
| setFlag(QSGNode::OwnsMaterial); |
| m_material = new QSGVideoMaterial_YUV(format); |
| setMaterial(m_material); |
| } |
| |
| QSGVideoNode_YUV::~QSGVideoNode_YUV() |
| { |
| } |
| |
| void QSGVideoNode_YUV::setCurrentFrame(const QVideoFrame &frame, FrameFlags) |
| { |
| m_material->setCurrentFrame(frame); |
| markDirty(DirtyMaterial); |
| } |
| |
| void QSGVideoMaterialShader_YUV_BiPlanar::updateState(const RenderState &state, |
| QSGMaterial *newMaterial, |
| QSGMaterial *oldMaterial) |
| { |
| Q_UNUSED(oldMaterial); |
| |
| QSGVideoMaterial_YUV *mat = static_cast<QSGVideoMaterial_YUV *>(newMaterial); |
| program()->setUniformValue(m_id_plane1Texture, 0); |
| program()->setUniformValue(m_id_plane2Texture, 1); |
| |
| mat->bind(); |
| |
| program()->setUniformValue(m_id_colorMatrix, mat->m_colorMatrix); |
| program()->setUniformValue(m_id_plane1Width, mat->m_planeWidth[0]); |
| program()->setUniformValue(m_id_plane2Width, mat->m_planeWidth[1]); |
| if (state.isOpacityDirty()) { |
| mat->m_opacity = state.opacity(); |
| program()->setUniformValue(m_id_opacity, GLfloat(mat->m_opacity)); |
| } |
| if (state.isMatrixDirty()) |
| program()->setUniformValue(m_id_matrix, state.combinedMatrix()); |
| } |
| |
| void QSGVideoMaterialShader_YUV_TriPlanar::updateState(const RenderState &state, |
| QSGMaterial *newMaterial, |
| QSGMaterial *oldMaterial) |
| { |
| QSGVideoMaterialShader_YUV_BiPlanar::updateState(state, newMaterial, oldMaterial); |
| |
| QSGVideoMaterial_YUV *mat = static_cast<QSGVideoMaterial_YUV *>(newMaterial); |
| program()->setUniformValue(m_id_plane3Texture, 2); |
| program()->setUniformValue(m_id_plane3Width, mat->m_planeWidth[2]); |
| } |
| |
| void QSGVideoMaterialShader_UYVY::updateState(const RenderState &state, |
| QSGMaterial *newMaterial, |
| QSGMaterial *oldMaterial) |
| { |
| Q_UNUSED(oldMaterial); |
| |
| QSGVideoMaterial_YUV *mat = static_cast<QSGVideoMaterial_YUV *>(newMaterial); |
| program()->setUniformValue(m_id_yTexture, 0); |
| program()->setUniformValue(m_id_uvTexture, 1); |
| |
| mat->bind(); |
| |
| program()->setUniformValue(m_id_colorMatrix, mat->m_colorMatrix); |
| |
| if (state.isOpacityDirty()) { |
| mat->m_opacity = state.opacity(); |
| program()->setUniformValue(m_id_opacity, GLfloat(mat->m_opacity)); |
| } |
| |
| if (state.isMatrixDirty()) |
| program()->setUniformValue(m_id_matrix, state.combinedMatrix()); |
| } |
| |
| QT_END_NAMESPACE |