/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2020, Linaro
 *
 * OpenGL Viewfinder for rendering by OpenGL shader
 */

#include "viewfinder_gl.h"

#include <array>

#include <QByteArray>
#include <QFile>
#include <QImage>
#include <QMatrix4x4>
#include <QStringList>

#include <libcamera/formats.h>

#include "../common/image.h"

static const QList<libcamera::PixelFormat> 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,
	/* Raw Bayer 8-bit */
	libcamera::formats::SBGGR8,
	libcamera::formats::SGBRG8,
	libcamera::formats::SGRBG8,
	libcamera::formats::SRGGB8,
	/* Raw Bayer 10-bit packed */
	libcamera::formats::SBGGR10_CSI2P,
	libcamera::formats::SGBRG10_CSI2P,
	libcamera::formats::SGRBG10_CSI2P,
	libcamera::formats::SRGGB10_CSI2P,
	/* Raw Bayer 12-bit packed */
	libcamera::formats::SBGGR12_CSI2P,
	libcamera::formats::SGBRG12_CSI2P,
	libcamera::formats::SGRBG12_CSI2P,
	libcamera::formats::SRGGB12_CSI2P,
};

ViewFinderGL::ViewFinderGL(QWidget *parent)
	: QOpenGLWidget(parent), buffer_(nullptr),
	  colorSpace_(libcamera::ColorSpace::Raw), image_(nullptr),
	  vertexBuffer_(QOpenGLBuffer::VertexBuffer)
{
}

ViewFinderGL::~ViewFinderGL()
{
	removeShader();
}

const QList<libcamera::PixelFormat> &ViewFinderGL::nativeFormats() const
{
	return supportedFormats;
}

