/* gdkdmabuf.c
*
* Copyright 2023 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkdmabufprivate.h"
#include "gdkdebugprivate.h"
#include "gdkdmabuffourccprivate.h"
#include "gdkdmabuftextureprivate.h"
#include "gdkmemoryformatprivate.h"
#ifdef HAVE_DMABUF
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/dma-buf.h>
#ifdef HAVE_UDMABUF
#include <linux/udmabuf.h>
#endif
#include <epoxy/egl.h>
#include <errno.h>
GdkDmabufFormats *
gdk_dmabuf_get_mmap_formats (void )
{
static GdkDmabufFormats *formats = NULL;
if (formats == NULL)
{
GdkDmabufFormatsBuilder *builder;
guint32 fourcc;
gsize i;
builder = gdk_dmabuf_formats_builder_new ();
for (i = 0 ; i < GDK_MEMORY_N_FORMATS; i++)
{
fourcc = gdk_memory_format_get_dmabuf_rgb_fourcc (i);
if (fourcc != 0 )
{
GDK_DEBUG (DMABUF,
"mmap advertises dmabuf format %.4s::%016" G_GINT64_MODIFIER "x as RGB" ,
(char *) &fourcc, (guint64) DRM_FORMAT_MOD_LINEAR);
gdk_dmabuf_formats_builder_add_format (builder, fourcc, DRM_FORMAT_MOD_LINEAR);
}
fourcc = gdk_memory_format_get_dmabuf_yuv_fourcc (i);
if (fourcc != 0 )
{
GDK_DEBUG (DMABUF,
"mmap advertises dmabuf format %.4s::%016" G_GINT64_MODIFIER "x as YUV" ,
(char *) &fourcc, (guint64) DRM_FORMAT_MOD_LINEAR);
gdk_dmabuf_formats_builder_add_format (builder, fourcc, DRM_FORMAT_MOD_LINEAR);
}
}
formats = gdk_dmabuf_formats_builder_free_to_formats (builder);
}
return formats;
}
const guchar *
gdk_dmabuf_mmap (int dmabuf_fd,
gsize *out_size)
{
gsize size;
const guchar *result;
size = lseek (dmabuf_fd, 0 , SEEK_END);
if (size == (off_t) -1 )
{
g_warning ("Failed to seek dmabuf: %s" , g_strerror (errno));
return NULL;
}
/* be a good citizen and seek back to the start, as the docs recommend */
lseek (dmabuf_fd, 0 , SEEK_SET);
if (gdk_dmabuf_ioctl (dmabuf_fd,
DMA_BUF_IOCTL_SYNC,
&(struct dma_buf_sync) { DMA_BUF_SYNC_START|DMA_BUF_SYNC_READ }) < 0 )
{
g_warning ("Failed to sync dmabuf before mmap(): %s" , g_strerror (errno));
/* not a fatal error, but might cause glitches */
}
result = mmap (NULL, size, PROT_READ, MAP_SHARED, dmabuf_fd, 0 );
if (result == NULL || result == MAP_FAILED)
{
g_warning ("Failed to mmap dmabuf: %s" , g_strerror (errno));
return NULL;
}
*out_size = size;
return result;
}
void
gdk_dmabuf_munmap (int dmabuf_fd,
const guchar *addr,
gsize size)
{
munmap ((void *) addr, size);
if (gdk_dmabuf_ioctl (dmabuf_fd,
DMA_BUF_IOCTL_SYNC,
&(struct dma_buf_sync) { DMA_BUF_SYNC_END|DMA_BUF_SYNC_READ }) < 0 )
{
g_warning ("Failed to sync dmabuf after munmap(): %s" , g_strerror (errno));
/* not a fatal error, but might cause glitches */
}
}
static gboolean
gdk_dmabuf_do_download_mmap (GdkTexture *texture,
guchar *data,
const GdkMemoryLayout *layout,
GdkColorState *color_state)
{
const GdkDmabuf *dmabuf;
GdkMemoryLayout dmabuf_layout;
const guchar *src_data[GDK_DMABUF_MAX_PLANES];
gsize sizes[GDK_DMABUF_MAX_PLANES];
gsize needs_unmap[GDK_DMABUF_MAX_PLANES] = { FALSE , };
gsize i, j;
gboolean retval = FALSE ;
dmabuf = gdk_dmabuf_texture_get_dmabuf (GDK_DMABUF_TEXTURE (texture));
if (dmabuf->modifier != DRM_FORMAT_MOD_LINEAR)
return FALSE ;
for (i = 0 ; i < dmabuf->n_planes; i++)
{
if (dmabuf->planes[i].fd == -1 )
{
g_assert (i > 0 );
src_data[i] = src_data[0 ];
sizes[i] = sizes[0 ];
continue ;
}
for (j = 0 ; j < i; j++)
{
if (dmabuf->planes[i].fd == dmabuf->planes[j].fd)
break ;
}
if (j < i)
{
src_data[i] = src_data[j];
sizes[i] = sizes[j];
continue ;
}
src_data[i] = gdk_dmabuf_mmap (dmabuf->planes[i].fd, &sizes[i]);
if (src_data[i] == NULL)
goto out;
needs_unmap[i] = TRUE ;
}
if (gdk_memory_layout_init_from_dmabuf (&dmabuf_layout,
dmabuf,
gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_PREMULTIPLIED,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture)))
{
gdk_memory_convert (data,
layout,
gdk_texture_get_color_state (texture),
src_data[0 ],
&dmabuf_layout,
gdk_texture_get_color_state (texture));
retval = TRUE ;
}
GDK_DISPLAY_DEBUG (gdk_dmabuf_texture_get_display (GDK_DMABUF_TEXTURE (texture)), DMABUF,
"Used mmap for downloading %dx%d dmabuf (format %.4s:%#" G_GINT64_MODIFIER "x)" ,
gdk_texture_get_width (texture), gdk_texture_get_height (texture),
(char *)&dmabuf->fourcc, dmabuf->modifier);
out:
for (i = 0 ; i < dmabuf->n_planes; i++)
{
if (!needs_unmap[i])
continue ;
gdk_dmabuf_munmap (dmabuf->planes[i].fd, src_data[i], sizes[i]);
}
return retval;
}
gboolean
gdk_dmabuf_download_mmap (GdkTexture *texture,
guchar *data,
const GdkMemoryLayout *layout,
GdkColorState *color_state)
{
GdkMemoryFormat src_format = gdk_texture_get_format (texture);
GdkColorState *src_color_state = gdk_texture_get_color_state (texture);
gboolean retval;
if (layout->format == src_format)
{
retval = gdk_dmabuf_do_download_mmap (texture, data, layout, color_state);
gdk_memory_convert_color_state (data,
layout,
src_color_state,
color_state);
}
else
{
GdkMemoryLayout src_layout;
guchar *src_data;
gdk_memory_layout_init (&src_layout,
src_format,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
1 );
src_data = g_new (guchar, src_layout.size);
retval = gdk_dmabuf_do_download_mmap (texture, src_data, &src_layout, src_color_state);
gdk_memory_convert (data,
layout,
color_state,
src_data,
&src_layout,
src_color_state);
g_free (src_data);
}
return retval;
}
int
gdk_dmabuf_ioctl (int fd,
unsigned long request,
void *arg)
{
int ret;
do {
ret = ioctl (fd, request, arg);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
return ret;
}
#if !defined (DMA_BUF_IOCTL_IMPORT_SYNC_FILE)
struct dma_buf_import_sync_file
{
__u32 flags;
__s32 fd;
};
#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3 , struct dma_buf_import_sync_file)
#endif
gboolean
gdk_dmabuf_import_sync_file (int dmabuf_fd,
guint32 flags,
int sync_file_fd)
{
struct dma_buf_import_sync_file data = {
.flags = flags,
.fd = sync_file_fd,
};
if (gdk_dmabuf_ioctl (dmabuf_fd, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0 )
{
GDK_DEBUG (DMABUF, "Importing dmabuf sync failed: %s" , g_strerror (errno));
return FALSE ;
}
return TRUE ;
}
#if !defined (DMA_BUF_IOCTL_EXPORT_SYNC_FILE)
struct dma_buf_export_sync_file
{
__u32 flags;
__s32 fd;
};
#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2 , struct dma_buf_export_sync_file)
#endif
int
gdk_dmabuf_export_sync_file (int dmabuf_fd,
guint32 flags)
{
struct dma_buf_export_sync_file data = {
.flags = flags,
.fd = -1 ,
};
if (gdk_dmabuf_ioctl (dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &data) != 0 )
{
GDK_DEBUG (DMABUF, "Exporting dmabuf sync failed: %s" , g_strerror (errno));
return -1 ;
}
return data.fd;
}
/*
* Tries to sanitize the dmabuf to conform to the values expected
* by Vulkan / EGL which should also be the values expected by
* Wayland compositors
*
* We put these sanitized values into the GdkDmabufTexture , by
* sanitizing the input from GdkDmabufTextureBuilder , which are
* controlled by the callers .
*
* Things we do here :
*
* 1 . Ignore non - linear modifiers .
*
* 2 . Try and fix various inconsistencies between V4L and Mesa
* for linear modifiers , like the e . g . single - plane NV12 .
*
* * * * WARNING * * *
*
* This function is not absolutely perfect , you do not have a
* perfect dmabuf afterwards .
*
* In particular , it doesn ' t check sizes .
*
* * * * WARNING * * *
*/
gboolean
gdk_dmabuf_sanitize (GdkDmabuf *dest,
gsize width,
gsize height,
const GdkDmabuf *src,
GError **error)
{
if (src->n_planes > GDK_DMABUF_MAX_PLANES)
{
g_set_error (error,
GDK_DMABUF_ERROR, GDK_DMABUF_ERROR_UNSUPPORTED_FORMAT,
"GTK only support dmabufs with %u planes, not %u" ,
GDK_DMABUF_MAX_PLANES, src->n_planes);
return FALSE ;
}
*dest = *src;
if (src->modifier)
return TRUE ;
switch (dest->fourcc)
{
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV16:
case DRM_FORMAT_NV61:
if (dest->n_planes == 1 )
{
dest->n_planes = 2 ;
dest->planes[1 ].fd = dest->planes[0 ].fd;
dest->planes[1 ].stride = dest->planes[0 ].stride;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
}
break ;
case DRM_FORMAT_NV24:
case DRM_FORMAT_NV42:
if (dest->n_planes == 1 )
{
dest->n_planes = 2 ;
dest->planes[1 ].stride = dest->planes[0 ].stride * 2 ;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
}
break ;
case DRM_FORMAT_YUV410:
case DRM_FORMAT_YVU410:
if (dest->n_planes == 1 )
{
dest->n_planes = 3 ;
dest->planes[1 ].stride = (dest->planes[0 ].stride + 3 ) / 4 ;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
dest->planes[2 ].stride = dest->planes[1 ].stride;
dest->planes[2 ].offset = dest->planes[1 ].offset + dest->planes[1 ].stride * ((height + 3 ) / 4 );
}
break ;
case DRM_FORMAT_YUV411:
case DRM_FORMAT_YVU411:
if (dest->n_planes == 1 )
{
dest->n_planes = 3 ;
dest->planes[1 ].stride = (dest->planes[0 ].stride + 3 ) / 4 ;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
dest->planes[2 ].stride = dest->planes[1 ].stride;
dest->planes[2 ].offset = dest->planes[1 ].offset + dest->planes[1 ].stride * height;
}
break ;
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
if (dest->n_planes == 1 )
{
dest->n_planes = 3 ;
dest->planes[1 ].stride = (dest->planes[0 ].stride + 1 ) / 2 ;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
dest->planes[2 ].stride = dest->planes[1 ].stride;
dest->planes[2 ].offset = dest->planes[1 ].offset + dest->planes[1 ].stride * ((height + 1 ) / 2 );
}
break ;
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YVU422:
if (dest->n_planes == 1 )
{
dest->n_planes = 3 ;
dest->planes[1 ].stride = (dest->planes[0 ].stride + 1 ) / 2 ;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
dest->planes[2 ].stride = dest->planes[1 ].stride;
dest->planes[2 ].offset = dest->planes[1 ].offset + dest->planes[1 ].stride * height;
}
break ;
case DRM_FORMAT_YUV444:
case DRM_FORMAT_YVU444:
if (dest->n_planes == 1 )
{
dest->n_planes = 3 ;
dest->planes[1 ].stride = dest->planes[0 ].stride;
dest->planes[1 ].offset = dest->planes[0 ].offset + dest->planes[0 ].stride * height;
dest->planes[2 ].stride = dest->planes[1 ].stride;
dest->planes[2 ].offset = dest->planes[1 ].offset + dest->planes[1 ].stride * height;
}
break ;
default :
break ;
}
return TRUE ;
}
/*
* gdk_dmabuf_is_disjoint :
* @ dmabuf : a sanitized GdkDmabuf
*
* A dmabuf is considered disjoint if it uses more than
* 1 inode .
* Multiple file descriptors may exist when the creator
* of the dmabuf just dup ( ) ed once for every plane . . .
*
* Returns : % TRUE if the dmabuf is disjoint
**/
gboolean
gdk_dmabuf_is_disjoint (const GdkDmabuf *dmabuf)
{
struct stat first_stat;
unsigned i;
/* First, do a fast check */
for (i = 1 ; i < dmabuf->n_planes; i++)
{
if (dmabuf->planes[i].fd == -1 )
continue ;
if (dmabuf->planes[0 ].fd != dmabuf->planes[i].fd)
break ;
}
if (i == dmabuf->n_planes)
return FALSE ;
/* We have different fds, do the fancy check instead */
if (fstat (dmabuf->planes[0 ].fd, &first_stat) != 0 )
return TRUE ;
for (i = 1 ; i < dmabuf->n_planes; i++)
{
struct stat plane_stat;
if (dmabuf->planes[i].fd == -1 )
continue ;
if (fstat (dmabuf->planes[i].fd, &plane_stat) != 0 )
return TRUE ;
if (first_stat.st_ino != plane_stat.st_ino)
return TRUE ;
}
return FALSE ;
}
static int
udmabuf_initialize (GError **error)
{
static int udmabuf_fd;
if (udmabuf_fd == 0 )
{
udmabuf_fd = open ("/dev/udmabuf" , O_RDWR);
if (udmabuf_fd == -1 )
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to open /dev/udmabuf: %s" ,
g_strerror (errno));
}
}
return udmabuf_fd;
}
#define align(x,y) (((x) + (y) - 1 ) & ~((y) - 1 ))
/*<private>
* gdk_dmabuf_new_for_bytes :
* @ bytes : the bytes to create a dmabuf for
* @ error : Return location for an error
*
* Creates a dmabuf representing the given bytes .
*
* Returns : The new dmabuf fd or - 1 on error .
**/
int
gdk_dmabuf_new_for_bytes (GBytes *bytes,
GError **error)
{
int udmabuf_fd;
int mem_fd = -1 ;
int dmabuf_fd = -1 ;
gsize alignment, size;
gpointer data;
int res;
g_return_val_if_fail (bytes != NULL, -1 );
if (!gdk_has_feature (GDK_FEATURE_DMABUF))
{
g_set_error_literal (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Dmabuf support is disabled" );
return -1 ;
}
udmabuf_fd = udmabuf_initialize (error);
if (udmabuf_fd < 0 )
return -1 ;
alignment = sysconf (_SC_PAGE_SIZE);
size = align (g_bytes_get_size (bytes), alignment);
#ifdef HAVE_MEMFD_CREATE
mem_fd = memfd_create ("gtk" , MFD_ALLOW_SEALING);
#endif
if (mem_fd == -1 )
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create memfd: %s" ,
g_strerror (errno));
goto out;
}
res = ftruncate (mem_fd, size);
if (res == -1 )
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"ftruncate on memfd failed: %s" ,
g_strerror (errno));
goto out;
}
if (fcntl (mem_fd, F_ADD_SEALS, F_SEAL_SHRINK) < 0 )
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"fcntl on memfd failed: %s" ,
g_strerror (errno));
goto out;
}
data = mmap (NULL, size, PROT_WRITE, MAP_SHARED, mem_fd, 0 );
if (data == NULL || data == MAP_FAILED)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"mmap failed: %s" ,
g_strerror (errno));
goto out;
}
memcpy (data, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
munmap (data, size);
#ifdef HAVE_UDMABUF
dmabuf_fd = ioctl (udmabuf_fd,
UDMABUF_CREATE,
&(struct udmabuf_create) {
.memfd = mem_fd,
.flags = UDMABUF_FLAGS_CLOEXEC,
.offset = 0 ,
.size = size
});
#endif
if (dmabuf_fd < 0 )
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"UDMABUF_CREATE ioctl failed: %s" ,
g_strerror (errno));
}
out:
if (mem_fd != -1 )
close (mem_fd);
return dmabuf_fd;
}
#endif /* HAVE_DMABUF */
void
gdk_dmabuf_close_fds (GdkDmabuf *dmabuf)
{
guint i, j;
for (i = 0 ; i < dmabuf->n_planes; i++)
{
if (dmabuf->planes[i].fd == -1 )
continue ;
for (j = 0 ; j < i; j++)
{
if (dmabuf->planes[i].fd == dmabuf->planes[j].fd)
break ;
}
if (i == j)
g_close (dmabuf->planes[i].fd, NULL);
}
}
gboolean
gdk_memory_layout_init_from_dmabuf (GdkMemoryLayout *self,
const GdkDmabuf *dmabuf,
gboolean premultiplied,
gsize width,
gsize height)
{
gboolean is_yuv;
gsize i;
if (dmabuf->modifier != DRM_FORMAT_MOD_LINEAR)
return FALSE ;
if (!gdk_memory_format_find_by_dmabuf_fourcc (dmabuf->fourcc, premultiplied, &self->format, &is_yuv))
return FALSE ;
if (!gdk_memory_format_is_block_boundary (self->format, width, height))
return FALSE ;
g_return_val_if_fail (dmabuf->n_planes == gdk_memory_format_get_n_planes (self->format), FALSE );
self->width = width;
self->height = height;
for (i = 0 ; i < dmabuf->n_planes; i++)
{
self->planes[i].offset = dmabuf->planes[i].offset;
self->planes[i].stride = dmabuf->planes[i].stride;
}
self->size = self->planes[dmabuf->n_planes - 1 ].offset +
(self->height - 1 ) / gdk_memory_format_get_plane_block_height (self->format, dmabuf->n_planes - 1 )
* self->planes[dmabuf->n_planes - 1 ].stride +
self->width / gdk_memory_format_get_plane_block_width (self->format, dmabuf->n_planes - 1 )
* gdk_memory_format_get_plane_block_bytes (self->format, dmabuf->n_planes - 1 );
return TRUE ;
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland