From patchwork Wed Jun 24 07:37:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Show Liu X-Patchwork-Id: 4198 Return-Path: Received: from mail-pg1-x544.google.com (mail-pg1-x544.google.com [IPv6:2607:f8b0:4864:20::544]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 97E83609B3 for ; Wed, 24 Jun 2020 09:37:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.b="jyUAi5+e"; dkim-atps=neutral Received: by mail-pg1-x544.google.com with SMTP id t6so1025435pgq.1 for ; Wed, 24 Jun 2020 00:37:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=pSZyXUI79qqOOKSmYAcge+eR8C6r79IJIuFPZ+hzHoo=; b=jyUAi5+eO7VwN34X7M3jQERlFqlCy7zPcfMkt1qeAd9Hu9OXzZRvBEuWXMrx6yfUoZ ooppCGOPAIt3hrdXu9p8W6trw/UdCxfyB8xBSbDlXu0kxJUetblw2kvhQDRxWyWAB9MG ssv2KT3b13xcOt0fMfLMcxjR8DHhTRHkzpL4hPMCOFksQXOvFnYk3Vk9L1dnC9MIjs5E i/oOuubKA7Ns7XyMC2I82q9aEqZi24XmBEX9JnewnR++KoUrI/0+KBqQaGwkAOvQeGZb SiuGVDbsvusBYb5FH4g+ojC4z9NRU1oaGjTft/ef4Ads+cU61D93ub8KcESvjYcDxYGc uWxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=pSZyXUI79qqOOKSmYAcge+eR8C6r79IJIuFPZ+hzHoo=; b=MlNAqxJ/EyNPW2KccHWUUTh5Ob9V8XVVl+yvy1YW/p8IPd9CARL7iGCbM3etMmvY+t iLwG703P5Z/DaR+BctHo5GAceuKgQndK5+T7YLzKrrKMTFAqr8i6luugJxYmc+3F73Z6 tL1vY0BcNCKvLVCd46qLn57YXu99Q3jL97etOGDLaNA0koZOEQwHvfczvrnWA+/2qzwo FcNUX4AOHEhCCZFGwLOmNgzMOgbKcMyRBX991ivbwu5wKhvL3kFgkMdyVfuDXtSs/j5G hpg2dslz/wrlQiPXtwSldQr8gh5dX81Hi4VyuX/1KUGQB8QMx9rnvk/PbBQdSD8KACY5 tF2g== X-Gm-Message-State: AOAM532QJaEFeOdUJkhGXBseeulpfhZtvwyUE1jbJZOvDeYi2Rz87va8 l1u8OqDbOTz79cQ3d28I19r9fBC4FZ7/wg== X-Google-Smtp-Source: ABdhPJwFkxHr68s9j7UAfjBq92iPIt2lWXceqU6hTZnET32B8b6xQU4ymn9uFmhHnBRAGlCpBuh1/g== X-Received: by 2002:a62:ce46:: with SMTP id y67mr3557920pfg.118.1592984244226; Wed, 24 Jun 2020 00:37:24 -0700 (PDT) Received: from localhost.localdomain (211-20-20-223.HINET-IP.hinet.net. [211.20.20.223]) by smtp.gmail.com with ESMTPSA id b19sm4281226pjo.57.2020.06.24.00.37.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 24 Jun 2020 00:37:23 -0700 (PDT) From: Show Liu To: libcamera-devel@lists.libcamera.org Date: Wed, 24 Jun 2020 15:37:05 +0800 Message-Id: <20200624073705.14737-2-show.liu@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200624073705.14737-1-show.liu@linaro.org> References: <20200624073705.14737-1-show.liu@linaro.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 1/1] qcam: Render YUV formats frame by OpenGL shader X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 24 Jun 2020 07:37:27 -0000 The patch is to render the NV family YUV formats by OpenGL shader. V3: refine the fragment shader for better pixel color. Signed-off-by: Show Liu --- src/qcam/main.cpp | 2 + src/qcam/main_window.cpp | 19 ++- src/qcam/main_window.h | 3 +- src/qcam/meson.build | 2 + src/qcam/shader.h | 104 ++++++++++++ src/qcam/viewfinder.cpp | 18 +- src/qcam/viewfinder.h | 23 ++- src/qcam/viewfinderGL.cpp | 335 ++++++++++++++++++++++++++++++++++++++ src/qcam/viewfinderGL.h | 101 ++++++++++++ 9 files changed, 593 insertions(+), 14 deletions(-) create mode 100644 src/qcam/shader.h create mode 100644 src/qcam/viewfinderGL.cpp create mode 100644 src/qcam/viewfinderGL.h diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp index b3468cb..a3ea471 100644 --- a/src/qcam/main.cpp +++ b/src/qcam/main.cpp @@ -35,6 +35,8 @@ OptionsParser::Options parseOptions(int argc, char *argv[]) "help"); parser.addOption(OptStream, &streamKeyValue, "Set configuration of a camera stream", "stream", true); + parser.addOption(OptOpenGL, OptionNone, "Render YUV formats frame via OpenGL shader", + "opengl"); OptionsParser::Options options = parser.parse(argc, argv); if (options.isSet(OptHelp)) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index 7bc1360..6edf370 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -28,6 +28,9 @@ #include #include "dng_writer.h" +#include "main_window.h" +#include "viewfinder.h" +#include "viewfinderGL.h" using namespace libcamera; @@ -105,10 +108,18 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) setWindowTitle(title_); connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle())); - viewfinder_ = new ViewFinder(this); - connect(viewfinder_, &ViewFinder::renderComplete, - this, &MainWindow::queueRequest); - setCentralWidget(viewfinder_); + if (options_.isSet(OptOpenGL)) { + viewfinder_ = new ViewFinderGL(this); + connect(dynamic_cast(viewfinder_), &ViewFinderGL::renderComplete, + this, &MainWindow::queueRequest); + setCentralWidget(dynamic_cast(viewfinder_)); + } else { + viewfinder_ = new ViewFinder(this); + connect(dynamic_cast(viewfinder_), &ViewFinder::renderComplete, + this, &MainWindow::queueRequest); + setCentralWidget(dynamic_cast(viewfinder_)); + } + adjustSize(); /* Hotplug/unplug support */ diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 4606fe4..a852ef4 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -38,6 +38,7 @@ enum { OptCamera = 'c', OptHelp = 'h', OptStream = 's', + OptOpenGL = 'o', }; class CaptureRequest @@ -102,7 +103,7 @@ private: QAction *startStopAction_; QComboBox *cameraCombo_; QAction *saveRaw_; - ViewFinder *viewfinder_; + ViewFinderHandler *viewfinder_; QIcon iconPlay_; QIcon iconStop_; diff --git a/src/qcam/meson.build b/src/qcam/meson.build index 045db52..6a58947 100644 --- a/src/qcam/meson.build +++ b/src/qcam/meson.build @@ -7,11 +7,13 @@ qcam_sources = files([ 'main.cpp', 'main_window.cpp', 'viewfinder.cpp', + 'viewfinderGL.cpp' ]) qcam_moc_headers = files([ 'main_window.h', 'viewfinder.h', + 'viewfinderGL.h' ]) qcam_resources = files([ diff --git a/src/qcam/shader.h b/src/qcam/shader.h new file mode 100644 index 0000000..f54c264 --- /dev/null +++ b/src/qcam/shader.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Linaro + * + * shader.h - YUV format converter shader source code + */ +#ifndef __SHADER_H__ +#define __SHADER_H__ + +/* Vertex shader for NV formats */ +char NV_Vertex_shader[] = "attribute vec4 vertexIn;\n" + "attribute vec2 textureIn;\n" + "varying vec2 textureOut;\n" + "void main(void)\n" + "{\n" + "gl_Position = vertexIn;\n" + "textureOut = textureIn;\n" + "}\n"; + +/* Fragment shader for NV12, NV16 and NV24 */ +char NV_2_planes_UV[] = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" + "varying vec2 textureOut;\n" + "uniform sampler2D tex_y;\n" + "uniform sampler2D tex_u;\n" + "void main(void)\n" + "{\n" + "vec3 yuv;\n" + "vec3 rgb;\n" + "mat3 convert_mat = mat3(vec3(1.1640625, 1.1640625, 1.1640625),\n" + " vec3(0.0, -0.390625, 2.015625),\n" + " vec3(1.5975625, -0.8125, 0.0));\n" + "yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n" + "yuv.y = texture2D(tex_u, textureOut).r - 0.5;\n" + "yuv.z = texture2D(tex_u, textureOut).g - 0.5;\n" + "rgb = convert_mat * yuv;\n" + "gl_FragColor = vec4(rgb, 1.0);\n" + "}\n"; + +/* Fragment shader for NV21, NV61 and NV42 */ +char NV_2_planes_VU[] = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" + "varying vec2 textureOut;\n" + "uniform sampler2D tex_y;\n" + "uniform sampler2D tex_u;\n" + "void main(void)\n" + "{\n" + "vec3 yuv;\n" + "vec3 rgb;\n" + "mat3 convert_mat = mat3(vec3(1.1640625, 1.1640625, 1.1640625),\n" + " vec3(0.0, -0.390625, 2.015625),\n" + " vec3(1.5975625, -0.8125, 0.0));\n" + "yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n" + "yuv.y = texture2D(tex_u, textureOut).g - 0.5;\n" + "yuv.z = texture2D(tex_u, textureOut).r - 0.5;\n" + "rgb = convert_mat * yuv;\n" + "gl_FragColor = vec4(rgb, 1.0);\n" + "}\n"; + +/* Fragment shader for YUV420 */ +char NV_3_planes_UV[] = "#ifdef GL_ES\n" + "precision highp float;\n" + "#endif\n" + "varying vec2 textureOut;\n" + "uniform sampler2D tex_y;\n" + "uniform sampler2D tex_u;\n" + "uniform sampler2D tex_v;\n" + "void main(void)\n" + "{\n" + "vec3 yuv;\n" + "vec3 rgb;\n" + "mat3 convert_mat = mat3(vec3(1.1640625, 1.1640625, 1.1640625),\n" + " vec3(0.0, -0.390625, 2.015625),\n" + " vec3(1.5975625, -0.8125, 0.0));\n" + "yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n" + "yuv.y = texture2D(tex_u, textureOut).r - 0.5;\n" + "yuv.z = texture2D(tex_v, textureOut).g - 0.5;\n" + "rgb = convert_mat * yuv;\n" + "gl_FragColor = vec4(rgb, 1);\n" + "}\n"; + +/* Fragment shader for YVU420 */ +char NV_3_planes_VU[] = "precision highp float;\n" + "#endif\n" + "varying vec2 textureOut;\n" + "uniform sampler2D tex_y;\n" + "uniform sampler2D tex_u;\n" + "uniform sampler2D tex_v;\n" + "void main(void)\n" + "{\n" + "vec3 yuv;\n" + "vec3 rgb;\n" + "mat3 convert_mat = mat3(vec3(1.1640625, 1.1640625, 1.1640625),\n" + " vec3(0.0, -0.390625, 2.015625),\n" + " vec3(1.5975625, -0.8125, 0.0));\n" + "yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n" + "yuv.y = texture2D(tex_v, textureOut).g - 0.5;\n" + "yuv.z = texture2D(tex_u, textureOut).r - 0.5;\n" + "rgb = convert_mat * yuv;\n" + "gl_FragColor = vec4(rgb, 1);\n" + "}\n"; +#endif // __SHADER_H__ diff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp index dcf8a15..d3a2422 100644 --- a/src/qcam/viewfinder.cpp +++ b/src/qcam/viewfinder.cpp @@ -33,22 +33,30 @@ static const QMap nativeFormats { libcamera::formats::BGR888, QImage::Format_RGB888 }, }; -ViewFinder::ViewFinder(QWidget *parent) - : QWidget(parent), buffer_(nullptr) +ViewFinderHandler::ViewFinderHandler() { - icon_ = QIcon(":camera-off.svg"); } -ViewFinder::~ViewFinder() +ViewFinderHandler::~ViewFinderHandler() { } -const QList &ViewFinder::nativeFormats() const +const QList &ViewFinderHandler::nativeFormats() const { static const QList formats = ::nativeFormats.keys(); return formats; } +ViewFinder::ViewFinder(QWidget *parent) + : QWidget(parent), buffer_(nullptr) +{ + icon_ = QIcon(":camera-off.svg"); +} + +ViewFinder::~ViewFinder() +{ +} + int ViewFinder::setFormat(const libcamera::PixelFormat &format, const QSize &size) { diff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h index 26a1320..741d694 100644 --- a/src/qcam/viewfinder.h +++ b/src/qcam/viewfinder.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -28,7 +30,23 @@ struct MappedBuffer { size_t size; }; -class ViewFinder : public QWidget +class ViewFinderHandler +{ +public: + ViewFinderHandler(); + virtual ~ViewFinderHandler(); + + const QList &nativeFormats() const; + + virtual int setFormat(const libcamera::PixelFormat &format, const QSize &size) =0; + virtual void render(libcamera::FrameBuffer *buffer, MappedBuffer *map) =0; + virtual void stop() =0; + + virtual QImage getCurrentImage() =0; + +}; + +class ViewFinder : public QWidget, public ViewFinderHandler { Q_OBJECT @@ -36,8 +54,6 @@ public: ViewFinder(QWidget *parent); ~ViewFinder(); - const QList &nativeFormats() const; - int setFormat(const libcamera::PixelFormat &format, const QSize &size); void render(libcamera::FrameBuffer *buffer, MappedBuffer *map); void stop(); @@ -67,5 +83,4 @@ private: QImage image_; QMutex mutex_; /* Prevent concurrent access to image_ */ }; - #endif /* __QCAM_VIEWFINDER__ */ diff --git a/src/qcam/viewfinderGL.cpp b/src/qcam/viewfinderGL.cpp new file mode 100644 index 0000000..7b47beb --- /dev/null +++ b/src/qcam/viewfinderGL.cpp @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Linaro + * + * viewfinderGL.cpp - Render YUV format frame by OpenGL shader + */ + +#include "shader.h" +#include "viewfinderGL.h" + +#include + +#include + +#define ATTRIB_VERTEX 0 +#define ATTRIB_TEXTURE 1 + +ViewFinderGL::ViewFinderGL(QWidget *parent) + : QOpenGLWidget(parent), + glBuffer(QOpenGLBuffer::VertexBuffer), + pFShader(nullptr), + pVShader(nullptr), + textureU(QOpenGLTexture::Target2D), + textureV(QOpenGLTexture::Target2D), + textureY(QOpenGLTexture::Target2D), + yuvDataPtr(nullptr) + +{ +} + +ViewFinderGL::~ViewFinderGL() +{ + removeShader(); + + if(textureY.isCreated()) + textureY.destroy(); + + if(textureU.isCreated()) + textureU.destroy(); + + if(textureV.isCreated()) + textureV.destroy(); + + glBuffer.destroy(); +} + +int ViewFinderGL::setFormat(const libcamera::PixelFormat &format, + const QSize &size) +{ + format_ = format; + size_ = size; + + updateGeometry(); + return 0; +} + +void ViewFinderGL::stop() +{ + if (buffer_) { + renderComplete(buffer_); + buffer_ = nullptr; + } +} + +QImage ViewFinderGL::getCurrentImage() +{ + QMutexLocker locker(&mutex_); + + return(grabFramebuffer()); +} + +void ViewFinderGL::render(libcamera::FrameBuffer *buffer, MappedBuffer *map) +{ + if (buffer->planes().size() != 1) { + qWarning() << "Multi-planar buffers are not supported"; + return; + } + + unsigned char *memory = static_cast(map->memory); + if(memory) { + yuvDataPtr = memory; + update(); + buffer_ = buffer; + } + + if (buffer) + renderComplete(buffer); +} + +void ViewFinderGL::updateFrame(unsigned char *buffer) +{ + if(buffer) { + yuvDataPtr = buffer; + update(); + } +} + +void ViewFinderGL::setFrameSize(int width, int height) +{ + if(width > 0 && height > 0) { + width_ = width; + height_ = height; + } +} + +char *ViewFinderGL::selectFragmentShader(unsigned int format) +{ + char *fsrc = nullptr; + + switch(format) { + case libcamera::formats::NV12: + horzSubSample_ = 2; + vertSubSample_ = 2; + fsrc = NV_2_planes_UV; + break; + case libcamera::formats::NV21: + horzSubSample_ = 2; + vertSubSample_ = 2; + fsrc = NV_2_planes_VU; + break; + case libcamera::formats::NV16: + horzSubSample_ = 2; + vertSubSample_ = 1; + fsrc = NV_2_planes_UV; + break; + case libcamera::formats::NV61: + horzSubSample_ = 2; + vertSubSample_ = 1; + fsrc = NV_2_planes_VU; + break; + case libcamera::formats::NV24: + horzSubSample_ = 1; + vertSubSample_ = 1; + fsrc = NV_2_planes_UV; + break; + case libcamera::formats::NV42: + horzSubSample_ = 1; + vertSubSample_ = 1; + fsrc = NV_2_planes_VU; + break; + case libcamera::formats::YUV420: + horzSubSample_ = 2; + vertSubSample_ = 2; + fsrc = NV_3_planes_UV; + break; + default: + break; + }; + + if(fsrc == nullptr) { + qDebug() << __func__ << "[ERROR]:" <<" No suitable fragment shader can be select."; + } + return fsrc; +} + +void ViewFinderGL::createFragmentShader() +{ + bool bCompile; + + pFShader = new QOpenGLShader(QOpenGLShader::Fragment, this); + char *fsrc = selectFragmentShader(format_); + + bCompile = pFShader->compileSourceCode(fsrc); + if(!bCompile) + { + qDebug() << __func__ << ":" << pFShader->log(); + } + + shaderProgram.addShader(pFShader); + + // Link shader pipeline + if (!shaderProgram.link()) { + qDebug() << __func__ << ": shader program link failed.\n" << shaderProgram.log(); + close(); + } + + // Bind shader pipeline for use + if (!shaderProgram.bind()) { + qDebug() << __func__ << ": shader program binding failed.\n" << shaderProgram.log(); + close(); + } + + shaderProgram.enableAttributeArray(ATTRIB_VERTEX); + shaderProgram.enableAttributeArray(ATTRIB_TEXTURE); + + shaderProgram.setAttributeBuffer(ATTRIB_VERTEX,GL_FLOAT,0,2,2*sizeof(GLfloat)); + shaderProgram.setAttributeBuffer(ATTRIB_TEXTURE,GL_FLOAT,8*sizeof(GLfloat),2,2*sizeof(GLfloat)); + + textureUniformY = shaderProgram.uniformLocation("tex_y"); + textureUniformU = shaderProgram.uniformLocation("tex_u"); + textureUniformV = shaderProgram.uniformLocation("tex_v"); + + if(!textureY.isCreated()) + textureY.create(); + + if(!textureU.isCreated()) + textureU.create(); + + if(!textureV.isCreated()) + textureV.create(); + + id_y = textureY.textureId(); + id_u = textureU.textureId(); + id_v = textureV.textureId(); +} + +void ViewFinderGL::configureTexture(unsigned int id) +{ + glBindTexture(GL_TEXTURE_2D, id); + 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); +} + +void ViewFinderGL::removeShader() +{ + if (shaderProgram.isLinked()) { + shaderProgram.release(); + shaderProgram.removeAllShaders(); + } + + if(pFShader) + delete pFShader; + + if(pVShader) + delete pVShader; +} + +void ViewFinderGL::initializeGL() +{ + bool bCompile; + + initializeOpenGLFunctions(); + glEnable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + + static const GLfloat vertices[] { + -1.0f,-1.0f, + -1.0f,+1.0f, + +1.0f,+1.0f, + +1.0f,-1.0f, + 0.0f,1.0f, + 0.0f,0.0f, + 1.0f,0.0f, + 1.0f,1.0f, + }; + + glBuffer.create(); + glBuffer.bind(); + glBuffer.allocate(vertices,sizeof(vertices)); + + /* Create Vertex Shader */ + pVShader = new QOpenGLShader(QOpenGLShader::Vertex, this); + char *vsrc = NV_Vertex_shader; + + bCompile = pVShader->compileSourceCode(vsrc); + if(!bCompile) { + qDebug() << __func__<< ":" << pVShader->log(); + } + + shaderProgram.addShader(pVShader); + + glClearColor(1.0f, 1.0f, 1.0f, 0.0f); +} + +void ViewFinderGL::render() +{ + switch(format_) { + case libcamera::formats::NV12: + case libcamera::formats::NV21: + case libcamera::formats::NV16: + case libcamera::formats::NV61: + case libcamera::formats::NV24: + case libcamera::formats::NV42: + /* activate texture 0 */ + glActiveTexture(GL_TEXTURE0); + configureTexture(id_y); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width(), size_.height(), 0, GL_RED, GL_UNSIGNED_BYTE, yuvDataPtr); + glUniform1i(textureUniformY, 0); + + /* activate texture 1 */ + glActiveTexture(GL_TEXTURE1); + configureTexture(id_u); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size_.width()/horzSubSample_, size_.height()/vertSubSample_, 0, GL_RG, GL_UNSIGNED_BYTE, (char*)yuvDataPtr+size_.width()*size_.height()); + glUniform1i(textureUniformU, 1); + break; + case libcamera::formats::YUV420: + /* activate texture 0 */ + glActiveTexture(GL_TEXTURE0); + configureTexture(id_y); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width(), size_.height(), 0, GL_RED, GL_UNSIGNED_BYTE, yuvDataPtr); + glUniform1i(textureUniformY, 0); + + /* activate texture 1 */ + glActiveTexture(GL_TEXTURE1); + configureTexture(id_u); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size_.width()/horzSubSample_, size_.height()/vertSubSample_, 0, GL_RG, GL_UNSIGNED_BYTE, (char*)yuvDataPtr+size_.width()*size_.height()); + glUniform1i(textureUniformU, 1); + + /* activate texture 2 */ + glActiveTexture(GL_TEXTURE2); + configureTexture(id_v); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size_.width()/horzSubSample_, size_.height()/vertSubSample_, 0, GL_RG, GL_UNSIGNED_BYTE, (char*)yuvDataPtr+size_.width()*size_.height()*5/4); + glUniform1i(textureUniformV, 2); + break; + default: + break; + }; +} + +void ViewFinderGL::paintGL() +{ + if(pFShader == nullptr) + createFragmentShader(); + + if(yuvDataPtr) + { + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + render(); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } +} + +void ViewFinderGL::resizeGL(int w, int h) +{ + glViewport(0,0,w,h); +} + +QSize ViewFinderGL::sizeHint() const +{ + return size_.isValid() ? size_ : QSize(640, 480); +} diff --git a/src/qcam/viewfinderGL.h b/src/qcam/viewfinderGL.h new file mode 100644 index 0000000..08662ca --- /dev/null +++ b/src/qcam/viewfinderGL.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Linaro + * + * viewfinderGL.h - Render YUV format frame by OpenGL shader + */ +#ifndef __VIEWFINDERGL_H__ +#define __VIEWFINDERGL_H__ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "viewfinder.h" + +class ViewFinderGL : public QOpenGLWidget, + public ViewFinderHandler, + protected QOpenGLFunctions +{ + Q_OBJECT + +public: + ViewFinderGL(QWidget *parent = 0); + ~ViewFinderGL(); + + int setFormat(const libcamera::PixelFormat &format, const QSize &size); + void render(libcamera::FrameBuffer *buffer, MappedBuffer *map); + void stop(); + + QImage getCurrentImage(); + + void setFrameSize(int width, int height); + void updateFrame(unsigned char *buffer); + + char *selectFragmentShader(unsigned int format); + void createFragmentShader(); + void render(); + +Q_SIGNALS: + void renderComplete(libcamera::FrameBuffer *buffer); + +protected: + void initializeGL() Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; + void resizeGL(int w, int h) Q_DECL_OVERRIDE; + QSize sizeHint() const override; + +private: + + void configureTexture(unsigned int id); + void removeShader(); + + /* Captured image size, format and buffer */ + libcamera::FrameBuffer *buffer_; + libcamera::PixelFormat format_; + QOpenGLBuffer glBuffer; + QSize size_; + + GLuint id_u; + GLuint id_v; + GLuint id_y; + + QMutex mutex_; /* Prevent concurrent access to image_ */ + + QOpenGLShader *pFShader; + QOpenGLShader *pVShader; + QOpenGLShaderProgram shaderProgram; + + GLuint textureUniformU; + GLuint textureUniformV; + GLuint textureUniformY; + + QOpenGLTexture textureU; + QOpenGLTexture textureV; + QOpenGLTexture textureY; + + unsigned int width_; + unsigned int height_; + + unsigned char* yuvDataPtr; + + /* NV parameters */ + unsigned int horzSubSample_ ; + unsigned int vertSubSample_; +}; +#endif // __VIEWFINDERGL_H__