mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 01:01:12 -07:00
forgectrl: NEON demosaic - 15 fps, sensor-limited
NEON kernel for the YUV420 superpixel convert (vld2q deinterleave, rounding-halving greens, mlal/rshrn luma, pairwise-add chroma block sums), bit-identical to the scalar path and proven so on a live frame via the FORGECTRL_NEON_CHECK one-shot memcmp. Convert 75 to 18 ms; the stream now runs at the OV5648 frame rate. Motion coexistence re-proven at 15 fps (clamped 0).
This commit is contained in:
@@ -704,6 +704,33 @@ static void *worker(void *arg)
|
||||
vpu_jpeg_planes(vpu, &yp, &up, &vp, &ys, &uvs);
|
||||
debayer_bggr_half_yuv420(raw, CAM_W, CAM_H, HFLIP,
|
||||
yp, ys, up, vp, uvs);
|
||||
#ifdef __ARM_NEON
|
||||
/* One-shot NEON-vs-scalar equivalence check on a live
|
||||
* frame (the paths are constructed to be bit-identical;
|
||||
* this proves it on real data). Stride == width here. */
|
||||
static int neon_checked;
|
||||
if (!neon_checked && getenv("FORGECTRL_NEON_CHECK")) {
|
||||
neon_checked = 1;
|
||||
size_t ysz = (size_t)ys * HALF_H;
|
||||
size_t usz = (size_t)uvs * (HALF_H / 2);
|
||||
uint8_t *ry = malloc(ysz);
|
||||
uint8_t *ru = malloc(usz);
|
||||
uint8_t *rv = malloc(usz);
|
||||
if (ry && ru && rv) {
|
||||
debayer_bggr_half_yuv420_scalar(raw, CAM_W, CAM_H,
|
||||
HFLIP, ry, ys,
|
||||
ru, rv, uvs);
|
||||
fprintf(stderr, "cam: NEON/scalar compare: %s\n",
|
||||
(!memcmp(ry, yp, ysz) &&
|
||||
!memcmp(ru, up, usz) &&
|
||||
!memcmp(rv, vp, usz))
|
||||
? "IDENTICAL" : "MISMATCH");
|
||||
}
|
||||
free(ry);
|
||||
free(ru);
|
||||
free(rv);
|
||||
}
|
||||
#endif
|
||||
now_ts(&e1);
|
||||
if (vpu_jpeg_encode(vpu, &jpg, &len) == 0) {
|
||||
via_vpu = 1;
|
||||
|
||||
@@ -64,9 +64,9 @@ void debayer_bggr_bilinear(const uint8_t *raw, uint8_t *rgb,
|
||||
}
|
||||
}
|
||||
|
||||
void debayer_bggr_half_yuv420(const uint8_t *raw, int w, int h, int hflip,
|
||||
uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp, int uv_stride)
|
||||
void debayer_bggr_half_yuv420_scalar(const uint8_t *raw, int w, int h,
|
||||
int hflip, uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp, int uv_stride)
|
||||
{
|
||||
const int ow = w / 2; /* luma dimensions */
|
||||
const int oh = h / 2;
|
||||
@@ -127,3 +127,139 @@ void debayer_bggr_half(const uint8_t *raw, uint8_t *rgb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __ARM_NEON
|
||||
#include <arm_neon.h>
|
||||
|
||||
/* Store 16 luma bytes at column x, mirrored when hflip (the vector is
|
||||
* byte-reversed and lands at the mirrored block position). */
|
||||
static inline void store16_flip(uint8_t *row, int x, int ow, int hflip,
|
||||
uint8x16_t v)
|
||||
{
|
||||
if (!hflip) {
|
||||
vst1q_u8(row + x, v);
|
||||
} else {
|
||||
uint8x16_t r = vrev64q_u8(v);
|
||||
r = vextq_u8(r, r, 8); /* swap halves: full 16-byte reverse */
|
||||
vst1q_u8(row + (ow - 16 - x), r);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void store8_flip(uint8_t *row, int x, int n, int hflip,
|
||||
uint8x8_t v)
|
||||
{
|
||||
if (!hflip)
|
||||
vst1_u8(row + x, v);
|
||||
else
|
||||
vst1_u8(row + (n - 8 - x), vrev64_u8(v));
|
||||
}
|
||||
|
||||
/* Y = (77R + 150G + 29B + 128) >> 8 for 16 pixels. */
|
||||
static inline uint8x16_t luma16(uint8x16_t R, uint8x16_t G, uint8x16_t B)
|
||||
{
|
||||
const uint8x8_t cR = vdup_n_u8(77), cG = vdup_n_u8(150),
|
||||
cB = vdup_n_u8(29);
|
||||
uint16x8_t lo = vmull_u8(vget_low_u8(R), cR);
|
||||
lo = vmlal_u8(lo, vget_low_u8(G), cG);
|
||||
lo = vmlal_u8(lo, vget_low_u8(B), cB);
|
||||
uint16x8_t hi = vmull_u8(vget_high_u8(R), cR);
|
||||
hi = vmlal_u8(hi, vget_high_u8(G), cG);
|
||||
hi = vmlal_u8(hi, vget_high_u8(B), cB);
|
||||
return vcombine_u8(vrshrn_n_u16(lo, 8), vrshrn_n_u16(hi, 8));
|
||||
}
|
||||
|
||||
/* 4-superpixel block sums (vertical add then horizontal pair-add) for one
|
||||
* 16-column pair of rows -> 8 lanes of u32 split across two quads. */
|
||||
static inline void block_sums(uint8x16_t a, uint8x16_t b,
|
||||
int32x4_t *q0, int32x4_t *q1)
|
||||
{
|
||||
uint16x8_t lo = vaddl_u8(vget_low_u8(a), vget_low_u8(b));
|
||||
uint16x8_t hi = vaddl_u8(vget_high_u8(a), vget_high_u8(b));
|
||||
*q0 = vreinterpretq_s32_u32(vpaddlq_u16(lo));
|
||||
*q1 = vreinterpretq_s32_u32(vpaddlq_u16(hi));
|
||||
}
|
||||
|
||||
/* ((cr*rs + cg*gs + cb*bs + 512) >> 10) + 128, clamped to 0..255. */
|
||||
static inline uint16x4_t chroma4(int32x4_t rs, int32x4_t gs, int32x4_t bs,
|
||||
int cr, int cg, int cb)
|
||||
{
|
||||
int32x4_t acc = vmulq_n_s32(rs, cr);
|
||||
acc = vmlaq_n_s32(acc, gs, cg);
|
||||
acc = vmlaq_n_s32(acc, bs, cb);
|
||||
acc = vaddq_s32(acc, vdupq_n_s32(512));
|
||||
acc = vshrq_n_s32(acc, 10);
|
||||
acc = vaddq_s32(acc, vdupq_n_s32(128));
|
||||
return vqmovun_s32(acc); /* clamps < 0 */
|
||||
}
|
||||
|
||||
static void debayer_bggr_half_yuv420_neon(const uint8_t *raw, int w, int h,
|
||||
int hflip,
|
||||
uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp,
|
||||
int uv_stride)
|
||||
{
|
||||
const int ow = w / 2;
|
||||
const int oh = h / 2;
|
||||
const int uvw = ow / 2;
|
||||
|
||||
for (int y2 = 0; y2 < oh / 2; y2++) {
|
||||
const uint8_t *r0 = raw + (size_t)(4 * y2) * w; /* B G ... */
|
||||
const uint8_t *r1 = r0 + w; /* G R ... */
|
||||
const uint8_t *r2 = r1 + w;
|
||||
const uint8_t *r3 = r2 + w;
|
||||
uint8_t *ya = yp + (size_t)(2 * y2) * y_stride;
|
||||
uint8_t *yb = ya + y_stride;
|
||||
uint8_t *ur = up + (size_t)y2 * uv_stride;
|
||||
uint8_t *vr = vp + (size_t)y2 * uv_stride;
|
||||
|
||||
for (int x = 0; x < ow; x += 16) {
|
||||
/* superpixel row A: raw rows r0/r1 */
|
||||
uint8x16x2_t ea = vld2q_u8(r0 + 2 * x); /* [0]=B [1]=G1 */
|
||||
uint8x16x2_t oa = vld2q_u8(r1 + 2 * x); /* [0]=G2 [1]=R */
|
||||
uint8x16_t Ba = ea.val[0];
|
||||
uint8x16_t Ga = vrhaddq_u8(ea.val[1], oa.val[0]);
|
||||
uint8x16_t Ra = oa.val[1];
|
||||
store16_flip(ya, x, ow, hflip, luma16(Ra, Ga, Ba));
|
||||
|
||||
/* superpixel row B: raw rows r2/r3 */
|
||||
uint8x16x2_t eb = vld2q_u8(r2 + 2 * x);
|
||||
uint8x16x2_t ob = vld2q_u8(r3 + 2 * x);
|
||||
uint8x16_t Bb = eb.val[0];
|
||||
uint8x16_t Gb = vrhaddq_u8(eb.val[1], ob.val[0]);
|
||||
uint8x16_t Rb = ob.val[1];
|
||||
store16_flip(yb, x, ow, hflip, luma16(Rb, Gb, Bb));
|
||||
|
||||
/* chroma from the 2x2 superpixel blocks of both rows */
|
||||
int32x4_t rs0, rs1, gs0, gs1, bs0, bs1;
|
||||
block_sums(Ra, Rb, &rs0, &rs1);
|
||||
block_sums(Ga, Gb, &gs0, &gs1);
|
||||
block_sums(Ba, Bb, &bs0, &bs1);
|
||||
|
||||
uint16x8_t cb = vcombine_u16(chroma4(rs0, gs0, bs0, -43, -85, 128),
|
||||
chroma4(rs1, gs1, bs1, -43, -85, 128));
|
||||
uint16x8_t cr = vcombine_u16(chroma4(rs0, gs0, bs0, 128, -107, -21),
|
||||
chroma4(rs1, gs1, bs1, 128, -107, -21));
|
||||
store8_flip(ur, x / 2, uvw, hflip, vqmovn_u16(cb));
|
||||
store8_flip(vr, x / 2, uvw, hflip, vqmovn_u16(cr));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* __ARM_NEON */
|
||||
|
||||
void debayer_bggr_half_yuv420(const uint8_t *raw, int w, int h, int hflip,
|
||||
uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp, int uv_stride)
|
||||
{
|
||||
#ifdef __ARM_NEON
|
||||
static int no_neon = -1;
|
||||
if (no_neon < 0)
|
||||
no_neon = getenv("FORGECTRL_NO_NEON") != NULL;
|
||||
if (!no_neon && w % 32 == 0 && h % 4 == 0) {
|
||||
debayer_bggr_half_yuv420_neon(raw, w, h, hflip, yp, y_stride,
|
||||
up, vp, uv_stride);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
debayer_bggr_half_yuv420_scalar(raw, w, h, hflip, yp, y_stride,
|
||||
up, vp, uv_stride);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,19 @@ void debayer_bggr_half(const uint8_t *raw, uint8_t *rgb,
|
||||
/* Half-resolution demosaic straight to planar YUV420 (JFIF full-range,
|
||||
* ITU-R 601) for the VPU JPEG encoder: luma per 2x2 BGGR quad at
|
||||
* (w/2)x(h/2), chroma averaged per 2x2 luma block at (w/4)x(h/4).
|
||||
* w/2 and h/2 must be even. Strides are in bytes. */
|
||||
* w/2 and h/2 must be even. Strides are in bytes.
|
||||
*
|
||||
* Dispatches to a NEON kernel when compiled for NEON and the geometry
|
||||
* allows (w%32==0, h%4==0); FORGECTRL_NO_NEON=1 forces the scalar path.
|
||||
* Both paths produce bit-identical output. */
|
||||
void debayer_bggr_half_yuv420(const uint8_t *raw, int w, int h, int hflip,
|
||||
uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp, int uv_stride);
|
||||
|
||||
/* The scalar reference path (used by the NEON self-check). */
|
||||
void debayer_bggr_half_yuv420_scalar(const uint8_t *raw, int w, int h,
|
||||
int hflip, uint8_t *yp, int y_stride,
|
||||
uint8_t *up, uint8_t *vp,
|
||||
int uv_stride);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user