Obs中Vulkan游戏的抓图逻辑
Obs中,从DX9到DX12都用到了一个很关键的技术:共享纹理;共享纹理要比共享内存的效率要高得多。也因此在Vulkan中仍然采用的是共享纹理的方式。
Vulkan不支持像Direct3D中一样的可以直接创建一个用于共享的纹理(从obs源码中得出结论,不代表真实结论),不过Vulkan模式中采用的另辟蹊径的方式来搞定。
图形捕获逻辑
源码文件:...\obs-studio\plugins\win-capture\graphics-hook\vulkan-capture.c
-
两个步骤如下:
if (capture_should_stop()) { vk_shtex_free(data); } if (capture_should_init()) { // vk_shtex_init初始化共享纹理 if (valid_rect(swap) && !vk_shtex_init(data, window, swap)) { vk_shtex_free(data); data->valid = false; flog("vk_shtex_init failed"); } } if (capture_ready()) { if (swap != data->cur_swap) { vk_shtex_free(data); return; } // vk_shtex_capture:复制图形 vk_shtex_capture(data, &data->funcs, swap, idx, queue, info); }
-
初始化阶段:Obs首先创建一个DX11的共享纹理,并得到共享纹理的句柄;然后在当前Vulkan的运行时中使用前面得到的句柄创建一个纹理。
// 创建DX11设备 if (!vk_shtex_init_d3d11(data)) { return false; } // 创建DX11共享纹理 if (!vk_shtex_init_d3d11_tex(data, swap)) { return false; } // 根据DX11的共享句柄创建Vulkan的纹理 if (!vk_shtex_init_vulkan_tex(data, swap)) { return false; } data->cur_swap = swap; // 传递当前的录制信息到obs主进程 swap->captured = capture_init_shtex( &swap->shtex_info, window, swap->image_extent.width, swap->image_extent.height, swap->image_extent.width, swap->image_extent.height, (uint32_t)swap->format, false, (uintptr_t)swap->handle); if (swap->captured) { if (global_hook_info->force_shmem) { flog("shared memory capture currently " "unsupported; ignoring"); } hlog("vulkan shared texture capture successful"); return true; }
-
录制阶段:后面在每次捕获图形时,就是不断的从当前的SwapChain的图形的图形复制到初始化时创建的Vulkan共享纹理中。
if (!swap->layout_initialized) { VkImageMemoryBarrier imb; imb.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imb.pNext = NULL; imb.srcAccessMask = 0; imb.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imb.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; imb.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imb.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imb.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imb.image = swap->export_image; imb.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imb.subresourceRange.baseMipLevel = 0; imb.subresourceRange.levelCount = 1; imb.subresourceRange.baseArrayLayer = 0; imb.subresourceRange.layerCount = 1; funcs->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &imb); swap->layout_initialized = true; } /* ------------------------------------------------------ */ /* transition cur_backbuffer to transfer source state */ src_mb->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; src_mb->pNext = NULL; src_mb->srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; src_mb->dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; src_mb->oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; src_mb->newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; src_mb->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; src_mb->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; src_mb->image = cur_backbuffer; src_mb->subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; src_mb->subresourceRange.baseMipLevel = 0; src_mb->subresourceRange.levelCount = 1; src_mb->subresourceRange.baseArrayLayer = 0; src_mb->subresourceRange.layerCount = 1; /* ------------------------------------------------------ */ /* transition exportedTexture to transfer dest state */ dst_mb->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; dst_mb->pNext = NULL; dst_mb->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; dst_mb->dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; dst_mb->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; dst_mb->newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; dst_mb->srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; dst_mb->dstQueueFamilyIndex = fam_idx; dst_mb->image = swap->export_image; // 设置在初始化中创建的vulkan共享纹理 dst_mb->subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; dst_mb->subresourceRange.baseMipLevel = 0; dst_mb->subresourceRange.levelCount = 1; dst_mb->subresourceRange.baseArrayLayer = 0; dst_mb->subresourceRange.layerCount = 1; funcs->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 2, mb); /* ------------------------------------------------------ */ /* copy cur_backbuffer's content to our interop image */ VkImageCopy cpy; cpy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; cpy.srcSubresource.mipLevel = 0; cpy.srcSubresource.baseArrayLayer = 0; cpy.srcSubresource.layerCount = 1; cpy.srcOffset.x = 0; cpy.srcOffset.y = 0; cpy.srcOffset.z = 0; cpy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; cpy.dstSubresource.mipLevel = 0; cpy.dstSubresource.baseArrayLayer = 0; cpy.dstSubresource.layerCount = 1; cpy.dstOffset.x = 0; cpy.dstOffset.y = 0; cpy.dstOffset.z = 0; cpy.extent.width = swap->image_extent.width; cpy.extent.height = swap->image_extent.height; cpy.extent.depth = 1; // 复制图形 funcs->CmdCopyImage(cmd_buffer, cur_backbuffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swap->export_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cpy); /* ------------------------------------------------------ */ /* Restore the swap chain image layout to what it was * before. This may not be strictly needed, but it is * generally good to restore things to their original * state. */ src_mb->srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; src_mb->dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; src_mb->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; src_mb->newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; dst_mb->srcQueueFamilyIndex = fam_idx; dst_mb->dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; funcs->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 2, mb); funcs->EndCommandBuffer(cmd_buffer); /* ------------------------------------------------------ */ VkSubmitInfo submit_info; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = NULL; submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = NULL; submit_info.pWaitDstStageMask = NULL; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &cmd_buffer; submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = NULL; VkFence fence = pool_data->fences[image_index]; res = funcs->QueueSubmit(queue, 1, &submit_info, fence);