videoconvert: Add videoconvert-vulkan

This commit is contained in:
columbarius 2023-09-24 18:32:23 +02:00
parent 87736cc685
commit 0af8a47058
17 changed files with 3586 additions and 2 deletions

View file

@ -73,6 +73,9 @@ extern "C" {
#define SPA_NAME_VIDEO_CONVERT "video.convert" /**< converts raw video from one format
* to another. Must include at least
* format and scaling */
#define SPA_NAME_VIDEO_CONVERT_VULKAN "video.convert.vulkan" /**< converts raw video from one format
* to another using vulkan. Must
* include at least format and scaling */
#define SPA_NAME_VIDEO_ADAPT "video.adapt" /**< combination of a node and a
* video.convert. */
/** keys for alsa factory names */

View file

@ -0,0 +1,62 @@
// Copyright (c) 2023 The wlroots contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/
/* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */
/* SPDX-License-Identifier: MIT */
#ifndef RENDER_DMABUF_H
#define RENDER_DMABUF_H
#include <stdbool.h>
#include <stdint.h>
#include "spa/support/log.h"
// Copied from <linux/dma-buf.h> to avoid #ifdef soup
#define DMA_BUF_SYNC_READ (1 << 0)
#define DMA_BUF_SYNC_WRITE (2 << 0)
#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
/**
* Check whether DMA-BUF import/export from/to sync_file is available.
*
* If this function returns true, dmabuf_import_sync_file() is supported.
*/
bool dmabuf_check_sync_file_import_export(struct spa_log *log);
/**
* Import a sync_file into a DMA-BUF with DMA_BUF_IOCTL_IMPORT_SYNC_FILE.
*
* This can be used to make explicit sync interoperate with implicit sync.
*/
bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd);
/**
* Export a sync_file from a DMA-BUF with DMA_BUF_IOCTL_EXPORT_SYNC_FILE.
*
* The sync_file FD is returned on success, -1 is returned on error.
*
* This can be used to make explicit sync interoperate with implicit sync.
*/
int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags);
#endif

View file

@ -0,0 +1,42 @@
// Copyright (c) 2023 The wlroots contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/
/* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */
/* SPDX-License-Identifier: MIT */
#include <spa/support/log.h>
#include <spa/utils/result.h>
bool dmabuf_check_sync_file_import_export(struct spa_log *log) {
return false;
}
bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd) {
spa_log_error("DMA-BUF sync_file import IOCTL not available on this system");
return false;
}
int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags) {
spa_log_error("DMA-BUF sync_file export IOCTL not available on this system");
return false;
}

View file

@ -0,0 +1,127 @@
// Copyright (c) 2023 The wlroots contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/
/* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */
/* SPDX-License-Identifier: MIT */
#include <linux/dma-buf.h>
#include <linux/version.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include <xf86drm.h>
#include <errno.h>
#include <spa/support/log.h>
#include <spa/utils/result.h>
#include "dmabuf.h"
bool dmabuf_check_sync_file_import_export(struct spa_log *log) {
/* Unfortunately there's no better way to check the availability of the
* IOCTL than to check the kernel version. See the discussion at:
* https://lore.kernel.org/dri-devel/20220601161303.64797-1-contact@emersion.fr/
*/
struct utsname utsname = {0};
if (uname(&utsname) != 0) {
spa_log_warn(log, "uname failed");
return false;
}
if (strcmp(utsname.sysname, "Linux") != 0) {
return false;
}
// Trim release suffix if any, e.g. "-arch1-1"
for (size_t i = 0; utsname.release[i] != '\0'; i++) {
char ch = utsname.release[i];
if ((ch < '0' || ch > '9') && ch != '.') {
utsname.release[i] = '\0';
break;
}
}
char *rel = strtok(utsname.release, ".");
int major = atoi(rel);
int minor = 0;
rel = strtok(NULL, ".");
if (rel != NULL) {
minor = atoi(rel);
}
int patch = 0;
rel = strtok(NULL, ".");
if (rel != NULL) {
patch = atoi(rel);
}
return KERNEL_VERSION(major, minor, patch) >= KERNEL_VERSION(5, 20, 0);
}
// TODO: drop these definitions once widespread
#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
#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
bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd) {
struct dma_buf_import_sync_file data = {
.flags = flags,
.fd = sync_file_fd,
};
if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) {
spa_log_error(log, "drmIoctl(IMPORT_SYNC_FILE) failed with %d (%s)", errno, spa_strerror(-errno));
return false;
}
return true;
}
int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags) {
struct dma_buf_export_sync_file data = {
.flags = flags,
.fd = -1,
};
if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &data) != 0) {
spa_log_error(log, "drmIoctl(EXPORT_SYNC_FILE) failed with %d (%s)", errno, spa_strerror(-errno));
return -1;
}
return data.fd;
}

View file

@ -3,13 +3,35 @@ videoconvert_sources = [
'plugin.c'
]
videoconvert_dep = []
if vulkan_headers
videoconvert_sources += files(
'pixel-formats.c',
'videoconvert-vulkan.c',
'vulkan-blit-utils.c',
'vulkan-utils.c',
'utils.c'
)
drm = dependency('libdrm')
if cc.has_header('linux/dma-buf.h') and target_machine.system() == 'linux'
videoconvert_sources += files('dmabuf_linux.c')
else
videoconvert_sources += files('dmabuf_fallback.c')
endif
videoconvert_dep += vulkan_dep
videoconvert_dep += drm
endif
simd_cargs = []
simd_dependencies = []
videoconvertlib = shared_library('spa-videoconvert',
videoconvert_sources,
c_args : simd_cargs,
dependencies : [ spa_dep, mathlib ],
dependencies : [ spa_dep, mathlib, videoconvert_dep ],
link_with : simd_dependencies,
install : true,
install_dir : spa_plugindir / 'videoconvert')

View file

@ -0,0 +1,33 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include "pixel-formats.h"
#include <spa/utils/defs.h>
#include <spa/param/video/raw.h>
struct pixel_info {
uint32_t format;
uint32_t bpp;
} pixel_infos[] = {
{ SPA_VIDEO_FORMAT_RGBA_F32, 16 },
{ SPA_VIDEO_FORMAT_BGRA, 4 },
{ SPA_VIDEO_FORMAT_RGBA, 4 },
{ SPA_VIDEO_FORMAT_BGRx, 4 },
{ SPA_VIDEO_FORMAT_RGBx, 4 },
{ SPA_VIDEO_FORMAT_BGR, 3 },
{ SPA_VIDEO_FORMAT_RGB, 3 },
};
bool get_pixel_format_info(uint32_t format, struct pixel_format_info *info)
{
struct pixel_info *p;
SPA_FOR_EACH_ELEMENT(pixel_infos, p) {
if (p->format != format)
continue;
info->bpp = p->bpp;
return true;
}
return false;
}

View file

@ -0,0 +1,12 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include <stdint.h>
#include <stdbool.h>
struct pixel_format_info {
uint32_t bpp; // bytes per pixel
};
bool get_pixel_format_info(uint32_t format, struct pixel_format_info *info);

View file

@ -7,6 +7,7 @@
#include <spa/support/plugin.h>
extern const struct spa_handle_factory spa_videoadapter_factory;
extern const struct spa_handle_factory spa_videoconvert_vulkan_factory;
SPA_EXPORT
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
@ -18,6 +19,9 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t
case 0:
*factory = &spa_videoadapter_factory;
break;
case 1:
*factory = &spa_videoconvert_vulkan_factory;
break;
default:
return 0;
}

View file

@ -0,0 +1,94 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include "utils.h"
#include <spa/param/param.h>
#include <spa/param/video/dsp.h>
#include <spa/param/video/raw.h>
// This function enumerates the available formats in vulkan_state::formats, announcing all formats capable to support DmaBufs
// first and then falling back to those supported with SHM buffers.
bool find_EnumFormatInfo(struct vulkan_format_infos *fmtInfos, uint32_t index, uint32_t caps, uint32_t *fmt_idx, bool *has_modifier) {
int64_t fmtIterator = 0;
int64_t maxIterator = 0;
if (caps & VULKAN_BUFFER_TYPE_CAP_SHM)
maxIterator += fmtInfos->formatCount;
if (caps & VULKAN_BUFFER_TYPE_CAP_DMABUF)
maxIterator += fmtInfos->formatCount;
// Count available formats until index underflows, while fmtIterator indexes the current format.
// Iterate twice over formats first time with modifiers, second time without if both caps are supported.
while (index < (uint32_t)-1 && fmtIterator < maxIterator) {
const struct vulkan_format_info *f_info = &fmtInfos->infos[fmtIterator%fmtInfos->formatCount];
if (caps & VULKAN_BUFFER_TYPE_CAP_DMABUF && fmtIterator < fmtInfos->formatCount) {
// First round, check for modifiers
if (f_info->modifierCount > 0) {
index--;
}
} else if (caps & VULKAN_BUFFER_TYPE_CAP_SHM) {
// Second round, every format should be supported.
index--;
}
fmtIterator++;
}
if (index != (uint32_t)-1) {
// No more formats available
return false;
}
// Undo end of loop increment
fmtIterator--;
*fmt_idx = fmtIterator%fmtInfos->formatCount;
// Loop finished in first round
*has_modifier = caps & VULKAN_BUFFER_TYPE_CAP_DMABUF && fmtIterator < fmtInfos->formatCount;
return true;
}
struct spa_pod *build_dsp_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder) {
struct spa_pod_frame f[2];
uint32_t i, c;
spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), 0);
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(fmt->spa_format), 0);
if (with_modifiers && fmt->modifierCount > 0) {
spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0);
for (i = 0, c = 0; i < fmt->modifierCount; i++) {
spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier);
if (c++ == 0)
spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier);
}
spa_pod_builder_pop(builder, &f[1]);
}
return spa_pod_builder_pop(builder, &f[0]);
}
struct spa_pod *build_raw_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder) {
struct spa_pod_frame f[2];
uint32_t i, c;
spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(fmt->spa_format), 0);
if (with_modifiers && fmt->modifierCount > 0) {
spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0);
for (i = 0, c = 0; i < fmt->modifierCount; i++) {
spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier);
if (c++ == 0)
spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier);
}
spa_pod_builder_pop(builder, &f[1]);
}
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
SPA_POD_CHOICE_RANGE_Rectangle(
&SPA_RECTANGLE(640, 480), // Arbitrary
&SPA_RECTANGLE(1, 1),
&SPA_RECTANGLE(8192, 4320)),
0);
return spa_pod_builder_pop(builder, &f[0]);
}

View file

@ -0,0 +1,11 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include "vulkan-types.h"
#include "spa/pod/builder.h"
bool find_EnumFormatInfo(struct vulkan_format_infos *fmtInfos, uint32_t index, uint32_t caps, uint32_t *fmt_idx, bool *has_modifier);
struct spa_pod *build_dsp_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder);
struct spa_pod *build_raw_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder);

View file

