From dc3c195c2128ee585387804dd0f1e54d579b76ab Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Thu, 29 Jan 2026 12:10:59 +0300 Subject: [PATCH] feat: CameraView + CameraService fences --- GSS2.Core/GSS2.Core.csproj | 2 +- GSS2.Core/Hardware/CameraService.cs | 78 ++++++++------ GSS2.Test/Views/CameraView.axaml.cs | 158 +++++++++++++++------------- GSS2.Vulkan/VulkanExtensions.cs | 2 +- 4 files changed, 136 insertions(+), 104 deletions(-) diff --git a/GSS2.Core/GSS2.Core.csproj b/GSS2.Core/GSS2.Core.csproj index 061e878..3298c1f 100644 --- a/GSS2.Core/GSS2.Core.csproj +++ b/GSS2.Core/GSS2.Core.csproj @@ -21,7 +21,7 @@ - + diff --git a/GSS2.Core/Hardware/CameraService.cs b/GSS2.Core/Hardware/CameraService.cs index ec53e1f..a66060a 100644 --- a/GSS2.Core/Hardware/CameraService.cs +++ b/GSS2.Core/Hardware/CameraService.cs @@ -9,13 +9,15 @@ namespace GSS2.Core.Hardware; public class CameraService : IDisposable { - private static class LibC + public static class LibC { public const int PROT_READ = 0x01; public const int MAP_SHARED = 0x0001; [DllImport("libc", SetLastError = true)] public static extern IntPtr mmap(IntPtr address, ulong length, int prot, int flags, int fd, ulong offset); [DllImport("libc", SetLastError = true)] public static extern int munmap(IntPtr address, ulong length); + [DllImport("libc", SetLastError = true)] public static extern int close(int fd); + [DllImport("libc", SetLastError = true)] public static extern int memfd_create(string name, uint flags); } private sealed class MappedBuffer { @@ -677,53 +679,67 @@ public class CameraService : IDisposable var buffer = buffers.Skip(bufferId % buffers.Count()).First(); var request = _camera.CreateRequest(checked((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())); - var result = request.AddBuffer(_viewFinderStream, buffer); + + int fd = LibC.memfd_create($"buffer_fence_{bufferId % buffers.Count()}", 0x0002U); + if (fd < 0) + { + throw new Exception($"memfd_create error: {Marshal.GetLastPInvokeError()} {Marshal.GetLastPInvokeErrorMessage()}"); + } + Console.WriteLine($"Created fd {fd}"); + var fence = new Fence(new UniqueFD(fd)); + var result = request.AddBuffer(_viewFinderStream, buffer, fence); if (result < 0) + { + var fenceFd = buffer.ReleaseFence()?.Fd?.Release(); + Console.WriteLine($"Request queue failed, closing fd {fenceFd}"); + if (fenceFd is not null) + LibC.close(fenceFd.Value); throw new Exception("Error while request create"); + } // _logger.LogDebug("Request(s) created"); effectiveCancellationToken.ThrowIfCancellationRequested(); - var cts = new TaskCompletionSource(); - void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e) - { - if (request.Handle != e.Request.Handle) - return; + // var cts = new TaskCompletionSource(); + // void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e) + // { + // if (request.Handle != e.Request.Handle) + // return; - var buffer = e.Request.FindBuffer(_viewFinderStream); - if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError || - buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup) - { - // _logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status); - e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers); - var result = _camera.QueueRequest(e.Request); - if (result >= 0) - return; - } + // var buffer = e.Request.FindBuffer(_viewFinderStream); + // if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError || + // buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup) + // { + // // _logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status); + // e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers); + // var result = _camera.QueueRequest(e.Request); + // if (result >= 0) + // return; + // } - // _logger.LogDebug("Request complited: \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", e.Request.Status, e.Request.Cookie, e.Request.Sequence, e.Request.HasPendingBuffers); - // _logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp); - // _logger.LogDebug("Request buffer planes: "); - // foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) - // _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed); - // foreach (var (i, plane) in buffer.Planes.Enumerate()) - // _logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset); + // // _logger.LogDebug("Request complited: \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", e.Request.Status, e.Request.Cookie, e.Request.Sequence, e.Request.HasPendingBuffers); + // // _logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp); + // // _logger.LogDebug("Request buffer planes: "); + // // foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) + // // _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed); + // // foreach (var (i, plane) in buffer.Planes.Enumerate()) + // // _logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset); - effectiveCancellationToken.ThrowIfCancellationRequested(); + // effectiveCancellationToken.ThrowIfCancellationRequested(); - cts.SetResult(); - } + // cts.SetResult(); + // } - _camera.RequestCompleted += RequestCompleted; + // _camera.RequestCompleted += RequestCompleted; result = _camera.QueueRequest(request); if (result < 0) { - _camera.RequestCompleted -= RequestCompleted; + // _camera.RequestCompleted -= RequestCompleted; throw new Exception("Error while request queue"); } // _logger.LogDebug("Request queued"); - await cts.Task.WaitAsync(effectiveCancellationToken); - _camera.RequestCompleted -= RequestCompleted; + // await cts.Task.WaitAsync(effectiveCancellationToken); + // _camera.RequestCompleted -= RequestCompleted; // _logger.LogDebug("Request complited"); return new(buffer, _viewFinderStreamConfiguration); diff --git a/GSS2.Test/Views/CameraView.axaml.cs b/GSS2.Test/Views/CameraView.axaml.cs index fdbd2c8..647f89d 100644 --- a/GSS2.Test/Views/CameraView.axaml.cs +++ b/GSS2.Test/Views/CameraView.axaml.cs @@ -30,6 +30,7 @@ class VulkanExportedImage { public required int ImageExportedSemaphoreFd { get; init; } public required int ImageRenderedSemaphoreFd { get; init; } + public required int? FenceFd { get; init; } public required int OrignalFd { get; init; } public required int ImageFd { get; init; } public required int ImageWidth { get; init; } @@ -91,18 +92,18 @@ class VulkanRenderer }; _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); - // } + 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.OpaqueFDBit + }; + _context.KhrExternalFenceFd.ImportFenceF(_context.Device, &importSemaphoreFdInfoKHR); + } // Console.WriteLine("Import 9"); var (importImage, importMemoryReqirements, importMemory) = ImportImage(imageFdDuplicate, width, height, stride); @@ -160,67 +161,71 @@ class VulkanRenderer _context.KhrExternalMemoryFd.GetMemoryF(_context.Device, &memoryGetFdInfoKHR, out var vulkanImageFd).ThrowOnError(); // 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(waitSemaphoresArray); - // var pipelineStageFlagsSpan = new ReadOnlySpan(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.AllGraphicsBit]; - fixed (PipelineStageFlags* pPipelineStageFlags = pipelineStageFlags) + var waitSemaphoreIndex = 0; + Silk.NET.Vulkan.Semaphore[] waitSemaphoresArray = new Silk.NET.Vulkan.Semaphore[ + !initializationFrame ? 1 : 0 + ]; + PipelineStageFlags[] pipelineStageFlagsArray = new PipelineStageFlags[waitSemaphoresArray.Length]; + if (!initializationFrame) { - ReadOnlySpan 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 - }; - - // Console.WriteLine("Import 18"); - _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, new Silk.NET.Vulkan.Fence(UIntPtr.Zero)).ThrowOnError(); + waitSemaphoresArray[waitSemaphoreIndex] = imageRenderedSemaphore; + pipelineStageFlagsArray[waitSemaphoreIndex++] = PipelineStageFlags.AllGraphicsBit; } + var waitSemaphoresSpan = new ReadOnlySpan(waitSemaphoresArray); + var pipelineStageFlagsSpan = new ReadOnlySpan(pipelineStageFlagsArray); + + _context.Api.Semaphore + + fixed (PipelineStageFlags* pPipelineStageFlagsSpan = pipelineStageFlagsSpan) + { + fixed (Silk.NET.Vulkan.Semaphore* pWaitSemaphoresSpan = waitSemaphoresSpan) + { + var submitInfo = new SubmitInfo + { + SType = StructureType.SubmitInfo, + CommandBufferCount = 1, + PCommandBuffers = &commandBuffer, + PSignalSemaphores = &imageExportedSemaphore, + SignalSemaphoreCount = 1, + PWaitSemaphores = waitSemaphoresArray.Length == 0 ? null : pWaitSemaphoresSpan, + PWaitDstStageMask = waitSemaphoresArray.Length == 0 ? null : pPipelineStageFlagsSpan, + WaitSemaphoreCount = checked((uint)waitSemaphoresArray.Length) + }; + // Console.WriteLine("Import 18"); + Console.WriteLine(imageCapturedFence.Handle); + _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, imageCapturedFence).ThrowOnError(); + } + } + + // ReadOnlySpan pipelineStageFlags = [PipelineStageFlags.AllGraphicsBit]; + // fixed (PipelineStageFlags* pPipelineStageFlags = pipelineStageFlags) + // { + // ReadOnlySpan 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 + // }; + + // // Console.WriteLine("Import 18"); + // _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, new Silk.NET.Vulkan.Fence(UIntPtr.Zero)).ThrowOnError(); + // } // Console.WriteLine("Import 19"); return new VulkanExportedImage { ImageExportedSemaphoreFd = imageExportedSemaphoreFd, ImageRenderedSemaphoreFd = imageRenderedSemaphoreFd, + FenceFd = fenceFd, OrignalFd = imageFdDuplicate, ImageWidth = width, ImageHeight = height, @@ -251,6 +256,8 @@ class VulkanRenderer _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); + if (image.FenceFd is not null) + close(image.FenceFd.Value); close(image.OrignalFd); close(image.ImageFd); close(image.ImageExportedSemaphoreFd); @@ -455,9 +462,9 @@ public partial class CameraView : Control if (!_initialized) return; int i = 0; - _updateTimer = new System.Timers.Timer(100) + _updateTimer = new System.Timers.Timer(105) { - AutoReset = true + AutoReset = false }; _updateTimer.Elapsed += async (_, _) => { @@ -471,8 +478,16 @@ public partial class CameraView : Control if (result is not null) { var (frameBuffer, streamConfiguration) = result.Value; - if (frameBuffer.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess) - Update(frameBuffer, streamConfiguration); + // if (frameBuffer.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess) + Update(frameBuffer, streamConfiguration); + _updateTimer.Start(); + // else + // { + // var fenceFd = frameBuffer.ReleaseFence()?.Fd?.Release(); + // Console.WriteLine($"Buffer returned not success result, closing fd {fenceFd}"); + // if (fenceFd is not null) + // GSS2.Core.Hardware.CameraService.LibC.close(fenceFd.Value); + // } } }); }; @@ -591,13 +606,14 @@ public partial class CameraView : Control _renerer is not null) { // Console.WriteLine("Update 2"); - // var fenceFd = frameBuffer.ReleaseFence()?.Fd?.Release(); + var fenceFd = frameBuffer.ReleaseFence()?.Fd?.Release(); + Console.WriteLine($"Released fd {fenceFd}"); 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, + fenceFd, _initializationFrame); // _renerer.ClearResource(image); // Console.WriteLine("Update 3"); diff --git a/GSS2.Vulkan/VulkanExtensions.cs b/GSS2.Vulkan/VulkanExtensions.cs index 72f41e1..262441d 100644 --- a/GSS2.Vulkan/VulkanExtensions.cs +++ b/GSS2.Vulkan/VulkanExtensions.cs @@ -11,7 +11,7 @@ public static class VulkanExtensions { public static void ThrowOnError(this Result result, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - // Console.WriteLine($"{callerFilePath} {callerMemberName} {callerLineNumber} {result}"); + Console.WriteLine($"{callerFilePath} {callerMemberName} {callerLineNumber} {result}"); if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\""); }