[v5,06/11] software-isp: Expose the configured image crop
diff mbox series

Message ID 20261029152058.896209-7-halfline@gmail.com
State Rejected
Headers show
Series
  • software-isp: Align metering and report the configured crop
Related show

Commit Message

Ray Strode Oct. 29, 2026, 3:20 p.m. UTC
From: Ray Strode <rstrode@redhat.com>

This commit makes the image region selected by the debayer available to the
pipeline handler, so ScalerCrop metadata can describe the processed output.

The CPU and EGL backends crop input differently. CPU debayering keeps an
output-sized window whose origin depends on the Bayer pattern and output
channel order, while EGL scales a centered crop to fill the output.

Each backend returns its actual configured image crop in input-buffer
coordinates. The EGL crop is retained before statistics alignment, so the
returned rectangle describes the image rather than its metering samples.

Signed-off-by: Ray Strode <rstrode@redhat.com>
---
 .../libcamera/internal/software_isp/software_isp.h |  1 +
 src/libcamera/software_isp/debayer.cpp             | 11 +++++++++++
 src/libcamera/software_isp/debayer.h               |  1 +
 src/libcamera/software_isp/debayer_cpu.h           |  4 ++++
 src/libcamera/software_isp/debayer_egl.cpp         |  3 ++-
 src/libcamera/software_isp/debayer_egl.h           |  2 ++
 src/libcamera/software_isp/software_isp.cpp        | 14 ++++++++++++++
 7 files changed, 35 insertions(+), 1 deletion(-)

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index b6491063..d32bb7d2 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -40,60 +40,61 @@  class Debayer;
 class FrameBuffer;
 class PixelFormat;
 class Stream;
 struct StreamConfiguration;
 
 LOG_DECLARE_CATEGORY(SoftwareIsp)
 
 class SoftwareIsp : public Object
 {
 public:
 	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 		    ControlInfoMap *ipaControls);
 	~SoftwareIsp();
 
 	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
 
 	bool isValid() const;
 
 	std::vector<PixelFormat> formats(PixelFormat input);
 
 	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
 
 	std::tuple<unsigned int, unsigned int>
 	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
 	uint32_t preferredInputStride(const PixelFormat &inputFormat, const Size &size);
 
 	int configure(const StreamConfiguration &inputCfg,
 		      const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 		      const ipa::softisp::IPAConfigInfo &configInfo,
 		      ControlInfoMap *ipaControls);
