Obs中Vulkan遊戲的取圖

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);
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章