Vulkan共享句柄创建OpenGL纹理拷贝后读数据为NULL问题
报错核心原因是跨API共享链路存在配置缺失、同步错误或API使用不当,按以下点逐一排查修正即可:
- Vulkan侧导出配置缺失(最高发原因)
你当前代码只取了内存句柄,没有在创建Image、分配内存时显式声明外部内存导出属性,导出的句柄实际不具备跨API共享权限,OpenGL侧导入不会抛错,但生成的纹理对象是空的。
修正时必须在Vulkan创建Image的pNext链上加入vk::ExternalMemoryImageCreateInfo,指定和导出句柄匹配的类型(Windows为eOpaqueWin32、Linux为eOpaqueFd);分配内存时pNext链加入vk::ExportMemoryAllocateInfo,指定相同的句柄类型,同时Image的usage必须包含VK_IMAGE_USAGE_TRANSFER_SRC_BIT,内存类型选带VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT标记的类型。
修正代码参考:// Image创建时追加外部内存配置 vk::ExternalMemoryImageCreateInfo ext_img_conf{};
#ifdef _WIN32
ext_img_conf.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueWin32;
#else
ext_img_conf.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd;
#endif
vk::ImageCreateInfo img_conf{};
img_conf.pNext = &ext_img_conf;
img_conf.imageType = vk::ImageType::e2D;
img_conf.format = /* 必须和OpenGL侧内部格式严格对应,比如VK_FORMAT_R8G8B8A8_UNORM */;
img_conf.extent = vk::Extent3D{texGl.imgSize.width, texGl.imgSize.height, 1};
img_conf.mipLevels = texGl.mipLevels;
img_conf.arrayLayers = 1;
img_conf.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled;
texGl.texVk.image = device.createImage(img_conf);
// 内存分配时追加导出配置
vk::MemoryRequirements mem_req = device.getImageMemoryRequirements(texGl.texVk.image);
vk::ExportMemoryAllocateInfo export_mem_conf{};
#ifdef _WIN32
export_mem_conf.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueWin32;
#else
export_mem_conf.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd;
#endif
vk::MemoryAllocateInfo alloc_conf{};
alloc_conf.pNext = &export_mem_conf;
alloc_conf.allocationSize = mem_req.size;
alloc_conf.memoryTypeIndex = /* 匹配mem_req.memoryTypeBits的DEVICE_LOCAL内存类型索引 */;
info.memory = device.allocateMemory(alloc_conf);
device.bindImageMemory(texGl.texVk.image, info.memory, 0);
- **OpenGL侧导入参数不匹配** 一是格式必须严格对齐:调用`glTextureStorageMem2DEXT`传入的内部格式必须和Vulkan侧Image格式一一对应,比如Vulkan用`VK_FORMAT_R8G8B8A8_UNORM`时,GL侧必须传`GL_RGBA8`,不能传`GL_RGBA`这种无内部存储格式定义的参数;二是内存偏移`info.offset`必须是`vk::getImageMemoryRequirements`返回的alignment值的整数倍,不对齐会导致内存映射偏移错误,纹理内容为空;三是句柄类型必须匹配,Windows下如果导出的是KMT类型句柄,GL侧要改用`GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT`导入,不要混用NT句柄和KMT句柄的导入接口。 导入后加校验逻辑确认纹理有效: ```cpp GLint tex_w = 0, tex_h = 0; glGetTextureLevelParameteriv(texGl.oglId, 0, GL_TEXTURE_WIDTH, &tex_w); glGetTextureLevelParameteriv(texGl.oglId, 0, GL_TEXTURE_HEIGHT, &tex_h); if (tex_w == 0 || tex_h == 0) { // 纹理导入失败,回头检查前面的参数配置 }
- 跨API同步逻辑缺失
Vulkan侧写入共享纹理完成后,必须插入管线屏障将Image布局切换为VK_IMAGE_LAYOUT_GENERAL(跨API共享兼容度最高的布局),通过跨API共享的信号量发出写入完成通知;OpenGL侧在执行拷贝、读纹理操作前,必须先等待该信号量,确认Vulkan侧写入已经落地,否则会读到GPU未写入完成的空数据。 - 纹理读取API使用错误
你当前读取mip level 1时,缓冲区大小仍按0层宽高计算,会触发GL_INVALID_OPERATION错误,glGetTextureImage不会向缓冲区写入任何数据,malloc出来的未初始化内存会表现为全空。读取任意mip层前先查询该层实际宽高再计算缓冲区大小:
另外auto read_mip_data = [](GLuint tex_id, GLint mip_level, GLenum fmt, GLenum type, GLuint pix_size) -> GLubyte* { GLint w = 0, h = 0; glGetTextureLevelParameteriv(tex_id, mip_level, GL_TEXTURE_WIDTH, &w); glGetTextureLevelParameteriv(tex_id, mip_level, GL_TEXTURE_HEIGHT, &h); GLsizei buf_size = w * h * pix_size; GLubyte* data = (GLubyte*)malloc(buf_size); glGetTextureImage(tex_id, mip_level, fmt, type, buf_size, data); return data; }; // 读0层 GLubyte* lvl0_data = read_mip_data(desTexture->getID(), 0, desTexture->getFormat(), desTexture->getType(), pixsize); // 读1层 GLubyte* lvl1_data = read_mip_data(desTexture->getID(), 1, desTexture->getFormat(), desTexture->getType(), pixsize);glCopyImageSubData执行时要确认源、目标纹理的内部格式、宽高完全匹配,拷贝区域不要超出纹理边界。
内容的提问来源于stack exchange,提问作者cfl997

