All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Ville Syrjälä" <ville.syrjala@linux.intel.com>
To: Maxime Ripard <maxime.ripard@bootlin.com>
Cc: Paul Kocialkowski <paul.kocialkowski@bootlin.com>,
	eben@raspberrypi.org, igt-dev@lists.freedesktop.org,
	Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Subject: Re: [igt-dev] [PATCH v6 03/13] fb: Create common function to convert frame formats
Date: Fri, 31 Aug 2018 16:41:10 +0300	[thread overview]
Message-ID: <20180831134110.GY5565@intel.com> (raw)
In-Reply-To: <f9e020246e4999bec72c9005e729b87b51391cdb.1535721136.git-series.maxime.ripard@bootlin.com>

On Fri, Aug 31, 2018 at 03:12:12PM +0200, Maxime Ripard wrote:
> The current code to convert between two buffer formats is quite tied to the
> cairo surface infrastructure. Since we'll want to reuse it, make that
> function more generic by introducing a common structure that passes all the
> arguments and a common function that will call the right functions we
> needed.
> 
> Reviewed-by: Eric Anholt <eric@anholt.net>
> Signed-off-by: Maxime Ripard <maxime.ripard@bootlin.com>
> ---
>  lib/igt_fb.c | 243 ++++++++++++++++++++++++++++++++--------------------
>  1 file changed, 153 insertions(+), 90 deletions(-)
> 
> diff --git a/lib/igt_fb.c b/lib/igt_fb.c
> index 4061fedec0c1..1914233786a5 100644
> --- a/lib/igt_fb.c
> +++ b/lib/igt_fb.c
> @@ -1384,6 +1384,24 @@ struct fb_convert_blit_upload {
>  	struct fb_blit_linear linear;
>  };
>  
> +struct fb_convert_buf {
> +	void			*ptr;
> +	unsigned int		stride;
> +	unsigned int		size;
> +	uint32_t		fmt;
> +	enum igt_color_encoding	color_encoding;
> +	enum igt_color_range	color_range;
> +	uint32_t		offsets[4];
> +};

I wonder if we can just use igt_fb for that instead of duplicating most
of it?

I have a patch that did something like that for the fb_blit_upload
stuff: https://patchwork.freedesktop.org/patch/239832/

