feat: GSS2.Test Vulkan render image

This commit is contained in:
2026-01-22 14:47:10 +03:00
parent 5af1c508f5
commit 20d3807b8e
2 changed files with 267 additions and 125 deletions
+263 -124
View File
@@ -9,16 +9,13 @@ using Avalonia.Threading;
using GSS2.Vulkan;
using Iot.Device.FtCommon;
using LibCameraSharp;
using Microsoft.Extensions.Logging.Abstractions;
using Silk.NET.Core;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using Image = Silk.NET.Vulkan.Image;
namespace GSS2.Test;
interface IImage
@@ -46,7 +43,7 @@ class VkImage
{
Width = Width,
Height = Height,
Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm,
MemorySize = MemorySize
},
Semaphore
@@ -63,66 +60,22 @@ class VkRenderer
return new PlatformHandle(new IntPtr(fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
}
unsafe public VkImage ImportOrUpdate(int fd, int width, int height)
unsafe public VkImage ImportOrUpdate(int fd, int width, int height, int stride)
{
try
{
var extImageInfo = new ExternalMemoryImageCreateInfo
{
SType = StructureType.ExternalMemoryImageCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
var (importImage, importMemoryReqirements, importMemory) = ImportImage(fd, width, height, stride);
var (exportImage, exportMemoryReqirements, exportMemory) = CreateExportImage(width, height);
var imageInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
PNext = &extImageInfo,
ImageType = ImageType.Type2D,
Format = Format.R8G8B8A8Unorm,
Extent = new Extent3D
{
Width = checked((uint)width),
Height = checked((uint)height),
Depth = 1
},
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined
};
context.Api.CreateImage(context.Device, &imageInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memReq);
var exportAllocInfo = new ExportMemoryAllocateInfo
{
SType = StructureType.ExportMemoryAllocateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
var allocInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
PNext = &exportAllocInfo,
AllocationSize = memReq.Size,
MemoryTypeIndex = checked((uint)FindMemoryType(memReq.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit))
};
context.Api.AllocateMemory(context.Device, &allocInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
var poolInfo = new CommandPoolCreateInfo
var commandPoolCreateInfo = new CommandPoolCreateInfo
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
QueueFamilyIndex = context.QueueFamilyIndex
};
context.Api.CreateCommandPool(context.Device, &poolInfo, null, out var commandPool).ThrowOnError();
context.Api.CreateCommandPool(context.Device, &commandPoolCreateInfo, null, out var commandPool).ThrowOnError();
var cmdBufferAllocInfo = new CommandBufferAllocateInfo
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
{
SType = StructureType.CommandBufferAllocateInfo,
CommandPool = commandPool,
@@ -130,65 +83,75 @@ class VkRenderer
CommandBufferCount = 1
};
context.Api.AllocateCommandBuffers(context.Device, &cmdBufferAllocInfo, out var cmd).ThrowOnError();
context.Api.AllocateCommandBuffers(context.Device, &commandBufferAllocateInfo, out var commandBuffers).ThrowOnError();
var clearColor = new ClearColorValue((float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble());
var range = new ImageSubresourceRange
{
AspectMask = ImageAspectFlags.ColorBit,
BaseMipLevel = 0,
LevelCount = 1,
BaseArrayLayer = 0,
LayerCount = 1
};
var beginInfo = new CommandBufferBeginInfo
var commandBufferBeginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
};
context.Api.BeginCommandBuffer(cmd, &beginInfo).ThrowOnError();
context.Api.BeginCommandBuffer(commandBuffers, &commandBufferBeginInfo).ThrowOnError();
TransitionImageLayout(cmd, image, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
TransitionImageLayout(commandBuffers, importImage, ImageLayout.Undefined, ImageLayout.TransferSrcOptimal);
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
context.Api.CmdClearColorImage(cmd, image, ImageLayout.TransferDstOptimal, &clearColor, 1, &range);
var region = new ImageCopy
{
SrcSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ColorBit,
MipLevel = 0,
BaseArrayLayer = 0,
LayerCount = 1
},
SrcOffset = new Offset3D(0, 0, 0),
DstSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ColorBit,
MipLevel = 0,
BaseArrayLayer = 0,
LayerCount = 1
},
DstOffset = new Offset3D(0, 0, 0),
Extent = new Extent3D((uint)width, (uint)height, 1)
};
context.Api.CmdCopyImage(commandBuffers, importImage, ImageLayout.Undefined, exportImage, ImageLayout.Undefined, [region]);
TransitionImageLayout(cmd, image, ImageLayout.TransferDstOptimal, ImageLayout.General);
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.TransferDstOptimal, ImageLayout.General);
context.Api.EndCommandBuffer(cmd).ThrowOnError();
context.Api.EndCommandBuffer(commandBuffers).ThrowOnError();
if (context.Api.TryGetDeviceExtension<KhrExternalMemoryFd>(context.Instance, context.Device, out var khrExternalMemoryFd))
Console.WriteLine("KhrExternalMemoryFd import success");
else
throw new Exception("KhrExternalMemoryFd import error");
var MemoryGetFdInfoKHR = new MemoryGetFdInfoKHR
{
SType = StructureType.MemoryGetFDInfoKhr,
Memory = exportMemory,
HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
khrExternalMemoryFd.GetMemoryF(context.Device, &MemoryGetFdInfoKHR, out var vulkanFd).ThrowOnError();
Console.WriteLine($"Image export success {vulkanFd}");
var renderFinished = context.RenderFinishedSemaphore;
var submitInfo = new SubmitInfo
{
SType = StructureType.SubmitInfo,
CommandBufferCount = 1,
PCommandBuffers = &cmd,
PCommandBuffers = &commandBuffers,
PSignalSemaphores = &renderFinished,
SignalSemaphoreCount = 1
};
context.Api.QueueSubmit(context.Queue, 1, &submitInfo, default).ThrowOnError();
var getFdInfo = new MemoryGetFdInfoKHR
{
SType = StructureType.MemoryGetFDInfoKhr,
Memory = memory,
HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
if (context.Api.TryGetDeviceExtension<KhrExternalMemoryFd>(context.Instance, context.Device, out var khrExternalMemoryFd))
Console.WriteLine("KhrExternalMemoryFd import success");
else
throw new Exception("KhrExternalMemoryFd import error");
khrExternalMemoryFd.GetMemoryF(context.Device, &getFdInfo, out var fd1).ThrowOnError();
Console.WriteLine($"Image export success {fd1}");
return new VkImage()
{
Width = width,
Height = height,
Fd = fd1,
MemorySize = memReq.Size
Fd = vulkanFd,
MemorySize = exportMemoryReqirements.Size
};
}
catch (Exception e)
@@ -199,6 +162,109 @@ class VkRenderer
}
}
unsafe public (Image, MemoryRequirements, DeviceMemory) ImportImage(int fd, int width, int height, int stride)
{
var subresourceLayout = new SubresourceLayout
{
Offset = 0,
RowPitch = checked((ulong)stride),
};
var imageDrmFormatModifierExplicitCreateInfoEXT = new ImageDrmFormatModifierExplicitCreateInfoEXT
{
SType = StructureType.ImageDrmFormatModifierExplicitCreateInfoExt,
DrmFormatModifier = 0,
DrmFormatModifierPlaneCount = 1,
PPlaneLayouts = &subresourceLayout,
};
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
PNext = &imageDrmFormatModifierExplicitCreateInfoEXT,
ImageType = ImageType.Type2D,
Format = Format.B8G8R8A8Unorm,
Extent = new Extent3D
{
Width = checked((uint)width),
Height = checked((uint)height),
Depth = 1
},
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.DrmFormatModifierExt,
Usage = ImageUsageFlags.TransferSrcBit | ImageUsageFlags.SampledBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
};
context.Api.CreateImage(context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memoryReqirements);
var importMemoryFdInfoKHR = new ImportMemoryFdInfoKHR
{
SType = StructureType.ImportMemoryFDInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.DmaBufBitExt,
Fd = fd,
};
var memoryAllocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryReqirements.Size,
MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
PNext = &importMemoryFdInfoKHR,
};
context.Api.AllocateMemory(context.Device, &memoryAllocateInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
return (image, memoryReqirements, memory);
}
unsafe public (Image, MemoryRequirements, DeviceMemory) CreateExportImage(int width, int height)
{
var externalMemoryImageCreateInfo = new ExternalMemoryImageCreateInfo
{
SType = StructureType.ExternalMemoryImageCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
PNext = &externalMemoryImageCreateInfo,
ImageType = ImageType.Type2D,
Format = Format.B8G8R8A8Unorm,
Extent = new Extent3D
{
Width = checked((uint)width),
Height = checked((uint)height),
Depth = 1
},
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
};
context.Api.CreateImage(context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memoryReqirements);
var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR
{
SType = StructureType.ExportMemoryAllocateInfoKhr,
HandleTypes = ExternalMemoryHandleTypeFlags.DmaBufBitExt
};
var exportAllocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryReqirements.Size,
MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
PNext = &exportMemoryFdInfoKHR,
};
context.Api.AllocateMemory(context.Device, &exportAllocateInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
return (image, memoryReqirements, memory);
}
private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
{
context!.Api.GetPhysicalDeviceMemoryProperties(context.PhysicalDevice, out var memProps);
@@ -207,15 +273,15 @@ class VkRenderer
return i;
return -1;
}
unsafe private void TransitionImageLayout(CommandBuffer cmd, Silk.NET.Vulkan.Image image, ImageLayout oldLayout, ImageLayout newLayout)
unsafe private void TransitionImageLayout(CommandBuffer cmd, Image image, ImageLayout oldLayout, ImageLayout newLayout)
{
var barrier = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
OldLayout = oldLayout,
NewLayout = newLayout,
SrcQueueFamilyIndex = 0,
DstQueueFamilyIndex = 0,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
SubresourceRange = new ImageSubresourceRange
{
@@ -227,37 +293,70 @@ class VkRenderer
}
};
PipelineStageFlags srcStage;
PipelineStageFlags dstStage;
if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
{
barrier.SrcAccessMask = 0;
barrier.DstAccessMask = AccessFlags.TransferWriteBit;
srcStage = PipelineStageFlags.TopOfPipeBit;
dstStage = PipelineStageFlags.TransferBit;
}
else if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal)
{
barrier.SrcAccessMask = AccessFlags.TransferWriteBit;
barrier.DstAccessMask = AccessFlags.ShaderReadBit;
srcStage = PipelineStageFlags.TransferBit;
dstStage = PipelineStageFlags.FragmentShaderBit;
}
else
{
srcStage = PipelineStageFlags.AllCommandsBit;
dstStage = PipelineStageFlags.AllCommandsBit;
}
context!.Api.CmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, null, 0, null, 1, &barrier);
context.Api.CmdPipelineBarrier(
cmd,
PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.TransferBit,
0,
0, null,
0, null,
1, &barrier);
}
// unsafe private void TransitionImageLayout(CommandBuffer cmd, Silk.NET.Vulkan.Image image, ImageLayout oldLayout, ImageLayout newLayout)
// {
// var barrier = new ImageMemoryBarrier
// {
// SType = StructureType.ImageMemoryBarrier,
// OldLayout = oldLayout,
// NewLayout = newLayout,
// SrcQueueFamilyIndex = 0,
// DstQueueFamilyIndex = 0,
// Image = image,
// SubresourceRange = new ImageSubresourceRange
// {
// AspectMask = ImageAspectFlags.ColorBit,
// BaseMipLevel = 0,
// LevelCount = 1,
// BaseArrayLayer = 0,
// LayerCount = 1
// }
// };
// PipelineStageFlags srcStage;
// PipelineStageFlags dstStage;
// if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
// {
// barrier.SrcAccessMask = 0;
// barrier.DstAccessMask = AccessFlags.TransferWriteBit;
// srcStage = PipelineStageFlags.TopOfPipeBit;
// dstStage = PipelineStageFlags.TransferBit;
// }
// else if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.ShaderReadOnlyOptimal)
// {
// barrier.SrcAccessMask = 0;
// barrier.DstAccessMask = AccessFlags.ShaderReadBit;
// srcStage = PipelineStageFlags.TopOfPipeBit;
// dstStage = PipelineStageFlags.FragmentShaderBit;
// }
// else if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal)
// {
// barrier.SrcAccessMask = AccessFlags.TransferWriteBit;
// barrier.DstAccessMask = AccessFlags.ShaderReadBit;
// srcStage = PipelineStageFlags.TransferBit;
// dstStage = PipelineStageFlags.FragmentShaderBit;
// }
// else
// {
// srcStage = PipelineStageFlags.AllCommandsBit;
// dstStage = PipelineStageFlags.AllCommandsBit;
// }
// context!.Api.CmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, null, 0, null, 1, &barrier);
// }
}
public partial class CameraView : Control
@@ -327,16 +426,56 @@ public partial class CameraView : Control
var (handle, properties, semaphore) = image.Export();
var imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(false));
imageRenderedSemaphore.ImportCompleted.ContinueWith(t =>
{
Console.WriteLine($"imageRenderedSemaphore.ImportCompleted {t.Status}");
if (t.Exception is not null)
{
Console.WriteLine($"Error: \n{t.Exception.Message}\n{t.Exception.StackTrace} ");
foreach (var e in t.Exception.InnerExceptions)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
});
var renderCompletedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(true));
renderCompletedSemaphore.ImportCompleted.ContinueWith(t =>
{
Console.WriteLine($"renderCompletedSemaphore.ImportCompleted {t.Status}");
if (t.Exception is not null)
{
Console.WriteLine($"Error: \n{t.Exception.Message}\n{t.Exception.StackTrace} ");
foreach (var e in t.Exception.InnerExceptions)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
});
Console.WriteLine("ImportImage");
var importedImage = _gpuInterop!.ImportImage(handle, properties);
importedImage.ImportCompleted.ContinueWith(t =>
{
Console.WriteLine($"importedImage.ImportCompleted {t.Status}");
if (t.Exception is not null)
{
Console.WriteLine($"Error: \n{t.Exception.Message}\n{t.Exception.StackTrace} ");
foreach (var e in t.Exception.InnerExceptions)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
});
Dispatcher.UIThread.Invoke(async () =>
{
await importedImage.ImportCompleted;
await Surface!.UpdateWithSemaphoresAsync(importedImage, renderCompletedSemaphore, imageRenderedSemaphore)
.ContinueWith((t) =>
{
Console.WriteLine($"UpdateWithSemaphoresAsync {t.Status}");
if (t.Exception is not null)
{
Console.WriteLine($"Error while surface update: \n{t.Exception.Message}\n{t.Exception.StackTrace} ");
@@ -367,7 +506,7 @@ public partial class CameraView : Control
if (frameBuffer is not null && streamConfiguration is not null && _renerer is not null)
{
var image = _renerer.ImportOrUpdate(frameBuffer.Planes.First().Fd.Get(), checked((int)streamConfiguration.Size.Width), checked((int)streamConfiguration.Size.Height));
var image = _renerer.ImportOrUpdate(frameBuffer.Planes.First().Fd.Get(), checked((int)streamConfiguration.Size.Width), checked((int)streamConfiguration.Size.Height), checked((int)streamConfiguration.Stride));
_latestImage = image;
}