@ -28,7 +28,7 @@
#define SPA_LOG_TOPIC_DEFAULT log_topic
static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.videoadapter");
#if 0
#if 1
#define VIDEOCONVERTER 1
#define VIDEOCONVERTER_FACTORY spa_videoconvert_vulkan_factory
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,639 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include <vulkan/vulkan.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <vulkan/vulkan_core.h>
#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
#include <alloca.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/support/log.h>
#include <spa/debug/mem.h>
#include "vulkan-blit-utils.h"
#include "vulkan-utils.h"
#include "utils.h"
#define VULKAN_INSTANCE_FUNCTION(name) \
PFN_##name name = (PFN_##name)vkGetInstanceProcAddr(s->base.instance, #name)
static int updateBuffers(struct vulkan_blit_state *s)
{
uint32_t i;
for (i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
if (p->current_buffer_id == p->pending_buffer_id ||
p->pending_buffer_id == SPA_ID_INVALID)
continue;
p->current_buffer_id = p->pending_buffer_id;
p->busy_buffer_id = p->current_buffer_id;
p->pending_buffer_id = SPA_ID_INVALID;
}
return 0;
}
static int runImportSHMBuffers(struct vulkan_blit_state *s) {
struct vulkan_stream *p = &s->streams[SPA_DIRECTION_INPUT];
if (p->spa_buffers[p->current_buffer_id]->datas[0].type == SPA_DATA_MemPtr) {
struct vulkan_buffer *vk_buf = &p->buffers[p->current_buffer_id];
struct spa_buffer *spa_buf = p->spa_buffers[p->current_buffer_id];
VkBufferImageCopy copy;
struct vulkan_write_pixels_info writeInfo = {
.data = spa_buf->datas[0].data,
.offset = 0,
.stride = p->bpp * p->dim.width,
.bytes_per_pixel = p->bpp,
.size.width = p->dim.width,
.size.height = p->dim.height,
.copies = &copy,
};
CHECK(vulkan_write_pixels(&s->base, &writeInfo, &s->staging_buffer));
vkCmdCopyBufferToImage(s->commandBuffer, s->staging_buffer.buffer, vk_buf->image,
VK_IMAGE_LAYOUT_GENERAL, 1, &copy);
}
return 0;
}
static int runExportSHMBuffers(struct vulkan_blit_state *s) {
struct vulkan_stream *p = &s->streams[SPA_DIRECTION_OUTPUT];
if (p->spa_buffers[p->current_buffer_id]->datas[0].type == SPA_DATA_MemPtr) {
struct spa_buffer *spa_buf = p->spa_buffers[p->current_buffer_id];
struct vulkan_read_pixels_info readInfo = {
.data = spa_buf->datas[0].data,
.offset = 0,
.stride = p->bpp * p->dim.width,
.bytes_per_pixel = p->bpp,
.size.width = p->dim.width,
.size.height = p->dim.height,
};
CHECK(vulkan_read_pixels(&s->base, &readInfo, &p->buffers[p->current_buffer_id]));
}
return 0;
}
/** runCommandBuffer
* The return value of this functions means the following:
* ret < 0: Error
* ret = 0: queueSubmit was succsessful, but manual synchronization is required
* ret = 1: queueSubmit was succsessful and buffers can be released without synchronization
*/
static int runCommandBuffer(struct vulkan_blit_state *s)
{
VULKAN_INSTANCE_FUNCTION(vkQueueSubmit2KHR);
VULKAN_INSTANCE_FUNCTION(vkGetSemaphoreFdKHR);
static const VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_CHECK_RESULT(vkBeginCommandBuffer(s->commandBuffer, &beginInfo));
CHECK(runImportSHMBuffers(s));
uint32_t i;
struct vulkan_stream *stream_input = &s->streams[SPA_DIRECTION_INPUT];
struct vulkan_stream *stream_output = &s->streams[SPA_DIRECTION_OUTPUT];
VkImage src_image = stream_input->buffers[stream_input->current_buffer_id].image;
VkImage dst_image = stream_output->buffers[stream_output->current_buffer_id].image;
VkImageBlit imageBlitRegion = {
.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.srcSubresource.layerCount = 1,
.srcOffsets[0] = {
.x = 0,
.y = 0,
.z = 0,
},
.srcOffsets[1] = {
.x = stream_input->dim.width,
.y = stream_input->dim.height,
.z = 1,
},
.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.dstSubresource.layerCount = 1,
.dstOffsets[1] = {
.x = stream_output->dim.width,
.y = stream_output->dim.height,
.z = 1,
}
};
spa_log_trace_fp(s->log, "Blitting from (%p, %d, %d) %d,%dx%d,%d to (%p, %d, %d) %d,%dx%d,%d",
stream_input, stream_input->current_buffer_id, stream_input->direction, 0, 0, stream_input->dim.width, stream_input->dim.height,
stream_output, stream_output->current_buffer_id, stream_output->direction, 0, 0, stream_output->dim.width, stream_output->dim.height);
vkCmdBlitImage(s->commandBuffer, src_image, VK_IMAGE_LAYOUT_GENERAL,
dst_image, VK_IMAGE_LAYOUT_GENERAL,
1, &imageBlitRegion, VK_FILTER_NEAREST);
VkImageMemoryBarrier acquire_barrier[s->n_streams];
VkImageMemoryBarrier release_barrier[s->n_streams];
VkSemaphoreSubmitInfo semaphore_wait_info[s->n_streams];
uint32_t semaphore_wait_info_len = 0;
VkSemaphoreSubmitInfo semaphore_signal_info[1];
uint32_t semaphore_signal_info_len = 0;
for (i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
struct vulkan_buffer *current_buffer = &p->buffers[p->current_buffer_id];
struct spa_buffer *current_spa_buffer = p->spa_buffers[p->current_buffer_id];
VkAccessFlags access_flags;
if (p->direction == SPA_DIRECTION_INPUT) {
access_flags = VK_ACCESS_TRANSFER_READ_BIT;
} else {
access_flags = VK_ACCESS_TRANSFER_WRITE_BIT;
}
acquire_barrier[i]= (VkImageMemoryBarrier) {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = s->base.queueFamilyIndex,
.image = current_buffer->image,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcAccessMask = 0,
.dstAccessMask = access_flags,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
release_barrier[i]= (VkImageMemoryBarrier) {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcQueueFamilyIndex = s->base.queueFamilyIndex,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.image = current_buffer->image,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcAccessMask = access_flags,
.dstAccessMask = 0,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
if (current_spa_buffer->datas[0].type != SPA_DATA_DmaBuf)
continue;
if (vulkan_sync_foreign_dmabuf(&s->base, current_buffer) < 0) {
spa_log_warn(s->log, "Failed to wait for foreign buffer DMA-BUF fence");
} else {
if (current_buffer->foreign_semaphore != VK_NULL_HANDLE) {
semaphore_wait_info[semaphore_wait_info_len++] = (VkSemaphoreSubmitInfo) {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.semaphore = current_buffer->foreign_semaphore,
.stageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
};
}
}
}
vkCmdPipelineBarrier(s->commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_2_TRANSFER_BIT,
0, 0, NULL, 0, NULL,
s->n_streams, acquire_barrier);
vkCmdPipelineBarrier(s->commandBuffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, NULL, 0, NULL,
s->n_streams, release_barrier);
VK_CHECK_RESULT(vkEndCommandBuffer(s->commandBuffer));
VK_CHECK_RESULT(vkResetFences(s->base.device, 1, &s->fence));
if (s->pipelineSemaphore == VK_NULL_HANDLE) {
VkExportSemaphoreCreateInfo export_info = {
.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
};
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = &export_info,
};
VK_CHECK_RESULT(vkCreateSemaphore(s->base.device, &semaphore_info, NULL, &s->pipelineSemaphore));
}
semaphore_signal_info[semaphore_signal_info_len++] = (VkSemaphoreSubmitInfo) {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.semaphore = s->pipelineSemaphore,
};
VkCommandBufferSubmitInfoKHR commandBufferInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.commandBuffer = s->commandBuffer,
};
const VkSubmitInfo2KHR submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR,
.commandBufferInfoCount = 1,
.pCommandBufferInfos = &commandBufferInfo,
.waitSemaphoreInfoCount = semaphore_wait_info_len,
.pWaitSemaphoreInfos = semaphore_wait_info,
.signalSemaphoreInfoCount = semaphore_signal_info_len,
.pSignalSemaphoreInfos = semaphore_signal_info,
};
VK_CHECK_RESULT(vkQueueSubmit2KHR(s->base.queue, 1, &submitInfo, s->fence));
s->started = true;
VkSemaphoreGetFdInfoKHR get_fence_fd_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
.semaphore = s->pipelineSemaphore,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
};
int sync_file_fd = -1;
VK_CHECK_RESULT(vkGetSemaphoreFdKHR(s->base.device, &get_fence_fd_info, &sync_file_fd));
int ret = 1;
for (uint32_t i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
struct spa_buffer *current_spa_buffer = p->spa_buffers[p->current_buffer_id];
if (current_spa_buffer->datas[0].type != SPA_DATA_DmaBuf)
continue;
if (!vulkan_sync_export_dmabuf(&s->base, &p->buffers[p->current_buffer_id], sync_file_fd)) {
ret = 0;
}
}
close(sync_file_fd);
return ret;
}
static void clear_buffers(struct vulkan_blit_state *s, struct vulkan_stream *p)
{
uint32_t i;
for (i = 0; i < p->n_buffers; i++) {
vulkan_buffer_clear(&s->base, &p->buffers[i]);
p->spa_buffers[i] = NULL;
}
p->n_buffers = 0;
if (p->direction == SPA_DIRECTION_INPUT) {
vulkan_staging_buffer_destroy(&s->base, &s->staging_buffer);
s->staging_buffer.buffer = VK_NULL_HANDLE;
}
}
static void clear_streams(struct vulkan_blit_state *s)
{
uint32_t i;
for (i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
clear_buffers(s, p);
}
}
int spa_vulkan_blit_fixate_modifier(struct vulkan_blit_state *s, struct vulkan_stream *p, struct spa_video_info *info,
uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier)
{
VkFormat format;
struct spa_rectangle size;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_dsp:
format = vulkan_id_to_vkformat(info->info.dsp.format);
size.width = p->dim.width;
size.height = p->dim.height;
break;
case SPA_MEDIA_SUBTYPE_raw:
format = vulkan_id_to_vkformat(info->info.raw.format);
size.width = p->dim.width;
size.height = p->dim.height;
break;
default:
spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype);
return -1;
}
if (format == VK_FORMAT_UNDEFINED) {
return -1;
}
struct dmabuf_fixation_info fixation_info = {
.format = format,
.modifierCount = modifierCount,
.modifiers = modifiers,
.size = size,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
};
return vulkan_fixate_modifier(&s->base, &fixation_info, modifier);
}
int spa_vulkan_blit_use_buffers(struct vulkan_blit_state *s, struct vulkan_stream *p, uint32_t flags,
struct spa_video_info *info, uint32_t n_buffers, struct spa_buffer **buffers)
{
struct external_buffer_info externalBufferInfo = {0};
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_dsp:
externalBufferInfo.format = vulkan_id_to_vkformat(info->info.dsp.format);
externalBufferInfo.size.width = p->dim.width;
externalBufferInfo.size.height = p->dim.height;
if (info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER)
externalBufferInfo.modifier = info->info.dsp.modifier;
break;
case SPA_MEDIA_SUBTYPE_raw:
externalBufferInfo.format = vulkan_id_to_vkformat(info->info.raw.format);
externalBufferInfo.size.width = p->dim.width;
externalBufferInfo.size.height = p->dim.height;
if (info->info.raw.flags & SPA_VIDEO_FLAG_MODIFIER)
externalBufferInfo.modifier = info->info.raw.modifier;
break;
default:
spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype);
return -1;
}
if (externalBufferInfo.format == VK_FORMAT_UNDEFINED)
return -1;
vulkan_wait_idle(&s->base);
clear_buffers(s, p);
bool alloc = flags & SPA_NODE_BUFFERS_FLAG_ALLOC;
int ret;
p->n_buffers = 0;
for (uint32_t i = 0; i < n_buffers; i++) {
externalBufferInfo.usage = p->direction == SPA_DIRECTION_OUTPUT
? VK_IMAGE_USAGE_TRANSFER_DST_BIT
: VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
externalBufferInfo.spa_buf = buffers[i];
if (alloc) {
if (SPA_FLAG_IS_SET(buffers[i]->datas[0].type, 1<<SPA_DATA_DmaBuf)) {
ret = vulkan_create_dmabuf(&s->base, &externalBufferInfo, &p->buffers[i]);
} else {
spa_log_error(s->log, "Unsupported buffer type mask %d", buffers[i]->datas[0].type);
return -1;
}
} else {
switch (buffers[i]->datas[0].type) {
case SPA_DATA_DmaBuf:;
ret = vulkan_import_dmabuf(&s->base, &externalBufferInfo, &p->buffers[i]);
break;
case SPA_DATA_MemPtr:;
if (p->direction == SPA_DIRECTION_OUTPUT) {
externalBufferInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
} else {
externalBufferInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
ret = vulkan_import_memptr(&s->base, &externalBufferInfo, &p->buffers[i]);
break;
default:
spa_log_error(s->log, "Unsupported buffer type %d", buffers[i]->datas[0].type);
return -1;
}
}
if (ret != 0) {
spa_log_error(s->log, "Failed to use buffer %d", i);
return ret;
}
p->spa_buffers[i] = buffers[i];
p->n_buffers++;
}
if (p->direction == SPA_DIRECTION_INPUT && buffers[0]->datas[0].type == SPA_DATA_MemPtr) {
ret = vulkan_staging_buffer_create(&s->base, buffers[0]->datas[0].maxsize, &s->staging_buffer);
if (ret < 0) {
spa_log_error(s->log, "Failed to create staging buffer");
return ret;
}
}
return 0;
}
int spa_vulkan_blit_enumerate_raw_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder)
{
uint32_t fmt_idx;
bool has_modifier;
if (!find_EnumFormatInfo(&s->formatInfosRaw, index, caps, &fmt_idx, &has_modifier))
return 0;
*param = build_raw_EnumFormat(&s->formatInfosRaw.infos[fmt_idx], has_modifier, builder);
return 1;
}
int spa_vulkan_blit_enumerate_dsp_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder)
{
uint32_t fmt_idx;
bool has_modifier;
if (!find_EnumFormatInfo(&s->formatInfosDSP, index, caps, &fmt_idx, &has_modifier))
return 0;
*param = build_dsp_EnumFormat(&s->formatInfosDSP.infos[fmt_idx], has_modifier, builder);
return 1;
}
int spa_vulkan_blit_enumerate_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder)
{
uint32_t fmt_idx;
bool has_modifier;
uint32_t raw_offset = 0;
if ((caps & VULKAN_BUFFER_TYPE_CAP_SHM) > 0)
raw_offset += s->formatInfosDSP.formatCount;
if ((caps & VULKAN_BUFFER_TYPE_CAP_DMABUF) > 0)
raw_offset += s->formatInfosDSP.formatsWithModifiersCount;
if (index < raw_offset) {
if (find_EnumFormatInfo(&s->formatInfosDSP, index, caps, &fmt_idx, &has_modifier)) {
*param = build_dsp_EnumFormat(&s->formatInfosDSP.infos[fmt_idx], has_modifier, builder);
return 1;
}
} else {
if (find_EnumFormatInfo(&s->formatInfosRaw, index - raw_offset, caps, &fmt_idx, &has_modifier)) {
*param = build_raw_EnumFormat(&s->formatInfosRaw.infos[fmt_idx], has_modifier, builder);
return 1;
}
}
return 0;
}
static int vulkan_stream_init(struct vulkan_stream *stream, enum spa_direction direction,
struct spa_dict *props)
{
spa_zero(*stream);
stream->direction = direction;
stream->current_buffer_id = SPA_ID_INVALID;
stream->busy_buffer_id = SPA_ID_INVALID;
stream->ready_buffer_id = SPA_ID_INVALID;
return 0;
}
int spa_vulkan_blit_init_stream(struct vulkan_blit_state *s, struct vulkan_stream *stream,
enum spa_direction direction, struct spa_dict *props)
{
return vulkan_stream_init(stream, direction, props);
}
int spa_vulkan_blit_prepare(struct vulkan_blit_state *s)
{
if (!s->prepared) {
CHECK(vulkan_fence_create(&s->base, &s->fence));
CHECK(vulkan_commandPool_create(&s->base, &s->commandPool));
CHECK(vulkan_commandBuffer_create(&s->base, s->commandPool, &s->commandBuffer));
s->prepared = true;
}
return 0;
}
int spa_vulkan_blit_unprepare(struct vulkan_blit_state *s)
{
if (s->prepared) {
vkDestroyCommandPool(s->base.device, s->commandPool, NULL);
vkDestroyFence(s->base.device, s->fence, NULL);
s->prepared = false;
}
return 0;
}
int spa_vulkan_blit_start(struct vulkan_blit_state *s)
{
uint32_t i;
for (i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
p->current_buffer_id = SPA_ID_INVALID;
p->busy_buffer_id = SPA_ID_INVALID;
p->ready_buffer_id = SPA_ID_INVALID;
}
return 0;
}
int spa_vulkan_blit_stop(struct vulkan_blit_state *s)
{
VK_CHECK_RESULT(vkDeviceWaitIdle(s->base.device));
clear_streams(s);
s->started = false;
return 0;
}
int spa_vulkan_blit_ready(struct vulkan_blit_state *s)
{
uint32_t i;
VkResult result;
if (!s->started)
return 0;
result = vkGetFenceStatus(s->base.device, s->fence);
if (result == VK_NOT_READY)
return -EBUSY;
VK_CHECK_RESULT(result);
s->started = false;
for (i = 0; i < s->n_streams; i++) {
struct vulkan_stream *p = &s->streams[i];
p->ready_buffer_id = p->busy_buffer_id;
p->busy_buffer_id = SPA_ID_INVALID;
}
return 0;
}
int spa_vulkan_blit_process(struct vulkan_blit_state *s)
{
if (!s->initialized) {
spa_log_warn(s->log, "Renderer not initialized");
return -1;
}
if (!s->prepared) {
spa_log_warn(s->log, "Renderer not prepared");
return -1;
}
CHECK(updateBuffers(s));
CHECK(runCommandBuffer(s));
// CHECK(vulkan_wait_fence(&s->base, s->fence));
CHECK(vulkan_wait_idle(&s->base));
CHECK(runExportSHMBuffers(s));
return 0;
}
int spa_vulkan_blit_get_buffer_caps(struct vulkan_blit_state *s, enum spa_direction direction)
{
switch (direction) {
case SPA_DIRECTION_INPUT:
return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM;
case SPA_DIRECTION_OUTPUT:
return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM;
}
return 0;
}
struct vulkan_modifier_info *spa_vulkan_blit_get_modifier_info(struct vulkan_blit_state *s, struct spa_video_info *info) {
VkFormat format;
uint64_t modifier;
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_dsp:
format = vulkan_id_to_vkformat(info->info.dsp.format);
modifier = info->info.dsp.modifier;
return vulkan_modifierInfo_find(&s->formatInfosDSP, format, modifier);
case SPA_MEDIA_SUBTYPE_raw:
format = vulkan_id_to_vkformat(info->info.raw.format);
modifier = info->info.raw.modifier;
return vulkan_modifierInfo_find(&s->formatInfosRaw, format, modifier);
default:
spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype);
return NULL;
}
}
int spa_vulkan_blit_init(struct vulkan_blit_state *s)
{
int ret;
s->base.log = s->log;
struct vulkan_base_info baseInfo = {
.queueFlags = VK_QUEUE_TRANSFER_BIT,
};
if ((ret = vulkan_base_init(&s->base, &baseInfo)) < 0)
return ret;
uint32_t dsp_formats [] = {
SPA_VIDEO_FORMAT_DSP_F32
};
vulkan_format_infos_init(&s->base, SPA_N_ELEMENTS(dsp_formats), dsp_formats, &s->formatInfosDSP);
uint32_t raw_formats [] = {
SPA_VIDEO_FORMAT_BGRA,
SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_BGRx,
SPA_VIDEO_FORMAT_RGBx,
SPA_VIDEO_FORMAT_BGR,
SPA_VIDEO_FORMAT_RGB,
};
vulkan_format_infos_init(&s->base, SPA_N_ELEMENTS(raw_formats), raw_formats, &s->formatInfosRaw);
s->initialized = true;
return 0;
}
void spa_vulkan_blit_deinit(struct vulkan_blit_state *s)
{
vulkan_format_infos_deinit(&s->formatInfosRaw);
vulkan_format_infos_deinit(&s->formatInfosDSP);
vulkan_base_deinit(&s->base);
s->initialized = false;
}

