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