| /**************************************************************************** |
| ** |
| ** 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 "qpaintervideosurface_p.h" |
| |
| #include <qmath.h> |
| |
| #include <qpainter.h> |
| #include <qvariant.h> |
| #include <qvideosurfaceformat.h> |
| #include <private/qmediaopenglhelper_p.h> |
| |
| #if QT_CONFIG(opengl) |
| #include <QOpenGLContext> |
| #include <QOpenGLFunctions> |
| #include <QOpenGLShaderProgram> |
| #include <QtGui/QWindow> |
| #ifndef GL_CLAMP_TO_EDGE |
| #define GL_CLAMP_TO_EDGE 0x812F |
| #endif |
| #ifndef GL_RGB8 |
| #define GL_RGB8 0x8051 |
| #endif |
| #endif |
| |
| #include <QtDebug> |
| QT_BEGIN_NAMESPACE |
| |
| QVideoSurfacePainter::~QVideoSurfacePainter() |
| { |
| } |
| |
| class QVideoSurfaceGenericPainter : public QVideoSurfacePainter |
| { |
| public: |
| QVideoSurfaceGenericPainter(); |
| |
| QList<QVideoFrame::PixelFormat> supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const override; |
| |
| bool isFormatSupported(const QVideoSurfaceFormat &format) const override; |
| |
| QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format) override; |
| void stop() override; |
| |
| QAbstractVideoSurface::Error setCurrentFrame(const QVideoFrame &frame) override; |
| |
| QAbstractVideoSurface::Error paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) override; |
| |
| void updateColors(int brightness, int contrast, int hue, int saturation) override; |
| |
| private: |
| QList<QVideoFrame::PixelFormat> m_imagePixelFormats; |
| QVideoFrame m_frame; |
| QSize m_imageSize; |
| QImage::Format m_imageFormat; |
| QVideoSurfaceFormat::Direction m_scanLineDirection; |
| bool m_mirrored; |
| }; |
| |
| QVideoSurfaceGenericPainter::QVideoSurfaceGenericPainter() |
| : m_imageFormat(QImage::Format_Invalid) |
| , m_scanLineDirection(QVideoSurfaceFormat::TopToBottom) |
| , m_mirrored(false) |
| { |
| m_imagePixelFormats << QVideoFrame::Format_RGB32; |
| |
| // The raster formats should be a subset of the GL formats. |
| #ifndef QT_NO_OPENGL |
| if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGLES) |
| #endif |
| m_imagePixelFormats << QVideoFrame::Format_RGB24; |
| |
| m_imagePixelFormats << QVideoFrame::Format_ARGB32 |
| << QVideoFrame::Format_RGB565 |
| << QVideoFrame::Format_Y8; |
| } |
| |
| QList<QVideoFrame::PixelFormat> QVideoSurfaceGenericPainter::supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const |
| { |
| switch (handleType) { |
| case QAbstractVideoBuffer::QPixmapHandle: |
| case QAbstractVideoBuffer::NoHandle: |
| return m_imagePixelFormats; |
| default: |
| ; |
| } |
| return QList<QVideoFrame::PixelFormat>(); |
| } |
| |
| bool QVideoSurfaceGenericPainter::isFormatSupported(const QVideoSurfaceFormat &format) const |
| { |
| switch (format.handleType()) { |
| case QAbstractVideoBuffer::QPixmapHandle: |
| return true; |
| case QAbstractVideoBuffer::NoHandle: |
| return m_imagePixelFormats.contains(format.pixelFormat()) |
| && !format.frameSize().isEmpty(); |
| default: |
| ; |
| } |
| return false; |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGenericPainter::start(const QVideoSurfaceFormat &format) |
| { |
| m_frame = QVideoFrame(); |
| m_imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); |
| // Do not render into ARGB32 images using QPainter. |
| // Using QImage::Format_ARGB32_Premultiplied is significantly faster. |
| if (m_imageFormat == QImage::Format_ARGB32) |
| m_imageFormat = QImage::Format_ARGB32_Premultiplied; |
| |
| m_imageSize = format.frameSize(); |
| m_scanLineDirection = format.scanLineDirection(); |
| m_mirrored = format.property("mirrored").toBool(); |
| |
| const QAbstractVideoBuffer::HandleType t = format.handleType(); |
| if (t == QAbstractVideoBuffer::NoHandle) { |
| bool ok = m_imageFormat != QImage::Format_Invalid && !m_imageSize.isEmpty(); |
| #ifndef QT_NO_OPENGL |
| if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) |
| ok &= format.pixelFormat() != QVideoFrame::Format_RGB24; |
| #endif |
| if (ok) |
| return QAbstractVideoSurface::NoError; |
| } else if (t == QAbstractVideoBuffer::QPixmapHandle) { |
| return QAbstractVideoSurface::NoError; |
| } |
| return QAbstractVideoSurface::UnsupportedFormatError; |
| } |
| |
| void QVideoSurfaceGenericPainter::stop() |
| { |
| m_frame = QVideoFrame(); |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGenericPainter::setCurrentFrame(const QVideoFrame &frame) |
| { |
| m_frame = frame; |
| |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGenericPainter::paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) |
| { |
| if (!m_frame.isValid()) { |
| painter->fillRect(target, Qt::black); |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| if (m_frame.handleType() == QAbstractVideoBuffer::QPixmapHandle) { |
| painter->drawPixmap(target, m_frame.handle().value<QPixmap>(), source); |
| } else if (m_frame.map(QAbstractVideoBuffer::ReadOnly)) { |
| QImage image( |
| m_frame.bits(), |
| m_imageSize.width(), |
| m_imageSize.height(), |
| m_frame.bytesPerLine(), |
| m_imageFormat); |
| |
| const QTransform oldTransform = painter->transform(); |
| QTransform transform = oldTransform; |
| QRectF targetRect = target; |
| if (m_scanLineDirection == QVideoSurfaceFormat::BottomToTop) { |
| transform.scale(1, -1); |
| transform.translate(0, -target.bottom()); |
| targetRect = QRectF(target.x(), 0, target.width(), target.height()); |
| } |
| |
| if (m_mirrored) { |
| transform.scale(-1, 1); |
| transform.translate(-target.right(), 0); |
| targetRect = QRectF(0, targetRect.y(), target.width(), target.height()); |
| } |
| painter->setTransform(transform); |
| painter->drawImage(targetRect, image, source); |
| painter->setTransform(oldTransform); |
| |
| m_frame.unmap(); |
| } else if (m_frame.isValid()) { |
| return QAbstractVideoSurface::IncorrectFormatError; |
| } else { |
| painter->fillRect(target, Qt::black); |
| } |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| void QVideoSurfaceGenericPainter::updateColors(int, int, int, int) |
| { |
| } |
| |
| #if QT_CONFIG(opengl) |
| |
| #ifndef APIENTRYP |
| # ifdef APIENTRY |
| # define APIENTRYP APIENTRY * |
| # else |
| # define APIENTRY |
| # define APIENTRYP * |
| # endif |
| #endif |
| |
| #ifndef GL_TEXTURE0 |
| # define GL_TEXTURE0 0x84C0 |
| # define GL_TEXTURE1 0x84C1 |
| # define GL_TEXTURE2 0x84C2 |
| #endif |
| #ifndef GL_PROGRAM_ERROR_STRING_ARB |
| # define GL_PROGRAM_ERROR_STRING_ARB 0x8874 |
| #endif |
| |
| #ifndef GL_UNSIGNED_SHORT_5_6_5 |
| # define GL_UNSIGNED_SHORT_5_6_5 33635 |
| #endif |
| |
| class QVideoSurfaceGLPainter : public QVideoSurfacePainter, protected QOpenGLFunctions |
| { |
| public: |
| QVideoSurfaceGLPainter(QOpenGLContext *context); |
| ~QVideoSurfaceGLPainter(); |
| QList<QVideoFrame::PixelFormat> supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const override; |
| |
| bool isFormatSupported(const QVideoSurfaceFormat &format) const override; |
| |
| void stop() override; |
| |
| QAbstractVideoSurface::Error setCurrentFrame(const QVideoFrame &frame) override; |
| |
| QAbstractVideoSurface::Error paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) override; |
| |
| void updateColors(int brightness, int contrast, int hue, int saturation) override; |
| void viewportDestroyed() override; |
| |
| protected: |
| void initRgbTextureInfo(GLenum internalFormat, GLuint format, GLenum type, const QSize &size); |
| void initYuv420PTextureInfo(const QSize &size); |
| void initYv12TextureInfo(const QSize &size); |
| |
| bool needsSwizzling(const QVideoSurfaceFormat &format) const { |
| return !QMediaOpenGLHelper::isANGLE() |
| && (format.pixelFormat() == QVideoFrame::Format_RGB32 |
| || format.pixelFormat() == QVideoFrame::Format_ARGB32); |
| } |
| |
| QList<QVideoFrame::PixelFormat> m_imagePixelFormats; |
| QList<QVideoFrame::PixelFormat> m_glPixelFormats; |
| QMatrix4x4 m_colorMatrix; |
| QVideoFrame m_frame; |
| |
| QOpenGLContext *m_context; |
| QAbstractVideoBuffer::HandleType m_handleType; |
| QVideoSurfaceFormat::Direction m_scanLineDirection; |
| bool m_mirrored; |
| QVideoSurfaceFormat::YCbCrColorSpace m_colorSpace; |
| GLenum m_textureFormat; |
| GLuint m_textureInternalFormat; |
| GLenum m_textureType; |
| int m_textureCount; |
| |
| static const uint Max_Textures = 3; |
| GLuint m_textureIds[Max_Textures]; |
| int m_textureWidths[Max_Textures]; |
| int m_textureHeights[Max_Textures]; |
| int m_textureOffsets[Max_Textures]; |
| bool m_yuv; |
| }; |
| |
| QVideoSurfaceGLPainter::QVideoSurfaceGLPainter(QOpenGLContext *context) |
| : m_context(context) |
| , m_handleType(QAbstractVideoBuffer::NoHandle) |
| , m_scanLineDirection(QVideoSurfaceFormat::TopToBottom) |
| , m_mirrored(false) |
| , m_colorSpace(QVideoSurfaceFormat::YCbCr_BT601) |
| , m_textureFormat(0) |
| , m_textureInternalFormat(0) |
| , m_textureType(0) |
| , m_textureCount(0) |
| , m_yuv(false) |
| { |
| memset(m_textureIds, 0, sizeof(m_textureIds)); |
| memset(m_textureWidths, 0, sizeof(m_textureWidths)); |
| memset(m_textureHeights, 0, sizeof(m_textureHeights)); |
| memset(m_textureOffsets, 0, sizeof(m_textureOffsets)); |
| |
| initializeOpenGLFunctions(); |
| } |
| |
| QVideoSurfaceGLPainter::~QVideoSurfaceGLPainter() |
| { |
| } |
| |
| void QVideoSurfaceGLPainter::viewportDestroyed() |
| { |
| m_context = 0; |
| } |
| |
| QList<QVideoFrame::PixelFormat> QVideoSurfaceGLPainter::supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const |
| { |
| switch (handleType) { |
| case QAbstractVideoBuffer::NoHandle: |
| return m_imagePixelFormats; |
| case QAbstractVideoBuffer::QPixmapHandle: |
| case QAbstractVideoBuffer::GLTextureHandle: |
| return m_glPixelFormats; |
| default: |
| ; |
| } |
| return QList<QVideoFrame::PixelFormat>(); |
| } |
| |
| bool QVideoSurfaceGLPainter::isFormatSupported(const QVideoSurfaceFormat &format) const |
| { |
| if (format.frameSize().isEmpty()) { |
| return false; |
| } else { |
| switch (format.handleType()) { |
| case QAbstractVideoBuffer::NoHandle: |
| return m_imagePixelFormats.contains(format.pixelFormat()); |
| case QAbstractVideoBuffer::QPixmapHandle: |
| case QAbstractVideoBuffer::GLTextureHandle: |
| return m_glPixelFormats.contains(format.pixelFormat()); |
| default: |
| ; |
| } |
| } |
| return false; |
| } |
| |
| |
| void QVideoSurfaceGLPainter::stop() |
| { |
| m_frame = QVideoFrame(); |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGLPainter::setCurrentFrame(const QVideoFrame &frame) |
| { |
| m_frame = frame; |
| |
| if (m_handleType == QAbstractVideoBuffer::GLTextureHandle) { |
| m_textureIds[0] = frame.handle().toInt(); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| } else if (m_frame.map(QAbstractVideoBuffer::ReadOnly)) { |
| for (int i = 0; i < m_textureCount; ++i) { |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[i]); |
| glTexImage2D( |
| GL_TEXTURE_2D, |
| 0, |
| m_textureInternalFormat, |
| m_textureWidths[i], |
| m_textureHeights[i], |
| 0, |
| m_textureFormat, |
| m_textureType, |
| m_frame.bits() + m_textureOffsets[i]); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| } |
| m_frame.unmap(); |
| } else if (m_handleType != QAbstractVideoBuffer::QPixmapHandle && m_frame.isValid()) { |
| return QAbstractVideoSurface::IncorrectFormatError; |
| } |
| |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGLPainter::paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) |
| { |
| if (!m_frame.isValid()) { |
| painter->fillRect(target, Qt::black); |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| if (m_frame.handleType() == QAbstractVideoBuffer::QPixmapHandle) { |
| painter->drawPixmap(target, m_frame.handle().value<QPixmap>(), source); |
| } else if (m_frame.isValid()) { |
| return QAbstractVideoSurface::IncorrectFormatError; |
| } else { |
| painter->fillRect(target, Qt::black); |
| } |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| void QVideoSurfaceGLPainter::updateColors(int brightness, int contrast, int hue, int saturation) |
| { |
| const qreal b = brightness / 200.0; |
| const qreal c = contrast / 100.0 + 1.0; |
| const qreal h = hue / 100.0; |
| const qreal s = saturation / 100.0 + 1.0; |
| |
| const qreal cosH = qCos(M_PI * h); |
| const qreal sinH = qSin(M_PI * h); |
| |
| const qreal h11 = 0.787 * cosH - 0.213 * sinH + 0.213; |
| const qreal h21 = -0.213 * cosH + 0.143 * sinH + 0.213; |
| const qreal h31 = -0.213 * cosH - 0.787 * sinH + 0.213; |
| |
| const qreal h12 = -0.715 * cosH - 0.715 * sinH + 0.715; |
| const qreal h22 = 0.285 * cosH + 0.140 * sinH + 0.715; |
| const qreal h32 = -0.715 * cosH + 0.715 * sinH + 0.715; |
| |
| const qreal h13 = -0.072 * cosH + 0.928 * sinH + 0.072; |
| const qreal h23 = -0.072 * cosH - 0.283 * sinH + 0.072; |
| const qreal h33 = 0.928 * cosH + 0.072 * sinH + 0.072; |
| |
| const qreal sr = (1.0 - s) * 0.3086; |
| const qreal sg = (1.0 - s) * 0.6094; |
| const qreal sb = (1.0 - s) * 0.0820; |
| |
| const qreal sr_s = sr + s; |
| const qreal sg_s = sg + s; |
| const qreal sb_s = sr + s; |
| |
| const float m4 = (s + sr + sg + sb) * (0.5 - 0.5 * c + b); |
| |
| m_colorMatrix(0, 0) = c * (sr_s * h11 + sg * h21 + sb * h31); |
| m_colorMatrix(0, 1) = c * (sr_s * h12 + sg * h22 + sb * h32); |
| m_colorMatrix(0, 2) = c * (sr_s * h13 + sg * h23 + sb * h33); |
| m_colorMatrix(0, 3) = m4; |
| |
| m_colorMatrix(1, 0) = c * (sr * h11 + sg_s * h21 + sb * h31); |
| m_colorMatrix(1, 1) = c * (sr * h12 + sg_s * h22 + sb * h32); |
| m_colorMatrix(1, 2) = c * (sr * h13 + sg_s * h23 + sb * h33); |
| m_colorMatrix(1, 3) = m4; |
| |
| m_colorMatrix(2, 0) = c * (sr * h11 + sg * h21 + sb_s * h31); |
| m_colorMatrix(2, 1) = c * (sr * h12 + sg * h22 + sb_s * h32); |
| m_colorMatrix(2, 2) = c * (sr * h13 + sg * h23 + sb_s * h33); |
| m_colorMatrix(2, 3) = m4; |
| |
| m_colorMatrix(3, 0) = 0.0; |
| m_colorMatrix(3, 1) = 0.0; |
| m_colorMatrix(3, 2) = 0.0; |
| m_colorMatrix(3, 3) = 1.0; |
| |
| if (m_yuv) { |
| QMatrix4x4 colorSpaceMatrix; |
| |
| switch (m_colorSpace) { |
| case QVideoSurfaceFormat::YCbCr_JPEG: |
| colorSpaceMatrix = 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: |
| colorSpaceMatrix = 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: |
| colorSpaceMatrix = 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); |
| } |
| |
| m_colorMatrix = m_colorMatrix * colorSpaceMatrix; |
| } |
| } |
| |
| void QVideoSurfaceGLPainter::initRgbTextureInfo( |
| GLenum internalFormat, GLuint format, GLenum type, const QSize &size) |
| { |
| m_yuv = false; |
| m_textureInternalFormat = internalFormat; |
| m_textureFormat = format; |
| m_textureType = type; |
| m_textureCount = 1; // Note: ensure this is always <= Max_Textures |
| m_textureWidths[0] = size.width(); |
| m_textureHeights[0] = size.height(); |
| m_textureOffsets[0] = 0; |
| } |
| |
| void QVideoSurfaceGLPainter::initYuv420PTextureInfo(const QSize &size) |
| { |
| int bytesPerLine = (size.width() + 3) & ~3; |
| int bytesPerLine2 = (size.width() / 2 + 3) & ~3; |
| |
| m_yuv = true; |
| m_textureInternalFormat = GL_LUMINANCE; |
| m_textureFormat = GL_LUMINANCE; |
| m_textureType = GL_UNSIGNED_BYTE; |
| m_textureCount = 3; // Note: ensure this is always <= Max_Textures |
| m_textureWidths[0] = bytesPerLine; |
| m_textureHeights[0] = size.height(); |
| m_textureOffsets[0] = 0; |
| m_textureWidths[1] = bytesPerLine2; |
| m_textureHeights[1] = size.height() / 2; |
| m_textureOffsets[1] = bytesPerLine * size.height(); |
| m_textureWidths[2] = bytesPerLine2; |
| m_textureHeights[2] = size.height() / 2; |
| m_textureOffsets[2] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; |
| } |
| |
| void QVideoSurfaceGLPainter::initYv12TextureInfo(const QSize &size) |
| { |
| int bytesPerLine = (size.width() + 3) & ~3; |
| int bytesPerLine2 = (size.width() / 2 + 3) & ~3; |
| |
| m_yuv = true; |
| m_textureInternalFormat = GL_LUMINANCE; |
| m_textureFormat = GL_LUMINANCE; |
| m_textureType = GL_UNSIGNED_BYTE; |
| m_textureCount = 3; // Note: ensure this is always <= Max_Textures |
| m_textureWidths[0] = bytesPerLine; |
| m_textureHeights[0] = size.height(); |
| m_textureOffsets[0] = 0; |
| m_textureWidths[1] = bytesPerLine2; |
| m_textureHeights[1] = size.height() / 2; |
| m_textureOffsets[1] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; |
| m_textureWidths[2] = bytesPerLine2; |
| m_textureHeights[2] = size.height() / 2; |
| m_textureOffsets[2] = bytesPerLine * size.height(); |
| } |
| |
| #if !defined(QT_OPENGL_ES) && !defined(QT_OPENGL_DYNAMIC) |
| |
| # ifndef GL_FRAGMENT_PROGRAM_ARB |
| # define GL_FRAGMENT_PROGRAM_ARB 0x8804 |
| # define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 |
| # endif |
| |
| // Paints an RGB32 frame |
| static const char *qt_arbfp_xrgbShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP xrgb;\n" |
| "TEX xrgb.xyz, fragment.texcoord[0], texture[0], 2D;\n" |
| "MOV xrgb.w, matrix[3].w;\n" |
| "DP4 result.color.x, xrgb.zyxw, matrix[0];\n" |
| "DP4 result.color.y, xrgb.zyxw, matrix[1];\n" |
| "DP4 result.color.z, xrgb.zyxw, matrix[2];\n" |
| "END"; |
| |
| // Paints an ARGB frame. |
| static const char *qt_arbfp_argbShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP argb;\n" |
| "TEX argb, fragment.texcoord[0], texture[0], 2D;\n" |
| "MOV argb.w, matrix[3].w;\n" |
| "DP4 result.color.x, argb.zyxw, matrix[0];\n" |
| "DP4 result.color.y, argb.zyxw, matrix[1];\n" |
| "DP4 result.color.z, argb.zyxw, matrix[2];\n" |
| "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" |
| "END"; |
| |
| // Paints an RGB(A) frame. |
| static const char *qt_arbfp_rgbShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP rgb;\n" |
| "TEX rgb, fragment.texcoord[0], texture[0], 2D;\n" |
| "MOV rgb.w, matrix[3].w;\n" |
| "DP4 result.color.x, rgb, matrix[0];\n" |
| "DP4 result.color.y, rgb, matrix[1];\n" |
| "DP4 result.color.z, rgb, matrix[2];\n" |
| "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" |
| "END"; |
| |
| // Paints a YUV420P or YV12 frame. |
| static const char *qt_arbfp_yuvPlanarShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP yuv;\n" |
| "TEX yuv.x, fragment.texcoord[0], texture[0], 2D;\n" |
| "TEX yuv.y, fragment.texcoord[0], texture[1], 2D;\n" |
| "TEX yuv.z, fragment.texcoord[0], texture[2], 2D;\n" |
| "MOV yuv.w, matrix[3].w;\n" |
| "DP4 result.color.x, yuv, matrix[0];\n" |
| "DP4 result.color.y, yuv, matrix[1];\n" |
| "DP4 result.color.z, yuv, matrix[2];\n" |
| "END"; |
| |
| // Paints a YUV444 frame. |
| static const char *qt_arbfp_xyuvShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP ayuv;\n" |
| "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" |
| "MOV ayuv.x, matrix[3].w;\n" |
| "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" |
| "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" |
| "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" |
| "END"; |
| |
| // Paints a AYUV444 frame. |
| static const char *qt_arbfp_ayuvShaderProgram = |
| "!!ARBfp1.0\n" |
| "PARAM matrix[4] = { program.local[0..2]," |
| "{ 0.0, 0.0, 0.0, 1.0 } };\n" |
| "TEMP ayuv;\n" |
| "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" |
| "MOV ayuv.x, matrix[3].w;\n" |
| "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" |
| "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" |
| "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" |
| "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" |
| "END"; |
| |
| class QVideoSurfaceArbFpPainter : public QVideoSurfaceGLPainter |
| { |
| public: |
| QVideoSurfaceArbFpPainter(QOpenGLContext *context); |
| |
| QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format) override; |
| void stop() override; |
| |
| QAbstractVideoSurface::Error paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) override; |
| |
| private: |
| typedef void (APIENTRY *_glProgramStringARB) (GLenum, GLenum, GLsizei, const GLvoid *); |
| typedef void (APIENTRY *_glBindProgramARB) (GLenum, GLuint); |
| typedef void (APIENTRY *_glDeleteProgramsARB) (GLsizei, const GLuint *); |
| typedef void (APIENTRY *_glGenProgramsARB) (GLsizei, GLuint *); |
| typedef void (APIENTRY *_glProgramLocalParameter4fARB) ( |
| GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); |
| typedef void (APIENTRY *_glActiveTexture) (GLenum); |
| |
| _glProgramStringARB glProgramStringARB; |
| _glBindProgramARB glBindProgramARB; |
| _glDeleteProgramsARB glDeleteProgramsARB; |
| _glGenProgramsARB glGenProgramsARB; |
| _glProgramLocalParameter4fARB glProgramLocalParameter4fARB; |
| |
| GLuint m_programId; |
| QSize m_frameSize; |
| }; |
| |
| QVideoSurfaceArbFpPainter::QVideoSurfaceArbFpPainter(QOpenGLContext *context) |
| : QVideoSurfaceGLPainter(context) |
| , m_programId(0) |
| { |
| glProgramStringARB = (_glProgramStringARB) m_context->getProcAddress( |
| QByteArray("glProgramStringARB")); |
| glBindProgramARB = (_glBindProgramARB) m_context->getProcAddress( |
| QByteArray("glBindProgramARB")); |
| glDeleteProgramsARB = (_glDeleteProgramsARB) m_context->getProcAddress( |
| QByteArray("glDeleteProgramsARB")); |
| glGenProgramsARB = (_glGenProgramsARB) m_context->getProcAddress( |
| QByteArray("glGenProgramsARB")); |
| glProgramLocalParameter4fARB = (_glProgramLocalParameter4fARB) m_context->getProcAddress( |
| QByteArray("glProgramLocalParameter4fARB")); |
| |
| m_imagePixelFormats |
| << QVideoFrame::Format_RGB32 |
| << QVideoFrame::Format_BGR32 |
| << QVideoFrame::Format_ARGB32 |
| << QVideoFrame::Format_RGB24 |
| << QVideoFrame::Format_BGR24 |
| << QVideoFrame::Format_RGB565 |
| << QVideoFrame::Format_AYUV444 |
| << QVideoFrame::Format_YUV444 |
| << QVideoFrame::Format_YV12 |
| << QVideoFrame::Format_YUV420P; |
| m_glPixelFormats |
| << QVideoFrame::Format_RGB32 |
| << QVideoFrame::Format_ARGB32 |
| << QVideoFrame::Format_BGR32 |
| << QVideoFrame::Format_BGRA32; |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceArbFpPainter::start(const QVideoSurfaceFormat &format) |
| { |
| Q_ASSERT(m_textureCount == 0); |
| |
| QAbstractVideoSurface::Error error = QAbstractVideoSurface::NoError; |
| |
| const char *program = 0; |
| |
| if (format.handleType() == QAbstractVideoBuffer::NoHandle) { |
| switch (format.pixelFormat()) { |
| case QVideoFrame::Format_RGB32: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_xrgbShaderProgram; |
| break; |
| case QVideoFrame::Format_BGR32: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_rgbShaderProgram; |
| break; |
| case QVideoFrame::Format_ARGB32: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_argbShaderProgram; |
| break; |
| case QVideoFrame::Format_RGB24: |
| initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_rgbShaderProgram; |
| break; |
| case QVideoFrame::Format_BGR24: |
| initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_xrgbShaderProgram; |
| break; |
| case QVideoFrame::Format_RGB565: |
| initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); |
| program = qt_arbfp_rgbShaderProgram; |
| break; |
| case QVideoFrame::Format_YUV444: |
| initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_xyuvShaderProgram; |
| m_yuv = true; |
| break; |
| case QVideoFrame::Format_AYUV444: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| program = qt_arbfp_ayuvShaderProgram; |
| m_yuv = true; |
| break; |
| case QVideoFrame::Format_YV12: |
| initYv12TextureInfo(format.frameSize()); |
| program = qt_arbfp_yuvPlanarShaderProgram; |
| break; |
| case QVideoFrame::Format_YUV420P: |
| initYuv420PTextureInfo(format.frameSize()); |
| program = qt_arbfp_yuvPlanarShaderProgram; |
| break; |
| default: |
| break; |
| } |
| } else if (format.handleType() == QAbstractVideoBuffer::GLTextureHandle) { |
| switch (format.pixelFormat()) { |
| case QVideoFrame::Format_RGB32: |
| case QVideoFrame::Format_ARGB32: |
| case QVideoFrame::Format_BGR32: |
| case QVideoFrame::Format_BGRA32: |
| m_yuv = false; |
| m_textureCount = 1; |
| if (needsSwizzling(format)) |
| program = qt_arbfp_xrgbShaderProgram; |
| else |
| program = qt_arbfp_rgbShaderProgram; |
| break; |
| default: |
| break; |
| } |
| } else if (format.handleType() == QAbstractVideoBuffer::QPixmapHandle) { |
| m_handleType = QAbstractVideoBuffer::QPixmapHandle; |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| if (!program) { |
| error = QAbstractVideoSurface::UnsupportedFormatError; |
| } else { |
| while (glGetError() != GL_NO_ERROR) { } // clear previous unrelated errors |
| |
| glGenProgramsARB(1, &m_programId); |
| |
| GLenum glError = glGetError(); |
| if (glError != GL_NO_ERROR) { |
| qWarning("QPainterVideoSurface: ARBfb Shader allocation error %x", int(glError)); |
| m_textureCount = 0; |
| m_programId = 0; |
| |
| error = QAbstractVideoSurface::ResourceError; |
| } else { |
| glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); |
| glProgramStringARB( |
| GL_FRAGMENT_PROGRAM_ARB, |
| GL_PROGRAM_FORMAT_ASCII_ARB, |
| qstrlen(program), |
| reinterpret_cast<const GLvoid *>(program)); |
| |
| if ((glError = glGetError()) != GL_NO_ERROR) { |
| const GLubyte* errorString = glGetString(GL_PROGRAM_ERROR_STRING_ARB); |
| |
| qWarning("QPainterVideoSurface: ARBfp Shader compile error %x, %s", |
| int(glError), |
| reinterpret_cast<const char *>(errorString)); |
| glDeleteProgramsARB(1, &m_programId); |
| |
| m_textureCount = 0; |
| m_programId = 0; |
| |
| error = QAbstractVideoSurface::ResourceError; |
| } else { |
| m_handleType = format.handleType(); |
| m_scanLineDirection = format.scanLineDirection(); |
| m_mirrored = format.property("mirrored").toBool(); |
| m_frameSize = format.frameSize(); |
| m_colorSpace = format.yCbCrColorSpace(); |
| |
| if (m_handleType == QAbstractVideoBuffer::NoHandle) |
| glGenTextures(m_textureCount, m_textureIds); |
| } |
| } |
| } |
| |
| return error; |
| } |
| |
| void QVideoSurfaceArbFpPainter::stop() |
| { |
| if (m_context) { |
| if (m_handleType != QAbstractVideoBuffer::GLTextureHandle) |
| glDeleteTextures(m_textureCount, m_textureIds); |
| glDeleteProgramsARB(1, &m_programId); |
| } |
| |
| m_textureCount = 0; |
| m_programId = 0; |
| m_handleType = QAbstractVideoBuffer::NoHandle; |
| |
| QVideoSurfaceGLPainter::stop(); |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceArbFpPainter::paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) |
| { |
| if (!m_frame.isValid()) { |
| painter->fillRect(target, Qt::black); |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| const QAbstractVideoBuffer::HandleType h = m_frame.handleType(); |
| if (h == QAbstractVideoBuffer::NoHandle || h == QAbstractVideoBuffer::GLTextureHandle) { |
| bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); |
| bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); |
| |
| painter->beginNativePainting(); |
| |
| if (stencilTestEnabled) |
| glEnable(GL_STENCIL_TEST); |
| if (scissorTestEnabled) |
| glEnable(GL_SCISSOR_TEST); |
| |
| const float txLeft = m_mirrored ? source.right() / m_frameSize.width() |
| : source.left() / m_frameSize.width(); |
| const float txRight = m_mirrored ? source.left() / m_frameSize.width() |
| : source.right() / m_frameSize.width(); |
| const float txTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom |
| ? source.top() / m_frameSize.height() |
| : source.bottom() / m_frameSize.height(); |
| const float txBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom |
| ? source.bottom() / m_frameSize.height() |
| : source.top() / m_frameSize.height(); |
| |
| const float tx_array[] = |
| { |
| txLeft , txBottom, |
| txRight, txBottom, |
| txLeft , txTop, |
| txRight, txTop |
| }; |
| |
| const GLfloat v_array[] = |
| { |
| GLfloat(target.left()) , GLfloat(target.bottom() + 1), |
| GLfloat(target.right() + 1), GLfloat(target.bottom() + 1), |
| GLfloat(target.left()) , GLfloat(target.top()), |
| GLfloat(target.right() + 1), GLfloat(target.top()) |
| }; |
| |
| glEnable(GL_FRAGMENT_PROGRAM_ARB); |
| glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); |
| |
| glProgramLocalParameter4fARB( |
| GL_FRAGMENT_PROGRAM_ARB, |
| 0, |
| m_colorMatrix(0, 0), |
| m_colorMatrix(0, 1), |
| m_colorMatrix(0, 2), |
| m_colorMatrix(0, 3)); |
| glProgramLocalParameter4fARB( |
| GL_FRAGMENT_PROGRAM_ARB, |
| 1, |
| m_colorMatrix(1, 0), |
| m_colorMatrix(1, 1), |
| m_colorMatrix(1, 2), |
| m_colorMatrix(1, 3)); |
| glProgramLocalParameter4fARB( |
| GL_FRAGMENT_PROGRAM_ARB, |
| 2, |
| m_colorMatrix(2, 0), |
| m_colorMatrix(2, 1), |
| m_colorMatrix(2, 2), |
| m_colorMatrix(2, 3)); |
| |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); |
| |
| if (m_textureCount == 3) { |
| glActiveTexture(GL_TEXTURE1); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); |
| glActiveTexture(GL_TEXTURE2); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); |
| glActiveTexture(GL_TEXTURE0); |
| } |
| |
| glVertexPointer(2, GL_FLOAT, 0, v_array); |
| glTexCoordPointer(2, GL_FLOAT, 0, tx_array); |
| |
| glEnableClientState(GL_VERTEX_ARRAY); |
| glEnableClientState(GL_TEXTURE_COORD_ARRAY); |
| |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| |
| glDisableClientState(GL_TEXTURE_COORD_ARRAY); |
| glDisableClientState(GL_VERTEX_ARRAY); |
| glDisable(GL_FRAGMENT_PROGRAM_ARB); |
| |
| painter->endNativePainting(); |
| |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| return QVideoSurfaceGLPainter::paint(target, painter, source); |
| } |
| |
| #endif // !QT_OPENGL_ES && !QT_OPENGL_DYNAMIC |
| |
| static const char *qt_glsl_vertexShaderProgram = |
| "attribute highp vec4 vertexCoordArray;\n" |
| "attribute highp vec2 textureCoordArray;\n" |
| "uniform highp mat4 positionMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " gl_Position = positionMatrix * vertexCoordArray;\n" |
| " textureCoord = textureCoordArray;\n" |
| "}\n"; |
| |
| // Paints an RGB32 frame |
| static const char *qt_glsl_xrgbShaderProgram = |
| "uniform sampler2D texRgb;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" |
| " gl_FragColor = colorMatrix * color;\n" |
| "}\n"; |
| |
| // Paints an ARGB frame. |
| static const char *qt_glsl_argbShaderProgram = |
| "uniform sampler2D texRgb;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" |
| " color = colorMatrix * color;\n" |
| " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" |
| "}\n"; |
| |
| // Paints an RGB(A) frame. |
| static const char *qt_glsl_rgbShaderProgram = |
| "uniform sampler2D texRgb;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).rgb, 1.0);\n" |
| " color = colorMatrix * color;\n" |
| " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" |
| "}\n"; |
| |
| // Paints a YUV420P or YV12 frame. |
| static const char *qt_glsl_yuvPlanarShaderProgram = |
| "uniform sampler2D texY;\n" |
| "uniform sampler2D texU;\n" |
| "uniform sampler2D texV;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(\n" |
| " texture2D(texY, textureCoord.st).r,\n" |
| " texture2D(texU, textureCoord.st).r,\n" |
| " texture2D(texV, textureCoord.st).r,\n" |
| " 1.0);\n" |
| " gl_FragColor = colorMatrix * color;\n" |
| "}\n"; |
| |
| // Paints a YUV444 frame. |
| static const char *qt_glsl_xyuvShaderProgram = |
| "uniform sampler2D texRgb;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" |
| " gl_FragColor = colorMatrix * color;\n" |
| "}\n"; |
| |
| // Paints a AYUV444 frame. |
| static const char *qt_glsl_ayuvShaderProgram = |
| "uniform sampler2D texRgb;\n" |
| "uniform mediump mat4 colorMatrix;\n" |
| "varying highp vec2 textureCoord;\n" |
| "void main(void)\n" |
| "{\n" |
| " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" |
| " color = colorMatrix * color;\n" |
| " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).r);\n" |
| "}\n"; |
| |
| class QVideoSurfaceGlslPainter : public QVideoSurfaceGLPainter |
| { |
| public: |
| QVideoSurfaceGlslPainter(QOpenGLContext *context); |
| |
| QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format) override; |
| void stop() override; |
| |
| QAbstractVideoSurface::Error paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) override; |
| |
| private: |
| QOpenGLShaderProgram m_program; |
| QSize m_frameSize; |
| }; |
| |
| QVideoSurfaceGlslPainter::QVideoSurfaceGlslPainter(QOpenGLContext *context) |
| : QVideoSurfaceGLPainter(context) |
| , m_program(context) |
| { |
| m_imagePixelFormats |
| << QVideoFrame::Format_RGB32 |
| << QVideoFrame::Format_BGR32 |
| << QVideoFrame::Format_ARGB32; |
| if (!context->isOpenGLES()) { |
| m_imagePixelFormats |
| << QVideoFrame::Format_RGB24 |
| << QVideoFrame::Format_BGR24; |
| } |
| m_imagePixelFormats |
| << QVideoFrame::Format_RGB565 |
| << QVideoFrame::Format_YUV444 |
| << QVideoFrame::Format_AYUV444 |
| << QVideoFrame::Format_YV12 |
| << QVideoFrame::Format_YUV420P; |
| m_glPixelFormats |
| << QVideoFrame::Format_RGB32 |
| << QVideoFrame::Format_ARGB32 |
| << QVideoFrame::Format_BGR32 |
| << QVideoFrame::Format_BGRA32; |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGlslPainter::start(const QVideoSurfaceFormat &format) |
| { |
| Q_ASSERT(m_textureCount == 0); |
| |
| QAbstractVideoSurface::Error error = QAbstractVideoSurface::NoError; |
| |
| const char *fragmentProgram = 0; |
| |
| if (format.handleType() == QAbstractVideoBuffer::NoHandle) { |
| switch (format.pixelFormat()) { |
| case QVideoFrame::Format_RGB32: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_xrgbShaderProgram; |
| break; |
| case QVideoFrame::Format_BGR32: |
| initRgbTextureInfo(GL_RGB, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_rgbShaderProgram; |
| break; |
| case QVideoFrame::Format_ARGB32: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_argbShaderProgram; |
| break; |
| case QVideoFrame::Format_RGB24: |
| if (!m_context->isOpenGLES()) { |
| initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_rgbShaderProgram; |
| } |
| break; |
| case QVideoFrame::Format_BGR24: |
| if (!m_context->isOpenGLES()) { |
| initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_argbShaderProgram; |
| } |
| break; |
| case QVideoFrame::Format_RGB565: |
| initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); |
| fragmentProgram = qt_glsl_rgbShaderProgram; |
| break; |
| case QVideoFrame::Format_YUV444: |
| initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_xyuvShaderProgram; |
| m_yuv = true; |
| break; |
| case QVideoFrame::Format_AYUV444: |
| initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); |
| fragmentProgram = qt_glsl_ayuvShaderProgram; |
| m_yuv = true; |
| break; |
| case QVideoFrame::Format_YV12: |
| initYv12TextureInfo(format.frameSize()); |
| fragmentProgram = qt_glsl_yuvPlanarShaderProgram; |
| break; |
| case QVideoFrame::Format_YUV420P: |
| initYuv420PTextureInfo(format.frameSize()); |
| fragmentProgram = qt_glsl_yuvPlanarShaderProgram; |
| break; |
| default: |
| break; |
| } |
| } else if (format.handleType() == QAbstractVideoBuffer::GLTextureHandle) { |
| switch (format.pixelFormat()) { |
| case QVideoFrame::Format_RGB32: |
| case QVideoFrame::Format_ARGB32: |
| case QVideoFrame::Format_BGR32: |
| case QVideoFrame::Format_BGRA32: |
| m_yuv = false; |
| m_textureCount = 1; |
| if (needsSwizzling(format)) |
| fragmentProgram = qt_glsl_xrgbShaderProgram; |
| else |
| fragmentProgram = qt_glsl_rgbShaderProgram; |
| break; |
| default: |
| break; |
| } |
| } else if (format.handleType() == QAbstractVideoBuffer::QPixmapHandle) { |
| m_handleType = QAbstractVideoBuffer::QPixmapHandle; |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| if (!fragmentProgram) { |
| error = QAbstractVideoSurface::UnsupportedFormatError; |
| } else if (!m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, qt_glsl_vertexShaderProgram)) { |
| qWarning("QPainterVideoSurface: Vertex shader compile error %s", |
| qPrintable(m_program.log())); |
| error = QAbstractVideoSurface::ResourceError; |
| } else if (!m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentProgram)) { |
| qWarning("QPainterVideoSurface: Shader compile error %s", qPrintable(m_program.log())); |
| error = QAbstractVideoSurface::ResourceError; |
| m_program.removeAllShaders(); |
| } else if(!m_program.link()) { |
| qWarning("QPainterVideoSurface: Shader link error %s", qPrintable(m_program.log())); |
| m_program.removeAllShaders(); |
| error = QAbstractVideoSurface::ResourceError; |
| } else { |
| m_handleType = format.handleType(); |
| m_scanLineDirection = format.scanLineDirection(); |
| m_mirrored = format.property("mirrored").toBool(); |
| m_frameSize = format.frameSize(); |
| m_colorSpace = format.yCbCrColorSpace(); |
| |
| if (m_handleType == QAbstractVideoBuffer::NoHandle) |
| glGenTextures(m_textureCount, m_textureIds); |
| } |
| |
| return error; |
| } |
| |
| void QVideoSurfaceGlslPainter::stop() |
| { |
| if (m_context) { |
| if (m_handleType != QAbstractVideoBuffer::GLTextureHandle) |
| glDeleteTextures(m_textureCount, m_textureIds); |
| } |
| |
| m_program.removeAllShaders(); |
| |
| m_textureCount = 0; |
| m_handleType = QAbstractVideoBuffer::NoHandle; |
| |
| QVideoSurfaceGLPainter::stop(); |
| } |
| |
| QAbstractVideoSurface::Error QVideoSurfaceGlslPainter::paint( |
| const QRectF &target, QPainter *painter, const QRectF &source) |
| { |
| if (!m_frame.isValid()) { |
| painter->fillRect(target, Qt::black); |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| const QAbstractVideoBuffer::HandleType h = m_frame.handleType(); |
| if (h == QAbstractVideoBuffer::NoHandle || h == QAbstractVideoBuffer::GLTextureHandle) { |
| bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); |
| bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); |
| |
| painter->beginNativePainting(); |
| |
| if (stencilTestEnabled) |
| glEnable(GL_STENCIL_TEST); |
| if (scissorTestEnabled) |
| glEnable(GL_SCISSOR_TEST); |
| |
| const int width = painter->viewport().width(); |
| const int height = painter->viewport().height(); |
| |
| const QTransform transform = painter->deviceTransform(); |
| |
| const GLfloat wfactor = 2.0 / width; |
| const GLfloat hfactor = -2.0 / height; |
| |
| const GLfloat positionMatrix[4][4] = |
| { |
| { |
| /*(0,0)*/ GLfloat(wfactor * transform.m11() - transform.m13()), |
| /*(0,1)*/ GLfloat(hfactor * transform.m12() + transform.m13()), |
| /*(0,2)*/ 0.0, |
| /*(0,3)*/ GLfloat(transform.m13()) |
| }, { |
| /*(1,0)*/ GLfloat(wfactor * transform.m21() - transform.m23()), |
| /*(1,1)*/ GLfloat(hfactor * transform.m22() + transform.m23()), |
| /*(1,2)*/ 0.0, |
| /*(1,3)*/ GLfloat(transform.m23()) |
| }, { |
| /*(2,0)*/ 0.0, |
| /*(2,1)*/ 0.0, |
| /*(2,2)*/ -1.0, |
| /*(2,3)*/ 0.0 |
| }, { |
| /*(3,0)*/ GLfloat(wfactor * transform.dx() - transform.m33()), |
| /*(3,1)*/ GLfloat(hfactor * transform.dy() + transform.m33()), |
| /*(3,2)*/ 0.0, |
| /*(3,3)*/ GLfloat(transform.m33()) |
| } |
| }; |
| |
| const GLfloat vertexCoordArray[] = |
| { |
| GLfloat(target.left()) , GLfloat(target.bottom() + 1), |
| GLfloat(target.right() + 1), GLfloat(target.bottom() + 1), |
| GLfloat(target.left()) , GLfloat(target.top()), |
| GLfloat(target.right() + 1), GLfloat(target.top()) |
| }; |
| |
| const GLfloat txLeft = m_mirrored ? source.right() / m_frameSize.width() |
| : source.left() / m_frameSize.width(); |
| const GLfloat txRight = m_mirrored ? source.left() / m_frameSize.width() |
| : source.right() / m_frameSize.width(); |
| const GLfloat txTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom |
| ? source.top() / m_frameSize.height() |
| : source.bottom() / m_frameSize.height(); |
| const GLfloat txBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom |
| ? source.bottom() / m_frameSize.height() |
| : source.top() / m_frameSize.height(); |
| |
| const GLfloat textureCoordArray[] = |
| { |
| txLeft , txBottom, |
| txRight, txBottom, |
| txLeft , txTop, |
| txRight, txTop |
| }; |
| |
| m_program.bind(); |
| |
| m_program.enableAttributeArray("vertexCoordArray"); |
| m_program.enableAttributeArray("textureCoordArray"); |
| m_program.setAttributeArray("vertexCoordArray", vertexCoordArray, 2); |
| m_program.setAttributeArray("textureCoordArray", textureCoordArray, 2); |
| m_program.setUniformValue("positionMatrix", positionMatrix); |
| |
| if (m_textureCount == 3) { |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); |
| glActiveTexture(GL_TEXTURE1); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); |
| glActiveTexture(GL_TEXTURE2); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); |
| glActiveTexture(GL_TEXTURE0); |
| |
| m_program.setUniformValue("texY", 0); |
| m_program.setUniformValue("texU", 1); |
| m_program.setUniformValue("texV", 2); |
| } else { |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); |
| |
| m_program.setUniformValue("texRgb", 0); |
| } |
| m_program.setUniformValue("colorMatrix", m_colorMatrix); |
| |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| |
| m_program.release(); |
| |
| painter->endNativePainting(); |
| |
| return QAbstractVideoSurface::NoError; |
| } |
| |
| return QVideoSurfaceGLPainter::paint(target, painter, source); |
| } |
| |
| #endif |
| |
| /*! |
| \class QPainterVideoSurface |
| \internal |
| */ |
| |
| /*! |
| */ |
| QPainterVideoSurface::QPainterVideoSurface(QObject *parent) |
| : QAbstractVideoSurface(parent) |
| , m_painter(0) |
| #if QT_CONFIG(opengl) |
| , m_glContext(0) |
| , m_shaderTypes(NoShaders) |
| , m_shaderType(NoShaders) |
| #endif |
| , m_brightness(0) |
| , m_contrast(0) |
| , m_hue(0) |
| , m_saturation(0) |
| , m_pixelFormat(QVideoFrame::Format_Invalid) |
| , m_colorsDirty(true) |
| , m_ready(false) |
| { |
| } |
| |
| /*! |
| */ |
| QPainterVideoSurface::~QPainterVideoSurface() |
| { |
| if (isActive()) |
| m_painter->stop(); |
| |
| delete m_painter; |
| } |
| |
| /*! |
| */ |
| QList<QVideoFrame::PixelFormat> QPainterVideoSurface::supportedPixelFormats( |
| QAbstractVideoBuffer::HandleType handleType) const |
| { |
| if (!m_painter) |
| const_cast<QPainterVideoSurface *>(this)->createPainter(); |
| |
| return m_painter->supportedPixelFormats(handleType); |
| } |
| |
| /*! |
| */ |
| bool QPainterVideoSurface::isFormatSupported(const QVideoSurfaceFormat &format) const |
| { |
| if (!m_painter) |
| const_cast<QPainterVideoSurface *>(this)->createPainter(); |
| |
| return m_painter->isFormatSupported(format); |
| } |
| |
| /*! |
| */ |
| bool QPainterVideoSurface::start(const QVideoSurfaceFormat &format) |
| { |
| if (isActive()) |
| m_painter->stop(); |
| |
| if (!m_painter) |
| createPainter(); |
| |
| if (format.frameSize().isEmpty()) { |
| setError(UnsupportedFormatError); |
| } else { |
| QAbstractVideoSurface::Error error = m_painter->start(format); |
| |
| if (error != QAbstractVideoSurface::NoError) { |
| setError(error); |
| } else { |
| m_pixelFormat = format.pixelFormat(); |
| m_frameSize = format.frameSize(); |
| m_sourceRect = format.viewport(); |
| m_colorsDirty = true; |
| m_ready = true; |
| |
| return QAbstractVideoSurface::start(format); |
| } |
| } |
| |
| QAbstractVideoSurface::stop(); |
| |
| return false; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::stop() |
| { |
| if (isActive()) { |
| m_painter->stop(); |
| m_ready = false; |
| |
| QAbstractVideoSurface::stop(); |
| } |
| } |
| |
| /*! |
| */ |
| bool QPainterVideoSurface::present(const QVideoFrame &frame) |
| { |
| if (!m_ready) { |
| if (!isActive()) { |
| setError(StoppedError); |
| return false; |
| } |
| } else if (frame.isValid() |
| && (frame.pixelFormat() != m_pixelFormat || frame.size() != m_frameSize)) { |
| setError(IncorrectFormatError); |
| |
| stop(); |
| return false; |
| } else { |
| QAbstractVideoSurface::Error error = m_painter->setCurrentFrame(frame); |
| if (error != QAbstractVideoSurface::NoError) { |
| setError(error); |
| stop(); |
| return false; |
| } |
| |
| m_ready = false; |
| emit frameChanged(); |
| } |
| return true; |
| } |
| |
| /*! |
| */ |
| int QPainterVideoSurface::brightness() const |
| { |
| return m_brightness; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setBrightness(int brightness) |
| { |
| m_brightness = brightness; |
| |
| m_colorsDirty = true; |
| } |
| |
| /*! |
| */ |
| int QPainterVideoSurface::contrast() const |
| { |
| return m_contrast; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setContrast(int contrast) |
| { |
| m_contrast = contrast; |
| |
| m_colorsDirty = true; |
| } |
| |
| /*! |
| */ |
| int QPainterVideoSurface::hue() const |
| { |
| return m_hue; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setHue(int hue) |
| { |
| m_hue = hue; |
| |
| m_colorsDirty = true; |
| } |
| |
| /*! |
| */ |
| int QPainterVideoSurface::saturation() const |
| { |
| return m_saturation; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setSaturation(int saturation) |
| { |
| m_saturation = saturation; |
| |
| m_colorsDirty = true; |
| } |
| |
| /*! |
| */ |
| bool QPainterVideoSurface::isReady() const |
| { |
| return m_ready; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setReady(bool ready) |
| { |
| m_ready = ready; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::paint(QPainter *painter, const QRectF &target, const QRectF &source) |
| { |
| if (!isActive()) { |
| painter->fillRect(target, QBrush(Qt::black)); |
| } else { |
| if (m_colorsDirty) { |
| m_painter->updateColors(m_brightness, m_contrast, m_hue, m_saturation); |
| m_colorsDirty = false; |
| } |
| |
| const QRectF sourceRect( |
| m_sourceRect.x() + m_sourceRect.width() * source.x(), |
| m_sourceRect.y() + m_sourceRect.height() * source.y(), |
| m_sourceRect.width() * source.width(), |
| m_sourceRect.height() * source.height()); |
| |
| QAbstractVideoSurface::Error error = m_painter->paint(target, painter, sourceRect); |
| |
| if (error != QAbstractVideoSurface::NoError) { |
| setError(error); |
| |
| stop(); |
| } |
| } |
| } |
| |
| /*! |
| \fn QPainterVideoSurface::frameChanged() |
| */ |
| |
| #if QT_CONFIG(opengl) |
| |
| /*! |
| */ |
| const QOpenGLContext *QPainterVideoSurface::glContext() const |
| { |
| return m_glContext; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::updateGLContext() |
| { |
| auto oldContext = m_glContext; |
| m_glContext = QOpenGLContext::currentContext(); |
| if (oldContext == m_glContext) |
| return; |
| |
| m_shaderTypes = NoShaders; |
| |
| if (m_glContext) { |
| //Set a dynamic property to access the OpenGL context |
| this->setProperty("GLContext", QVariant::fromValue<QObject *>(m_glContext)); |
| |
| const QByteArray extensions(reinterpret_cast<const char *>( |
| m_glContext->functions()->glGetString(GL_EXTENSIONS))); |
| #if !defined(QT_OPENGL_ES) && !defined(QT_OPENGL_DYNAMIC) |
| if (extensions.contains("ARB_fragment_program")) |
| m_shaderTypes |= FragmentProgramShader; |
| #endif |
| if (QOpenGLShaderProgram::hasOpenGLShaderPrograms(m_glContext) |
| #if !defined(QT_OPENGL_ES_2) && !defined(QT_OPENGL_DYNAMIC) |
| && extensions.contains("ARB_shader_objects") |
| #endif |
| ) |
| m_shaderTypes |= GlslShader; |
| } |
| |
| ShaderType type = (m_shaderType & m_shaderTypes) |
| ? m_shaderType |
| : NoShaders; |
| |
| if (type != m_shaderType || type != NoShaders) { |
| m_shaderType = type; |
| |
| if (isActive()) { |
| m_painter->stop(); |
| delete m_painter; |
| m_painter = 0; |
| m_ready = false; |
| |
| setError(ResourceError); |
| QAbstractVideoSurface::stop(); |
| } |
| emit supportedFormatsChanged(); |
| } |
| } |
| |
| /*! |
| \enum QPainterVideoSurface::ShaderType |
| |
| \value NoShaders |
| \value FragmentProgramShader |
| \value HlslShader |
| */ |
| |
| /*! |
| \typedef QPainterVideoSurface::ShaderTypes |
| */ |
| |
| /*! |
| */ |
| QPainterVideoSurface::ShaderTypes QPainterVideoSurface::supportedShaderTypes() const |
| { |
| return m_shaderTypes; |
| } |
| |
| /*! |
| */ |
| QPainterVideoSurface::ShaderType QPainterVideoSurface::shaderType() const |
| { |
| return m_shaderType; |
| } |
| |
| /*! |
| */ |
| void QPainterVideoSurface::setShaderType(ShaderType type) |
| { |
| if (!(type & m_shaderTypes)) |
| type = NoShaders; |
| |
| if (type != m_shaderType) { |
| m_shaderType = type; |
| |
| if (isActive()) { |
| m_painter->stop(); |
| delete m_painter; |
| m_painter = 0; |
| m_ready = false; |
| |
| setError(ResourceError); |
| QAbstractVideoSurface::stop(); |
| } else { |
| delete m_painter; |
| m_painter = 0; |
| } |
| emit supportedFormatsChanged(); |
| } |
| } |
| |
| #endif |
| |
| void QPainterVideoSurface::viewportDestroyed() |
| { |
| if (m_painter) { |
| m_painter->viewportDestroyed(); |
| |
| setError(ResourceError); |
| stop(); |
| delete m_painter; |
| m_painter = 0; |
| } |
| } |
| |
| void QPainterVideoSurface::createPainter() |
| { |
| Q_ASSERT(!m_painter); |
| |
| #if QT_CONFIG(opengl) |
| switch (m_shaderType) { |
| #if !defined(QT_OPENGL_ES) && !defined(QT_OPENGL_DYNAMIC) |
| case FragmentProgramShader: |
| Q_ASSERT(m_glContext); |
| m_painter = new QVideoSurfaceArbFpPainter(m_glContext); |
| break; |
| #endif // !QT_OPENGL_ES && !QT_OPENGL_DYNAMIC |
| case GlslShader: |
| Q_ASSERT(m_glContext); |
| m_painter = new QVideoSurfaceGlslPainter(m_glContext); |
| break; |
| default: |
| m_painter = new QVideoSurfaceGenericPainter; |
| break; |
| } |
| #else |
| m_painter = new QVideoSurfaceGenericPainter; |
| #endif |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qpaintervideosurface_p.cpp" |