@@ -21,9 +21,10 @@ precision highp float;
/** Monochrome RGBA or GL_LUMINANCE Bayer encoded texture.*/
uniform sampler2D tex_y;
-varying vec4 center;
-varying vec4 yCoord;
-varying vec4 xCoord;
+uniform vec2 tex_size;
+uniform float stride_factor;
+uniform vec2 tex_bayer_first_red;
+varying vec2 pixelPos;
uniform vec3 awb;
uniform mat3 ccm;
uniform vec3 blacklevel;
@@ -52,11 +53,23 @@ void main(void) {
#define fetch(x, y) texture2D(tex_y, vec2(x, y)).r
#endif
+ /*
+ * Derive the parity and every fetch coordinate from one floor() of the
+ * pixel position, snapped to texel centres. Computed separately they
+ * can round to different texels when downscaling lands a sample on a
+ * texel boundary.
+ */
+ vec2 texStep = vec2(stride_factor / tex_size.x, 1.0 / tex_size.y);
+ vec2 pix = floor(pixelPos);
+ vec2 center = (pix + 0.5) * texStep;
+ vec4 xCoord = center.x + vec4(-2.0, -1.0, 1.0, 2.0) * texStep.x;
+ vec4 yCoord = center.y + vec4(-2.0, -1.0, 1.0, 2.0) * texStep.y;
+
float C = fetch(center.x, center.y); // ( 0, 0)
const vec4 kC = vec4( 4.0, 6.0, 5.0, 5.0) / 8.0;
// Determine which of four types of pixels we are on.
- vec2 alternate = mod(floor(center.zw), 2.0);
+ vec2 alternate = mod(pix + tex_bayer_first_red, 2.0);
vec4 Dvec = vec4(
fetch(xCoord[1], yCoord[1]), // (-1,-1)
@@ -22,34 +22,12 @@ attribute vec2 textureIn;
uniform mat4 proj_matrix;
uniform vec2 tex_size; /* The texture size in pixels */
-uniform vec2 tex_step;
-/** Pixel position of the first red pixel in the */
-/** Bayer pattern. [{0,1}, {0, 1}]*/
-uniform vec2 tex_bayer_first_red;
-
-/** .xy = Pixel being sampled in the fragment shader on the range [0, 1]
- .zw = ...on the range [0, sourceSize], offset by firstRed */
-varying vec4 center;
-
-/** center.x + (-2/w, -1/w, 1/w, 2/w); These are the x-positions */
-/** of the adjacent pixels.*/
-varying vec4 xCoord;
-
-/** center.y + (-2/h, -1/h, 1/h, 2/h); These are the y-positions */
-/** of the adjacent pixels.*/
-varying vec4 yCoord;
-
-uniform float stride_factor;
+/** Position of the pixel being sampled, in image pixels. */
+varying vec2 pixelPos;
void main(void) {
- center.xy = vec2(textureIn.x * stride_factor, textureIn.y);
- center.zw = textureIn * tex_size + tex_bayer_first_red;
-
- xCoord = center.x + vec4(-2.0 * tex_step.x,
- -tex_step.x, tex_step.x, 2.0 * tex_step.x);
- yCoord = center.y + vec4(-2.0 * tex_step.y,
- -tex_step.y, tex_step.y, 2.0 * tex_step.y);
+ pixelPos = textureIn * tex_size;
gl_Position = proj_matrix * vertexIn;
}
@@ -432,11 +432,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
/*
* 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
+ * firstRed = tex_bayer_first_red - bayer_unpacked.frag, bayer_1x_packed.frag
+ * imgSize = tex_size - bayer_unpacked.vert, bayer_unpacked.frag, bayer_1x_packed.frag
+ * step = tex_step - bayer_1x_packed.frag
+ * Stride = stride_factor - identity.vert, bayer_unpacked.frag
+ * projMatrix = proj_matrix - identity.vert, bayer_unpacked.vert
*/
glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);
glUniform2fv(textureUniformSize_, 1, imgSize);
The bayer_unpacked fragment shader derives the Bayer parity of the sampled pixel from one interpolated varying, in pixel units, while the texture unit selects the nearest texel from another, in texture units. At native size every sample lands on a texel centre and the two agree. When the render target is smaller than the source, as in the software ISP GPU debayer and qcam's scaled viewfinder, sample positions land on texel boundaries on a periodic subset of output rows and columns, and rounding differences make the parity and the fetched texel disagree by one. Such rows debayer with the wrong row parity, raising R and B over G: regular magenta lines. On an imx471 (1928x1088 SRGGB10 through the Intel IPU7 ISYS, debayered by Mesa 26.1 on Intel Arc B390 graphics) scaled to 640x480, output row y samples source row (2y + 1) * 17 / 15, an exact texel boundary for y = 7 (mod 15). Captures showed one magenta line every 15 output rows, at those offsets. Select the source pixel with a single floor() in pixel space, as bayer_1x_packed.frag already does, and derive the parity, the centre fetch and the neighbour fetches from that one index, snapped to texel centres. The two computations can then no longer disagree. The vertex shader now only forwards the source position in pixels; the center, xCoord and yCoord varyings and tex_step are dropped from the unpacked programs, and stride_factor and tex_bayer_first_red move to the fragment shader. DebayerEGL and qcam already set those uniforms, so no C++ changes are needed beyond a comment. At native size the sampled texels are unchanged. Measured with the mean of R + B - 2G per output row, in 8-bit output levels: at 640x480 the mean excess of the y = 7 (mod 15) rows drops from +13.1 (about 5% of full scale), with all other phases within +/-0.05, to 0.00, within the spread of the other phases. 480x320, 640x360, 1280x720 and the native output size of 1924x1088 stay clean and the field of view is unchanged. Assisted by Claude Code (claude-fable-5). Signed-off-by: Christian Murphy <christian@themurphys.eu> --- src/libcamera/shaders/bayer_unpacked.frag | 21 ++++++++++++---- src/libcamera/shaders/bayer_unpacked.vert | 28 +++------------------- src/libcamera/software_isp/debayer_egl.cpp | 10 ++++---- 3 files changed, 25 insertions(+), 34 deletions(-)