View file

@ -0,0 +1,84 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */
/* SPDX-License-Identifier: MIT */
#include <vulkan/vulkan.h>
#include <spa/buffer/buffer.h>
#include <spa/param/video/format.h>
#include <spa/node/node.h>
#include <spa/pod/builder.h>
#include "vulkan-utils.h"
#define MAX_STREAMS 2
struct vulkan_stream {
enum spa_direction direction;
uint32_t pending_buffer_id;
uint32_t current_buffer_id;
uint32_t busy_buffer_id;
uint32_t ready_buffer_id;
struct spa_rectangle dim;
uint32_t bpp;
struct vulkan_buffer buffers[MAX_BUFFERS];
struct spa_buffer *spa_buffers[MAX_BUFFERS];
uint32_t n_buffers;
};
struct vulkan_blit_state {
struct spa_log *log;
struct vulkan_base base;
struct vulkan_format_infos formatInfosRaw;
struct vulkan_format_infos formatInfosDSP;
VkCommandPool commandPool;
VkCommandBuffer commandBuffer;
struct vulkan_staging_buffer staging_buffer;
VkFence fence;
VkSemaphore pipelineSemaphore;
unsigned int initialized:1;
unsigned int prepared:1;
unsigned int started:1;
//VkSampler sampler;
uint32_t n_streams;
struct vulkan_stream streams[MAX_STREAMS];
};
int spa_vulkan_blit_init_stream(struct vulkan_blit_state *s, struct vulkan_stream *stream, enum spa_direction,
struct spa_dict *props);
int spa_vulkan_blit_fixate_modifier(struct vulkan_blit_state *s, struct vulkan_stream *p, struct spa_video_info *info,
uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier);
int spa_vulkan_blit_use_buffers(struct vulkan_blit_state *s, struct vulkan_stream *stream, uint32_t flags,
struct spa_video_info *info, uint32_t n_buffers, struct spa_buffer **buffers);
int spa_vulkan_blit_enumerate_raw_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder);
int spa_vulkan_blit_enumerate_dsp_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder);
int spa_vulkan_blit_enumerate_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps,
struct spa_pod **param, struct spa_pod_builder *builder);
int spa_vulkan_blit_prepare(struct vulkan_blit_state *s);
int spa_vulkan_blit_unprepare(struct vulkan_blit_state *s);
int spa_vulkan_blit_start(struct vulkan_blit_state *s);
int spa_vulkan_blit_stop(struct vulkan_blit_state *s);
int spa_vulkan_blit_ready(struct vulkan_blit_state *s);
int spa_vulkan_blit_process(struct vulkan_blit_state *s);
int spa_vulkan_blit_cleanup(struct vulkan_blit_state *s);
int spa_vulkan_blit_get_buffer_caps(struct vulkan_blit_state *s, enum spa_direction direction);
struct vulkan_modifier_info *spa_vulkan_blit_get_modifier_info(struct vulkan_blit_state *s,
struct spa_video_info *info);
int spa_vulkan_blit_init(struct vulkan_blit_state *s);
void spa_vulkan_blit_deinit(struct vulkan_blit_state *s);