int ViewFinderGL::setFormat(const libcamera::PixelFormat &format, const QSize &size,
			    const libcamera::ColorSpace &colorSpace,
			    unsigned int stride)
{
	if (format != format_ || colorSpace != colorSpace_) {
		/*
		 * 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;

		selectColorSpace(colorSpace);

		format_ = format;
		colorSpace_ = colorSpace;
	}

	size_ = size;
	stride_ = stride;

	updateGeometry();
	return 0;
}

void ViewFinderGL::stop()
{
	if (buffer_) {
		renderComplete(buffer_);
		buffer_ = nullptr;
		image_ = nullptr;
	}
}

QImage ViewFinderGL::getCurrentImage()
{
	QMutexLocker locker(&mutex_);

	return grabFramebuffer();
}

void ViewFinderGL::render(libcamera::FrameBuffer *buffer, Image *image)
{
	if (buffer_)
		renderComplete(buffer_);

	image_ = image;
	update();
	buffer_ = buffer;
}

bool ViewFinderGL::selectFormat(const libcamera::PixelFormat &format)
{
	bool ret = true;

	/* Set min/mag filters to GL_LINEAR by default. */
	textureMinMagFilters_ = GL_LINEAR;

	/* Use identity.vert as the default vertex shader. */
	vertexShaderFile_ = ":identity.vert";

	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;
	case libcamera::formats::SBGGR8:
		firstRed_.setX(1.0);
		firstRed_.setY(1.0);
		vertexShaderFile_ = ":bayer_8.vert";
		fragmentShaderFile_ = ":bayer_8.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGBRG8:
		firstRed_.setX(0.0);
		firstRed_.setY(1.0);
		vertexShaderFile_ = ":bayer_8.vert";
		fragmentShaderFile_ = ":bayer_8.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGRBG8:
		firstRed_.setX(1.0);
		firstRed_.setY(0.0);
		vertexShaderFile_ = ":bayer_8.vert";
		fragmentShaderFile_ = ":bayer_8.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SRGGB8:
		firstRed_.setX(0.0);
		firstRed_.setY(0.0);
		vertexShaderFile_ = ":bayer_8.vert";
		fragmentShaderFile_ = ":bayer_8.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SBGGR10_CSI2P:
		firstRed_.setX(1.0);
		firstRed_.setY(1.0);
		fragmentShaderDefines_.append("#define RAW10P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGBRG10_CSI2P:
		firstRed_.setX(0.0);
		firstRed_.setY(1.0);
		fragmentShaderDefines_.append("#define RAW10P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGRBG10_CSI2P:
		firstRed_.setX(1.0);
		firstRed_.setY(0.0);
		fragmentShaderDefines_.append("#define RAW10P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SRGGB10_CSI2P:
		firstRed_.setX(0.0);
		firstRed_.setY(0.0);
		fragmentShaderDefines_.append("#define RAW10P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SBGGR12_CSI2P:
		firstRed_.setX(1.0);
		firstRed_.setY(1.0);
		fragmentShaderDefines_.append("#define RAW12P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGBRG12_CSI2P:
		firstRed_.setX(0.0);
		firstRed_.setY(1.0);
		fragmentShaderDefines_.append("#define RAW12P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SGRBG12_CSI2P:
		firstRed_.setX(1.0);
		firstRed_.setY(0.0);
		fragmentShaderDefines_.append("#define RAW12P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	case libcamera::formats::SRGGB12_CSI2P:
		firstRed_.setX(0.0);
		firstRed_.setY(0.0);
		fragmentShaderDefines_.append("#define RAW12P");
		fragmentShaderFile_ = ":bayer_1x_packed.frag";
		textureMinMagFilters_ = GL_NEAREST;
		break;
	default:
		ret = false;
		qWarning() << "[ViewFinderGL]:"
			   << "format not supported.";
		break;
	};

	return ret;
}

void ViewFinderGL::selectColorSpace(const libcamera::ColorSpace &colorSpace)
{
	std::array<double, 9> yuv2rgb;

	/* OpenGL stores arrays in column-major order. */
	switch (colorSpace.ycbcrEncoding) {
	case libcamera::ColorSpace::YcbcrEncoding::None:
	default:
		yuv2rgb = {
			1.0000,  0.0000,  0.0000,
			0.0000,  1.0000,  0.0000,
			0.0000,  0.0000,  1.0000,
		};
		break;

	case libcamera::ColorSpace::YcbcrEncoding::Rec601:
		yuv2rgb = {
			1.0000,  1.0000,  1.0000,
			0.0000, -0.3441,  1.7720,
			1.4020, -0.7141,  0.0000,
		};
		break;

	case libcamera::ColorSpace::YcbcrEncoding::Rec709:
		yuv2rgb = {
			1.0000,  1.0000,  1.0000,
			0.0000, -0.1873,  1.8856,
			1.5748, -0.4681,  0.0000,
		};
		break;

	case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
		yuv2rgb = {
			1.0000,  1.0000,  1.0000,
			0.0000, -0.1646,  1.8814,
			1.4746, -0.5714,  0.0000,
		};
		break;
	}

	double offset;

	switch (colorSpace.range) {
	case libcamera::ColorSpace::Range::Full:
	default:
		offset = 0.0;
		break;

	case libcamera::ColorSpace::Range::Limited:
		offset = 16.0;

		for (unsigned int i = 0; i < 3; ++i)
			yuv2rgb[i] *= 255.0 / 219.0;
		for (unsigned int i = 4; i < 9; ++i)
			yuv2rgb[i] *= 255.0 / 224.0;
		break;
	}

	QStringList matrix;

	for (double coeff : yuv2rgb)
		matrix.append(QString::number(coeff, 'f'));

	fragmentShaderDefines_.append("#define YUV2RGB_MATRIX " + matrix.join(", "));
	fragmentShaderDefines_.append(QString("#define YUV2RGB_Y_OFFSET %1")
		.arg(offset, 0, 'f', 1));
}

bool ViewFinderGL::createVertexShader()
{
	/* Create Vertex Shader */
	vertexShader_ = std::make_unique<QOpenGLShader>(QOpenGLShader::Vertex, this);

	/* Compile the vertex shader */
	if (!vertexShader_->compileSourceFile(vertexShaderFile_)) {
		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>(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();
	}

	attributeVertex = shaderProgram_.attributeLocation("vertexIn");
	attributeTexture = shaderProgram_.attributeLocation("textureIn");

	vertexBuffer_.bind();

	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));

	vertexBuffer_.release();

	projMatrixUniform_ = shaderProgram_.uniformLocation("proj_matrix");
	textureUniformY_ = shaderProgram_.uniformLocation("tex_y");
	textureUniformU_ = shaderProgram_.uniformLocation("tex_u");
	textureUniformV_ = shaderProgram_.uniformLocation("tex_v");
	textureUniformStep_ = shaderProgram_.uniformLocation("tex_step");
	textureUniformSize_ = shaderProgram_.uniformLocation("tex_size");
	textureUniformStrideFactor_ = shaderProgram_.uniformLocation("stride_factor");
	textureUniformBayerFirstRed_ = shaderProgram_.uniformLocation("tex_bayer_first_red");

	/* Create the textures. */
	for (std::unique_ptr<QOpenGLTexture> &texture : textures_) {
		if (texture)
			continue;

		texture = std::make_unique<QOpenGLTexture>(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,
			textureMinMagFilters_);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
			textureMinMagFilters_);
	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);

	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.";
}

void ViewFinderGL::doRender()
{
	/* Stride of the first plane, in pixels. */
	unsigned int stridePixels;

	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_LUMINANCE,
			     stride_,
			     size_.height(),
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		/* Activate texture UV/VU */
		glActiveTexture(GL_TEXTURE1);
		configureTexture(*textures_[1]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE_ALPHA,
			     stride_ / horzSubSample_,
			     size_.height() / vertSubSample_,
			     0,
			     GL_LUMINANCE_ALPHA,
			     GL_UNSIGNED_BYTE,
			     image_->data(1).data());
		shaderProgram_.setUniformValue(textureUniformU_, 1);

		stridePixels = stride_;
		break;

	case libcamera::formats::YUV420:
		/* Activate texture Y */
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_,
			     size_.height(),
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		/* Activate texture U */
		glActiveTexture(GL_TEXTURE1);
		configureTexture(*textures_[1]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_ / horzSubSample_,
			     size_.height() / vertSubSample_,
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(1).data());
		shaderProgram_.setUniformValue(textureUniformU_, 1);

		/* Activate texture V */
		glActiveTexture(GL_TEXTURE2);
		configureTexture(*textures_[2]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_ / horzSubSample_,
			     size_.height() / vertSubSample_,
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(2).data());
		shaderProgram_.setUniformValue(textureUniformV_, 2);

		stridePixels = stride_;
		break;

	case libcamera::formats::YVU420:
		/* Activate texture Y */
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_,
			     size_.height(),
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		/* Activate texture V */
		glActiveTexture(GL_TEXTURE2);
		configureTexture(*textures_[2]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_ / horzSubSample_,
			     size_.height() / vertSubSample_,
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(1).data());
		shaderProgram_.setUniformValue(textureUniformV_, 2);

		/* Activate texture U */
		glActiveTexture(GL_TEXTURE1);
		configureTexture(*textures_[1]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_ / horzSubSample_,
			     size_.height() / vertSubSample_,
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(2).data());
		shaderProgram_.setUniformValue(textureUniformU_, 1);

		stridePixels = stride_;
		break;

	case libcamera::formats::UYVY:
	case libcamera::formats::VYUY:
	case libcamera::formats::YUYV:
	case libcamera::formats::YVYU:
		/*
		 * Packed YUV formats are stored in a RGBA texture to match the
		 * OpenGL texel size with the 4 bytes repeating pattern in YUV.
		 * The texture width is thus half of the image_ with.
		 */
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_RGBA,
			     stride_ / 4,
			     size_.height(),
			     0,
			     GL_RGBA,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		/*
		 * The shader needs the step between two texture pixels in the
		 * horizontal direction, expressed in texture coordinate units
		 * ([0, 1]). There are exactly width - 1 steps between the
		 * leftmost and rightmost texels.
		 */
		shaderProgram_.setUniformValue(textureUniformStep_,
					       1.0f / (size_.width() / 2 - 1),
					       1.0f /* not used */);

		stridePixels = stride_ / 2;
		break;

	case libcamera::formats::ABGR8888:
	case libcamera::formats::ARGB8888:
	case libcamera::formats::BGRA8888:
	case libcamera::formats::RGBA8888:
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_RGBA,
			     stride_ / 4,
			     size_.height(),
			     0,
			     GL_RGBA,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		stridePixels = stride_ / 4;
		break;

	case libcamera::formats::BGR888:
	case libcamera::formats::RGB888:
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_RGB,
			     stride_ / 3,
			     size_.height(),
			     0,
			     GL_RGB,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);

		stridePixels = stride_ / 3;
		break;

	case libcamera::formats::SBGGR8:
	case libcamera::formats::SGBRG8:
	case libcamera::formats::SGRBG8:
	case libcamera::formats::SRGGB8:
	case libcamera::formats::SBGGR10_CSI2P:
	case libcamera::formats::SGBRG10_CSI2P:
	case libcamera::formats::SGRBG10_CSI2P:
	case libcamera::formats::SRGGB10_CSI2P:
	case libcamera::formats::SBGGR12_CSI2P:
	case libcamera::formats::SGBRG12_CSI2P:
	case libcamera::formats::SGRBG12_CSI2P:
	case libcamera::formats::SRGGB12_CSI2P:
		/*
		 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
		 * are stored in a GL_LUMINANCE texture. The texture width is
		 * equal to the stride.
		 */
		glActiveTexture(GL_TEXTURE0);
		configureTexture(*textures_[0]);
		glTexImage2D(GL_TEXTURE_2D,
			     0,
			     GL_LUMINANCE,
			     stride_,
			     size_.height(),
			     0,
			     GL_LUMINANCE,
			     GL_UNSIGNED_BYTE,
			     image_->data(0).data());
		shaderProgram_.setUniformValue(textureUniformY_, 0);
		shaderProgram_.setUniformValue(textureUniformBayerFirstRed_,
					       firstRed_);
		shaderProgram_.setUniformValue(textureUniformSize_,
					       size_.width(), /* in pixels */
					       size_.height());
		shaderProgram_.setUniformValue(textureUniformStep_,
					       1.0f / (stride_ - 1),
					       1.0f / (size_.height() - 1));

		/*
		 * The stride is already taken into account in the shaders, set
		 * the generic stride factor to 1.0.
		 */
		stridePixels = size_.width();
		break;

	default:
		stridePixels = size_.width();
		break;
	};

	/*
	 * Compute the stride factor for the vertex shader, to map the
	 * horizontal texture coordinate range [0.0, 1.0] to the active portion
	 * of the image.
	 */
	shaderProgram_.setUniformValue(textureUniformStrideFactor_,
				       static_cast<float>(size_.width() - 1) /
				       (stridePixels - 1));

	/*
	 * Place the viewfinder in the centre of the widget, preserving the
	 * aspect ratio of the image.
	 */
	QMatrix4x4 projMatrix;
	QSizeF scaledSize = size_.scaled(size(), Qt::KeepAspectRatio);
	projMatrix.scale(scaledSize.width() / size().width(),
			 scaledSize.height() / size().height());

	shaderProgram_.setUniformValue(projMatrixUniform_, projMatrix);
}

void ViewFinderGL::paintGL()
{
	if (!fragmentShader_) {
		if (!createFragmentShader()) {
			qWarning() << "[ViewFinderGL]:"
				   << "create fragment shader failed.";
		}
	}

	/* Bind shader pipeline for use. */
	if (!shaderProgram_.bind()) {
		qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
		close();
	}

	if (!image_)
		return;

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	doRender();
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

QSize ViewFinderGL::sizeHint() const
{
	return size_.isValid() ? size_ : QSize(640, 480);
}
