fix: CameraView memory leakage

This commit is contained in:
2026-01-28 15:03:46 +03:00
parent 8ff52f7d1e
commit 3283d73b3b
10 changed files with 363 additions and 266 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
<PackageReference Include="OpenCvSharp4.runtime.linux-arm" Version="4.11.0.20250506" />
<PackageReference Include="System.Device.Gpio" Version="4.0.1" />
<PackageReference Include="Iot.Device.Bindings" Version="4.0.1" />
<PackageReference Include="LibCameraSharp" Version="0.5.2-20260116" />
<PackageReference Include="LibCameraSharp" Version="0.5.2-20260129-5" />
</ItemGroup>
<ItemGroup>
-1
View File
@@ -716,7 +716,6 @@ public class CameraService : IDisposable
_camera.RequestCompleted += RequestCompleted;
result = _camera.QueueRequest(request);
Console.WriteLine(result);
if (result < 0)
{
_camera.RequestCompleted -= RequestCompleted;
+3 -3
View File
@@ -11,7 +11,7 @@ public static class DependencyInjectionHelper
.AddSingleton<MainView>()
.AddSingleton<MainViewModel>()
.AddSingleton<ResourcesView>()
.AddSingleton<ResourcesViewModel>();
// .AddSingleton<CameraView>()
// .AddSingleton<CameraViewModel>();
.AddSingleton<ResourcesViewModel>()
.AddSingleton<CameraView>()
.AddSingleton<CameraViewModel>();
}
+1 -1
View File
@@ -13,7 +13,7 @@ public class CameraViewModel : ViewModelBase
{
_cameraService = cameraService;
_logger = logger;
_logger.LogInformation("Initialized");
_logger.LogInformation("{} initialized", nameof(CameraViewModel));
}
public async Task<(FrameBuffer, StreamConfiguration)?> CaptureImage(int bufferId)
+2 -2
View File
@@ -8,10 +8,10 @@ public partial class MainViewModel : ViewModelBase
[ObservableProperty] private CameraViewModel _cameraViewModel;
[ObservableProperty] private ResourcesViewModel _resourcesViewModel;
public MainViewModel(ILogger<MainViewModel> logger, ResourcesViewModel resourcesViewModel)
public MainViewModel(ILogger<MainViewModel> logger, CameraViewModel cameraViewModel, ResourcesViewModel resourcesViewModel)
{
_logger = logger;
// CameraViewModel = cameraViewModel;
CameraViewModel = cameraViewModel;
ResourcesViewModel = resourcesViewModel;
_logger.LogInformation("{} initialized", nameof(MainViewModel));
}
+1 -1
View File
@@ -43,7 +43,6 @@ public partial class ResourcesViewModel : ViewModelBase, IDisposable
[ObservableProperty] private ObservableCollection<Axis> _memoryUsageSeriesXAxes;
[ObservableProperty] private ObservableCollection<ISeries> _memoryUsagePieSeries;
[ObservableProperty] private int _intervalMs = 500;
[ObservableProperty] private int _chartsHistorySize = 120;
private readonly System.Timers.Timer _updateTimer;
@@ -409,6 +408,7 @@ public partial class ResourcesViewModel : ViewModelBase, IDisposable
memoryUsageValues.Add(Math.Round(snapshot.MemoryUsage.UsedMB, 2));
while (memoryUsageValues.Count > ChartsHistorySize)
memoryUsageValues.RemoveAt(0);
// _logger.LogInformation("Memory leakage: {} MB/s", (memoryUsageValues.TakeLast(10).Average() - memoryUsageValues.TakeLast(20).Take(10).Average()) / 5.0);
}
if (MemoryUsageSeriesYAxes.Count() == 1)
MemoryUsageSeriesYAxes.First().MaxLimit = Math.Round(snapshot.MemoryUsage.TotalMB);
+280 -218
View File
@@ -1,105 +1,128 @@
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using GSS2.Test.ViewModels;
using GSS2.Vulkan;
using LibCameraSharp;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using Image = Silk.NET.Vulkan.Image;
namespace GSS2.Test.Views;
interface IImage
public class VulkanImageResources
{
public (IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties, Silk.NET.Vulkan.Semaphore semaphore) Export();
public Image ImportImage { get; init; }
public DeviceMemory ImportMemory { get; init; }
public Image ExportImage { get; init; }
public DeviceMemory ExportMemory { get; init; }
}
interface IRenderer
class VulkanExportedImage
{
public abstract IImage ImportOrUpdate(int fd, int width, int height);
}
public required int ImageExportedSemaphoreFd { get; init; }
public required int ImageRenderedSemaphoreFd { get; init; }
public required int OrignalFd { get; init; }
public required int ImageFd { get; init; }
public required int ImageWidth { get; init; }
public required int ImageHeight { get; init; }
public required ulong ImageMemorySize { get; init; }
public required int WaitSemaphoreFd { get; init; }
public required int SignalSemaphoreFd { get; init; }
public required VulkanImageResources Resources { get; init; }
class VkImage
{
public int Fd { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public ulong MemorySize { get; set; }
public Silk.NET.Vulkan.Semaphore Semaphore { get; set; }
public (IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties, Silk.NET.Vulkan.Semaphore semaphore) Export()
public (IPlatformHandle imageHandle, PlatformGraphicsExternalImageProperties imageProperties, IPlatformHandle waitSemaphoreHandle, IPlatformHandle signalSemaphoreHandle) Export()
{
return (
new PlatformHandle(new IntPtr(Fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor),
new PlatformHandle(new IntPtr(ImageFd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor),
new PlatformGraphicsExternalImageProperties
{
Width = Width,
Height = Height,
Width = ImageWidth,
Height = ImageHeight,
Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm,
MemorySize = MemorySize
MemorySize = ImageMemorySize
},
Semaphore
new PlatformHandle(new IntPtr(WaitSemaphoreFd), KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor),
new PlatformHandle(new IntPtr(SignalSemaphoreFd), KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
);
}
}
class VkRenderer
class VulkanRenderer
{
[DllImport("libc")] private static extern int dup(int fd);
readonly VulkanContext context = new VulkanContext("GSS2.Test", "CameraView");
[DllImport("libc")] private static extern int close(int fd);
private readonly VulkanContext _context = new VulkanContext("GSS2.Test", "CameraView");
public IPlatformHandle ExportSemaphore(bool renderFinished)
unsafe public VulkanExportedImage Import(int imageFd, int width, int height, int stride, int? fenceFd, bool initializationFrame)
{
int fd = context.ExportSemaphoreFd(renderFinished ? context.RenderFinishedSemaphore : context.ImageAvailableSemaphore);
return new PlatformHandle(new IntPtr(fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
}
unsafe public VkImage ImportOrUpdate(int fd, int width, int height, int stride)
{
var fdDup = dup(fd);
// Console.WriteLine("Import 1");
var imageFdDuplicate = dup(imageFd);
// Console.WriteLine("Import 2");
try
{
FenceCreateInfo fenceInfo = new FenceCreateInfo
{
SType = StructureType.FenceCreateInfo
};
context.Api.CreateFence(context.Device, &fenceInfo, null, out var fence).ThrowOnError();
// Console.WriteLine("Import 3");
var imageExportedSemaphore = _context.ImageExportedSemaphore;
var imageRenderedSemaphore = _context.ImageRenderedSemaphore;
var commandBuffer = _context.CommandBuffer;
var (importImage, importMemoryReqirements, importMemory) = ImportImage(fdDup, width, height, stride);
// Console.WriteLine("Import 7");
var imageExportedSemaphoreGetFdInfoKHR = new SemaphoreGetFdInfoKHR()
{
SType = StructureType.SemaphoreGetFDInfoKhr,
Semaphore = imageExportedSemaphore,
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
_context.KhrExternalSemaphoreFd.GetSemaphoreF(_context.Device, in imageExportedSemaphoreGetFdInfoKHR, out var imageExportedSemaphoreFd).ThrowOnError();
// Console.WriteLine("Import 8");
var imageRenderedSemaphoreGetFdInfoKHR = new SemaphoreGetFdInfoKHR()
{
SType = StructureType.SemaphoreGetFDInfoKhr,
Semaphore = imageRenderedSemaphore,
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
_context.KhrExternalSemaphoreFd.GetSemaphoreF(_context.Device, in imageRenderedSemaphoreGetFdInfoKHR, out var imageRenderedSemaphoreFd).ThrowOnError();
// Silk.NET.Vulkan.Fence imageCapturedFence = new Silk.NET.Vulkan.Fence(null);
// if (fenceFd is not null)
// {
// var importSemaphoreFdInfoKHR = new ImportFenceFdInfoKHR()
// {
// SType = StructureType.ImportSemaphoreFDInfoKhr,
// Fd = fenceFd.Value,
// Flags = FenceImportFlags.TemporaryBit,
// HandleType = ExternalFenceHandleTypeFlags.SyncFDBit
// };
// _context.KhrExternalFenceFd.ImportFenceF(_context.Device, &importSemaphoreFdInfoKHR);
// }
// Console.WriteLine("Import 9");
var (importImage, importMemoryReqirements, importMemory) = ImportImage(imageFdDuplicate, width, height, stride);
// Console.WriteLine("Import 10");
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();
// Console.WriteLine("Import 11");
var commandBufferBeginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
};
context.Api.BeginCommandBuffer(commandBuffers, &commandBufferBeginInfo).ThrowOnError();
_context.Api.BeginCommandBuffer(_context.CommandBuffer, &commandBufferBeginInfo).ThrowOnError();
TransitionImageLayout(commandBuffers, importImage, ImageLayout.Undefined, ImageLayout.TransferSrcOptimal);
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
// Console.WriteLine("Import 12");
TransitionImageLayout(_context.CommandBuffer, importImage, ImageLayout.Undefined, ImageLayout.TransferSrcOptimal);
TransitionImageLayout(_context.CommandBuffer, exportImage, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
var region = new ImageCopy
// Console.WriteLine("Import 13");
var imageCopy = new ImageCopy
{
SrcSubresource = new ImageSubresourceLayers
{
@@ -119,55 +142,121 @@ class VkRenderer
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]);
_context.Api.CmdCopyImage(_context.CommandBuffer, importImage, ImageLayout.Undefined, exportImage, ImageLayout.Undefined, [imageCopy]);
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.TransferDstOptimal, ImageLayout.General);
// Console.WriteLine("Import 14");
TransitionImageLayout(_context.CommandBuffer, exportImage, ImageLayout.TransferDstOptimal, ImageLayout.General);
context.Api.EndCommandBuffer(commandBuffers).ThrowOnError();
// Console.WriteLine("Import 154");
_context.Api.EndCommandBuffer(_context.CommandBuffer).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");
// Console.WriteLine("Import 16");
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}");
_context.KhrExternalMemoryFd.GetMemoryF(_context.Device, &memoryGetFdInfoKHR, out var vulkanImageFd).ThrowOnError();
var renderFinished = context.RenderFinishedSemaphore;
var submitInfo = new SubmitInfo
// Console.WriteLine("Import 17");
// var waitSemaphoreIndex = 0;
// Silk.NET.Vulkan.Semaphore[] waitSemaphoresArray = new Silk.NET.Vulkan.Semaphore[
// !initializationFrame ? 1 : 0
// ];
// PipelineStageFlags[] pipelineStageFlagsArray = new PipelineStageFlags[waitSemaphoresArray.Length];
// if (!initializationFrame)
// {
// waitSemaphoresArray[waitSemaphoreIndex] = imageRenderedSemaphore;
// pipelineStageFlagsArray[waitSemaphoreIndex++] = PipelineStageFlags.AllGraphicsBit;
// }
// var waitSemaphoresSpan = new ReadOnlySpan<Silk.NET.Vulkan.Semaphore>(waitSemaphoresArray);
// var pipelineStageFlagsSpan = new ReadOnlySpan<PipelineStageFlags>(pipelineStageFlagsArray);
// fixed (PipelineStageFlags* pPipelineStageFlags = pipelineStageFlagsSpan)
// {
// fixed (Silk.NET.Vulkan.Semaphore* pWaitSemaphoresSpan = waitSemaphoresSpan)
// {
// var submitInfo = new SubmitInfo
// {
// SType = StructureType.SubmitInfo,
// CommandBufferCount = 1,
// PCommandBuffers = &commandBuffer,
// PSignalSemaphores = &imageExportedSemaphore,
// SignalSemaphoreCount = 1,
// PWaitSemaphores = pWaitSemaphoresSpan,
// PWaitDstStageMask = pPipelineStageFlags,
// WaitSemaphoreCount = checked((uint)waitSemaphoresArray.Length)
// };
// // Console.WriteLine("Import 18");
// _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, imageCapturedFence).ThrowOnError();
// }
// }
ReadOnlySpan<PipelineStageFlags> pipelineStageFlags = [PipelineStageFlags.AllGraphicsBit];
fixed (PipelineStageFlags* pPipelineStageFlags = pipelineStageFlags)
{
SType = StructureType.SubmitInfo,
CommandBufferCount = 1,
PCommandBuffers = &commandBuffers,
PSignalSemaphores = &renderFinished,
SignalSemaphoreCount = 1
};
ReadOnlySpan<Silk.NET.Vulkan.Semaphore> waitSemaphores =
[
imageRenderedSemaphore
];
var submitInfo = new SubmitInfo
{
SType = StructureType.SubmitInfo,
CommandBufferCount = 1,
PCommandBuffers = &commandBuffer,
PSignalSemaphores = &imageExportedSemaphore,
SignalSemaphoreCount = 1,
PWaitSemaphores = initializationFrame ? null : &imageRenderedSemaphore,
WaitSemaphoreCount = initializationFrame ? 0u : 1u,
PWaitDstStageMask = initializationFrame ? null : pPipelineStageFlags
};
context.Api.QueueSubmit(context.Queue, 1, &submitInfo, fence).ThrowOnError();
context.Api.WaitForFences(context.Device, 1, &fence, true, ulong.MaxValue).ThrowOnError();
context.Api.ResetFences(context.Device, 1, &fence).ThrowOnError();
// Console.WriteLine("Import 18");
_context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, new Silk.NET.Vulkan.Fence(UIntPtr.Zero)).ThrowOnError();
}
return new VkImage()
// Console.WriteLine("Import 19");
return new VulkanExportedImage
{
Width = width,
Height = height,
Fd = vulkanFd,
MemorySize = exportMemoryReqirements.Size
ImageExportedSemaphoreFd = imageExportedSemaphoreFd,
ImageRenderedSemaphoreFd = imageRenderedSemaphoreFd,
OrignalFd = imageFdDuplicate,
ImageWidth = width,
ImageHeight = height,
ImageFd = vulkanImageFd,
ImageMemorySize = exportMemoryReqirements.Size,
WaitSemaphoreFd = imageExportedSemaphoreFd,
SignalSemaphoreFd = imageRenderedSemaphoreFd,
Resources = new VulkanImageResources
{
ImportImage = importImage,
ImportMemory = importMemory,
ExportImage = exportImage,
ExportMemory = exportMemory
}
};
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
// Console.WriteLine(e.Message);
// Console.WriteLine(e.StackTrace);
throw;
}
}
unsafe public void ClearResource(VulkanExportedImage image)
{
_context.Api.DestroyImage(_context.Device, image.Resources.ImportImage, null);
_context.Api.FreeMemory(_context.Device, image.Resources.ImportMemory, null);
_context.Api.DestroyImage(_context.Device, image.Resources.ExportImage, null);
_context.Api.FreeMemory(_context.Device, image.Resources.ExportMemory, null);
close(image.OrignalFd);
close(image.ImageFd);
close(image.ImageExportedSemaphoreFd);
close(image.ImageRenderedSemaphoreFd);
}
unsafe public (Image, MemoryRequirements, DeviceMemory) ImportImage(int fd, int width, int height, int stride)
{
var subresourceLayout = new SubresourceLayout
@@ -202,8 +291,8 @@ class VkRenderer
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);
_context.Api.CreateImage(_context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
_context.Api.GetImageMemoryRequirements(_context.Device, image, out var memoryReqirements);
var importMemoryFdInfoKHR = new ImportMemoryFdInfoKHR
{
@@ -218,8 +307,8 @@ class VkRenderer
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();
_context.Api.AllocateMemory(_context.Device, &memoryAllocateInfo, null, out var memory).ThrowOnError();
_context.Api.BindImageMemory(_context.Device, image, memory, 0).ThrowOnError();
return (image, memoryReqirements, memory);
}
@@ -250,8 +339,8 @@ class VkRenderer
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);
_context.Api.CreateImage(_context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
_context.Api.GetImageMemoryRequirements(_context.Device, image, out var memoryReqirements);
var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR
{
@@ -265,15 +354,15 @@ class VkRenderer
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();
_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);
_context!.Api.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, out var memProps);
for (int i = 0; i < memProps.MemoryTypeCount; i++)
if ((typeFilter & (1 << i)) != 0 && (memProps.MemoryTypes[i].PropertyFlags & properties) == properties)
return i;
@@ -299,7 +388,7 @@ class VkRenderer
}
};
context.Api.CmdPipelineBarrier(
_context.Api.CmdPipelineBarrier(
cmd,
PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.TransferBit,
@@ -308,77 +397,56 @@ class VkRenderer
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
{
class PresentedFrame
{
public required ICompositionImportedGpuImage Image { get; init; }
public required ICompositionImportedGpuSemaphore Wait { get; init; }
public required ICompositionImportedGpuSemaphore Signal { get; init; }
public required VulkanExportedImage Vulkan { get; init; }
public async ValueTask DisposeAsync(VulkanRenderer? _renerer)
{
Dispatcher.UIThread.Invoke(() =>
{
try
{
Image.DisposeAsync().AsTask();
Wait.DisposeAsync().AsTask();
Signal.DisposeAsync().AsTask();
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}\n{ex.StackTrace}");
}
});
_renerer?.ClearResource(Vulkan);
}
}
private readonly ILogger<CameraView> _logger;
private bool _initialized = false;
private bool _initializationFrame = true;
private Compositor? _compositor;
private CompositionSurfaceVisual? _visual;
private ICompositionGpuInterop? _gpuInterop;
private CompositionDrawingSurface? Surface { get; set; }
private CompositionDrawingSurface? _surface;
private VulkanRenderer? _renerer;
private VulkanExportedImage? _latestImage;
private PresentedFrame? _lastFrame;
private bool _compositionRequested;
private VkRenderer? _renerer;
private VkImage? _latestImage;
private System.Timers.Timer? _updateTimer;
public CameraView()
{ }
public CameraView(ILogger<CameraView> logger)
{
_logger = logger;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
@@ -416,7 +484,7 @@ public partial class CameraView : Control
{
_updateTimer?.Stop();
_updateTimer?.Dispose();
Surface?.Dispose();
_surface?.Dispose();
}
_initialized = false;
@@ -429,17 +497,18 @@ public partial class CameraView : Control
var selfVisual = ElementComposition.GetElementVisual(this)!;
_compositor = selfVisual.Compositor;
Surface = _compositor.CreateDrawingSurface();
_surface = _compositor.CreateDrawingSurface();
_visual = _compositor.CreateSurfaceVisual();
_visual.Surface = Surface;
_visual.Surface = _surface;
ElementComposition.SetElementChildVisual(this, _visual);
_gpuInterop = await _compositor.TryGetCompositionGpuInterop();
_renerer = new VkRenderer();
_renerer = new VulkanRenderer();
_initialized = true;
_logger.LogInformation("{} initialized", nameof(CameraView));
}
catch (Exception e)
catch (Exception ex)
{
Console.WriteLine($"Error while initialization: \n{e.Message}\n{e.StackTrace}");
_logger.LogError(ex, "Error while {} initialization", nameof(CameraView));
}
}
@@ -453,73 +522,52 @@ public partial class CameraView : Control
base.OnPropertyChanged(change);
}
private void RenderToSurface(VkImage image)
private void RenderToSurface(VulkanExportedImage image)
{
var (handle, properties, semaphore) = image.Export();
var (handle, properties, waitSemaphore, signalSemaphore) = image.Export();
var imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(false));
imageRenderedSemaphore.ImportCompleted.ContinueWith(t =>
var imageExportedSemaphore = _gpuInterop!.ImportSemaphore(waitSemaphore);
imageExportedSemaphore.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);
}
}
if (t.IsCompletedSuccessfully)
return;
_logger.LogError(t.Exception, "Error while imageExported semaphore import");
});
var renderCompletedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(true));
renderCompletedSemaphore.ImportCompleted.ContinueWith(t =>
var imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(signalSemaphore);
imageExportedSemaphore.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);
}
}
if (t.IsCompletedSuccessfully)
return;
_logger.LogError(t.Exception, "Error while imageRendered semaphore import");
});
Console.WriteLine("ImportImage");
var importedImage = _gpuInterop!.ImportImage(handle, properties);
importedImage.ImportCompleted.ContinueWith(t =>
imageExportedSemaphore.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);
}
}
if (t.IsCompletedSuccessfully)
return;
_logger.LogError(t.Exception, "Error while image import");
});
var frame = new PresentedFrame
{
Image = importedImage,
Wait = imageExportedSemaphore,
Signal = imageRenderedSemaphore,
Vulkan = image
};
Dispatcher.UIThread.Invoke(async () =>
{
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} ");
foreach (var e in t.Exception.InnerExceptions)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
});
}
);
await _surface!.UpdateWithSemaphoresAsync(importedImage, imageExportedSemaphore, imageRenderedSemaphore).ContinueWith(t =>
{
_updateTimer?.Start();
_lastFrame?.DisposeAsync(_renerer);
_lastFrame = frame;
if (t.IsCompletedSuccessfully)
return;
_logger.LogError(t.Exception, "Error while image update");
});
});
}
private void OnComposition()
{
@@ -533,18 +581,32 @@ public partial class CameraView : Control
}
private void Update(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null)
{
// Console.WriteLine("Update 1");
if (!_initialized || _compositor is null)
return;
if (frameBuffer is not null && streamConfiguration is not null && _renerer is not null)
if (frameBuffer is not null &&
frameBuffer.Planes.Count() == 1 &&
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), checked((int)streamConfiguration.Stride));
// Console.WriteLine("Update 2");
// var fenceFd = frameBuffer.ReleaseFence()?.Fd?.Release();
var image = _renerer.Import(
frameBuffer.Planes.First().Fd.Get(),
checked((int)streamConfiguration.Size.Width),
checked((int)streamConfiguration.Size.Height),
checked((int)streamConfiguration.Stride),
null, // fenceFd,
_initializationFrame);
// _renerer.ClearResource(image);
// Console.WriteLine("Update 3");
_initializationFrame = false;
_latestImage = image;
}
if (_compositionRequested)
return;
_compositionRequested = true;
_compositor?.RequestCompositionUpdate(OnComposition);
}
+4 -2
View File
@@ -5,6 +5,8 @@
xmlns:local="using:GSS2.Test.Views"
x:DataType="vm:MainViewModel"
x:Class="GSS2.Test.Views.MainView">
<!-- <ContentControl Content="{Binding CameraViewModel}"/> -->
<ContentControl Content="{Binding ResourcesViewModel}"/>
<StackPanel Orientation="Vertical">
<ContentControl Content="{Binding CameraViewModel}" Width="200" Height="200"/>
<ContentControl Content="{Binding ResourcesViewModel}"/>
</StackPanel>
</UserControl>
+15 -15
View File
@@ -11,7 +11,7 @@
Series="{Binding CpuUsageSeries}"
YAxes="{Binding CpuUsageSeriesYAxes}"
XAxes="{Binding CpuUsageSeriesXAxes}"
Height="300"
Height="150"
ZoomMode="None"
EasingFunction="{x:Null}" />
<lvc:CartesianChart
@@ -20,7 +20,7 @@
Series="{Binding CpuTemperatureSeries}"
YAxes="{Binding CpuTemperatureSeriesYAxes}"
XAxes="{Binding CpuTemperatureSeriesXAxes}"
Height="300"
Height="150"
ZoomMode="None"
EasingFunction="{x:Null}" />
<lvc:CartesianChart
@@ -29,7 +29,7 @@
Series="{Binding MemoryUsageSeries}"
YAxes="{Binding MemoryUsageSeriesYAxes}"
XAxes="{Binding MemoryUsageSeriesXAxes}"
Height="300"
Height="150"
ZoomMode="None"
EasingFunction="{x:Null}" />
@@ -44,39 +44,39 @@
<lvc:PieChart
Grid.Row="0"
Grid.Column="1"
Height="120"
Width="120"
Height="60"
Width="60"
Series="{Binding Cpu0UsagePieSeries}" />
<lvc:PieChart
Grid.Row="0"
Grid.Column="2"
Height="120"
Width="120"
Height="60"
Width="60"
Series="{Binding Cpu1UsagePieSeries}" />
<lvc:PieChart
Grid.Row="1"
Grid.Column="1"
Height="120"
Width="120"
Height="60"
Width="60"
Series="{Binding Cpu2UsagePieSeries}" />
<lvc:PieChart
Grid.Row="1"
Grid.Column="2"
Height="120"
Width="120"
Height="60"
Width="60"
Series="{Binding Cpu3UsagePieSeries}" />
</Grid>
<lvc:PieChart
Grid.Row="1"
Grid.Column="1"
Height="300"
Width="300"
Height="150"
Width="150"
Series="{Binding CpuTemperaturePieSeries}" />
<lvc:PieChart
Grid.Row="2"
Grid.Column="1"
Height="300"
Width="300"
Height="150"
Width="150"
Series="{Binding MemoryUsagePieSeries}" />
</Grid>
</UserControl>
+56 -22
View File
@@ -19,8 +19,13 @@ unsafe public class VulkanContext : IDisposable
public Device Device { get; init; }
public uint QueueFamilyIndex { get; init; }
public Queue Queue { get; init; }
public Semaphore ImageAvailableSemaphore { get; init; }
public Semaphore RenderFinishedSemaphore { get; init; }
public Semaphore ImageExportedSemaphore { get; init; }
public Semaphore ImageRenderedSemaphore { get; init; }
public CommandPool CommandPool { get; init; }
public CommandBuffer CommandBuffer { get; init; }
public KhrExternalMemoryFd KhrExternalMemoryFd { get; init; }
public KhrExternalSemaphoreFd KhrExternalSemaphoreFd { get; init; }
public KhrExternalFenceFd KhrExternalFenceFd { get; init; }
public VulkanContext(string applicationName, string engineName)
{
@@ -30,42 +35,42 @@ unsafe public class VulkanContext : IDisposable
[
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_fence_capabilities",
"VK_KHR_external_semaphore_capabilities",
"VK_EXT_debug_utils"
];
List<string> instanceLayers =
[
];
List<string> deviceExtensions =
[
"VK_KHR_external_memory",
"VK_KHR_external_memory_fd",
"VK_KHR_external_semaphore",
"VK_KHR_external_semaphore_fd"
"VK_KHR_external_semaphore_fd",
"VK_KHR_external_fence",
"VK_KHR_external_fence_fd"
];
Instance = CreateInstance(Api, applicationName, engineName, instanceExtensions, instanceLayers);
PhysicalDevice = FindPhysicalDevice(Api, Instance, deviceExtensions);
(Device, QueueFamilyIndex) = CreateDevice(Api, Instance, PhysicalDevice, deviceExtensions);
Queue = CreateQueue(Api, Device, QueueFamilyIndex);
ImageAvailableSemaphore = CreateSemaphore(Api, Device);
RenderFinishedSemaphore = CreateSemaphore(Api, Device);
}
ImageExportedSemaphore = CreateSemaphore(Api, Device);
ImageRenderedSemaphore = CreateSemaphore(Api, Device);
CommandPool = CreateCommandPool(Api, Device, QueueFamilyIndex);
CommandBuffer = CreateCommandBuffer(Api, Device, CommandPool);
public int ExportSemaphoreFd(Semaphore semaphore)
{
if (!Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(Instance, Device, out var extension))
throw new InvalidOperationException($"No {KhrExternalSemaphoreFd.ExtensionName} device extension");
var info = new SemaphoreGetFdInfoKHR()
{
SType = StructureType.SemaphoreGetFDInfoKhr,
Semaphore = semaphore,
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
extension.GetSemaphoreF(Device, in info, out var fd).ThrowOnError();
return fd;
if (!Api.TryGetDeviceExtension<KhrExternalMemoryFd>(Instance, Device, out var khrExternalMemoryFd))
throw new Exception("KhrExternalMemoryFd import error");
KhrExternalMemoryFd = khrExternalMemoryFd;
if (!Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(Instance, Device, out var khrExternalSemaphoreFd))
throw new Exception("KhrExternalMemoryFd import error");
KhrExternalSemaphoreFd = khrExternalSemaphoreFd;
if (!Api.TryGetDeviceExtension<KhrExternalFenceFd>(Instance, Device, out var khrExternalFenceFd))
throw new Exception("KhrExternalFenceFd import error");
KhrExternalFenceFd = khrExternalFenceFd;
}
private static Instance CreateInstance(Vk api, string applicationName, string engineName, List<string> extentions, List<string> layers)
@@ -186,6 +191,32 @@ unsafe public class VulkanContext : IDisposable
api.CreateSemaphore(device, in semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
return semaphore;
}
private static CommandPool CreateCommandPool(Vk api, Device device, uint queueFamilyIndex)
{
var commandPoolCreateInfo = new CommandPoolCreateInfo
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
QueueFamilyIndex = queueFamilyIndex
};
api.CreateCommandPool(device, &commandPoolCreateInfo, null, out var commandPool).ThrowOnError();
return commandPool;
}
private static CommandBuffer CreateCommandBuffer(Vk api, Device device, CommandPool commandPool)
{
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
{
SType = StructureType.CommandBufferAllocateInfo,
CommandPool = commandPool,
Level = CommandBufferLevel.Primary,
CommandBufferCount = 1
};
api.AllocateCommandBuffers(device, &commandBufferAllocateInfo, out var commandBuffer).ThrowOnError();
return commandBuffer;
}
protected virtual void Dispose(bool disposing)
{
@@ -195,8 +226,11 @@ unsafe public class VulkanContext : IDisposable
{
if (Api is null)
return;
Api.DestroySemaphore(Device, ImageAvailableSemaphore, null);
Api.DestroySemaphore(Device, RenderFinishedSemaphore, null);
Api.DestroySemaphore(Device, ImageExportedSemaphore, null);
Api.DestroySemaphore(Device, ImageRenderedSemaphore, null);
Api.FreeCommandBuffers(Device, CommandPool, [CommandBuffer]);
Api.DestroyCommandPool(Device, CommandPool, null);
Api.DestroyDevice(Device, null);
}
_disposedValue = true;
}