+	Rectangle inputCrop() const;
 
 	int exportBuffers(const Stream *stream, unsigned int count,
 			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
 
 	void processStats(const uint32_t frame, const uint32_t bufferId,
 			  const ControlList &sensorControls);
 
 	int start();
 	void stop();
 
 	void queueRequest(const uint32_t frame, const ControlList &controls);
 	int queueBuffers(uint32_t frame, FrameBuffer *input,
 			 const std::map<const Stream *, FrameBuffer *> &outputs);
 
 	void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output);
 
 	Signal<FrameBuffer *> inputBufferReady;
 	Signal<FrameBuffer *> outputBufferReady;
 	Signal<uint32_t, uint32_t> ispStatsReady;
 	Signal<uint32_t, const ControlList &> metadataReady;
 	Signal<const ControlList &> setSensorControls;
 
 private:
 	void saveIspParams(uint32_t frame);
 	void setSensorCtrls(const ControlList &sensorControls);
 	void statsReady(uint32_t frame, uint32_t bufferId);
 	void inputReady(FrameBuffer *input);
 	void outputReady(FrameBuffer *output);
 	std::unique_ptr<Debayer> debayer_;
 	Thread ispWorkerThread_;
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index 4f665900..c7b1b2b3 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -79,60 +79,71 @@  namespace libcamera {
  * not apply any real lens shading correction.
  */
 
 /**
  * \var DebayerParams::lscLutVersion
  * \brief Incremented on each \a lscLut change
  */
 
 /**
  * \class Debayer
  * \brief Base debayering class
  *
  * Base class that provides functions for setting up the debayering process.
  */
 
 LOG_DEFINE_CATEGORY(Debayer)
 
 /**
  * \brief Construct a Debayer object
  * \param[in] cm The camera manager
  */
 Debayer::Debayer(const CameraManager &cm)
 	: bench_(cm, "Debayer")
 {
 }
 
 Debayer::~Debayer()
 {
 }
 
+/**
+ * \fn Rectangle Debayer::inputCrop() const
+ * \brief Retrieve the input region visible in the configured output
+ *
+ * The rectangle is in input-buffer coordinates and excludes any additional
+ * alignment performed for statistics sampling. It is valid after a successful
+ * configure() call.
+ *
+ * \return The configured image crop
+ */
+
 /**
  * \fn int Debayer::configure()
  * \brief Configure the debayer object according to the passed in parameters
  * \param[in] inputCfg The input configuration
  * \param[in] outputCfgs The output configurations
  * \param[in] ccmEnabled Whether a color correction matrix is applied
  * \param[in] lscEnabled Whether lens shading correction grid is provided
  *
  * \return 0 on success, a negative errno on failure
  */
 
 /**
  * \fn Size Debayer::patternSize(PixelFormat inputFormat)
  * \brief Get the width and height at which the bayer pattern repeats
  * \param[in] inputFormat The input format
  *
  * Valid sizes are: 2x2, 4x2 or 4x4.
  *
  * \return Pattern size or an empty size for unsupported inputFormats
  */
 
 /**
  * \fn std::vector<PixelFormat> Debayer::formats(PixelFormat inputFormat)
  * \brief Get the supported output formats
  * \param[in] inputFormat The input format
  *
  * \return All supported output formats or an empty vector if there are none
  */
 
 /**
diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
index e30a0527..ef36f3c8 100644
--- a/src/libcamera/software_isp/debayer.h
+++ b/src/libcamera/software_isp/debayer.h
@@ -27,60 +27,61 @@ 
 
 namespace libcamera {
 
 class CameraManager;
 class FrameBuffer;
 
 LOG_DECLARE_CATEGORY(Debayer)
 
 class Debayer : public Object
 {
 public:
 	Debayer(const CameraManager &cm);
 	virtual ~Debayer() = 0;
 
 	virtual int configure(const StreamConfiguration &inputCfg,
 			      const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 			      bool ccmEnabled,
 			      bool lscEnabled) = 0;
 
 	virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0;
 
 	virtual std::tuple<unsigned int, unsigned int>
 	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0;
 	virtual uint32_t preferredInputStride([[maybe_unused]] const PixelFormat &inputFormat, [[maybe_unused]] const Size &size) { return 0; }
 
 	virtual void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) = 0;
 	virtual int start() { return 0; }
 	virtual void stop() {}
 
 	virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;
+	virtual Rectangle inputCrop() const = 0;
 
 	virtual const SharedFD &getStatsFD() = 0;
 
 	unsigned int frameSize() { return outputConfig_.frameSize; }
 
 	Signal<FrameBuffer *> inputBufferReady;
 	Signal<FrameBuffer *> outputBufferReady;
 
 	struct DebayerInputConfig {
 		Size patternSize;
 		unsigned int bpp;
 		unsigned int stride;
 		std::vector<PixelFormat> outputFormats;
 	};
 
 	struct DebayerOutputConfig {
 		unsigned int stride;
 		unsigned int frameSize;
 	};
 
 	DebayerInputConfig inputConfig_;
 	DebayerOutputConfig outputConfig_;
 	Size outputSize_;
 	PixelFormat inputPixelFormat_;
 	PixelFormat outputPixelFormat_;
 	bool swapRedBlueGains_;
 	Benchmark bench_;
 
 private:
 	virtual Size patternSize(PixelFormat inputFormat) = 0;
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index ae570893..0c025b75 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -20,60 +20,64 @@ 
 
 #include <libcamera/camera_manager.h>
 
 #include "libcamera/internal/bayer_format.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
 #include "libcamera/internal/software_isp/swstats_cpu.h"
 
 #include "debayer.h"
 
 namespace libcamera {
 
 class DebayerCpuThread;
 class DebayerCpu : public Debayer
 {
 public:
 	DebayerCpu(std::unique_ptr<SwStatsCpu> stats, const CameraManager &cm);
 	~DebayerCpu();
 
 	int configure(const StreamConfiguration &inputCfg,
 		      const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 		      bool ccmEnabled,
 		      bool lscEnabled) override;
 	Size patternSize(PixelFormat inputFormat) override;
 	std::vector<PixelFormat> formats(PixelFormat input) override;
 	std::tuple<unsigned int, unsigned int>
 	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override;
 	void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) override;
 	int start() override;
 	void stop() override;
 	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) override;
+	Rectangle inputCrop() const override
+	{
+		return window_.translatedBy({ static_cast<int>(xShift_), 0 });
+	}
 	const SharedFD &getStatsFD() override { return stats_->getStatsFD(); }
 
 private:
 	friend class DebayerCpuThread;
 
 	/**
 	 * \brief Called to debayer 1 line of Bayer input data to output format
 	 * \param[out] dst Pointer to the start of the output line to write
 	 * \param[in] src The input data
 	 *
 	 * Input data is an array of (patternSize_.height + 1) src
 	 * pointers each pointing to a line in the Bayer source. The middle
 	 * element of the array will point 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).
 	 *
 	 * These functions take an array of src pointers, rather than
 	 * a single src pointer + a stride for the source, so that when the src
 	 * is slow uncached memory it can be copied to faster memory before
 	 * debayering. Debayering a standard 2x2 Bayer pattern requires access
 	 * to the previous and next src lines for interpolating the missing
 	 * colors. To allow copying the src lines only once 3 temporary buffers
 	 * each holding a single line are used, re-using the oldest buffer for
 	 * the next line and the pointers are swizzled so that:
 	 * src[0] = previous-line, src[1] = currrent-line, src[2] = next-line.
 	 * This way the 3 pointers passed to the debayer functions form
 	 * a sliding window over the src avoiding the need to copy each
 	 * line more than once.
 	 *
 	 * Similarly for bayer patterns which repeat every 4 lines, 5 src
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 5a27a38d..d6636302 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -309,61 +309,62 @@  int DebayerEGL::configure(const StreamConfiguration &inputCfg,
 	height_ = inputCfg.size.height;
 
 	if (outputCfgs.size() != 1) {
 		LOG(Debayer, Error)
 			<< "Unsupported number of output streams: "
 			<< outputCfgs.size();
 		return -EINVAL;
 	}
 
 	const StreamConfiguration &outputCfg = outputCfgs[0];
 	SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);
 	std::tie(outputConfig_.stride, outputConfig_.frameSize) =
 		strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);
 
 	if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {
 		LOG(Debayer, Error)
 			<< "Invalid output size/stride: "
 			<< "\n  " << outputCfg.size << " (" << outSizeRange << ")"
 			<< "\n  " << outputCfg.stride << " (" << outputConfig_.stride << ")";
 		return -EINVAL;
 	}
 
 	outputPixelFormat_ = outputCfg.pixelFormat;
 	outputSize_ = outputCfg.size;
 	nativeOutputSize_ = outSizeRange.max;
 
 	/*
 	 * The GPU scales the visible sensor crop to outputSize_. Statistics
 	 * consume the original input buffer, so use sensor coordinates here.
 	 */
-	stats_->setWindow(debayerCrop(inputCfg.size, nativeOutputSize_, outputSize_));
+	inputCrop_ = debayerCrop(inputCfg.size, nativeOutputSize_, outputSize_);
+	stats_->setWindow(inputCrop_);
 
 	inputBufferCount_ = inputCfg.bufferCount;
 	outputBufferCount_ = outputCfg.bufferCount;
 
 	return 0;
 }
 
 Size DebayerEGL::patternSize(PixelFormat inputFormat)
 {
 	DebayerEGL::DebayerInputConfig config;
 
 	if (getInputConfig(inputFormat, config) != 0)
 		return {};
 
 	return config.patternSize;
 }
 
 std::vector<PixelFormat> DebayerEGL::formats(PixelFormat inputFormat)
 {
 	DebayerEGL::DebayerInputConfig config;
 
 	if (getInputConfig(inputFormat, config) != 0)
 		return std::vector<PixelFormat>();
 
 	return config.outputFormats;
 }
 
 std::tuple<unsigned int, unsigned int>
 DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
 {
diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index 6910fa49..90e361ce 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -34,99 +34,101 @@  namespace libcamera {
 
 #define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4
 #define DEBAYER_OPENGL_COORDS 4
 
 class CameraManager;
 
 class DebayerEGL : public Debayer
 {
 public:
 	DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const CameraManager &cm, EGLDisplay display);
 	~DebayerEGL();
 
 	int configure(const StreamConfiguration &inputCfg,
 		      const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 		      bool ccmEnabled,
 		      bool lscEnabled) override;
 
 	Size patternSize(PixelFormat inputFormat) override;
 
 	std::vector<PixelFormat> formats(PixelFormat input) override;
 	std::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override;
 	uint32_t preferredInputStride(const PixelFormat &inputFormat, const Size &size) override;
 
 	void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) override;
 	int start() override;
 	void stop() override;
 
 	const SharedFD &getStatsFD() override { return stats_->getStatsFD(); }
 
 	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) override;
