I am trying to use vulkan’s external memory extension to share a texture created inside of Unity to another vulkan program.
Here is my process:
I have a native plugin with this function
extern "C" UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API CreateVulkanTextureForSharing(void* textureHandle, int width, int height, int targetPid) {
// Ensure Vulkan is initialized
InitializeVulkan();
// Use the VkImage provided by Unity
g_SharedImage = *reinterpret_cast<VkImage*>(textureHandle);
// Get memory requirements for the image
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(g_Device, g_SharedImage, &memRequirements);
// Allocate external memory
VkExportMemoryAllocateInfo exportAllocInfo{};
exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
allocInfo.pNext = &exportAllocInfo;
if (vkAllocateMemory(g_Device, &allocInfo, nullptr, &g_Memory) != VK_SUCCESS) {
return -3; // Failed to allocate memory
}
// Bind the allocated memory to the image
if (vkBindImageMemory(g_Device, g_SharedImage, g_Memory, 0) != VK_SUCCESS) {
vkFreeMemory(g_Device, g_Memory, nullptr);
return -6; // Failed to bind memory
}
// Export the memory as a file descriptor
HANDLE fd;
VkMemoryGetWin32HandleInfoKHR getFdInfo{};
getFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR;
getFdInfo.memory = g_Memory;
getFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR = (PFN_vkGetMemoryWin32HandleKHR)vkGetDeviceProcAddr(g_Device, "vkGetMemoryWin32HandleKHR");
if (!vkGetMemoryWin32HandleKHR) {
vkFreeMemory(g_Device, g_Memory, nullptr);
return -5; // Failed to load vkGetMemoryWin32HandleKHR
}
if (vkGetMemoryWin32HandleKHR(g_Device, &getFdInfo, &fd) != VK_SUCCESS) {
vkFreeMemory(g_Device, g_Memory, nullptr);
return -4; // Failed to export FD
}
if (targetPid < 0) //Invalid PID
{
return -10; // Return the FD for external use
}
HANDLE targetPidHandler = OpenProcess(PROCESS_DUP_HANDLE, false, targetPid);
HANDLE duplicatedHandle = nullptr;
if (!DuplicateHandle(GetCurrentProcess(), fd, targetPidHandler, &duplicatedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
throw std::runtime_error("Failed to duplicate handle!");
}
return reinterpret_cast<int>(duplicatedHandle); // Return the FD for external use
}
I import this function from the dll and use it in this C# script:
using UnityEngine;
using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine.Rendering;
public class SendTextureWithPlugin : MonoBehaviour
{
#region External functions
[DllImport("RenderingPlugin")]
private static extern int CreateVulkanTextureForSharing(IntPtr textureHandle, int width, int height, int targetPid);
#endregion
[Header("Inter process info")]
public int targetPid;
public int fdValue = -1;
public int width = 256;
public int height = 256;
[Header("Textures")]
public Texture vulkanTex;
public Texture2D tex;
public Texture2D externalTexture;
IntPtr texHandle = IntPtr.Zero;
public bool useRenderTex;
[Header("Links")]
public Camera cam;
void SetTexGradient(Texture2D tex)
{
int delta = Mathf.RoundToInt(Time.time);// 0;// Mathf.RoundToInt(Time.time);//updateTimeCounter;
Color[] colors = new Color[tex.width * tex.height];
for (int i = 0; i < tex.height; i++)
{
i = (i + delta) % tex.height;
for (int j = 0; j < tex.width; j++)
{
j = (j + delta) % tex.width;
colors[i * tex.width + j] = new Color(j / (float)tex.width, i / (float)tex.height, 0);
}
}
tex.SetPixels(colors);
tex.Apply();
}
IEnumerator Start()
{
Texture vulkanTexture = null;
if (!useRenderTex)
{
tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
yield return new WaitForEndOfFrame();
SetTexGradient(tex);
vulkanTexture = tex;
}
else
{
RenderTexture renderTexture = new RenderTexture(new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32));
cam.targetTexture = renderTexture;
yield return null;
yield return new WaitForEndOfFrame();
vulkanTexture = renderTexture;
}
fdValue = CreateVulkanTextureForSharing(vulkanTexture.GetNativeTexturePtr(), vulkanTexture.width, vulkanTexture.height, targetPid);
Debug.Log($"Texture fd: {fdValue}");
}
}
The handle value is then used in an external vulkan program (it’s converted to int and back to handle but this part does not seem to cause any issue). This is the relevant part (but the whole program is attached)
void importExternalTexture(HANDLE fd, int width, int height) {
// Import memory information
VkImportMemoryWin32HandleInfoKHR importFdInfo = {};
importFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR;
importFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; // Use OPAQUE_FD_BIT for Linux
importFdInfo.handle = fd;
VkExternalMemoryImageCreateInfo externalInfo{};
externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
// Create an image with the desired size (e.g., 256x256).
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.pNext = &externalInfo;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent = { static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1 };
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = texFormat;
imageInfo.tiling = VK_IMAGE_TILING_LINEAR;//VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_GENERAL;//Right Layout ?
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.flags = 0;
// Create the image
VkResult result = vkCreateImage(device, &imageInfo, nullptr, &textureImage);
if (result != VK_SUCCESS) {
std::cout << "Failed to create image with external memory! Error code: " << result << std::endl;
throw std::runtime_error("Failed to create image with external memory!");
}
// Get memory requirements for the image
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, textureImage, &memReqs);
// Memory allocation info
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = findMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
allocInfo.pNext = &importFdInfo; // Pass the external memory import info, allocation work if this is commented
std::cout << "Memory Requirements - Size: " << memReqs.size
<< ", Alignment: " << memReqs.alignment
<< ", allocInfo.memoryTypeIndex: " << allocInfo.memoryTypeIndex
<< ", MemoryTypeBits: " << memReqs.memoryTypeBits << std::endl;
// Allocate memory with the external handle
result = vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory);
if (result != VK_SUCCESS) {
std::cout << "Failed to allocate memory for external texture! Error code: " << VkResultToCString(result) << std::endl;
vkDestroyImage(device, textureImage, nullptr);
throw std::runtime_error("Failed to allocate memory for external texture!");
}
// Bind the memory to the image
result = vkBindImageMemory(device, textureImage, textureImageMemory, 0);
if (result != VK_SUCCESS) {
std::cout << "Failed to bind memory to external image! Error code: " << result << std::endl;
vkDestroyImage(device, textureImage, nullptr);
vkFreeMemory(device, textureImageMemory, nullptr);
throw std::runtime_error("Failed to bind memory to external image!");
}
std::cout << "FD: " << fd << std::endl;
HANDLE targetProcessHandle = GetCurrentProcess();
std::cout << "targetProcess HANDLE: " << targetProcessHandle << std::endl;
}
The result is strange. If we use a Render texture, we don’t see anything (as though the whole texture is clear).
If we use a Texture2D, the texture appears weird on the vulkan program (note that when sharing a picture from another vulkan program to this one it worked).
Here is what it looks like in the case of a Texture2D of a gradient:
Texture in unity after applying a gradient:
Texture displayed in the vulkan program:
Also, the texture in unity after the CreateVulkanTextureForSharing call appears completely corrupted. Specifically, this happens after vkBindImageMemory is called in the function.
So
-RenderTexture does not seem to be visible in the other program
-Texture2D seems corrupted when vkBindImageMemory is called in the plugin and it looks weird in the vulkan program
Preferably, we’d like to render inside of Unity and share it to another program and it needs to work on linux, hence why we can’t use Spout or Syphon and chose to directly work with vulkan’s external memory capabilities, so we would want this to work with RenderTextures.