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 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,22 +60,176 @@ 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
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,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
var imageInfo = new ImageCreateInfo
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
PNext = &extImageInfo,
PNext = &externalMemoryImageCreateInfo,
ImageType = ImageType.Type2D,
Format = Format.R8G8B8A8Unorm,
Format = Format.B8G8R8A8Unorm,
Extent = new Extent3D
{
Width = checked((uint)width),
@@ -91,112 +242,27 @@ class VkRenderer
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
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();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memReq);
var exportAllocInfo = new ExportMemoryAllocateInfo
var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR
{
SType = StructureType.ExportMemoryAllocateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
SType = StructureType.ExportMemoryAllocateInfoKhr,
HandleTypes = ExternalMemoryHandleTypeFlags.DmaBufBitExt
};
var allocInfo = new MemoryAllocateInfo
var exportAllocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
PNext = &exportAllocInfo,
AllocationSize = memReq.Size,
MemoryTypeIndex = checked((uint)FindMemoryType(memReq.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit))
AllocationSize = memoryReqirements.Size,
MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
PNext = &exportMemoryFdInfoKHR,
};
context.Api.AllocateMemory(context.Device, &allocInfo, null, out var memory).ThrowOnError();
context.Api.AllocateMemory(context.Device, &exportAllocateInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
var poolInfo = new CommandPoolCreateInfo
{
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;
}
return (image, memoryReqirements, memory);
}
private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
@@ -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;
context.Api.CmdPipelineBarrier(
cmd,
PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.TransferBit,
0,
0, null,
0, null,
1, &barrier);
}
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;
barrier.DstAccessMask = AccessFlags.ShaderReadBit;
// PipelineStageFlags srcStage;
// PipelineStageFlags dstStage;
srcStage = PipelineStageFlags.TransferBit;
dstStage = PipelineStageFlags.FragmentShaderBit;
// if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
// {
// barrier.SrcAccessMask = 0;
// barrier.DstAccessMask = AccessFlags.TransferWriteBit;
}
else
{
srcStage = PipelineStageFlags.AllCommandsBit;
dstStage = PipelineStageFlags.AllCommandsBit;
}
// srcStage = PipelineStageFlags.TopOfPipeBit;
// dstStage = PipelineStageFlags.TransferBit;
// }
// else if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.ShaderReadOnlyOptimal)
// {
// 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
@@ -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;
}
+4 -1
View File
@@ -1,14 +1,17 @@
// Основан на примере GpuInterop Avalonia
// https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
using System.Runtime.CompilerServices;
using Silk.NET.Vulkan;
namespace GSS2.Vulkan;
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)
throw new Exception($"Unexpected API error \"{result}\"");
}