feat: CameraView + CameraService fences

This commit is contained in:
2026-01-29 12:10:59 +03:00
parent 3283d73b3b
commit dc3c195c21
4 changed files with 136 additions and 104 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-20260129-5" /> <PackageReference Include="LibCameraSharp" Version="0.5.2-20260129-7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+47 -31
View File
@@ -9,13 +9,15 @@ namespace GSS2.Core.Hardware;
public class CameraService : IDisposable public class CameraService : IDisposable
{ {
private static class LibC public static class LibC
{ {
public const int PROT_READ = 0x01; public const int PROT_READ = 0x01;
public const int MAP_SHARED = 0x0001; 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 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 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 private sealed class MappedBuffer
{ {
@@ -677,53 +679,67 @@ public class CameraService : IDisposable
var buffer = buffers.Skip(bufferId % buffers.Count()).First(); var buffer = buffers.Skip(bufferId % buffers.Count()).First();
var request = _camera.CreateRequest(checked((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())); 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) 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"); throw new Exception("Error while request create");
}
// _logger.LogDebug("Request(s) created"); // _logger.LogDebug("Request(s) created");
effectiveCancellationToken.ThrowIfCancellationRequested(); effectiveCancellationToken.ThrowIfCancellationRequested();
var cts = new TaskCompletionSource(); // var cts = new TaskCompletionSource();
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e) // void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
{ // {
if (request.Handle != e.Request.Handle) // if (request.Handle != e.Request.Handle)
return; // return;
var buffer = e.Request.FindBuffer(_viewFinderStream); // var buffer = e.Request.FindBuffer(_viewFinderStream);
if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError || // if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError ||
buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup) // buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup)
{ // {
// _logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status); // // _logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status);
e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers); // e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers);
var result = _camera.QueueRequest(e.Request); // var result = _camera.QueueRequest(e.Request);
if (result >= 0) // if (result >= 0)
return; // 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 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: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
// _logger.LogDebug("Request buffer planes: "); // // _logger.LogDebug("Request buffer planes: ");
// foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) // // foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate())
// _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed); // // _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
// foreach (var (i, plane) in buffer.Planes.Enumerate()) // // 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("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); result = _camera.QueueRequest(request);
if (result < 0) if (result < 0)
{ {
_camera.RequestCompleted -= RequestCompleted; // _camera.RequestCompleted -= RequestCompleted;
throw new Exception("Error while request queue"); throw new Exception("Error while request queue");
} }
// _logger.LogDebug("Request queued"); // _logger.LogDebug("Request queued");
await cts.Task.WaitAsync(effectiveCancellationToken); // await cts.Task.WaitAsync(effectiveCancellationToken);
_camera.RequestCompleted -= RequestCompleted; // _camera.RequestCompleted -= RequestCompleted;
// _logger.LogDebug("Request complited"); // _logger.LogDebug("Request complited");
return new(buffer, _viewFinderStreamConfiguration); return new(buffer, _viewFinderStreamConfiguration);
+87 -71
View File
@@ -30,6 +30,7 @@ class VulkanExportedImage
{ {
public required int ImageExportedSemaphoreFd { get; init; } public required int ImageExportedSemaphoreFd { get; init; }
public required int ImageRenderedSemaphoreFd { get; init; } public required int ImageRenderedSemaphoreFd { get; init; }
public required int? FenceFd { get; init; }
public required int OrignalFd { get; init; } public required int OrignalFd { get; init; }
public required int ImageFd { get; init; } public required int ImageFd { get; init; }
public required int ImageWidth { 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(); _context.KhrExternalSemaphoreFd.GetSemaphoreF(_context.Device, in imageRenderedSemaphoreGetFdInfoKHR, out var imageRenderedSemaphoreFd).ThrowOnError();
// Silk.NET.Vulkan.Fence imageCapturedFence = new Silk.NET.Vulkan.Fence(null); Silk.NET.Vulkan.Fence imageCapturedFence = new Silk.NET.Vulkan.Fence(null);
// if (fenceFd is not null) if (fenceFd is not null)
// { {
// var importSemaphoreFdInfoKHR = new ImportFenceFdInfoKHR() var importSemaphoreFdInfoKHR = new ImportFenceFdInfoKHR()
// { {
// SType = StructureType.ImportSemaphoreFDInfoKhr, SType = StructureType.ImportSemaphoreFDInfoKhr,
// Fd = fenceFd.Value, Fd = fenceFd.Value,
// Flags = FenceImportFlags.TemporaryBit, Flags = FenceImportFlags.TemporaryBit,
// HandleType = ExternalFenceHandleTypeFlags.SyncFDBit HandleType = ExternalFenceHandleTypeFlags.OpaqueFDBit
// }; };
// _context.KhrExternalFenceFd.ImportFenceF(_context.Device, &importSemaphoreFdInfoKHR); _context.KhrExternalFenceFd.ImportFenceF(_context.Device, &importSemaphoreFdInfoKHR);
// } }
// Console.WriteLine("Import 9"); // Console.WriteLine("Import 9");
var (importImage, importMemoryReqirements, importMemory) = ImportImage(imageFdDuplicate, width, height, stride); 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(); _context.KhrExternalMemoryFd.GetMemoryF(_context.Device, &memoryGetFdInfoKHR, out var vulkanImageFd).ThrowOnError();
// Console.WriteLine("Import 17"); // Console.WriteLine("Import 17");
// var waitSemaphoreIndex = 0; var waitSemaphoreIndex = 0;
// Silk.NET.Vulkan.Semaphore[] waitSemaphoresArray = new Silk.NET.Vulkan.Semaphore[ Silk.NET.Vulkan.Semaphore[] waitSemaphoresArray = new Silk.NET.Vulkan.Semaphore[
// !initializationFrame ? 1 : 0 !initializationFrame ? 1 : 0
// ]; ];
// PipelineStageFlags[] pipelineStageFlagsArray = new PipelineStageFlags[waitSemaphoresArray.Length]; PipelineStageFlags[] pipelineStageFlagsArray = new PipelineStageFlags[waitSemaphoresArray.Length];
// if (!initializationFrame) 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 = waitSemaphoresArray[waitSemaphoreIndex] = imageRenderedSemaphore;
[ pipelineStageFlagsArray[waitSemaphoreIndex++] = PipelineStageFlags.AllGraphicsBit;
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();
} }
var waitSemaphoresSpan = new ReadOnlySpan<Silk.NET.Vulkan.Semaphore>(waitSemaphoresArray);
var pipelineStageFlagsSpan = new ReadOnlySpan<PipelineStageFlags>(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 = [PipelineStageFlags.AllGraphicsBit];
// fixed (PipelineStageFlags* pPipelineStageFlags = pipelineStageFlags)
// {
// 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
// };
// // Console.WriteLine("Import 18");
// _context.Api.QueueSubmit(_context.Queue, 1, &submitInfo, new Silk.NET.Vulkan.Fence(UIntPtr.Zero)).ThrowOnError();
// }
// Console.WriteLine("Import 19"); // Console.WriteLine("Import 19");
return new VulkanExportedImage return new VulkanExportedImage
{ {
ImageExportedSemaphoreFd = imageExportedSemaphoreFd, ImageExportedSemaphoreFd = imageExportedSemaphoreFd,
ImageRenderedSemaphoreFd = imageRenderedSemaphoreFd, ImageRenderedSemaphoreFd = imageRenderedSemaphoreFd,
FenceFd = fenceFd,
OrignalFd = imageFdDuplicate, OrignalFd = imageFdDuplicate,
ImageWidth = width, ImageWidth = width,
ImageHeight = height, ImageHeight = height,
@@ -251,6 +256,8 @@ class VulkanRenderer
_context.Api.FreeMemory(_context.Device, image.Resources.ImportMemory, null); _context.Api.FreeMemory(_context.Device, image.Resources.ImportMemory, null);
_context.Api.DestroyImage(_context.Device, image.Resources.ExportImage, null); _context.Api.DestroyImage(_context.Device, image.Resources.ExportImage, null);
_context.Api.FreeMemory(_context.Device, image.Resources.ExportMemory, 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.OrignalFd);
close(image.ImageFd); close(image.ImageFd);
close(image.ImageExportedSemaphoreFd); close(image.ImageExportedSemaphoreFd);
@@ -455,9 +462,9 @@ public partial class CameraView : Control
if (!_initialized) if (!_initialized)
return; return;
int i = 0; int i = 0;
_updateTimer = new System.Timers.Timer(100) _updateTimer = new System.Timers.Timer(105)
{ {
AutoReset = true AutoReset = false
}; };
_updateTimer.Elapsed += async (_, _) => _updateTimer.Elapsed += async (_, _) =>
{ {
@@ -471,8 +478,16 @@ public partial class CameraView : Control
if (result is not null) if (result is not null)
{ {
var (frameBuffer, streamConfiguration) = result.Value; var (frameBuffer, streamConfiguration) = result.Value;
if (frameBuffer.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess) // if (frameBuffer.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess)
Update(frameBuffer, streamConfiguration); 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) _renerer is not null)
{ {
// Console.WriteLine("Update 2"); // 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( var image = _renerer.Import(
frameBuffer.Planes.First().Fd.Get(), frameBuffer.Planes.First().Fd.Get(),
checked((int)streamConfiguration.Size.Width), checked((int)streamConfiguration.Size.Width),
checked((int)streamConfiguration.Size.Height), checked((int)streamConfiguration.Size.Height),
checked((int)streamConfiguration.Stride), checked((int)streamConfiguration.Stride),
null, // fenceFd, fenceFd,
_initializationFrame); _initializationFrame);
// _renerer.ClearResource(image); // _renerer.ClearResource(image);
// Console.WriteLine("Update 3"); // Console.WriteLine("Update 3");
+1 -1
View File
@@ -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) 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) if (result != Result.Success)
throw new Exception($"Unexpected API error \"{result}\""); throw new Exception($"Unexpected API error \"{result}\"");
} }