/src/gstreamer/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideodmabufpool.c
Line | Count | Source |
1 | | /* GStreamer video dmabuf pool |
2 | | * |
3 | | * Copyright (C) 2025 Collabora Ltd. |
4 | | * Author: Robert Mader <robert.mader@collabora.com> |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Library General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Library General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Library General Public |
17 | | * License along with this library; if not, write to the |
18 | | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
19 | | * Boston, MA 02110-1301, USA. |
20 | | */ |
21 | | |
22 | | /** |
23 | | * SECTION:gstvideodmabufpool |
24 | | * @title: GstVideoDmabufPool |
25 | | * @short_description: Pool for virtual memory backed dmabufs |
26 | | * @see_also: #GstUdmabufAllocator |
27 | | * |
28 | | * Using #GstUdmabufAllocator, setting defaults and implementing implicit sync. |
29 | | * |
30 | | * Since: 1.28 |
31 | | */ |
32 | | |
33 | | #ifdef HAVE_CONFIG_H |
34 | | #include "config.h" |
35 | | #endif |
36 | | |
37 | | #include "gstvideodmabufpool.h" |
38 | | |
39 | | #include <gst/allocators/gstudmabufallocator.h> |
40 | | |
41 | | #ifdef HAVE_LINUX_DMA_BUF_H |
42 | | #include <linux/dma-buf.h> |
43 | | #include <sys/ioctl.h> |
44 | | #include <unistd.h> |
45 | | #endif |
46 | | |
47 | | /* This alignment is needed on many AMD GPUs and is known to work well across |
48 | | * many vendors/GPUs, see e.g. GstGLDMABufBufferPool. */ |
49 | 0 | #define UDMABUF_ALIGNMENT_MASK (256 - 1) |
50 | | |
51 | | struct _GstVideoDmabufPool |
52 | | { |
53 | | GstVideoBufferPool parent; |
54 | | GMainContext *context; |
55 | | GMainLoop *loop; |
56 | | GThread *thread; |
57 | | }; |
58 | | |
59 | | GST_DEBUG_CATEGORY_STATIC (gst_video_dmabuf_pool_debug); |
60 | | |
61 | 0 | G_DEFINE_TYPE_WITH_CODE (GstVideoDmabufPool, gst_video_dmabuf_pool, |
62 | 0 | GST_TYPE_VIDEO_BUFFER_POOL, |
63 | 0 | GST_DEBUG_CATEGORY_INIT (gst_video_dmabuf_pool_debug, "video-dmabuf-pool", |
64 | 0 | 0, "video dmabuf pool"); |
65 | 0 | ); |
66 | 0 |
|
67 | 0 | #ifdef HAVE_LINUX_DMA_BUF_H |
68 | 0 | #ifdef DMA_BUF_IOCTL_EXPORT_SYNC_FILE |
69 | 0 | typedef struct _DmaBufSource |
70 | 0 | { |
71 | 0 | GSource base; |
72 | 0 |
|
73 | 0 | GstVideoDmabufPool *pool; |
74 | 0 | GstBuffer *buffer; |
75 | 0 |
|
76 | 0 | gint mem_fds[GST_VIDEO_MAX_PLANES]; |
77 | 0 | gpointer fd_tags[GST_VIDEO_MAX_PLANES]; |
78 | 0 | } DmaBufSource; |
79 | 0 |
|
80 | 0 | static gboolean |
81 | 0 | dma_buf_fd_readable (gint fd) |
82 | 0 | { |
83 | 0 | GPollFD poll_fd; |
84 | 0 |
|
85 | 0 | poll_fd.fd = fd; |
86 | 0 | poll_fd.events = G_IO_IN; |
87 | 0 | poll_fd.revents = 0; |
88 | 0 |
|
89 | 0 | if (!g_poll (&poll_fd, 1, 0)) |
90 | 0 | return FALSE; |
91 | 0 |
|
92 | 0 | return (poll_fd.revents & (G_IO_IN | G_IO_NVAL)) != 0; |
93 | 0 | } |
94 | 0 |
|
95 | 0 | static int |
96 | 0 | get_sync_file (gint fd) |
97 | 0 | { |
98 | 0 | struct dma_buf_export_sync_file sync_file_in_out = { |
99 | 0 | .flags = DMA_BUF_SYNC_WRITE, |
100 | 0 | .fd = -1 |
101 | 0 | }; |
102 | 0 | gint ret; |
103 | 0 |
|
104 | 0 | do { |
105 | 0 | ret = ioctl (fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &sync_file_in_out); |
106 | 0 | } while (ret == -1 && errno == EINTR); |
107 | 0 |
|
108 | 0 | if (ret == 0) |
109 | 0 | return sync_file_in_out.fd; |
110 | 0 |
|
111 | 0 | return -1; |
112 | 0 | } |
113 | 0 |
|
114 | 0 | static gboolean |
115 | 0 | dma_buf_source_dispatch (GSource * base, |
116 | 0 | GSourceFunc callback, gpointer user_data) |
117 | 0 | { |
118 | 0 | DmaBufSource *source = (DmaBufSource *) base; |
119 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (source->pool); |
120 | 0 | gboolean ready; |
121 | 0 |
|
122 | 0 | GST_DEBUG_OBJECT (self, "Dispatch source for buffer %p", source->buffer); |
123 | 0 |
|
124 | 0 | ready = TRUE; |
125 | 0 |
|
126 | 0 | for (gint i = 0; i < GST_VIDEO_MAX_PLANES; i++) { |
127 | 0 | if (!source->fd_tags[i]) |
128 | 0 | continue; |
129 | 0 |
|
130 | 0 | if (!dma_buf_fd_readable (source->mem_fds[i])) { |
131 | 0 | GST_DEBUG_OBJECT (self, "Buffer %p not ready, sync file: %d", |
132 | 0 | source->buffer, source->mem_fds[i]); |
133 | 0 | ready = FALSE; |
134 | 0 | continue; |
135 | 0 | } |
136 | 0 |
|
137 | 0 | close (source->mem_fds[i]); |
138 | 0 | g_source_remove_unix_fd (base, source->fd_tags[i]); |
139 | 0 | source->fd_tags[i] = NULL; |
140 | 0 | } |
141 | 0 |
|
142 | 0 | if (!ready) |
143 | 0 | return G_SOURCE_CONTINUE; |
144 | 0 |
|
145 | 0 | GST_DEBUG_OBJECT (self, "Releasing buffer %p from source, pool %p", |
146 | 0 | source->buffer, self); |
147 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->release_buffer |
148 | 0 | (GST_BUFFER_POOL_CAST (self), source->buffer); |
149 | 0 | g_source_unref (base); |
150 | 0 |
|
151 | 0 | return G_SOURCE_REMOVE; |
152 | 0 | } |
153 | 0 |
|
154 | 0 | static void |
155 | 0 | dma_buf_source_finalize (GSource * base) |
156 | 0 | { |
157 | 0 | DmaBufSource *source = (DmaBufSource *) base; |
158 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (source->pool); |
159 | 0 | gboolean need_buffer_release = FALSE; |
160 | 0 |
|
161 | 0 | for (gint i = 0; i < GST_VIDEO_MAX_PLANES; i++) { |
162 | 0 | if (!source->fd_tags[i]) |
163 | 0 | continue; |
164 | 0 |
|
165 | 0 | close (source->mem_fds[i]); |
166 | 0 | g_source_remove_unix_fd (base, source->fd_tags[i]); |
167 | 0 | source->fd_tags[i] = NULL; |
168 | 0 | need_buffer_release = TRUE; |
169 | 0 | } |
170 | 0 |
|
171 | 0 | if (need_buffer_release) { |
172 | 0 | GST_DEBUG_OBJECT (self, "Releasing buffer %p from source, pool %p", |
173 | 0 | source->buffer, self); |
174 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->release_buffer |
175 | 0 | (GST_BUFFER_POOL_CAST (self), source->buffer); |
176 | 0 | } |
177 | 0 | } |
178 | 0 |
|
179 | 0 | static GSourceFuncs dma_buf_source_funcs = { |
180 | 0 | .dispatch = dma_buf_source_dispatch, |
181 | 0 | .finalize = dma_buf_source_finalize, |
182 | 0 | }; |
183 | 0 |
|
184 | 0 | static gpointer |
185 | 0 | dmabuf_source_thread (gpointer data) |
186 | 0 | { |
187 | 0 | GstVideoDmabufPool *self = data; |
188 | 0 | GMainContext *context = NULL; |
189 | 0 | GMainLoop *loop = NULL; |
190 | 0 |
|
191 | 0 | GST_OBJECT_LOCK (self); |
192 | 0 | if (self->context) |
193 | 0 | context = g_main_context_ref (self->context); |
194 | 0 | if (self->loop) |
195 | 0 | loop = g_main_loop_ref (self->loop); |
196 | 0 |
|
197 | 0 | if (context == NULL || loop == NULL) { |
198 | 0 | g_clear_pointer (&loop, g_main_loop_unref); |
199 | 0 | g_clear_pointer (&context, g_main_context_unref); |
200 | 0 | GST_OBJECT_UNLOCK (self); |
201 | 0 | return NULL; |
202 | 0 | } |
203 | 0 | GST_OBJECT_UNLOCK (self); |
204 | 0 |
|
205 | 0 | g_main_context_push_thread_default (context); |
206 | 0 |
|
207 | 0 | GST_DEBUG_OBJECT (self, "Running main loop"); |
208 | 0 | g_main_loop_run (loop); |
209 | 0 | g_main_loop_unref (loop); |
210 | 0 | g_main_context_unref (context); |
211 | 0 |
|
212 | 0 | gst_object_unref (self); |
213 | 0 |
|
214 | 0 | return NULL; |
215 | 0 | } |
216 | 0 |
|
217 | 0 | static gboolean |
218 | 0 | gst_video_dmabuf_pool_start (GstBufferPool * pool) |
219 | 0 | { |
220 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (pool); |
221 | 0 |
|
222 | 0 | GST_OBJECT_LOCK (self); |
223 | 0 | GST_DEBUG_OBJECT (self, "Starting main loop"); |
224 | 0 | g_assert (self->context == NULL); |
225 | 0 |
|
226 | 0 | self->context = g_main_context_new (); |
227 | 0 | self->loop = g_main_loop_new (self->context, FALSE); |
228 | 0 |
|
229 | 0 | self->thread = |
230 | 0 | g_thread_new ("video-dmabuf-pool-source-loop", dmabuf_source_thread, |
231 | 0 | g_object_ref (self)); |
232 | 0 |
|
233 | 0 | GST_OBJECT_UNLOCK (self); |
234 | 0 | return |
235 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->start (pool); |
236 | 0 | } |
237 | 0 |
|
238 | 0 | static gboolean |
239 | 0 | gst_video_dmabuf_pool_stop (GstBufferPool * pool) |
240 | 0 | { |
241 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (pool); |
242 | 0 | GMainContext *context; |
243 | 0 | GMainLoop *loop; |
244 | 0 | GSource *idle_stop_source; |
245 | 0 |
|
246 | 0 | GST_OBJECT_LOCK (self); |
247 | 0 | GST_DEBUG_OBJECT (self, "Stopping main loop"); |
248 | 0 | context = self->context; |
249 | 0 | loop = self->loop; |
250 | 0 | self->context = NULL; |
251 | 0 | self->loop = NULL; |
252 | 0 | GST_OBJECT_UNLOCK (self); |
253 | 0 |
|
254 | 0 | if (!context || !loop) { |
255 | 0 | g_clear_pointer (&loop, g_main_loop_unref); |
256 | 0 | g_clear_pointer (&context, g_main_context_unref); |
257 | 0 | goto out; |
258 | 0 | } |
259 | 0 |
|
260 | 0 | idle_stop_source = g_idle_source_new (); |
261 | 0 | g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop, |
262 | 0 | NULL); |
263 | 0 | g_source_attach (idle_stop_source, context); |
264 | 0 | g_source_unref (idle_stop_source); |
265 | 0 |
|
266 | 0 | g_thread_join (self->thread); |
267 | 0 | self->thread = NULL; |
268 | 0 |
|
269 | 0 | g_main_loop_unref (loop); |
270 | 0 | g_main_context_unref (context); |
271 | 0 |
|
272 | 0 | out: |
273 | 0 | return |
274 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->stop (pool); |
275 | 0 | } |
276 | 0 |
|
277 | 0 | static void |
278 | 0 | gst_video_dmabuf_pool_release_buffer (GstBufferPool * pool, GstBuffer * buffer) |
279 | 0 | { |
280 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (pool); |
281 | 0 | DmaBufSource *source = NULL; |
282 | 0 | guint n_mem; |
283 | 0 |
|
284 | 0 | GST_DEBUG_OBJECT (self, "Buffer: %p", buffer); |
285 | 0 |
|
286 | 0 | n_mem = gst_buffer_n_memory (buffer); |
287 | 0 | for (gint i = 0; i < n_mem; i++) { |
288 | 0 | GstMemory *mem; |
289 | 0 | gint mem_fd, sync_file; |
290 | 0 |
|
291 | 0 | mem = gst_buffer_peek_memory (buffer, i); |
292 | 0 | if (!gst_is_dmabuf_memory (mem)) |
293 | 0 | continue; |
294 | 0 |
|
295 | 0 | mem_fd = gst_dmabuf_memory_get_fd (mem); |
296 | 0 | sync_file = get_sync_file (mem_fd); |
297 | 0 | if (sync_file == -1) { |
298 | 0 | GST_ERROR_OBJECT (self, "Exporting sync file failed"); |
299 | 0 | continue; |
300 | 0 | } |
301 | 0 |
|
302 | 0 | if (dma_buf_fd_readable (sync_file)) { |
303 | 0 | GST_DEBUG_OBJECT (self, "Sync file readable"); |
304 | 0 | close (sync_file); |
305 | 0 | continue; |
306 | 0 | } |
307 | 0 |
|
308 | 0 | if (!source) { |
309 | 0 | GST_DEBUG_OBJECT (self, "Creating source for buffer %p, pool %p", buffer, |
310 | 0 | self); |
311 | 0 | source = |
312 | 0 | (DmaBufSource *) g_source_new (&dma_buf_source_funcs, |
313 | 0 | sizeof (*source)); |
314 | 0 | source->pool = self; |
315 | 0 | source->buffer = buffer; |
316 | 0 | } |
317 | 0 |
|
318 | 0 | GST_DEBUG_OBJECT (self, "Adding sync file to source"); |
319 | 0 | source->mem_fds[i] = sync_file; |
320 | 0 | source->fd_tags[i] = |
321 | 0 | g_source_add_unix_fd (&source->base, sync_file, G_IO_IN); |
322 | 0 | } |
323 | 0 |
|
324 | 0 | if (source) { |
325 | 0 | g_assert (self->context); |
326 | 0 | g_source_attach ((GSource *) source, self->context); |
327 | 0 | return; |
328 | 0 | } |
329 | 0 |
|
330 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->release_buffer |
331 | 0 | (pool, buffer); |
332 | 0 | } |
333 | 0 | #endif /* DMA_BUF_IOCTL_EXPORT_SYNC_FILE */ |
334 | 0 |
|
335 | 0 | static gboolean |
336 | 0 | gst_video_dmabuf_pool_set_config (GstBufferPool * pool, GstStructure * config) |
337 | 0 | { |
338 | 0 | GstVideoDmabufPool *self = GST_VIDEO_DMABUF_POOL (pool); |
339 | 0 | GstAllocator *allocator; |
340 | 0 | GstAllocationParams params; |
341 | 0 | GstVideoAlignment video_align = { 0 }; |
342 | 0 | gboolean config_updated = FALSE; |
343 | 0 | gboolean alignment_updated = FALSE; |
344 | 0 | gboolean res; |
345 | |
|
346 | 0 | gst_buffer_pool_config_get_allocator (config, &allocator, ¶ms); |
347 | 0 | if (!GST_IS_DMABUF_ALLOCATOR (allocator) || |
348 | 0 | GST_OBJECT_FLAG_IS_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC)) { |
349 | 0 | GST_DEBUG_OBJECT (self, |
350 | 0 | "Allocator not a dmabuf allocator or having the CUSTOM_ALLOC flag set, trying to update to udmabuf"); |
351 | |
|
352 | 0 | allocator = gst_udmabuf_allocator_get (); |
353 | 0 | if (allocator) { |
354 | 0 | params.align |= UDMABUF_ALIGNMENT_MASK; |
355 | 0 | gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); |
356 | 0 | gst_object_unref (allocator); |
357 | 0 | config_updated = TRUE; |
358 | 0 | } else { |
359 | 0 | GST_ERROR_OBJECT (self, "udmabuf allocator not available"); |
360 | 0 | return FALSE; |
361 | 0 | } |
362 | 0 | } else if (params.align < UDMABUF_ALIGNMENT_MASK) { |
363 | 0 | GST_DEBUG_OBJECT (self, "updating allocator params"); |
364 | 0 | params.align |= UDMABUF_ALIGNMENT_MASK; |
365 | 0 | gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); |
366 | 0 | config_updated = TRUE; |
367 | 0 | } |
368 | | |
369 | 0 | if (!gst_buffer_pool_config_has_option (config, |
370 | 0 | GST_BUFFER_POOL_OPTION_VIDEO_META)) { |
371 | 0 | GST_DEBUG_OBJECT (self, "missing video meta option"); |
372 | 0 | return FALSE; |
373 | 0 | } |
374 | 0 | if (!gst_buffer_pool_config_has_option (config, |
375 | 0 | GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT)) { |
376 | 0 | GST_DEBUG_OBJECT (self, "missing video alignment option"); |
377 | 0 | return FALSE; |
378 | 0 | } |
379 | | |
380 | 0 | gst_buffer_pool_config_get_video_alignment (config, &video_align); |
381 | 0 | for (int i = 0; i != GST_VIDEO_MAX_PLANES; ++i) { |
382 | 0 | if (video_align.stride_align[i] < UDMABUF_ALIGNMENT_MASK) { |
383 | 0 | video_align.stride_align[i] |= UDMABUF_ALIGNMENT_MASK; |
384 | 0 | alignment_updated = TRUE; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | if (alignment_updated) { |
388 | 0 | GST_DEBUG_OBJECT (self, "updating video alignment"); |
389 | 0 | gst_buffer_pool_config_set_video_alignment (config, &video_align); |
390 | 0 | config_updated = TRUE; |
391 | 0 | } |
392 | |
|
393 | 0 | res = |
394 | 0 | GST_BUFFER_POOL_CLASS (gst_video_dmabuf_pool_parent_class)->set_config |
395 | 0 | (pool, config); |
396 | |
|
397 | 0 | if (config_updated) |
398 | 0 | return FALSE; |
399 | | |
400 | 0 | return res; |
401 | 0 | } |
402 | | #endif /* HAVE_LINUX_DMA_BUF_H */ |
403 | | |
404 | | static void |
405 | | gst_video_dmabuf_pool_init (GstVideoDmabufPool * self) |
406 | 0 | { |
407 | 0 | } |
408 | | |
409 | | static void |
410 | | gst_video_dmabuf_pool_class_init (GstVideoDmabufPoolClass * klass) |
411 | 0 | { |
412 | 0 | #ifdef HAVE_LINUX_DMA_BUF_H |
413 | 0 | GstBufferPoolClass *pool_class = GST_BUFFER_POOL_CLASS (klass); |
414 | |
|
415 | | #ifdef DMA_BUF_IOCTL_EXPORT_SYNC_FILE |
416 | | pool_class->start = gst_video_dmabuf_pool_start; |
417 | | pool_class->stop = gst_video_dmabuf_pool_stop; |
418 | | pool_class->release_buffer = gst_video_dmabuf_pool_release_buffer; |
419 | | #endif /* DMA_BUF_IOCTL_EXPORT_SYNC_FILE */ |
420 | 0 | pool_class->set_config = gst_video_dmabuf_pool_set_config; |
421 | 0 | #endif |
422 | 0 | } |
423 | | |
424 | | /** |
425 | | * gst_video_dmabuf_pool_new: |
426 | | * |
427 | | * Create a new #GstVideoDmabufPool instance. |
428 | | * |
429 | | * Returns: (transfer full) (nullable): a #GstVideoDmabufPool or %NULL |
430 | | * if dmabufs are not supported. |
431 | | * |
432 | | * Since: 1.28 |
433 | | */ |
434 | | GstBufferPool * |
435 | | gst_video_dmabuf_pool_new (void) |
436 | 0 | { |
437 | 0 | #ifdef HAVE_LINUX_DMA_BUF_H |
438 | 0 | return g_object_new (GST_TYPE_VIDEO_DMABUF_POOL, NULL); |
439 | | #else |
440 | | return NULL; |
441 | | #endif |
442 | 0 | } |