/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Linaro * * viewfinderGL.cpp - OpenGL Viewfinder for rendering by OpenGL shader */ #include "viewfinder_gl.h" #include #include #include #include static const QList supportedFormats{ /* YUV - packed (single plane) */ libcamera::formats::UYVY, libcamera::formats::VYUY, libcamera::formats::YUYV, libcamera::formats::YVYU, /* YUV - semi planar (two planes) */ libcamera::formats::NV12, libcamera::formats::NV21, libcamera::formats::NV16, libcamera::formats::NV61, libcamera::formats::NV24, libcamera::formats::NV42, /* YUV - fully planar (three planes) */ libcamera::formats::YUV420, libcamera::formats::YVU420, /* RGB */ libcamera::formats::ABGR8888, libcamera::formats::ARGB8888, libcamera::formats::BGRA8888, libcamera::formats::RGBA8888, libcamera::formats::BGR888, libcamera::formats::RGB888, }; ViewFinderGL::ViewFinderGL(QWidget *parent) : QOpenGLWidget(parent), buffer_(nullptr), data_(nullptr), vertexBuffer_(QOpenGLBuffer::VertexBuffer) { } ViewFinderGL::~ViewFinderGL() { removeShader(); } const QList &ViewFinderGL::nativeFormats() const { return supportedFormats; } int ViewFinderGL::setFormat(const libcamera::PixelFormat &format, const QSize &size) { if (format != format_) { /* * If the fragment already exists, remove it and create a new * one for the new format. */ if (shaderProgram_.isLinked()) { shaderProgram_.release(); shaderProgram_.removeShader(fragmentShader_.get()); fragmentShader_.reset(); } if (!selectFormat(format)) return -1; 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; } if (buffer_) renderComplete(buffer_); data_ = static_cast(map->memory); update(); buffer_ = buffer; } bool ViewFinderGL::selectFormat(const libcamera::PixelFormat &format) { bool ret = true; fragmentShaderDefines_.clear(); switch (format) { case libcamera::formats::NV12: horzSubSample_ = 2; vertSubSample_ = 2; fragmentShaderDefines_.append("#define YUV_PATTERN_UV"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::NV21: horzSubSample_ = 2; vertSubSample_ = 2; fragmentShaderDefines_.append("#define YUV_PATTERN_VU"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::NV16: horzSubSample_ = 2; vertSubSample_ = 1; fragmentShaderDefines_.append("#define YUV_PATTERN_UV"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::NV61: horzSubSample_ = 2; vertSubSample_ = 1; fragmentShaderDefines_.append("#define YUV_PATTERN_VU"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::NV24: horzSubSample_ = 1; vertSubSample_ = 1; fragmentShaderDefines_.append("#define YUV_PATTERN_UV"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::NV42: horzSubSample_ = 1; vertSubSample_ = 1; fragmentShaderDefines_.append("#define YUV_PATTERN_VU"); fragmentShaderFile_ = ":YUV_2_planes.frag"; break; case libcamera::formats::YUV420: horzSubSample_ = 2; vertSubSample_ = 2; fragmentShaderFile_ = ":YUV_3_planes.frag"; break; case libcamera::formats::YVU420: horzSubSample_ = 2; vertSubSample_ = 2; fragmentShaderFile_ = ":YUV_3_planes.frag"; break; case libcamera::formats::UYVY: fragmentShaderDefines_.append("#define YUV_PATTERN_UYVY"); fragmentShaderFile_ = ":YUV_packed.frag"; break; case libcamera::formats::VYUY: fragmentShaderDefines_.append("#define YUV_PATTERN_VYUY"); fragmentShaderFile_ = ":YUV_packed.frag"; break; case libcamera::formats::YUYV: fragmentShaderDefines_.append("#define YUV_PATTERN_YUYV"); fragmentShaderFile_ = ":YUV_packed.frag"; break; case libcamera::formats::YVYU: fragmentShaderDefines_.append("#define YUV_PATTERN_YVYU"); fragmentShaderFile_ = ":YUV_packed.frag"; break; case libcamera::formats::ABGR8888: fragmentShaderDefines_.append("#define RGB_PATTERN rgb"); fragmentShaderFile_ = ":RGB.frag"; break; case libcamera::formats::ARGB8888: fragmentShaderDefines_.append("#define RGB_PATTERN bgr"); fragmentShaderFile_ = ":RGB.frag"; break; case libcamera::formats::BGRA8888: fragmentShaderDefines_.append("#define RGB_PATTERN gba"); fragmentShaderFile_ = ":RGB.frag"; break; case libcamera::formats::RGBA8888: fragmentShaderDefines_.append("#define RGB_PATTERN abg"); fragmentShaderFile_ = ":RGB.frag"; break; case libcamera::formats::BGR888: fragmentShaderDefines_.append("#define RGB_PATTERN rgb"); fragmentShaderFile_ = ":RGB.frag"; break; case libcamera::formats::RGB888: fragmentShaderDefines_.append("#define RGB_PATTERN bgr"); fragmentShaderFile_ = ":RGB.frag"; break; default: ret = false; qWarning() << "[ViewFinderGL]:" << "format not supported."; break; }; return ret; } bool ViewFinderGL::createVertexShader() { /* Create Vertex Shader */ vertexShader_ = std::make_unique(QOpenGLShader::Vertex, this); /* Compile the vertex shader */ if (!vertexShader_->compileSourceFile(":identity.vert")) { qWarning() << "[ViewFinderGL]:" << vertexShader_->log(); return false; } shaderProgram_.addShader(vertexShader_.get()); return true; } bool ViewFinderGL::createFragmentShader() { int attributeVertex; int attributeTexture; /* * Create the fragment shader, compile it, and add it to the shader * program. The #define macros stored in fragmentShaderDefines_, if * any, are prepended to the source code. */ fragmentShader_ = std::make_unique(QOpenGLShader::Fragment, this); QFile file(fragmentShaderFile_); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Shader" << fragmentShaderFile_ << "not found"; return false; } QString defines = fragmentShaderDefines_.join('\n') + "\n"; QByteArray src = file.readAll(); src.prepend(defines.toUtf8()); if (!fragmentShader_->compileSourceCode(src)) { qWarning() << "[ViewFinderGL]:" << fragmentShader_->log(); return false; } shaderProgram_.addShader(fragmentShader_.get()); /* Link shader pipeline */ if (!shaderProgram_.link()) { qWarning() << "[ViewFinderGL]:" << shaderProgram_.log(); close(); } /* Bind shader pipeline for use */ if (!shaderProgram_.bind()) { qWarning() << "[ViewFinderGL]:" << shaderProgram_.log(); close(); } attributeVertex = shaderProgram_.attributeLocation("vertexIn"); attributeTexture = shaderProgram_.attributeLocation("textureIn"); shaderProgram_.enableAttributeArray(attributeVertex); shaderProgram_.setAttributeBuffer(attributeVertex, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat)); shaderProgram_.enableAttributeArray(attributeTexture); shaderProgram_.setAttributeBuffer(attributeTexture, GL_FLOAT, 8 * sizeof(GLfloat), 2, 2 * sizeof(GLfloat)); textureUniformY_ = shaderProgram_.uniformLocation("tex_y"); textureUniformU_ = shaderProgram_.uniformLocation("tex_u"); textureUniformV_ = shaderProgram_.uniformLocation("tex_v"); textureUniformStepX_ = shaderProgram_.uniformLocation("tex_stepx"); /* Create the textures. */ for (std::unique_ptr &texture : textures_) { if (texture) continue; texture = std::make_unique(QOpenGLTexture::Target2D); texture->create(); } return true; } void ViewFinderGL::configureTexture(QOpenGLTexture &texture) { glBindTexture(GL_TEXTURE_2D, texture.textureId()); 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(); } } void ViewFinderGL::initializeGL() { initializeOpenGLFunctions(); glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); static const GLfloat coordinates[2][4][2]{ { /* Vertex coordinates */ { -1.0f, -1.0f }, { -1.0f, +1.0f }, { +1.0f, +1.0f }, { +1.0f, -1.0f }, }, { /* Texture coordinates */ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, }, }; vertexBuffer_.create(); vertexBuffer_.bind(); vertexBuffer_.allocate(coordinates, sizeof(coordinates)); /* Create Vertex Shader */ if (!createVertexShader()) qWarning() << "[ViewFinderGL]: create vertex shader failed."; glClearColor(1.0f, 1.0f, 1.0f, 0.0f); } void ViewFinderGL::doRender() { 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 Y */ glActiveTexture(GL_TEXTURE0); configureTexture(*textures_[0]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width(), size_.height(), 0, GL_RED, GL_UNSIGNED_BYTE, data_); shaderProgram_.setUniformValue(textureUniformY_, 0); /* Activate texture UV/VU */ glActiveTexture(GL_TEXTURE1); configureTexture(*textures_[1]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size_.width() / horzSubSample_, size_.height() / vertSubSample_, 0, GL_RG, GL_UNSIGNED_BYTE, data_ + size_.width() * size_.height()); shaderProgram_.setUniformValue(textureUniformU_, 1); break; case libcamera::formats::YUV420: /* Activate texture Y */ glActiveTexture(GL_TEXTURE0); configureTexture(*textures_[0]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width(), size_.height(), 0, GL_RED, GL_UNSIGNED_BYTE, data_); shaderProgram_.setUniformValue(textureUniformY_, 0); /* Activate texture U */ glActiveTexture(GL_TEXTURE1); configureTexture(*textures_[1]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width() / horzSubSample_, size_.height() / vertSubSample_, 0, GL_RED, GL_UNSIGNED_BYTE, data_ + size_.width() * size_.height()); shaderProgram_.setUniformValue(textureUniformU_, 1); /* Activate texture V */ glActiveTexture(GL_TEXTURE2); configureTexture(*textures_[2]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width() / horzSubSample_, size_.height() / vertSubSample_, 0, GL_RED, GL_UNSIGNED_BYTE, data_ + size_.width() * size_.height() * 5 / 4); shaderProgram_.setUniformValue(textureUniformV_, 2); break; case libcamera::formats::YVU420: /* Activate texture Y */ glActiveTexture(GL_TEXTURE0); configureTexture(*textures_[0]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width(), size_.height(), 0, GL_RED, GL_UNSIGNED_BYTE, data_); shaderProgram_.setUniformValue(textureUniformY_, 0); /* Activate texture V */ glActiveTexture(GL_TEXTURE2); configureTexture(*textures_[2]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size_.width() / horzSubSample_, /* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2020, Sebastian Fricke * * bayer_format.cpp - BayerFormat class tests */ #include <iostream> #include <libcamera/transform.h> #include "libcamera/internal/bayer_format.h" #include "test.h" using namespace std; using namespace libcamera; class BayerFormatTest : public Test { protected: int run() { /* An empty Bayer format has to be invalid. */ BayerFormat bayerFmt; if (bayerFmt.isValid()) { cerr << "An empty BayerFormat has to be invalid." << endl; return TestFail; } /* A correct Bayer format has to be valid. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); if (!bayerFmt.isValid()) { cerr << "A correct BayerFormat has to be valid." << endl; return TestFail; } /* * Two bayer formats created with the same order and bit depth * have to be equal. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); BayerFormat bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); if (bayerFmt != bayerFmtExpect) { cerr << "Comparison of identical formats failed." << endl; return TestFail; } /* * Two Bayer formats created with the same order but with a * different bitDepth are not equal. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 12, BayerFormat::None); if (bayerFmt == bayerFmtExpect) { cerr << "Comparison of different formats failed." << endl; return TestFail; } /* * Create a Bayer format with a V4L2PixelFormat and check if we * get the same format after converting back to the V4L2 Format. */ V4L2PixelFormat v4l2FmtExpect = V4L2PixelFormat( V4L2_PIX_FMT_SBGGR8); bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtExpect); V4L2PixelFormat v4l2Fmt = bayerFmt.toV4L2PixelFormat(); if (v4l2Fmt != v4l2FmtExpect) { cerr << "Expected: '" << v4l2FmtExpect.toString() << "' got: '" << v4l2Fmt.toString() << "'" << endl; return TestFail; } /* * Use an empty Bayer format and verify that no matching * V4L2PixelFormat is found. */ v4l2FmtExpect = V4L2PixelFormat(); bayerFmt = BayerFormat(); v4l2Fmt = bayerFmt.toV4L2PixelFormat(); if (v4l2Fmt != v4l2FmtExpect) { cerr << "Expected: empty V4L2PixelFormat got: '" << v4l2Fmt.toString() << "'" << endl; return TestFail; } /* * Check if we get the expected Bayer format BGGR8 * when we convert the V4L2PixelFormat (V4L2_PIX_FMT_SBGGR8) * to a Bayer format. */ bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); v4l2Fmt = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8); bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2Fmt); if (bayerFmt != bayerFmtExpect) { cerr << "Expected BayerFormat '" << bayerFmtExpect.toString() << "', got: '" << bayerFmt.toString() << "'" << endl; return TestFail; } /* * Confirm that a V4L2PixelFormat that is not found in * the conversion table, doesn't yield a Bayer format. */ V4L2PixelFormat v4l2FmtUnknown = V4L2PixelFormat( V4L2_PIX_FMT_BGRA444); bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtUnknown); if (bayerFmt.isValid()) { cerr << "Expected empty BayerFormat got: '" << bayerFmt.toString() << "'" << endl; return TestFail; } /* * Test if a valid Bayer format can be converted to a * string representation. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); if (bayerFmt.toString() != "BGGR-8") { cerr << "String representation != 'BGGR-8' (got: '" << bayerFmt.toString() << "' ) " << endl; return TestFail; } /* * Determine if an empty Bayer format results in no * string representation. */ bayerFmt = BayerFormat(); if (bayerFmt.toString() != "INVALID") { cerr << "String representation != 'INVALID' (got: '" << bayerFmt.toString() << "' ) " << endl; return TestFail; } /* * Perform a horizontal Flip and make sure that the * order is adjusted accordingly. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); bayerFmtExpect = BayerFormat(BayerFormat::GBRG, 8, BayerFormat::None); BayerFormat hFlipFmt = bayerFmt.transform(Transform::HFlip); if (hFlipFmt != bayerFmtExpect) { cerr << "Horizontal flip of 'BGGR-8' should result in '" << bayerFmtExpect.toString() << "', got: '" << hFlipFmt.toString() << "'" << endl; return TestFail; } /* * Perform a vertical Flip and make sure that * the order is adjusted accordingly. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8, BayerFormat::None); BayerFormat vFlipFmt = bayerFmt.transform(Transform::VFlip); if (vFlipFmt != bayerFmtExpect) { cerr << "Vertical flip of 'BGGR-8' should result in '" << bayerFmtExpect.toString() << "', got: '" << vFlipFmt.toString() << "'" << endl; return TestFail; } /* * Perform a transposition on a pixel order with both green * pixels on the bottom left to top right diagonal and make * sure, that it doesn't change. */ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::None); BayerFormat transposeFmt = bayerFmt.transform( Transform::Transpose); if (transposeFmt != bayerFmt) { cerr << "Transpose with both green pixels on the " << "antidiagonal should not change the order " << "(got '" << transposeFmt.toString() << "')" << endl; return TestFail; } /* * Perform a transposition on an pixel order with red and blue * on the bottom left to top right diagonal and make sure * that their positions are switched. */ bayerFmt = BayerFormat(BayerFormat::GBRG, 8, BayerFormat::None); bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8, BayerFormat::None); transposeFmt = bayerFmt.transform(Transform::Transpose); if (transposeFmt != bayerFmtExpect) { cerr << "Transpose with the red & blue pixels on the " << "antidiagonal should switch their position " << "(got '" << transposeFmt.toString() << "')" << endl; return TestFail; } return TestPass; } }; TEST_REGISTER(BayerFormatTest)