blob: f426043d5818df192fa4f88896556ad0cf84e52b [file] [log] [blame]
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "vkmemory.h"
/**
* SECTION:vkmemory
* @title: GstVkMemory
* @short_description: memory subclass for Vulkan device memory
* @see_also: #GstMemory, #GstAllocator
*
* GstVulkanMemory is a #GstMemory subclass providing support for the mapping of
* Vulkan device memory.
*/
/* WARNING: while suballocation is allowed, nothing prevents aliasing which
* requires external synchronisation */
#define GST_CAT_DEFUALT GST_CAT_VULKAN_MEMORY
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFUALT);
static GstAllocator *_vulkan_memory_allocator;
static gchar *
_memory_properties_to_string (VkMemoryPropertyFlags prop_bits)
{
GString *s;
gboolean first = TRUE;
#define STR_APPEND(s,str) \
G_STMT_START { \
if (!first) \
g_string_append (s, "|"); \
g_string_append (s, str); \
first = FALSE; \
} G_STMT_END
s = g_string_new (NULL);
if (prop_bits & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
STR_APPEND (s, "device-local");
}
if (prop_bits & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
STR_APPEND (s, "host-visible");
if (prop_bits & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
STR_APPEND (s, "host-coherent");
} else {
STR_APPEND (s, "host-incoherent");
}
if (prop_bits & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
STR_APPEND (s, "host-cached");
} else {
STR_APPEND (s, "host-uncached");
}
}
if (prop_bits & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
STR_APPEND (s, "lazily-allocated");
}
return g_string_free (s, FALSE);
}
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)
{
gsize align = gst_memory_alignment, offset = 0, maxsize = size;
GstMemoryFlags flags = 0;
gchar *props_str;
if (params) {
flags = params->flags;
align |= params->align;
offset = params->prefix;
maxsize += params->prefix + params->padding;
if ((maxsize & align) != 0)
maxsize += ~(maxsize & align) + 1;
}
gst_memory_init (GST_MEMORY_CAST (mem), flags, allocator, parent, maxsize,
align, offset, size);
mem->device = gst_object_ref (device);
mem->alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
mem->alloc_info.pNext = NULL;
mem->alloc_info.allocationSize = (VkDeviceSize) mem->mem.maxsize;
mem->alloc_info.memoryTypeIndex = memory_type_index;
mem->properties = mem_prop_flags;
mem->notify = notify;
mem->user_data = user_data;
mem->vk_offset = 0;
g_mutex_init (&mem->lock);
props_str = _memory_properties_to_string (mem_prop_flags);
GST_CAT_DEBUG (GST_CAT_VULKAN_MEMORY, "new Vulkan memory:%p size:%"
G_GSIZE_FORMAT " properties:%s", mem, maxsize, props_str);
g_free (props_str);
}
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)
{
GstVulkanMemory *mem = g_new0 (GstVulkanMemory, 1);
GError *error = NULL;
VkResult err;
_vk_mem_init (mem, allocator, parent, device, memory_type_index, params,
size, mem_props_flags, user_data, notify);
err =
vkAllocateMemory (device->device, &mem->alloc_info, NULL, &mem->mem_ptr);
if (gst_vulkan_error_to_g_error (err, &error, "vkAllocMemory") < 0) {
GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Failed to allocate device memory %s",
error->message);
gst_memory_unref ((GstMemory *) mem);
g_clear_error (&error);
return NULL;
}
return mem;
}
static gpointer
_vk_mem_map_full (GstVulkanMemory * mem, GstMapInfo * info, gsize size)
{
gpointer data;
VkResult err;
GError *error = NULL;
if ((mem->properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Cannot map host-invisible memory");
return NULL;
}
err = vkMapMemory (mem->device->device, mem->mem_ptr, mem->vk_offset,
size, 0, &data);
if (gst_vulkan_error_to_g_error (err, &error, "vkMapMemory") < 0) {
GST_CAT_ERROR (GST_CAT_VULKAN_MEMORY, "Failed to map device memory %s",
error->message);
g_clear_error (&error);
return NULL;
}
return data;
}
static void
_vk_mem_unmap_full (GstVulkanMemory * mem, GstMapInfo * info)
{
vkUnmapMemory (mem->device->device, mem->mem_ptr);
}
static GstMemory *
_vk_mem_copy (GstVulkanMemory * src, gssize offset, gssize size)
{
return NULL;
}
static GstMemory *
_vk_mem_share (GstVulkanMemory * mem, gssize offset, gsize size)
{
GstVulkanMemory *shared = g_new0 (GstVulkanMemory, 1);
GstVulkanMemory *parent = mem;
GstAllocationParams params = { 0, };
if (size == -1)
size = mem->mem.size - offset;
g_return_val_if_fail (size > 0, NULL);
while ((parent = (GstVulkanMemory *) (GST_MEMORY_CAST (parent)->parent)));
params.flags = GST_MEMORY_FLAGS (mem);
params.align = GST_MEMORY_CAST (parent)->align;
_vk_mem_init (shared, _vulkan_memory_allocator, GST_MEMORY_CAST (mem),
parent->device, parent->alloc_info.memoryTypeIndex, &params, size,
parent->properties, NULL, NULL);
shared->mem_ptr = parent->mem_ptr;
shared->wrapped = TRUE;
shared->vk_offset = offset + mem->vk_offset;
return GST_MEMORY_CAST (shared);
}
static gboolean
_vk_mem_is_span (GstVulkanMemory * mem1, GstVulkanMemory * mem2, gsize * offset)
{
return FALSE;
}
static GstMemory *
_vk_mem_alloc (GstAllocator * allocator, gsize size,
GstAllocationParams * params)
{
g_critical ("Subclass should override GstAllocatorClass::alloc() function");
return NULL;
}
static void
_vk_mem_free (GstAllocator * allocator, GstMemory * memory)
{
GstVulkanMemory *mem = (GstVulkanMemory *) memory;
GST_CAT_TRACE (GST_CAT_VULKAN_MEMORY, "freeing buffer memory:%p "
"id:%" G_GUINT64_FORMAT, mem, (guint64) mem->mem_ptr);
g_mutex_clear (&mem->lock);
if (mem->notify)
mem->notify (mem->user_data);
if (mem->mem_ptr && !mem->wrapped)
vkFreeMemory (mem->device->device, mem->mem_ptr, NULL);
gst_object_unref (mem->device);
}
gboolean
gst_vulkan_memory_find_memory_type_index_with_type_properties (GstVulkanDevice *
device, guint32 typeBits, VkMemoryPropertyFlags properties,
guint32 * typeIndex)
{
guint32 i;
/* Search memtypes to find first index with those properties */
for (i = 0; i < 32; i++) {
if ((typeBits & 1) == 1) {
/* Type is available, does it match user properties? */
if ((device->memory_properties.memoryTypes[i].
propertyFlags & properties) == properties) {
*typeIndex = i;
return TRUE;
}
}
typeBits >>= 1;
}
return FALSE;
}
/**
* gst_vulkan_memory_alloc:
* @device:a #GstVulkanDevice
* @memory_type_index: the Vulkan memory type index
* @params: a #GstAllocationParams
* @size: the size to allocate
*
* Allocated a new #GstVulkanMemory.
*
* Returns: a #GstMemory object backed by a vulkan device memory
*/
GstMemory *
gst_vulkan_memory_alloc (GstVulkanDevice * device, guint32 memory_type_index,
GstAllocationParams * params, gsize size, VkMemoryPropertyFlags mem_flags)
{
GstVulkanMemory *mem;
mem = _vk_mem_new (_vulkan_memory_allocator, NULL, device, memory_type_index,
params, size, mem_flags, NULL, NULL);
return (GstMemory *) mem;
}
G_DEFINE_TYPE (GstVulkanMemoryAllocator, gst_vulkan_memory_allocator,
GST_TYPE_ALLOCATOR);
static void
gst_vulkan_memory_allocator_class_init (GstVulkanMemoryAllocatorClass * klass)
{
GstAllocatorClass *allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = _vk_mem_alloc;
allocator_class->free = _vk_mem_free;
}
static void
gst_vulkan_memory_allocator_init (GstVulkanMemoryAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = GST_VULKAN_MEMORY_ALLOCATOR_NAME;
alloc->mem_map_full = (GstMemoryMapFullFunction) _vk_mem_map_full;
alloc->mem_unmap_full = (GstMemoryUnmapFullFunction) _vk_mem_unmap_full;
alloc->mem_copy = (GstMemoryCopyFunction) _vk_mem_copy;
alloc->mem_share = (GstMemoryShareFunction) _vk_mem_share;
alloc->mem_is_span = (GstMemoryIsSpanFunction) _vk_mem_is_span;
}
/**
* gst_vulkan_memory_init_once:
*
* Initializes the Vulkan memory allocator. It is safe to call this function
* multiple times. This must be called before any other #GstVulkanMemory operation.
*/
void
gst_vulkan_memory_init_once (void)
{
static volatile gsize _init = 0;
if (g_once_init_enter (&_init)) {
GST_DEBUG_CATEGORY_INIT (GST_CAT_VULKAN_MEMORY, "vulkanmemory", 0,
"Vulkan Memory");
_vulkan_memory_allocator =
g_object_new (gst_vulkan_memory_allocator_get_type (), NULL);
gst_object_ref_sink (_vulkan_memory_allocator);
gst_allocator_register (GST_VULKAN_MEMORY_ALLOCATOR_NAME,
gst_object_ref (_vulkan_memory_allocator));
g_once_init_leave (&_init, 1);
}
}
/**
* gst_is_vulkan_memory:
* @mem:a #GstMemory
*
* Returns: whether the memory at @mem is a #GstVulkanMemory
*/
gboolean
gst_is_vulkan_memory (GstMemory * mem)
{
return mem != NULL && mem->allocator != NULL &&
g_type_is_a (G_OBJECT_TYPE (mem->allocator),
GST_TYPE_VULKAN_MEMORY_ALLOCATOR);
}