View file

@ -0,0 +1,66 @@
#pragma once
#include <vulkan/vulkan.h>
#include <spa/buffer/buffer.h>
#include <spa/node/node.h>
#define MAX_BUFFERS 16
#define DMABUF_MAX_PLANES 1
enum buffer_type_caps {
VULKAN_BUFFER_TYPE_CAP_SHM = 1<<0,
VULKAN_BUFFER_TYPE_CAP_DMABUF = 1<<1,
};
struct vulkan_modifier_info {
VkDrmFormatModifierPropertiesEXT props;
VkExtent2D max_extent;
};
struct vulkan_format_info {
uint32_t spa_format;
VkFormat vk_format;
uint32_t modifierCount;
struct vulkan_modifier_info *infos;
};
struct vulkan_format_infos {
uint32_t formatCount;
struct vulkan_format_info *infos;
uint32_t formatsWithModifiersCount;
};
struct vulkan_buffer {
int fd;
VkImage image;
VkImageView view;
VkDeviceMemory memory;
VkSemaphore foreign_semaphore;
};
struct vulkan_staging_buffer {
VkBuffer buffer;
VkDeviceMemory memory;
};
struct vulkan_base_info {
uint32_t queueFlags;
};
struct vulkan_base {
struct spa_log *log;
VkInstance instance;
VkPhysicalDevice physicalDevice;
VkQueue queue;
uint32_t queueFamilyIndex;
VkDevice device;
bool implicit_sync_interop;
unsigned int initialized:1;
};

View file

