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
+278 -139
View File
@@ -9,16 +9,13 @@ using Avalonia.Threading;
using GSS2.Vulkan; using GSS2.Vulkan;
using Iot.Device.FtCommon;
using LibCameraSharp; using LibCameraSharp;
using Microsoft.Extensions.Logging.Abstractions;
using Silk.NET.Core;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR; using Silk.NET.Vulkan.Extensions.KHR;
using Image = Silk.NET.Vulkan.Image;
namespace GSS2.Test; namespace GSS2.Test;
interface IImage interface IImage
@@ -46,7 +43,7 @@ class VkImage
{ {
Width = Width, Width = Width,
Height = Height, Height = Height,
Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm, Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm,
MemorySize = MemorySize MemorySize = MemorySize
}, },
Semaphore Semaphore
@@ -63,22 +60,176 @@ class VkRenderer
return new PlatformHandle(new IntPtr(fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); 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 try
{ {
var extImageInfo = new ExternalMemoryImageCreateInfo var (importImage, importMemoryReqirements, importMemory) = ImportImage(fd, width, height, stride);
var (exportImage, exportMemoryReqirements, exportMemory) = CreateExportImage(width, height);
var commandPoolCreateInfo = new CommandPoolCreateInfo
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
QueueFamilyIndex = context.QueueFamilyIndex
};
context.Api.CreateCommandPool(context.Device, &commandPoolCreateInfo, null, out var commandPool).ThrowOnError();
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
{
SType = StructureType.CommandBufferAllocateInfo,
CommandPool = commandPool,
Level = CommandBufferLevel.Primary,
CommandBufferCount = 1
};
context.Api.AllocateCommandBuffers(context.Device, &commandBufferAllocateInfo, out var commandBuffers).ThrowOnError();
var commandBufferBeginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
};
context.Api.BeginCommandBuffer(commandBuffers, &commandBufferBeginInfo).ThrowOnError();
TransitionImageLayout(commandBuffers, importImage, ImageLayout.Undefined, ImageLayout.TransferSrcOptimal);
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
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(commandBuffers, exportImage, ImageLayout.TransferDstOptimal, ImageLayout.General);
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 = &commandBuffers,
PSignalSemaphores = &renderFinished,
SignalSemaphoreCount = 1
};
context.Api.QueueSubmit(context.Queue, 1, &submitInfo, default).ThrowOnError();
return new VkImage()
{
Width = width,
Height = height,
Fd = vulkanFd,
MemorySize = exportMemoryReqirements.Size
};
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
throw;
}
}
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, SType = StructureType.ExternalMemoryImageCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
}; };
var imageCreateInfo = new ImageCreateInfo
var imageInfo = new ImageCreateInfo
{ {
SType = StructureType.ImageCreateInfo, SType = StructureType.ImageCreateInfo,
PNext = &extImageInfo, PNext = &externalMemoryImageCreateInfo,
ImageType = ImageType.Type2D, ImageType = ImageType.Type2D,
Format = Format.R8G8B8A8Unorm, Format = Format.B8G8R8A8Unorm,
Extent = new Extent3D Extent = new Extent3D
{ {
Width = checked((uint)width), Width = checked((uint)width),
@@ -91,112 +242,27 @@ class VkRenderer
Tiling = ImageTiling.Optimal, Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit, Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
SharingMode = SharingMode.Exclusive, SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined InitialLayout = ImageLayout.Undefined,
}; };
context.Api.CreateImage(context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memoryReqirements);
context.Api.CreateImage(context.Device, &imageInfo, null, out var image).ThrowOnError(); var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR
context.Api.GetImageMemoryRequirements(context.Device, image, out var memReq);
var exportAllocInfo = new ExportMemoryAllocateInfo
{ {
SType = StructureType.ExportMemoryAllocateInfo, SType = StructureType.ExportMemoryAllocateInfoKhr,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit HandleTypes = ExternalMemoryHandleTypeFlags.DmaBufBitExt
}; };
var exportAllocateInfo = new MemoryAllocateInfo
var allocInfo = new MemoryAllocateInfo
{ {
SType = StructureType.MemoryAllocateInfo, SType = StructureType.MemoryAllocateInfo,
PNext = &exportAllocInfo, AllocationSize = memoryReqirements.Size,
AllocationSize = memReq.Size, MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
MemoryTypeIndex = checked((uint)FindMemoryType(memReq.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)) PNext = &exportMemoryFdInfoKHR,
}; };
context.Api.AllocateMemory(context.Device, &exportAllocateInfo, null, out var memory).ThrowOnError();
context.Api.AllocateMemory(context.Device, &allocInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError(); context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
var poolInfo = new CommandPoolCreateInfo return (image, memoryReqirements, memory);
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
QueueFamilyIndex = context.QueueFamilyIndex
};
context.Api.CreateCommandPool(context.Device, &poolInfo, null, out var commandPool).ThrowOnError();
var cmdBufferAllocInfo = new CommandBufferAllocateInfo
{
SType = StructureType.CommandBufferAllocateInfo,
CommandPool = commandPool,
Level = CommandBufferLevel.Primary,
CommandBufferCount = 1
};
context.Api.AllocateCommandBuffers(context.Device, &cmdBufferAllocInfo, out var cmd).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
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
};
context.Api.BeginCommandBuffer(cmd, &beginInfo).ThrowOnError();
TransitionImageLayout(cmd, image, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
context.Api.CmdClearColorImage(cmd, image, ImageLayout.TransferDstOptimal, &clearColor, 1, &range);
TransitionImageLayout(cmd, image, ImageLayout.TransferDstOptimal, ImageLayout.General);
context.Api.EndCommandBuffer(cmd).ThrowOnError();
var renderFinished = context.RenderFinishedSemaphore;
var submitInfo = new SubmitInfo
{
SType = StructureType.SubmitInfo,
CommandBufferCount = 1,
PCommandBuffers = &cmd,
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
};
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
throw;
}
} }
private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties) private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
@@ -207,15 +273,15 @@ class VkRenderer
return i; return i;
return -1; 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 var barrier = new ImageMemoryBarrier
{ {
SType = StructureType.ImageMemoryBarrier, SType = StructureType.ImageMemoryBarrier,
OldLayout = oldLayout, OldLayout = oldLayout,
NewLayout = newLayout, NewLayout = newLayout,
SrcQueueFamilyIndex = 0, SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = 0, DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image, Image = image,
SubresourceRange = new ImageSubresourceRange SubresourceRange = new ImageSubresourceRange
{ {
@@ -227,37 +293,70 @@ class VkRenderer
} }
}; };
PipelineStageFlags srcStage; context.Api.CmdPipelineBarrier(
PipelineStageFlags dstStage; cmd,
PipelineStageFlags.TopOfPipeBit,
if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal) PipelineStageFlags.TransferBit,
{ 0,
barrier.SrcAccessMask = 0; 0, null,
barrier.DstAccessMask = AccessFlags.TransferWriteBit; 0, null,
1, &barrier);
srcStage = PipelineStageFlags.TopOfPipeBit;
dstStage = PipelineStageFlags.TransferBit;
} }
else if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal) // 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
// }
// };
barrier.SrcAccessMask = AccessFlags.TransferWriteBit; // PipelineStageFlags srcStage;
barrier.DstAccessMask = AccessFlags.ShaderReadBit; // PipelineStageFlags dstStage;
srcStage = PipelineStageFlags.TransferBit; // if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
dstStage = PipelineStageFlags.FragmentShaderBit; // {
// barrier.SrcAccessMask = 0;
// barrier.DstAccessMask = AccessFlags.TransferWriteBit;
} // srcStage = PipelineStageFlags.TopOfPipeBit;
else // dstStage = PipelineStageFlags.TransferBit;
{ // }
srcStage = PipelineStageFlags.AllCommandsBit; // else if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.ShaderReadOnlyOptimal)
dstStage = PipelineStageFlags.AllCommandsBit; // {
} // barrier.SrcAccessMask = 0;
// barrier.DstAccessMask = AccessFlags.ShaderReadBit;
context!.Api.CmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, null, 0, null, 1, &barrier); // 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 public partial class CameraView : Control
@@ -327,16 +426,56 @@ public partial class CameraView : Control
var (handle, properties, semaphore) = image.Export(); var (handle, properties, semaphore) = image.Export();
var imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(false)); 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)); 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); 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 () => Dispatcher.UIThread.Invoke(async () =>
{ {
await importedImage.ImportCompleted;
await Surface!.UpdateWithSemaphoresAsync(importedImage, renderCompletedSemaphore, imageRenderedSemaphore) await Surface!.UpdateWithSemaphoresAsync(importedImage, renderCompletedSemaphore, imageRenderedSemaphore)
.ContinueWith((t) => .ContinueWith((t) =>
{ {
Console.WriteLine($"UpdateWithSemaphoresAsync {t.Status}");
if (t.Exception is not null) if (t.Exception is not null)
{ {
Console.WriteLine($"Error while surface update: \n{t.Exception.Message}\n{t.Exception.StackTrace} "); 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) 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; _latestImage = image;
} }
+4 -1
View File
@@ -1,14 +1,17 @@
// Основан на примере GpuInterop Avalonia // Основан на примере GpuInterop Avalonia
// https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs // https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
using System.Runtime.CompilerServices;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
namespace GSS2.Vulkan; namespace GSS2.Vulkan;
public static class VulkanExtensions public static class VulkanExtensions
{ {
public static void ThrowOnError(this Result result) public static void ThrowOnError(this Result result, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1)
{ {
Console.WriteLine($"{callerFilePath} {callerMemberName} {callerLineNumber} {result}");
if (result != Result.Success) if (result != Result.Success)
throw new Exception($"Unexpected API error \"{result}\""); throw new Exception($"Unexpected API error \"{result}\"");
} }