| Message ID | 20260618122245.946138-7-bryan.odonoghue@linaro.org |
|---|---|
| State | RFC |
| Headers | show |
| Series |
|
| Related | show |
Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes: > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > .../gpu_pipeline_shader_pass_demosiac.cpp | 357 ++++++++++++++++++ 1,$s/demosiac/demosaic/g > .../gpu_pipeline_shader_pass_demosiac.h | 72 ++++ > src/libcamera/software_isp/meson.build | 1 + > 3 files changed, 430 insertions(+) > create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > > diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > new file mode 100644 > index 000000000..02d9da2f0 > --- /dev/null > +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > @@ -0,0 +1,357 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Linaro Ltd > + * > + * GPU ISP Demosiac pass > + */ > + > +#include <cmath> > +#include <stdint.h> > +#include <sys/mman.h> > +#include <sys/types.h> > +#include <unistd.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/thread.h> > +#include <libcamera/base/utils.h> > + > +#include <libcamera/controls.h> > +#include <libcamera/formats.h> > +#include <libcamera/stream.h> > + > +#include "libcamera/internal/bayer_format.h" > +#include "libcamera/internal/framebuffer.h" > +#include "libcamera/internal/ipa_manager.h" > +#include "libcamera/internal/software_isp/debayer_params.h" > + > +#include "gpu_pipeline_shader_pass_demosiac.h" > + > +/** > + * \file software_isp.cpp > + * \brief Simple software ISP implementation > + */ > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(GpuShaderDemosiac) > + > +int GpuIspShaderPassDemosiac::start() > +{ > + return 0; > +} The same as in the inherited method? > + > +void GpuIspShaderPassDemosiac::stop() > +{ > +} No need to delete the program here? > + > +int GpuIspShaderPassDemosiac::initShaders(PixelFormat inputFormat, PixelFormat outputFormat) > +{ > + std::vector<std::string> shaderEnv; > + unsigned int fragmentShaderDataLen = 0; > + const unsigned char *fragmentShaderData = 0; > + unsigned int vertexShaderDataLen = 0; > + const unsigned char *vertexShaderData = 0; > + GLenum err; > + > + /* Target gles 100 glsl requires "#version x" as first directive in shader */ > + egl_.pushEnv(shaderEnv, "#version 100"); > + > + /* Specify GL_OES_EGL_image_external */ > + egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable"); > + > + /* > + * Tell shaders how to re-order output taking account of how the pixels > + * are actually stored by EGL. > + */ > + switch (outputFormat) { > + case formats::ARGB8888: > + case formats::XRGB8888: > + break; > + case formats::ABGR8888: > + case formats::XBGR8888: > + egl_.pushEnv(shaderEnv, "#define SWAP_BLUE"); > + break; > + default: > + LOG(GpuShaderDemosiac, Error) << "Unsupported output format"; > + return -EINVAL; > + } > + > + /* Pixel location parameters */ > + glFormat_ = GL_LUMINANCE; > + bytesPerPixel_ = 1; > + shaderStridePixels_ = passInputCfg_.stride; > + > + switch (inputFormat) { > + case libcamera::formats::SBGGR8: > + case libcamera::formats::SBGGR10_CSI2P: > + case libcamera::formats::SBGGR12_CSI2P: > + firstRed_x_ = 1.0; > + firstRed_y_ = 1.0; > + break; > + case libcamera::formats::SGBRG8: > + case libcamera::formats::SGBRG10_CSI2P: > + case libcamera::formats::SGBRG12_CSI2P: > + firstRed_x_ = 0.0; > + firstRed_y_ = 1.0; > + break; > + case libcamera::formats::SGRBG8: > + case libcamera::formats::SGRBG10_CSI2P: > + case libcamera::formats::SGRBG12_CSI2P: > + firstRed_x_ = 1.0; > + firstRed_y_ = 0.0; > + break; > + case libcamera::formats::SRGGB8: > + case libcamera::formats::SRGGB10_CSI2P: > + case libcamera::formats::SRGGB12_CSI2P: > + firstRed_x_ = 0.0; > + firstRed_y_ = 0.0; > + break; > + default: > + LOG(GpuShaderDemosiac, Error) << "Unsupported input format"; > + return -EINVAL; > + }; > + > + /* Shader selection */ > + switch (inputFormat) { > + case libcamera::formats::SBGGR8: > + case libcamera::formats::SGBRG8: > + case libcamera::formats::SGRBG8: > + case libcamera::formats::SRGGB8: > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + break; > + case libcamera::formats::SBGGR10_CSI2P: > + case libcamera::formats::SGBRG10_CSI2P: > + case libcamera::formats::SGRBG10_CSI2P: > + case libcamera::formats::SRGGB10_CSI2P: > + egl_.pushEnv(shaderEnv, "#define RAW10P"); > + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + glFormat_ = GL_RG; > + bytesPerPixel_ = 2; > + } else { > + fragmentShaderData = bayer_1x_packed_frag; > + fragmentShaderDataLen = bayer_1x_packed_frag_len; > + vertexShaderData = identity_vert; > + vertexShaderDataLen = identity_vert_len; > + shaderStridePixels_ = passInputCfg_.size.width; > + } > + break; > + case libcamera::formats::SBGGR12_CSI2P: > + case libcamera::formats::SGBRG12_CSI2P: > + case libcamera::formats::SGRBG12_CSI2P: > + case libcamera::formats::SRGGB12_CSI2P: > + egl_.pushEnv(shaderEnv, "#define RAW12P"); > + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + glFormat_ = GL_RG; > + bytesPerPixel_ = 2; > + } else { > + fragmentShaderData = bayer_1x_packed_frag; > + fragmentShaderDataLen = bayer_1x_packed_frag_len; > + vertexShaderData = identity_vert; > + vertexShaderDataLen = identity_vert_len; > + shaderStridePixels_ = passInputCfg_.size.width; > + } > + break; > + }; > + > + /* TODO: move from here to the end of the method into a helper function in the base class > + * this logic will be common to all pipeline instances > + */ > + if (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv)) { > + LOG(GpuShaderDemosiac, Error) << "Compile vertex shader fail"; > + return -ENODEV; > + } > + utils::scope_exit vShaderGuard([&] { glDeleteShader(vertexShaderId_); }); > + > + if (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv)) { > + LOG(GpuShaderDemosiac, Error) << "Compile fragment shader fail"; > + return -ENODEV; > + } > + utils::scope_exit fShaderGuard([&] { glDeleteShader(fragmentShaderId_); }); > + > + if (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_)) { > + LOG(GpuShaderDemosiac, Error) << "Linking program fail"; > + return -ENODEV; > + } > + > + egl_.dumpShaderSource(vertexShaderId_); > + egl_.dumpShaderSource(fragmentShaderId_); > + > + /* Ensure we set the programId_ */ > + egl_.useProgram(programId_); > + err = glGetError(); > + if (err != GL_NO_ERROR) { > + LOG(GpuShaderDemosiac, Error) << "Use program error " << err; > + return -ENODEV; > + } > + > + return getShaderVariableLocations(); > +} > + > +int GpuIspShaderPassDemosiac::getShaderVariableLocations(void) s/void// > +{ > + attributeVertex_ = glGetAttribLocation(programId_, "vertexIn"); > + attributeTexture_ = glGetAttribLocation(programId_, "textureIn"); > + > + textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y"); > + ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm"); > + blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel"); > + gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma"); > + contrastExpUniformDataIn_ = glGetUniformLocation(programId_, "contrastExp"); > + > + textureUniformStep_ = glGetUniformLocation(programId_, "tex_step"); > + textureUniformSize_ = glGetUniformLocation(programId_, "tex_size"); > + textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor"); > + textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red"); > + textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix"); > + > + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ > + << " tex_y " << textureUniformBayerDataIn_ > + << " ccm " << ccmUniformDataIn_ > + << " blacklevel " << blackLevelUniformDataIn_ > + << " gamma " << gammaUniformDataIn_ > + << " contrastExp " << contrastExpUniformDataIn_ > + << " tex_step " << textureUniformStep_ > + << " tex_size " << textureUniformSize_ > + << " stride_factor " << textureUniformStrideFactor_ > + << " tex_bayer_first_red " << textureUniformBayerFirstRed_ > + << " proj_matrix " << textureUniformProjMatrix_; > + > + /* TODO: trap errors */ > + return 0; > +} > + > +void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn) > +{ > + /* > + * 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. > + */ > + GLfloat firstRed[] = { firstRed_x_, firstRed_y_ }; > + GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width, > + (GLfloat)passInputCfg_.size.height }; > + GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1), > + 1.0f / (passInputCfg_.size.height - 1) }; > + GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_); > + /* > + * Scale input to output size, keeping the aspect ratio and preferring > + * cropping over black bars. > + */ > + GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width, > + (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height); > + GLfloat trans = -(1.0f - scale); > + GLfloat projMatrix[] = { > + scale, 0, 0, 0, > + 0, scale, 0, 0, > + 0, 0, 1, 0, > + trans, trans, 0, 1 > + }; > + /* Static const coordinates */ > + static const GLfloat vcoordinates[4][2] = { > + { -1.0f, -1.0f }, > + { -1.0f, +1.0f }, > + { +1.0f, +1.0f }, > + { +1.0f, -1.0f }, > + }; > + static const GLfloat tcoordinates[4][2] = { > + { 0.0f, 0.0f }, > + { 0.0f, 1.0f }, > + { 1.0f, 1.0f }, > + { 1.0f, 0.0f }, > + }; > + > + /* vertexIn - bayer_8.vert */ > + glEnableVertexAttribArray(attributeVertex_); > + glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE, > + 2 * sizeof(GLfloat), vcoordinates); > + > + /* textureIn - bayer_8.vert */ > + glEnableVertexAttribArray(attributeTexture_); > + glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE, > + 2 * sizeof(GLfloat), tcoordinates); > + > + /* > + * Set the sampler2D to the respective texture unit for each texutre > + * To simultaneously sample multiple textures we need to use multiple > + * texture units > + */ > + glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_); > + > + /* > + * These values are: > + * firstRed = tex_bayer_first_red - bayer_8.vert > + * imgSize = tex_size - bayer_8.vert > + * step = tex_step - bayer_8.vert > + * Stride = stride_factor identity.vert > + * textureUniformProjMatri = No scaling > + */ > + glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed); > + glUniform2fv(textureUniformSize_, 1, imgSize); > + glUniform2fv(textureUniformStep_, 1, Step); > + glUniform1f(textureUniformStrideFactor_, Stride); > + glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix); > + > + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ > + << " tex_y " << textureUniformBayerDataIn_ > + << " tex_step " << textureUniformStep_ > + << " tex_size " << textureUniformSize_ > + << " stride_factor " << textureUniformStrideFactor_ > + << " tex_bayer_first_red " << textureUniformBayerFirstRed_; > + > + LOG(GpuShaderDemosiac, Debug) << "textureUniformY_ = 0 " > + << " firstRed.x " << firstRed[0] > + << " firstRed.y " << firstRed[1] > + << " textureUniformSize_.width " << imgSize[0] > + << " textureUniformSize_.height " << imgSize[1] > + << " textureUniformStep_.x " << Step[0] > + << " textureUniformStep_.y " << Step[1] > + << " textureUniformStrideFactor_ " << Stride > + << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; > + > + GLfloat ccm[9] = { > + params.combinedMatrix[0][0], > + params.combinedMatrix[0][1], > + params.combinedMatrix[0][2], > + params.combinedMatrix[1][0], > + params.combinedMatrix[1][1], > + params.combinedMatrix[1][2], > + params.combinedMatrix[2][0], > + params.combinedMatrix[2][1], > + params.combinedMatrix[2][2], > + }; > + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); > + LOG(GpuShaderDemosiac, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; > + > + /* > + * 0 = Red, 1 = Green, 2 = Blue > + */ > + glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]); > + LOG(GpuShaderDemosiac, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel; > + > + /* > + * Gamma > + */ > + glUniform1f(gammaUniformDataIn_, params.gamma); > + LOG(GpuShaderDemosiac, Debug) << " gammaUniformDataIn_ " << gammaUniformDataIn_ << " data " << params.gamma; > + > + /* > + * Contrast > + */ > + glUniform1f(contrastExpUniformDataIn_, params.contrastExp); > + LOG(GpuShaderDemosiac, Debug) << " contrastExpUniformDataIn_ " << contrastExpUniformDataIn_ << " data " << params.contrastExp; > + > + return; > +} > + > +} > diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > new file mode 100644 > index 000000000..11bb04c30 > --- /dev/null > +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > @@ -0,0 +1,72 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Linaro Ltd > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * GpuIspIspShaderPass base class > + */ > + > +#pragma once > + > +#include <stdint.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/object.h> > +#include <libcamera/base/signal.h> > + > +#include <libcamera/geometry.h> > +#include <libcamera/stream.h> > + > +#include "libcamera/internal/egl.h" > +#include "libcamera/internal/software_isp/debayer_params.h" > + > +#include "gpu_pipeline_shader_pass.h" > + > +namespace libcamera { > + > +class FrameBuffer; > + > +class GpuIspShaderPassDemosiac : public GpuIspShaderPass > +{ > +public: > + GpuIspShaderPassDemosiac(eGL& egl) : GpuIspShaderPass(egl) {}; > + > + int start(); > + void stop(); > + > + /* Things that every ISP pipeline pass will need to do */ > + int initShaders(PixelFormat inputFormat, PixelFormat outputFormat); > + int getShaderVariableLocations(void); > + void setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn); > + const char *name() const override { return "GpuIspShaderPassDemosiac"; } I think `override' should be used on all the redefined methods? > +private: > + /* Shader parameters */ > + float firstRed_x_; > + float firstRed_y_; > + GLint attributeVertex_; > + GLint attributeTexture_; > + GLint textureUniformStep_; > + GLint textureUniformSize_; > + GLint textureUniformStrideFactor_; > + GLint textureUniformBayerFirstRed_; > + GLint textureUniformProjMatrix_; > + GLint textureUniformBayerDataIn_; > + > + uint32_t shaderStridePixels_; > + > + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ > + GLint ccmUniformDataIn_; > + > + /* Black Level compensation */ > + GLint blackLevelUniformDataIn_; > + > + /* Gamma */ > + GLint gammaUniformDataIn_; > + > + /* Contrast */ > + GLint contrastExpUniformDataIn_; > +}; > + > +} /* namespace libcamera */ > diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build > index f8bce5203..3e7d21318 100644 > --- a/src/libcamera/software_isp/meson.build > +++ b/src/libcamera/software_isp/meson.build > @@ -33,6 +33,7 @@ if mesa_works > '../egl.cpp', > 'software_isp_pipeline_gpu.cpp', > 'gpu_pipeline_shader_pass.cpp', > + 'gpu_pipeline_shader_pass_demosiac.cpp', > ]) > libcamera_deps += [ > libegl,
Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes: > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > .../gpu_pipeline_shader_pass_demosiac.cpp | 357 ++++++++++++++++++ > .../gpu_pipeline_shader_pass_demosiac.h | 72 ++++ > src/libcamera/software_isp/meson.build | 1 + > 3 files changed, 430 insertions(+) > create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > > diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > new file mode 100644 > index 000000000..02d9da2f0 > --- /dev/null > +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp > @@ -0,0 +1,357 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Linaro Ltd > + * > + * GPU ISP Demosiac pass > + */ > + > +#include <cmath> > +#include <stdint.h> > +#include <sys/mman.h> > +#include <sys/types.h> > +#include <unistd.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/thread.h> > +#include <libcamera/base/utils.h> > + > +#include <libcamera/controls.h> > +#include <libcamera/formats.h> > +#include <libcamera/stream.h> > + > +#include "libcamera/internal/bayer_format.h" > +#include "libcamera/internal/framebuffer.h" > +#include "libcamera/internal/ipa_manager.h" > +#include "libcamera/internal/software_isp/debayer_params.h" > + > +#include "gpu_pipeline_shader_pass_demosiac.h" > + > +/** > + * \file software_isp.cpp > + * \brief Simple software ISP implementation > + */ > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(GpuShaderDemosiac) > + > +int GpuIspShaderPassDemosiac::start() > +{ > + return 0; > +} > + > +void GpuIspShaderPassDemosiac::stop() > +{ > +} > + > +int GpuIspShaderPassDemosiac::initShaders(PixelFormat inputFormat, PixelFormat outputFormat) > +{ > + std::vector<std::string> shaderEnv; > + unsigned int fragmentShaderDataLen = 0; > + const unsigned char *fragmentShaderData = 0; > + unsigned int vertexShaderDataLen = 0; > + const unsigned char *vertexShaderData = 0; > + GLenum err; > + > + /* Target gles 100 glsl requires "#version x" as first directive in shader */ > + egl_.pushEnv(shaderEnv, "#version 100"); > + > + /* Specify GL_OES_EGL_image_external */ > + egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable"); > + > + /* > + * Tell shaders how to re-order output taking account of how the pixels > + * are actually stored by EGL. > + */ > + switch (outputFormat) { > + case formats::ARGB8888: > + case formats::XRGB8888: > + break; > + case formats::ABGR8888: > + case formats::XBGR8888: > + egl_.pushEnv(shaderEnv, "#define SWAP_BLUE"); > + break; > + default: > + LOG(GpuShaderDemosiac, Error) << "Unsupported output format"; > + return -EINVAL; > + } > + > + /* Pixel location parameters */ > + glFormat_ = GL_LUMINANCE; > + bytesPerPixel_ = 1; > + shaderStridePixels_ = passInputCfg_.stride; > + > + switch (inputFormat) { > + case libcamera::formats::SBGGR8: > + case libcamera::formats::SBGGR10_CSI2P: > + case libcamera::formats::SBGGR12_CSI2P: > + firstRed_x_ = 1.0; > + firstRed_y_ = 1.0; > + break; > + case libcamera::formats::SGBRG8: > + case libcamera::formats::SGBRG10_CSI2P: > + case libcamera::formats::SGBRG12_CSI2P: > + firstRed_x_ = 0.0; > + firstRed_y_ = 1.0; > + break; > + case libcamera::formats::SGRBG8: > + case libcamera::formats::SGRBG10_CSI2P: > + case libcamera::formats::SGRBG12_CSI2P: > + firstRed_x_ = 1.0; > + firstRed_y_ = 0.0; > + break; > + case libcamera::formats::SRGGB8: > + case libcamera::formats::SRGGB10_CSI2P: > + case libcamera::formats::SRGGB12_CSI2P: > + firstRed_x_ = 0.0; > + firstRed_y_ = 0.0; > + break; > + default: > + LOG(GpuShaderDemosiac, Error) << "Unsupported input format"; > + return -EINVAL; > + }; > + > + /* Shader selection */ > + switch (inputFormat) { > + case libcamera::formats::SBGGR8: > + case libcamera::formats::SGBRG8: > + case libcamera::formats::SGRBG8: > + case libcamera::formats::SRGGB8: > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + break; > + case libcamera::formats::SBGGR10_CSI2P: > + case libcamera::formats::SGBRG10_CSI2P: > + case libcamera::formats::SGRBG10_CSI2P: > + case libcamera::formats::SRGGB10_CSI2P: > + egl_.pushEnv(shaderEnv, "#define RAW10P"); > + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + glFormat_ = GL_RG; > + bytesPerPixel_ = 2; Once demosaicing becomes separated as the second pass, the input format is the same for both packed and unpacked. These settings slip into the final version of the series, resulting in broken output for unpacked encoding. > + } else { > + fragmentShaderData = bayer_1x_packed_frag; > + fragmentShaderDataLen = bayer_1x_packed_frag_len; > + vertexShaderData = identity_vert; > + vertexShaderDataLen = identity_vert_len; > + shaderStridePixels_ = passInputCfg_.size.width; > + } > + break; > + case libcamera::formats::SBGGR12_CSI2P: > + case libcamera::formats::SGBRG12_CSI2P: > + case libcamera::formats::SGRBG12_CSI2P: > + case libcamera::formats::SRGGB12_CSI2P: > + egl_.pushEnv(shaderEnv, "#define RAW12P"); > + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { > + fragmentShaderData = bayer_unpacked_frag; > + fragmentShaderDataLen = bayer_unpacked_frag_len; > + vertexShaderData = bayer_unpacked_vert; > + vertexShaderDataLen = bayer_unpacked_vert_len; > + glFormat_ = GL_RG; > + bytesPerPixel_ = 2; The same here. > + } else { > + fragmentShaderData = bayer_1x_packed_frag; > + fragmentShaderDataLen = bayer_1x_packed_frag_len; > + vertexShaderData = identity_vert; > + vertexShaderDataLen = identity_vert_len; > + shaderStridePixels_ = passInputCfg_.size.width; > + } > + break; > + }; > + > + /* TODO: move from here to the end of the method into a helper function in the base class > + * this logic will be common to all pipeline instances > + */ > + if (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv)) { > + LOG(GpuShaderDemosiac, Error) << "Compile vertex shader fail"; > + return -ENODEV; > + } > + utils::scope_exit vShaderGuard([&] { glDeleteShader(vertexShaderId_); }); > + > + if (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv)) { > + LOG(GpuShaderDemosiac, Error) << "Compile fragment shader fail"; > + return -ENODEV; > + } > + utils::scope_exit fShaderGuard([&] { glDeleteShader(fragmentShaderId_); }); > + > + if (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_)) { > + LOG(GpuShaderDemosiac, Error) << "Linking program fail"; > + return -ENODEV; > + } > + > + egl_.dumpShaderSource(vertexShaderId_); > + egl_.dumpShaderSource(fragmentShaderId_); > + > + /* Ensure we set the programId_ */ > + egl_.useProgram(programId_); > + err = glGetError(); > + if (err != GL_NO_ERROR) { > + LOG(GpuShaderDemosiac, Error) << "Use program error " << err; > + return -ENODEV; > + } > + > + return getShaderVariableLocations(); > +} > + > +int GpuIspShaderPassDemosiac::getShaderVariableLocations(void) > +{ > + attributeVertex_ = glGetAttribLocation(programId_, "vertexIn"); > + attributeTexture_ = glGetAttribLocation(programId_, "textureIn"); > + > + textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y"); > + ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm"); > + blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel"); > + gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma"); > + contrastExpUniformDataIn_ = glGetUniformLocation(programId_, "contrastExp"); > + > + textureUniformStep_ = glGetUniformLocation(programId_, "tex_step"); > + textureUniformSize_ = glGetUniformLocation(programId_, "tex_size"); > + textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor"); > + textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red"); > + textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix"); > + > + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ > + << " tex_y " << textureUniformBayerDataIn_ > + << " ccm " << ccmUniformDataIn_ > + << " blacklevel " << blackLevelUniformDataIn_ > + << " gamma " << gammaUniformDataIn_ > + << " contrastExp " << contrastExpUniformDataIn_ > + << " tex_step " << textureUniformStep_ > + << " tex_size " << textureUniformSize_ > + << " stride_factor " << textureUniformStrideFactor_ > + << " tex_bayer_first_red " << textureUniformBayerFirstRed_ > + << " proj_matrix " << textureUniformProjMatrix_; > + > + /* TODO: trap errors */ > + return 0; > +} > + > +void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn) > +{ > + /* > + * 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. > + */ > + GLfloat firstRed[] = { firstRed_x_, firstRed_y_ }; > + GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width, > + (GLfloat)passInputCfg_.size.height }; > + GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1), > + 1.0f / (passInputCfg_.size.height - 1) }; > + GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_); > + /* > + * Scale input to output size, keeping the aspect ratio and preferring > + * cropping over black bars. > + */ > + GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width, > + (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height); > + GLfloat trans = -(1.0f - scale); > + GLfloat projMatrix[] = { > + scale, 0, 0, 0, > + 0, scale, 0, 0, > + 0, 0, 1, 0, > + trans, trans, 0, 1 > + }; > + /* Static const coordinates */ > + static const GLfloat vcoordinates[4][2] = { > + { -1.0f, -1.0f }, > + { -1.0f, +1.0f }, > + { +1.0f, +1.0f }, > + { +1.0f, -1.0f }, > + }; > + static const GLfloat tcoordinates[4][2] = { > + { 0.0f, 0.0f }, > + { 0.0f, 1.0f }, > + { 1.0f, 1.0f }, > + { 1.0f, 0.0f }, > + }; > + > + /* vertexIn - bayer_8.vert */ > + glEnableVertexAttribArray(attributeVertex_); > + glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE, > + 2 * sizeof(GLfloat), vcoordinates); > + > + /* textureIn - bayer_8.vert */ > + glEnableVertexAttribArray(attributeTexture_); > + glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE, > + 2 * sizeof(GLfloat), tcoordinates); > + > + /* > + * Set the sampler2D to the respective texture unit for each texutre > + * To simultaneously sample multiple textures we need to use multiple > + * texture units > + */ > + glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_); > + > + /* > + * These values are: > + * firstRed = tex_bayer_first_red - bayer_8.vert > + * imgSize = tex_size - bayer_8.vert > + * step = tex_step - bayer_8.vert > + * Stride = stride_factor identity.vert > + * textureUniformProjMatri = No scaling > + */ > + glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed); > + glUniform2fv(textureUniformSize_, 1, imgSize); > + glUniform2fv(textureUniformStep_, 1, Step); > + glUniform1f(textureUniformStrideFactor_, Stride); > + glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix); > + > + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ > + << " tex_y " << textureUniformBayerDataIn_ > + << " tex_step " << textureUniformStep_ > + << " tex_size " << textureUniformSize_ > + << " stride_factor " << textureUniformStrideFactor_ > + << " tex_bayer_first_red " << textureUniformBayerFirstRed_; > + > + LOG(GpuShaderDemosiac, Debug) << "textureUniformY_ = 0 " > + << " firstRed.x " << firstRed[0] > + << " firstRed.y " << firstRed[1] > + << " textureUniformSize_.width " << imgSize[0] > + << " textureUniformSize_.height " << imgSize[1] > + << " textureUniformStep_.x " << Step[0] > + << " textureUniformStep_.y " << Step[1] > + << " textureUniformStrideFactor_ " << Stride > + << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; > + > + GLfloat ccm[9] = { > + params.combinedMatrix[0][0], > + params.combinedMatrix[0][1], > + params.combinedMatrix[0][2], > + params.combinedMatrix[1][0], > + params.combinedMatrix[1][1], > + params.combinedMatrix[1][2], > + params.combinedMatrix[2][0], > + params.combinedMatrix[2][1], > + params.combinedMatrix[2][2], > + }; > + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); > + LOG(GpuShaderDemosiac, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; > + > + /* > + * 0 = Red, 1 = Green, 2 = Blue > + */ > + glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]); > + LOG(GpuShaderDemosiac, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel; > + > + /* > + * Gamma > + */ > + glUniform1f(gammaUniformDataIn_, params.gamma); > + LOG(GpuShaderDemosiac, Debug) << " gammaUniformDataIn_ " << gammaUniformDataIn_ << " data " << params.gamma; > + > + /* > + * Contrast > + */ > + glUniform1f(contrastExpUniformDataIn_, params.contrastExp); > + LOG(GpuShaderDemosiac, Debug) << " contrastExpUniformDataIn_ " << contrastExpUniformDataIn_ << " data " << params.contrastExp; > + > + return; > +} > + > +} > diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > new file mode 100644 > index 000000000..11bb04c30 > --- /dev/null > +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h > @@ -0,0 +1,72 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Linaro Ltd > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * GpuIspIspShaderPass base class > + */ > + > +#pragma once > + > +#include <stdint.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/object.h> > +#include <libcamera/base/signal.h> > + > +#include <libcamera/geometry.h> > +#include <libcamera/stream.h> > + > +#include "libcamera/internal/egl.h" > +#include "libcamera/internal/software_isp/debayer_params.h" > + > +#include "gpu_pipeline_shader_pass.h" > + > +namespace libcamera { > + > +class FrameBuffer; > + > +class GpuIspShaderPassDemosiac : public GpuIspShaderPass > +{ > +public: > + GpuIspShaderPassDemosiac(eGL& egl) : GpuIspShaderPass(egl) {}; > + > + int start(); > + void stop(); > + > + /* Things that every ISP pipeline pass will need to do */ > + int initShaders(PixelFormat inputFormat, PixelFormat outputFormat); > + int getShaderVariableLocations(void); > + void setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn); > + const char *name() const override { return "GpuIspShaderPassDemosiac"; } > +private: > + /* Shader parameters */ > + float firstRed_x_; > + float firstRed_y_; > + GLint attributeVertex_; > + GLint attributeTexture_; > + GLint textureUniformStep_; > + GLint textureUniformSize_; > + GLint textureUniformStrideFactor_; > + GLint textureUniformBayerFirstRed_; > + GLint textureUniformProjMatrix_; > + GLint textureUniformBayerDataIn_; > + > + uint32_t shaderStridePixels_; > + > + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ > + GLint ccmUniformDataIn_; > + > + /* Black Level compensation */ > + GLint blackLevelUniformDataIn_; > + > + /* Gamma */ > + GLint gammaUniformDataIn_; > + > + /* Contrast */ > + GLint contrastExpUniformDataIn_; > +}; > + > +} /* namespace libcamera */ > diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build > index f8bce5203..3e7d21318 100644 > --- a/src/libcamera/software_isp/meson.build > +++ b/src/libcamera/software_isp/meson.build > @@ -33,6 +33,7 @@ if mesa_works > '../egl.cpp', > 'software_isp_pipeline_gpu.cpp', > 'gpu_pipeline_shader_pass.cpp', > + 'gpu_pipeline_shader_pass_demosiac.cpp', > ]) > libcamera_deps += [ > libegl,
On 25/06/2026 20:22, Milan Zamazal wrote: >> + vertexShaderData = bayer_unpacked_vert; >> + vertexShaderDataLen = bayer_unpacked_vert_len; >> + glFormat_ = GL_RG; >> + bytesPerPixel_ = 2; > Once demosaicing becomes separated as the second pass, the input format > is the same for both packed and unpacked. These settings slip into the > final version of the series, resulting in broken output for unpacked > encoding. > >> + } else { >> + fragmentShaderData = bayer_1x_packed_frag; >> + fragmentShaderDataLen = bayer_1x_packed_frag_len; >> + vertexShaderData = identity_vert; >> + vertexShaderDataLen = identity_vert_len; >> + shaderStridePixels_ = passInputCfg_.size.width; >> + } >> + break; >> + case libcamera::formats::SBGGR12_CSI2P: >> + case libcamera::formats::SGBRG12_CSI2P: >> + case libcamera::formats::SGRBG12_CSI2P: >> + case libcamera::formats::SRGGB12_CSI2P: >> + egl_.pushEnv(shaderEnv, "#define RAW12P"); >> + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { >> + fragmentShaderData = bayer_unpacked_frag; >> + fragmentShaderDataLen = bayer_unpacked_frag_len; >> + vertexShaderData = bayer_unpacked_vert; >> + vertexShaderDataLen = bayer_unpacked_vert_len; >> + glFormat_ = GL_RG; >> + bytesPerPixel_ = 2; > The same here. Ah yes thank you. I couldn't test this change on my hardware - not surprised to find it failed. --- bod
diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp new file mode 100644 index 000000000..02d9da2f0 --- /dev/null +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp @@ -0,0 +1,357 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Linaro Ltd + * + * GPU ISP Demosiac pass + */ + +#include <cmath> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/utils.h> + +#include <libcamera/controls.h> +#include <libcamera/formats.h> +#include <libcamera/stream.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/software_isp/debayer_params.h" + +#include "gpu_pipeline_shader_pass_demosiac.h" + +/** + * \file software_isp.cpp + * \brief Simple software ISP implementation + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(GpuShaderDemosiac) + +int GpuIspShaderPassDemosiac::start() +{ + return 0; +} + +void GpuIspShaderPassDemosiac::stop() +{ +} + +int GpuIspShaderPassDemosiac::initShaders(PixelFormat inputFormat, PixelFormat outputFormat) +{ + std::vector<std::string> shaderEnv; + unsigned int fragmentShaderDataLen = 0; + const unsigned char *fragmentShaderData = 0; + unsigned int vertexShaderDataLen = 0; + const unsigned char *vertexShaderData = 0; + GLenum err; + + /* Target gles 100 glsl requires "#version x" as first directive in shader */ + egl_.pushEnv(shaderEnv, "#version 100"); + + /* Specify GL_OES_EGL_image_external */ + egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable"); + + /* + * Tell shaders how to re-order output taking account of how the pixels + * are actually stored by EGL. + */ + switch (outputFormat) { + case formats::ARGB8888: + case formats::XRGB8888: + break; + case formats::ABGR8888: + case formats::XBGR8888: + egl_.pushEnv(shaderEnv, "#define SWAP_BLUE"); + break; + default: + LOG(GpuShaderDemosiac, Error) << "Unsupported output format"; + return -EINVAL; + } + + /* Pixel location parameters */ + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = passInputCfg_.stride; + + switch (inputFormat) { + case libcamera::formats::SBGGR8: + case libcamera::formats::SBGGR10_CSI2P: + case libcamera::formats::SBGGR12_CSI2P: + firstRed_x_ = 1.0; + firstRed_y_ = 1.0; + break; + case libcamera::formats::SGBRG8: + case libcamera::formats::SGBRG10_CSI2P: + case libcamera::formats::SGBRG12_CSI2P: + firstRed_x_ = 0.0; + firstRed_y_ = 1.0; + break; + case libcamera::formats::SGRBG8: + case libcamera::formats::SGRBG10_CSI2P: + case libcamera::formats::SGRBG12_CSI2P: + firstRed_x_ = 1.0; + firstRed_y_ = 0.0; + break; + case libcamera::formats::SRGGB8: + case libcamera::formats::SRGGB10_CSI2P: + case libcamera::formats::SRGGB12_CSI2P: + firstRed_x_ = 0.0; + firstRed_y_ = 0.0; + break; + default: + LOG(GpuShaderDemosiac, Error) << "Unsupported input format"; + return -EINVAL; + }; + + /* Shader selection */ + switch (inputFormat) { + case libcamera::formats::SBGGR8: + case libcamera::formats::SGBRG8: + case libcamera::formats::SGRBG8: + case libcamera::formats::SRGGB8: + fragmentShaderData = bayer_unpacked_frag; + fragmentShaderDataLen = bayer_unpacked_frag_len; + vertexShaderData = bayer_unpacked_vert; + vertexShaderDataLen = bayer_unpacked_vert_len; + break; + case libcamera::formats::SBGGR10_CSI2P: + case libcamera::formats::SGBRG10_CSI2P: + case libcamera::formats::SGRBG10_CSI2P: + case libcamera::formats::SRGGB10_CSI2P: + egl_.pushEnv(shaderEnv, "#define RAW10P"); + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { + fragmentShaderData = bayer_unpacked_frag; + fragmentShaderDataLen = bayer_unpacked_frag_len; + vertexShaderData = bayer_unpacked_vert; + vertexShaderDataLen = bayer_unpacked_vert_len; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + fragmentShaderData = bayer_1x_packed_frag; + fragmentShaderDataLen = bayer_1x_packed_frag_len; + vertexShaderData = identity_vert; + vertexShaderDataLen = identity_vert_len; + shaderStridePixels_ = passInputCfg_.size.width; + } + break; + case libcamera::formats::SBGGR12_CSI2P: + case libcamera::formats::SGBRG12_CSI2P: + case libcamera::formats::SGRBG12_CSI2P: + case libcamera::formats::SRGGB12_CSI2P: + egl_.pushEnv(shaderEnv, "#define RAW12P"); + if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) { + fragmentShaderData = bayer_unpacked_frag; + fragmentShaderDataLen = bayer_unpacked_frag_len; + vertexShaderData = bayer_unpacked_vert; + vertexShaderDataLen = bayer_unpacked_vert_len; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + fragmentShaderData = bayer_1x_packed_frag; + fragmentShaderDataLen = bayer_1x_packed_frag_len; + vertexShaderData = identity_vert; + vertexShaderDataLen = identity_vert_len; + shaderStridePixels_ = passInputCfg_.size.width; + } + break; + }; + + /* TODO: move from here to the end of the method into a helper function in the base class + * this logic will be common to all pipeline instances + */ + if (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv)) { + LOG(GpuShaderDemosiac, Error) << "Compile vertex shader fail"; + return -ENODEV; + } + utils::scope_exit vShaderGuard([&] { glDeleteShader(vertexShaderId_); }); + + if (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv)) { + LOG(GpuShaderDemosiac, Error) << "Compile fragment shader fail"; + return -ENODEV; + } + utils::scope_exit fShaderGuard([&] { glDeleteShader(fragmentShaderId_); }); + + if (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_)) { + LOG(GpuShaderDemosiac, Error) << "Linking program fail"; + return -ENODEV; + } + + egl_.dumpShaderSource(vertexShaderId_); + egl_.dumpShaderSource(fragmentShaderId_); + + /* Ensure we set the programId_ */ + egl_.useProgram(programId_); + err = glGetError(); + if (err != GL_NO_ERROR) { + LOG(GpuShaderDemosiac, Error) << "Use program error " << err; + return -ENODEV; + } + + return getShaderVariableLocations(); +} + +int GpuIspShaderPassDemosiac::getShaderVariableLocations(void) +{ + attributeVertex_ = glGetAttribLocation(programId_, "vertexIn"); + attributeTexture_ = glGetAttribLocation(programId_, "textureIn"); + + textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y"); + ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm"); + blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel"); + gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma"); + contrastExpUniformDataIn_ = glGetUniformLocation(programId_, "contrastExp"); + + textureUniformStep_ = glGetUniformLocation(programId_, "tex_step"); + textureUniformSize_ = glGetUniformLocation(programId_, "tex_size"); + textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor"); + textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red"); + textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix"); + + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ + << " tex_y " << textureUniformBayerDataIn_ + << " ccm " << ccmUniformDataIn_ + << " blacklevel " << blackLevelUniformDataIn_ + << " gamma " << gammaUniformDataIn_ + << " contrastExp " << contrastExpUniformDataIn_ + << " tex_step " << textureUniformStep_ + << " tex_size " << textureUniformSize_ + << " stride_factor " << textureUniformStrideFactor_ + << " tex_bayer_first_red " << textureUniformBayerFirstRed_ + << " proj_matrix " << textureUniformProjMatrix_; + + /* TODO: trap errors */ + return 0; +} + +void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn) +{ + /* + * 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. + */ + GLfloat firstRed[] = { firstRed_x_, firstRed_y_ }; + GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width, + (GLfloat)passInputCfg_.size.height }; + GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1), + 1.0f / (passInputCfg_.size.height - 1) }; + GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_); + /* + * Scale input to output size, keeping the aspect ratio and preferring + * cropping over black bars. + */ + GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width, + (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height); + GLfloat trans = -(1.0f - scale); + GLfloat projMatrix[] = { + scale, 0, 0, 0, + 0, scale, 0, 0, + 0, 0, 1, 0, + trans, trans, 0, 1 + }; + /* Static const coordinates */ + static const GLfloat vcoordinates[4][2] = { + { -1.0f, -1.0f }, + { -1.0f, +1.0f }, + { +1.0f, +1.0f }, + { +1.0f, -1.0f }, + }; + static const GLfloat tcoordinates[4][2] = { + { 0.0f, 0.0f }, + { 0.0f, 1.0f }, + { 1.0f, 1.0f }, + { 1.0f, 0.0f }, + }; + + /* vertexIn - bayer_8.vert */ + glEnableVertexAttribArray(attributeVertex_); + glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE, + 2 * sizeof(GLfloat), vcoordinates); + + /* textureIn - bayer_8.vert */ + glEnableVertexAttribArray(attributeTexture_); + glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE, + 2 * sizeof(GLfloat), tcoordinates); + + /* + * Set the sampler2D to the respective texture unit for each texutre + * To simultaneously sample multiple textures we need to use multiple + * texture units + */ + glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_); + + /* + * These values are: + * firstRed = tex_bayer_first_red - bayer_8.vert + * imgSize = tex_size - bayer_8.vert + * step = tex_step - bayer_8.vert + * Stride = stride_factor identity.vert + * textureUniformProjMatri = No scaling + */ + glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed); + glUniform2fv(textureUniformSize_, 1, imgSize); + glUniform2fv(textureUniformStep_, 1, Step); + glUniform1f(textureUniformStrideFactor_, Stride); + glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix); + + LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ + << " tex_y " << textureUniformBayerDataIn_ + << " tex_step " << textureUniformStep_ + << " tex_size " << textureUniformSize_ + << " stride_factor " << textureUniformStrideFactor_ + << " tex_bayer_first_red " << textureUniformBayerFirstRed_; + + LOG(GpuShaderDemosiac, Debug) << "textureUniformY_ = 0 " + << " firstRed.x " << firstRed[0] + << " firstRed.y " << firstRed[1] + << " textureUniformSize_.width " << imgSize[0] + << " textureUniformSize_.height " << imgSize[1] + << " textureUniformStep_.x " << Step[0] + << " textureUniformStep_.y " << Step[1] + << " textureUniformStrideFactor_ " << Stride + << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; + + GLfloat ccm[9] = { + params.combinedMatrix[0][0], + params.combinedMatrix[0][1], + params.combinedMatrix[0][2], + params.combinedMatrix[1][0], + params.combinedMatrix[1][1], + params.combinedMatrix[1][2], + params.combinedMatrix[2][0], + params.combinedMatrix[2][1], + params.combinedMatrix[2][2], + }; + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); + LOG(GpuShaderDemosiac, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + + /* + * 0 = Red, 1 = Green, 2 = Blue + */ + glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]); + LOG(GpuShaderDemosiac, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel; + + /* + * Gamma + */ + glUniform1f(gammaUniformDataIn_, params.gamma); + LOG(GpuShaderDemosiac, Debug) << " gammaUniformDataIn_ " << gammaUniformDataIn_ << " data " << params.gamma; + + /* + * Contrast + */ + glUniform1f(contrastExpUniformDataIn_, params.contrastExp); + LOG(GpuShaderDemosiac, Debug) << " contrastExpUniformDataIn_ " << contrastExpUniformDataIn_ << " data " << params.contrastExp; + + return; +} + +} diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h new file mode 100644 index 000000000..11bb04c30 --- /dev/null +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Linaro Ltd + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + * GpuIspIspShaderPass base class + */ + +#pragma once + +#include <stdint.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/object.h> +#include <libcamera/base/signal.h> + +#include <libcamera/geometry.h> +#include <libcamera/stream.h> + +#include "libcamera/internal/egl.h" +#include "libcamera/internal/software_isp/debayer_params.h" + +#include "gpu_pipeline_shader_pass.h" + +namespace libcamera { + +class FrameBuffer; + +class GpuIspShaderPassDemosiac : public GpuIspShaderPass +{ +public: + GpuIspShaderPassDemosiac(eGL& egl) : GpuIspShaderPass(egl) {}; + + int start(); + void stop(); + + /* Things that every ISP pipeline pass will need to do */ + int initShaders(PixelFormat inputFormat, PixelFormat outputFormat); + int getShaderVariableLocations(void); + void setShaderVariableValues(const DebayerParams ¶ms, eGLImage &eglImageIn); + const char *name() const override { return "GpuIspShaderPassDemosiac"; } +private: + /* Shader parameters */ + float firstRed_x_; + float firstRed_y_; + GLint attributeVertex_; + GLint attributeTexture_; + GLint textureUniformStep_; + GLint textureUniformSize_; + GLint textureUniformStrideFactor_; + GLint textureUniformBayerFirstRed_; + GLint textureUniformProjMatrix_; + GLint textureUniformBayerDataIn_; + + uint32_t shaderStridePixels_; + + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ + GLint ccmUniformDataIn_; + + /* Black Level compensation */ + GLint blackLevelUniformDataIn_; + + /* Gamma */ + GLint gammaUniformDataIn_; + + /* Contrast */ + GLint contrastExpUniformDataIn_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build index f8bce5203..3e7d21318 100644 --- a/src/libcamera/software_isp/meson.build +++ b/src/libcamera/software_isp/meson.build @@ -33,6 +33,7 @@ if mesa_works '../egl.cpp', 'software_isp_pipeline_gpu.cpp', 'gpu_pipeline_shader_pass.cpp', + 'gpu_pipeline_shader_pass_demosiac.cpp', ]) libcamera_deps += [ libegl,
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> --- .../gpu_pipeline_shader_pass_demosiac.cpp | 357 ++++++++++++++++++ .../gpu_pipeline_shader_pass_demosiac.h | 72 ++++ src/libcamera/software_isp/meson.build | 1 + 3 files changed, 430 insertions(+) create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h