@ -0,0 +1,980 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <vulkan/vulkan.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
#include <alloca.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/param/video/format.h>
#include <spa/support/log.h>
#include <spa/debug/mem.h>
#include "vulkan-utils.h"
#include "dmabuf.h"
//#define ENABLE_VALIDATION
#define VULKAN_INSTANCE_FUNCTION(name) \
PFN_##name name = (PFN_##name)vkGetInstanceProcAddr(s->instance, #name)
static int vkresult_to_errno(VkResult result)
{
switch (result) {
case VK_SUCCESS:
case VK_EVENT_SET:
case VK_EVENT_RESET:
return 0;
case VK_NOT_READY:
case VK_INCOMPLETE:
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
return EBUSY;
case VK_TIMEOUT:
return ETIMEDOUT;
case VK_ERROR_OUT_OF_HOST_MEMORY:
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
case VK_ERROR_MEMORY_MAP_FAILED:
case VK_ERROR_OUT_OF_POOL_MEMORY:
case VK_ERROR_FRAGMENTED_POOL:
#ifdef VK_ERROR_FRAGMENTATION_EXT
case VK_ERROR_FRAGMENTATION_EXT:
#endif
return ENOMEM;
case VK_ERROR_INITIALIZATION_FAILED:
return EIO;
case VK_ERROR_DEVICE_LOST:
case VK_ERROR_SURFACE_LOST_KHR:
#ifdef VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
#endif
return ENODEV;
case VK_ERROR_LAYER_NOT_PRESENT:
case VK_ERROR_EXTENSION_NOT_PRESENT:
case VK_ERROR_FEATURE_NOT_PRESENT:
return ENOENT;
case VK_ERROR_INCOMPATIBLE_DRIVER:
case VK_ERROR_FORMAT_NOT_SUPPORTED:
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
return ENOTSUP;
case VK_ERROR_TOO_MANY_OBJECTS:
return ENFILE;
case VK_SUBOPTIMAL_KHR:
case VK_ERROR_OUT_OF_DATE_KHR:
return EIO;
case VK_ERROR_INVALID_EXTERNAL_HANDLE:
case VK_ERROR_INVALID_SHADER_NV:
#ifdef VK_ERROR_VALIDATION_FAILED_EXT
case VK_ERROR_VALIDATION_FAILED_EXT:
#endif
#ifdef VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT
case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
#endif
#ifdef VK_ERROR_INVALID_DEVICE_ADDRESS_EXT
case VK_ERROR_INVALID_DEVICE_ADDRESS_EXT:
#endif
return EINVAL;
#ifdef VK_ERROR_NOT_PERMITTED_EXT
case VK_ERROR_NOT_PERMITTED_EXT:
return EPERM;
#endif
default:
return EIO;
}
}
static struct {
VkFormat format;
uint32_t id;
} vk_video_format_convs[] = {
{ VK_FORMAT_R32G32B32A32_SFLOAT, SPA_VIDEO_FORMAT_RGBA_F32 },
{ VK_FORMAT_B8G8R8A8_SRGB, SPA_VIDEO_FORMAT_BGRA },
{ VK_FORMAT_R8G8B8A8_SRGB, SPA_VIDEO_FORMAT_RGBA },
{ VK_FORMAT_B8G8R8A8_SRGB, SPA_VIDEO_FORMAT_BGRx },
{ VK_FORMAT_R8G8B8A8_SRGB, SPA_VIDEO_FORMAT_RGBx },
{ VK_FORMAT_B8G8R8_SRGB, SPA_VIDEO_FORMAT_BGR },
{ VK_FORMAT_R8G8B8_SRGB, SPA_VIDEO_FORMAT_RGB },
};
static int createInstance(struct vulkan_base *s)
{
static const VkApplicationInfo applicationInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "PipeWire",
.applicationVersion = 0,
.pEngineName = "PipeWire Vulkan Engine",
.engineVersion = 0,
.apiVersion = VK_API_VERSION_1_1
};
static const char * const extensions[] = {
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME
};
static const char * const checkLayers[] = {
#ifdef ENABLE_VALIDATION
"VK_LAYER_KHRONOS_validation",
#endif
NULL
};
uint32_t i, j, layerCount, n_layers = 0;
const char *layers[1];
vkEnumerateInstanceLayerProperties(&layerCount, NULL);
VkLayerProperties availableLayers[layerCount];
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);
for (i = 0; i < layerCount; i++) {
for (j = 0; j < SPA_N_ELEMENTS(checkLayers); j++) {
if (spa_streq(availableLayers[i].layerName, checkLayers[j]))
layers[n_layers++] = checkLayers[j];
}
}
const VkInstanceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &applicationInfo,
.enabledExtensionCount = 1,
.ppEnabledExtensionNames = extensions,
.enabledLayerCount = n_layers,
.ppEnabledLayerNames = layers,
};
VK_CHECK_RESULT(vkCreateInstance(&createInfo, NULL, &s->instance));
return 0;
}
static int findPhysicalDevice(struct vulkan_base *s)
{
uint32_t deviceCount;
VkPhysicalDevice *devices;
vkEnumeratePhysicalDevices(s->instance, &deviceCount, NULL);
if (deviceCount == 0)
return -ENODEV;
devices = alloca(deviceCount * sizeof(VkPhysicalDevice));
vkEnumeratePhysicalDevices(s->instance, &deviceCount, devices);
s->physicalDevice = devices[0];
return 0;
}
static int getComputeQueueFamilyIndex(struct vulkan_base *s, uint32_t queueFlags, uint32_t *queueFamilyIndex)
{
uint32_t i, queueFamilyCount;
VkQueueFamilyProperties *queueFamilies;
vkGetPhysicalDeviceQueueFamilyProperties(s->physicalDevice, &queueFamilyCount, NULL);
queueFamilies = alloca(queueFamilyCount * sizeof(VkQueueFamilyProperties));
vkGetPhysicalDeviceQueueFamilyProperties(s->physicalDevice, &queueFamilyCount, queueFamilies);
for (i = 0; i < queueFamilyCount; i++) {
VkQueueFamilyProperties props = queueFamilies[i];
if (props.queueCount > 0 && ((props.queueFlags & queueFlags) == queueFlags))
break;
}
if (i == queueFamilyCount)
return -ENODEV;
*queueFamilyIndex = i;
return 0;
}
static int createDevice(struct vulkan_base *s, struct vulkan_base_info *info)
{
CHECK(getComputeQueueFamilyIndex(s, info->queueFlags, &s->queueFamilyIndex));
const VkDeviceQueueCreateInfo queueCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = s->queueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = (const float[]) { 1.0f }
};
const VkPhysicalDeviceSynchronization2FeaturesKHR sync2_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR,
.synchronization2 = VK_TRUE,
};
static const char * const extensions[] = {
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
};
const VkDeviceCreateInfo deviceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queueCreateInfo,
.enabledExtensionCount = SPA_N_ELEMENTS(extensions),
.ppEnabledExtensionNames = extensions,
.pNext = &sync2_features,
};
VK_CHECK_RESULT(vkCreateDevice(s->physicalDevice, &deviceCreateInfo, NULL, &s->device));
vkGetDeviceQueue(s->device, s->queueFamilyIndex, 0, &s->queue);
return 0;
}
int vulkan_write_pixels(struct vulkan_base *s, struct vulkan_write_pixels_info *info, struct vulkan_staging_buffer *vk_sbuf)
{
void *vmap;
VK_CHECK_RESULT(vkMapMemory(s->device, vk_sbuf->memory, 0, VK_WHOLE_SIZE, 0, &vmap));
char *map = (char *)vmap;
// upload data
const char *pdata = info->data;
memcpy(map, pdata, info->stride * info->size.height);
info->copies[0] = (VkBufferImageCopy) {
.imageExtent.width = info->size.width,
.imageExtent.height = info->size.height,
.imageExtent.depth = 1,
.imageOffset.x = 0,
.imageOffset.y = 0,
.imageOffset.z = 0,
.bufferOffset = 0,
.bufferRowLength = info->size.width,
.bufferImageHeight = info->size.height,
.imageSubresource.mipLevel = 0,
.imageSubresource.baseArrayLayer = 0,
.imageSubresource.layerCount = 1,
.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
};
vkUnmapMemory(s->device, vk_sbuf->memory);
return 0;
}
int vulkan_read_pixels(struct vulkan_base *s, struct vulkan_read_pixels_info *info, struct vulkan_buffer *vk_buf)
{
VkImageSubresource img_sub_res = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.arrayLayer = 0,
.mipLevel = 0,
};
VkSubresourceLayout img_sub_layout;
vkGetImageSubresourceLayout(s->device, vk_buf->image, &img_sub_res, &img_sub_layout);
void *v;
VK_CHECK_RESULT(vkMapMemory(s->device, vk_buf->memory, 0, VK_WHOLE_SIZE, 0, &v));
const char *d = (const char *)v + img_sub_layout.offset;
unsigned char *p = (unsigned char *)info->data + info->offset;
uint32_t pack_stride = img_sub_layout.rowPitch;
spa_log_trace_fp(s->log, "Read pixels: %p to %p, stride: %d, width %d, height %d, offset %d, pack_stride %d", d, p, info->stride, info->size.width, info->size.height, info->offset, pack_stride);
if (pack_stride == info->stride) {
memcpy(p, d, info->stride * info->size.height);
} else {
for (uint32_t i = 0; i < info->size.height; i++) {
memcpy(p + i * info->stride, d + i * pack_stride, info->size.width * info->bytes_per_pixel);
}
}
vkUnmapMemory(s->device, vk_buf->memory);
return 0;
}
int vulkan_sync_foreign_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf)
{
VULKAN_INSTANCE_FUNCTION(vkImportSemaphoreFdKHR);
if (!s->implicit_sync_interop) {
struct pollfd pollfd = {
.fd = vk_buf->fd,
.events = POLLIN,
};
int timeout_ms = 1000;
int ret = poll(&pollfd, 1, timeout_ms);
if (ret < 0) {
spa_log_error(s->log, "Failed to wait for DMA-BUF fence");
return -1;
} else if (ret == 0) {
spa_log_error(s->log, "Timed out waiting for DMA-BUF fence");
return -1;
}
return 0;
}
int sync_file_fd = dmabuf_export_sync_file(s->log, vk_buf->fd, DMA_BUF_SYNC_READ);
if (sync_file_fd < 0) {
spa_log_error(s->log, "Failed to extract for DMA-BUF fence");
return -1;
}
if (vk_buf->foreign_semaphore == VK_NULL_HANDLE) {
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VK_CHECK_RESULT_WITH_CLEANUP(vkCreateSemaphore(s->device, &semaphore_info, NULL, &vk_buf->foreign_semaphore), close(sync_file_fd));
}
VkImportSemaphoreFdInfoKHR import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR,
.semaphore = vk_buf->foreign_semaphore,
.fd = sync_file_fd,
};
VK_CHECK_RESULT_WITH_CLEANUP(vkImportSemaphoreFdKHR(s->device, &import_info), close(sync_file_fd));
return 0;
}
bool vulkan_sync_export_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd)
{
if (!s->implicit_sync_interop)
return false;
return dmabuf_import_sync_file(s->log, vk_buf->fd, DMA_BUF_SYNC_WRITE, sync_file_fd);
}
int vulkan_fence_create(struct vulkan_base *s, VkFence *fence)
{
VkFenceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = 0,
};
VK_CHECK_RESULT(vkCreateFence(s->device, &createInfo, NULL, fence));
return 0;
}
int vulkan_commandPool_create(struct vulkan_base *s, VkCommandPool *commandPool)
{
const VkCommandPoolCreateInfo commandPoolCreateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = s->queueFamilyIndex,
};
VK_CHECK_RESULT(vkCreateCommandPool(s->device,
&commandPoolCreateInfo, NULL,
commandPool));
return 0;
}
int vulkan_commandBuffer_create(struct vulkan_base *s, VkCommandPool commandPool, VkCommandBuffer *commandBuffer)
{
const VkCommandBufferAllocateInfo commandBufferAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VK_CHECK_RESULT(vkAllocateCommandBuffers(s->device,
&commandBufferAllocateInfo,
commandBuffer));
return 0;
}
uint32_t vulkan_memoryType_find(struct vulkan_base *s,
uint32_t memoryTypeBits, VkMemoryPropertyFlags properties)
{
uint32_t i;
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(s->physicalDevice, &memoryProperties);
for (i = 0; i < memoryProperties.memoryTypeCount; i++) {
if ((memoryTypeBits & (1 << i)) &&
((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties))
return i;
}
return -1;
}
struct vulkan_format_info *vulkan_formatInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format)
{
for (uint32_t i = 0; i < fmtInfo->formatCount; i++) {
if (fmtInfo->infos[i].vk_format == format)
return &fmtInfo->infos[i];
}
return NULL;
}
struct vulkan_modifier_info *vulkan_modifierInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format, uint64_t mod)
{
struct vulkan_format_info *f_info = vulkan_formatInfo_find(fmtInfo, format);
if (!f_info)
return NULL;
for (uint32_t i = 0; i < f_info->modifierCount; i++) {
if (f_info->infos[i].props.drmFormatModifier == mod)
return &f_info->infos[i];
}
return NULL;
}
void vulkan_buffer_clear(struct vulkan_base *s, struct vulkan_buffer *buffer)
{
if (buffer->fd != -1)
close(buffer->fd);
vkFreeMemory(s->device, buffer->memory, NULL);
vkDestroyImage(s->device, buffer->image, NULL);
vkDestroyImageView(s->device, buffer->view, NULL);
}
static VkImageAspectFlagBits mem_plane_aspect(uint32_t i)
{
switch (i) {
case 0: return VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT;
case 1: return VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT;
case 2: return VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT;
case 3: return VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT;
default: abort(); // unreachable
}
}
int vulkan_staging_buffer_create(struct vulkan_base *s, uint32_t size, struct vulkan_staging_buffer *s_buf) {
VkBufferCreateInfo buf_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VK_CHECK_RESULT(vkCreateBuffer(s->device, &buf_info, NULL, &s_buf->buffer));
VkMemoryRequirements memoryRequirements;
vkGetBufferMemoryRequirements(s->device, s_buf->buffer, &memoryRequirements);
VkMemoryAllocateInfo mem_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = vulkan_memoryType_find(s,
memoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT),
};
VK_CHECK_RESULT(vkAllocateMemory(s->device, &mem_info, NULL, &s_buf->memory));
VK_CHECK_RESULT(vkBindBufferMemory(s->device, s_buf->buffer, s_buf->memory, 0));
return 0;
}
void vulkan_staging_buffer_destroy(struct vulkan_base *s, struct vulkan_staging_buffer *s_buf) {
if (s_buf->buffer == VK_NULL_HANDLE)
return;
vkFreeMemory(s->device, s_buf->memory, NULL);
vkDestroyBuffer(s->device, s_buf->buffer, NULL);
}
static int allocate_dmabuf(struct vulkan_base *s, VkFormat format, uint32_t modifierCount, uint64_t *modifiers, VkImageUsageFlags usage, struct spa_rectangle *size, struct vulkan_buffer *vk_buf)
{
VkImageDrmFormatModifierListCreateInfoEXT imageDrmFormatModifierListCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
.drmFormatModifierCount = modifierCount,
.pDrmFormatModifiers = modifiers,
};
VkExternalMemoryImageCreateInfo extMemoryImageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
extMemoryImageCreateInfo.pNext = &imageDrmFormatModifierListCreateInfo;
VkImageCreateInfo imageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent.width = size->width,
.extent.height = size->height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
imageCreateInfo.pNext = &extMemoryImageCreateInfo;
VK_CHECK_RESULT(vkCreateImage(s->device,
&imageCreateInfo, NULL, &vk_buf->image));
VkMemoryRequirements memoryRequirements = {0};
vkGetImageMemoryRequirements(s->device,
vk_buf->image, &memoryRequirements);
VkExportMemoryAllocateInfo exportAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
VkMemoryAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = vulkan_memoryType_find(s,
memoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
allocateInfo.pNext = &exportAllocateInfo;
VK_CHECK_RESULT(vkAllocateMemory(s->device,
&allocateInfo, NULL, &vk_buf->memory));
VK_CHECK_RESULT(vkBindImageMemory(s->device,
vk_buf->image, vk_buf->memory, 0));
return 0;
}
int vulkan_fixate_modifier(struct vulkan_base *s, struct dmabuf_fixation_info *info, uint64_t *modifier)
{
VULKAN_INSTANCE_FUNCTION(vkGetImageDrmFormatModifierPropertiesEXT);
struct vulkan_buffer vk_buf;
vk_buf.fd = -1;
vk_buf.view = VK_NULL_HANDLE;
VK_CHECK_RESULT(allocate_dmabuf(s, info->format, info->modifierCount, info->modifiers, info->usage, &info->size, &vk_buf));
VkImageDrmFormatModifierPropertiesEXT mod_prop = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT,
};
VK_CHECK_RESULT(vkGetImageDrmFormatModifierPropertiesEXT(s->device, vk_buf.image, &mod_prop));
*modifier = mod_prop.drmFormatModifier;
vulkan_buffer_clear(s, &vk_buf);
return 0;
}
int vulkan_create_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf)
{
VULKAN_INSTANCE_FUNCTION(vkGetMemoryFdKHR);
if (info->spa_buf->n_datas != 1)
return -1;
VK_CHECK_RESULT(allocate_dmabuf(s, info->format, 1, &info->modifier, info->usage, &info->size, vk_buf));
const VkMemoryGetFdInfoKHR getFdInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = vk_buf->memory,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
};
int fd = -1;
VK_CHECK_RESULT(vkGetMemoryFdKHR(s->device, &getFdInfo, &fd));
/*
const struct vulkan_modifier_info *modInfo = vulkan_modifierInfo_find(s, info->format, info->modifier);
if (info->spa_buf->n_datas != modInfo->props.drmFormatModifierPlaneCount)
return -1;
*/
VkMemoryRequirements memoryRequirements = {0};
vkGetImageMemoryRequirements(s->device,
vk_buf->image, &memoryRequirements);
spa_log_info(s->log, "export DMABUF %zd", memoryRequirements.size);
for (uint32_t i = 0; i < info->spa_buf->n_datas; i++) {
VkImageSubresource subresource = {
.aspectMask = mem_plane_aspect(i),
};
VkSubresourceLayout subresLayout = {0};
vkGetImageSubresourceLayout(s->device, vk_buf->image, &subresource, &subresLayout);
info->spa_buf->datas[i].type = SPA_DATA_DmaBuf;
info->spa_buf->datas[i].fd = fd;
info->spa_buf->datas[i].flags = SPA_DATA_FLAG_READABLE;
info->spa_buf->datas[i].mapoffset = 0;
info->spa_buf->datas[i].chunk->offset = subresLayout.offset;
info->spa_buf->datas[i].chunk->stride = subresLayout.rowPitch;
info->spa_buf->datas[i].chunk->size = subresLayout.size;
info->spa_buf->datas[i].maxsize = memoryRequirements.size;
}
vk_buf->fd = fd;
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = vk_buf->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = info->format,
.components.r = VK_COMPONENT_SWIZZLE_R,
.components.g = VK_COMPONENT_SWIZZLE_G,
.components.b = VK_COMPONENT_SWIZZLE_B,
.components.a = VK_COMPONENT_SWIZZLE_A,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
VK_CHECK_RESULT(vkCreateImageView(s->device,
&viewInfo, NULL, &vk_buf->view));
return 0;
}
int vulkan_import_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf)
{
if (info->spa_buf->n_datas == 0 || info->spa_buf->n_datas > DMABUF_MAX_PLANES)
return -1;
/*
struct vulkan_modifier_info *modProps = vulkan_modifierInfo_find(s, info->format, info->modifier);
if (!modProps)
return -1;
*/
uint32_t planeCount = info->spa_buf->n_datas;
/*
if (planeCount != modProps->props.drmFormatModifierPlaneCount)
return -1;
if (info->size.width > modProps->max_extent.width || info->size.height > modProps->max_extent.height)
return -1;
*/
VkSubresourceLayout planeLayouts[DMABUF_MAX_PLANES] = {0};
for (uint32_t i = 0; i < planeCount; i++) {
planeLayouts[i].offset = info->spa_buf->datas[i].chunk->offset;
planeLayouts[i].rowPitch = info->spa_buf->datas[i].chunk->stride;
planeLayouts[i].size = 0;
}
VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT,
.drmFormatModifierPlaneCount = planeCount,
.drmFormatModifier = info->modifier,
.pPlaneLayouts = planeLayouts,
};
VkExternalMemoryImageCreateInfo extInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
.pNext = &modInfo,
};
VkImageCreateInfo imageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = info->format,
.extent.width = info->size.width,
.extent.height = info->size.height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
.usage = info->usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.pNext = &extInfo,
};
VK_CHECK_RESULT(vkCreateImage(s->device,
&imageCreateInfo, NULL, &vk_buf->image));
VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements(s->device,
vk_buf->image, &memoryRequirements);
vk_buf->fd = fcntl(info->spa_buf->datas[0].fd, F_DUPFD_CLOEXEC, 0);
VkImportMemoryFdInfoKHR importInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
.fd = fcntl(info->spa_buf->datas[0].fd, F_DUPFD_CLOEXEC, 0),
};
VkMemoryAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = vulkan_memoryType_find(s,
memoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
allocateInfo.pNext = &importInfo;
spa_log_info(s->log, "import DMABUF");
VK_CHECK_RESULT(vkAllocateMemory(s->device,
&allocateInfo, NULL, &vk_buf->memory));
VK_CHECK_RESULT(vkBindImageMemory(s->device,
vk_buf->image, vk_buf->memory, 0));
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = vk_buf->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = info->format,
.components.r = VK_COMPONENT_SWIZZLE_R,
.components.g = VK_COMPONENT_SWIZZLE_G,
.components.b = VK_COMPONENT_SWIZZLE_B,
.components.a = VK_COMPONENT_SWIZZLE_A,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
VK_CHECK_RESULT(vkCreateImageView(s->device,
&viewInfo, NULL, &vk_buf->view));
return 0;
}
int vulkan_import_memptr(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf)
{
VkImageCreateInfo imageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = info->format,
.extent.width = info->size.width,
.extent.height = info->size.height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = info->usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VK_CHECK_RESULT(vkCreateImage(s->device,
&imageCreateInfo, NULL, &vk_buf->image));
VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements(s->device,
vk_buf->image, &memoryRequirements);
VkMemoryAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = vulkan_memoryType_find(s,
memoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT),
};
vk_buf->fd = -1;
spa_log_info(s->log, "import MemPtr");
VK_CHECK_RESULT(vkAllocateMemory(s->device,
&allocateInfo, NULL, &vk_buf->memory));
VK_CHECK_RESULT(vkBindImageMemory(s->device,
vk_buf->image, vk_buf->memory, 0));
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = vk_buf->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = info->format,
.components.r = VK_COMPONENT_SWIZZLE_R,
.components.g = VK_COMPONENT_SWIZZLE_G,
.components.b = VK_COMPONENT_SWIZZLE_B,
.components.a = VK_COMPONENT_SWIZZLE_A,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
VK_CHECK_RESULT(vkCreateImageView(s->device,
&viewInfo, NULL, &vk_buf->view));
return 0;
}
uint32_t vulkan_vkformat_to_id(VkFormat format)
{
SPA_FOR_EACH_ELEMENT_VAR(vk_video_format_convs, f) {
if (f->format == format)
return f->id;
}
return SPA_VIDEO_FORMAT_UNKNOWN;
}
VkFormat vulkan_id_to_vkformat(uint32_t id)
{
SPA_FOR_EACH_ELEMENT_VAR(vk_video_format_convs, f) {
if (f->id == id)
return f->format;
}
return VK_FORMAT_UNDEFINED;
}
int vulkan_vkresult_to_errno(VkResult result)
{
return vkresult_to_errno(result);
}
int vulkan_wait_fence(struct vulkan_base *s, VkFence fence)
{
VK_CHECK_RESULT(vkWaitForFences(s->device, 1, &fence, VK_TRUE, UINT64_MAX));
return 0;
}
int vulkan_wait_idle(struct vulkan_base *s)
{
VK_CHECK_RESULT(vkDeviceWaitIdle(s->device));
return 0;
}
int vulkan_format_infos_init(struct vulkan_base *s, uint32_t formatCount, uint32_t *formats,
struct vulkan_format_infos *info)
{
if (info->infos)
return 0;
info->infos = calloc(formatCount, sizeof(struct vulkan_format_info));
if (!info->infos)
return -ENOMEM;
uint32_t i;
for (i = 0; i < formatCount; i++) {
VkFormat format = vulkan_id_to_vkformat(formats[i]);
if (format == VK_FORMAT_UNDEFINED)
continue;
struct vulkan_format_info *f_info = &info->infos[info->formatCount++];
f_info->spa_format = formats[i];
f_info->vk_format = format;
spa_log_info(s->log, "Adding format %d (spa_format %d)", format, formats[i]);
VkDrmFormatModifierPropertiesListEXT modPropsList = {
.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
};
VkFormatProperties2 fmtProps = {
.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
.pNext = &modPropsList,
};
vkGetPhysicalDeviceFormatProperties2(s->physicalDevice, format, &fmtProps);
if (modPropsList.drmFormatModifierCount == 0) {
spa_log_info(s->log, "Format has no modifiers");
continue;
}
modPropsList.pDrmFormatModifierProperties = calloc(modPropsList.drmFormatModifierCount,
sizeof(modPropsList.pDrmFormatModifierProperties[0]));
if (!modPropsList.pDrmFormatModifierProperties) {
spa_log_info(s->log, "Failed to allocate DrmFormatModifierProperties");
continue;
}
vkGetPhysicalDeviceFormatProperties2(s->physicalDevice, format, &fmtProps);
f_info->infos = calloc(modPropsList.drmFormatModifierCount, sizeof(f_info->infos[0]));
if (!f_info->infos) {
spa_log_info(s->log, "Failed to allocate modifier infos");
free(modPropsList.pDrmFormatModifierProperties);
continue;
}
spa_log_info(s->log, "Found %d modifiers", modPropsList.drmFormatModifierCount);
for (uint32_t j = 0; j < modPropsList.drmFormatModifierCount; j++) {
VkDrmFormatModifierPropertiesEXT props = modPropsList.pDrmFormatModifierProperties[j];
if (!(props.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT))
continue;
if (props.drmFormatModifierPlaneCount > DMABUF_MAX_PLANES)
continue;
VkPhysicalDeviceImageDrmFormatModifierInfoEXT modInfo = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
.drmFormatModifier = props.drmFormatModifier,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkPhysicalDeviceExternalImageFormatInfo extImgFmtInfo = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
.pNext = &modInfo,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
VkPhysicalDeviceImageFormatInfo2 imgFmtInfo = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
.pNext = &extImgFmtInfo,
.type = VK_IMAGE_TYPE_2D,
.format = format,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
};
VkExternalImageFormatProperties extImgFmtProps = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES,
};
VkImageFormatProperties2 imgFmtProps = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
.pNext = &extImgFmtProps,
};
VK_CHECK_RESULT_LOOP(vkGetPhysicalDeviceImageFormatProperties2(s->physicalDevice, &imgFmtInfo, &imgFmtProps))
VkExternalMemoryFeatureFlags extMemFeatures =
extImgFmtProps.externalMemoryProperties.externalMemoryFeatures;
if (!(extMemFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT)) {
continue;
}
VkExtent3D max_extent = imgFmtProps.imageFormatProperties.maxExtent;
f_info->infos[f_info->modifierCount++] = (struct vulkan_modifier_info){
.props = props,
.max_extent = { .width = max_extent.width, .height = max_extent.height },
};
spa_log_info(s->log, "Adding modifier %"PRIu64, props.drmFormatModifier);
}
free(modPropsList.pDrmFormatModifierProperties);
}
for (i = 0; i < info->formatCount; i++) {
if (info->infos[i].modifierCount > 0)
info->formatsWithModifiersCount++;
}
return 0;
}
void vulkan_format_infos_deinit(struct vulkan_format_infos *info)
{
for (uint32_t i = 0; i < info->formatCount; i++) {
free(info->infos[i].infos);
}
free(info->infos);
}
int vulkan_base_init(struct vulkan_base *s, struct vulkan_base_info *info)
{
if (!s->initialized) {
CHECK(createInstance(s));
CHECK(findPhysicalDevice(s));
CHECK(createDevice(s, info));
s->implicit_sync_interop = dmabuf_check_sync_file_import_export(s->log);
s->initialized = true;
}
return 0;
}
void vulkan_base_deinit(struct vulkan_base *s)
{
if (s->initialized) {
vkDestroyDevice(s->device, NULL);
vkDestroyInstance(s->instance, NULL);
s->initialized = false;
}
}