+	Rectangle inputCrop() const override { return inputCrop_; }
 
 private:
 	static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
 	int initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat);
 	int getShaderVariableLocations();
 	void setShaderVariableValues(eGLImage &eGLImageIn, const DebayerParams &params);
 	int debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams &params, std::optional<MappedFrameBuffer> *mappedInputBuffer, std::optional<DmaSyncer> *inputBufferDmaSyncer);
 
 	eGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer);
 	eGLImage *getCachedOutputFrameBuffer(FrameBuffer *output);
 
 	/* Shader program identifiers */
 	GLuint vertexShaderId_ = 0;
 	GLuint fragmentShaderId_ = 0;
 	GLuint programId_ = 0;
 
 	/* Pointer to object representing input texture */
 	std::deque<std::pair<SharedFD, std::unique_ptr<eGLImage>>> eglImageInCache_;
 	std::deque<std::pair<SharedFD, std::unique_ptr<eGLImage>>> eglImageOutCache_;
 	unsigned int inputBufferCount_;
 	unsigned int outputBufferCount_;
 
 	/* LSC lookup table */
 	bool lscEnabled_;
 	std::optional<eGLImage> eglImageLscLookup_;
 	std::optional<uint64_t> lscLutVersion_;
 
 	/* Shader parameters */
 	float firstRed_x_;
 	float firstRed_y_;
 	GLint attributeVertex_;
 	GLint attributeTexture_;
 	GLint textureUniformStep_;
 	GLint textureUniformSize_;
 	GLint textureUniformStrideFactor_;
 	GLint textureUniformBayerFirstRed_;
 	GLint textureUniformProjMatrix_;
 
 	GLint textureUniformBayerDataIn_;
 
 	/* Per-frame AWB gains */
 	GLint awbUniformDataIn_;
 
 	/* Lens shading correction */
 	GLint textureUniformLsc_;
 
 	/* 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_;
 
 	Size nativeOutputSize_;
+	Rectangle inputCrop_;
 	std::unique_ptr<SwStatsCpu> stats_;
 	eGL egl_;
 	uint32_t width_;
 	uint32_t height_;
 	GLint glFormat_;
 	unsigned int bytesPerPixel_;
 	uint32_t shaderStridePixels_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 627625b4..8a06e53d 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -279,60 +279,74 @@  uint32_t SoftwareIsp::preferredInputStride(const PixelFormat &inputFormat, const
  * \brief Configure the SoftwareIsp object according to the passed in parameters
  * \param[in] inputCfg The input configuration
  * \param[in] outputCfgs The output configurations
  * \param[in] configInfo The IPA configuration data, received from the pipeline handler
  * \param[out] ipaControls The IPA controls to update
  * \return 0 on success, a negative errno on failure
  */
 int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
 			   const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 			   const ipa::softisp::IPAConfigInfo &configInfo,
 			   ControlInfoMap *ipaControls)
 {
 	ASSERT(ipa_ && debayer_);
 
 	int ret = ipa_->configure(configInfo, ipaControls);
 	if (ret < 0)
 		return ret;
 
 	ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_, lscEnabled_);
 	if (ret < 0)
 		return ret;
 
 	LOG(SoftwareIsp, Info)
 		<< "Input " << inputCfg.size
 		<< "-" << BayerFormat::fromPixelFormat(inputCfg.pixelFormat)
 		<< " stride " << inputCfg.stride;
 
 	return 0;
 }
 
