diff options
| author | dec05eba <dec05eba@protonmail.com> | 2026-04-17 00:56:22 +0200 |
|---|---|---|
| committer | dec05eba <dec05eba@protonmail.com> | 2026-04-17 00:56:22 +0200 |
| commit | 286158a838e94420403257cf8c6e9052ff02b9f1 (patch) | |
| tree | 5ed24ffebd5524327bd273a9a8cba6d3cfb4d6fc /src/encoder | |
| parent | 8b953e95d850522fd5d17b7d65df48ab217c9186 (diff) | |
Implement vulkan video capture (h264, hevc and av1)
Diffstat (limited to 'src/encoder')
| -rw-r--r-- | src/encoder/video/vulkan.c | 822 |
1 files changed, 684 insertions, 138 deletions
diff --git a/src/encoder/video/vulkan.c b/src/encoder/video/vulkan.c index 5fa3c3d..cc8c6f6 100644 --- a/src/encoder/video/vulkan.c +++ b/src/encoder/video/vulkan.c @@ -4,29 +4,120 @@ #include <libavcodec/avcodec.h> #define VK_NO_PROTOTYPES -//#include <libavutil/hwcontext_vulkan.h> +#include <libavutil/hwcontext_vulkan.h> -//#include <vulkan/vulkan_core.h> +#include <vulkan/vulkan_core.h> -#define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 -#define GL_TEXTURE_TILING_EXT 0x9580 -#define GL_OPTIMAL_TILING_EXT 0x9584 -#define GL_LINEAR_TILING_EXT 0x9585 +#define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 +#define GL_TEXTURE_TILING_EXT 0x9580 +#define GL_OPTIMAL_TILING_EXT 0x9584 +#define GL_LINEAR_TILING_EXT 0x9585 +#define GL_DEDICATED_MEMORY_OBJECT_EXT 0x9581 +#define GL_LAYOUT_GENERAL_EXT 0x958D + +typedef struct { + PFN_vkCreateImage vkCreateImage; + PFN_vkDestroyImage vkDestroyImage; + PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; + PFN_vkAllocateMemory vkAllocateMemory; + PFN_vkFreeMemory vkFreeMemory; + PFN_vkBindImageMemory vkBindImageMemory; + PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + PFN_vkCreateCommandPool vkCreateCommandPool; + PFN_vkDestroyCommandPool vkDestroyCommandPool; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; + PFN_vkBeginCommandBuffer vkBeginCommandBuffer; + PFN_vkEndCommandBuffer vkEndCommandBuffer; + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; + PFN_vkCmdCopyImage vkCmdCopyImage; + PFN_vkCreateFence vkCreateFence; + PFN_vkDestroyFence vkDestroyFence; + PFN_vkResetFences vkResetFences; + PFN_vkWaitForFences vkWaitForFences; + PFN_vkGetDeviceQueue vkGetDeviceQueue; + PFN_vkQueueSubmit vkQueueSubmit; + PFN_vkResetCommandBuffer vkResetCommandBuffer; + PFN_vkCreateSemaphore vkCreateSemaphore; + PFN_vkDestroySemaphore vkDestroySemaphore; + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR; +} gsr_vk_funcs; typedef struct { gsr_video_encoder_vulkan_params params; unsigned int target_textures[2]; vec2i texture_sizes[2]; AVBufferRef *device_ctx; + + gsr_vk_funcs vk; + VkDevice vk_device; + VkQueue vk_queue; + + /* Exportable images that GL renders into */ + VkImage export_images[2]; + VkDeviceMemory export_memory[2]; + VkDeviceSize export_memory_size[2]; + unsigned int gl_memory_objects[2]; + + /* Vulkan command infrastructure for copying to encoder frame */ + VkCommandPool command_pool; + VkCommandBuffer command_buffer; + VkFence fence; + bool fence_submitted; /* true if the fence was submitted and not yet waited on */ + + /* GL→Vulkan semaphore (binary, exported to GL via GL_EXT_semaphore_fd) */ + VkSemaphore gl_ready_semaphore; + unsigned int gl_semaphore; } gsr_video_encoder_vulkan; +static bool gsr_vk_funcs_load(gsr_vk_funcs *vk, PFN_vkGetInstanceProcAddr get_inst_proc, VkInstance inst, VkDevice dev) { + PFN_vkGetDeviceProcAddr get_dev_proc = (PFN_vkGetDeviceProcAddr)get_inst_proc(inst, "vkGetDeviceProcAddr"); + if(!get_dev_proc) { + fprintf(stderr, "gsr error: gsr_vk_funcs_load: failed to load vkGetDeviceProcAddr\n"); + return false; + } + +#define LOAD_INST(name) vk->name = (PFN_##name)get_inst_proc(inst, #name); if(!vk->name) { fprintf(stderr, "gsr error: gsr_vk_funcs_load: failed to load " #name "\n"); return false; } +#define LOAD_DEV(name) vk->name = (PFN_##name)get_dev_proc(dev, #name); if(!vk->name) { fprintf(stderr, "gsr error: gsr_vk_funcs_load: failed to load " #name "\n"); return false; } + + LOAD_INST(vkGetPhysicalDeviceMemoryProperties) + LOAD_DEV(vkCreateImage) + LOAD_DEV(vkDestroyImage) + LOAD_DEV(vkGetImageMemoryRequirements) + LOAD_DEV(vkAllocateMemory) + LOAD_DEV(vkFreeMemory) + LOAD_DEV(vkBindImageMemory) + LOAD_DEV(vkGetMemoryFdKHR) + LOAD_DEV(vkCreateCommandPool) + LOAD_DEV(vkDestroyCommandPool) + LOAD_DEV(vkAllocateCommandBuffers) + LOAD_DEV(vkBeginCommandBuffer) + LOAD_DEV(vkEndCommandBuffer) + LOAD_DEV(vkCmdPipelineBarrier) + LOAD_DEV(vkCmdCopyImage) + LOAD_DEV(vkCreateFence) + LOAD_DEV(vkDestroyFence) + LOAD_DEV(vkResetFences) + LOAD_DEV(vkWaitForFences) + LOAD_DEV(vkGetDeviceQueue) + LOAD_DEV(vkQueueSubmit) + LOAD_DEV(vkResetCommandBuffer) + LOAD_DEV(vkCreateSemaphore) + LOAD_DEV(vkDestroySemaphore) + LOAD_DEV(vkGetSemaphoreFdKHR) + +#undef LOAD_INST +#undef LOAD_DEV + return true; +} + static bool gsr_video_encoder_vulkan_setup_context(gsr_video_encoder_vulkan *self, AVCodecContext *video_codec_context) { AVDictionary *options = NULL; - //av_dict_set(&options, "linear_images", "1", 0); - //av_dict_set(&options, "disable_multiplane", "1", 0); -#if 0 - // TODO: Use correct device - if(av_hwdevice_ctx_create(&self->device_ctx, AV_HWDEVICE_TYPE_VULKAN, NULL, options, 0) < 0) { + + char device_index_str[32]; + snprintf(device_index_str, sizeof(device_index_str), "%d", self->params.egl->vulkan_device_index); + + if(av_hwdevice_ctx_create(&self->device_ctx, AV_HWDEVICE_TYPE_VULKAN, device_index_str, options, 0) < 0) { fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_context: failed to create hardware device context\n"); return false; } @@ -45,23 +136,19 @@ static bool gsr_video_encoder_vulkan_setup_context(gsr_video_encoder_vulkan *sel hw_frame_context->format = video_codec_context->pix_fmt; hw_frame_context->device_ctx = (AVHWDeviceContext*)self->device_ctx->data; - //AVVulkanFramesContext *vk_frame_ctx = (AVVulkanFramesContext*)hw_frame_context->hwctx; - //hw_frame_context->initial_pool_size = 20; - if (av_hwframe_ctx_init(frame_context) < 0) { fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_context: failed to initialize hardware frame context " "(note: ffmpeg version needs to be > 4.0)\n"); av_buffer_unref(&self->device_ctx); - //av_buffer_unref(&frame_context); return false; } video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context); av_buffer_unref(&frame_context); -#endif + return true; } -#if 0 + static AVVulkanDeviceContext* video_codec_context_get_vulkan_data(AVCodecContext *video_codec_context) { AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx; if(!hw_frames_ctx) @@ -75,161 +162,299 @@ static AVVulkanDeviceContext* video_codec_context_get_vulkan_data(AVCodecContext return (AVVulkanDeviceContext*)device_context->hwctx; } +static int get_graphics_queue_family(AVVulkanDeviceContext *vv) { + for(int i = 0; i < vv->nb_qf; i++) { + if(vv->qf[i].flags & VK_QUEUE_GRAPHICS_BIT) + return vv->qf[i].idx; + } + /* Fall back to any queue that supports transfer */ + for(int i = 0; i < vv->nb_qf; i++) { + if(vv->qf[i].flags & VK_QUEUE_TRANSFER_BIT) + return vv->qf[i].idx; + } + return -1; +} + static uint32_t get_memory_type_idx(VkPhysicalDevice pdev, const VkMemoryRequirements *mem_reqs, VkMemoryPropertyFlagBits prop_flags, PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties) { VkPhysicalDeviceMemoryProperties pdev_mem_props; - uint32_t i; - vkGetPhysicalDeviceMemoryProperties(pdev, &pdev_mem_props); - for (i = 0; i < pdev_mem_props.memoryTypeCount; i++) { - const VkMemoryType *type = &pdev_mem_props.memoryTypes[i]; - - if ((mem_reqs->memoryTypeBits & (1 << i)) && - (type->propertyFlags & prop_flags) == prop_flags) { + for(uint32_t i = 0; i < pdev_mem_props.memoryTypeCount; i++) { + if((mem_reqs->memoryTypeBits & (1 << i)) && + (pdev_mem_props.memoryTypes[i].propertyFlags & prop_flags) == prop_flags) { return i; - break; } } return UINT32_MAX; } -#endif -static bool gsr_video_encoder_vulkan_setup_textures(gsr_video_encoder_vulkan *self, AVCodecContext *video_codec_context, AVFrame *frame) { - const int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, frame, 0); - if(res < 0) { - fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: av_hwframe_get_buffer failed: %d\n", res); + +static bool create_exportable_image( + const gsr_vk_funcs *vk, + VkDevice dev, + VkPhysicalDevice phys_dev, + int width, int height, + VkFormat format, + VkImage *out_image, + VkDeviceMemory *out_memory, + VkDeviceSize *out_size) +{ + VkExternalMemoryImageCreateInfo ext_img_info = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &ext_img_info, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { (uint32_t)width, (uint32_t)height, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_STORAGE_BIT, + .flags = VK_IMAGE_CREATE_ALIAS_BIT | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + if(vk->vkCreateImage(dev, &img_info, NULL, out_image) != VK_SUCCESS) { + fprintf(stderr, "gsr error: create_exportable_image: vkCreateImage failed\n"); return false; } - while(self->params.egl->glGetError()) {} -#if 0 - AVVkFrame *target_surface_id = (AVVkFrame*)frame->data[0]; - AVVulkanDeviceContext* vv = video_codec_context_get_vulkan_data(video_codec_context); - const size_t luma_size = frame->width * frame->height; - if(vv) { - PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vv->get_proc_addr(vv->inst, "vkGetImageMemoryRequirements"); - PFN_vkAllocateMemory vkAllocateMemory = (PFN_vkAllocateMemory)vv->get_proc_addr(vv->inst, "vkAllocateMemory"); - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vv->get_proc_addr(vv->inst, "vkGetPhysicalDeviceMemoryProperties"); - PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR = (PFN_vkGetMemoryFdKHR)vv->get_proc_addr(vv->inst, "vkGetMemoryFdKHR"); - - VkMemoryRequirements mem_reqs = {0}; - vkGetImageMemoryRequirements(vv->act_dev, target_surface_id->img[0], &mem_reqs); - - fprintf(stderr, "size: %lu, alignment: %lu, memory bits: 0x%08x\n", mem_reqs.size, mem_reqs.alignment, mem_reqs.memoryTypeBits); - VkDeviceMemory mem; - { - VkExportMemoryAllocateInfo exp_mem_info; - VkMemoryAllocateInfo mem_alloc_info; - VkMemoryDedicatedAllocateInfoKHR ded_info; - - memset(&exp_mem_info, 0, sizeof(exp_mem_info)); - exp_mem_info.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO; - exp_mem_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; - - memset(&ded_info, 0, sizeof(ded_info)); - ded_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; - ded_info.image = target_surface_id->img[0]; - - exp_mem_info.pNext = &ded_info; - - memset(&mem_alloc_info, 0, sizeof(mem_alloc_info)); - mem_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - mem_alloc_info.pNext = &exp_mem_info; - mem_alloc_info.allocationSize = target_surface_id->size[0]; - mem_alloc_info.memoryTypeIndex = get_memory_type_idx(vv->phys_dev, &mem_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vkGetPhysicalDeviceMemoryProperties); - - if (mem_alloc_info.memoryTypeIndex == UINT32_MAX) { - fprintf(stderr, "No suitable memory type index found.\n"); - return VK_NULL_HANDLE; - } + VkMemoryRequirements mem_reqs; + vk->vkGetImageMemoryRequirements(dev, *out_image, &mem_reqs); - if (vkAllocateMemory(vv->act_dev, &mem_alloc_info, 0, &mem) != - VK_SUCCESS) - return VK_NULL_HANDLE; - - fprintf(stderr, "memory: %p\n", (void*)mem); + uint32_t mem_type_idx = get_memory_type_idx(phys_dev, &mem_reqs, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vk->vkGetPhysicalDeviceMemoryProperties); + if(mem_type_idx == UINT32_MAX) { + fprintf(stderr, "gsr error: create_exportable_image: no suitable memory type\n"); + vk->vkDestroyImage(dev, *out_image, NULL); + *out_image = VK_NULL_HANDLE; + return false; + } - } + VkMemoryDedicatedAllocateInfo ded_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .image = *out_image, + }; + VkExportMemoryAllocateInfo exp_mem_info = { + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = &ded_info, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + VkMemoryAllocateInfo mem_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &exp_mem_info, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = mem_type_idx, + }; - fprintf(stderr, "target surface id: %p, %zu, %zu\n", (void*)target_surface_id->mem[0], target_surface_id->offset[0], target_surface_id->offset[1]); - fprintf(stderr, "vkGetMemoryFdKHR: %p\n", (void*)vkGetMemoryFdKHR); - - int fd = 0; - VkMemoryGetFdInfoKHR fd_info; - memset(&fd_info, 0, sizeof(fd_info)); - fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; - fd_info.memory = target_surface_id->mem[0]; - fd_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; - if(vkGetMemoryFdKHR(vv->act_dev, &fd_info, &fd) != VK_SUCCESS) { - fprintf(stderr, "failed!\n"); - } else { - fprintf(stderr, "fd: %d\n", fd); - } + if(vk->vkAllocateMemory(dev, &mem_alloc_info, NULL, out_memory) != VK_SUCCESS) { + fprintf(stderr, "gsr error: create_exportable_image: vkAllocateMemory failed\n"); + vk->vkDestroyImage(dev, *out_image, NULL); + *out_image = VK_NULL_HANDLE; + return false; + } - fprintf(stderr, "glImportMemoryFdEXT: %p, size: %zu\n", (void*)self->params.egl->glImportMemoryFdEXT, target_surface_id->size[0]); - const int tiling = target_surface_id->tiling == VK_IMAGE_TILING_LINEAR ? GL_LINEAR_TILING_EXT : GL_OPTIMAL_TILING_EXT; + if(vk->vkBindImageMemory(dev, *out_image, *out_memory, 0) != VK_SUCCESS) { + fprintf(stderr, "gsr error: create_exportable_image: vkBindImageMemory failed\n"); + vk->vkFreeMemory(dev, *out_memory, NULL); + vk->vkDestroyImage(dev, *out_image, NULL); + *out_memory = VK_NULL_HANDLE; + *out_image = VK_NULL_HANDLE; + return false; + } - if(tiling != GL_OPTIMAL_TILING_EXT) { - fprintf(stderr, "tiling %d is not supported, only GL_OPTIMAL_TILING_EXT (%d) is supported\n", tiling, GL_OPTIMAL_TILING_EXT); - } + *out_size = mem_reqs.size; + return true; +} +static bool gsr_video_encoder_vulkan_setup_textures(gsr_video_encoder_vulkan *self, AVCodecContext *video_codec_context, AVFrame *frame) { + const int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, frame, 0); + if(res < 0) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: av_hwframe_get_buffer failed: %d\n", res); + return false; + } - unsigned int gl_memory_obj = 0; - self->params.egl->glCreateMemoryObjectsEXT(1, &gl_memory_obj); + while(self->params.egl->glGetError()) {} - //const int dedicated = GL_TRUE; - //self->params.egl->glMemoryObjectParameterivEXT(gl_memory_obj, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated); + AVVulkanDeviceContext *vv = video_codec_context_get_vulkan_data(video_codec_context); + if(!vv) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: failed to get vulkan device context\n"); + return false; + } - self->params.egl->glImportMemoryFdEXT(gl_memory_obj, target_surface_id->size[0], GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); - if(!self->params.egl->glIsMemoryObjectEXT(gl_memory_obj)) - fprintf(stderr, "failed to create object!\n"); + if(!gsr_vk_funcs_load(&self->vk, vv->get_proc_addr, vv->inst, vv->act_dev)) + return false; - fprintf(stderr, "gl memory obj: %u, error: %d\n", gl_memory_obj, self->params.egl->glGetError()); + self->vk_device = vv->act_dev; - // fprintf(stderr, "0 gl error: %d\n", self->params.egl->glGetError()); - // unsigned int vertex_buffer = 0; - // self->params.egl->glGenBuffers(1, &vertex_buffer); - // self->params.egl->glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); - // self->params.egl->glBufferStorageMemEXT(GL_ARRAY_BUFFER, target_surface_id->size[0], gl_memory_obj, target_surface_id->offset[0]); - // fprintf(stderr, "1 gl error: %d\n", self->params.egl->glGetError()); + const bool is_p010 = self->params.color_depth == GSR_COLOR_DEPTH_10_BITS; + const VkFormat fmt_y = is_p010 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R8_UNORM; + const VkFormat fmt_uv = is_p010 ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R8G8_UNORM; + const unsigned int gl_fmt_y = is_p010 ? GL_R16 : GL_R8; + const unsigned int gl_fmt_uv = is_p010 ? GL_RG16 : GL_RG8; - // fprintf(stderr, "0 gl error: %d\n", self->params.egl->glGetError()); - // unsigned int buffer = 0; - // self->params.egl->glCreateBuffers(1, &buffer); - // self->params.egl->glNamedBufferStorageMemEXT(buffer, target_surface_id->size[0], gl_memory_obj, target_surface_id->offset[0]); - // fprintf(stderr, "1 gl error: %d\n", self->params.egl->glGetError()); + if(!create_exportable_image(&self->vk, vv->act_dev, vv->phys_dev, + frame->width, frame->height, fmt_y, + &self->export_images[0], &self->export_memory[0], &self->export_memory_size[0])) + return false; - self->params.egl->glGenTextures(1, &self->target_textures[0]); - self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[0]); + if(!create_exportable_image(&self->vk, vv->act_dev, vv->phys_dev, + frame->width / 2, frame->height / 2, fmt_uv, + &self->export_images[1], &self->export_memory[1], &self->export_memory_size[1])) + return false; - fprintf(stderr, "1 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, tiling); + /* Export Vulkan memory as FDs and import into GL */ + for(int i = 0; i < 2; i++) { + VkMemoryGetFdInfoKHR fd_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = self->export_memory[i], + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + int fd = -1; + if(self->vk.vkGetMemoryFdKHR(vv->act_dev, &fd_info, &fd) != VK_SUCCESS) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: vkGetMemoryFdKHR failed for plane %d\n", i); + return false; + } - fprintf(stderr, "tiling: %d\n", tiling); + self->params.egl->glCreateMemoryObjectsEXT(1, &self->gl_memory_objects[i]); + const int dedicated = 1; + self->params.egl->glMemoryObjectParameterivEXT(self->gl_memory_objects[i], + GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated); + self->params.egl->glImportMemoryFdEXT(self->gl_memory_objects[i], self->export_memory_size[i], + GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); + if(!self->params.egl->glIsMemoryObjectEXT(self->gl_memory_objects[i])) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: failed to import memory FD for plane %d\n", i); + return false; + } + } - fprintf(stderr, "2 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_R8, frame->width, frame->height, gl_memory_obj, target_surface_id->offset[0]); + /* Create GL textures backed by the exportable images */ + self->params.egl->glGenTextures(2, self->target_textures); + + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[0]); + self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT); + self->params.egl->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, gl_fmt_y, + frame->width, frame->height, + self->gl_memory_objects[0], 0); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + + self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[1]); + self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT); + self->params.egl->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, gl_fmt_uv, + frame->width / 2, frame->height / 2, + self->gl_memory_objects[1], 0); + self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + + self->texture_sizes[0] = (vec2i){ frame->width, frame->height }; + self->texture_sizes[1] = (vec2i){ frame->width / 2, frame->height / 2 }; + + /* Set up Vulkan command infrastructure */ + VkCommandPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = (uint32_t)get_graphics_queue_family(vv), + }; + if(self->vk.vkCreateCommandPool(vv->act_dev, &pool_info, NULL, &self->command_pool) != VK_SUCCESS) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: vkCreateCommandPool failed\n"); + return false; + } - fprintf(stderr, "3 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + VkCommandBufferAllocateInfo cb_alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = self->command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + if(self->vk.vkAllocateCommandBuffers(vv->act_dev, &cb_alloc_info, &self->command_buffer) != VK_SUCCESS) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: vkAllocateCommandBuffers failed\n"); + return false; + } - self->params.egl->glGenTextures(1, &self->target_textures[1]); - self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[1]); + VkFenceCreateInfo fence_info = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + }; + if(self->vk.vkCreateFence(vv->act_dev, &fence_info, NULL, &self->fence) != VK_SUCCESS) { + fprintf(stderr, "gsr error: gsr_video_encoder_vulkan_setup_textures: vkCreateFence failed\n"); + return false; + } - fprintf(stderr, "1 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, tiling); + self->vk.vkGetDeviceQueue(vv->act_dev, (uint32_t)get_graphics_queue_family(vv), 0, &self->vk_queue); - fprintf(stderr, "tiling: %d\n", tiling); + /* Transition export images UNDEFINED → GENERAL so GL can use them */ + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + self->vk.vkBeginCommandBuffer(self->command_buffer, &begin_info); + + VkImageMemoryBarrier init_barriers[2]; + for(int i = 0; i < 2; i++) { + init_barriers[i] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = self->export_images[i], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + } + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 2, init_barriers); - fprintf(stderr, "2 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RG8, frame->width/2, frame->height/2, gl_memory_obj, target_surface_id->offset[0] + luma_size); + self->vk.vkEndCommandBuffer(self->command_buffer); - fprintf(stderr, "3 gl error: %d\n", self->params.egl->glGetError()); - self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &self->command_buffer, + }; + self->vk.vkQueueSubmit(self->vk_queue, 1, &submit_info, self->fence); + self->vk.vkWaitForFences(vv->act_dev, 1, &self->fence, VK_TRUE, UINT64_MAX); + self->vk.vkResetFences(vv->act_dev, 1, &self->fence); + self->vk.vkResetCommandBuffer(self->command_buffer, 0); + + /* Create GL→Vulkan sync semaphore (binary, exported via OPAQUE_FD) */ + if(self->params.egl->glGenSemaphoresEXT && self->params.egl->glImportSemaphoreFdEXT) { + VkExportSemaphoreCreateInfo exp_sem_info = { + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + VkSemaphoreCreateInfo sem_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &exp_sem_info, + }; + if(self->vk.vkCreateSemaphore(vv->act_dev, &sem_info, NULL, &self->gl_ready_semaphore) == VK_SUCCESS) { + VkSemaphoreGetFdInfoKHR get_fd_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, + .semaphore = self->gl_ready_semaphore, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + int sem_fd = -1; + if(self->vk.vkGetSemaphoreFdKHR(vv->act_dev, &get_fd_info, &sem_fd) == VK_SUCCESS) { + self->params.egl->glGenSemaphoresEXT(1, &self->gl_semaphore); + self->params.egl->glImportSemaphoreFdEXT(self->gl_semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, sem_fd); + } else { + self->vk.vkDestroySemaphore(vv->act_dev, self->gl_ready_semaphore, NULL); + self->gl_ready_semaphore = VK_NULL_HANDLE; + } + } + } - self->texture_sizes[0] = (vec2i){ frame->width, frame->height }; - self->texture_sizes[1] = (vec2i){ frame->width/2, frame->height/2 }; - } -#endif return true; } @@ -263,11 +488,332 @@ static bool gsr_video_encoder_vulkan_start(gsr_video_encoder *encoder, AVCodecCo return true; } +static void gsr_video_encoder_vulkan_copy_textures_to_frame(gsr_video_encoder *encoder, AVFrame *frame, gsr_color_conversion *color_conversion) { + (void)color_conversion; + gsr_video_encoder_vulkan *self = encoder->priv; + AVVkFrame *vk_frame = (AVVkFrame*)frame->data[0]; + + /* Wait for the previous frame's copy to finish before reusing the command buffer */ + // if(self->fence_submitted) { + // self->vk.vkWaitForFences(self->vk_device, 1, &self->fence, VK_TRUE, UINT64_MAX); + self->vk.vkResetFences(self->vk_device, 1, &self->fence); + self->fence_submitted = false; + // } + + if(self->gl_ready_semaphore != VK_NULL_HANDLE && self->params.egl->glSignalSemaphoreEXT) { + /* GPU-side GL→Vulkan sync: signal the semaphore from GL, then flush (no CPU stall) */ + unsigned int gl_textures[2] = { self->target_textures[0], self->target_textures[1] }; + unsigned int dst_layouts[2] = { GL_LAYOUT_GENERAL_EXT, GL_LAYOUT_GENERAL_EXT }; + self->params.egl->glSignalSemaphoreEXT(self->gl_semaphore, 0, NULL, 2, gl_textures, dst_layouts); + self->params.egl->glFlush(); + } else { + /* Fallback: CPU stall to ensure GL has finished */ + self->params.egl->glFinish(); + } + + self->vk.vkResetCommandBuffer(self->command_buffer, 0); + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + self->vk.vkBeginCommandBuffer(self->command_buffer, &begin_info); + + /* Transition export images: GENERAL → TRANSFER_SRC_OPTIMAL */ + VkImageMemoryBarrier src_barriers[2]; + for(int i = 0; i < 2; i++) { + src_barriers[i] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = self->export_images[i], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + } + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 2, src_barriers); + + /* + * Detect whether the encoder frame uses one multi-plane image (default NV12/P010 + * in FFmpeg) or two separate single-plane images. + * Multi-plane: img[1] == VK_NULL_HANDLE or img[1] == img[0] + */ + const bool multiplane = (vk_frame->img[1] == VK_NULL_HANDLE || vk_frame->img[1] == vk_frame->img[0]); + + if(multiplane) { + /* Transition the encoder's multi-plane image to TRANSFER_DST_OPTIMAL */ + VkImageMemoryBarrier dst_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = vk_frame->layout[0], + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[0], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &dst_barrier); + + /* Copy Y plane: export_images[0] (R8/R16) → encoder img PLANE_0 */ + VkImageCopy copy_y = { + .srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + .dstSubresource = { VK_IMAGE_ASPECT_PLANE_0_BIT, 0, 0, 1 }, + .extent = { (uint32_t)frame->width, (uint32_t)frame->height, 1 }, + }; + self->vk.vkCmdCopyImage(self->command_buffer, + self->export_images[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk_frame->img[0], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©_y); + + /* Copy UV plane: export_images[1] (RG8/RG16) → encoder img PLANE_1 */ + VkImageCopy copy_uv = { + .srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + .dstSubresource = { VK_IMAGE_ASPECT_PLANE_1_BIT, 0, 0, 1 }, + .extent = { (uint32_t)frame->width / 2, (uint32_t)frame->height / 2, 1 }, + }; + self->vk.vkCmdCopyImage(self->command_buffer, + self->export_images[1], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk_frame->img[0], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©_uv); + + /* Transition encoder image to GENERAL and update tracked layout */ + VkImageMemoryBarrier dst_barrier_back = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[0], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, 0, NULL, 0, NULL, 1, &dst_barrier_back); + + vk_frame->layout[0] = VK_IMAGE_LAYOUT_GENERAL; + vk_frame->layout[1] = VK_IMAGE_LAYOUT_GENERAL; + } else { + /* Two separate single-plane images */ + VkImageMemoryBarrier dst_barriers[2] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = vk_frame->layout[0], + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[0], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = vk_frame->layout[1], + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[1], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 2, dst_barriers); + + for(int i = 0; i < 2; i++) { + VkImageCopy copy = { + .srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + .dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + .extent = { + (uint32_t)(i == 0 ? frame->width : frame->width / 2), + (uint32_t)(i == 0 ? frame->height : frame->height / 2), + 1 + }, + }; + self->vk.vkCmdCopyImage(self->command_buffer, + self->export_images[i], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk_frame->img[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©); + } + + VkImageMemoryBarrier dst_barriers_back[2] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[0], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_frame->img[1], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, 0, NULL, 0, NULL, 2, dst_barriers_back); + + vk_frame->layout[0] = VK_IMAGE_LAYOUT_GENERAL; + vk_frame->layout[1] = VK_IMAGE_LAYOUT_GENERAL; + } + + /* Transition export images back: TRANSFER_SRC_OPTIMAL → GENERAL for next GL frame */ + VkImageMemoryBarrier src_barriers_back[2]; + for(int i = 0; i < 2; i++) { + src_barriers_back[i] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = self->export_images[i], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + } + self->vk.vkCmdPipelineBarrier(self->command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, 0, NULL, 0, NULL, 2, src_barriers_back); + + self->vk.vkEndCommandBuffer(self->command_buffer); + + /* + * Detect whether the encoder frame is multiplane to know how many timeline + * semaphores need to be signaled. + */ + const bool mp = (vk_frame->img[1] == VK_NULL_HANDLE || vk_frame->img[1] == vk_frame->img[0]); + const int num_sems = mp ? 1 : 2; + + if(self->gl_ready_semaphore != VK_NULL_HANDLE) { + /* + * GPU-side sync path: + * - Wait on the GL binary semaphore before executing the copy. + * - Signal each AVVkFrame timeline semaphore so FFmpeg knows the frame is ready. + */ + uint64_t signal_values[2]; + VkSemaphore signal_sems[2]; + for(int i = 0; i < num_sems; i++) { + signal_values[i] = vk_frame->sem_value[i] + 1; + signal_sems[i] = vk_frame->sem[i]; + } + + VkTimelineSemaphoreSubmitInfo timeline_info = { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + .signalSemaphoreValueCount = (uint32_t)num_sems, + .pSignalSemaphoreValues = signal_values, + }; + + const VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = &timeline_info, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &self->gl_ready_semaphore, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &self->command_buffer, + .signalSemaphoreCount = (uint32_t)num_sems, + .pSignalSemaphores = signal_sems, + }; + self->vk.vkQueueSubmit(self->vk_queue, 1, &submit_info, self->fence); + + for(int i = 0; i < num_sems; i++) + vk_frame->sem_value[i]++; + } else { + /* Fallback: plain submit, we already stalled via glFinish() */ + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &self->command_buffer, + }; + self->vk.vkQueueSubmit(self->vk_queue, 1, &submit_info, self->fence); + } + + self->fence_submitted = true; +} + void gsr_video_encoder_vulkan_stop(gsr_video_encoder_vulkan *self, AVCodecContext *video_codec_context) { self->params.egl->glDeleteTextures(2, self->target_textures); self->target_textures[0] = 0; self->target_textures[1] = 0; + if(self->vk_device) { + /* Drain any in-flight copy before freeing resources */ + if(self->fence_submitted) { + self->vk.vkWaitForFences(self->vk_device, 1, &self->fence, VK_TRUE, UINT64_MAX); + self->fence_submitted = false; + } + if(self->gl_ready_semaphore) + self->vk.vkDestroySemaphore(self->vk_device, self->gl_ready_semaphore, NULL); + if(self->fence) + self->vk.vkDestroyFence(self->vk_device, self->fence, NULL); + /* Destroying the command pool also frees the command buffer */ + if(self->command_pool) + self->vk.vkDestroyCommandPool(self->vk_device, self->command_pool, NULL); + for(int i = 0; i < 2; i++) { + if(self->export_images[i]) + self->vk.vkDestroyImage(self->vk_device, self->export_images[i], NULL); + if(self->export_memory[i]) + self->vk.vkFreeMemory(self->vk_device, self->export_memory[i], NULL); + } + } + if(video_codec_context->hw_frames_ctx) av_buffer_unref(&video_codec_context->hw_frames_ctx); if(self->device_ctx) @@ -305,7 +851,7 @@ gsr_video_encoder* gsr_video_encoder_vulkan_create(const gsr_video_encoder_vulka *encoder = (gsr_video_encoder) { .start = gsr_video_encoder_vulkan_start, - .copy_textures_to_frame = NULL, + .copy_textures_to_frame = gsr_video_encoder_vulkan_copy_textures_to_frame, .get_textures = gsr_video_encoder_vulkan_get_textures, .destroy = gsr_video_encoder_vulkan_destroy, .priv = encoder_vulkan |
