1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <string.h>
26 
27 #include "vkmemory.h"
28 
29 /**
30  * SECTION:vkmemory
31  * @title: GstVkMemory
32  * @short_description: memory subclass for Vulkan device memory
33  * @see_also: #GstMemory, #GstAllocator
34  *
35  * GstVulkanMemory is a #GstMemory subclass providing support for the mapping of
36  * Vulkan device memory.
37  */
38 
39 /* WARNING: while suballocation is allowed, nothing prevents aliasing which
40  * requires external synchronisation */
41 
42 #define GST_CAT_DEFUALT GST_CAT_VULKAN_MEMORY
43 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFUALT);
44 
45 static GstAllocator *_vulkan_memory_allocator;
46 
47 static gchar *
_memory_properties_to_string(VkMemoryPropertyFlags prop_bits)48 _memory_properties_to_string (VkMemoryPropertyFlags prop_bits)
49 {
50   GString *s;
51   gboolean first = TRUE;
52 
53 #define STR_APPEND(s,str) \
54   G_STMT_START { \
55     if (!first) \
56       g_string_append (s, "|"); \
57     g_string_append (s, str); \
58     first = FALSE; \
59   } G_STMT_END
60 
61   s = g_string_new (NULL);
62   if (prop_bits & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
63     STR_APPEND (s, "device-local");
64   }
65   if (prop_bits & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
66     STR_APPEND (s, "host-visible");
67     if (prop_bits & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
68       STR_APPEND (s, "host-coherent");
69     } else {
70       STR_APPEND (s, "host-incoherent");
71     }
72     if (prop_bits & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
73       STR_APPEND (s, "host-cached");
74     } else {
75       STR_APPEND (s, "host-uncached");
76     }
77   }
78 
79   if (prop_bits & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
80     STR_APPEND (s, "lazily-allocated");
81   }
82 
83   return g_string_free (s, FALSE);
84 }
85 
86 static void
_vk_mem_init(GstVulkanMemory * mem,GstAllocator * allocator,GstMemory * parent,GstVulkanDevice * device,guint32 memory_type_index,GstAllocationParams * params,gsize size,VkMemoryPropertyFlags mem_prop_flags,gpointer user_data,GDestroyNotify notify)87 _vk_mem_init (GstVulkanMemory * mem, GstAllocator * allocator,
88     GstMemory * parent, GstVulkanDevice * device, guint32 memory_type_index,
89     GstAllocationParams * params, gsize size,
90     VkMemoryPropertyFlags mem_prop_flags, gpointer user_data,
91     GDestroyNotify notify)
92 {
93   gsize align = gst_memory_alignment, offset = 0, maxsize = size;
94   GstMemoryFlags flags = 0;
95   gchar *props_str;
96 
97   if (params) {
98     flags = params->flags;
99     align |= params->align;
100     offset = params->prefix;
101     maxsize += params->prefix + params->padding;
102     if ((maxsize & align) != 0)
103       maxsize += ~(maxsize & align) + 1;
104   }
105 
106   gst_memory_init (GST_MEMORY_CAST (mem), flags, allocator, parent, maxsize,
107       align, offset, size);
108 
109   mem->device = gst_object_ref (device);
110   mem->alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
111   mem->alloc_info.pNext = NULL;
112   mem->alloc_info.allocationSize = (VkDeviceSize) mem->mem.maxsize;
113   mem->alloc_info.memoryTypeIndex = memory_type_index;
114   mem->properties = mem_prop_flags;
115   mem->notify = notify;
116   mem->user_data = user_data;
117   mem->vk_offset = 0;
118 
119   g_mutex_init (&mem->lock);
120 
121   props_str = _memory_properties_to_string (mem_prop_flags);
122 
123   GST_CAT_DEBUG (GST_CAT_VULKAN_MEMORY, "new Vulkan memory:%p size:%"
124       G_GSIZE_FORMAT " properties:%s", mem, maxsize, props_str);
125 
126   g_free (props_str);
127 }
128 
129 static GstVulkanMemory *
_vk_mem_new(GstAllocator * allocator,GstMemory * parent,GstVulkanDevice * device,guint32 memory_type_index,GstAllocationParams * params,gsize size,VkMemoryPropertyFlags mem_props_flags,gpointer user_data,GDestroyNotify notify)130 _vk_mem_new (GstAllocator * allocator, GstMemory * parent,
131     GstVulkanDevice * device, guint32 memory_type_index,
132     GstAllocationParams * params, gsize size,
133     VkMemoryPropertyFlags mem_props_flags, gpointer user_data,
134     GDestroyNotify notify)
135 {
136   GstVulkanMemory *mem = g_new0 (GstVulkanMemory, 1);
137   GError *error = NULL;
138   VkResult err;
139 
140   _vk_mem_init (mem, allocator, parent, device, memory_type_index, params,
141       size, mem_props_flags, user_data, notify);
142 
143   err =
144       vkAllocateMemory (device->device, &mem->alloc_info, NULL, &mem->mem_ptr);
145   if (gst_vulkan_error_to_g_error (err, &error, "vkAllocMemory") < 0) {
146     GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Failed to allocate device memory %s",
147         error->message);
148     gst_memory_unref ((GstMemory *) mem);
149     g_clear_error (&error);
150     return NULL;
151   }
152 
153   return mem;
154 }
155 
156 static gpointer
_vk_mem_map_full(GstVulkanMemory * mem,GstMapInfo * info,gsize size)157 _vk_mem_map_full (GstVulkanMemory * mem, GstMapInfo * info, gsize size)
158 {
159   gpointer data;
160   VkResult err;
161   GError *error = NULL;
162 
163   if ((mem->properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
164     GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Cannot map host-invisible memory");
165     return NULL;
166   }
167 
168   err = vkMapMemory (mem->device->device, mem->mem_ptr, mem->vk_offset,
169       size, 0, &data);
170   if (gst_vulkan_error_to_g_error (err, &error, "vkMapMemory") < 0) {
171     GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Failed to map device memory %s",
172         error->message);
173     g_clear_error (&error);
174     return NULL;
175   }
176 
177   return data;
178 }
179 
180 static void
_vk_mem_unmap_full(GstVulkanMemory * mem,GstMapInfo * info)181 _vk_mem_unmap_full (GstVulkanMemory * mem, GstMapInfo * info)
182 {
183   vkUnmapMemory (mem->device->device, mem->mem_ptr);
184 }
185 
186 static GstMemory *
_vk_mem_copy(GstVulkanMemory * src,gssize offset,gssize size)187 _vk_mem_copy (GstVulkanMemory * src, gssize offset, gssize size)
188 {
189   return NULL;
190 }
191 
192 static GstMemory *
_vk_mem_share(GstVulkanMemory * mem,gssize offset,gsize size)193 _vk_mem_share (GstVulkanMemory * mem, gssize offset, gsize size)
194 {
195   GstVulkanMemory *shared = g_new0 (GstVulkanMemory, 1);
196   GstVulkanMemory *parent = mem;
197   GstAllocationParams params = { 0, };
198 
199   if (size == -1)
200     size = mem->mem.size - offset;
201 
202   g_return_val_if_fail (size > 0, NULL);
203 
204   while ((parent = (GstVulkanMemory *) (GST_MEMORY_CAST (parent)->parent)));
205 
206   params.flags = GST_MEMORY_FLAGS (mem);
207   params.align = GST_MEMORY_CAST (parent)->align;
208 
209   _vk_mem_init (shared, _vulkan_memory_allocator, GST_MEMORY_CAST (mem),
210       parent->device, parent->alloc_info.memoryTypeIndex, &params, size,
211       parent->properties, NULL, NULL);
212   shared->mem_ptr = parent->mem_ptr;
213   shared->wrapped = TRUE;
214   shared->vk_offset = offset + mem->vk_offset;
215 
216   return GST_MEMORY_CAST (shared);
217 }
218 
219 static gboolean
_vk_mem_is_span(GstVulkanMemory * mem1,GstVulkanMemory * mem2,gsize * offset)220 _vk_mem_is_span (GstVulkanMemory * mem1, GstVulkanMemory * mem2, gsize * offset)
221 {
222   return FALSE;
223 }
224 
225 static GstMemory *
_vk_mem_alloc(GstAllocator * allocator,gsize size,GstAllocationParams * params)226 _vk_mem_alloc (GstAllocator * allocator, gsize size,
227     GstAllocationParams * params)
228 {
229   g_critical ("Subclass should override GstAllocatorClass::alloc() function");
230 
231   return NULL;
232 }
233 
234 static void
_vk_mem_free(GstAllocator * allocator,GstMemory * memory)235 _vk_mem_free (GstAllocator * allocator, GstMemory * memory)
236 {
237   GstVulkanMemory *mem = (GstVulkanMemory *) memory;
238 
239   GST_CAT_TRACE (GST_CAT_VULKAN_MEMORY, "freeing buffer memory:%p "
240       "id:%" G_GUINT64_FORMAT, mem, (guint64) mem->mem_ptr);
241 
242   g_mutex_clear (&mem->lock);
243 
244   if (mem->notify)
245     mem->notify (mem->user_data);
246 
247   if (mem->mem_ptr && !mem->wrapped)
248     vkFreeMemory (mem->device->device, mem->mem_ptr, NULL);
249 
250   gst_object_unref (mem->device);
251 }
252 
253 gboolean
gst_vulkan_memory_find_memory_type_index_with_type_properties(GstVulkanDevice * device,guint32 typeBits,VkMemoryPropertyFlags properties,guint32 * typeIndex)254 gst_vulkan_memory_find_memory_type_index_with_type_properties (GstVulkanDevice *
255     device, guint32 typeBits, VkMemoryPropertyFlags properties,
256     guint32 * typeIndex)
257 {
258   guint32 i;
259 
260   /* Search memtypes to find first index with those properties */
261   for (i = 0; i < 32; i++) {
262     if ((typeBits & 1) == 1) {
263       /* Type is available, does it match user properties? */
264       if ((device->memory_properties.memoryTypes[i].
265               propertyFlags & properties) == properties) {
266         *typeIndex = i;
267         return TRUE;
268       }
269     }
270     typeBits >>= 1;
271   }
272 
273   return FALSE;
274 }
275 
276 /**
277  * gst_vulkan_memory_alloc:
278  * @device:a #GstVulkanDevice
279  * @memory_type_index: the Vulkan memory type index
280  * @params: a #GstAllocationParams
281  * @size: the size to allocate
282  *
283  * Allocated a new #GstVulkanMemory.
284  *
285  * Returns: a #GstMemory object backed by a vulkan device memory
286  */
287 GstMemory *
gst_vulkan_memory_alloc(GstVulkanDevice * device,guint32 memory_type_index,GstAllocationParams * params,gsize size,VkMemoryPropertyFlags mem_flags)288 gst_vulkan_memory_alloc (GstVulkanDevice * device, guint32 memory_type_index,
289     GstAllocationParams * params, gsize size, VkMemoryPropertyFlags mem_flags)
290 {
291   GstVulkanMemory *mem;
292 
293   mem = _vk_mem_new (_vulkan_memory_allocator, NULL, device, memory_type_index,
294       params, size, mem_flags, NULL, NULL);
295 
296   return (GstMemory *) mem;
297 }
298 
299 G_DEFINE_TYPE (GstVulkanMemoryAllocator, gst_vulkan_memory_allocator,
300     GST_TYPE_ALLOCATOR);
301 
302 static void
gst_vulkan_memory_allocator_class_init(GstVulkanMemoryAllocatorClass * klass)303 gst_vulkan_memory_allocator_class_init (GstVulkanMemoryAllocatorClass * klass)
304 {
305   GstAllocatorClass *allocator_class = (GstAllocatorClass *) klass;
306 
307   allocator_class->alloc = _vk_mem_alloc;
308   allocator_class->free = _vk_mem_free;
309 }
310 
311 static void
gst_vulkan_memory_allocator_init(GstVulkanMemoryAllocator * allocator)312 gst_vulkan_memory_allocator_init (GstVulkanMemoryAllocator * allocator)
313 {
314   GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
315 
316   alloc->mem_type = GST_VULKAN_MEMORY_ALLOCATOR_NAME;
317   alloc->mem_map_full = (GstMemoryMapFullFunction) _vk_mem_map_full;
318   alloc->mem_unmap_full = (GstMemoryUnmapFullFunction) _vk_mem_unmap_full;
319   alloc->mem_copy = (GstMemoryCopyFunction) _vk_mem_copy;
320   alloc->mem_share = (GstMemoryShareFunction) _vk_mem_share;
321   alloc->mem_is_span = (GstMemoryIsSpanFunction) _vk_mem_is_span;
322 }
323 
324 /**
325  * gst_vulkan_memory_init_once:
326  *
327  * Initializes the Vulkan memory allocator. It is safe to call this function
328  * multiple times.  This must be called before any other #GstVulkanMemory operation.
329  */
330 void
gst_vulkan_memory_init_once(void)331 gst_vulkan_memory_init_once (void)
332 {
333   static volatile gsize _init = 0;
334 
335   if (g_once_init_enter (&_init)) {
336     GST_DEBUG_CATEGORY_INIT (GST_CAT_VULKAN_MEMORY, "vulkanmemory", 0,
337         "Vulkan Memory");
338 
339     _vulkan_memory_allocator =
340         g_object_new (gst_vulkan_memory_allocator_get_type (), NULL);
341     gst_object_ref_sink (_vulkan_memory_allocator);
342 
343     gst_allocator_register (GST_VULKAN_MEMORY_ALLOCATOR_NAME,
344         gst_object_ref (_vulkan_memory_allocator));
345     g_once_init_leave (&_init, 1);
346   }
347 }
348 
349 /**
350  * gst_is_vulkan_memory:
351  * @mem:a #GstMemory
352  *
353  * Returns: whether the memory at @mem is a #GstVulkanMemory
354  */
355 gboolean
gst_is_vulkan_memory(GstMemory * mem)356 gst_is_vulkan_memory (GstMemory * mem)
357 {
358   return mem != NULL && mem->allocator != NULL &&
359       g_type_is_a (G_OBJECT_TYPE (mem->allocator),
360       GST_TYPE_VULKAN_MEMORY_ALLOCATOR);
361 }
362