+/**
+ * \brief Retrieve the input region used to produce the configured output
+ *
+ * The rectangle is expressed in input-buffer pixels, before any Bayer
+ * alignment applied by the statistics engine. It is valid after configure()
+ * succeeds and describes the selected CPU or EGL debayer backend.
+ *
+ * \return The configured input crop
+ */
+Rectangle SoftwareIsp::inputCrop() const
+{
+	return debayer_->inputCrop();
+}
+
 /**
  * \brief Export the buffers from the Software ISP
  * \param[in] stream Output stream exporting the buffers
  * \param[in] count Number of buffers to allocate
  * \param[out] buffers Vector to store the allocated buffers
  * \return The number of allocated buffers on success or a negative error code
  * otherwise
  */
 int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count,
 			       std::vector<std::unique_ptr<FrameBuffer>> *buffers)
 {
 	ASSERT(debayer_ != nullptr);
 
 	/* single output for now */
 	if (stream == nullptr)
 		return -EINVAL;
 
 	return dmaHeap_.exportBuffers(count, { debayer_->frameSize() }, buffers);
 }
 
 /**
  * \brief Queue a request and process the control list from the application
  * \param[in] frame The number of the frame which will be processed next
  * \param[in] controls The controls for the \a frame
  */
 void SoftwareIsp::queueRequest(const uint32_t frame, const ControlList &controls)
 {
 	ipa_->queueRequest(frame, controls);
 }