gtk/tests/testdmabuf.c
Matthias Clasen 70380661fe testdmabuf: Test dmabuf viewports
Allow specifying padding via --padding. The argument to --padding
is a string of up to 4 comma-separated numbers, for the left, right,
top, bottom padding. If less numbers are given, the remaining ones
are set to zero.

This commit also includes an image that can be used for testing with

testdmabuf --padding 20,20,20,20 NV12 padded.png
2024-02-11 21:49:34 -05:00

896 lines
26 KiB
C

#include <gtk/gtk.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/dma-heap.h>
#include <drm_fourcc.h>
#ifdef GDK_RENDERING_VULKAN
#include <vulkan/vulkan.h>
#endif
#include "gtkclipperprivate.h"
/* For this to work, you may need to give /dev/dma_heap/system
* lax permissions.
*/
static int dma_heap_fd = -1;
#ifdef GDK_RENDERING_VULKAN
static uint32_t vk_memory_type_index = 0;
static VkDevice vk_device = VK_NULL_HANDLE;
#endif
static gboolean
initialize_dma_heap (void)
{
dma_heap_fd = open ("/dev/dma_heap/system", O_RDONLY | O_CLOEXEC);
return dma_heap_fd != -1;
}
static gboolean
initialize_vulkan (void)
{
#ifdef GDK_RENDERING_VULKAN
VkInstance vk_instance;
VkPhysicalDevice vk_physical_device;
VkResult res;
uint32_t i, n_devices = 1;
VkPhysicalDeviceMemoryProperties properties;
if (vkCreateInstance (&(VkInstanceCreateInfo) {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &(VkApplicationInfo) {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = g_get_application_name (),
.applicationVersion = 0,
.pEngineName = "GTK testsuite",
.engineVersion = VK_MAKE_VERSION (GDK_MAJOR_VERSION, GDK_MINOR_VERSION, GDK_MICRO_VERSION),
.apiVersion = VK_API_VERSION_1_0,
},
.enabledExtensionCount = 3,
.ppEnabledExtensionNames = (const char * [3]) {
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME,
}
},
NULL,
&vk_instance) != VK_SUCCESS)
return FALSE;
res = vkEnumeratePhysicalDevices (vk_instance, &n_devices, &vk_physical_device);
if (res != VK_SUCCESS && res != VK_INCOMPLETE)
{
vkDestroyInstance (vk_instance, NULL);
return FALSE;
}
if (vkCreateDevice (vk_physical_device,
&(VkDeviceCreateInfo) {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &(VkDeviceQueueCreateInfo) {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = (float []) { 1.0f },
},
.enabledExtensionCount = 11,
.ppEnabledExtensionNames = (const char * [11]) {
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_KHR_MAINTENANCE_1_EXTENSION_NAME,
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
},
},
NULL,
&vk_device) != VK_SUCCESS)
{
vkDestroyInstance (vk_instance, NULL);
return FALSE;
}
vkGetPhysicalDeviceMemoryProperties (vk_physical_device, &properties);
#define REQUIRED_FLAGS (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
for (i = 0; i < properties.memoryTypeCount; i++)
{
if ((properties.memoryTypes[i].propertyFlags & REQUIRED_FLAGS) == REQUIRED_FLAGS)
break;
}
#undef REQUIRED_FLAGS
if (i >= properties.memoryTypeCount)
{
vkDestroyDevice (vk_device, NULL);
vkDestroyInstance (vk_instance, NULL);
vk_device = VK_NULL_HANDLE;
return FALSE;
}
vk_memory_type_index = i;
return TRUE;
#else
return FALSE;
#endif
}
#ifdef GDK_RENDERING_VULKAN
static int
allocate_vulkan (gsize size)
{
VkDeviceMemory vk_memory;
PFN_vkGetMemoryFdKHR func_vkGetMemoryFdKHR;
int fd;
func_vkGetMemoryFdKHR = (PFN_vkGetMemoryFdKHR) vkGetDeviceProcAddr (vk_device, "vkGetMemoryFdKHR");
if (vkAllocateMemory (vk_device,
&(VkMemoryAllocateInfo) {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = size,
.memoryTypeIndex = vk_memory_type_index,
.pNext = &(VkExportMemoryAllocateInfo) {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
},
},
NULL,
&vk_memory) != VK_SUCCESS)
return -1;
if (func_vkGetMemoryFdKHR (vk_device,
&(VkMemoryGetFdInfoKHR) {
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = vk_memory,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
},
&fd) != VK_SUCCESS)
return -1;
return fd;
}
#endif
static int
allocate_dma_buf (gsize size)
{
struct dma_heap_allocation_data heap_data;
int ret;
heap_data.len = size;
heap_data.fd = 0;
heap_data.fd_flags = O_RDWR | O_CLOEXEC;
heap_data.heap_flags = 0;
ret = ioctl (dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &heap_data);
if (ret)
g_error ("dma-buf allocation failed");
return heap_data.fd;
}
static int
allocate_memfd (gsize size)
{
int fd;
fd = memfd_create ("buffer", MFD_CLOEXEC);
if (fd == -1)
g_error ("memfd allocation failed");
ftruncate (fd, size);
return fd;
}
static int
allocate_buffer (gsize size)
{
#ifdef GDK_RENDERING_VULKAN
if (vk_device)
return allocate_vulkan (size);
#endif
if (dma_heap_fd != -1)
return allocate_dma_buf (size);
else
return allocate_memfd (size);
}
static void
populate_buffer (int fd,
const 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];
/*
* 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;
uint8_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 plane, half size UV plane */
bytes = rgb_width * rgb_height +
rgb_width * (rgb_height / 2);
*size = bytes;
buf = g_new0 (guchar, bytes);
*uv_offset = rgb_width * rgb_height;
y_base = buf;
uv_base = 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);
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];
/*
* 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 void
texture_builder_set_planes (GdkDmabufTextureBuilder *builder,
gboolean disjoint,
const guchar *buf,
unsigned size,
unsigned n_planes,
unsigned strides[4],
unsigned sizes[4])
{
gdk_dmabuf_texture_builder_set_n_planes (builder, n_planes);
if (disjoint)
{
unsigned offset = 0;
unsigned i;
for (i = 0; i < n_planes; i++)
{
int fd = allocate_buffer (sizes[i]);
populate_buffer (fd, buf + offset, sizes[i]);
gdk_dmabuf_texture_builder_set_fd (builder, i, fd);
gdk_dmabuf_texture_builder_set_stride (builder, i, strides[i]);
gdk_dmabuf_texture_builder_set_offset (builder, i, 0);
offset += sizes[i];
}
}
else
{
unsigned offset = 0;
unsigned i;
int fd = allocate_buffer (size);
populate_buffer (fd, buf, size);
for (i = 0; i < n_planes; i++)
{
gdk_dmabuf_texture_builder_set_fd (builder, i, fd);
gdk_dmabuf_texture_builder_set_stride (builder, i, strides[i]);
gdk_dmabuf_texture_builder_set_offset (builder, i, offset);
offset += sizes[i];
}
}
}
static GdkTexture *
make_dmabuf_texture (GdkTexture *texture,
guint32 format,
gboolean disjoint,
gboolean premultiplied,
gboolean flip)
{
GdkTextureDownloader *downloader;
int width, height;
gsize rgb_stride, rgb_size;
guchar *rgb_data;
int fd;
GdkDmabufTextureBuilder *builder;
GError *error = NULL;
if (initialize_vulkan ())
g_print ("Using Vulkan\n");
else if (initialize_dma_heap ())
g_print ("Using dma_heap\n");
else
g_print ("Using memfd\n");
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);
downloader = gdk_texture_downloader_new (texture);
if (premultiplied)
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_B8G8R8A8_PREMULTIPLIED);
else
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_B8G8R8A8);
gdk_texture_downloader_download_into (downloader, rgb_data, rgb_stride);
gdk_texture_downloader_free (downloader);
if (flip)
{
for (int y = 0; y < height; y++)
{
guint32 *row = (guint32 *) (rgb_data + y * rgb_stride);
for (int x = 0; x < width / 2; x++)
{
guint32 p = row[x];
row[x] = row[width - 1 - x];
row[width - 1 - x] = p;
}
}
}
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);
gdk_dmabuf_texture_builder_set_premultiplied (builder, premultiplied);
if (format == DRM_FORMAT_XRGB8888 ||
format == DRM_FORMAT_ARGB8888)
{
gdk_dmabuf_texture_builder_set_n_planes (builder, 1);
fd = allocate_buffer (rgb_size);
populate_buffer (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_XRGB8888_A8)
{
guchar *alpha_data;
gsize alpha_stride;
gsize alpha_size;
gdk_dmabuf_texture_builder_set_n_planes (builder, 2);
fd = allocate_buffer (rgb_size);
populate_buffer (fd, rgb_data, rgb_size);
gdk_dmabuf_texture_builder_set_fd (builder, 0, fd);
gdk_dmabuf_texture_builder_set_stride (builder, 0, rgb_stride);
alpha_stride = width;
alpha_size = alpha_stride * height;
alpha_data = g_new0 (guchar, alpha_size);
for (gsize i = 0; i < height; i++)
for (gsize j = 0; j < width; j++)
alpha_data[i * alpha_stride + j] = rgb_data[i * rgb_stride + j * 4 + 3];
fd = allocate_buffer (alpha_size);
populate_buffer (fd, alpha_data, alpha_size);
gdk_dmabuf_texture_builder_set_fd (builder, 1, fd);
gdk_dmabuf_texture_builder_set_stride (builder, 1, alpha_stride);
g_free (alpha_data);
}
else if (format == DRM_FORMAT_YUV420)
{
guchar *buf;
int size, u_offset, v_offset;
buf = y_u_v_create_buffer (format, rgb_data, width, height, &size, &u_offset, &v_offset);
texture_builder_set_planes (builder,
disjoint,
buf,
size,
3,
(unsigned[4]) { width, width / 2, width / 2 },
(unsigned[4]) { width * height, width * height / 4, width * height / 4 });
g_free (buf);
}
else if (format == DRM_FORMAT_NV12)
{
guchar *buf;
int size, uv_offset;
buf = nv12_create_buffer (format, rgb_data, width, height, &size, &uv_offset);
texture_builder_set_planes (builder,
disjoint,
buf,
size,
2,
(unsigned[4]) { width, width, },
(unsigned[4]) { width * height, width * height / 2 });
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,
DRM_FORMAT_XRGB8888_A8,
};
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 [--undecorated][--disjoint][--download-to FILE][--padding PADDING] 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;
}
static gboolean
toggle_fullscreen (GtkWidget *widget,
GVariant *args,
gpointer data)
{
GtkWindow *window = GTK_WINDOW (widget);
if (gtk_window_is_fullscreen (window))
gtk_window_unfullscreen (window);
else
gtk_window_fullscreen (window);
return TRUE;
}
static gboolean
toggle_overlay (GtkWidget *widget,
GVariant *args,
gpointer data)
{
static GtkWidget *child = NULL;
GtkOverlay *overlay = data;
if (child)
{
gtk_overlay_remove_overlay (overlay, child);
child = NULL;
}
else
{
GtkWidget *spinner;
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
child = gtk_box_new (0, FALSE);
gtk_box_append (GTK_BOX (child), spinner);
gtk_box_append (GTK_BOX (child), gtk_image_new_from_icon_name ("media-playback-start-symbolic"));
gtk_widget_set_halign (child, GTK_ALIGN_CENTER);
gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
gtk_overlay_add_overlay (overlay, child);
}
return TRUE;
}
static GdkTexture *texture;
static GdkTexture *texture_flipped;
static gboolean
toggle_flip (GtkWidget *widget,
GVariant *args,
gpointer data)
{
GtkPicture *picture = data;
if (!texture_flipped)
return FALSE;
if (gtk_picture_get_paintable (picture) == GDK_PAINTABLE (texture))
gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture_flipped));
else
gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture));
return TRUE;
}
static gboolean
toggle_start (GtkWidget *widget,
GVariant *args,
gpointer data)
{
GtkWidget *offload = data;
if (gtk_widget_get_halign (offload) == GTK_ALIGN_CENTER)
gtk_widget_set_halign (offload, GTK_ALIGN_START);
else
gtk_widget_set_halign (offload, GTK_ALIGN_CENTER);
return TRUE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *offload, *picture, *overlay;
char *filename;
guint32 format;
gboolean disjoint = FALSE;
gboolean premultiplied = TRUE;
gboolean decorated = TRUE;
gboolean fullscreen = FALSE;
unsigned int i;
const char *save_filename = NULL;
GtkEventController *controller;
GtkShortcutTrigger *trigger;
GtkShortcutAction *action;
GtkShortcut *shortcut;
GdkPaintable *paintable;
GdkTexture *orig;
int padding[4] = { 0, }; /* left, right, top, bottom */
int padding_set = 0;
for (i = 1; i < argc; i++)
{
if (g_str_equal (argv[i], "--disjoint"))
disjoint = TRUE;
else if (g_str_equal (argv[i], "--undecorated"))
decorated = FALSE;
else if (g_str_equal (argv[i], "--fullscreen"))
fullscreen = TRUE;
else if (g_str_equal (argv[i], "--unpremultiplied"))
premultiplied = FALSE;
else if (g_str_equal (argv[i], "--download-to"))
{
i++;
if (i == argc)
usage ();
save_filename = argv[i];
}
else if (g_str_equal (argv[i], "--padding"))
{
if (padding_set < 4)
{
char **strv;
i++;
if (i == argc)
usage ();
strv = g_strsplit (argv[i], ",", 0);
if (g_strv_length (strv) > 4)
g_error ("Too much padding");
for (padding_set = 0; padding_set < 4; padding_set++)
{
guint64 num;
GError *error = NULL;
if (!strv[padding_set])
break;
if (!g_ascii_string_to_unsigned (strv[padding_set], 10, 0, 100, &num, &error))
g_error ("%s", error->message);
padding[padding_set] = (int) num;
}
}
else
g_error ("Too much padding");
}
else
break;
}
if (argc - i != 2)
{
usage ();
return 1;
}
format = parse_format (argv[argc - 2]);
filename = argv[argc - 1];
gtk_init ();
/* Get the list of supported formats with GDK_DEBUG=opengl */
gdk_display_get_dmabuf_formats (gdk_display_get_default ());
orig = gdk_texture_new_from_filename (filename, NULL);
texture = make_dmabuf_texture (orig, format, disjoint, premultiplied, FALSE);
texture_flipped = make_dmabuf_texture (orig, format, disjoint, premultiplied, TRUE);
g_object_unref (orig);
if (padding_set > 0)
{
paintable = gtk_clipper_new (GDK_PAINTABLE (texture),
&GRAPHENE_RECT_INIT (padding[0],
padding[2],
gdk_texture_get_width (texture) - padding[0] - padding[1],
gdk_texture_get_height (texture) - padding[2] - padding[3]));
}
else
paintable = GDK_PAINTABLE (texture);
if (save_filename)
gdk_texture_save_to_png (texture, save_filename);
window = gtk_window_new ();
gtk_window_set_decorated (GTK_WINDOW (window), decorated);
if (fullscreen)
gtk_window_fullscreen (GTK_WINDOW (window));
picture = gtk_picture_new_for_paintable (paintable);
offload = gtk_graphics_offload_new (picture);
gtk_widget_set_halign (offload, GTK_ALIGN_CENTER);
gtk_widget_set_valign (offload, GTK_ALIGN_CENTER);
overlay = gtk_overlay_new ();
gtk_overlay_set_child (GTK_OVERLAY (overlay), offload);
gtk_window_set_child (GTK_WINDOW (window), overlay);
controller = gtk_shortcut_controller_new ();
trigger = gtk_keyval_trigger_new (GDK_KEY_F11, GDK_NO_MODIFIER_MASK);
action = gtk_callback_action_new (toggle_fullscreen, NULL, NULL);
shortcut = gtk_shortcut_new (trigger, action);
gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
trigger = gtk_keyval_trigger_new (GDK_KEY_O, GDK_CONTROL_MASK);
action = gtk_callback_action_new (toggle_overlay, overlay, NULL);
shortcut = gtk_shortcut_new (trigger, action);
gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
trigger = gtk_keyval_trigger_new (GDK_KEY_F, GDK_CONTROL_MASK);
action = gtk_callback_action_new (toggle_flip, picture, NULL);
shortcut = gtk_shortcut_new (trigger, action);
gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
trigger = gtk_keyval_trigger_new (GDK_KEY_S, GDK_CONTROL_MASK);
action = gtk_callback_action_new (toggle_start, offload, NULL);
shortcut = gtk_shortcut_new (trigger, action);
gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
gtk_widget_add_controller (window, controller);
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;
}