如何从Vulkan渲染到OpenGL?

13
3个回答

18

是的,如果Vulkan实现和OpenGL实现都有适当的扩展可用,则可以实现。

这是来自Vulkan示例存储库中示例应用的屏幕截图,该应用使用OpenGL将简单的shadertoy渲染到纹理中,然后在Vulkan渲染窗口中使用该纹理。

gl to vulkan example

尽管您的问题似乎表明您想要执行相反的操作(使用Vulkan渲染并使用OpenGL显示结果),但是相同的概念仍然适用......在一个API中填充纹理,使用同步确保GPU工作完成,然后在另一个API中使用纹理。 您也可以使用缓冲区执行相同的操作,因此例如,您可以在Vulkan中使用计算操作,然后在OpenGL渲染中使用结果。
要做到这一点,需要OpenGL和Vulkan实现都支持所需的扩展名,但是根据此网站,只要使用最新版本(> 1.0.51)的Vulkan,这些扩展名就得到了广泛支持,跨操作系统版本和GPU供应商。
您需要OpenGL的External Objects扩展以及Vulkan的External Memory/Fence/Sempahore扩展。
Vulkan扩展允许您分配内存、创建信号量或栅栏,并将生成的对象标记为可导出。相应的GL扩展允许您获取这些对象并使用新的GL命令进行操作,这些命令允许您等待栅栏、信号和等待信号量,或使用Vulkan分配的内存作为OpenGL纹理的后备。通过在OpenGL帧缓冲区中使用这样的纹理,您几乎可以渲染任何想要的内容,然后在Vulkan中使用渲染结果。
例如,在Vulkan端为图像分配内存时,可以执行以下操作...
vk::Image image;
... // create the image as normal
vk::MemoryRequirements memReqs = device.getImageMemoryRequirements(image);
vk::MemoryAllocateInfo memAllocInfo;
vk::ExportMemoryAllocateInfo exportAllocInfo{
  vk::ExternalMemoryHandleTypeFlagBits::eOpaqueWin32 
};
memAllocInfo.pNext = &exportAllocInfo;
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = context.getMemoryType(
  memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::DeviceMemory memory;
memory = device.allocateMemory(memAllocInfo);
device.bindImageMemory(image, memory, 0);
HANDLE sharedMemoryHandle = device.getMemoryWin32HandleKHR({ 
  texture.memory, vk::ExternalMemoryHandleTypeFlagBits::eOpaqueWin32 
});

使用C++接口并使用扩展的Win32变体。对于Posix平台,有替代方法可以获取文件描述符而不是WIN32句柄。 sharedMemoryHandle是您需要传递给OpenGL的值,以及实际分配大小。在GL侧,您可以这样做...
// These values should be populated by the vulkan code
HANDLE sharedMemoryHandle;
GLuint64 sharedMemorySize;

// Create a 'memory object' in OpenGL, and associate it with the memory 
// allocated in vulkan
GLuint mem;
glCreateMemoryObjectsEXT(1, mem);
glImportMemoryWin32HandleEXT(mem, sharedMemorySize,
  GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, sharedMemoryHandle);

// Having created the memory object we can now create a texture and use
// the memory object for backing it
glCreateTextures(GL_TEXTURE_2D, 1, &color);
// The internalFormat here should correspond to the format of 
// the Vulkan image.  Similarly, the w & h values should correspond to 
// the extent of the Vulkan image
glTextureStorageMem2DEXT(color, 1, GL_RGBA8, w, h, mem, 0 );

同步

这里最棘手的是同步问题。Vulkan规范要求在对其进行相应操作之前,图像必须处于某些状态(布局)中。因此,为了正确执行此操作(基于我的理解),您需要执行以下操作:

  • 在Vulkan中,创建一个命令缓冲区,将图像转换为ColorAttachmentOptimal布局
  • 提交命令缓冲区,以便向OpenGL导出的信号量发出信号
  • 在OpenGL中,使用glWaitSemaphoreEXT函数使GL驱动程序等待转换完成。
    • 请注意,这是一种GPU端的等待,因此该函数不会阻塞。在这方面,它类似于glWaitSync(而不是glClientWaitSync)。
  • 执行渲染到帧缓冲区的GL命令
  • 使用glSignalSemaphoreEXT函数在GL端用不同的导出信号量发出信号
  • 在Vulkan中,执行另一张图像布局转换,从ColorAttachmentOptimal转换为ShaderReadOnlyOptimal
  • 使用刚刚从GL侧信号量发出的信号设置等待信号量,并提交转换命令缓冲区。
那将是最佳路径。或者,一种快速而不太规范的方法是完成Vulkan过渡,然后执行队列和设备waitIdle命令以确保工作完成,执行GL命令,接着使用glFlushglFinish命令以确保GPU完成了工作,然后继续进行Vulkan命令。这更像是一种蛮力方法,并且很可能会产生比进行适当的同步性能更差的结果。

如果我想在Vulkan中进行渲染,然后将结果(在缓冲区或纹理中)共享到OpenGL上下文中,我是否也使用相同的扩展来完成? - Michael IV
@MichaelIV 是的。内存对两个API都是可见的,因此您可以在任一API中编写并在另一个API中读取。但是,您仍然需要管理同步原语,以确保读取和写入不会交叉。 - Jherico

6
NVIDIA创建了一个OpenGL扩展NV_draw_vulkan_image,可以在OpenGL中呈现VkImage。它甚至具有与Vulkan信号量等交互的机制。
但是,根据文档,您必须绕过所有Vulkan层,因为层可以修改非调度句柄,而OpenGL扩展不知道这些修改。他们推荐的方法是对所有Vulkan函数使用glGetVkProcAddrNV。
这也意味着您无法访问依赖于Vulkan层的任何调试。

当然,除了将数据复制到 CPU 端再上传到另一个 API 的不道德方式。 - krOoze
2
这个答案已经过时了。nVidia已经发布了他们的OpenGL互操作扩展。它们允许您等待或信号一个Vulkan信号量,信号一个Vulkan围栏,并将一个Vulkan图像绘制到当前绑定的绘制帧缓冲区中。我在这里创建了一个示例应用程序:https://github.com/jherico/Vulkan/blob/cpp/examples/windows/glinterop.cpp - Jherico
1
@Jherico: 这里 是官方的Vulkan扩展注册表。这里 是官方的OpenGL扩展注册表。在这两个注册表中都没有你所讨论的扩展之前,我对我的陈述感到满意。 - Nicol Bolas
@NicolBolas:NV_draw_vulkan_image扩展现已在OpenGL注册表中:https://khronos.org/registry/OpenGL/extensions/NV/NV_draw_vulkan_image.txt - skalarproduktraum
1
Vulkan / OpenGL 中的新外部对象扩展可用于此目的(跨 API 纹理和缓冲区共享),并且具有额外的好处,即在启用验证层时不会出现错误。 - Jherico

1

这里有更多信息,来自于2016年SIGGRAPH的幻灯片。第63-65张幻灯片描述了如何将Vulkan图像blit到OpenGL后缓冲区。我的看法是,对于NVIDIA来说,支持这一点可能相当容易,因为Vulkan驱动程序包含在libGL.so中(在Linux上)。因此,将Vulkan图像句柄传递给驱动程序的GL侧并使其有用可能并不那么困难。

正如另一个答案所指出的,仍然没有官方注册的多供应商interop扩展。这种方法只适用于NVIDIA。


NV_draw_vulkan_image似乎是一个未注册的扩展名。专有扩展名和未在OpenGL注册表中正确注册的扩展名之间存在区别。所有扩展名都要求后者。它甚至不在所有OpenGL功能的gl.xml注册表中,所以您不能使用OpenGL加载器加载指向它的函数指针。这似乎是NVIDIA的一种不正当行为。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接