Re: [RFC] media: Add AFBC pixel formats
From: Nicolas Dufresne
Date: Thu Apr 16 2026 - 12:55:31 EST
Le jeudi 16 avril 2026 à 18:29 +0200, Benjamin Gaignard a écrit :
>
> Le 16/04/2026 à 17:23, Nicolas Dufresne a écrit :
> > Le jeudi 16 avril 2026 à 14:41 +0200, Benjamin Gaignard a écrit :
> > > Add 8-bit and 10-bit YUV420 Arm Frame Buffer Compression (AFBC)
> > > pixel formats.
> > >
> > > AFBC stride and image size computation needed to be done by
> > > specific helpers functions which are also exported to be used
> > > by drivers.
> > >
> > > Add documentation for each of the formats.
> > >
> > > Signed-off-by: Benjamin Gaignard <benjamin.gaignard@xxxxxxxxxxxxx>
> > > ---
> > > .../userspace-api/media/v4l/pixfmt-afbc.rst | 64 +++++++++++
> > > .../userspace-api/media/v4l/pixfmt.rst | 1 +
> > > drivers/media/v4l2-core/v4l2-common.c | 104 ++++++++++++++++++
> > > drivers/media/v4l2-core/v4l2-ioctl.c | 4 +
> > > include/media/v4l2-common.h | 4 +
> > > include/uapi/linux/videodev2.h | 6 +
> > > 6 files changed, 183 insertions(+)
> > > create mode 100644 Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
> > >
> > > diff --git a/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst b/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
> > > new file mode 100644
> > > index 000000000000..2867e5d45810
> > > --- /dev/null
> > > +++ b/Documentation/userspace-api/media/v4l/pixfmt-afbc.rst
> > > @@ -0,0 +1,64 @@
> > > +.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later
> > > +
> > > +.. afbc:
> > > +
> > > +*******************************************
> > > +ARM Frame Buffer Compression formats (AFBC)
> > > +*******************************************
> > > +
> > > +The AFBC format is a lossless compression format which can support
> > > +up to four components. It could compress 8 bits to 64 bits per pixel.
> > > +The internal superblock size could be:
> > > +
> > > +- 16x16 pixels
> > > +
> > > +- 32x8 pixels
> > > +
> > > +- 64x4 pixels.
> > > +
> > > +The memory layout is composed of a header block followed by payload data.
> > Because these format are so tied with the Graphics, I would avoid past mistakes
> > with QC formats, and up-front document their matching DRM format and modifiers.
>
> sure, I can add a column with the matching DRM format + modifier
>
> >
> > While its documented in ./Documentation/userspace-api/dma-buf-alloc-exchange.rst
> > I would still document a bit the way we calculate a stride for this formats, or
> > cross-reference that documentation, but being explicit for this specific format
> > may avoid a lot of issues.
>
> I don't see that in dma-buf-alloc-exchange.rst if you a place where it is
> documented I can add a link to it.
The documentation is:
stride:
A value that denotes the relationship between pixel-location co-ordinates
and byte-offset values. Typically used as the byte offset between two
pixels at the start of vertically-consecutive tiling blocks. For linear
layouts, the byte offset between two vertically-adjacent pixels. For
non-linear formats the stride must be computed in a consistent way, which
usually is done as-if the layout was linear.
Its heavily generalized, not obvious, that's why I think documenting it might be
nice. Specially that its arbitrary, but if you get it wrong it fails. In
practice, once you have aligned the width, its just aligned_width * 3 *
bit_depth / 2 / 8 iirc (so either 12bit per pixel, or 15bit per pixels, aligned
so we don't chop pixels or bytes.
>
> > > +AFBC Formats
> > > +============
> > > +
> > > +.. tabularcolumns:: |p{5.2cm}|p{1.0cm}|p{1.5cm}|p{1.9cm}|p{1.2cm}|p{1.8cm}|
> > > +
> > > +.. flat-table:: Overview of AFBC formats
> > > + :header-rows: 1
> > > + :stub-columns: 0
> > > +
> > > + * - Identifier
> > > + - Code
> > > + - Colorspace
> > > + - Bits per component
> > > + - Superblock size
> > > + - Compression parameters
> > > + * - V4L2_PIX_FMT_AFBC_YUV420_16x16
> > > + - 'A168'
> > > + - YUV420
> > > + - 8 bits
> > > + - 16x16
> > > + - Sparse, Split
> > From my accumulate knowledge, RK35xx RKVDEC produce non-split, and this one is
> > split. To make it easy for naming in the future, I'd rename this one.
> >
> > V4L2_PIX_FMT_AFBC_YUV420_16x16_SPLIT
> >
> > Or
> >
> > V4L2_PIX_FMT_AFBC_YUV420_16x16_S
> > V4L2_PIX_FMT_AFBC_YUV420_16x16S
> >
> > Other ideas, but it would be really hard to come up with a name for "not slip".
> > While non-sparse is very special, and not commonly used.
>
> Let's go for SPLIT it is more readable.
Great!
>
> >
> > > + * - V4L2_PIX_FMT_AFBC_YUV420_32x8
> > > + - 'A328'
> > > + - YUV420
> > > + - 8 bits
> > > + - 32x8
> > > + - Sparse
> > > + * - V4L2_PIX_FMT_AFBC_YUV420_16x16_10
> > > + - 'A16a'
> > > + - YUV420
> > > + - 10 bits
> > > + - 16x16
> > > + - Sparse, Split
> > > + * - V4L2_PIX_FMT_AFBC_YUV420_32x8_10
> > > + - 'A32a'
> > > + - YUV420
> > > + - 10 bits
> > > + - 32x8
> > > + - Sparse
> > > +
> > > +.. _V4L2-PIX-FMT-AFBC-YUV420-16x16:
> > > +.. _V4L2-PIX-FMT-AFBC-YUV420-32x8:
> > > +.. _V4L2-PIX-FMT-AFBC-YUV420-16x16-10:
> > > +.. _V4L2-PIX-FMT-AFBC-YUV420-32x8-10:
> > > diff --git a/Documentation/userspace-api/media/v4l/pixfmt.rst b/Documentation/userspace-api/media/v4l/pixfmt.rst
> > > index 71b29267488f..c6728b91b74f 100644
> > > --- a/Documentation/userspace-api/media/v4l/pixfmt.rst
> > > +++ b/Documentation/userspace-api/media/v4l/pixfmt.rst
> > > @@ -26,6 +26,7 @@ see also :ref:`VIDIOC_G_FBUF <VIDIOC_G_FBUF>`.)
> > > pixfmt-indexed
> > > pixfmt-rgb
> > > pixfmt-bayer
> > > + pixfmt-afbc
> > > yuv-formats
> > > hsv-formats
> > > depth-formats
> > > diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
> > > index 554c591e1113..9187cb18a4ef 100644
> > > --- a/drivers/media/v4l2-core/v4l2-common.c
> > > +++ b/drivers/media/v4l2-core/v4l2-common.c
> > > @@ -332,6 +332,16 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
> > > { .format = V4L2_PIX_FMT_NV12MT_16X16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2,
> > > .block_w = { 16, 8, 0, 0 }, .block_h = { 16, 8, 0, 0 }},
> > >
> > > + /* AFBC formats */
> > > + { .format = V4L2_PIX_FMT_AFBC_YUV420_16x16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> > > + .block_w = { 16, 0, 0, 0 }, .block_h = { 16, 0, 0, 0 }},
> > > + { .format = V4L2_PIX_FMT_AFBC_YUV420_32x8, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> > > + .block_w = { 32, 0, 0, 0 }, .block_h = { 8, 0, 0, 0 }},
> > > + { .format = V4L2_PIX_FMT_AFBC_YUV420_16x16_10, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 15, 0, 0, 0 }, .bpp_div = { 8, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> > > + .block_w = { 16, 0, 0, 0 }, .block_h = { 16, 0, 0, 0 }},
> > > + { .format = V4L2_PIX_FMT_AFBC_YUV420_32x8_10, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 15, 0, 0, 0 }, .bpp_div = { 8, 1, 1, 1 }, .hdiv = 1, .vdiv = 1,
> > > + .block_w = { 32, 0, 0, 0 }, .block_h = { 8, 0, 0, 0 }},
> > > +
> > > /* Bayer RGB formats */
> > > { .format = V4L2_PIX_FMT_SBGGR8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
> > > { .format = V4L2_PIX_FMT_SGBRG8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
> > > @@ -448,6 +458,97 @@ void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
> > > }
> > > EXPORT_SYMBOL_GPL(v4l2_apply_frmsize_constraints);
> > >
> > > +size_t v4l2_pixfmt_afbc_header_size(int fourcc, int width, int height)
> > > +{
> > > + int width_in_block, height_in_block;
> > > +
> > > + if (!v4l2_is_format_afbc(fourcc))
> > > + return 0;
> > > +
> > > + switch (fourcc) {
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> > > + width_in_block = ALIGN(width, 16) >> 4;
> > > + height_in_block = ALIGN(height, 16) >> 4;
> > > + break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> > > + width_in_block = ALIGN(width, 32) >> 5;
> > > + height_in_block = ALIGN(height, 8) >> 3;
> > > + break;
> > > + }
> > > +
> > > + return ALIGN(width_in_block * 16 * height_in_block, 128);
> > > +}
> > > +EXPORT_SYMBOL_GPL(v4l2_pixfmt_afbc_header_size);
> > > +
> > > +size_t v4l2_pixfmt_afbc_payload_size(int fourcc, int width, int height)
> > > +{
> > > + int width_in_block, height_in_block, block_payload_size;
> > > +
> > > + if (!v4l2_is_format_afbc(fourcc))
> > > + return 0;
> > > +
> > > + switch (fourcc) {
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> > > + width_in_block = ALIGN(width, 16) >> 4;
> > > + height_in_block = ALIGN(height, 16) >> 4;
> > > + break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> > > + width_in_block = ALIGN(width, 32) >> 5;
> > > + height_in_block = ALIGN(height, 8) >> 3;
> > > + break;
> > > + }
> > > +
> > > + switch (fourcc) {
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> > > + block_payload_size = 384;
> > > + break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> > > + block_payload_size = 512;
> > > + break;
> > > + }
> > > +
> > > + return ALIGN(block_payload_size * width_in_block * height_in_block, 128);
> > > +}
> > > +EXPORT_SYMBOL_GPL(v4l2_pixfmt_afbc_payload_size);
> > > +
> > > +static int v4l2_fill_pixfmt_afbc(struct v4l2_pix_format_mplane *pixfmt,
> > > + const struct v4l2_format_info *info)
> > > +{
> > > + struct v4l2_plane_pix_format *plane = &pixfmt->plane_fmt[0];
> > > + unsigned int width = pixfmt->width;
> > > + unsigned int height = pixfmt->height;
> > > + unsigned int aligned_width = ALIGN(width, v4l2_format_block_width(info, 0));
> > > + unsigned int stride = DIV_ROUND_UP(aligned_width, info->hdiv) *
> > > + info->bpp[0] / info->bpp_div[0];
> > > + size_t header_size = v4l2_pixfmt_afbc_header_size(info->format, width, height);
> > > + size_t payload_size = v4l2_pixfmt_afbc_payload_size(info->format, width, height);
> > > +
> > > + plane->bytesperline = stride;
> > > + plane->sizeimage = header_size + payload_size;
> > I want to give some more thought on this, its literally by-pass
> > v4l2_apply_frmsize_constraints(), and that I don't like much.
> >
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +bool v4l2_is_format_afbc(int fourcc)
> > > +{
> > > + switch (fourcc) {
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10:
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10:
> > > + return true;
> > > + }
> > > +
> > > + return false;
> > > +}
> > > +EXPORT_SYMBOL_GPL(v4l2_is_format_afbc);
> > Does that really need to be exported ?
>
> Yes it is useful to not duplicate it in all drivers.
>
> >
> > > +
> > > int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
> > > u32 pixelformat, u32 width, u32 height)
> > > {
> > > @@ -464,6 +565,9 @@ int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
> > > pixfmt->pixelformat = pixelformat;
> > > pixfmt->num_planes = info->mem_planes;
> > >
> > > + if (v4l2_is_format_afbc(info->format))
> > > + return v4l2_fill_pixfmt_afbc(pixfmt, info);
> > > +
> > > if (info->mem_planes == 1) {
> > > plane = &pixfmt->plane_fmt[0];
> > > plane->bytesperline = v4l2_format_plane_stride(info, 0, width);
> > > diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
> > > index a2b650f4ec3c..016d4244c9ee 100644
> > > --- a/drivers/media/v4l2-core/v4l2-ioctl.c
> > > +++ b/drivers/media/v4l2-core/v4l2-ioctl.c
> > > @@ -1387,6 +1387,10 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
> > > case V4L2_PIX_FMT_YVU422M: descr = "Planar YVU 4:2:2 (N-C)"; break;
> > > case V4L2_PIX_FMT_YUV444M: descr = "Planar YUV 4:4:4 (N-C)"; break;
> > > case V4L2_PIX_FMT_YVU444M: descr = "Planar YVU 4:4:4 (N-C)"; break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16: descr = "AFBC 8-bit YUV420 16x16"; break;
> > Missing "split" in the descr, which will cause confusion in the future.
>
> Ok, just keep in mind that description field is limited to 32 bytes.
I know, pretty annoying. You already compressed YUV 4:2:0 to YUV420, perhaps you
can compress 8-bit in 8b. I'm out of idea if you need more.
Nicolas
>
> Benjamin
>
> >
> > Nicolas
> >
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8: descr = "AFBC 8-bit YUV420 32x8"; break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_16x16_10: descr = "AFBC 10-bit YUV420 16x16"; break;
> > > + case V4L2_PIX_FMT_AFBC_YUV420_32x8_10: descr = "AFBC 10-bit YUV420 32x8"; break;
> > > case V4L2_PIX_FMT_SBGGR8: descr = "8-bit Bayer BGBG/GRGR"; break;
> > > case V4L2_PIX_FMT_SGBRG8: descr = "8-bit Bayer GBGB/RGRG"; break;
> > > case V4L2_PIX_FMT_SGRBG8: descr = "8-bit Bayer GRGR/BGBG"; break;
> > > diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
> > > index f8b1faced79c..d1a75e6a7d4c 100644
> > > --- a/include/media/v4l2-common.h
> > > +++ b/include/media/v4l2-common.h
> > > @@ -549,6 +549,10 @@ static inline bool v4l2_is_format_bayer(const struct v4l2_format_info *f)
> > > return f && f->pixel_enc == V4L2_PIXEL_ENC_BAYER;
> > > }
> > >
> > > +bool v4l2_is_format_afbc(int fourcc);
> > > +size_t v4l2_pixfmt_afbc_header_size(int fourcc, int width, int height);
> > > +size_t v4l2_pixfmt_afbc_payload_size(int fourcc, int width, int height);
> > > +
> > > const struct v4l2_format_info *v4l2_format_info(u32 format);
> > > void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
> > > const struct v4l2_frmsize_stepwise *frmsize);
> > > diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
> > > index eda4492e40dc..88fafadbe13c 100644
> > > --- a/include/uapi/linux/videodev2.h
> > > +++ b/include/uapi/linux/videodev2.h
> > > @@ -698,6 +698,12 @@ struct v4l2_pix_format {
> > > #define V4L2_PIX_FMT_NV12M_8L128 v4l2_fourcc('N', 'A', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
> > > #define V4L2_PIX_FMT_NV12M_10BE_8L128 v4l2_fourcc_be('N', 'T', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
> > >
> > > +/* AFBC formats */
> > > +#define V4L2_PIX_FMT_AFBC_YUV420_16x16 v4l2_fourcc('A', '1', '6', '8') /* AFBC containing 8-bit YUV420 in 16x16 blocks, sparse, split */
> > > +#define V4L2_PIX_FMT_AFBC_YUV420_32x8 v4l2_fourcc('A', '3', '2', '8') /* AFBC containing 8-bit YUV420 in 32x8 blocks, sparse */
> > > +#define V4L2_PIX_FMT_AFBC_YUV420_16x16_10 v4l2_fourcc('A', '1', '6', 'a') /* AFBC containing 10-bit YUV420 in 16x16 blocks, sparse, split */
> > > +#define V4L2_PIX_FMT_AFBC_YUV420_32x8_10 v4l2_fourcc('A', '3', '2', 'a') /* AFBC containing 10-bit YUV420 in 32x8 blocks, sparse */
> > > +
> > > /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
> > > #define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B', 'A', '8', '1') /* 8 BGBG.. GRGR.. */
> > > #define V4L2_PIX_FMT_SGBRG8 v4l2_fourcc('G', 'B', 'R', 'G') /* 8 GBGB.. RGRG.. */
Attachment:
signature.asc
Description: This is a digitally signed message part