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="OpenCvSharp4.runtime.linux-arm" Version="4.11.0.20250506" />
<PackageReference Include="System.Device.Gpio" Version="4.0.1" /> <PackageReference Include="System.Device.Gpio" Version="4.0.1" />
<PackageReference Include="Iot.Device.Bindings" 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>
<ItemGroup> <ItemGroup>
-1
View File
@@ -716,7 +716,6 @@ public class CameraService : IDisposable
_camera.RequestCompleted += RequestCompleted; _camera.RequestCompleted += RequestCompleted;
result = _camera.QueueRequest(request); result = _camera.QueueRequest(request);
Console.WriteLine(result);
if (result < 0) if (result < 0)
{ {
_camera.RequestCompleted -= RequestCompleted; _camera.RequestCompleted -= RequestCompleted;
+3 -3
View File
@@ -11,7 +11,7 @@ public static class DependencyInjectionHelper
.AddSingleton<MainView>() .AddSingleton<MainView>()
.AddSingleton<MainViewModel>() .AddSingleton<MainViewModel>()
.AddSingleton<ResourcesView>() .AddSingleton<ResourcesView>()
.AddSingleton<ResourcesViewModel>(); .AddSingleton<ResourcesViewModel>()
// .AddSingleton<CameraView>() .AddSingleton<CameraView>()
// .AddSingleton<CameraViewModel>(); .AddSingleton<CameraViewModel>();
} }
+1 -1
View File
@@ -13,7 +13,7 @@ public class CameraViewModel : ViewModelBase
{ {
_cameraService = cameraService; _cameraService = cameraService;
_logger = logger; _logger = logger;
_logger.LogInformation("Initialized"); _logger.LogInformation("{} initialized", nameof(CameraViewModel));
} }
public async Task<(FrameBuffer, StreamConfiguration)?> CaptureImage(int bufferId) 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 CameraViewModel _cameraViewModel;
[ObservableProperty] private ResourcesViewModel _resourcesViewModel; [ObservableProperty] private ResourcesViewModel _resourcesViewModel;
public MainViewModel(ILogger<MainViewModel> logger, ResourcesViewModel resourcesViewModel) public MainViewModel(ILogger<MainViewModel> logger, CameraViewModel cameraViewModel, ResourcesViewModel resourcesViewModel)
{ {
_logger = logger; _logger = logger;
// CameraViewModel = cameraViewModel; CameraViewModel = cameraViewModel;
ResourcesViewModel = resourcesViewModel; ResourcesViewModel = resourcesViewModel;
_logger.LogInformation("{} initialized", nameof(MainViewModel)); _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<Axis> _memoryUsageSeriesXAxes;
[ObservableProperty] private ObservableCollection<ISeries> _memoryUsagePieSeries; [ObservableProperty] private ObservableCollection<ISeries> _memoryUsagePieSeries;
[ObservableProperty] private int _intervalMs = 500; [ObservableProperty] private int _intervalMs = 500;
[ObservableProperty] private int _chartsHistorySize = 120; [ObservableProperty] private int _chartsHistorySize = 120;
private readonly System.Timers.Timer _updateTimer; private readonly System.Timers.Timer _updateTimer;
@@ -409,6 +408,7 @@ public partial class ResourcesViewModel : ViewModelBase, IDisposable
memoryUsageValues.Add(Math.Round(snapshot.MemoryUsage.UsedMB, 2)); memoryUsageValues.Add(Math.Round(snapshot.MemoryUsage.UsedMB, 2));
while (memoryUsageValues.Count > ChartsHistorySize) while (memoryUsageValues.Count > ChartsHistorySize)
memoryUsageValues.RemoveAt(0); 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) if (MemoryUsageSeriesYAxes.Count() == 1)
MemoryUsageSeriesYAxes.First().MaxLimit = Math.Round(snapshot.MemoryUsage.TotalMB); MemoryUsageSeriesYAxes.First().MaxLimit = Math.Round(snapshot.MemoryUsage.TotalMB);
+274 -212
View File
@@ -1,105 +1,128 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Threading; using Avalonia.Threading;
using GSS2.Test.ViewModels; using GSS2.Test.ViewModels;
using GSS2.Vulkan; using GSS2.Vulkan;
using LibCameraSharp; using LibCameraSharp;
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; using Image = Silk.NET.Vulkan.Image;
namespace GSS2.Test.Views; 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 (IPlatformHandle imageHandle, PlatformGraphicsExternalImageProperties imageProperties, IPlatformHandle waitSemaphoreHandle, IPlatformHandle signalSemaphoreHandle) Export()
{
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()
{ {
return ( return (
new PlatformHandle(new IntPtr(Fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor), new PlatformHandle(new IntPtr(ImageFd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor),
new PlatformGraphicsExternalImageProperties new PlatformGraphicsExternalImageProperties
{ {
Width = Width, Width = ImageWidth,
Height = Height, Height = ImageHeight,
Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm, 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); [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); // Console.WriteLine("Import 1");
return new PlatformHandle(new IntPtr(fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); var imageFdDuplicate = dup(imageFd);
} // Console.WriteLine("Import 2");
unsafe public VkImage ImportOrUpdate(int fd, int width, int height, int stride)
{
var fdDup = dup(fd);
try try
{ {
FenceCreateInfo fenceInfo = new FenceCreateInfo // Console.WriteLine("Import 3");
{ var imageExportedSemaphore = _context.ImageExportedSemaphore;
SType = StructureType.FenceCreateInfo var imageRenderedSemaphore = _context.ImageRenderedSemaphore;
}; var commandBuffer = _context.CommandBuffer;
context.Api.CreateFence(context.Device, &fenceInfo, null, out var fence).ThrowOnError();
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 (exportImage, exportMemoryReqirements, exportMemory) = CreateExportImage(width, height);
var commandPoolCreateInfo = new CommandPoolCreateInfo // Console.WriteLine("Import 11");
{
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 var commandBufferBeginInfo = new CommandBufferBeginInfo
{ {
SType = StructureType.CommandBufferBeginInfo, SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit Flags = CommandBufferUsageFlags.OneTimeSubmitBit
}; };
context.Api.BeginCommandBuffer(commandBuffers, &commandBufferBeginInfo).ThrowOnError(); _context.Api.BeginCommandBuffer(_context.CommandBuffer, &commandBufferBeginInfo).ThrowOnError();
TransitionImageLayout(commandBuffers, importImage, ImageLayout.Undefined, ImageLayout.TransferSrcOptimal); // Console.WriteLine("Import 12");
TransitionImageLayout(commandBuffers, exportImage, ImageLayout.Undefined, ImageLayout.TransferDstOptimal); 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 SrcSubresource = new ImageSubresourceLayers
{ {
@@ -119,55 +142,121 @@ class VkRenderer
DstOffset = new Offset3D(0, 0, 0), DstOffset = new Offset3D(0, 0, 0),
Extent = new Extent3D((uint)width, (uint)height, 1) 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("Import 16");
Console.WriteLine("KhrExternalMemoryFd import success");
else
throw new Exception("KhrExternalMemoryFd import error");
var memoryGetFdInfoKHR = new MemoryGetFdInfoKHR var memoryGetFdInfoKHR = new MemoryGetFdInfoKHR
{ {
SType = StructureType.MemoryGetFDInfoKhr, SType = StructureType.MemoryGetFDInfoKhr,
Memory = exportMemory, Memory = exportMemory,
HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
}; };
khrExternalMemoryFd.GetMemoryF(context.Device, &memoryGetFdInfoKHR, out var vulkanFd).ThrowOnError(); _context.KhrExternalMemoryFd.GetMemoryF(_context.Device, &memoryGetFdInfoKHR, out var vulkanImageFd).ThrowOnError();
Console.WriteLine($"Image export success {vulkanFd}");
var renderFinished = context.RenderFinishedSemaphore; // 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)
{
ReadOnlySpan<Silk.NET.Vulkan.Semaphore> waitSemaphores =
[
imageRenderedSemaphore
];
var submitInfo = new SubmitInfo var submitInfo = new SubmitInfo
{ {
SType = StructureType.SubmitInfo, SType = StructureType.SubmitInfo,
CommandBufferCount = 1, CommandBufferCount = 1,
PCommandBuffers = &commandBuffers, PCommandBuffers = &commandBuffer,
PSignalSemaphores = &renderFinished, PSignalSemaphores = &imageExportedSemaphore,
SignalSemaphoreCount = 1 SignalSemaphoreCount = 1,
PWaitSemaphores = initializationFrame ? null : &imageRenderedSemaphore,
WaitSemaphoreCount = initializationFrame ? 0u : 1u,
PWaitDstStageMask = initializationFrame ? null : pPipelineStageFlags
}; };
context.Api.QueueSubmit(context.Queue, 1, &submitInfo, fence).ThrowOnError(); // Console.WriteLine("Import 18");
context.Api.WaitForFences(context.Device, 1, &fence, true, ulong.MaxValue).ThrowOnError(); _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, new Silk.NET.Vulkan.Fence(UIntPtr.Zero)).ThrowOnError();
context.Api.ResetFences(context.Device, 1, &fence).ThrowOnError(); }
return new VkImage() // Console.WriteLine("Import 19");
return new VulkanExportedImage
{ {
Width = width, ImageExportedSemaphoreFd = imageExportedSemaphoreFd,
Height = height, ImageRenderedSemaphoreFd = imageRenderedSemaphoreFd,
Fd = vulkanFd, OrignalFd = imageFdDuplicate,
MemorySize = exportMemoryReqirements.Size 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) catch (Exception e)
{ {
Console.WriteLine(e.Message); // Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace); // Console.WriteLine(e.StackTrace);
throw; 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) unsafe public (Image, MemoryRequirements, DeviceMemory) ImportImage(int fd, int width, int height, int stride)
{ {
var subresourceLayout = new SubresourceLayout var subresourceLayout = new SubresourceLayout
@@ -202,8 +291,8 @@ class VkRenderer
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.CreateImage(_context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memoryReqirements); _context.Api.GetImageMemoryRequirements(_context.Device, image, out var memoryReqirements);
var importMemoryFdInfoKHR = new ImportMemoryFdInfoKHR var importMemoryFdInfoKHR = new ImportMemoryFdInfoKHR
{ {
@@ -218,8 +307,8 @@ class VkRenderer
MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)), MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
PNext = &importMemoryFdInfoKHR, PNext = &importMemoryFdInfoKHR,
}; };
context.Api.AllocateMemory(context.Device, &memoryAllocateInfo, null, out var memory).ThrowOnError(); _context.Api.AllocateMemory(_context.Device, &memoryAllocateInfo, null, out var memory).ThrowOnError();
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError(); _context.Api.BindImageMemory(_context.Device, image, memory, 0).ThrowOnError();
return (image, memoryReqirements, memory); return (image, memoryReqirements, memory);
} }
@@ -250,8 +339,8 @@ class VkRenderer
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.CreateImage(_context.Device, &imageCreateInfo, null, out var image).ThrowOnError();
context.Api.GetImageMemoryRequirements(context.Device, image, out var memoryReqirements); _context.Api.GetImageMemoryRequirements(_context.Device, image, out var memoryReqirements);
var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR var exportMemoryFdInfoKHR = new ExportMemoryAllocateInfoKHR
{ {
@@ -265,15 +354,15 @@ class VkRenderer
MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)), MemoryTypeIndex = checked((uint)FindMemoryType(memoryReqirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)),
PNext = &exportMemoryFdInfoKHR, PNext = &exportMemoryFdInfoKHR,
}; };
context.Api.AllocateMemory(context.Device, &exportAllocateInfo, 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(); _context.Api.BindImageMemory(_context.Device, image, memory, 0).ThrowOnError();
return (image, memoryReqirements, memory); return (image, memoryReqirements, memory);
} }
private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties) 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++) for (int i = 0; i < memProps.MemoryTypeCount; i++)
if ((typeFilter & (1 << i)) != 0 && (memProps.MemoryTypes[i].PropertyFlags & properties) == properties) if ((typeFilter & (1 << i)) != 0 && (memProps.MemoryTypes[i].PropertyFlags & properties) == properties)
return i; return i;
@@ -299,7 +388,7 @@ class VkRenderer
} }
}; };
context.Api.CmdPipelineBarrier( _context.Api.CmdPipelineBarrier(
cmd, cmd,
PipelineStageFlags.TopOfPipeBit, PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.TransferBit, PipelineStageFlags.TransferBit,
@@ -308,77 +397,56 @@ class VkRenderer
0, null, 0, null,
1, &barrier); 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 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 _initialized = false;
private bool _initializationFrame = true;
private Compositor? _compositor; private Compositor? _compositor;
private CompositionSurfaceVisual? _visual; private CompositionSurfaceVisual? _visual;
private ICompositionGpuInterop? _gpuInterop; private ICompositionGpuInterop? _gpuInterop;
private CompositionDrawingSurface? Surface { get; set; } private CompositionDrawingSurface? _surface;
private VulkanRenderer? _renerer;
private VulkanExportedImage? _latestImage;
private PresentedFrame? _lastFrame;
private bool _compositionRequested; private bool _compositionRequested;
private VkRenderer? _renerer;
private VkImage? _latestImage;
private System.Timers.Timer? _updateTimer; private System.Timers.Timer? _updateTimer;
public CameraView() public CameraView(ILogger<CameraView> logger)
{ } {
_logger = logger;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
@@ -416,7 +484,7 @@ public partial class CameraView : Control
{ {
_updateTimer?.Stop(); _updateTimer?.Stop();
_updateTimer?.Dispose(); _updateTimer?.Dispose();
Surface?.Dispose(); _surface?.Dispose();
} }
_initialized = false; _initialized = false;
@@ -429,17 +497,18 @@ public partial class CameraView : Control
var selfVisual = ElementComposition.GetElementVisual(this)!; var selfVisual = ElementComposition.GetElementVisual(this)!;
_compositor = selfVisual.Compositor; _compositor = selfVisual.Compositor;
Surface = _compositor.CreateDrawingSurface(); _surface = _compositor.CreateDrawingSurface();
_visual = _compositor.CreateSurfaceVisual(); _visual = _compositor.CreateSurfaceVisual();
_visual.Surface = Surface; _visual.Surface = _surface;
ElementComposition.SetElementChildVisual(this, _visual); ElementComposition.SetElementChildVisual(this, _visual);
_gpuInterop = await _compositor.TryGetCompositionGpuInterop(); _gpuInterop = await _compositor.TryGetCompositionGpuInterop();
_renerer = new VkRenderer(); _renerer = new VulkanRenderer();
_initialized = true; _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); 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)); var imageExportedSemaphore = _gpuInterop!.ImportSemaphore(waitSemaphore);
imageRenderedSemaphore.ImportCompleted.ContinueWith(t => imageExportedSemaphore.ImportCompleted.ContinueWith(t =>
{ {
Console.WriteLine($"imageRenderedSemaphore.ImportCompleted {t.Status}"); if (t.IsCompletedSuccessfully)
if (t.Exception is not null) return;
{ _logger.LogError(t.Exception, "Error while imageExported semaphore import");
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 imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(signalSemaphore);
renderCompletedSemaphore.ImportCompleted.ContinueWith(t => imageExportedSemaphore.ImportCompleted.ContinueWith(t =>
{ {
Console.WriteLine($"renderCompletedSemaphore.ImportCompleted {t.Status}"); if (t.IsCompletedSuccessfully)
if (t.Exception is not null) return;
{ _logger.LogError(t.Exception, "Error while imageRendered semaphore import");
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 => imageExportedSemaphore.ImportCompleted.ContinueWith(t =>
{ {
Console.WriteLine($"importedImage.ImportCompleted {t.Status}"); if (t.IsCompletedSuccessfully)
if (t.Exception is not null) return;
{ _logger.LogError(t.Exception, "Error while image import");
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 frame = new PresentedFrame
{
Image = importedImage,
Wait = imageExportedSemaphore,
Signal = imageRenderedSemaphore,
Vulkan = image
};
Dispatcher.UIThread.Invoke(async () => Dispatcher.UIThread.Invoke(async () =>
{ {
await Surface!.UpdateWithSemaphoresAsync(importedImage, renderCompletedSemaphore, imageRenderedSemaphore) await _surface!.UpdateWithSemaphoresAsync(importedImage, imageExportedSemaphore, imageRenderedSemaphore).ContinueWith(t =>
.ContinueWith((t) =>
{ {
Console.WriteLine($"UpdateWithSemaphoresAsync {t.Status}"); _updateTimer?.Start();
if (t.Exception is not null) _lastFrame?.DisposeAsync(_renerer);
{ _lastFrame = frame;
Console.WriteLine($"Error while surface update: \n{t.Exception.Message}\n{t.Exception.StackTrace} "); if (t.IsCompletedSuccessfully)
foreach (var e in t.Exception.InnerExceptions) return;
{ _logger.LogError(t.Exception, "Error while image update");
Console.WriteLine(e.Message); });
Console.WriteLine(e.StackTrace);
}
}
}); });
}
);
} }
private void OnComposition() private void OnComposition()
{ {
@@ -533,18 +581,32 @@ public partial class CameraView : Control
} }
private void Update(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null) private void Update(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null)
{ {
// Console.WriteLine("Update 1");
if (!_initialized || _compositor is null) if (!_initialized || _compositor is null)
return; 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; _latestImage = image;
} }
if (_compositionRequested) if (_compositionRequested)
return; return;
_compositionRequested = true; _compositionRequested = true;
_compositor?.RequestCompositionUpdate(OnComposition); _compositor?.RequestCompositionUpdate(OnComposition);
} }
+3 -1
View File
@@ -5,6 +5,8 @@
xmlns:local="using:GSS2.Test.Views" xmlns:local="using:GSS2.Test.Views"
x:DataType="vm:MainViewModel" x:DataType="vm:MainViewModel"
x:Class="GSS2.Test.Views.MainView"> x:Class="GSS2.Test.Views.MainView">
<!-- <ContentControl Content="{Binding CameraViewModel}"/> --> <StackPanel Orientation="Vertical">
<ContentControl Content="{Binding CameraViewModel}" Width="200" Height="200"/>
<ContentControl Content="{Binding ResourcesViewModel}"/> <ContentControl Content="{Binding ResourcesViewModel}"/>
</StackPanel>
</UserControl> </UserControl>
+15 -15
View File
@@ -11,7 +11,7 @@
Series="{Binding CpuUsageSeries}" Series="{Binding CpuUsageSeries}"
YAxes="{Binding CpuUsageSeriesYAxes}" YAxes="{Binding CpuUsageSeriesYAxes}"
XAxes="{Binding CpuUsageSeriesXAxes}" XAxes="{Binding CpuUsageSeriesXAxes}"
Height="300" Height="150"
ZoomMode="None" ZoomMode="None"
EasingFunction="{x:Null}" /> EasingFunction="{x:Null}" />
<lvc:CartesianChart <lvc:CartesianChart
@@ -20,7 +20,7 @@
Series="{Binding CpuTemperatureSeries}" Series="{Binding CpuTemperatureSeries}"
YAxes="{Binding CpuTemperatureSeriesYAxes}" YAxes="{Binding CpuTemperatureSeriesYAxes}"
XAxes="{Binding CpuTemperatureSeriesXAxes}" XAxes="{Binding CpuTemperatureSeriesXAxes}"
Height="300" Height="150"
ZoomMode="None" ZoomMode="None"
EasingFunction="{x:Null}" /> EasingFunction="{x:Null}" />
<lvc:CartesianChart <lvc:CartesianChart
@@ -29,7 +29,7 @@
Series="{Binding MemoryUsageSeries}" Series="{Binding MemoryUsageSeries}"
YAxes="{Binding MemoryUsageSeriesYAxes}" YAxes="{Binding MemoryUsageSeriesYAxes}"
XAxes="{Binding MemoryUsageSeriesXAxes}" XAxes="{Binding MemoryUsageSeriesXAxes}"
Height="300" Height="150"
ZoomMode="None" ZoomMode="None"
EasingFunction="{x:Null}" /> EasingFunction="{x:Null}" />
@@ -44,39 +44,39 @@
<lvc:PieChart <lvc:PieChart
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Height="120" Height="60"
Width="120" Width="60"
Series="{Binding Cpu0UsagePieSeries}" /> Series="{Binding Cpu0UsagePieSeries}" />
<lvc:PieChart <lvc:PieChart
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Height="120" Height="60"
Width="120" Width="60"
Series="{Binding Cpu1UsagePieSeries}" /> Series="{Binding Cpu1UsagePieSeries}" />
<lvc:PieChart <lvc:PieChart
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Height="120" Height="60"
Width="120" Width="60"
Series="{Binding Cpu2UsagePieSeries}" /> Series="{Binding Cpu2UsagePieSeries}" />
<lvc:PieChart <lvc:PieChart
Grid.Row="1" Grid.Row="1"
Grid.Column="2" Grid.Column="2"
Height="120" Height="60"
Width="120" Width="60"
Series="{Binding Cpu3UsagePieSeries}" /> Series="{Binding Cpu3UsagePieSeries}" />
</Grid> </Grid>
<lvc:PieChart <lvc:PieChart
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Height="300" Height="150"
Width="300" Width="150"
Series="{Binding CpuTemperaturePieSeries}" /> Series="{Binding CpuTemperaturePieSeries}" />
<lvc:PieChart <lvc:PieChart
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Height="300" Height="150"
Width="300" Width="150"
Series="{Binding MemoryUsagePieSeries}" /> Series="{Binding MemoryUsagePieSeries}" />
</Grid> </Grid>
</UserControl> </UserControl>
+55 -21
View File
@@ -19,8 +19,13 @@ unsafe public class VulkanContext : IDisposable
public Device Device { get; init; } public Device Device { get; init; }
public uint QueueFamilyIndex { get; init; } public uint QueueFamilyIndex { get; init; }
public Queue Queue { get; init; } public Queue Queue { get; init; }
public Semaphore ImageAvailableSemaphore { get; init; } public Semaphore ImageExportedSemaphore { get; init; }
public Semaphore RenderFinishedSemaphore { 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) public VulkanContext(string applicationName, string engineName)
{ {
@@ -30,6 +35,7 @@ unsafe public class VulkanContext : IDisposable
[ [
"VK_KHR_get_physical_device_properties2", "VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities", "VK_KHR_external_memory_capabilities",
"VK_KHR_external_fence_capabilities",
"VK_KHR_external_semaphore_capabilities", "VK_KHR_external_semaphore_capabilities",
"VK_EXT_debug_utils" "VK_EXT_debug_utils"
]; ];
@@ -42,30 +48,29 @@ unsafe public class VulkanContext : IDisposable
"VK_KHR_external_memory", "VK_KHR_external_memory",
"VK_KHR_external_memory_fd", "VK_KHR_external_memory_fd",
"VK_KHR_external_semaphore", "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); Instance = CreateInstance(Api, applicationName, engineName, instanceExtensions, instanceLayers);
PhysicalDevice = FindPhysicalDevice(Api, Instance, deviceExtensions); PhysicalDevice = FindPhysicalDevice(Api, Instance, deviceExtensions);
(Device, QueueFamilyIndex) = CreateDevice(Api, Instance, PhysicalDevice, deviceExtensions); (Device, QueueFamilyIndex) = CreateDevice(Api, Instance, PhysicalDevice, deviceExtensions);
Queue = CreateQueue(Api, Device, QueueFamilyIndex); Queue = CreateQueue(Api, Device, QueueFamilyIndex);
ImageAvailableSemaphore = CreateSemaphore(Api, Device); ImageExportedSemaphore = CreateSemaphore(Api, Device);
RenderFinishedSemaphore = 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<KhrExternalMemoryFd>(Instance, Device, out var khrExternalMemoryFd))
{ throw new Exception("KhrExternalMemoryFd import error");
if (!Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(Instance, Device, out var extension)) KhrExternalMemoryFd = khrExternalMemoryFd;
throw new InvalidOperationException($"No {KhrExternalSemaphoreFd.ExtensionName} device extension"); if (!Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(Instance, Device, out var khrExternalSemaphoreFd))
throw new Exception("KhrExternalMemoryFd import error");
var info = new SemaphoreGetFdInfoKHR() KhrExternalSemaphoreFd = khrExternalSemaphoreFd;
{ if (!Api.TryGetDeviceExtension<KhrExternalFenceFd>(Instance, Device, out var khrExternalFenceFd))
SType = StructureType.SemaphoreGetFDInfoKhr, throw new Exception("KhrExternalFenceFd import error");
Semaphore = semaphore, KhrExternalFenceFd = khrExternalFenceFd;
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
extension.GetSemaphoreF(Device, in info, out var fd).ThrowOnError();
return fd;
} }
private static Instance CreateInstance(Vk api, string applicationName, string engineName, List<string> extentions, List<string> layers) 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(); api.CreateSemaphore(device, in semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
return semaphore; 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) protected virtual void Dispose(bool disposing)
{ {
@@ -195,8 +226,11 @@ unsafe public class VulkanContext : IDisposable
{ {
if (Api is null) if (Api is null)
return; return;
Api.DestroySemaphore(Device, ImageAvailableSemaphore, null); Api.DestroySemaphore(Device, ImageExportedSemaphore, null);
Api.DestroySemaphore(Device, RenderFinishedSemaphore, null); Api.DestroySemaphore(Device, ImageRenderedSemaphore, null);
Api.FreeCommandBuffers(Device, CommandPool, [CommandBuffer]);
Api.DestroyCommandPool(Device, CommandPool, null);
Api.DestroyDevice(Device, null);
} }
_disposedValue = true; _disposedValue = true;
} }