> +
> +struct fb_convert {
> +	unsigned int		width;
> +	unsigned int		height;
> +
> +	struct fb_convert_buf	dst;
> +	struct fb_convert_buf	src;
> +};
> +
>  static uint8_t clamprgb(float val)
>  {
>  	return clamp((int)(val + 0.5f), 0, 255);
> @@ -1404,27 +1422,28 @@ static void write_rgb(uint8_t *rgb24, const struct igt_vec4 *rgb)
>  	rgb24[0] = clamprgb(rgb->d[2]);
>  }
>  
> -static void convert_nv12_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_upload *blit)
> +static void convert_nv12_to_rgb24(struct fb_convert *cvt)
>  {
>  	int i, j;
>  	const uint8_t *y, *uv;
> -	uint8_t *rgb24 = blit->rgb24.map;
> -	unsigned rgb24_stride = blit->rgb24.stride, planar_stride = blit->linear.stride;
> -	uint8_t *buf = malloc(blit->linear.size);
> -	struct igt_mat4 m = igt_ycbcr_to_rgb_matrix(fb->color_encoding,
> -						    fb->color_range);
> +	uint8_t *rgb24 = cvt->dst.ptr;
> +	unsigned int rgb24_stride = cvt->dst.stride;
> +	unsigned int planar_stride = cvt->src.stride;
> +	uint8_t *buf = malloc(cvt->src.size);
> +	struct igt_mat4 m = igt_ycbcr_to_rgb_matrix(cvt->src.color_encoding,
> +						    cvt->src.color_range);
>  
>  	/*
>  	 * Reading from the BO is awfully slow because of lack of read caching,
>  	 * it's faster to copy the whole BO to a temporary buffer and convert
>  	 * from there.
>  	 */
> -	igt_memcpy_from_wc(buf, blit->linear.map, blit->linear.size);
> -	y = &buf[blit->linear.offsets[0]];
> -	uv = &buf[blit->linear.offsets[1]];
> +	igt_memcpy_from_wc(buf, cvt->src.ptr, cvt->src.size);
> +	y = cvt->src.ptr + cvt->src.offsets[0];
> +	uv = cvt->src.ptr + cvt->src.offsets[1];
>  
> -	for (i = 0; i < fb->height / 2; i++) {
> -		for (j = 0; j < fb->width / 2; j++) {
> +	for (i = 0; i < cvt->height / 2; i++) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x2 pixel blocks */
>  			struct igt_vec4 yuv[4];
>  			struct igt_vec4 rgb[4];
> @@ -1449,7 +1468,7 @@ static void convert_nv12_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			write_rgb(&rgb24[j * 8 + 4 + rgb24_stride], &rgb[3]);
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			/* Convert 1x2 pixel block */
>  			struct igt_vec4 yuv[2];
>  			struct igt_vec4 rgb[2];
> @@ -1473,9 +1492,9 @@ static void convert_nv12_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  		uv += planar_stride;
>  	}
>  
> -	if (fb->height & 1) {
> +	if (cvt->height & 1) {
>  		/* Convert last row */
> -		for (j = 0; j < fb->width / 2; j++) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x1 pixel blocks */
>  			struct igt_vec4 yuv[2];
>  			struct igt_vec4 rgb[2];
> @@ -1493,7 +1512,7 @@ static void convert_nv12_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			write_rgb(&rgb24[j * 8 + 4], &rgb[0]);
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			/* Convert single pixel */
>  			struct igt_vec4 yuv;
>  			struct igt_vec4 rgb;
> @@ -1512,22 +1531,22 @@ static void convert_nv12_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  	free(buf);
>  }
>  
> -static void convert_rgb24_to_nv12(struct igt_fb *fb, struct fb_convert_blit_upload *blit)
> +static void convert_rgb24_to_nv12(struct fb_convert *cvt)
>  {
>  	int i, j;
> -	uint8_t *y = &blit->linear.map[blit->linear.offsets[0]];
> -	uint8_t *uv = &blit->linear.map[blit->linear.offsets[1]];
> -	const uint8_t *rgb24 = blit->rgb24.map;
> -	unsigned rgb24_stride = blit->rgb24.stride;
> -	unsigned planar_stride = blit->linear.stride;
> -	struct igt_mat4 m = igt_rgb_to_ycbcr_matrix(fb->color_encoding,
> -						    fb->color_range);
> -
> -	igt_assert_f(fb->drm_format == DRM_FORMAT_NV12,
> +	uint8_t *y = cvt->dst.ptr + cvt->dst.offsets[0];
> +	uint8_t *uv = cvt->dst.ptr + cvt->dst.offsets[1];
> +	const uint8_t *rgb24 = cvt->src.ptr;
> +	unsigned rgb24_stride = cvt->src.stride;
> +	unsigned planar_stride = cvt->dst.stride;
> +	struct igt_mat4 m = igt_rgb_to_ycbcr_matrix(cvt->dst.color_encoding,
> +						    cvt->dst.color_range);
> +
> +	igt_assert_f(cvt->dst.fmt == DRM_FORMAT_NV12,
>  		     "Conversion not implemented for !NV12 planar formats\n");
>  
> -	for (i = 0; i < fb->height / 2; i++) {
> -		for (j = 0; j < fb->width / 2; j++) {
> +	for (i = 0; i < cvt->height / 2; i++) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x2 pixel blocks */
>  			struct igt_vec4 rgb[4];
>  			struct igt_vec4 yuv[4];
> @@ -1556,7 +1575,7 @@ static void convert_rgb24_to_nv12(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			uv[j * 2 + 1] = (yuv[0].d[2] + yuv[2].d[2]) / 2.0f;
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			/* Convert 1x2 pixel block */
>  			struct igt_vec4 rgb[2];
>  			struct igt_vec4 yuv[2];
> @@ -1585,8 +1604,8 @@ static void convert_rgb24_to_nv12(struct igt_fb *fb, struct fb_convert_blit_uplo
>  	}
>  
>  	/* Last row cannot be interpolated between 2 pixels, take the single value */
> -	if (fb->height & 1) {
> -		for (j = 0; j < fb->width / 2; j++) {
> +	if (cvt->height & 1) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x1 pixel blocks */
>  			struct igt_vec4 rgb[2];
>  			struct igt_vec4 yuv[2];
> @@ -1603,7 +1622,7 @@ static void convert_rgb24_to_nv12(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			uv[j * 2 + 1] = yuv[0].d[2];
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			/* Convert single pixel */
>  			struct igt_vec4 rgb;
>  			struct igt_vec4 yuv;
> @@ -1640,27 +1659,28 @@ static const unsigned char *yuyv_swizzle(uint32_t format)
>  	}
>  }
>  
> -static void convert_yuyv_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_upload *blit)
> +static void convert_yuyv_to_rgb24(struct fb_convert *cvt)
>  {
>  	int i, j;
>  	const uint8_t *yuyv;
> -	uint8_t *rgb24 = blit->rgb24.map;
> -	unsigned rgb24_stride = blit->rgb24.stride, yuyv_stride = blit->linear.stride;
> -	uint8_t *buf = malloc(blit->linear.size);
> -	struct igt_mat4 m = igt_ycbcr_to_rgb_matrix(fb->color_encoding,
> -						    fb->color_range);
> -	const unsigned char *swz = yuyv_swizzle(fb->drm_format);
> +	uint8_t *rgb24 = cvt->dst.ptr;
> +	unsigned int rgb24_stride = cvt->dst.stride;
> +	unsigned int yuyv_stride = cvt->src.stride;
> +	uint8_t *buf = malloc(cvt->src.size);
> +	struct igt_mat4 m = igt_ycbcr_to_rgb_matrix(cvt->src.color_encoding,
> +						    cvt->src.color_range);
> +	const unsigned char *swz = yuyv_swizzle(cvt->src.fmt);
>  
>  	/*
>  	 * Reading from the BO is awfully slow because of lack of read caching,
>  	 * it's faster to copy the whole BO to a temporary buffer and convert
>  	 * from there.
>  	 */
> -	igt_memcpy_from_wc(buf, blit->linear.map, blit->linear.size);
> +	igt_memcpy_from_wc(buf, cvt->src.ptr, cvt->src.size);
>  	yuyv = buf;
>  
> -	for (i = 0; i < fb->height; i++) {
> -		for (j = 0; j < fb->width / 2; j++) {
> +	for (i = 0; i < cvt->height; i++) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x1 pixel blocks */
>  			struct igt_vec4 yuv[2];
>  			struct igt_vec4 rgb[2];
> @@ -1678,7 +1698,7 @@ static void convert_yuyv_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			write_rgb(&rgb24[j * 8 + 4], &rgb[1]);
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			struct igt_vec4 yuv;
>  			struct igt_vec4 rgb;
>  
> @@ -1699,25 +1719,25 @@ static void convert_yuyv_to_rgb24(struct igt_fb *fb, struct fb_convert_blit_uplo
>  	free(buf);
>  }
>  
> -static void convert_rgb24_to_yuyv(struct igt_fb *fb, struct fb_convert_blit_upload *blit)
> +static void convert_rgb24_to_yuyv(struct fb_convert *cvt)
>  {
>  	int i, j;
> -	uint8_t *yuyv = blit->linear.map;
> -	const uint8_t *rgb24 = blit->rgb24.map;
> -	unsigned rgb24_stride = blit->rgb24.stride;
> -	unsigned yuyv_stride = blit->linear.stride;
> -	struct igt_mat4 m = igt_rgb_to_ycbcr_matrix(fb->color_encoding,
> -						    fb->color_range);
> -	const unsigned char *swz = yuyv_swizzle(fb->drm_format);
> -
> -	igt_assert_f(fb->drm_format == DRM_FORMAT_YUYV ||
> -		     fb->drm_format == DRM_FORMAT_YVYU ||
> -		     fb->drm_format == DRM_FORMAT_UYVY ||
> -		     fb->drm_format == DRM_FORMAT_VYUY,
> +	uint8_t *yuyv = cvt->dst.ptr;
> +	const uint8_t *rgb24 = cvt->src.ptr;
> +	unsigned rgb24_stride = cvt->src.stride;
> +	unsigned yuyv_stride = cvt->dst.stride;
> +	struct igt_mat4 m = igt_rgb_to_ycbcr_matrix(cvt->dst.color_encoding,
> +						    cvt->dst.color_range);
> +	const unsigned char *swz = yuyv_swizzle(cvt->dst.fmt);
> +
> +	igt_assert_f(cvt->dst.fmt == DRM_FORMAT_YUYV ||
> +		     cvt->dst.fmt == DRM_FORMAT_YVYU ||
> +		     cvt->dst.fmt == DRM_FORMAT_UYVY ||
> +		     cvt->dst.fmt == DRM_FORMAT_VYUY,
>  		     "Conversion not implemented for !YUYV planar formats\n");
>  
> -	for (i = 0; i < fb->height; i++) {
> -		for (j = 0; j < fb->width / 2; j++) {
> +	for (i = 0; i < cvt->height; i++) {
> +		for (j = 0; j < cvt->width / 2; j++) {
>  			/* Convert 2x1 pixel blocks */
>  			struct igt_vec4 rgb[2];
>  			struct igt_vec4 yuv[2];
> @@ -1734,7 +1754,7 @@ static void convert_rgb24_to_yuyv(struct igt_fb *fb, struct fb_convert_blit_uplo
>  			yuyv[j * 4 + swz[3]] = (yuv[0].d[2] + yuv[1].d[2]) / 2.0f;
>  		}
>  
> -		if (fb->width & 1) {
> +		if (cvt->width & 1) {
>  			struct igt_vec4 rgb;
>  			struct igt_vec4 yuv;
>  
> @@ -1752,27 +1772,65 @@ static void convert_rgb24_to_yuyv(struct igt_fb *fb, struct fb_convert_blit_uplo
>  	}
>  }
>  
> +static void fb_convert(struct fb_convert *cvt)
> +{
> +	if (cvt->dst.fmt == DRM_FORMAT_RGB888) {
> +		switch (cvt->src.fmt) {
> +		case DRM_FORMAT_NV12:
> +			convert_nv12_to_rgb24(cvt);
> +			return;
> +		case DRM_FORMAT_YUYV:
> +		case DRM_FORMAT_YVYU:
> +		case DRM_FORMAT_UYVY:
> +		case DRM_FORMAT_VYUY:
> +			convert_yuyv_to_rgb24(cvt);
> +			return;
> +		}
> +	} else if (cvt->src.fmt == DRM_FORMAT_RGB888) {
> +		switch (cvt->dst.fmt) {
> +		case DRM_FORMAT_NV12:
> +			convert_rgb24_to_nv12(cvt);
> +			return;
> +		case DRM_FORMAT_YUYV:
> +		case DRM_FORMAT_YVYU:
> +		case DRM_FORMAT_UYVY:
> +		case DRM_FORMAT_VYUY:
> +			convert_rgb24_to_yuyv(cvt);
> +			return;
> +		}
> +	}
> +
> +	igt_assert_f(false,
> +		     "Conversion not implemented (from format 0x%x to 0x%x)\n",
> +		     cvt->src.fmt, cvt->dst.fmt);
> +}
> +
>  static void destroy_cairo_surface__convert(void *arg)
>  {
>  	struct fb_convert_blit_upload *blit = arg;
>  	struct igt_fb *fb = blit->fb;
> -
> -	/* Convert linear rgb back! */
> -	switch(fb->drm_format) {
> -	case DRM_FORMAT_NV12:
> -		convert_rgb24_to_nv12(fb, blit);
> -		break;
> -	case DRM_FORMAT_YUYV:
> -	case DRM_FORMAT_YVYU:
> -	case DRM_FORMAT_UYVY:
> -	case DRM_FORMAT_VYUY:
> -		convert_rgb24_to_yuyv(fb, blit);
> -		break;
> -	default:
> -		igt_assert_f(false, "Conversion not implemented for formats 0x%x\n",
> -			     fb->drm_format);
> -	}
> -
> +	struct fb_convert cvt = {
> +		.width	= fb->width,
> +		.height	= fb->height,
> +
> +		.dst	= {
> +			.ptr		= blit->linear.map,
> +			.fmt		= fb->drm_format,
> +			.stride		= blit->linear.stride,
> +			.size		= blit->linear.size,
> +			.color_encoding	= fb->color_encoding,
> +			.color_range	= fb->color_range,
> +		},
> +
> +		.src	= {
> +			.ptr		= blit->rgb24.map,
> +			.fmt		= DRM_FORMAT_RGB888,
> +			.stride		= blit->rgb24.stride,
> +			.size		= blit->rgb24.size,
> +		},
> +	};
> +
> +	fb_convert(&cvt);
>  	munmap(blit->rgb24.map, blit->rgb24.size);
>  
>  	if (blit->linear.handle)
> @@ -1788,6 +1846,8 @@ static void destroy_cairo_surface__convert(void *arg)
>  static void create_cairo_surface__convert(int fd, struct igt_fb *fb)
>  {
>  	struct fb_convert_blit_upload *blit = malloc(sizeof(*blit));
> +	struct fb_convert cvt = { 0 };
> +
>  	igt_assert(blit);
>  
>  	blit->fd = fd;
> @@ -1809,21 +1869,24 @@ static void create_cairo_surface__convert(int fd, struct igt_fb *fb)
>  		memcpy(blit->linear.offsets, fb->offsets, sizeof(fb->offsets));
>  	}
>  
> -	/* Convert to linear rgb! */
> -	switch(fb->drm_format) {
> -	case DRM_FORMAT_NV12:
> -		convert_nv12_to_rgb24(fb, blit);
> -		break;
> -	case DRM_FORMAT_YUYV:
> -	case DRM_FORMAT_YVYU:
> -	case DRM_FORMAT_UYVY:
> -	case DRM_FORMAT_VYUY:
> -		convert_yuyv_to_rgb24(fb, blit);
> -		break;
> -	default:
> -		igt_assert_f(false, "Conversion not implemented for formats 0x%x\n",
> -			     fb->drm_format);
> -	}
> +	cvt.width = fb->width;
> +	cvt.height = fb->height;
> +
> +	cvt.dst.ptr = blit->rgb24.map;
> +	cvt.dst.fmt = DRM_FORMAT_RGB888;
> +	cvt.dst.stride = blit->rgb24.stride;
> +	cvt.dst.size = blit->rgb24.size;
> +
> +	cvt.src.ptr = blit->linear.map;
> +	cvt.src.fmt = fb->drm_format;
> +	cvt.src.stride	= blit->linear.stride;
> +	cvt.src.size = blit->linear.size;
> +	cvt.src.color_encoding = fb->color_encoding;
> +	cvt.src.color_range = fb->color_range;
> +	memcpy(cvt.dst.offsets, blit->linear.offsets,
> +	       sizeof(blit->linear.offsets));
> +
> +	fb_convert(&cvt);
>  
>  	fb->cairo_surface =
>  		cairo_image_surface_create_for_data(blit->rgb24.map,
> -- 
> git-series 0.9.1

-- 
Ville Syrjälä
Intel
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

  reply	other threads:[~2018-08-31 13:41 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-08-31 13:12 [igt-dev] [PATCH v6 00/13] chamelium: Test the plane formats Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 01/13] fb: Add buffer map/unmap functions Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 02/13] fb: convert: Remove swizzle from the arguments Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 03/13] fb: Create common function to convert frame formats Maxime Ripard
2018-08-31 13:41   ` Ville Syrjälä [this message]
2018-09-05 13:16     ` Maxime Ripard
2018-09-06 11:41     ` Maxime Ripard
2018-09-06 12:11       ` Ville Syrjälä
2018-09-06 12:49         ` Maxime Ripard
2018-09-06 13:30           ` Ville Syrjälä
2018-08-31 13:12 ` [igt-dev] [PATCH v6 04/13] fb: Add format conversion routine Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 05/13] fb: Fix ARGB8888 color depth Maxime Ripard
2018-08-31 13:42   ` Ville Syrjälä
2018-09-05  9:35     ` Maxime Ripard
2018-09-05 10:14       ` Ville Syrjälä
2018-09-05 13:19         ` Maxime Ripard
2018-09-05 13:37           ` Ville Syrjälä
2018-09-06  7:22             ` Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 06/13] fb: Add support for conversions through pixman Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 07/13] fb: Add depth lookup function Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 08/13] fb: Add more formats Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 09/13] chamelium: Split CRC test function in two Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 10/13] chamelium: Change our pattern for a custom one if needed Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 11/13] chamelium: Add format support Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 12/13] chamelium: Add format subtests Maxime Ripard
2018-08-31 13:12 ` [igt-dev] [PATCH v6 13/13] tests: Add chamelium formats subtests to vc4 test lists Maxime Ripard
2018-09-03  8:33 ` [igt-dev] ✓ Fi.CI.BAT: success for chamelium: Test the plane formats (rev5) Patchwork
2018-09-03  9:43 ` [igt-dev] ✗ Fi.CI.IGT: failure " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180831134110.GY5565@intel.com \
    --to=ville.syrjala@linux.intel.com \
    --cc=eben@raspberrypi.org \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=maxime.ripard@bootlin.com \
    --cc=paul.kocialkowski@bootlin.com \
    --cc=thomas.petazzoni@bootlin.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.