mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-14 20:51:07 +00:00
897 lines
26 KiB
C
897 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 <gdk/gdkdmabuffourccprivate.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");
|
|
|
|
if (ftruncate (fd, size) != 0)
|
|
g_error ("ftruncate failed");
|
|
|
|
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;
|
|
}
|