libcamera/src/libcamera/software_isp/swstats_cpu.cpp
Hans de Goede 26dba2e048 libcamera: swstats_cpu: Drop patternSize_ documentation
patternSize_ is a private variable and its meaning is already documented
in the patternSize() getter documentation.

Move the list of valid sizes to the patternSize() getter documentation
and drop the patternSize_ documentation.

While at it also add 1x1 as valid size for use with future support
of single plane non Bayer input data.

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
2025-06-03 13:22:56 +01:00

428 lines
11 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2023, Linaro Ltd
* Copyright (C) 2023, Red Hat Inc.
*
* Authors:
* Hans de Goede <hdegoede@redhat.com>
*
* CPU based software statistics implementation
*/
#include "swstats_cpu.h"
#include <libcamera/base/log.h>
#include <libcamera/stream.h>
#include "libcamera/internal/bayer_format.h"
namespace libcamera {
/**
* \class SwStatsCpu
* \brief Class for gathering statistics on the CPU
*
* CPU based software ISP statistics implementation.
*
* This class offers a configure function + functions to gather statistics on a
* line by line basis. This allows CPU based software debayering to interleave
* debayering and statistics gathering on a line by line basis while the input
* data is still hot in the cache.
*
* It is also possible to specify a window over which to gather statistics
* instead of processing the whole frame.
*/
/**
* \fn bool SwStatsCpu::isValid() const
* \brief Gets whether the statistics object is valid
*
* \return True if it's valid, false otherwise
*/
/**
* \fn const SharedFD &SwStatsCpu::getStatsFD()
* \brief Get the file descriptor for the statistics
*
* \return The file descriptor
*/
/**
* \fn const Size &SwStatsCpu::patternSize()
* \brief Get the pattern size
*
* For some input-formats, e.g. Bayer data, processing is done multiple lines
* and/or columns at a time. Get width and height at which the (bayer) pattern
* repeats. Window values are rounded down to a multiple of this and the height
* also indicates if processLine2() should be called or not.
* This may only be called after a successful configure() call.
*
* Valid sizes are: 1x1, 2x2, 4x2 or 4x4.
*
* \return The pattern size
*/
/**
* \fn void SwStatsCpu::processLine0(unsigned int y, const uint8_t *src[])
* \brief Process line 0
* \param[in] y The y coordinate.
* \param[in] src The input data.
*
* This function processes line 0 for input formats with
* patternSize height == 1.
* It'll process line 0 and 1 for input formats with patternSize height >= 2.
* This function may only be called after a successful setWindow() call.
*
* This function takes an array of src pointers each pointing to a line in
* the source image.
*
* Bayer input data requires (patternSize_.height + 1) src pointers, with
* the middle element of the array pointing to the actual line being processed.
* Earlier element(s) will point to the previous line(s) and later element(s)
* to the next line(s). See the DebayerCpu::debayerFn documentation for details.
*
* Planar input data requires a src pointer for each plane, with src[0] pointing
* to the line in plane 0, etc.
*
* For non Bayer single plane input data only a single src pointer is required.
*/
/**
* \fn void SwStatsCpu::processLine2(unsigned int y, const uint8_t *src[])
* \brief Process line 2 and 3
* \param[in] y The y coordinate.
* \param[in] src The input data.
*
* This function processes line 2 and 3 for input formats with
* patternSize height == 4.
* This function may only be called after a successful setWindow() call.
*/
/**
* \var Signal<> SwStatsCpu::statsReady
* \brief Signals that the statistics are ready
*/
/**
* \var unsigned int SwStatsCpu::ySkipMask_
* \brief Skip lines where this bitmask is set in y
*/
/**
* \var Rectangle SwStatsCpu::window_
* \brief Statistics window, set by setWindow(), used every line
*/
/**
* \var unsigned int SwStatsCpu::xShift_
* \brief The offset of x, applied to window_.x for bayer variants
*
* This can either be 0 or 1.
*/
LOG_DEFINE_CATEGORY(SwStatsCpu)
SwStatsCpu::SwStatsCpu()
: sharedStats_("softIsp_stats")
{
if (!sharedStats_)
LOG(SwStatsCpu, Error)
<< "Failed to create shared memory for statistics";
}
static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */
static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */
static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
#define SWSTATS_START_LINE_STATS(pixel_t) \
pixel_t r, g, g2, b; \
uint64_t yVal; \
\
uint64_t sumR = 0; \
uint64_t sumG = 0; \
uint64_t sumB = 0;
#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
sumR += r; \
sumG += g; \
sumB += b; \
\
yVal = r * kRedYMul; \
yVal += g * kGreenYMul; \
yVal += b * kBlueYMul; \
stats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
#define SWSTATS_FINISH_LINE_STATS() \
stats_.sumR_ += sumR; \
stats_.sumG_ += sumG; \
stats_.sumB_ += sumB;
void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[])
{
const uint8_t *src0 = src[1] + window_.x;
const uint8_t *src1 = src[2] + window_.x;
SWSTATS_START_LINE_STATS(uint8_t)
if (swapLines_)
std::swap(src0, src1);
/* x += 4 sample every other 2x2 block */
for (int x = 0; x < (int)window_.width; x += 4) {
b = src0[x];
g = src0[x + 1];
g2 = src1[x];
r = src1[x + 1];
g = (g + g2) / 2;
SWSTATS_ACCUMULATE_LINE_STATS(1)
}
SWSTATS_FINISH_LINE_STATS()
}
void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[])
{
const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
SWSTATS_START_LINE_STATS(uint16_t)
if (swapLines_)
std::swap(src0, src1);
/* x += 4 sample every other 2x2 block */
for (int x = 0; x < (int)window_.width; x += 4) {
b = src0[x];
g = src0[x + 1];
g2 = src1[x];
r = src1[x + 1];
g = (g + g2) / 2;
/* divide Y by 4 for 10 -> 8 bpp value */
SWSTATS_ACCUMULATE_LINE_STATS(4)
}
SWSTATS_FINISH_LINE_STATS()
}
void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[])
{
const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
SWSTATS_START_LINE_STATS(uint16_t)
if (swapLines_)
std::swap(src0, src1);
/* x += 4 sample every other 2x2 block */
for (int x = 0; x < (int)window_.width; x += 4) {
b = src0[x];
g = src0[x + 1];
g2 = src1[x];
r = src1[x + 1];
g = (g + g2) / 2;
/* divide Y by 16 for 12 -> 8 bpp value */
SWSTATS_ACCUMULATE_LINE_STATS(16)
}
SWSTATS_FINISH_LINE_STATS()
}
void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])
{
const uint8_t *src0 = src[1] + window_.x * 5 / 4;
const uint8_t *src1 = src[2] + window_.x * 5 / 4;
const int widthInBytes = window_.width * 5 / 4;
if (swapLines_)
std::swap(src0, src1);
SWSTATS_START_LINE_STATS(uint8_t)
/* x += 5 sample every other 2x2 block */
for (int x = 0; x < widthInBytes; x += 5) {
/* BGGR */
b = src0[x];
g = src0[x + 1];
g2 = src1[x];
r = src1[x + 1];
g = (g + g2) / 2;
/* Data is already 8 bits, divide by 1 */
SWSTATS_ACCUMULATE_LINE_STATS(1)
}
SWSTATS_FINISH_LINE_STATS()
}
void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])
{
const uint8_t *src0 = src[1] + window_.x * 5 / 4;
const uint8_t *src1 = src[2] + window_.x * 5 / 4;
const int widthInBytes = window_.width * 5 / 4;
if (swapLines_)
std::swap(src0, src1);
SWSTATS_START_LINE_STATS(uint8_t)
/* x += 5 sample every other 2x2 block */
for (int x = 0; x < widthInBytes; x += 5) {
/* GBRG */
g = src0[x];
b = src0[x + 1];
r = src1[x];
g2 = src1[x + 1];
g = (g + g2) / 2;
/* Data is already 8 bits, divide by 1 */
SWSTATS_ACCUMULATE_LINE_STATS(1)
}
SWSTATS_FINISH_LINE_STATS()
}
/**
* \brief Reset state to start statistics gathering for a new frame
*
* This may only be called after a successful setWindow() call.
*/
void SwStatsCpu::startFrame(void)
{
if (window_.width == 0)
LOG(SwStatsCpu, Error) << "Calling startFrame() without setWindow()";
stats_.sumR_ = 0;
stats_.sumB_ = 0;
stats_.sumG_ = 0;
stats_.yHistogram.fill(0);
}
/**
* \brief Finish statistics calculation for the current frame
* \param[in] frame The frame number
* \param[in] bufferId ID of the statistics buffer
*
* This may only be called after a successful setWindow() call.
*/
void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId)
{
*sharedStats_ = stats_;
statsReady.emit(frame, bufferId);
}
/**
* \brief Setup SwStatsCpu object for standard Bayer orders
* \param[in] order The Bayer order
*
* Check if order is a standard Bayer order and setup xShift_ and swapLines_
* so that a single BGGR stats function can be used for all 4 standard orders.
*/
int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order)
{
switch (order) {
case BayerFormat::BGGR:
xShift_ = 0;
swapLines_ = false;
break;
case BayerFormat::GBRG:
xShift_ = 1; /* BGGR -> GBRG */
swapLines_ = false;
break;
case BayerFormat::GRBG:
xShift_ = 0;
swapLines_ = true; /* BGGR -> GRBG */
break;
case BayerFormat::RGGB:
xShift_ = 1; /* BGGR -> GBRG */
swapLines_ = true; /* GBRG -> RGGB */
break;
default:
return -EINVAL;
}
patternSize_.height = 2;
patternSize_.width = 2;
ySkipMask_ = 0x02; /* Skip every 3th and 4th line */
return 0;
}
/**
* \brief Configure the statistics object for the passed in input format
* \param[in] inputCfg The input format
*
* \return 0 on success, a negative errno value on failure
*/
int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
{
BayerFormat bayerFormat =
BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
if (bayerFormat.packing == BayerFormat::Packing::None &&
setupStandardBayerOrder(bayerFormat.order) == 0) {
switch (bayerFormat.bitDepth) {
case 8:
stats0_ = &SwStatsCpu::statsBGGR8Line0;
return 0;
case 10:
stats0_ = &SwStatsCpu::statsBGGR10Line0;
return 0;
case 12:
stats0_ = &SwStatsCpu::statsBGGR12Line0;
return 0;
}
}
if (bayerFormat.bitDepth == 10 &&
bayerFormat.packing == BayerFormat::Packing::CSI2) {
patternSize_.height = 2;
patternSize_.width = 4; /* 5 bytes per *4* pixels */
/* Skip every 3th and 4th line, sample every other 2x2 block */
ySkipMask_ = 0x02;
xShift_ = 0;
switch (bayerFormat.order) {
case BayerFormat::BGGR:
case BayerFormat::GRBG:
stats0_ = &SwStatsCpu::statsBGGR10PLine0;
swapLines_ = bayerFormat.order == BayerFormat::GRBG;
return 0;
case BayerFormat::GBRG:
case BayerFormat::RGGB:
stats0_ = &SwStatsCpu::statsGBRG10PLine0;
swapLines_ = bayerFormat.order == BayerFormat::RGGB;
return 0;
default:
break;
}
}
LOG(SwStatsCpu, Info)
<< "Unsupported input format " << inputCfg.pixelFormat.toString();
return -EINVAL;
}
/**
* \brief Specify window coordinates over which to gather statistics
* \param[in] window The window object.
*/
void SwStatsCpu::setWindow(const Rectangle &window)
{
window_ = window;
window_.x &= ~(patternSize_.width - 1);
window_.x += xShift_;
window_.y &= ~(patternSize_.height - 1);
/* width_ - xShift_ to make sure the window fits */
window_.width -= xShift_;
window_.width &= ~(patternSize_.width - 1);
window_.height &= ~(patternSize_.height - 1);
}
} /* namespace libcamera */