View file

@ -0,0 +1,120 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#pragma once
#include <vulkan/vulkan.h>
#include <spa/buffer/buffer.h>
#include <spa/node/node.h>
#include "vulkan-types.h"
#define VK_CHECK_RESULT(f) \
{ \
VkResult _result = (f); \
int _r = -vulkan_vkresult_to_errno(_result); \
if (_result != VK_SUCCESS) { \
spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \
return _r; \
} \
}
#define VK_CHECK_RESULT_WITH_CLEANUP(f, c) \
{ \
VkResult _result = (f); \
int _r = -vkresult_to_errno(_result); \
if (_result != VK_SUCCESS) { \
spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \
(c); \
return _r; \
} \
}
#define VK_CHECK_RESULT_LOOP(f) \
{ \
VkResult _result = (f); \
int _r = -vkresult_to_errno(_result); \
if (_result != VK_SUCCESS) { \
spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \
continue; \
} \
}
#define CHECK(f) \
{ \
int _res = (f); \
if (_res < 0) \
return _res; \
}
struct vulkan_write_pixels_info {
struct spa_rectangle size;
uint32_t offset;
uint32_t stride;
uint32_t bytes_per_pixel;
VkBufferImageCopy *copies;
void *data;
};
struct vulkan_read_pixels_info {
struct spa_rectangle size;
void *data;
uint32_t offset;
uint32_t stride;
uint32_t bytes_per_pixel;
};
struct dmabuf_fixation_info {
VkFormat format;
uint64_t modifierCount;
uint64_t *modifiers;
struct spa_rectangle size;
VkImageUsageFlags usage;
};
struct external_buffer_info {
VkFormat format;
uint64_t modifier;
struct spa_rectangle size;
VkImageUsageFlags usage;
struct spa_buffer *spa_buf;
};
int vulkan_write_pixels(struct vulkan_base *s, struct vulkan_write_pixels_info *info, struct vulkan_staging_buffer *vk_sbuf);
int vulkan_read_pixels(struct vulkan_base *s, struct vulkan_read_pixels_info *info, struct vulkan_buffer *vk_buf);
int vulkan_sync_foreign_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf);
bool vulkan_sync_export_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd);
int vulkan_staging_buffer_create(struct vulkan_base *s, uint32_t size, struct vulkan_staging_buffer *s_buf);
void vulkan_staging_buffer_destroy(struct vulkan_base *s, struct vulkan_staging_buffer *s_buf);
int vulkan_fixate_modifier(struct vulkan_base *s, struct dmabuf_fixation_info *info, uint64_t *modifier);
int vulkan_create_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf);
int vulkan_import_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf);
int vulkan_import_memptr(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf);
int vulkan_fence_create(struct vulkan_base *s, VkFence *fence);
int vulkan_commandPool_create(struct vulkan_base *s, VkCommandPool *commandPool);
int vulkan_commandBuffer_create(struct vulkan_base *s, VkCommandPool commandPool, VkCommandBuffer *commandBuffer);
uint32_t vulkan_memoryType_find(struct vulkan_base *s,
uint32_t memoryTypeBits, VkMemoryPropertyFlags properties);
struct vulkan_format_info *vulkan_formatInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format);
struct vulkan_modifier_info *vulkan_modifierInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format, uint64_t modifier);
void vulkan_buffer_clear(struct vulkan_base *s, struct vulkan_buffer *buffer);
uint32_t vulkan_vkformat_to_id(VkFormat vkFormat);
VkFormat vulkan_id_to_vkformat(uint32_t id);
int vulkan_vkresult_to_errno(VkResult result);
int vulkan_wait_fence(struct vulkan_base *s, VkFence fence);
int vulkan_wait_idle(struct vulkan_base *s);
int vulkan_format_infos_init(struct vulkan_base *s, uint32_t formatCount, uint32_t *formats,
struct vulkan_format_infos *info);
void vulkan_format_infos_deinit(struct vulkan_format_infos *info);
int vulkan_base_init(struct vulkan_base *s, struct vulkan_base_info *info);
void vulkan_base_deinit(struct vulkan_base *s);