From 98e883f9b41c8ffde81d18156e2c34ff19ad5e06 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 13 Oct 2023 20:26:19 -0400 Subject: [PATCH] Make a test app with dma heaps This uses the dma-heap kernel api to create a dma-buf and use it for a GdkDmabufTexture. It supports a few formats to test how well GL conversion of YUV works. The YUV code is adapted from weston tests. --- tests/meson.build | 5 +- tests/testdmabuf.c | 445 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 tests/testdmabuf.c diff --git a/tests/meson.build b/tests/meson.build index b974b321cf..81726f5c7b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -125,7 +125,10 @@ gtk_tests = [ ] if os_unix - gtk_tests += [['testfontchooserdialog']] + gtk_tests += [ + ['testfontchooserdialog'], + ['testdmabuf'], + ] endif if x11_enabled diff --git a/tests/testdmabuf.c b/tests/testdmabuf.c new file mode 100644 index 0000000000..075906bcd5 --- /dev/null +++ b/tests/testdmabuf.c @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include +#include + +/* For this to work, you may need to give /dev/dma_heap/system + * lax permissions. + */ + +static int +allocate_dma_buf (gsize size) +{ + static int fd = -1; + struct dma_heap_allocation_data heap_data; + int ret; + + if (fd == -1) + { + fd = open ("/dev/dma_heap/system", O_RDONLY | O_CLOEXEC); + if (fd == -1) + g_error ("Failed to open /dev/dma_heap/system"); + } + + heap_data.len = size; + heap_data.fd = 0; + heap_data.fd_flags = O_RDWR | O_CLOEXEC; + heap_data.heap_flags = 0; + + ret = ioctl (fd, DMA_HEAP_IOCTL_ALLOC, &heap_data); + if (ret) + g_error ("dma-buf allocation failed"); + + return heap_data.fd; +} + +static void +populate_dma_buf (int fd, + guchar *data, + gsize size) +{ + guchar *buf; + + buf = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0); + memcpy (buf, data, size); + munmap (buf, size); +} + + +/* The YUV conversion code is adapted from weston/tests/yuv-buffer-test.c */ + +/* + * Based on Rec. ITU-R BT.601-7 + * + * This is intended to be obvious and accurate, not fast. + */ +static void +x8r8g8b8_to_ycbcr8_bt601 (guint32 xrgb, + guchar *y_out, + guchar *cb_out, + guchar *cr_out) +{ + double y, cb, cr; + double r = (xrgb >> 16) & 0xff; + double g = (xrgb >> 8) & 0xff; + double b = (xrgb >> 0) & 0xff; + + /* normalize to [0.0, 1.0] */ + r /= 255.0; + g /= 255.0; + b /= 255.0; + + /* Y normalized to [0.0, 1.0], Cb and Cr [-0.5, 0.5] */ + y = 0.299 * r + 0.587 * g + 0.114 * b; + cr = (r - y) / 1.402; + cb = (b - y) / 1.772; + + /* limited range quantization to 8 bit */ + *y_out = round(219.0 * y + 16.0); + if (cr_out) + *cr_out = round(224.0 * cr + 128.0); + if (cb_out) + *cb_out = round(224.0 * cb + 128.0); +} + +/* + * 3 plane YCbCr + * plane 0: Y plane, [7:0] Y + * plane 1: Cb plane, [7:0] Cb + * plane 2: Cr plane, [7:0] Cr + * YUV420: 2x2 subsampled Cb (1) and Cr (2) planes + * YUV444: no subsampling + */ +static guchar * +y_u_v_create_buffer (uint32_t drm_format, + guchar *rgb_data, + int rgb_width, + int rgb_height, + int *size, + int *u_offset, + int *v_offset) +{ + gsize bytes; + int x, y; + guint32 *rgb_row; + guchar *y_base; + guchar *u_base; + guchar *v_base; + guchar *y_row; + guchar *u_row; + guchar *v_row; + guint32 argb; + int sub = (drm_format == DRM_FORMAT_YUV420) ? 2 : 1; + guchar *buf; + + g_assert (drm_format == DRM_FORMAT_YUV420 || + drm_format == DRM_FORMAT_YUV444); + + /* Full size Y plus quarter U and V */ + bytes = rgb_width * rgb_height + + (rgb_width / sub) * (rgb_height / sub) * 2; + + buf = g_new (guchar, bytes); + + *size = bytes; + *u_offset = rgb_width * rgb_height; + *v_offset = *u_offset + (rgb_width / sub) * (rgb_height / sub); + + y_base = buf; + u_base = y_base + rgb_width * rgb_height; + v_base = u_base + (rgb_width / sub) * (rgb_height / sub); + + for (y = 0; y < rgb_height; y++) + { + rgb_row = (guint32 *) (rgb_data + y * 4 * rgb_width); + y_row = y_base + y * rgb_width; + u_row = u_base + (y / sub) * (rgb_width / sub); + v_row = v_base + (y / sub) * (rgb_width / sub); + + for (x = 0; x < rgb_width; x++) + { + /* + * Sub-sample the source image instead, so that U and V + * sub-sampling does not require proper + * filtering/averaging/siting. + */ + argb = *(rgb_row + x / 2 * 2); + + /* + * A stupid way of "sub-sampling" chroma. This does not + * do the necessary filtering/averaging/siting or + * alternate Cb/Cr rows. + */ + if ((y & (sub - 1)) == 0 && (x & (sub - 1)) == 0) + x8r8g8b8_to_ycbcr8_bt601 (argb, y_row + x, u_row + x / sub, v_row + x / sub); + else + x8r8g8b8_to_ycbcr8_bt601 (argb, y_row + x, NULL, NULL); + } + } + + return buf; +} + +/* + * 2 plane YCbCr + * plane 0 = Y plane, [7:0] Y + * plane 1 = Cr:Cb plane, [15:0] Cr:Cb little endian + * 2x2 subsampled Cr:Cb plane + */ +static guchar * +nv12_create_buffer (uint32_t drm_format, + guchar *rgb_data, + int rgb_width, + int rgb_height, + int *size, + int *uv_offset) +{ + size_t bytes; + int x, y; + uint32_t *rgb_row; + uint8_t *y_base; + uint16_t *uv_base; + uint8_t *y_row; + uint16_t *uv_row; + uint32_t argb; + uint8_t cr; + uint8_t cb; + guchar *buf; + + g_assert (drm_format == DRM_FORMAT_NV12); + + /* Full size Y, quarter UV */ + bytes = rgb_width * rgb_height + + (rgb_width / 2) * (rgb_height / 2) * sizeof (uint16_t); + + buf = g_new0 (guchar, bytes); + + *uv_offset = rgb_width * rgb_height; + y_base = buf; + uv_base = (uint16_t *)(y_base + rgb_width * rgb_height); + + for (y = 0; y < rgb_height; y++) + { + rgb_row = (uint32_t *) (rgb_data + y * 4 * rgb_width); + y_row = y_base + y * rgb_width; + uv_row = (uint16_t *) (uv_base + (y / 2) * (rgb_width / 2)); + + for (x = 0; x < rgb_width; x++) + { + /* + * Sub-sample the source image instead, so that U and V + * sub-sampling does not require proper + * filtering/averaging/siting. + */ + argb = *(rgb_row + x / 2 * 2); + + /* + * A stupid way of "sub-sampling" chroma. This does not + * do the necessary filtering/averaging/siting. + */ + if ((y & 1) == 0 && (x & 1) == 0) + { + x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x, &cb, &cr); + *(uv_row + x / 2) = ((uint16_t)cr << 8) | cb; + } + else + { + x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x, NULL, NULL); + } + } + } + + return buf; +} + +static GdkTexture * +make_dmabuf_texture (const char *filename, + guint32 format) +{ + GdkTexture *texture; + int width, height; + gsize rgb_stride, rgb_size; + guchar *rgb_data; + int fd; + GdkDmabufTextureBuilder *builder; + GError *error = NULL; + + texture = gdk_texture_new_from_filename (filename, NULL); + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + rgb_stride = 4 * width; + rgb_size = rgb_stride * height; + + rgb_data = g_new0 (guchar, rgb_size); + + gdk_texture_download (texture, rgb_data, rgb_stride); + + g_object_unref (texture); + + builder = gdk_dmabuf_texture_builder_new (); + + gdk_dmabuf_texture_builder_set_display (builder, gdk_display_get_default ()); + gdk_dmabuf_texture_builder_set_width (builder, width); + gdk_dmabuf_texture_builder_set_height (builder, height); + gdk_dmabuf_texture_builder_set_fourcc (builder, format); + gdk_dmabuf_texture_builder_set_modifier (builder, DRM_FORMAT_MOD_LINEAR); + + if (format == DRM_FORMAT_XRGB8888 || + format == DRM_FORMAT_ARGB8888) + { + gdk_dmabuf_texture_builder_set_n_planes (builder, 1); + + fd = allocate_dma_buf (rgb_size); + populate_dma_buf (fd, rgb_data, rgb_size); + + gdk_dmabuf_texture_builder_set_fd (builder, 0, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 0, rgb_stride); + } + else if (format == DRM_FORMAT_YUV420) + { + guchar *buf; + int size, u_offset, v_offset; + + gdk_dmabuf_texture_builder_set_n_planes (builder, 3); + + buf = y_u_v_create_buffer (format, rgb_data, width, height, &size, &u_offset, &v_offset); + + fd = allocate_dma_buf (width * height); + populate_dma_buf (fd, buf, width * height); + + gdk_dmabuf_texture_builder_set_fd (builder, 0, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 0, width); + gdk_dmabuf_texture_builder_set_offset (builder, 0, 0); + + fd = allocate_dma_buf ((width / 2) * (height / 2)); + populate_dma_buf (fd, buf + u_offset, (width / 2) * (height / 2)); + + gdk_dmabuf_texture_builder_set_fd (builder, 1, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 1, width / 2); + gdk_dmabuf_texture_builder_set_offset (builder, 1, 0); + + fd = allocate_dma_buf ((width / 2) * (height / 2)); + populate_dma_buf (fd, buf + v_offset, (width / 2) * (height / 2)); + + gdk_dmabuf_texture_builder_set_fd (builder, 2, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 2, width / 2); + gdk_dmabuf_texture_builder_set_offset (builder, 2, 0); + + g_free (buf); + } + else if (format == DRM_FORMAT_NV12) + { + guchar *buf; + int size, uv_offset; + + gdk_dmabuf_texture_builder_set_n_planes (builder, 2); + + buf = nv12_create_buffer (format, rgb_data, width, height, &size, &uv_offset); + + fd = allocate_dma_buf (width * height); + populate_dma_buf (fd, buf, width * height); + + gdk_dmabuf_texture_builder_set_fd (builder, 0, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 0, width); + gdk_dmabuf_texture_builder_set_offset (builder, 0, 0); + + fd = allocate_dma_buf ((width / 2) * (height / 2) * sizeof (uint16_t)); + populate_dma_buf (fd, buf + uv_offset, (width / 2) * (height / 2) * sizeof (uint16_t)); + + gdk_dmabuf_texture_builder_set_fd (builder, 1, fd); + gdk_dmabuf_texture_builder_set_stride (builder, 1, width); + gdk_dmabuf_texture_builder_set_offset (builder, 1, 0); + + g_free (buf); + } + + g_free (rgb_data); + + texture = gdk_dmabuf_texture_builder_build (builder, NULL, NULL, &error); + if (!texture) + g_error ("Failed to create dmabuf texture: %s", error->message); + + g_object_unref (builder); + + return texture; +} + +static guint32 supported_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUV420, + DRM_FORMAT_NV12, +}; + +static gboolean +format_is_supported (guint32 fmt) +{ + for (int i = 0; i < G_N_ELEMENTS (supported_formats); i++) + { + if (supported_formats[i] == fmt) + return TRUE; + } + + return FALSE; +} + +static char * +supported_formats_to_string (void) +{ + GString *s; + + s = g_string_new (""); + for (int i = 0; i < G_N_ELEMENTS (supported_formats); i++) + { + if (s->len) + g_string_append (s, ", "); + g_string_append_printf (s, "%.4s", (char *)&supported_formats[i]); + } + + return g_string_free (s, FALSE); +} + +G_GNUC_NORETURN +static void +usage (void) +{ + char *formats = supported_formats_to_string (); + g_print ("Usage: testdmabuf FORMAT FILE\n" + "Supported formats: %s\n", formats); + g_free (formats); + exit (1); +} + +static guint32 +parse_format (const char *a) +{ + if (strlen (a) == 4) + { + guint32 format = fourcc_code (a[0], a[1], a[2], a[3]); + if (format_is_supported (format)) + return format; + } + + usage (); + return 0; +} + +int +main (int argc, char *argv[]) +{ + GdkTexture *texture; + GtkWidget *window, *picture; + char *filename; + guint32 format; + + if (argc != 3) + usage (); + + format = parse_format (argv[1]); + filename = argv[2]; + + gtk_init (); + + /* Get the list of supported formats with GDK_DEBUG=opengl */ + gdk_display_get_dmabuf_formats (gdk_display_get_default ()); + + texture = make_dmabuf_texture (filename, format); + + gdk_texture_save_to_png (texture, "testdmabuf.out.png"); + + window = gtk_window_new (); + + picture = gtk_picture_new_for_paintable (GDK_PAINTABLE (texture)); + + gtk_window_set_child (GTK_WINDOW (window), picture); + + gtk_window_present (GTK_WINDOW (window)); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); + + return 0; +}