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);